Skip to content

Commit 9a971ef

Browse files
committed
Some light experiments
1 parent 27f28b3 commit 9a971ef

12 files changed

Lines changed: 286 additions & 60 deletions

File tree

scenes/many_point_lights_scene.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
{
22
"technique": {
33
"type": "path",
4-
"max_depth": 64,
5-
"use_uniform_light_selector": false
4+
"max_depth": 64
65
},
76
"camera": {
87
"type": "perspective",

src/artic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ set(ARTIC_EXTRA_SRC
9393
${CMAKE_CURRENT_SOURCE_DIR}/impl/emitter.art
9494
${CMAKE_CURRENT_SOURCE_DIR}/impl/fresnel.art
9595
${CMAKE_CURRENT_SOURCE_DIR}/impl/interval.art
96+
${CMAKE_CURRENT_SOURCE_DIR}/impl/light_hierarchy.art
9697
${CMAKE_CURRENT_SOURCE_DIR}/impl/light_selector.art
9798
${CMAKE_CURRENT_SOURCE_DIR}/impl/math.art
9899
${CMAKE_CURRENT_SOURCE_DIR}/impl/microfacet.art

src/artic/impl/light_hierarchy.art

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Based on:
2+
// Moreau, P., Clarberg, P. (2019). Importance Sampling of Many Lights on the GPU.
3+
// In: Haines, E., Akenine-Möller, T. (eds) Ray Tracing Gems.
4+
// Apress, Berkeley, CA. https://doi.org/10.1007/978-1-4842-4427-2_18
5+
6+
mod light_hierarchy {
7+
use super as all;
8+
use super::math_builtins;
9+
10+
type Vec3 = all::Vec3;
11+
type RndState = all::RndState;
12+
type DeviceBuffer = all::DeviceBuffer;
13+
14+
struct LightHierarchyEntry {
15+
pos: Vec3,
16+
dir: Vec3,
17+
flux: f32,
18+
id: i32,
19+
leaf: bool
20+
}
21+
22+
fn @load_entry(id: i32, data: all::DeviceBuffer) -> LightHierarchyEntry {
23+
let e1 = data.load_vec4(id * 8 + 0);
24+
let e2 = data.load_vec4(id * 8 + 4);
25+
26+
let index = all::bitcast[i32](e2.w);
27+
LightHierarchyEntry {
28+
pos = all::make_vec3(e1.x, e1.y, e1.z),
29+
dir = all::make_vec3(e2.x, e2.y, e2.z),
30+
flux = e1.w,
31+
id = all::select(index < 0, -index - 1, index),
32+
leaf = index >= 0
33+
}
34+
}
35+
36+
fn @get_entry_cost(entry: LightHierarchyEntry, pos: Vec3) -> f32 {
37+
// TODO: Normal direction?
38+
let cdir = all::vec3_sub(entry.pos, pos);
39+
let dist2 = all::vec3_len2(cdir);
40+
let cos_d = math_builtins::fabs(all::vec3_dot(entry.dir, cdir)); // Has dist applied
41+
42+
entry.flux * cos_d / math_builtins::fmax(0.0001:f32, dist2)
43+
}
44+
45+
fn @get_left_prop(left: LightHierarchyEntry, right: LightHierarchyEntry, pos: Vec3) -> f32 {
46+
let cl = get_entry_cost(left, pos);
47+
let cr = get_entry_cost(right, pos);
48+
cl / (cl + cr)
49+
}
50+
51+
fn @random_select_left(rnd: &mut RndState, left: LightHierarchyEntry, right: LightHierarchyEntry, pos: Vec3) -> (bool, f32) {
52+
let prop = get_left_prop(left, right, pos);
53+
(all::randf(rnd) < prop, prop)
54+
}
55+
56+
fn @sample_light_id(rnd: &mut RndState, pos: Vec3, data: DeviceBuffer) -> (i32, f32) {
57+
let mut pdf = 1:f32;
58+
let mut entry = load_entry(0, data);
59+
while !entry.leaf {
60+
let (is_left, prop) = random_select_left(rnd, load_entry(entry.id, data), load_entry(entry.id+1, data), pos);
61+
62+
entry = load_entry(all::select(is_left, entry.id, entry.id+1), data);
63+
pdf *= all::select(is_left, prop, 1-prop);
64+
}
65+
66+
(entry.id, pdf)
67+
}
68+
69+
fn @compute_pdf(light: all::Light, pos: Vec3, codes: DeviceBuffer, data: DeviceBuffer) -> f32 {
70+
let mut code = all::bitcast[u32](codes.load_i32(light.id));
71+
let mut pdf = 1:f32;
72+
let mut entry = load_entry(0, data);
73+
while !entry.leaf {
74+
let left = load_entry(entry.id, data);
75+
let right = load_entry(entry.id+1, data);
76+
let prop = get_left_prop(left, right, pos);
77+
78+
if (code & 0x1) == 0 {
79+
entry = left;
80+
pdf *= prop;
81+
} else {
82+
entry = right;
83+
pdf *= 1 - prop;
84+
}
85+
86+
code >>= 1;
87+
}
88+
89+
all::select(entry.id == light.id, pdf, 0:f32)
90+
}
91+
}
92+
93+
struct LightHierarchy {
94+
sample: fn (&mut RndState, Vec3) -> (Light, f32),
95+
pdf: fn (Light, Vec3) -> f32
96+
}
97+
98+
fn @make_light_hierarchy(finite_lights: LightTable, full: DeviceBuffer) -> LightHierarchy {
99+
let codes = full;
100+
let data = shift_device_buffer(round_up(finite_lights.count, 4), 0, full);
101+
102+
LightHierarchy {
103+
sample = @|rnd, pos| {
104+
let (id, pdf) = light_hierarchy::sample_light_id(rnd, pos, data);
105+
(finite_lights.get(id), pdf)
106+
},
107+
pdf = @|light, pos| light_hierarchy::compute_pdf(light, pos, codes, data)
108+
}
109+
}

src/artic/impl/light_selector.art

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
// Result of sampling a direction
22
struct LightSelector {
33
count: i32,
4-
sample: fn (&mut RndState) -> (Light, f32),
5-
pdf: fn (Light) -> f32,
4+
sample: fn (&mut RndState, Vec3) -> (Light, f32),
5+
pdf: fn (Light, Vec3) -> f32,
66
infinites: LightTable,
77
finites: LightTable
88
}
99

1010
fn @make_null_light_selector() = LightSelector {
1111
count = 0,
12-
sample = @|_| (make_null_light(0), 1),
13-
pdf = @|_| 1,
12+
sample = @|_,_| (make_null_light(0), 1),
13+
pdf = @|_,_| 1,
1414
infinites = make_empty_light_table(),
1515
finites = make_empty_light_table()
1616
};
@@ -25,21 +25,20 @@ fn @pick_light_id(rnd: &mut RndState, num_lights: i32) {
2525
}
2626

2727
fn @make_uniform_light_selector(infinite_lights: LightTable, finite_lights: LightTable) -> LightSelector {
28-
let num_lights = infinite_lights.count + finite_lights.count;
29-
let pdf_inf_lights = if infinite_lights.count == 0 { 1 } else { 1 / (infinite_lights.count as f32) };
30-
let pdf_fin_lights = if finite_lights.count == 0 { 1 } else { 1 / (finite_lights.count as f32) };
28+
let num_lights = infinite_lights.count + finite_lights.count;
29+
let pdf_lights = if num_lights == 0 { 1 } else { 1 / (num_lights as f32) };
3130

3231
LightSelector {
3332
count = num_lights,
34-
sample = @|rnd| {
33+
sample = @|rnd,_| {
3534
let id = pick_light_id(rnd, num_lights);
3635
if id < infinite_lights.count {
37-
(infinite_lights.get(id), pdf_inf_lights)
36+
(infinite_lights.get(id), pdf_lights)
3837
} else {
39-
(finite_lights.get(id - infinite_lights.count), pdf_fin_lights)
38+
(finite_lights.get(id - infinite_lights.count), pdf_lights)
4039
}
4140
},
42-
pdf = @|light| select(light.infinite, pdf_inf_lights, pdf_fin_lights),
41+
pdf = @|_,_| pdf_lights,
4342
infinites = infinite_lights,
4443
finites = finite_lights
4544
}
@@ -49,8 +48,8 @@ fn @make_cdf_light_selector(infinite_lights: LightTable, finite_lights: LightTab
4948
if infinite_lights.count == 0 {
5049
LightSelector {
5150
count = sampler.func_size,
52-
sample = @|rnd| { let s = sampler.sample_discrete(randf(rnd)); (finite_lights.get(s.off), s.pdf) },
53-
pdf = @|light| sampler.pdf_discrete(light.id).pdf,
51+
sample = @|rnd,_| { let s = sampler.sample_discrete(randf(rnd)); (finite_lights.get(s.off), s.pdf) },
52+
pdf = @|light,_| sampler.pdf_discrete(light.id).pdf,
5453
infinites = infinite_lights,
5554
finites = finite_lights
5655
}
@@ -59,24 +58,54 @@ fn @make_cdf_light_selector(infinite_lights: LightTable, finite_lights: LightTab
5958
let infinite_ratio = 0.5:f32;
6059
LightSelector {
6160
count = infinite_lights.count + sampler.func_size,
62-
sample = @|rnd| {
61+
sample = @|rnd,_| {
6362
let q = randf(rnd);
6463
if q < infinite_ratio {
6564
let id = pick_light_id(rnd, infinite_lights.count);
66-
(infinite_lights.get(id), pdf_infinite_lights)
65+
(infinite_lights.get(id), pdf_infinite_lights * infinite_ratio)
6766
} else {
6867
let s = sampler.sample_discrete(randf(rnd));
69-
(finite_lights.get(s.off), s.pdf)
68+
(finite_lights.get(s.off), s.pdf * (1-infinite_ratio))
7069
}
7170
},
72-
pdf = @|light| if light.infinite { pdf_infinite_lights } else { sampler.pdf_discrete(light.id).pdf },
71+
pdf = @|light,_| if light.infinite { pdf_infinite_lights * infinite_ratio } else { sampler.pdf_discrete(light.id).pdf * (1-infinite_ratio) },
7372
infinites = infinite_lights,
7473
finites = finite_lights
7574
}
7675
}
7776
}
7877

79-
fn @make_hierarchy_light_selector(infinite_lights: LightTable, finite_lights: LightTable, _data: DeviceBuffer) -> LightSelector {
80-
make_uniform_light_selector(infinite_lights, finite_lights)
81-
// TODO
78+
fn @make_hierarchy_light_selector(infinite_lights: LightTable, finite_lights: LightTable, data: DeviceBuffer) -> LightSelector {
79+
if finite_lights.count == 0 {
80+
make_uniform_light_selector(infinite_lights, finite_lights)
81+
} else if infinite_lights.count == 0 {
82+
let hierarchy = make_light_hierarchy(finite_lights, data);
83+
LightSelector {
84+
count = finite_lights.count,
85+
sample = hierarchy.sample,
86+
pdf = hierarchy.pdf,
87+
infinites = infinite_lights,
88+
finites = finite_lights
89+
}
90+
} else {
91+
let pdf_infinite_lights = 1 / (infinite_lights.count as f32);
92+
let infinite_ratio = 0.5:f32;
93+
let hierarchy = make_light_hierarchy(finite_lights, data);
94+
LightSelector {
95+
count = finite_lights.count,
96+
sample = @|rnd, from_pos| {
97+
let q = randf(rnd);
98+
if q < infinite_ratio {
99+
let id = pick_light_id(rnd, infinite_lights.count);
100+
(infinite_lights.get(id), pdf_infinite_lights * infinite_ratio)
101+
} else {
102+
let (l, pdf) = hierarchy.sample(rnd, from_pos);
103+
(l, pdf * (1-infinite_ratio))
104+
}
105+
},
106+
pdf = @|light, from_pos| if light.infinite { pdf_infinite_lights * infinite_ratio } else { hierarchy.pdf(light, from_pos) * (1-infinite_ratio) },
107+
infinites = infinite_lights,
108+
finites = finite_lights
109+
}
110+
}
82111
}

src/artic/impl/technique/pathtracer.art

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fn @make_path_renderer(max_path_len: i32, light_selector: LightSelector, aovs: A
6666
return(ShadowRay::None)
6767
}
6868

69-
let (light, light_select_pdf) = light_selector.sample(rnd);
69+
let (light, light_select_pdf) = light_selector.sample(rnd, ctx.surf.point);
7070

7171
let sample_direct = light.sample_direct;
7272
let light_sample = @sample_direct(rnd, ctx.surf);
@@ -120,7 +120,7 @@ fn @make_path_renderer(max_path_len: i32, light_selector: LightSelector, aovs: A
120120
if dot > flt_eps { // Only contribute proper aligned directions
121121
let emit = mat.emission(ctx);
122122
let pdf_s = emit.pdf.as_solid(dot, ctx.hit.distance * ctx.hit.distance);
123-
let mis = 1 / (1 + pt.inv_pdf * light_selector.pdf(mat.light) * pdf_s);
123+
let mis = 1 / (1 + pt.inv_pdf * light_selector.pdf(mat.light, ctx.ray.org) * pdf_s);
124124
let contrib = handle_color(color_mulf(color_mul(pt.contrib, emit.intensity), mis));
125125

126126
aov_di.splat(ctx.pixel, contrib);
@@ -148,7 +148,7 @@ fn @make_path_renderer(max_path_len: i32, light_selector: LightSelector, aovs: A
148148
let emit = light.emission(make_miss_shading_context(pixel, ray));
149149
let pdf = light.pdf_direct(ray, make_invalid_surface_element());
150150
let pdf_s = pdf.as_solid(1, 1/* We assume infinite lights are always given in solid angle measure */);
151-
let mis = 1 / (1 + pt.inv_pdf * light_selector.pdf(light) * pdf_s);
151+
let mis = 1 / (1 + pt.inv_pdf * light_selector.pdf(light, ray.org) * pdf_s);
152152
color = color_add(color, handle_color(color_mulf(color_mul(pt.contrib, emit), mis)));
153153
}
154154
}

src/artic/impl/technique/photonmapper.art

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ fn @make_ppm_light_emitter(light_selector: LightSelector, iter: i32) -> RayEmitt
151151
hash = fnv_hash(hash, y as u32);
152152
let mut rnd = hash /*as RndState*/;
153153

154-
let (light, light_pdf) = light_selector.sample(&mut rnd);
154+
let (light, light_pdf) = light_selector.sample(&mut rnd, vec3_expand(0));
155155
let sample_emission = light.sample_emission;
156156
let light_sample = @sample_emission(rnd);
157157

src/artic/impl/technique/volpathtracer.art

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ fn @make_volume_path_renderer(max_path_len: i32, light_selector: LightSelector,
5959
return(ShadowRay::None)
6060
}
6161

62-
let (light, light_select_pdf) = light_selector.sample(rnd);
62+
let (light, light_select_pdf) = light_selector.sample(rnd, ctx.surf.point);
6363

6464
let sample_direct = light.sample_direct;
6565
let light_sample = @sample_direct(rnd, ctx.surf);
@@ -124,7 +124,7 @@ fn @make_volume_path_renderer(max_path_len: i32, light_selector: LightSelector,
124124
let emit = mat.emission(ctx);
125125
let inv_pdf = math_builtins::fmax[f32](0/*Ignore medium interactions*/, pt.inv_pdf);
126126
let pdf_s = emit.pdf.as_solid(dot, ctx.hit.distance * ctx.hit.distance);
127-
let mis = 1 / (1 + inv_pdf * light_selector.pdf(mat.light) * pdf_s);
127+
let mis = 1 / (1 + inv_pdf * light_selector.pdf(mat.light, ctx.ray.org) * pdf_s);
128128
let vol = medium.eval(ctx.ray.org, ctx.surf.point);
129129
let contrib = handle_color(color_mulf(color_mul(pt.contrib, color_mul(emit.intensity, vol)), mis));
130130

@@ -152,7 +152,7 @@ fn @make_volume_path_renderer(max_path_len: i32, light_selector: LightSelector,
152152
let emit = light.emission(make_miss_shading_context(pixel, ray));
153153
let pdf = light.pdf_direct(ray, make_invalid_surface_element());
154154
let pdf_s = pdf.as_solid(1, 1/* We assume infinite lights are always given in solid angle measure */);
155-
let mis = 1 / (1 + math_builtins::fmax[f32](0/*Ignore medium interactions*/, pt.inv_pdf) * light_selector.pdf(light) * pdf_s);
155+
let mis = 1 / (1 + math_builtins::fmax[f32](0/*Ignore medium interactions*/, pt.inv_pdf) * light_selector.pdf(light, ray.org) * pdf_s);
156156
let vol = medium.eval_inf(ray.org, ray.dir);
157157
color = color_add(color, handle_color(color_mulf(color_mul(pt.contrib, color_mul(emit, vol)), mis)));
158158
}

src/artic/render/buffer.art

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,75 @@ struct DeviceBuffer {
3939
has_alignment: bool // True if vec2, vec3, int2 and int3 are expected to be like vec4 or int4 in memory
4040
}
4141

42+
fn @make_null_device_buffer() = DeviceBuffer {
43+
add_atomic_i32 = @|_, _| 0:i32,
44+
add_atomic_f32 = @|_, _| 0:f32,
45+
min_atomic_i32 = @|_, _| 0:i32,
46+
max_atomic_i32 = @|_, _| 0:i32,
47+
load_i32_host = @|_| 0:i32,
48+
store_i32_host = @|_, _| {},
49+
copy_from_host = @|_, _, _| {},
50+
copy_to_host = @|_, _, _| {},
51+
load_i32 = @|_| 0:i32,
52+
load_f32 = @|_| 0:f32,
53+
load_vec2 = @|_| make_vec2(0,0),
54+
load_vec3 = @|_| make_vec3(0,0,0),
55+
load_vec4 = @|_| make_vec4(0,0,0,0),
56+
load_int2 = @|_| (0,0),
57+
load_int3 = @|_| (0,0,0),
58+
load_int4 = @|_| (0,0,0,0),
59+
load_mat3x3 = @|_| mat3x3_identity(),
60+
load_mat3x4 = @|_| mat3x4_identity(),
61+
load_mat4x4 = @|_| mat4x4_identity(),
62+
store_i32 = @|_, _| {},
63+
store_f32 = @|_, _| {},
64+
store_vec2 = @|_, _| {},
65+
store_vec3 = @|_, _| {},
66+
store_vec4 = @|_, _| {},
67+
store_int2 = @|_, _, _| {},
68+
store_int3 = @|_, _, _, _| {},
69+
store_int4 = @|_, _, _, _, _| {},
70+
store_mat3x3 = @|_, _| {},
71+
store_mat3x4 = @|_, _| {},
72+
store_mat4x4 = @|_, _| {},
73+
count = 0,
74+
has_alignment = true
75+
};
76+
77+
fn @shift_device_buffer(off: i32, count: i32, buffer: DeviceBuffer) = DeviceBuffer {
78+
add_atomic_i32 = @|id, v| buffer.add_atomic_i32(id + off, v),
79+
add_atomic_f32 = @|id, v| buffer.add_atomic_f32(id + off, v),
80+
min_atomic_i32 = @|id, v| buffer.min_atomic_i32(id + off, v),
81+
max_atomic_i32 = @|id, v| buffer.max_atomic_i32(id + off, v),
82+
load_i32_host = @|id| buffer.load_i32_host(id + off),
83+
store_i32_host = @|id, v| buffer.store_i32_host(id + off, v),
84+
copy_from_host = @|s, e, ptr| buffer.copy_from_host(s + off, e + off, ptr),
85+
copy_to_host = @|s, e, ptr| buffer.copy_to_host(s + off, e + off, ptr),
86+
load_i32 = @|id| buffer.load_i32(id + off),
87+
load_f32 = @|id| buffer.load_f32(id + off),
88+
load_vec2 = @|id| buffer.load_vec2(id + off),
89+
load_vec3 = @|id| buffer.load_vec3(id + off),
90+
load_vec4 = @|id| buffer.load_vec4(id + off),
91+
load_int2 = @|id| buffer.load_int2(id + off),
92+
load_int3 = @|id| buffer.load_int3(id + off),
93+
load_int4 = @|id| buffer.load_int4(id + off),
94+
load_mat3x3 = @|id| buffer.load_mat3x3(id + off),
95+
load_mat3x4 = @|id| buffer.load_mat3x4(id + off),
96+
load_mat4x4 = @|id| buffer.load_mat4x4(id + off),
97+
store_i32 = @|id, v| buffer.store_i32(id + off, v),
98+
store_f32 = @|id, v| buffer.store_f32(id + off, v),
99+
store_vec2 = @|id, v| buffer.store_vec2(id + off, v),
100+
store_vec3 = @|id, v| buffer.store_vec3(id + off, v),
101+
store_vec4 = @|id, v| buffer.store_vec4(id + off, v),
102+
store_int2 = @|id, v1, v2| buffer.store_int2(id + off, v1, v2),
103+
store_int3 = @|id, v1, v2, v3| buffer.store_int3(id + off, v1, v2, v3),
104+
store_int4 = @|id, v1, v2, v3, v4| buffer.store_int4(id + off, v1, v2, v3, v4),
105+
store_mat3x3 = @|id, v| buffer.store_mat3x3(id + off, v),
106+
store_mat3x4 = @|id, v| buffer.store_mat3x4(id + off, v),
107+
store_mat4x4 = @|id, v| buffer.store_mat4x4(id + off, v),
108+
count = count,
109+
has_alignment = buffer.has_alignment
110+
};
111+
42112
// Unsized device buffer accessor
43113
type DeviceBufferAccessor = fn (&[u8]) -> DeviceBuffer;

0 commit comments

Comments
 (0)