-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathcompute.js
297 lines (234 loc) · 8.2 KB
/
compute.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// shaders where the path tracing magic happens
const computeWGSL = `
@group(0) @binding(0) var outputTex : texture_storage_2d<rgba8unorm, write>;
@group(0) @binding(1) var inputTex : texture_2d<f32>;
@group(0) @binding(2) var<storage> vertex: array<vec3f>;
@group(0) @binding(3) var<storage> index: array<vec3u>;
@group(0) @binding(4) var<storage> meshes: array<Mesh>;
@group(0) @binding(5) var<storage> materials: array<Material>;
@group(0) @binding(6) var<uniform> uniforms: Uniforms;
struct Mesh {
vi : u32, // first vertex
fi : u32, // first face
nv : u32, // total vertices
nf : u32, // total faces
}
struct Material {
color : vec4f,
emission : vec4f,
metallic : f32,
roughness : f32,
}
struct Ray {
origin : vec3<f32>,
direction : vec3<f32>,
}
struct HitRecord {
hit : bool,
point : vec3<f32>,
normal : vec3<f32>,
material : Material,
t: f32,
}
struct Uniforms {
seed: f32,
weight: f32,
cam_azimuth: f32,
cam_elevation: f32,
bounces: u32,
samples: u32,
};
var<private> seed : f32;
var<private> pixel : vec2f;
fn random() -> f32 {
let result = fract(sin(seed / 100.0 * dot(pixel, vec2(12.9898, 78.233))) * 43758.5453);
seed += 1.0;
return result;
}
fn v2random() -> vec2f {
let r1 = random();
let r2 = random();
return vec2f(r1, r2);
}
fn random_in_unit_disk() -> vec2f {
let r1 = random()*2.0-1.0;
let r2 = random()*2.0-1.0;
return vec2f(r1, r2);
}
fn random_in_unit_sphere() -> vec3f {
let r1 = random()*2.0-1.0;
let r2 = random()*2.0-1.0;
let r3 = random()*2.0-1.0;
return vec3f(r1, r2, r3);
}
fn mesh_random_point(mesh : Mesh, pdf : ptr<function, f32>) -> vec3f{
// get a random triangle, should be weighted with the triangles areas!
let trg = min(u32(f32(mesh.nf) * random()), mesh.nf-1) + mesh.fi;
let vi = index[trg];
let v0 = vertex[vi[0]];
let v1 = vertex[vi[1]];
let v2 = vertex[vi[2]];
let u = random();
let v = random();
let w = 1.0 - u - v;
// here we are again assuming all the triangles have the same area
let trg_area = length(cross(v1 - v0, v2 - v0)) * 0.5;
*pdf = 1.0/(f32(mesh.nf)*trg_area);
return v0*u + v1*v + v2*w;
}
fn ray_at(r: Ray, t : f32) -> vec3<f32> {
return r.origin + r.direction * t;
}
// Möller–Trumbore ray-triangle intersection algorithm
// from http://www.graphics.cornell.edu/pubs/1997/MT97.pdf
const EPSILON : f32 = 0.000001;
fn triangle_hit(r : Ray, v0 : vec3<f32>, v1: vec3<f32>, v2 : vec3<f32>, t : ptr<function, f32>) -> bool {
let e1 = v1 - v0;
let e2 = v2 - v0;
let p = cross(r.direction, e2);
let det = dot(e1, p);
// check if ray is parallel to triangle
if (abs(det) < EPSILON) { return false; }
// calculate barycentric coordinate u
let inv_det = 1.0 / det;
let s = r.origin - v0; // called T in paper, not used here to avoid confusion with *t
let u = inv_det * dot(s, p);
if (u < 0.0 || u > 1.0) { return false; }
// calculate barycentric coordinate v
let q = cross(s, e1);
let v = inv_det * dot(r.direction, q);
if (v < 0.0 || u + v > 1.0) { return false; }
// distance from the ray origin to the hit point
*t = inv_det * dot(e2, q);
if (*t < EPSILON) { return false; }
// backface culling
if (dot(cross(e1, e2), r.direction) > 0.0 ){ return false; }
return true;
}
fn world_hit(r : Ray) -> HitRecord {
var hit_rec : HitRecord;
hit_rec.hit = false;
var t = 100000000.0;
var closest_hit = t;
// loop through all the meshes in the scene
for(var m = 0; m < 6; m++){
let mesh = meshes[m];
// loop through all the triangles in each mesh
for(var i = mesh.fi; i < mesh.fi+mesh.nf; i++){
let vi = index[i];
let v0 = vertex[vi[0]];
let v1 = vertex[vi[1]];
let v2 = vertex[vi[2]];
let hit_bool = triangle_hit(r, v0, v1, v2, &t);
// we have to return the closest hit to the ray origin
if(hit_bool && t < closest_hit) {
closest_hit = t;
hit_rec.hit = true;
hit_rec.t = t;
hit_rec.normal = normalize(cross(v1 - v0, v2 - v0));
hit_rec.point = ray_at(r, t) + hit_rec.normal*EPSILON;
hit_rec.material = materials[m];
}
}
}
return hit_rec;
}
fn ray_color(r : Ray) -> vec3f {
var depth = 0u;
var color = vec3(0.0, 0.0, 0.0); // background color
var ray = r;
var hit_result = world_hit(ray);
var final_color = vec3(0.0, 0.0, 0.0); // background at first
var bounced_color = vec3(1.0, 1.0, 1.0);
// recursion is not allowed
while(depth < uniforms.bounces+1 && (hit_result.hit)){
// if the ray hits a emissive material, return it directly
if (hit_result.material.emission.a > 0.0) {
final_color = hit_result.material.emission.rgb;
break;
} else if (hit_result.material.metallic >= random()) {
let hit_point = hit_result.point;
ray.origin = hit_point;
ray.direction = reflect(ray.direction, hit_result.normal);
// surface roughness
ray.direction += random_in_unit_sphere()*hit_result.material.roughness;
ray.direction = normalize(ray.direction);
bounced_color *= hit_result.material.color.rgb;
depth++;
hit_result = world_hit(ray);
} else {
let hit_point = hit_result.point;
// bias towards lights
var light_pdf = 1.0;
let light_point = mesh_random_point(meshes[3], &light_pdf);
let lh = light_point - hit_point;
var shadow_ray : Ray;
shadow_ray.origin = hit_point;
shadow_ray.direction = normalize(lh);
var shadow_hit = world_hit(shadow_ray);
if (shadow_hit.material.emission.a > 0.0 && random()>0.5) {
final_color = (1/light_pdf)
* 1/(pow(shadow_hit.t, 2))
* shadow_hit.material.emission.rgb
* abs(dot(shadow_hit.normal, shadow_ray.direction))
* hit_result.material.color.rgb
* abs(dot(hit_result.normal, shadow_ray.direction));
break;
} else {
//scatter
ray.origin = hit_point;
ray.direction = normalize(hit_result.normal + random_in_unit_sphere());
bounced_color *= hit_result.material.color.rgb;
depth++;
hit_result = world_hit(ray);
}
}
}
color = final_color*bounced_color;
return color;
}
@compute @workgroup_size(8, 8, 1)
fn compute_main(@builtin(global_invocation_id) GlobalInvocationID: vec3u) {
// set the private vars
let pos = GlobalInvocationID.xy;
pixel = vec2f(pos)/512.0;
seed = uniforms.seed; // initial seed
// setup camera
var ray : Ray;
let camera_center = vec3(0.0, 0.0, 1.0796);
var color = vec4(0.0, 0.0, 0.0, 1.0);
var samples = uniforms.samples;
// camera rotation
let camera_rot_y = mat3x3(
cos(uniforms.cam_azimuth), 0.0, sin(uniforms.cam_azimuth),
0.0, 1.0, 0.0,
-sin(uniforms.cam_azimuth), 0.0, cos(uniforms.cam_azimuth),
);
let camera_rot_x = mat3x3(
1.0, 0.0, 0.0,
0.0, cos(uniforms.cam_elevation), -sin(uniforms.cam_elevation),
0.0, sin(uniforms.cam_elevation), cos(uniforms.cam_elevation),
);
let camera_matrix = camera_rot_y * camera_rot_x;
// repeat each pixel "samples" times
for(var i=0u; i<samples; i++){
let camera_disk = 0.00001*random_in_unit_disk(); // camera aperture
ray.origin = camera_center + vec3(camera_disk, 0.0);
let pos_norm = (vec2f(pos)+v2random())/256.0 - 1.0;
let pos_in_camera_plane = vec3(ray.origin.xy + pos_norm*0.0125, ray.origin.z - 0.035);
ray.direction = normalize(pos_in_camera_plane - ray.origin);
// apply camera transformation
ray.direction = camera_matrix * ray.direction;
ray.origin = camera_matrix * ray.origin;
color += vec4(ray_color(ray), 1.0);
}
// gamma 2
let newImage = clamp(sqrt(color/f32(samples)), vec4(0.0), vec4(1.0));
let accumulated = textureLoad(inputTex, pos, 0);
// weighted average between the new and the accumulated image
let result_color = uniforms.weight * newImage
+ (1.0 - uniforms.weight) * accumulated;
textureStore(outputTex, pos, result_color);
}
`
export default computeWGSL;