@plasius/gpu-lighting 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +54 -0
- package/dist/index.cjs +105 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -1
- package/dist/techniques/techniques/hybrid/direct-lighting.job.wgsl +69 -2
- package/dist/techniques/techniques/hybrid/final-gather.job.wgsl +62 -2
- package/dist/techniques/techniques/hybrid/prelude.wgsl +140 -0
- package/dist/techniques/techniques/hybrid/radiance-cache.job.wgsl +57 -2
- package/dist/techniques/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
- package/dist/techniques/techniques/hybrid/screen-trace.job.wgsl +219 -2
- package/dist/techniques/techniques/pathtracer/accumulate.job.wgsl +64 -2
- package/dist/techniques/techniques/pathtracer/denoise.job.wgsl +125 -2
- package/dist/techniques/techniques/pathtracer/pathtrace.job.wgsl +429 -2
- package/dist/techniques/techniques/pathtracer/prelude.wgsl +260 -0
- package/package.json +1 -1
- package/src/index.js +114 -0
- package/src/techniques/hybrid/direct-lighting.job.wgsl +69 -2
- package/src/techniques/hybrid/final-gather.job.wgsl +62 -2
- package/src/techniques/hybrid/prelude.wgsl +140 -0
- package/src/techniques/hybrid/radiance-cache.job.wgsl +57 -2
- package/src/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
- package/src/techniques/hybrid/screen-trace.job.wgsl +219 -2
- package/src/techniques/pathtracer/accumulate.job.wgsl +64 -2
- package/src/techniques/pathtracer/denoise.job.wgsl +125 -2
- package/src/techniques/pathtracer/pathtrace.job.wgsl +429 -2
- package/src/techniques/pathtracer/prelude.wgsl +260 -0
|
@@ -1,3 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@group(0) @binding(0) var<uniform> pathTracerParams: PathTracerParams;
|
|
2
|
+
@group(0) @binding(1) var<storage, read> pathSampleBuffer: array<PathSamplePixel>;
|
|
3
|
+
@group(0) @binding(2) var<storage, read_write> pathAccumulationBuffer: array<PathAccumulationPixel>;
|
|
4
|
+
|
|
5
|
+
fn accumulation_index(pixel: vec2<u32>) -> u32 {
|
|
6
|
+
return pixel.y * max(pathTracerParams.image_width, 1u) + pixel.x;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
fn reset_accumulation(sample: PathSamplePixel) -> PathAccumulationPixel {
|
|
10
|
+
let sample_luminance = luminance(sample.radiance_opacity.xyz);
|
|
11
|
+
return PathAccumulationPixel(
|
|
12
|
+
vec4<f32>(sample.radiance_opacity.xyz, max(sample.albedo_sample_count.w, 1.0)),
|
|
13
|
+
vec4<f32>(sample_luminance, 0.0, 0.0, f32(pathTracerParams.frame_index))
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@compute @workgroup_size(8, 8, 1)
|
|
18
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
19
|
+
if (
|
|
20
|
+
global_id.x >= pathTracerParams.image_width ||
|
|
21
|
+
global_id.y >= pathTracerParams.image_height
|
|
22
|
+
) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let index = accumulation_index(global_id.xy);
|
|
27
|
+
let sample = pathSampleBuffer[index];
|
|
28
|
+
let history = pathAccumulationBuffer[index];
|
|
29
|
+
let sample_luminance = luminance(sample.radiance_opacity.xyz);
|
|
30
|
+
let current_samples = max(sample.albedo_sample_count.w, 1.0);
|
|
31
|
+
let history_samples = max(history.integrated_radiance.w, 0.0);
|
|
32
|
+
let should_reset =
|
|
33
|
+
pathTracerParams.accumulation_reset != 0u ||
|
|
34
|
+
history_samples <= 0.0 ||
|
|
35
|
+
history.moments.w <= 0.0;
|
|
36
|
+
|
|
37
|
+
if (should_reset) {
|
|
38
|
+
pathAccumulationBuffer[index] = reset_accumulation(sample);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let combined_samples = min(history_samples + current_samples, 4096.0);
|
|
43
|
+
let progressive_weight = current_samples / max(combined_samples, 1.0);
|
|
44
|
+
let minimum_refresh = 1.0 - clamp(pathTracerParams.history_blend, 0.0, 0.98);
|
|
45
|
+
let blend = clamp(max(progressive_weight, minimum_refresh), 0.02, 1.0);
|
|
46
|
+
let integrated =
|
|
47
|
+
history.integrated_radiance.xyz * (1.0 - blend) +
|
|
48
|
+
sample.radiance_opacity.xyz * blend;
|
|
49
|
+
let mean_luminance =
|
|
50
|
+
history.moments.x * (1.0 - blend) +
|
|
51
|
+
sample_luminance * blend;
|
|
52
|
+
let variance_sample = sample_luminance - mean_luminance;
|
|
53
|
+
let variance =
|
|
54
|
+
max(0.0, history.moments.y * (1.0 - blend) + variance_sample * variance_sample * blend);
|
|
55
|
+
|
|
56
|
+
pathAccumulationBuffer[index] = PathAccumulationPixel(
|
|
57
|
+
vec4<f32>(integrated, combined_samples),
|
|
58
|
+
vec4<f32>(
|
|
59
|
+
mean_luminance,
|
|
60
|
+
variance,
|
|
61
|
+
1.0 - blend,
|
|
62
|
+
f32(pathTracerParams.frame_index)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
3
65
|
}
|
|
@@ -1,3 +1,126 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@group(0) @binding(0) var<uniform> pathTracerParams: PathTracerParams;
|
|
2
|
+
@group(0) @binding(1) var<storage, read> pathAccumulationBuffer: array<PathAccumulationPixel>;
|
|
3
|
+
@group(0) @binding(2) var<storage, read> pathSampleBuffer: array<PathSamplePixel>;
|
|
4
|
+
@group(0) @binding(3) var<storage, read_write> pathDenoiseHistoryBuffer: array<PathDenoisePixel>;
|
|
5
|
+
|
|
6
|
+
fn denoise_index(pixel: vec2<u32>) -> u32 {
|
|
7
|
+
return pixel.y * max(pathTracerParams.image_width, 1u) + pixel.x;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fn in_bounds(pixel: vec2<i32>) -> bool {
|
|
11
|
+
return
|
|
12
|
+
pixel.x >= 0 &&
|
|
13
|
+
pixel.y >= 0 &&
|
|
14
|
+
pixel.x < i32(pathTracerParams.image_width) &&
|
|
15
|
+
pixel.y < i32(pathTracerParams.image_height);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fn spatial_kernel(offset: vec2<i32>) -> f32 {
|
|
19
|
+
if (offset.x == 0 && offset.y == 0) {
|
|
20
|
+
return 4.0;
|
|
21
|
+
}
|
|
22
|
+
if (offset.x == 0 || offset.y == 0) {
|
|
23
|
+
return 2.0;
|
|
24
|
+
}
|
|
25
|
+
return 1.0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn bilateral_weight(
|
|
29
|
+
center_sample: PathSamplePixel,
|
|
30
|
+
center_accumulation: PathAccumulationPixel,
|
|
31
|
+
neighbor_sample: PathSamplePixel,
|
|
32
|
+
offset: vec2<i32>
|
|
33
|
+
) -> f32 {
|
|
34
|
+
let strength = clamp(pathTracerParams.denoise_strength, 0.0, 1.0);
|
|
35
|
+
let center_normal = safe_normalize(center_sample.normal_roughness.xyz);
|
|
36
|
+
let neighbor_normal = safe_normalize(neighbor_sample.normal_roughness.xyz);
|
|
37
|
+
let normal_alignment = pow(
|
|
38
|
+
saturate(dot(center_normal, neighbor_normal)),
|
|
39
|
+
6.0 + (1.0 - strength) * 24.0
|
|
40
|
+
);
|
|
41
|
+
let depth_delta = abs(center_sample.direction_distance.w - neighbor_sample.direction_distance.w);
|
|
42
|
+
let depth_scale =
|
|
43
|
+
max(center_sample.direction_distance.w, 1.0) *
|
|
44
|
+
(0.01 + strength * 0.04);
|
|
45
|
+
let depth_weight = exp(-depth_delta * safe_rcp(depth_scale));
|
|
46
|
+
let albedo_delta = length(
|
|
47
|
+
center_sample.albedo_sample_count.xyz - neighbor_sample.albedo_sample_count.xyz
|
|
48
|
+
);
|
|
49
|
+
let albedo_weight = exp(-albedo_delta * (2.0 + strength * 2.0));
|
|
50
|
+
let sample_weight = clamp(
|
|
51
|
+
neighbor_sample.albedo_sample_count.w /
|
|
52
|
+
max(center_sample.albedo_sample_count.w, 1.0),
|
|
53
|
+
0.25,
|
|
54
|
+
1.5
|
|
55
|
+
);
|
|
56
|
+
let variance_penalty = 1.0 / (1.0 + center_accumulation.moments.y * 0.5);
|
|
57
|
+
return spatial_kernel(offset) *
|
|
58
|
+
max(normal_alignment * depth_weight * albedo_weight * sample_weight * variance_penalty, 0.0001);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@compute @workgroup_size(8, 8, 1)
|
|
62
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
63
|
+
if (
|
|
64
|
+
global_id.x >= pathTracerParams.image_width ||
|
|
65
|
+
global_id.y >= pathTracerParams.image_height
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let pixel = vec2<i32>(i32(global_id.x), i32(global_id.y));
|
|
71
|
+
let index = denoise_index(global_id.xy);
|
|
72
|
+
let center_accumulation = pathAccumulationBuffer[index];
|
|
73
|
+
let center_sample = pathSampleBuffer[index];
|
|
74
|
+
let center_history = pathDenoiseHistoryBuffer[index];
|
|
75
|
+
var filtered = vec3<f32>(0.0);
|
|
76
|
+
var total_weight = 0.0;
|
|
77
|
+
|
|
78
|
+
for (var y = -1; y <= 1; y = y + 1) {
|
|
79
|
+
for (var x = -1; x <= 1; x = x + 1) {
|
|
80
|
+
let neighbor_pixel = pixel + vec2<i32>(x, y);
|
|
81
|
+
if (!in_bounds(neighbor_pixel)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let neighbor_index = denoise_index(vec2<u32>(u32(neighbor_pixel.x), u32(neighbor_pixel.y)));
|
|
86
|
+
let neighbor_accumulation = pathAccumulationBuffer[neighbor_index];
|
|
87
|
+
let neighbor_sample = pathSampleBuffer[neighbor_index];
|
|
88
|
+
let weight = bilateral_weight(
|
|
89
|
+
center_sample,
|
|
90
|
+
center_accumulation,
|
|
91
|
+
neighbor_sample,
|
|
92
|
+
vec2<i32>(x, y)
|
|
93
|
+
);
|
|
94
|
+
filtered = filtered + neighbor_accumulation.integrated_radiance.xyz * weight;
|
|
95
|
+
total_weight = total_weight + weight;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let filtered_color = filtered / max(total_weight, PATH_TRACER_EPSILON);
|
|
100
|
+
let sample_confidence = clamp(
|
|
101
|
+
log2(max(center_accumulation.integrated_radiance.w, 1.0) + 1.0) / 6.0,
|
|
102
|
+
0.0,
|
|
103
|
+
1.0
|
|
104
|
+
);
|
|
105
|
+
let variance_penalty = 1.0 / (1.0 + center_accumulation.moments.y * 0.75);
|
|
106
|
+
let confidence = clamp(sample_confidence * variance_penalty, 0.0, 1.0);
|
|
107
|
+
let use_history =
|
|
108
|
+
pathTracerParams.accumulation_reset == 0u &&
|
|
109
|
+
center_history.filtered_radiance.w > 0.0;
|
|
110
|
+
let temporal_blend = clamp(0.2 + confidence * 0.6, 0.2, 0.9);
|
|
111
|
+
let resolved_color = select(
|
|
112
|
+
filtered_color,
|
|
113
|
+
center_history.filtered_radiance.xyz * (1.0 - temporal_blend) +
|
|
114
|
+
filtered_color * temporal_blend,
|
|
115
|
+
use_history
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
pathDenoiseHistoryBuffer[index] = PathDenoisePixel(
|
|
119
|
+
vec4<f32>(resolved_color, confidence),
|
|
120
|
+
vec4<f32>(safe_normalize(center_sample.normal_roughness.xyz), center_sample.direction_distance.w),
|
|
121
|
+
vec4<f32>(
|
|
122
|
+
center_sample.albedo_sample_count.xyz,
|
|
123
|
+
center_accumulation.integrated_radiance.w
|
|
124
|
+
)
|
|
125
|
+
);
|
|
3
126
|
}
|
|
@@ -1,3 +1,430 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@group(0) @binding(0) var<uniform> pathTracerParams: PathTracerParams;
|
|
2
|
+
@group(0) @binding(1) var<uniform> pathTracerCamera: PathTracerCamera;
|
|
3
|
+
@group(0) @binding(2) var<storage, read> pathTracerScene: PathTracerSceneMetadata;
|
|
4
|
+
@group(0) @binding(3) var<uniform> pathTracerGroundPlane: PathTracerGroundPlane;
|
|
5
|
+
@group(0) @binding(4) var<storage, read> pathTracerMaterials: array<PathTracerMaterial>;
|
|
6
|
+
@group(0) @binding(5) var<storage, read> pathTracerSpheres: array<PathTracerSphere>;
|
|
7
|
+
@group(0) @binding(6) var<storage, read> pathTracerTriangles: array<PathTracerTriangle>;
|
|
8
|
+
@group(0) @binding(7) var<storage, read_write> pathStateBuffer: array<PathState>;
|
|
9
|
+
@group(0) @binding(8) var<storage, read_write> pathSampleBuffer: array<PathSamplePixel>;
|
|
10
|
+
|
|
11
|
+
fn pixel_index(pixel: vec2<u32>) -> u32 {
|
|
12
|
+
return pixel.y * max(pathTracerParams.image_width, 1u) + pixel.x;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fn load_path_material(index: u32) -> MaterialSample {
|
|
16
|
+
if (pathTracerScene.material_count == 0u) {
|
|
17
|
+
return unpack_material(make_default_material());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let safe_index = min(index, pathTracerScene.material_count - 1u);
|
|
21
|
+
return unpack_material(pathTracerMaterials[safe_index]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn set_best_hit(best: ptr<function, PathHit>, candidate: PathHit) {
|
|
25
|
+
if (candidate.hit == 1u && candidate.distance < (*best).distance) {
|
|
26
|
+
*best = candidate;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn trace_ground(ray: Ray, best: ptr<function, PathHit>) {
|
|
31
|
+
if (pathTracerGroundPlane.enabled == 0u) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let plane_normal = safe_normalize(pathTracerGroundPlane.normal);
|
|
36
|
+
let denominator = dot(plane_normal, ray.direction);
|
|
37
|
+
if (abs(denominator) <= PATH_TRACER_EPSILON) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let distance = -(dot(plane_normal, ray.origin) + pathTracerGroundPlane.height) / denominator;
|
|
42
|
+
if (distance <= ray.t_min || distance >= (*best).distance || distance >= ray.t_max) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
set_best_hit(
|
|
47
|
+
best,
|
|
48
|
+
PathHit(
|
|
49
|
+
1u,
|
|
50
|
+
distance,
|
|
51
|
+
ray.origin + ray.direction * distance,
|
|
52
|
+
pathTracerGroundPlane.material_index,
|
|
53
|
+
plane_normal,
|
|
54
|
+
1u,
|
|
55
|
+
vec3<f32>(1.0, 0.0, 0.0),
|
|
56
|
+
0u
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn trace_spheres(ray: Ray, best: ptr<function, PathHit>) {
|
|
62
|
+
var index = 0u;
|
|
63
|
+
loop {
|
|
64
|
+
if (index >= pathTracerScene.sphere_count) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let sphere = pathTracerSpheres[index];
|
|
69
|
+
let offset = ray.origin - sphere.center_radius.xyz;
|
|
70
|
+
let a = dot(ray.direction, ray.direction);
|
|
71
|
+
let half_b = dot(offset, ray.direction);
|
|
72
|
+
let c = dot(offset, offset) - sphere.center_radius.w * sphere.center_radius.w;
|
|
73
|
+
let discriminant = half_b * half_b - a * c;
|
|
74
|
+
if (discriminant >= 0.0) {
|
|
75
|
+
let root = sqrt(discriminant);
|
|
76
|
+
var distance = (-half_b - root) / max(a, PATH_TRACER_EPSILON);
|
|
77
|
+
if (distance <= ray.t_min) {
|
|
78
|
+
distance = (-half_b + root) / max(a, PATH_TRACER_EPSILON);
|
|
79
|
+
}
|
|
80
|
+
if (distance > ray.t_min && distance < (*best).distance && distance < ray.t_max) {
|
|
81
|
+
let position = ray.origin + ray.direction * distance;
|
|
82
|
+
let normal = safe_normalize(position - sphere.center_radius.xyz);
|
|
83
|
+
set_best_hit(
|
|
84
|
+
best,
|
|
85
|
+
PathHit(
|
|
86
|
+
1u,
|
|
87
|
+
distance,
|
|
88
|
+
position,
|
|
89
|
+
sphere.material_index,
|
|
90
|
+
normal,
|
|
91
|
+
2u,
|
|
92
|
+
vec3<f32>(0.0),
|
|
93
|
+
0u
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
index = index + 1u;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn trace_triangles(ray: Ray, best: ptr<function, PathHit>) {
|
|
104
|
+
var index = 0u;
|
|
105
|
+
loop {
|
|
106
|
+
if (index >= pathTracerScene.triangle_count) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let triangle = pathTracerTriangles[index];
|
|
111
|
+
let p0 = triangle.position0.xyz;
|
|
112
|
+
let p1 = triangle.position1.xyz;
|
|
113
|
+
let p2 = triangle.position2.xyz;
|
|
114
|
+
let edge1 = p1 - p0;
|
|
115
|
+
let edge2 = p2 - p0;
|
|
116
|
+
let p_vec = cross(ray.direction, edge2);
|
|
117
|
+
let determinant = dot(edge1, p_vec);
|
|
118
|
+
if (abs(determinant) > PATH_TRACER_EPSILON) {
|
|
119
|
+
let inverse_determinant = 1.0 / determinant;
|
|
120
|
+
let t_vec = ray.origin - p0;
|
|
121
|
+
let u = dot(t_vec, p_vec) * inverse_determinant;
|
|
122
|
+
if (u >= 0.0 && u <= 1.0) {
|
|
123
|
+
let q_vec = cross(t_vec, edge1);
|
|
124
|
+
let v = dot(ray.direction, q_vec) * inverse_determinant;
|
|
125
|
+
if (v >= 0.0 && u + v <= 1.0) {
|
|
126
|
+
let distance = dot(edge2, q_vec) * inverse_determinant;
|
|
127
|
+
if (distance > ray.t_min && distance < (*best).distance && distance < ray.t_max) {
|
|
128
|
+
let w = 1.0 - u - v;
|
|
129
|
+
let interpolated_normal =
|
|
130
|
+
triangle.normal0.xyz * w +
|
|
131
|
+
triangle.normal1.xyz * u +
|
|
132
|
+
triangle.normal2.xyz * v;
|
|
133
|
+
let geometric_normal = cross(edge1, edge2);
|
|
134
|
+
let resolved_normal = select(
|
|
135
|
+
safe_normalize(geometric_normal),
|
|
136
|
+
safe_normalize(interpolated_normal),
|
|
137
|
+
dot(interpolated_normal, interpolated_normal) > PATH_TRACER_EPSILON
|
|
138
|
+
);
|
|
139
|
+
set_best_hit(
|
|
140
|
+
best,
|
|
141
|
+
PathHit(
|
|
142
|
+
1u,
|
|
143
|
+
distance,
|
|
144
|
+
ray.origin + ray.direction * distance,
|
|
145
|
+
triangle.material_index,
|
|
146
|
+
resolved_normal,
|
|
147
|
+
3u,
|
|
148
|
+
vec3<f32>(w, u, v),
|
|
149
|
+
0u
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
index = index + 1u;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn trace_scene(ray: Ray) -> PathHit {
|
|
162
|
+
var best = miss_hit(ray);
|
|
163
|
+
trace_ground(ray, &best);
|
|
164
|
+
trace_spheres(ray, &best);
|
|
165
|
+
trace_triangles(ray, &best);
|
|
166
|
+
return best;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn trace_shadow(origin: vec3<f32>, direction: vec3<f32>, max_distance: f32) -> bool {
|
|
170
|
+
let shadow_ray = Ray(
|
|
171
|
+
origin + direction * PATH_TRACER_EPSILON * 8.0,
|
|
172
|
+
PATH_TRACER_EPSILON,
|
|
173
|
+
direction,
|
|
174
|
+
max_distance
|
|
175
|
+
);
|
|
176
|
+
return trace_scene(shadow_ray).hit == 1u;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn evaluate_direct_sun(
|
|
180
|
+
hit: PathHit,
|
|
181
|
+
material: MaterialSample,
|
|
182
|
+
view_direction: vec3<f32>
|
|
183
|
+
) -> vec3<f32> {
|
|
184
|
+
let surface_normal = faceforward_normal(hit.normal, -view_direction);
|
|
185
|
+
let sun_direction = safe_normalize(vec3<f32>(0.32, 0.91, 0.21));
|
|
186
|
+
let ndotl = saturate(dot(surface_normal, sun_direction));
|
|
187
|
+
if (ndotl <= 0.0) {
|
|
188
|
+
return vec3<f32>(0.0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let shadowed = trace_shadow(hit.position + surface_normal * PATH_TRACER_EPSILON * 12.0, sun_direction, pathTracerScene.max_trace_distance);
|
|
192
|
+
if (shadowed) {
|
|
193
|
+
return vec3<f32>(0.0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let halfway = safe_normalize(sun_direction + view_direction);
|
|
197
|
+
let base_reflectance =
|
|
198
|
+
vec3<f32>(0.04) * (1.0 - material.metalness) +
|
|
199
|
+
material.albedo * material.metalness;
|
|
200
|
+
let fresnel = fresnel_schlick(saturate(dot(surface_normal, view_direction)), base_reflectance);
|
|
201
|
+
let specular_power = 8.0 + (1.0 - material.roughness) * 120.0;
|
|
202
|
+
let specular = fresnel * pow(saturate(dot(surface_normal, halfway)), specular_power) * ndotl;
|
|
203
|
+
let diffuse =
|
|
204
|
+
material.albedo *
|
|
205
|
+
ndotl *
|
|
206
|
+
PATH_TRACER_INV_PI *
|
|
207
|
+
(1.0 - material.metalness);
|
|
208
|
+
let sun_color = vec3<f32>(10.0, 9.4, 8.6) * max(pathTracerParams.environment_intensity, 0.0001);
|
|
209
|
+
return (diffuse + specular) * sun_color;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn sample_scatter(
|
|
213
|
+
surface_normal: vec3<f32>,
|
|
214
|
+
material: MaterialSample,
|
|
215
|
+
view_direction: vec3<f32>,
|
|
216
|
+
random_state: ptr<function, u32>
|
|
217
|
+
) -> PathScatter {
|
|
218
|
+
let specular_chance = clamp(
|
|
219
|
+
material.metalness * 0.75 + (1.0 - material.roughness) * 0.35,
|
|
220
|
+
0.05,
|
|
221
|
+
0.95
|
|
222
|
+
);
|
|
223
|
+
let choose_specular = random_f32(random_state) < specular_chance;
|
|
224
|
+
let base_reflectance =
|
|
225
|
+
vec3<f32>(0.04) * (1.0 - material.metalness) +
|
|
226
|
+
material.albedo * material.metalness;
|
|
227
|
+
|
|
228
|
+
if (choose_specular) {
|
|
229
|
+
let reflected = reflect(-view_direction, surface_normal);
|
|
230
|
+
let glossy_direction = safe_normalize(
|
|
231
|
+
reflected + sample_unit_sphere(random_state) * material.roughness * 0.35
|
|
232
|
+
);
|
|
233
|
+
let attenuation = fresnel_schlick(
|
|
234
|
+
saturate(dot(surface_normal, view_direction)),
|
|
235
|
+
base_reflectance
|
|
236
|
+
) / specular_chance;
|
|
237
|
+
return PathScatter(glossy_direction, 1.0, attenuation, 1.0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let diffuse_direction = sample_cosine_hemisphere(surface_normal, random_state);
|
|
241
|
+
let cosine = saturate(dot(surface_normal, diffuse_direction));
|
|
242
|
+
let pdf = max(cosine * PATH_TRACER_INV_PI, PATH_TRACER_EPSILON);
|
|
243
|
+
let attenuation =
|
|
244
|
+
material.albedo *
|
|
245
|
+
(1.0 - material.metalness) *
|
|
246
|
+
cosine /
|
|
247
|
+
max((1.0 - specular_chance) * pdf, PATH_TRACER_EPSILON);
|
|
248
|
+
return PathScatter(diffuse_direction, pdf, attenuation, 0.0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fn make_camera_ray(pixel: vec2<u32>, jitter: vec2<f32>, random_state: ptr<function, u32>) -> Ray {
|
|
252
|
+
let image_size = vec2<f32>(
|
|
253
|
+
max(f32(pathTracerParams.image_width), 1.0),
|
|
254
|
+
max(f32(pathTracerParams.image_height), 1.0)
|
|
255
|
+
);
|
|
256
|
+
let uv = ((vec2<f32>(pixel) + jitter) / image_size) * 2.0 - vec2<f32>(1.0, 1.0);
|
|
257
|
+
let tan_half_fov = tan(pathTracerCamera.vertical_fov_radians * 0.5);
|
|
258
|
+
let sensor_offset =
|
|
259
|
+
pathTracerCamera.right * uv.x * pathTracerCamera.aspect_ratio * tan_half_fov +
|
|
260
|
+
pathTracerCamera.up * (-uv.y) * tan_half_fov;
|
|
261
|
+
let focal_point =
|
|
262
|
+
pathTracerCamera.origin +
|
|
263
|
+
(pathTracerCamera.forward + sensor_offset) * pathTracerCamera.focus_distance;
|
|
264
|
+
var origin = pathTracerCamera.origin;
|
|
265
|
+
if (pathTracerCamera.aperture_radius > 0.0) {
|
|
266
|
+
let lens = sample_unit_disk(random_state) * pathTracerCamera.aperture_radius;
|
|
267
|
+
origin =
|
|
268
|
+
origin +
|
|
269
|
+
pathTracerCamera.right * lens.x +
|
|
270
|
+
pathTracerCamera.up * lens.y;
|
|
271
|
+
}
|
|
272
|
+
return Ray(
|
|
273
|
+
origin,
|
|
274
|
+
PATH_TRACER_EPSILON,
|
|
275
|
+
safe_normalize(focal_point - origin),
|
|
276
|
+
pathTracerScene.max_trace_distance
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@compute @workgroup_size(8, 8, 1)
|
|
281
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
282
|
+
if (
|
|
283
|
+
global_id.x >= pathTracerParams.image_width ||
|
|
284
|
+
global_id.y >= pathTracerParams.image_height
|
|
285
|
+
) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let pixel = global_id.xy;
|
|
290
|
+
let index = pixel_index(pixel);
|
|
291
|
+
let sample_count = max(pathTracerParams.samples_per_pixel, 1u);
|
|
292
|
+
let bounce_count = max(pathTracerParams.max_bounces, 1u);
|
|
293
|
+
var random_state = hash_u32(
|
|
294
|
+
pathTracerParams.frame_index * 1664525u +
|
|
295
|
+
index * 1013904223u +
|
|
296
|
+
0x6d2b79f5u
|
|
297
|
+
);
|
|
298
|
+
var accumulated_radiance = vec3<f32>(0.0);
|
|
299
|
+
var accumulated_normal = vec3<f32>(0.0);
|
|
300
|
+
var accumulated_albedo = vec3<f32>(0.0);
|
|
301
|
+
var accumulated_distance = 0.0;
|
|
302
|
+
var accumulated_roughness = 0.0;
|
|
303
|
+
var primary_direction = vec3<f32>(0.0, 0.0, 1.0);
|
|
304
|
+
var terminal_direction = primary_direction;
|
|
305
|
+
var terminal_origin = pathTracerCamera.origin;
|
|
306
|
+
var terminal_throughput = vec3<f32>(1.0);
|
|
307
|
+
var last_hit_kind = 0u;
|
|
308
|
+
var hit_samples = 0.0;
|
|
309
|
+
|
|
310
|
+
for (var sample_index = 0u; sample_index < sample_count; sample_index = sample_index + 1u) {
|
|
311
|
+
let jitter = vec2<f32>(random_f32(&random_state), random_f32(&random_state));
|
|
312
|
+
var ray = make_camera_ray(pixel, jitter, &random_state);
|
|
313
|
+
primary_direction = primary_direction + ray.direction;
|
|
314
|
+
var throughput = vec3<f32>(1.0);
|
|
315
|
+
var sample_radiance = vec3<f32>(0.0);
|
|
316
|
+
var captured_primary_hit = false;
|
|
317
|
+
var current_hit_kind = 0u;
|
|
318
|
+
|
|
319
|
+
for (var bounce_index = 0u; bounce_index < bounce_count; bounce_index = bounce_index + 1u) {
|
|
320
|
+
let hit = trace_scene(ray);
|
|
321
|
+
if (hit.hit == 0u) {
|
|
322
|
+
sample_radiance = sample_radiance + throughput * environment_radiance(
|
|
323
|
+
ray.direction,
|
|
324
|
+
pathTracerParams.environment_intensity,
|
|
325
|
+
pathTracerParams.environment_mode
|
|
326
|
+
);
|
|
327
|
+
terminal_origin = ray.origin;
|
|
328
|
+
terminal_direction = ray.direction;
|
|
329
|
+
current_hit_kind = 0u;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let material = load_path_material(hit.material_index);
|
|
334
|
+
let surface_normal = faceforward_normal(hit.normal, ray.direction);
|
|
335
|
+
|
|
336
|
+
if (!captured_primary_hit) {
|
|
337
|
+
accumulated_normal = accumulated_normal + surface_normal;
|
|
338
|
+
accumulated_distance = accumulated_distance + hit.distance;
|
|
339
|
+
accumulated_albedo = accumulated_albedo + material.albedo;
|
|
340
|
+
accumulated_roughness = accumulated_roughness + material.roughness;
|
|
341
|
+
hit_samples = hit_samples + 1.0;
|
|
342
|
+
captured_primary_hit = true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
sample_radiance = sample_radiance + throughput * material.emission;
|
|
346
|
+
if (pathTracerParams.enable_next_event_estimation != 0u) {
|
|
347
|
+
sample_radiance = sample_radiance + throughput * evaluate_direct_sun(
|
|
348
|
+
hit,
|
|
349
|
+
material,
|
|
350
|
+
-ray.direction
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let scatter = sample_scatter(surface_normal, material, -ray.direction, &random_state);
|
|
355
|
+
throughput = throughput * scatter.attenuation;
|
|
356
|
+
if (luminance(throughput) <= 0.0001) {
|
|
357
|
+
terminal_origin = hit.position;
|
|
358
|
+
terminal_direction = scatter.direction;
|
|
359
|
+
current_hit_kind = hit.primitive_kind;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (bounce_index >= 2u) {
|
|
364
|
+
let russian_roulette = clamp(luminance(throughput), 0.05, 0.95);
|
|
365
|
+
if (random_f32(&random_state) > russian_roulette) {
|
|
366
|
+
terminal_origin = hit.position;
|
|
367
|
+
terminal_direction = scatter.direction;
|
|
368
|
+
current_hit_kind = hit.primitive_kind;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
throughput = throughput / russian_roulette;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
ray = Ray(
|
|
375
|
+
hit.position + scatter.direction * PATH_TRACER_EPSILON * 8.0,
|
|
376
|
+
PATH_TRACER_EPSILON,
|
|
377
|
+
scatter.direction,
|
|
378
|
+
pathTracerScene.max_trace_distance
|
|
379
|
+
);
|
|
380
|
+
terminal_origin = ray.origin;
|
|
381
|
+
terminal_direction = ray.direction;
|
|
382
|
+
current_hit_kind = hit.primitive_kind;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
terminal_throughput = throughput;
|
|
386
|
+
last_hit_kind = max(last_hit_kind, current_hit_kind);
|
|
387
|
+
accumulated_radiance = accumulated_radiance + sample_radiance;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let sample_count_f32 = f32(sample_count);
|
|
391
|
+
let averaged_radiance = accumulated_radiance / sample_count_f32;
|
|
392
|
+
let averaged_direction = safe_normalize(primary_direction / sample_count_f32);
|
|
393
|
+
let averaged_normal = select(
|
|
394
|
+
vec3<f32>(0.0, 1.0, 0.0),
|
|
395
|
+
safe_normalize(accumulated_normal / max(hit_samples, 1.0)),
|
|
396
|
+
hit_samples > 0.0
|
|
397
|
+
);
|
|
398
|
+
let averaged_distance = select(
|
|
399
|
+
pathTracerScene.max_trace_distance,
|
|
400
|
+
accumulated_distance / max(hit_samples, 1.0),
|
|
401
|
+
hit_samples > 0.0
|
|
402
|
+
);
|
|
403
|
+
let averaged_albedo = select(
|
|
404
|
+
vec3<f32>(0.0),
|
|
405
|
+
accumulated_albedo / max(hit_samples, 1.0),
|
|
406
|
+
hit_samples > 0.0
|
|
407
|
+
);
|
|
408
|
+
let averaged_roughness = select(
|
|
409
|
+
1.0,
|
|
410
|
+
accumulated_roughness / max(hit_samples, 1.0),
|
|
411
|
+
hit_samples > 0.0
|
|
412
|
+
);
|
|
413
|
+
let hit_mask = select(0.0, 1.0, hit_samples > 0.0);
|
|
414
|
+
|
|
415
|
+
pathStateBuffer[index] = PathState(
|
|
416
|
+
vec4<f32>(terminal_throughput, f32(bounce_count)),
|
|
417
|
+
vec4<f32>(terminal_origin, hit_mask),
|
|
418
|
+
vec4<f32>(terminal_direction, 1.0),
|
|
419
|
+
random_state,
|
|
420
|
+
last_hit_kind,
|
|
421
|
+
vec2<u32>(0u)
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
pathSampleBuffer[index] = PathSamplePixel(
|
|
425
|
+
vec4<f32>(averaged_radiance * pathTracerParams.exposure, hit_mask),
|
|
426
|
+
vec4<f32>(averaged_direction, averaged_distance),
|
|
427
|
+
vec4<f32>(averaged_normal, averaged_roughness),
|
|
428
|
+
vec4<f32>(averaged_albedo, sample_count_f32)
|
|
429
|
+
);
|
|
3
430
|
}
|