├── .vscode
└── settings.json
├── README.md
├── dist
├── 02415db87076fc633fbc4470b9aed4df.wasm
├── 1dbbe3b15f012401f122d7396d5c9802.wasm
├── HDRs
│ └── outdoor0.hdr
├── index.html
└── main.js
├── package-lock.json
├── package.json
├── src
├── C
│ ├── bvhbuild.cc
│ └── hdrToFloats.cc
├── js
│ ├── bvh.js
│ ├── hdr.js
│ ├── index.js
│ ├── renderFrame.js
│ ├── scene.js
│ └── utils.js
├── shaders
│ ├── fullscreen.wgsl
│ ├── logic.wgsl
│ ├── material.wgsl
│ ├── newray.wgsl
│ └── raycast.wgsl
└── wasm
│ ├── bvhbuild.js
│ ├── bvhbuild.wasm
│ ├── hdrToFloats.js
│ └── hdrToFloats.wasm
└── webpack.config.js
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5502
3 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebGPU-Path-Tracing
2 | Basic path tracing with WebGPU. Working as of Chrome 114.
3 | 
4 | Features material editor (diffuse, transmissive, specular, glossy), OBJ mesh importer, scene editor (mesh instancing, position/scale/rotation, per mesh material), camera editor (depth of field, render size, position).
5 |
--------------------------------------------------------------------------------
/dist/02415db87076fc633fbc4470b9aed4df.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddisonPrairie/WebGPU-Path-Tracing/cddae82fee543905b733a312f0be3782f45a1c54/dist/02415db87076fc633fbc4470b9aed4df.wasm
--------------------------------------------------------------------------------
/dist/1dbbe3b15f012401f122d7396d5c9802.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddisonPrairie/WebGPU-Path-Tracing/cddae82fee543905b733a312f0be3782f45a1c54/dist/1dbbe3b15f012401f122d7396d5c9802.wasm
--------------------------------------------------------------------------------
/dist/HDRs/outdoor0.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddisonPrairie/WebGPU-Path-Tracing/cddae82fee543905b733a312f0be3782f45a1c54/dist/HDRs/outdoor0.hdr
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebGPU
5 |
6 |
7 |
8 |
9 |
10 |
200 |
201 |
202 |
203 |
206 |
207 |
258 |
259 |
260 |
261 |
Camera & Film
262 |
263 |
264 |
265 |
266 |
267 |
268 |
275 |
276 |
277 |
278 |
279 | focal distance
280 |
281 |
282 |
283 |
284 |
285 | aperture
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
300 |
301 |
302 |
303 |
304 | offset x
305 |
306 |
307 |
308 |
309 |
310 | offset y
311 |
312 |
313 |
314 |
315 |
316 | offset z
317 |
318 |
319 |
320 |
321 |
322 | distance
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
337 |
338 |
339 |
340 |
341 | horizontal
342 |
343 |
344 |
345 |
346 |
347 | vertical
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
Objects
360 |
361 |
362 |
363 |
364 |
365 |
366 |
373 |
374 |
375 |
376 |
377 | new object
378 |
379 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
Meshes
392 |
393 |
394 |
395 |
396 |
397 |
398 |
405 |
406 |
407 |
408 |
409 | upload mesh
410 |
411 |
414 |
415 |
416 |
417 |
418 |
419 |
426 |
427 |
428 |
429 |
430 | index
431 |
432 |
435 |
436 |
437 |
438 | triangles
439 |
440 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
Materials
453 |
454 |
455 |
456 |
457 |
458 |
459 |
466 |
467 |
468 |
469 |
470 | new material
471 |
472 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wgpupathtracing",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "webpack": "webpack",
8 | "compileHDR": "em++ -s -O3 -s WASM=1 -s \"EXPORTED_RUNTIME_METHODS=[\"cwrap\", \"ccall\"]\" -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s \"EXPORT_NAME=\"hdrToFloats\"\" -s \"ENVIRONMENT='web'\" -s \"EXPORTED_FUNCTIONS=[\"_hdrToFloats\", \"_malloc\"]\" -o src/wasm/hdrToFloats.js src/C/hdrToFloats.cc",
9 | "compileBVH": "em++ -s -O3 -s WASM=1 -s \"EXPORTED_RUNTIME_METHODS=[\"cwrap\", \"ccall\"]\" -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s \"EXPORT_NAME=\"bvhbuild\"\" -s \"ENVIRONMENT='web'\" -s \"EXPORTED_FUNCTIONS=[\"_bvhbuild\", \"_malloc\"]\" -o src/wasm/bvhbuild.js src/C/bvhbuild.cc",
10 | "build": "npm run compileBVH && npm run webpack",
11 | "watch:build": "emsdk_env.bat && onchange \"src\\js\" \"src\\shaders\" \"src\\scss\" \"src\\C\" -- npm run build"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@gltf-transform/core": "^3.4.2",
17 | "exports-loader": "^4.0.0",
18 | "file-loader": "^6.2.0",
19 | "onchange": "^7.1.0",
20 | "raw-loader": "^4.0.2",
21 | "source-map-loader": "^4.0.1",
22 | "webpack": "^5.86.0"
23 | },
24 | "browser": {
25 | "fs": false
26 | },
27 | "devDependencies": {
28 | "webpack-cli": "^5.1.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/C/bvhbuild.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | struct vec3 {
6 | float x;
7 | float y;
8 | float z;
9 | };
10 |
11 | struct Triangle {
12 | struct vec3 v0;
13 | struct vec3 v1;
14 | struct vec3 v2;
15 | struct vec3 centroid;
16 | };
17 | //basically a "pointer" to a triangle
18 | typedef unsigned int uint;
19 | typedef uint Tri;
20 |
21 | //node for intermediate BVH construction - not the final packed version
22 | struct iBVHNode {
23 | struct vec3 AABBMin;
24 | struct vec3 AABBMax;
25 | uint leftChild;
26 | uint rightChild;
27 | uint isleaf;
28 | uint firstPrim;
29 | uint primCount;
30 | };
31 | //BVH node once it is flattened, assuming it is an internal node
32 | struct PackedBVHBranch {
33 | float AABBLLowX;
34 | float AABBLLowY;
35 | float AABBLLowZ;
36 | uint rightPtr;
37 | float AABBLHighX;
38 | float AABBLHighY;
39 | float AABBLHighZ;
40 | uint padding0; //should be set to 0u to signal an object
41 | float AABBRLowX;
42 | float AABBRLowY;
43 | float AABBRLowZ;
44 | uint padding1;
45 | float AABBRHighX;
46 | float AABBRHighY;
47 | float AABBRHighZ;
48 | uint padding2;
49 | };
50 | //BVH node once it is flattened, assuming it is a leaf that points to an array of primitives
51 | struct PackedBVHLeaf {
52 | uint primitiveStart;
53 | uint primitiveEnd;
54 | uint padding0;
55 | uint ptr; //Should be set to 0u to signal a leaf node
56 | uint padding1;
57 | uint padding2;
58 | uint padding3;
59 | uint padding4;//should be set to 0u to signal an object
60 | uint padding5;
61 | uint padding6;
62 | uint padding7;
63 | uint padding8;
64 | uint padding9;
65 | uint padding10;
66 | uint padding11;
67 | uint padding12;
68 | };
69 | //allows significantly faster swapping/duplication
70 | struct TriangleArray {
71 | int numTris;
72 | struct Triangle* trianglePool;
73 | Tri* triangles;
74 | };
75 | //this is a triangle when being sent out, 64 bytes
76 | struct PackedTriangle {
77 | float v0x;
78 | float v0y;
79 | float v0z;
80 | uint padding0;
81 | float v1x;
82 | float v1y;
83 | float v1z;
84 | uint padding1;
85 | float v2x;
86 | float v2y;
87 | float v2z;
88 | uint padding2;
89 | float xtra0;
90 | float xtra1;
91 | float xtra2;
92 | uint padding3;
93 | };
94 | //wraps up multiple pieces of data into one
95 | struct BVH {
96 | struct PackedTriangle* triangles;
97 | struct iBVHNode* nodes;
98 | uint rootIdx;
99 | uint nodesUsed;
100 | };
101 |
102 | float min(float a, float b);
103 | float max(float a, float b);
104 | struct vec3 addVec3(struct vec3 x, struct vec3 y);
105 | struct vec3 subVec3(struct vec3 a, struct vec3 b);
106 | struct vec3 multVec3(struct vec3 x, float a);
107 | struct vec3 ftoVec3(float a);
108 | struct vec3 minVec3(struct vec3 a, struct vec3 b);
109 | struct vec3 maxVec3(struct vec3 a, struct vec3 b);
110 |
111 | struct TriangleArray genTriArray(float* inTris, int numTris);
112 | struct BVH BuildiBVH(struct TriangleArray* tris);
113 | void UpdateNodeBounds(struct iBVHNode* bvh, struct TriangleArray* tris, uint index);
114 | void SubdivideNode(struct BVH* bvh, struct TriangleArray* tris, uint index);
115 | uint addINodeToPacked(struct BVH* bvh, void* outBVH, uint iIndex, int* openPtr);
116 |
117 | extern "C" {
118 | //returns an integer that is the size of the outgoing bvh in wasm memory
119 | EMSCRIPTEN_KEEPALIVE
120 | int bvhbuild(int numTriangles, float* inTriangles) {
121 | //printf("here0\n");
122 | struct TriangleArray triangleArray = genTriArray(inTriangles, numTriangles);
123 | //printf("here1\n");
124 | struct BVH bvh = BuildiBVH(&triangleArray);
125 | //printf("here2\n");
126 |
127 | //pack intermediate bvh into our format
128 | void* outBVH = malloc(sizeof(struct PackedBVHBranch) * bvh.nodesUsed);
129 | int openPtr = 0;
130 |
131 | addINodeToPacked(&bvh, outBVH, bvh.rootIdx, &openPtr);
132 |
133 | //now write the location/size of the two buffers to memory for JS to read
134 | //just write to beginning of the file
135 | int* outValues = (int*) inTriangles;
136 | outValues[0] = bvh.nodesUsed;
137 | outValues[1] = (int) outBVH;
138 | outValues[2] = triangleArray.numTris;
139 | outValues[3] = (int) bvh.triangles;
140 |
141 | return 0;
142 | }
143 | }
144 |
145 | //adds an intermediate BVH node to our packed format
146 | uint addINodeToPacked(struct BVH* bvh, void* outBVH, uint iIndex, int* openPtr) {
147 | struct iBVHNode* cur = &bvh->nodes[iIndex];
148 | //this is an internal node
149 | if (cur->primCount == 0) {
150 | struct iBVHNode* left = &bvh->nodes[cur->leftChild];
151 | struct iBVHNode* right = &bvh->nodes[cur->rightChild];
152 |
153 | struct PackedBVHBranch* curOut = &(((struct PackedBVHBranch*) outBVH)[*openPtr]);
154 | int idx = *openPtr;
155 | *openPtr += 1;
156 |
157 | curOut->AABBLLowX = left->AABBMin.x;
158 | curOut->AABBLLowY = left->AABBMin.y;
159 | curOut->AABBLLowZ = left->AABBMin.z;
160 | curOut->AABBLHighX = left->AABBMax.x;
161 | curOut->AABBLHighY = left->AABBMax.y;
162 | curOut->AABBLHighZ = left->AABBMax.z;
163 |
164 | curOut->AABBRLowX = right->AABBMin.x;
165 | curOut->AABBRLowY = right->AABBMin.y;
166 | curOut->AABBRLowZ = right->AABBMin.z;
167 | curOut->AABBRHighX = right->AABBMax.x;
168 | curOut->AABBRHighY = right->AABBMax.y;
169 | curOut->AABBRHighZ = right->AABBMax.z;
170 |
171 | curOut->padding0 = 123456789;
172 |
173 | //add left node directly after
174 | addINodeToPacked(bvh, outBVH, cur->leftChild, openPtr);
175 | //add write node and store a pointer to where it was
176 | curOut->rightPtr = addINodeToPacked(bvh, outBVH, cur->rightChild, openPtr);
177 | return idx;
178 | } else {
179 | struct PackedBVHLeaf* curOut = &(((struct PackedBVHLeaf*) outBVH)[*openPtr]);
180 | int idx = *openPtr;
181 | *openPtr += 1;
182 |
183 | curOut->ptr = 0;
184 | curOut->primitiveStart = cur->firstPrim;
185 | curOut->primitiveEnd = cur->firstPrim + cur->primCount;
186 | curOut->padding4 = 123456789;
187 | return idx;
188 | }
189 | }
190 |
191 | //builds the intermediate/standard BVH that will then be packed into our own format
192 | struct BVH BuildiBVH(struct TriangleArray* tris) {
193 | struct BVH bvh;
194 | bvh.nodes = (struct iBVHNode*) malloc(sizeof(struct iBVHNode) * (tris->numTris * 2 - 1));
195 | bvh.rootIdx = 0; bvh.nodesUsed = 1;
196 | struct iBVHNode* root = &bvh.nodes[bvh.rootIdx];
197 | root->leftChild = 0; root->rightChild = 0;
198 | root->firstPrim = 0; root->primCount = tris->numTris;
199 |
200 | UpdateNodeBounds(bvh.nodes, tris, bvh.rootIdx);
201 | SubdivideNode(&bvh, tris, bvh.rootIdx);
202 |
203 | //now pack the triangles
204 | bvh.triangles = (struct PackedTriangle*) malloc(sizeof(struct PackedTriangle) * tris->numTris);
205 | for (int i = 0; i < tris->numTris; i++) {
206 | struct Triangle* curTri = &tris->trianglePool[tris->triangles[i]];
207 | bvh.triangles[i].v0x = curTri->v0.x;
208 | bvh.triangles[i].v0y = curTri->v0.y;
209 | bvh.triangles[i].v0z = curTri->v0.z;
210 | bvh.triangles[i].v1x = curTri->v1.x;
211 | bvh.triangles[i].v1y = curTri->v1.y;
212 | bvh.triangles[i].v1z = curTri->v1.z;
213 | bvh.triangles[i].v2x = curTri->v2.x;
214 | bvh.triangles[i].v2y = curTri->v2.y;
215 | bvh.triangles[i].v2z = curTri->v2.z;
216 | }
217 |
218 | return bvh;
219 | }
220 |
221 | //makes the AABB have the min/max of all of the nodes
222 | void UpdateNodeBounds(struct iBVHNode* bvh, struct TriangleArray* tris, uint index) {
223 | struct iBVHNode* node = &bvh[index];
224 | node->AABBMin = ftoVec3(1e30f);
225 | node->AABBMax = ftoVec3(-1e30f);
226 | for (uint first = node->firstPrim, i = 0; i < node->primCount; i++) {
227 | struct Triangle* cur = &tris->trianglePool[tris->triangles[first + i]];
228 | node->AABBMin = minVec3(node->AABBMin, cur->v0);
229 | node->AABBMin = minVec3(node->AABBMin, cur->v1);
230 | node->AABBMin = minVec3(node->AABBMin, cur->v2);
231 | node->AABBMax = maxVec3(node->AABBMax, cur->v0);
232 | node->AABBMax = maxVec3(node->AABBMax, cur->v1);
233 | node->AABBMax = maxVec3(node->AABBMax, cur->v2);
234 | }
235 | }
236 |
237 | int partitionX(struct TriangleArray* tris, uint first, uint last, float split);
238 | int partitionY(struct TriangleArray* tris, uint first, uint last, float split);
239 | int partitionZ(struct TriangleArray* tris, uint first, uint last, float split);
240 | #define BINSPLITS 20
241 | float findSplitPlaneX(struct iBVHNode* node, struct TriangleArray* tris, float* split);
242 | float findSplitPlaneY(struct iBVHNode* node, struct TriangleArray* tris, float* split);
243 | float findSplitPlaneZ(struct iBVHNode* node, struct TriangleArray* tris, float* split);
244 |
245 | #define BINNED
246 | //splits a node into two, currently just splitting the largest axis
247 | void SubdivideNode(struct BVH* bvh, struct TriangleArray* tris, uint index) {
248 | struct iBVHNode* node = &bvh->nodes[index];
249 | struct vec3 extent = subVec3(node->AABBMax, node->AABBMin);
250 |
251 | #ifdef BINNED
252 | float splitX = 1e30f; float splitY = 1e30f; float splitZ = 1e30f;
253 | //printf("binned BVH construction\n");
254 | float costX = findSplitPlaneX(node, tris, &splitX);
255 | float costY = findSplitPlaneY(node, tris, &splitY);
256 | float costZ = findSplitPlaneZ(node, tris, &splitZ);
257 | float costLeaf = node->primCount * (extent.x * extent.y + extent.x * extent.z + extent.y * extent.z);
258 | float minCost = min(min(costX, costY), min(costZ, costLeaf));
259 |
260 | int leftCount = 0;
261 | if (minCost == costLeaf) {
262 | return;
263 | }
264 | else if (costX == minCost) {
265 | leftCount = partitionX(
266 | tris, node->firstPrim, node->firstPrim + node->primCount - 1,
267 | splitX
268 | );
269 | } else if (costY == minCost) {
270 | leftCount = partitionY(
271 | tris, node->firstPrim, node->firstPrim + node->primCount - 1,
272 | splitY
273 | );
274 | } else if (costZ == minCost) {
275 | leftCount = partitionZ(
276 | tris, node->firstPrim, node->firstPrim + node->primCount - 1,
277 | splitZ
278 | );
279 | } else {
280 | return;
281 | }
282 | leftCount -= node->firstPrim;
283 | #else
284 | int leftCount = 0;
285 | if (extent.x >= extent.y && extent.x >= extent.z) {
286 | leftCount= partitionX(
287 | tris, node->firstPrim, node->firstPrim + node->primCount - 1,
288 | extent.x * .5 + node->AABBMin.x
289 | );
290 | } else if (extent.y >= extent.z) {
291 | leftCount = partitionY(
292 | tris, node->firstPrim, node->firstPrim + node->primCount - 1,
293 | extent.y * .5 + node->AABBMin.y
294 | );
295 | } else {
296 | leftCount = partitionZ(
297 | tris, node->firstPrim, node->firstPrim + node->primCount - 1,
298 | extent.z * .5 + node->AABBMin.z
299 | );
300 | }
301 | leftCount -= node->firstPrim;
302 | #endif
303 |
304 | if (leftCount == 0 || leftCount == node->primCount) {
305 | return;
306 | }
307 |
308 | uint leftChildIdx = bvh->nodesUsed++;
309 | uint rightChildIdx = bvh->nodesUsed++;
310 |
311 | node->leftChild = leftChildIdx;
312 | node->rightChild = rightChildIdx;
313 |
314 | bvh->nodes[node->leftChild].firstPrim = node->firstPrim;
315 | bvh->nodes[node->leftChild].primCount = leftCount;
316 | bvh->nodes[node->rightChild].firstPrim = node->firstPrim + leftCount;
317 | bvh->nodes[node->rightChild].primCount = node->primCount - leftCount;
318 | node->primCount = 0;
319 |
320 | UpdateNodeBounds(bvh->nodes, tris, leftChildIdx);
321 | UpdateNodeBounds(bvh->nodes, tris, rightChildIdx);
322 |
323 | SubdivideNode(bvh, tris, leftChildIdx);
324 | SubdivideNode(bvh, tris, rightChildIdx);
325 | }
326 |
327 | //used for binned BVH construction
328 | struct Bin {
329 | struct vec3 AABBMin;
330 | struct vec3 AABBMax;
331 | int triCount;
332 | };
333 |
334 | //finds the axis and position of the plane that we should split this BVH node at
335 | //one for each component because I am lazy
336 | float findSplitPlaneX(struct iBVHNode* node, struct TriangleArray* tris, float* split) {
337 | float bMin = 1e30f; float bMax = -1e30f;
338 | for (uint i = 0; i < node->primCount; i++) {
339 | Triangle* cur = &tris->trianglePool[tris->triangles[node->firstPrim + i]];
340 | bMin = min(bMin, cur->centroid.x);
341 | bMax = max(bMax, cur->centroid.x);
342 | }
343 |
344 | if (bMin == bMax) {
345 | return 1e30f;
346 | }
347 |
348 | struct Bin bins[BINSPLITS];
349 | float step = BINSPLITS / (bMax - bMin);
350 |
351 | for (int i = 0; i < BINSPLITS; i++) {
352 | bins[i].triCount = 0;
353 | bins[i].AABBMin.x = 1e30f;
354 | bins[i].AABBMin.y = 1e30f;
355 | bins[i].AABBMin.z = 1e30f;
356 | bins[i].AABBMax.x = -1e30f;
357 | bins[i].AABBMax.y = -1e30f;
358 | bins[i].AABBMax.z = -1e30f;
359 | }
360 |
361 | for (uint i = 0; i < node->primCount; i++) {
362 | Triangle* cur = &tris->trianglePool[tris->triangles[node->firstPrim + i]];
363 | int goToBin = (cur->centroid.x - bMin) * step;
364 | if (goToBin >= BINSPLITS) {
365 | goToBin = BINSPLITS - 1;
366 | }
367 | bins[goToBin].triCount++;
368 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v0);
369 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v1);
370 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v2);
371 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v0);
372 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v1);
373 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v2);
374 | }
375 | float minCost = 1e30f;
376 | for (int i = 0; i < BINSPLITS - 1; i++) {
377 | struct vec3 LAABBMin;LAABBMin.x = 1e30f;LAABBMin.y = 1e30f;LAABBMin.z = 1e30f;
378 | struct vec3 LAABBMax;LAABBMax.x =-1e30f;LAABBMax.y =-1e30f;LAABBMax.z =-1e30f;
379 | struct vec3 RAABBMin;RAABBMin.x = 1e30f;RAABBMin.y = 1e30f;RAABBMin.z = 1e30f;
380 | struct vec3 RAABBMax;RAABBMax.x =-1e30f;RAABBMax.y =-1e30f;RAABBMax.z =-1e30f;
381 | int countLeft = 0; int countRight = 0;
382 | for (int j = 0; j <= i; j++) {
383 | LAABBMin = minVec3(LAABBMin, bins[j].AABBMin);
384 | LAABBMax = maxVec3(LAABBMax, bins[j].AABBMax);
385 | countLeft += bins[j].triCount;
386 | }
387 | for (int j = i + 1; j < BINSPLITS; j++) {
388 | RAABBMin = minVec3(RAABBMin, bins[j].AABBMin);
389 | RAABBMax = maxVec3(RAABBMax, bins[j].AABBMax);
390 | countRight += bins[j].triCount;
391 | }
392 | struct vec3 difL = subVec3(LAABBMax, LAABBMin); struct vec3 difR = subVec3(RAABBMax, RAABBMin);
393 | float cost = countLeft * (difL.x * difL.y + difL.x * difL.z + difL.y * difL.z) +
394 | countRight * (difR.x * difR.y + difR.x * difR.z + difR.y * difR.z);
395 |
396 | if (cost < minCost) {
397 | minCost = cost;
398 | *split = bMin + (i + 1) * (bMax - bMin) / BINSPLITS;
399 | }
400 | }
401 | return minCost;
402 | }
403 | float findSplitPlaneY(struct iBVHNode* node, struct TriangleArray* tris, float* split) {
404 | float bMin = 1e30f; float bMax = -1e30f;
405 | for (uint i = 0; i < node->primCount; i++) {
406 | Triangle* cur = &tris->trianglePool[tris->triangles[node->firstPrim + i]];
407 | bMin = min(bMin, cur->centroid.y);
408 | bMax = max(bMax, cur->centroid.y);
409 | }
410 |
411 | if (bMin == bMax) {
412 | return 1e30f;
413 | }
414 |
415 | struct Bin bins[BINSPLITS];
416 | float step = BINSPLITS / (bMax - bMin);
417 |
418 | for (int i = 0; i < BINSPLITS; i++) {
419 | bins[i].triCount = 0;
420 | bins[i].AABBMin.x = 1e30f;
421 | bins[i].AABBMin.y = 1e30f;
422 | bins[i].AABBMin.z = 1e30f;
423 | bins[i].AABBMax.x = -1e30f;
424 | bins[i].AABBMax.y = -1e30f;
425 | bins[i].AABBMax.z = -1e30f;
426 | }
427 |
428 | for (uint i = 0; i < node->primCount; i++) {
429 | Triangle* cur = &tris->trianglePool[tris->triangles[node->firstPrim + i]];
430 | int goToBin = (cur->centroid.y - bMin) * step;
431 | if (goToBin >= BINSPLITS) {
432 | goToBin = BINSPLITS - 1;
433 | }
434 | bins[goToBin].triCount++;
435 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v0);
436 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v1);
437 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v2);
438 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v0);
439 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v1);
440 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v2);
441 | }
442 | float minCost = 1e30f;
443 | for (int i = 0; i < BINSPLITS - 1; i++) {
444 | struct vec3 LAABBMin;LAABBMin.x = 1e30f;LAABBMin.y = 1e30f;LAABBMin.z = 1e30f;
445 | struct vec3 LAABBMax;LAABBMax.x =-1e30f;LAABBMax.y =-1e30f;LAABBMax.z =-1e30f;
446 | struct vec3 RAABBMin;RAABBMin.x = 1e30f;RAABBMin.y = 1e30f;RAABBMin.z = 1e30f;
447 | struct vec3 RAABBMax;RAABBMax.x =-1e30f;RAABBMax.y =-1e30f;RAABBMax.z =-1e30f;
448 | int countLeft = 0; int countRight = 0;
449 | for (int j = 0; j <= i; j++) {
450 | LAABBMin = minVec3(LAABBMin, bins[j].AABBMin);
451 | LAABBMax = maxVec3(LAABBMax, bins[j].AABBMax);
452 | countLeft += bins[j].triCount;
453 | }
454 | for (int j = i + 1; j < BINSPLITS; j++) {
455 | RAABBMin = minVec3(RAABBMin, bins[j].AABBMin);
456 | RAABBMax = maxVec3(RAABBMax, bins[j].AABBMax);
457 | countRight += bins[j].triCount;
458 | }
459 | struct vec3 difL = subVec3(LAABBMax, LAABBMin); struct vec3 difR = subVec3(RAABBMax, RAABBMin);
460 | float cost = countLeft * (difL.x * difL.y + difL.x * difL.z + difL.y * difL.z) +
461 | countRight * (difR.x * difR.y + difR.x * difR.z + difR.y * difR.z);
462 |
463 | if (cost < minCost) {
464 | minCost = cost;
465 | *split = bMin + (i + 1) * (bMax - bMin) / BINSPLITS;
466 | }
467 | }
468 | return minCost;
469 | }
470 | float findSplitPlaneZ(struct iBVHNode* node, struct TriangleArray* tris, float* split) {
471 | float bMin = 1e30f; float bMax = -1e30f;
472 | for (uint i = 0; i < node->primCount; i++) {
473 | Triangle* cur = &tris->trianglePool[tris->triangles[node->firstPrim + i]];
474 | bMin = min(bMin, cur->centroid.z);
475 | bMax = max(bMax, cur->centroid.z);
476 | }
477 |
478 | if (bMin == bMax) {
479 | return 1e30f;
480 | }
481 |
482 | struct Bin bins[BINSPLITS];
483 | float step = BINSPLITS / (bMax - bMin);
484 |
485 | for (int i = 0; i < BINSPLITS; i++) {
486 | bins[i].triCount = 0;
487 | bins[i].AABBMin.x = 1e30f;
488 | bins[i].AABBMin.y = 1e30f;
489 | bins[i].AABBMin.z = 1e30f;
490 | bins[i].AABBMax.x = -1e30f;
491 | bins[i].AABBMax.y = -1e30f;
492 | bins[i].AABBMax.z = -1e30f;
493 | }
494 |
495 | for (uint i = 0; i < node->primCount; i++) {
496 | Triangle* cur = &tris->trianglePool[tris->triangles[node->firstPrim + i]];
497 | int goToBin = (cur->centroid.z - bMin) * step;
498 | if (goToBin >= BINSPLITS) {
499 | goToBin = BINSPLITS - 1;
500 | }
501 | bins[goToBin].triCount++;
502 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v0);
503 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v1);
504 | bins[goToBin].AABBMin = minVec3(bins[goToBin].AABBMin, cur->v2);
505 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v0);
506 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v1);
507 | bins[goToBin].AABBMax = maxVec3(bins[goToBin].AABBMax, cur->v2);
508 | }
509 | float minCost = 1e30f;
510 | for (int i = 0; i < BINSPLITS - 1; i++) {
511 | struct vec3 LAABBMin;LAABBMin.x = 1e30f;LAABBMin.y = 1e30f;LAABBMin.z = 1e30f;
512 | struct vec3 LAABBMax;LAABBMax.x =-1e30f;LAABBMax.y =-1e30f;LAABBMax.z =-1e30f;
513 | struct vec3 RAABBMin;RAABBMin.x = 1e30f;RAABBMin.y = 1e30f;RAABBMin.z = 1e30f;
514 | struct vec3 RAABBMax;RAABBMax.x =-1e30f;RAABBMax.y =-1e30f;RAABBMax.z =-1e30f;
515 | int countLeft = 0; int countRight = 0;
516 | for (int j = 0; j <= i; j++) {
517 | LAABBMin = minVec3(LAABBMin, bins[j].AABBMin);
518 | LAABBMax = maxVec3(LAABBMax, bins[j].AABBMax);
519 | countLeft += bins[j].triCount;
520 | }
521 | for (int j = i + 1; j < BINSPLITS; j++) {
522 | RAABBMin = minVec3(RAABBMin, bins[j].AABBMin);
523 | RAABBMax = maxVec3(RAABBMax, bins[j].AABBMax);
524 | countRight += bins[j].triCount;
525 | }
526 | struct vec3 difL = subVec3(LAABBMax, LAABBMin); struct vec3 difR = subVec3(RAABBMax, RAABBMin);
527 | float cost = countLeft * (difL.x * difL.y + difL.x * difL.z + difL.y * difL.z) +
528 | countRight * (difR.x * difR.y + difR.x * difR.z + difR.y * difR.z);
529 |
530 | if (cost < minCost) {
531 | minCost = cost;
532 | *split = bMin + (i + 1) * (bMax - bMin) / BINSPLITS;
533 | }
534 | }
535 | return minCost;
536 | }
537 |
538 | //splits the triangles by their centroid as compared to the split plane
539 | //note that last is not the bounds, it is the actual primitive (bounds - 1)
540 | int partitionX(struct TriangleArray* tris, uint first, uint last, float split) {
541 | while (first <= last && last <= tris->numTris) {
542 | if (tris->trianglePool[tris->triangles[first]].centroid.x < split) {
543 | first++;
544 | } else {
545 | uint temp = tris->triangles[first];
546 | tris->triangles[first] = tris->triangles[last];
547 | tris->triangles[last] = temp;
548 | last -= 1;
549 | }
550 | }
551 | return first;
552 | }
553 | int partitionY(struct TriangleArray* tris, uint first, uint last, float split) {
554 | while (first <= last && last <= tris->numTris) {
555 | if (tris->trianglePool[tris->triangles[first]].centroid.y < split) {
556 | first++;
557 | } else {
558 | uint temp = tris->triangles[first];
559 | tris->triangles[first] = tris->triangles[last];
560 | tris->triangles[last] = temp;
561 | last -= 1;
562 | }
563 | }
564 | return first;
565 | }
566 | int partitionZ(struct TriangleArray* tris, uint first, uint last, float split) {
567 | while (first <= last && last <= tris->numTris) {
568 | if (tris->trianglePool[tris->triangles[first]].centroid.z < split) {
569 | first++;
570 | } else {
571 | uint temp = tris->triangles[first];
572 | tris->triangles[first] = tris->triangles[last];
573 | tris->triangles[last] = temp;
574 | last -= 1;
575 | }
576 | }
577 | return first;
578 | }
579 |
580 |
581 | //rewrites the array of vertices to a better format and calculate the centroids
582 | struct TriangleArray genTriArray(float* inTris, int numTris) {
583 | struct Triangle* trianglePool = (struct Triangle*) malloc(sizeof(struct Triangle) * numTris);
584 | Tri* triangles = (Tri*) malloc(sizeof(Tri) * numTris);
585 | for (int i = 0; i < numTris; i++) {
586 | triangles[i] = i;
587 | trianglePool[i].v0.x = inTris[9 * i + 0];
588 | trianglePool[i].v0.y = inTris[9 * i + 1];
589 | trianglePool[i].v0.z = inTris[9 * i + 2];
590 | trianglePool[i].v1.x = inTris[9 * i + 3];
591 | trianglePool[i].v1.y = inTris[9 * i + 4];
592 | trianglePool[i].v1.z = inTris[9 * i + 5];
593 | trianglePool[i].v2.x = inTris[9 * i + 6];
594 | trianglePool[i].v2.y = inTris[9 * i + 7];
595 | trianglePool[i].v2.z = inTris[9 * i + 8];
596 | trianglePool[i].centroid = multVec3(
597 | addVec3(addVec3(trianglePool[i].v0, trianglePool[i].v1), trianglePool[i].v2), .333333f
598 | );
599 | }
600 |
601 | struct TriangleArray returned;
602 | returned.numTris = numTris;
603 | returned.trianglePool = trianglePool;
604 | returned.triangles = triangles;
605 | return returned;
606 | }
607 |
608 | //helper functions
609 | struct vec3 addVec3(struct vec3 x, struct vec3 y) {
610 | struct vec3 returned;
611 | returned.x = x.x + y.x;
612 | returned.y = x.y + y.y;
613 | returned.z = x.z + y.z;
614 | return returned;
615 | }
616 | struct vec3 subVec3(struct vec3 a, struct vec3 b) {
617 | struct vec3 returned;
618 | returned.x = a.x - b.x;
619 | returned.y = a.y - b.y;
620 | returned.z = a.z - b.z;
621 | return returned;
622 | }
623 | struct vec3 multVec3(struct vec3 x, float a) {
624 | struct vec3 returned;
625 | returned.x = x.x * a;
626 | returned.y = x.y * a;
627 | returned.z = x.z * a;
628 | return returned;
629 | }
630 | struct vec3 ftoVec3(float a) {
631 | struct vec3 returned;
632 | returned.x = a; returned.y = a; returned.z = a;
633 | return returned;
634 | }
635 | float min(float a, float b) {
636 | return a < b ? a : b;
637 | }
638 | struct vec3 minVec3(struct vec3 a, struct vec3 b) {
639 | struct vec3 returned;
640 | returned.x = min(a.x, b.x);
641 | returned.y = min(a.y, b.y);
642 | returned.z = min(a.z, b.z);
643 | return returned;
644 | }
645 | float max(float a, float b) {
646 | return a > b ? a : b;
647 | }
648 | struct vec3 maxVec3(struct vec3 a, struct vec3 b) {
649 | struct vec3 returned;
650 | returned.x = max(a.x, b.x);
651 | returned.y = max(a.y, b.y);
652 | returned.z = max(a.z, b.z);
653 | return returned;
654 | }
--------------------------------------------------------------------------------
/src/C/hdrToFloats.cc:
--------------------------------------------------------------------------------
1 | //from: http://www.graphics.cornell.edu/online/formats/rgbe/
2 | #ifndef _H_RGBE
3 | #define _H_RGBE
4 | /* THIS CODE CARRIES NO GUARANTEE OF USABILITY OR FITNESS FOR ANY PURPOSE.
5 | * WHILE THE AUTHORS HAVE TRIED TO ENSURE THE PROGRAM WORKS CORRECTLY,
6 | * IT IS STRICTLY USE AT YOUR OWN RISK. */
7 |
8 | /* utility for reading and writing Ward's rgbe image format.
9 | See rgbe.txt file for more details.
10 | */
11 |
12 | #include
13 |
14 | typedef struct {
15 | int valid; /* indicate which fields are valid */
16 | char programtype[16]; /* listed at beginning of file to identify it
17 | * after "#?". defaults to "RGBE" */
18 | float gamma; /* image has already been gamma corrected with
19 | * given gamma. defaults to 1.0 (no correction) */
20 | float exposure; /* a value of 1.0 in an image corresponds to
21 | * watts/steradian/m^2.
22 | * defaults to 1.0 */
23 | } rgbe_header_info;
24 |
25 | /* flags indicating which fields in an rgbe_header_info are valid */
26 | #define RGBE_VALID_PROGRAMTYPE 0x01
27 | #define RGBE_VALID_GAMMA 0x02
28 | #define RGBE_VALID_EXPOSURE 0x04
29 |
30 | /* return codes for rgbe routines */
31 | #define RGBE_RETURN_SUCCESS 0
32 | #define RGBE_RETURN_FAILURE -1
33 |
34 | /* read or write headers */
35 | /* you may set rgbe_header_info to null if you want to */
36 | int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info);
37 | int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info);
38 |
39 | /* read or write pixels */
40 | /* can read or write pixels in chunks of any size including single pixels*/
41 | int RGBE_WritePixels(FILE *fp, float *data, int numpixels);
42 | int RGBE_ReadPixels(FILE *fp, float *data, int numpixels);
43 |
44 | /* read or write run length encoded files */
45 | /* must be called to read or write whole scanlines */
46 | int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
47 | int num_scanlines);
48 | int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
49 | int num_scanlines);
50 |
51 | #endif /* _H_RGBE */
52 |
53 |
54 | /* THIS CODE CARRIES NO GUARANTEE OF USABILITY OR FITNESS FOR ANY PURPOSE.
55 | * WHILE THE AUTHORS HAVE TRIED TO ENSURE THE PROGRAM WORKS CORRECTLY,
56 | * IT IS STRICTLY USE AT YOUR OWN RISK. */
57 |
58 | #include
59 | #include
60 | #include
61 | #include
62 | #include
63 | #include
64 |
65 |
66 | /* This file contains code to read and write four byte rgbe file format
67 | developed by Greg Ward. It handles the conversions between rgbe and
68 | pixels consisting of floats. The data is assumed to be an array of floats.
69 | By default there are three floats per pixel in the order red, green, blue.
70 | (RGBE_DATA_??? values control this.) Only the mimimal header reading and
71 | writing is implemented. Each routine does error checking and will return
72 | a status value as defined below. This code is intended as a skeleton so
73 | feel free to modify it to suit your needs.
74 |
75 | (Place notice here if you modified the code.)
76 | posted to http://www.graphics.cornell.edu/~bjw/
77 | written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
78 | based on code written by Greg Ward
79 | */
80 |
81 | extern "C" {
82 | EMSCRIPTEN_KEEPALIVE
83 | int hdrToFloats(int bufferSize, char* HDRIImageBuffer) {
84 | FILE* f = fopen("hello.bin", "wb");
85 | fwrite(HDRIImageBuffer, bufferSize, 1, f);
86 | fclose(f);
87 | FILE* f2 = fopen("hello.bin", "rb");
88 |
89 | int image_width, image_height;
90 | RGBE_ReadHeader(f2, &image_width, &image_height, NULL);
91 | float* image = (float*) malloc(sizeof(float) * 3 * image_width * image_height);
92 | RGBE_ReadPixels_RLE(f2, image, image_width, image_height);
93 |
94 | float* imagePadded = (float*) malloc(sizeof(float) * 4 * image_width * image_height);
95 | int size = image_width * image_height;
96 | for (int i = 0; i < size; i += 1) {
97 | imagePadded[i * 4 + 0] = image[i * 3 + 0];
98 | imagePadded[i * 4 + 1] = image[i * 3 + 1];
99 | imagePadded[i * 4 + 2] = image[i * 3 + 2];
100 | imagePadded[i * 4 + 3] = 3.1415;
101 | }
102 |
103 | int* outWrite = (int*) HDRIImageBuffer;
104 | outWrite[0] = image_width;
105 | outWrite[1] = image_height;
106 | outWrite[2] = (int) imagePadded;
107 | return 0;
108 | }
109 | }
110 |
111 | #ifdef _CPLUSPLUS
112 | /* define if your compiler understands inline commands */
113 | #define INLINE inline
114 | #else
115 | #define INLINE
116 | #endif
117 |
118 | /* offsets to red, green, and blue components in a data (float) pixel */
119 | #define RGBE_DATA_RED 0
120 | #define RGBE_DATA_GREEN 1
121 | #define RGBE_DATA_BLUE 2
122 | /* number of floats per pixel */
123 | #define RGBE_DATA_SIZE 3
124 |
125 | enum rgbe_error_codes {
126 | rgbe_read_error,
127 | rgbe_write_error,
128 | rgbe_format_error,
129 | rgbe_memory_error,
130 | };
131 |
132 | /* default error routine. change this to change error handling */
133 | static int rgbe_error(int rgbe_error_code, char *msg)
134 | {
135 | switch (rgbe_error_code) {
136 | case rgbe_read_error:
137 | perror("RGBE read error");
138 | break;
139 | case rgbe_write_error:
140 | perror("RGBE write error");
141 | break;
142 | case rgbe_format_error:
143 | fprintf(stderr,"RGBE bad file format: %s\n",msg);
144 | break;
145 | default:
146 | case rgbe_memory_error:
147 | fprintf(stderr,"RGBE error: %s\n",msg);
148 | }
149 | return RGBE_RETURN_FAILURE;
150 | }
151 |
152 | /* standard conversion from float pixels to rgbe pixels */
153 | /* note: you can remove the "inline"s if your compiler complains about it */
154 | static INLINE void
155 | float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
156 | {
157 | float v;
158 | int e;
159 |
160 | v = red;
161 | if (green > v) v = green;
162 | if (blue > v) v = blue;
163 | if (v < 1e-32) {
164 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
165 | }
166 | else {
167 | v = frexp(v,&e) * 256.0/v;
168 | rgbe[0] = (unsigned char) (red * v);
169 | rgbe[1] = (unsigned char) (green * v);
170 | rgbe[2] = (unsigned char) (blue * v);
171 | rgbe[3] = (unsigned char) (e + 128);
172 | }
173 | }
174 |
175 | /* standard conversion from rgbe to float pixels */
176 | /* note: Ward uses ldexp(col+0.5,exp-(128+8)). However we wanted pixels */
177 | /* in the range [0,1] to map back into the range [0,1]. */
178 | static INLINE void
179 | rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4])
180 | {
181 | float f;
182 |
183 | if (rgbe[3]) { /*nonzero pixel*/
184 | f = ldexp(1.0,rgbe[3]-(int)(128+8));
185 | *red = rgbe[0] * f;
186 | *green = rgbe[1] * f;
187 | *blue = rgbe[2] * f;
188 | }
189 | else
190 | *red = *green = *blue = 0.0;
191 | }
192 |
193 | /* default minimal header. modify if you want more information in header */
194 | int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info)
195 | {
196 | char *programtype = "RGBE";
197 |
198 | if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
199 | programtype = info->programtype;
200 | if (fprintf(fp,"#?%s\n",programtype) < 0)
201 | return rgbe_error(rgbe_write_error,NULL);
202 | /* The #? is to identify file type, the programtype is optional. */
203 | if (info && (info->valid & RGBE_VALID_GAMMA)) {
204 | if (fprintf(fp,"GAMMA=%g\n",info->gamma) < 0)
205 | return rgbe_error(rgbe_write_error,NULL);
206 | }
207 | if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
208 | if (fprintf(fp,"EXPOSURE=%g\n",info->exposure) < 0)
209 | return rgbe_error(rgbe_write_error,NULL);
210 | }
211 | if (fprintf(fp,"FORMAT=32-bit_rle_rgbe\n\n") < 0)
212 | return rgbe_error(rgbe_write_error,NULL);
213 | if (fprintf(fp, "-Y %d +X %d\n", height, width) < 0)
214 | return rgbe_error(rgbe_write_error,NULL);
215 | return RGBE_RETURN_SUCCESS;
216 | }
217 |
218 | /* minimal header reading. modify if you want to parse more information */
219 | int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info)
220 | {
221 | char buf[128];
222 | int found_format;
223 | float tempf;
224 | int i;
225 |
226 | found_format = 0;
227 | if (info) {
228 | info->valid = 0;
229 | info->programtype[0] = 0;
230 | info->gamma = info->exposure = 1.0;
231 | }
232 | if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == NULL)
233 | return rgbe_error(rgbe_read_error,NULL);
234 | if ((buf[0] != '#')||(buf[1] != '?')) {
235 | /* if you want to require the magic token then uncomment the next line */
236 | /*return rgbe_error(rgbe_format_error,"bad initial token"); */
237 | }
238 | else if (info) {
239 | info->valid |= RGBE_VALID_PROGRAMTYPE;
240 | for(i=0;iprogramtype)-1;i++) {
241 | if ((buf[i+2] == 0) || isspace(buf[i+2]))
242 | break;
243 | info->programtype[i] = buf[i+2];
244 | }
245 | info->programtype[i] = 0;
246 | if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
247 | return rgbe_error(rgbe_read_error,NULL);
248 | }
249 | for(;;) {
250 | if ((buf[0] == 0)||(buf[0] == '\n'))
251 | return rgbe_error(rgbe_format_error,"no FORMAT specifier found");
252 | else if (strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0)
253 | break; /* format found so break out of loop */
254 | else if (info && (sscanf(buf,"GAMMA=%g",&tempf) == 1)) {
255 | info->gamma = tempf;
256 | info->valid |= RGBE_VALID_GAMMA;
257 | }
258 | else if (info && (sscanf(buf,"EXPOSURE=%g",&tempf) == 1)) {
259 | info->exposure = tempf;
260 | info->valid |= RGBE_VALID_EXPOSURE;
261 | }
262 | if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
263 | return rgbe_error(rgbe_read_error,NULL);
264 | }
265 | if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
266 | return rgbe_error(rgbe_read_error,NULL);
267 | if (strcmp(buf,"\n") != 0)
268 | return rgbe_error(rgbe_format_error,
269 | "missing blank line after FORMAT specifier");
270 | if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
271 | return rgbe_error(rgbe_read_error,NULL);
272 | if (sscanf(buf,"-Y %d +X %d",height,width) < 2)
273 | return rgbe_error(rgbe_format_error,"missing image size specifier");
274 | return RGBE_RETURN_SUCCESS;
275 | }
276 |
277 | /* simple write routine that does not use run length encoding */
278 | /* These routines can be made faster by allocating a larger buffer and
279 | fread-ing and fwrite-ing the data in larger chunks */
280 | int RGBE_WritePixels(FILE *fp, float *data, int numpixels)
281 | {
282 | unsigned char rgbe[4];
283 |
284 | while (numpixels-- > 0) {
285 | float2rgbe(rgbe,data[RGBE_DATA_RED],
286 | data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
287 | data += RGBE_DATA_SIZE;
288 | if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1)
289 | return rgbe_error(rgbe_write_error,NULL);
290 | }
291 | return RGBE_RETURN_SUCCESS;
292 | }
293 |
294 | /* simple read routine. will not correctly handle run length encoding */
295 | int RGBE_ReadPixels(FILE *fp, float *data, int numpixels)
296 | {
297 | unsigned char rgbe[4];
298 |
299 | while(numpixels-- > 0) {
300 | if (fread(rgbe, sizeof(rgbe), 1, fp) < 1) {
301 | printf("with %i pixels remaining\n", numpixels);
302 | return rgbe_error(rgbe_read_error,NULL);
303 | }
304 |
305 | rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
306 | &data[RGBE_DATA_BLUE],rgbe);
307 | data += RGBE_DATA_SIZE;
308 | }
309 | return RGBE_RETURN_SUCCESS;
310 | }
311 |
312 | /* The code below is only needed for the run-length encoded files. */
313 | /* Run length encoding adds considerable complexity but does */
314 | /* save some space. For each scanline, each channel (r,g,b,e) is */
315 | /* encoded separately for better compression. */
316 |
317 | static int RGBE_WriteBytes_RLE(FILE *fp, unsigned char *data, int numbytes)
318 | {
319 | #define MINRUNLENGTH 4
320 | int cur, beg_run, run_count, old_run_count, nonrun_count;
321 | unsigned char buf[2];
322 |
323 | cur = 0;
324 | while(cur < numbytes) {
325 | beg_run = cur;
326 | /* find next run of length at least 4 if one exists */
327 | run_count = old_run_count = 0;
328 | while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) {
329 | beg_run += run_count;
330 | old_run_count = run_count;
331 | run_count = 1;
332 | while((data[beg_run] == data[beg_run + run_count])
333 | && (beg_run + run_count < numbytes) && (run_count < 127))
334 | run_count++;
335 | }
336 | /* if data before next big run is a short run then write it as such */
337 | if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) {
338 | buf[0] = 128 + old_run_count; /*write short run*/
339 | buf[1] = data[cur];
340 | if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
341 | return rgbe_error(rgbe_write_error,NULL);
342 | cur = beg_run;
343 | }
344 | /* write out bytes until we reach the start of the next run */
345 | while(cur < beg_run) {
346 | nonrun_count = beg_run - cur;
347 | if (nonrun_count > 128)
348 | nonrun_count = 128;
349 | buf[0] = nonrun_count;
350 | if (fwrite(buf,sizeof(buf[0]),1,fp) < 1)
351 | return rgbe_error(rgbe_write_error,NULL);
352 | if (fwrite(&data[cur],sizeof(data[0])*nonrun_count,1,fp) < 1)
353 | return rgbe_error(rgbe_write_error,NULL);
354 | cur += nonrun_count;
355 | }
356 | /* write out next run if one was found */
357 | if (run_count >= MINRUNLENGTH) {
358 | buf[0] = 128 + run_count;
359 | buf[1] = data[beg_run];
360 | if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
361 | return rgbe_error(rgbe_write_error,NULL);
362 | cur += run_count;
363 | }
364 | }
365 | return RGBE_RETURN_SUCCESS;
366 | #undef MINRUNLENGTH
367 | }
368 |
369 | int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
370 | int num_scanlines)
371 | {
372 | unsigned char rgbe[4];
373 | unsigned char *buffer;
374 | int i, err;
375 |
376 | if ((scanline_width < 8)||(scanline_width > 0x7fff))
377 | /* run length encoding is not allowed so write flat*/
378 | return RGBE_WritePixels(fp,data,scanline_width*num_scanlines);
379 | buffer = (unsigned char *)malloc(sizeof(unsigned char)*4*scanline_width);
380 | if (buffer == NULL)
381 | /* no buffer space so write flat */
382 | return RGBE_WritePixels(fp,data,scanline_width*num_scanlines);
383 | while(num_scanlines-- > 0) {
384 | rgbe[0] = 2;
385 | rgbe[1] = 2;
386 | rgbe[2] = scanline_width >> 8;
387 | rgbe[3] = scanline_width & 0xFF;
388 | if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1) {
389 | free(buffer);
390 | return rgbe_error(rgbe_write_error,NULL);
391 | }
392 | for(i=0;i 0x7fff))
423 | /* run length encoding is not allowed so read flat*/
424 | return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines);
425 | scanline_buffer = NULL;
426 | /* read in each successive scanline */
427 | while(num_scanlines > 0) {
428 | if (fread(rgbe,sizeof(rgbe),1,fp) < 1) {
429 | free(scanline_buffer);
430 | return rgbe_error(rgbe_read_error,NULL);
431 | }
432 | if ((rgbe[0] != 2)||(rgbe[1] != 2)||(rgbe[2] & 0x80)) {
433 | /* this file is not run length encoded */
434 | rgbe2float(&data[0],&data[1],&data[2],rgbe);
435 | data += RGBE_DATA_SIZE;
436 | free(scanline_buffer);
437 | return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines-1);
438 | }
439 | if ((((int)rgbe[2])<<8 | rgbe[3]) != scanline_width) {
440 | free(scanline_buffer);
441 | return rgbe_error(rgbe_format_error,"wrong scanline width");
442 | }
443 | if (scanline_buffer == NULL)
444 | scanline_buffer = (unsigned char *)
445 | malloc(sizeof(unsigned char)*4*scanline_width);
446 | if (scanline_buffer == NULL)
447 | return rgbe_error(rgbe_memory_error,"unable to allocate buffer space");
448 |
449 | ptr = &scanline_buffer[0];
450 | /* read each of the four channels for the scanline into the buffer */
451 | for(i=0;i<4;i++) {
452 | ptr_end = &scanline_buffer[(i+1)*scanline_width];
453 | while(ptr < ptr_end) {
454 | if (fread(buf,sizeof(buf[0])*2,1,fp) < 1) {
455 | free(scanline_buffer);
456 | return rgbe_error(rgbe_read_error,NULL);
457 | }
458 | if (buf[0] > 128) {
459 | /* a run of the same value */
460 | count = buf[0]-128;
461 | if ((count == 0)||(count > ptr_end - ptr)) {
462 | free(scanline_buffer);
463 | return rgbe_error(rgbe_format_error,"bad scanline data");
464 | }
465 | while(count-- > 0)
466 | *ptr++ = buf[1];
467 | }
468 | else {
469 | /* a non-run */
470 | count = buf[0];
471 | if ((count == 0)||(count > ptr_end - ptr)) {
472 | free(scanline_buffer);
473 | return rgbe_error(rgbe_format_error,"bad scanline data");
474 | }
475 | *ptr++ = buf[1];
476 | if (--count > 0) {
477 | if (fread(ptr,sizeof(*ptr)*count,1,fp) < 1) {
478 | free(scanline_buffer);
479 | return rgbe_error(rgbe_read_error,NULL);
480 | }
481 | ptr += count;
482 | }
483 | }
484 | }
485 | }
486 | /* now convert data from buffer into floats */
487 | for(i=0;i setTimeout(r, 20));
31 |
32 | bvhBuildInstance.ccall("bvhbuild", "number", ["number", "number"], [numTris, buf])
33 |
34 | const outReadValue = new Int32Array(
35 | bvhBuildInstance.HEAP8.buffer, buf, 4
36 | );
37 |
38 | const outBVH = new Float32Array(
39 | bvhBuildInstance.HEAP8.buffer, outReadValue[1], outReadValue[0] * 16
40 | );
41 | const outTris = new Float32Array(
42 | bvhBuildInstance.HEAP8.buffer, outReadValue[3], outReadValue[2] * 16
43 | );
44 |
45 | let minX = 100000000000; let minY = 100000000000; let minZ = 100000000000;
46 | let maxX = -100000000000;let maxY = -100000000000;let maxZ = -100000000000;
47 | const numVerts = numTris * 3;
48 | for (var i = 0; i < numVerts; i++) {
49 | minX = Math.min(minX, trisArr[i * 3 + 0]);
50 | minY = Math.min(minY, trisArr[i * 3 + 1]);
51 | minZ = Math.min(minZ, trisArr[i * 3 + 2]);
52 | maxX = Math.max(maxX, trisArr[i * 3 + 0]);
53 | maxY = Math.max(maxY, trisArr[i * 3 + 1]);
54 | maxZ = Math.max(maxZ, trisArr[i * 3 + 2]);
55 | }
56 |
57 | blockingElem.remove();
58 |
59 | const e = 0.;
60 | return {
61 | "bvhbuffer": outBVH,
62 | "tribuffer": outTris,
63 | "bounds": {
64 | min: {x: minX, y: minY, z: minZ},
65 | max: {x: maxX, y: maxY, z: maxZ}
66 | }
67 | };
68 | }
69 |
--------------------------------------------------------------------------------
/src/js/hdr.js:
--------------------------------------------------------------------------------
1 | import {hdrToFloats} from "../wasm/hdrToFloats.js";
2 | import hdrToFloatsModule from "../wasm/hdrToFloats.wasm";
3 |
4 | const wasm = hdrToFloats({
5 | locateFile(path) {
6 | if (path.endsWith(`.wasm`)) {
7 | return hdrToFloatsModule
8 | }
9 | return path
10 | },
11 | });
12 |
13 | export async function convertHDRtoFloat4() {
14 | const wasmInstance = await wasm;
15 |
16 | const HDRBuffer = await fetch("HDRs/outdoor0.hdr").then((response) => {return response.arrayBuffer()});
17 | var buf = wasmInstance._malloc(HDRBuffer.byteLength);
18 | wasmInstance.HEAP8.set(new Int8Array(HDRBuffer), buf);
19 |
20 | wasmInstance.ccall("hdrToFloats", "number", ["number", "number"], [HDRBuffer.byteLength, buf]);
21 |
22 | const outReadValue = new Int32Array(
23 | wasmInstance.HEAP8.buffer, buf, 3
24 | );
25 |
26 | const outHDRI = new Float32Array(
27 | wasmInstance.HEAP8.buffer, outReadValue[2], outReadValue[0] * outReadValue[1] * 4
28 | );
29 | const copyHDRI = new Float32Array(new ArrayBuffer(outHDRI.byteLength));
30 | copyHDRI.set(outHDRI);
31 |
32 | return {
33 | buffer: copyHDRI.buffer,
34 | width: outReadValue[0],
35 | height: outReadValue[1]
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | import {buildBVH} from "./bvh.js";
2 | import { convertHDRtoFloat4 } from "./hdr.js";
3 | import {init, GPUFrame, downloadImage} from "./renderFrame.js";
4 | import {
5 | createScene, sceneAddMesh,
6 | sceneMakeGPUArrays, sceneLoadObjects
7 | } from "./scene.js";
8 | import {initUI} from "./utils.js";
9 |
10 |
11 | window.onload = async () => {
12 | let theta = 0; let phi = 0; let rotationChanged = false;
13 | const uiChangedFlags = {
14 | "camera": true,
15 | "rendersize": false
16 | };
17 |
18 | //initialize all of the UI widgets/windows
19 | let {getObjects, getMaterials} = initUI(rotationCallBack, uiChangedFlags, convertMeshes);
20 |
21 | //load in the HDR
22 | const hdr = await convertHDRtoFloat4();
23 | const scene = createScene();
24 |
25 | //used by UI
26 | async function convertMeshes(arrs, callback) {
27 | for (var x in arrs) {
28 | let curArr = arrs[x];
29 | let {bvhbuffer, tribuffer, bounds} = await buildBVH(curArr["tris"]);
30 | sceneAddMesh(scene, bvhbuffer, tribuffer, bounds);
31 | callback(Math.floor(curArr["tris"].length / 9), curArr["name"]);
32 | }
33 | };
34 | //load in starter model - floor
35 | {
36 | let floor = [];
37 | let dist = 1.; let h = 0.;
38 | floor.push(-dist); floor.push(-dist); floor.push(h);
39 | floor.push(dist); floor.push(-dist); floor.push(h);
40 | floor.push(-dist); floor.push(dist); floor.push(h);
41 |
42 | floor.push(dist); floor.push(dist); floor.push(h);
43 | floor.push(-dist); floor.push(dist); floor.push(h);
44 | floor.push(dist); floor.push(-dist); floor.push(h);
45 |
46 | let {bvhbuffer, tribuffer, bounds} = await buildBVH(floor);
47 | sceneAddMesh(scene, bvhbuffer, tribuffer, bounds);
48 | }
49 | //make sure initial BVH is built
50 | document.querySelector("#new-object").onclick();
51 | sceneLoadObjects(scene, getObjects());
52 | let {bvh, tri} = sceneMakeGPUArrays(scene);
53 |
54 | const canvas = document.querySelector("#render-result");
55 | let GPUInfo = await init(canvas, bvh, tri, hdr);
56 |
57 | //make sure that the canvas is always properly in view
58 | function resizeCanvas() {
59 | const W = canvas.width; const H = canvas.height;
60 | let sHeight = .95 * window.innerHeight;
61 | let sWidth = sHeight * W / H;
62 | if (sWidth >= .95 * window.innerWidth) {
63 | sWidth = .95 * window.innerWidth;
64 | sHeight = sWidth * H / W;
65 | }
66 | document.querySelector(":root").style.setProperty("--canvas-width", sWidth + "px");
67 | document.querySelector(":root").style.setProperty("--canvas-height", sHeight + "px");
68 | } resizeCanvas();
69 | new ResizeObserver(resizeCanvas).observe(document.body);
70 |
71 | //flags for executing certain things each frame
72 | const flags = {"download": false, "exit": false};
73 | document.querySelector("#download-image").onclick = () => {
74 | flags["download"] = true;
75 | };
76 |
77 | function play() {
78 | document.querySelector("#pause").onclick = pause;
79 | document.querySelector("#pause").innerHTML = "Pause Render";
80 | frame();
81 | }
82 | function pause() {
83 | flags["exit"] = true;
84 | document.querySelector("#pause").innerHTML = "Start Render";
85 | document.querySelector("#pause").onclick = play;
86 | }
87 | play();
88 |
89 | document.querySelector("#reset").onclick = () => {
90 | GPUInfo["uniformValues"]["reset"] = true;
91 | };
92 |
93 | //main loop called each fram
94 | async function frame() {
95 | if (flags["download"]) {
96 | await downloadImage(GPUInfo);
97 | flags["download"] = false;
98 | }
99 | if (flags["exit"]) {
100 | flags["exit"] = false;
101 | return;
102 | }
103 | if (uiChangedFlags["material"]) {
104 | uiChangedFlags["material"] = false;
105 | GPUInfo["uniformValues"]["reset"] = true;
106 | GPUInfo["loadMaterials"](getMaterials());
107 | }
108 | if (rotationChanged || uiChangedFlags["camera"]) {
109 | const ct = Math.cos(theta); const st = Math.sin(theta);
110 | const cp = Math.cos(phi); const sp = Math.sin(phi);
111 | const dist = parseFloat(document.querySelector("#camDistance").value);
112 | const posX = parseFloat(document.querySelector("#camOffsetX").value);
113 | const posY = parseFloat(document.querySelector("#camOffsetY").value);
114 | const posZ = parseFloat(document.querySelector("#camOffsetZ").value);
115 | GPUInfo["uniformValues"]["position"] = [
116 | ct * cp * -dist + posX, st * cp * -dist + posY, sp * -dist + posZ
117 | ];
118 | GPUInfo["uniformValues"]["forward"] = [
119 | ct * cp, st * cp, sp
120 | ]
121 | GPUInfo["uniformValues"]["left"] = [
122 | -st, ct, 0.
123 | ];
124 | GPUInfo["uniformValues"]["focal"] = parseFloat(document.querySelector("#focal").value);
125 | GPUInfo["uniformValues"]["aperture"] = parseFloat(document.querySelector("#aperture").value);
126 | GPUInfo["uniformValues"]["reset"] = true;
127 | rotationChanged = false;
128 | uiChangedFlags["camera"] = false;
129 | }
130 | if (uiChangedFlags["scene"]) {
131 | GPUInfo["uniformValues"]["reset"] = true;
132 | sceneLoadObjects(scene, getObjects());
133 | let {bvh, tri} = sceneMakeGPUArrays(scene);
134 | GPUInfo["loadScene"](bvh, tri);
135 | uiChangedFlags["scene"] = false;
136 | }
137 | if (uiChangedFlags["rendersize"]) {
138 | await GPUInfo["resize"](parseInt(document.querySelector("#renderX").value), parseInt(document.querySelector("#renderY").value), canvas);
139 | GPUInfo["uniformValues"]["reset"] = true;
140 | uiChangedFlags["rendersize"] = false;
141 | }
142 | await GPUFrame(GPUInfo);
143 | window.requestAnimationFrame(frame);
144 | } frame();
145 |
146 | //used by UI
147 | function rotationCallBack(a, b) {
148 | theta = a; phi = b; rotationChanged = true;
149 | }
150 | };
--------------------------------------------------------------------------------
/src/js/scene.js:
--------------------------------------------------------------------------------
1 | //the scene class handles all of the meshes/materials/objects
2 | export function createScene() {
3 | return {
4 | meshes: [],
5 | objects: [],
6 | didChange: false
7 | };
8 | }
9 |
10 | //sceneAddMesh(scene, bvhbuffer, tribuffer, bounds);
11 | export function sceneAddMesh(scene, bvhbuffer, tribuffer, bounds) {
12 | const newBVH = new Float32Array(bvhbuffer.length);
13 | newBVH.set(bvhbuffer);
14 | const newTris = new Float32Array(tribuffer.length);
15 | newTris.set(tribuffer);
16 |
17 | scene.meshes.push({
18 | bounds: bounds,
19 | bvh: newBVH,
20 | tri: newTris
21 | });
22 | }
23 |
24 | function sceneAddObject(scene, meshIdx) {
25 | if (meshIdx >= scene.meshes.length) {
26 | console.error("in add object: mesh does not exist");
27 | return false;
28 | }
29 | scene.objects.push({
30 | mesh: meshIdx,
31 | material: 0,
32 | transforms: {
33 | position: {x: 0, y: 0, z: 0},
34 | scale: 1.,
35 | rotation: {x: 0, y: 0, z: 0}
36 | }
37 | });
38 | scene.didChange = true;
39 | return true;
40 | }
41 |
42 | export function sceneLoadObjects(scene, arr) {
43 | scene.objects = [];
44 | for (var x in arr) {
45 | const mesh = Math.min(arr[x].mesh, scene.meshes.length - 1);
46 |
47 | sceneAddObject(scene, mesh);
48 | scene.objects[x].material = arr[x].material;
49 | scene.objects[x].transforms = {
50 | position: {
51 | "x": arr[x].position[0], "y": arr[x].position[1], "z": arr[x].position[2]
52 | },
53 | scale: arr[x].scale,
54 | rotation: {
55 | "x": arr[x].rotation[0], "y": arr[x].rotation[1], "z": arr[x].rotation[2]
56 | }
57 | };
58 | }
59 | }
60 |
61 | //creates the TLAS bvh
62 | function BVHFromNodes(node) {
63 | node.bounds = nodesListBounds(node.objs);
64 | if (node.objs.length == 1) {
65 | return;
66 | }
67 | let xRes = findSplit(node.objs, "x");
68 | let yRes = findSplit(node.objs, "y");
69 | let zRes = findSplit(node.objs, "z");
70 |
71 | let minCost = Math.min(Math.min(xRes.sah, yRes.sah), zRes.sah);
72 | let leftArr = [];
73 | let rightArr = [];
74 | if (xRes == minCost) {
75 | for (let i = 0; i < node.objs.length; i++) {
76 | if (node.objs[i].centroid.x <= xRes.split) {
77 | leftArr.push(node.objs[i]);
78 | } else {
79 | rightArr.push(node.objs[i]);
80 | }
81 | }
82 | } else if (yRes == minCost) {
83 | for (let i = 0; i < node.objs.length; i++) {
84 | if (node.objs[i].centroid.y <= yRes.split) {
85 | leftArr.push(node.objs[i]);
86 | } else {
87 | rightArr.push(node.objs[i]);
88 | }
89 | }
90 | } else {
91 | for (let i = 0; i < node.objs.length; i++) {
92 | if (node.objs[i].centroid.z <= zRes.split) {
93 | leftArr.push(node.objs[i]);
94 | } else {
95 | rightArr.push(node.objs[i]);
96 | }
97 | }
98 | }
99 |
100 | if (leftArr.length == 0) {
101 | console.error("note - same position in costruction?");
102 | leftArr.push(rightArr.pop());
103 | } else if (rightArr.length == 0) {
104 | console.error("note - same position in construction?");
105 | rightArr.push(leftArr.pop());
106 | }
107 | node.objs = [];
108 |
109 | node.left = {
110 | objs: leftArr
111 | };
112 | node.right = {
113 | objs: rightArr
114 | };
115 |
116 | BVHFromNodes(node.left);
117 | BVHFromNodes(node.right);
118 | }
119 |
120 | //builds the TLAS bvh, combines it with the BLAS, creates buffers for GPU
121 | export function sceneMakeGPUArrays(scene) {
122 | //prep them for creating a BVH
123 | let nodes = [];
124 | for (let i = 0; i < scene.objects.length; i++) {
125 | let s = scene.objects[i].transforms.scale;
126 | let p = scene.objects[i].transforms.position;
127 | let r = scene.objects[i].transforms.rotation;
128 | let b = scene.meshes[scene.objects[i].mesh].bounds;
129 | let bl = b.min;
130 | let bh = b.max;
131 |
132 | const corners = [
133 | [bl.x * s, bl.y * s, bl.z * s],
134 | [bh.x * s, bl.y * s, bl.z * s],
135 | [bh.x * s, bh.y * s, bl.z * s],
136 | [bl.x * s, bh.y * s, bl.z * s],
137 | [bl.x * s, bl.y * s, bh.z * s],
138 | [bh.x * s, bl.y * s, bh.z * s],
139 | [bh.x * s, bh.y * s, bh.z * s],
140 | [bl.x * s, bh.y * s, bh.z * s],
141 | ];
142 |
143 | const cosX = Math.cos(r.x); const sinX = Math.sin(r.x);
144 | const cosY = Math.cos(r.y); const sinY = Math.sin(r.y);
145 | const cosZ = Math.cos(r.z); const sinZ = Math.sin(r.z);
146 | for (var x = 0; x < 8; x++) {
147 | corners[x] = [
148 | corners[x][0],
149 | corners[x][1] * cosX - corners[x][2] * sinX,
150 | corners[x][1] * sinX + corners[x][2] * cosX
151 | ];
152 | corners[x] = [
153 | corners[x][2] * sinY + corners[x][0] * cosY,
154 | corners[x][1],
155 | corners[x][2] * cosY - corners[x][0] * sinY
156 | ];
157 | corners[x] = [
158 | corners[x][0] * cosZ - corners[x][1] * sinZ,
159 | corners[x][0] * sinZ + corners[x][1] * cosZ,
160 | corners[x][2]
161 | ];
162 | }
163 |
164 | const nl = {
165 | "x": corners[0][0], "y": corners[0][1], "z": corners[0][2]
166 | };
167 | const nh = {
168 | "x": corners[0][0], "y": corners[0][1], "z": corners[0][2]
169 | };
170 | for (var x = 1; x < 8; x++) {
171 | nl.x = Math.min(nl.x, corners[x][0]);
172 | nl.y = Math.min(nl.y, corners[x][1]);
173 | nl.z = Math.min(nl.z, corners[x][2]);
174 | nh.x = Math.max(nh.x, corners[x][0]);
175 | nh.y = Math.max(nh.y, corners[x][1]);
176 | nh.z = Math.max(nh.z, corners[x][2]);
177 | }
178 |
179 | nodes.push({
180 | min: {
181 | x: p.x + nl.x,
182 | y: p.y + nl.y,
183 | z: p.z + nl.z
184 | },
185 | max: {
186 | x: p.x + nh.x,
187 | y: p.y + nh.y,
188 | z: p.z + nh.z
189 | },
190 | object: scene.objects[i]
191 | });
192 | let last = nodes[nodes.length - 1];
193 | last.centroid = {
194 | x: (last.min.x + last.max.x) * .5,
195 | y: (last.min.y + last.max.y) * .5,
196 | z: (last.min.z + last.max.z) * .5
197 | };
198 | }
199 |
200 | if (nodes.length == 0) {
201 | let buf = new ArrayBuffer(64);
202 | let dv = new DataView(buf); const byteOffset = 0;
203 | dv.setUint32(byteOffset + 0, 0, true);
204 | dv.setUint32(byteOffset + 4, 0, true);
205 | dv.setUint32(byteOffset + 8, 0, true);
206 | dv.setUint32(byteOffset + 12, 0, true);
207 | dv.setUint32(byteOffset + 16, 0, true);
208 | dv.setUint32(byteOffset + 20, 0, true);
209 | dv.setUint32(byteOffset + 24, 0, true);
210 | dv.setUint32(byteOffset + 28, 99, true);
211 | dv.setUint32(byteOffset + 32, 0, true);
212 | dv.setUint32(byteOffset + 36, 0, true);
213 | dv.setUint32(byteOffset + 40, 0, true);
214 | /*-------------------------------------------*/
215 | dv.setUint32(byteOffset + 48, 0, true);
216 | dv.setUint32(byteOffset + 52, 0, true);
217 | dv.setUint32(byteOffset + 56, 0, true);
218 | /*-------------------------------------------*/
219 | return {
220 | "bvh": new Float32Array(buf),
221 | "tri": new Float32Array(buf)
222 | };
223 | }
224 |
225 | //create BVH
226 | let rootNode = {objs: nodes};
227 | BVHFromNodes(rootNode);
228 | //flatten and pack to format
229 | let flattenedBVH = [];
230 | const addNode = (node) => {
231 | let idx = flattenedBVH.length;
232 | flattenedBVH.push(node);
233 | if (node.left) {
234 | addNode(node.left);
235 | flattenedBVH[idx].rightIndex = addNode(node.right);
236 | }
237 | return idx;
238 | }; addNode(rootNode);
239 | let packedArr = [];
240 | for (let i = 0; i < flattenedBVH.length; i++) {
241 | let newNode = {
242 | };
243 | if ("rightIndex" in flattenedBVH[i]) {//branch
244 | let rightIndex = flattenedBVH[i].rightIndex;
245 | newNode.lmin = {
246 | x: flattenedBVH[i + 1].bounds.min.x,
247 | y: flattenedBVH[i + 1].bounds.min.y,
248 | z: flattenedBVH[i + 1].bounds.min.z
249 | };
250 | newNode.lmax = {
251 | x: flattenedBVH[i + 1].bounds.max.x,
252 | y: flattenedBVH[i + 1].bounds.max.y,
253 | z: flattenedBVH[i + 1].bounds.max.z
254 | };
255 | newNode.rmin = {
256 | x: flattenedBVH[rightIndex].bounds.min.x,
257 | y: flattenedBVH[rightIndex].bounds.min.y,
258 | z: flattenedBVH[rightIndex].bounds.min.z
259 | };
260 | newNode.rmax = {
261 | x: flattenedBVH[rightIndex].bounds.max.x,
262 | y: flattenedBVH[rightIndex].bounds.max.y,
263 | z: flattenedBVH[rightIndex].bounds.max.z
264 | };
265 | newNode.right = rightIndex;
266 | } else {//leaf
267 | newNode.obj = flattenedBVH[i].objs[0].object;
268 | }
269 | packedArr.push(newNode);
270 | }
271 |
272 | //find all of the meshes that are actually used
273 | let usedMeshes = [];
274 | for (let i = 0; i < scene.objects.length; i++) {
275 | let found = false;
276 | for (let j = 0; j < usedMeshes.length; j++) {
277 | if (usedMeshes[j] === scene.objects[i].mesh) {
278 | found = true;
279 | continue;
280 | }
281 | }
282 | if (!found) {
283 | usedMeshes.push(scene.objects[i].mesh);
284 | }
285 | }
286 |
287 | let bufferByteSize = packedArr.length * 64; let meshByteOffsetMap = {};
288 | let trisCount = 0; let triOffsetMap = {};
289 | for (let i = 0; i < usedMeshes.length; i++) {
290 | meshByteOffsetMap[usedMeshes[i]] = bufferByteSize;
291 | bufferByteSize += scene.meshes[usedMeshes[i]].bvh.byteLength;
292 | triOffsetMap[usedMeshes[i]] = trisCount;
293 | trisCount += Math.floor(scene.meshes[usedMeshes[i]].tri.byteLength / 64)
294 | }
295 |
296 | for (let i = 0; i < packedArr.length; i++) {
297 | if ("obj" in packedArr[i]) {
298 | packedArr[i].ptr = Math.floor(meshByteOffsetMap[packedArr[i].obj.mesh] / 64);
299 | packedArr[i].trioff = triOffsetMap[packedArr[i].obj.mesh];
300 | }
301 | }
302 |
303 | const bvhBuffer = new ArrayBuffer(bufferByteSize);
304 | const dv = new DataView(bvhBuffer);
305 | for (let i = 0; i < packedArr.length; i++) {
306 | const byteOffset = i * 64;
307 | const c = packedArr[i];
308 | if ("obj" in c) {
309 | const p = c.obj.transforms.position;
310 | const r = c.obj.transforms.rotation;
311 | const s = c.obj.transforms.scale;
312 | const m = c.obj.material;
313 | dv.setFloat32(byteOffset + 0, p.x, true);
314 | dv.setFloat32(byteOffset + 4, p.y, true);
315 | dv.setFloat32(byteOffset + 8, p.z, true);
316 | dv.setUint32 (byteOffset + 12, 91234569, true);
317 | dv.setFloat32(byteOffset + 16, r.x, true);
318 | dv.setFloat32(byteOffset + 20, r.y, true);
319 | dv.setFloat32(byteOffset + 24, r.z, true);
320 | dv.setUint32 (byteOffset + 28, 0 , true);
321 | dv.setFloat32(byteOffset + 32, s, true);
322 | dv.setFloat32(byteOffset + 36, s, true);
323 | dv.setFloat32(byteOffset + 40, s, true);
324 | /*-------------------------------------------*/
325 | dv.setUint32 (byteOffset + 48, c.trioff, true);
326 | dv.setUint32 (byteOffset + 52, c.ptr, true);
327 | dv.setUint32 (byteOffset + 56, m, true);
328 | /*-------------------------------------------*/
329 | } else {
330 | dv.setFloat32(byteOffset + 0, c.lmin.x, true);
331 | dv.setFloat32(byteOffset + 4, c.lmin.y, true);
332 | dv.setFloat32(byteOffset + 8, c.lmin.z, true);
333 | dv.setUint32 (byteOffset + 12, c.right , true);
334 | dv.setFloat32(byteOffset + 16, c.lmax.x, true);
335 | dv.setFloat32(byteOffset + 20, c.lmax.y, true);
336 | dv.setFloat32(byteOffset + 24, c.lmax.z, true);
337 | dv.setUint32 (byteOffset + 28, 123456 , true);
338 | dv.setFloat32(byteOffset + 32, c.rmin.x, true);
339 | dv.setFloat32(byteOffset + 36, c.rmin.y, true);
340 | dv.setFloat32(byteOffset + 40, c.rmin.z, true);
341 | /*-------------------------------------------*/
342 | dv.setFloat32(byteOffset + 48, c.rmax.x, true);
343 | dv.setFloat32(byteOffset + 52, c.rmax.y, true);
344 | dv.setFloat32(byteOffset + 56, c.rmax.z, true);
345 | /*-------------------------------------------*/
346 | }
347 | }
348 |
349 | const triBuffer = new ArrayBuffer(trisCount * 64);
350 | for (let i = 0; i < usedMeshes.length; i++) {
351 | const arrBVH = new Float32Array(
352 | bvhBuffer, meshByteOffsetMap[usedMeshes[i]], scene.meshes[usedMeshes[i]].bvh.length
353 | );
354 | arrBVH.set(scene.meshes[usedMeshes[i]].bvh);
355 | const arrTri = new Float32Array(
356 | triBuffer, triOffsetMap[usedMeshes[i]] * 64, scene.meshes[usedMeshes[i]].tri.length
357 | );
358 | arrTri.set(scene.meshes[usedMeshes[i]].tri);
359 | }
360 |
361 | return {
362 | "bvh" : new Float32Array(bvhBuffer),
363 | "tri" : new Float32Array(triBuffer)
364 | };
365 | }
366 |
367 | //helper functions for BVH construction
368 | function nodesListBounds(list) {
369 | if (list.length == 0) {
370 | return {
371 | min: {x: -1e30, y: -1e30, z: -1e30},
372 | max: {x: 1e30, y: 1e30, z: 1e30}
373 | };
374 | }
375 | let minX = list[0].min.x; let minY = list[0].min.y; let minZ = list[0].min.z;
376 | let maxX = list[0].max.x; let maxY = list[0].max.y; let maxZ = list[0].max.z;
377 | for (let i = 1; i < list.length; i++) {
378 | minX = Math.min(minX, list[i].min.x);
379 | minY = Math.min(minY, list[i].min.y);
380 | minZ = Math.min(minZ, list[i].min.z);
381 | maxX = Math.max(maxX, list[i].max.x);
382 | maxY = Math.max(maxY, list[i].max.y);
383 | maxZ = Math.max(maxZ, list[i].max.z);
384 | }
385 | return {
386 | min: {
387 | x: minX, y: minY, z: minZ
388 | },
389 | max: {
390 | x: maxX, y: maxY, z: maxZ
391 | }
392 | };
393 | }
394 |
395 | function findSplit(objs, axis) {
396 | let minSAH = 999999999999.;
397 | let split;
398 | for (let i = 0; i < objs.length; i++) {
399 | if (costSplit(objs, objs[i].centroid[axis], axis) < minSAH) {
400 | split = objs[i].centroid[axis];
401 | }
402 | }
403 | return {
404 | sah: minSAH,
405 | split: split
406 | };
407 | }
408 |
409 | function costSplit(objs, split, axis) {
410 | let listLeft = [];
411 | let listRight = [];
412 | for (let i = 0; i < objs.length; i++) {
413 | if (objs[i].centroid[axis] <= split) {
414 | listLeft.push(objs[i]);
415 | } else {
416 | listRight.push(objs[i]);
417 | }
418 | }
419 | let bl = nodesListBounds(listLeft);
420 | let br = nodesListBounds(listRight);
421 | let le = {
422 | x: bl.max.x - bl.min.x,
423 | y: bl.max.y - bl.min.y,
424 | z: bl.max.z - bl.min.z
425 | };
426 | let re = {
427 | x: br.max.x - br.min.x,
428 | y: br.max.y - br.min.y,
429 | z: br.max.z - br.min.z
430 | };
431 | return listLeft.length * (le.x * le.y + le.y * le.z + le.x * le.z) +
432 | listRight.length * (re.x * re.y + re.y * re.z + re.x * re.z)
433 | }
--------------------------------------------------------------------------------
/src/js/utils.js:
--------------------------------------------------------------------------------
1 | export function initUI(callbackRotation, changedFlags, convertMeshes) {
2 | const elems = document.querySelectorAll(".expandable");
3 | for (let x = 0; x < elems.length; x++) {
4 | makeExpandable(elems[x]);
5 | }
6 |
7 | initOrientationEditor(callbackRotation);
8 |
9 | initMeshLoader(convertMeshes);
10 |
11 | for (var x = 0; x < document.querySelectorAll(".window").length; x++) {
12 | initWindow(document.querySelectorAll(".window")[x]);
13 | }
14 |
15 | document.querySelector("#camera-and-film-button").onclick = () => {
16 | document.querySelector("#camera-and-film").style.display = "";
17 | };
18 | document.querySelector("#objects-button").onclick = () => {
19 | document.querySelector("#objects").style.display = "";
20 | };
21 | document.querySelector("#mesh-button").onclick = () => {
22 | document.querySelector("#mesh").style.display = "";
23 | };
24 | document.querySelector("#material-button").onclick = () => {
25 | document.querySelector("#material").style.display = "";
26 | };
27 |
28 | let getObjects = initObjectsEditor(changedFlags);
29 | let getMaterials = initMaterialsEditor(changedFlags);
30 |
31 | //take all inputs, and use map them changing to let everything else know
32 | let UIChangeMap = {
33 | "focal": "camera",
34 | "aperture": "camera",
35 | "camOffsetX": "camera",
36 | "camOffsetY": "camera",
37 | "camOffsetZ": "camera",
38 | "camDistance": "camera",
39 | "renderX": "rendersize",
40 | "renderY": "rendersize"
41 | };
42 | const inputs = document.querySelectorAll("input");
43 | for (var x = 0; x < inputs.length; x++) {
44 | const copy = x;
45 | if (inputs[copy].getAttribute("type") === "number") {
46 | inputs[copy].addEventListener("keyup", (e) => {
47 | if (e.key === "Enter" || e.keyCode === 13) {
48 | e.preventDefault();
49 | inputs[copy].dispatchEvent(new Event("onchange"));
50 | }
51 | });
52 | inputs[copy].addEventListener("focusout", (e) => {
53 | e.preventDefault();
54 | inputs[copy].dispatchEvent(new Event("onchange"));
55 | });
56 | inputs[copy].addEventListener("onchange", () => {
57 | let value = inputs[copy].value;
58 | if (value === "" || value == null) {
59 | value = 0.;
60 | }
61 | if (inputs[copy].getAttribute("min") != null) {
62 | value = Math.max(inputs[copy].getAttribute("min"), value);
63 | }
64 | if (inputs[copy].getAttribute("max") != null) {
65 | value = Math.min(inputs[copy].getAttribute("max"), value);
66 | }
67 | if (inputs[copy].getAttribute("step") == 1) {
68 | value = Math.floor(value);
69 | }
70 | changedFlags[UIChangeMap[inputs[copy].id]] = true;
71 | inputs[copy].value = value;
72 | });
73 | }
74 | }
75 |
76 | return {getObjects, getMaterials};
77 | }
78 |
79 | function initMaterialsEditor(changedFlags) {
80 | const newMaterialButton = document.querySelector("#new-material");
81 | const materialsList = document.querySelector("#materials-list");
82 | let materialCount = 0;
83 |
84 | const materials = [
85 | "diffuse", "transmissive", "mirror", "glossy"
86 | ];
87 | let selectString = "";
88 | for (var x in materials) {
89 | selectString += ``;
90 | }
91 |
92 | const newMaterial = () => {
93 | let newElem = document.createElement("div");
94 | newElem.className = "expandable";
95 | newElem.innerHTML =
96 | `
103 |
104 |
105 |
106 |
107 | type
108 |
109 |
112 |
113 |
114 |
115 | `;
116 | materialsList.appendChild(newElem);
117 | makeExpandable(newElem);
118 | const typeSelector = newElem.querySelector("#type-selector");
119 | typeSelector.onchange = updateType;
120 | typeSelector.onchange();
121 | function updateType() {
122 | const type = newElem.querySelector("#type-selector").value;
123 | let str = "";
124 | changedFlags["material"] = true;
125 | switch (type) {
126 | case "diffuse":
127 | str =
128 | `
129 |
130 |
131 | type
132 |
133 |
136 |
137 |
138 |
139 | color R
140 |
141 |
142 |
143 |
144 |
145 | color G
146 |
147 |
148 |
149 |
150 |
151 | color B
152 |
153 |
154 |
155 |
156 | `;
157 | break;
158 | case "transmissive":
159 | str =
160 | `
161 |
162 |
163 | type
164 |
165 |
168 |
169 |
170 |
171 | i.o.r.
172 |
173 |
174 |
175 |
176 |
177 | volume color R
178 |
179 |
180 |
181 |
182 |
183 | volume color G
184 |
185 |
186 |
187 |
188 |
189 | volume color B
190 |
191 |
192 |
193 |
194 |
195 | volume density
196 |
197 |
198 |
199 |
200 | `;
201 | break;
202 | case "mirror":
203 | str =
204 | `
205 |
206 |
207 | type
208 |
209 |
212 |
213 |
214 |
215 | color R
216 |
217 |
218 |
219 |
220 |
221 | color G
222 |
223 |
224 |
225 |
226 |
227 | color B
228 |
229 |
230 |
231 |
232 | `;
233 | break;
234 | case "glossy":
235 | str =
236 | `
237 |
238 |
239 | type
240 |
241 |
244 |
245 |
246 |
247 | i.o.r.
248 |
249 |
250 |
251 |
252 |
253 | color R
254 |
255 |
256 |
257 |
258 |
259 | color G
260 |
261 |
262 |
263 |
264 |
265 | color B
266 |
267 |
268 |
269 |
270 | `;
271 | break;
272 | }
273 | newElem.querySelector(".expandable-content").innerHTML = str;
274 |
275 | const inputs = newElem.querySelector(".expandable-content").querySelectorAll("input");
276 | for (var j = 0; j < inputs.length; j++) {
277 | const copy = j;
278 | if (inputs[copy].getAttribute("type") === "number") {
279 | inputs[copy].addEventListener("keyup", (e) => {
280 | if (e.key === "Enter" || e.keyCode === 13) {
281 | e.preventDefault();
282 | inputs[copy].dispatchEvent(new Event("onchange"));
283 | }
284 | });
285 | inputs[copy].addEventListener("focusout", (e) => {
286 | e.preventDefault();
287 | inputs[copy].dispatchEvent(new Event("onchange"));
288 | });
289 | inputs[copy].addEventListener("onchange", () => {
290 | let value = inputs[copy].value;
291 | if (value === "" || value == null) {
292 | value = 0.;
293 | }
294 | if (inputs[copy].getAttribute("min") != null) {
295 | value = Math.max(inputs[copy].getAttribute("min"), value);
296 | }
297 | if (inputs[copy].getAttribute("max") != null) {
298 | value = Math.min(inputs[copy].getAttribute("max"), value);
299 | }
300 | if (inputs[copy].getAttribute("step") == 1) {
301 | value = Math.floor(value);
302 | }
303 | changedFlags["material"] = true;
304 | inputs[copy].value = value;
305 | });
306 | }
307 | }
308 |
309 | const selector = newElem.querySelector("#type-selector");
310 | selector.value = type;
311 | selector.onchange = updateType;
312 | };
313 |
314 | };
315 | newMaterial();
316 | newMaterialButton.onclick = newMaterial;
317 |
318 | return () => {
319 | const entries = materialsList.querySelectorAll("#material");
320 | const returned = [];
321 | for (var i = 0; i < entries.length; i++) {
322 | const curMaterial = {};
323 | const type = entries[i].querySelector("#type-selector").value;
324 | switch (type) {
325 | case "diffuse":
326 | curMaterial["type"] = "diffuse";
327 | curMaterial["color"] = [
328 | parseFloat(entries[i].querySelector("#color-0").value),
329 | parseFloat(entries[i].querySelector("#color-1").value),
330 | parseFloat(entries[i].querySelector("#color-2").value)
331 | ];
332 | break;
333 | case "transmissive":
334 | curMaterial["type"] = "glass";
335 | curMaterial["absorption-color"] = [
336 | parseFloat(entries[i].querySelector("#volume-color-0").value),
337 | parseFloat(entries[i].querySelector("#volume-color-1").value),
338 | parseFloat(entries[i].querySelector("#volume-color-2").value)
339 | ];
340 | curMaterial["surface-color"] = [1., 1., 1.];
341 | curMaterial["IOR"] = [
342 | parseFloat(entries[i].querySelector("#ior").value)
343 | ];
344 | curMaterial["density"] = [
345 | parseFloat(entries[i].querySelector("#volume-density").value)
346 | ];
347 | break;
348 | case "mirror":
349 | curMaterial["type"] = "mirror";
350 | curMaterial["color"] = [
351 | parseFloat(entries[i].querySelector("#color-0").value),
352 | parseFloat(entries[i].querySelector("#color-1").value),
353 | parseFloat(entries[i].querySelector("#color-2").value)
354 | ];
355 | break;
356 | case "glossy":
357 | curMaterial["type"] = "glossy";
358 | curMaterial["color"] = [
359 | parseFloat(entries[i].querySelector("#color-0").value),
360 | parseFloat(entries[i].querySelector("#color-1").value),
361 | parseFloat(entries[i].querySelector("#color-2").value)
362 | ];
363 | curMaterial["glossy-color"] = [1., 1., 1.];
364 | curMaterial["IOR"] = [
365 | parseFloat(entries[i].querySelector("#ior").value)
366 | ];
367 | break;
368 | }
369 | returned.push(curMaterial);
370 | }
371 | return returned;
372 | };
373 | }
374 |
375 | function initMeshLoader(convertMeshes) {
376 | const uploadButton = document.querySelector("#upload-mesh");
377 | let meshCount = 1;
378 | const newMeshUI = (numTris, name) => {
379 | const meshlist = document.querySelector("#meshes-list");
380 | let newElem = document.createElement("div");
381 | newElem.className = "expandable";
382 | if (name.length > 25) {
383 | name = name.substring(0, 25) + "...";
384 | }
385 | newElem.innerHTML =
386 | `
393 |
394 |
395 |
396 |
397 | index
398 |
399 |
402 |
403 |
404 |
405 | triangles
406 |
407 |
410 |
411 |
412 |
`;
413 | makeExpandable(newElem);
414 | meshlist.appendChild(newElem);
415 | };
416 |
417 | uploadButton.onclick = () => {
418 | const uploadElem = document.createElement("input");
419 | uploadElem.setAttribute("type", "file");
420 | uploadElem.click();
421 |
422 | uploadElem.onchange = () => {
423 | const fileToRead = uploadElem.files[0];
424 |
425 | if (fileToRead == null) {
426 | return;
427 | }
428 |
429 | const blockingElem = document.createElement("div");
430 | blockingElem.style.width = "100%"; blockingElem.style.height = "100%";
431 | blockingElem.style.position = "absolute"; blockingElem.style.background = "rgba(60, 60, 60, .5)";
432 | blockingElem.style["z-index"] = 100;
433 | blockingElem.style.display = "flex"; blockingElem.style.alignItems = "center"; blockingElem.style["justify-content"] = "center";
434 | const textElem = document.createElement("div"); textElem.innerHTML = "parsing file...";
435 | textElem.style["font-family"] = `'Roboto', sans-serif`; textElem.style["font-size"] = "15px"; textElem.style.color = "#cccccc"; textElem.style.cursor = "default";
436 | blockingElem.appendChild(textElem);
437 | document.body.appendChild(blockingElem);
438 |
439 | const fileReader = new FileReader();
440 | fileReader.onload = (e) => {
441 | const txt = e.target.result;
442 |
443 | let arrs; let flagNothing = false;
444 | try {
445 | arrs = parseObj(txt, fileToRead.name);
446 | } catch (error) {
447 | console.error(error);
448 | flagNothing = true;
449 | }
450 | if (!flagNothing) {
451 | convertMeshes(arrs, newMeshUI);
452 | }
453 |
454 | blockingElem.remove();
455 | };
456 |
457 | fileReader.readAsText(fileToRead, "UTF-8");
458 |
459 | };
460 | };
461 | }
462 |
463 | function parseObj(txt, filename) {
464 | const lines = txt.split("\n");
465 | let objects = [];
466 | let curObject = null;
467 | const floatRegex = /[+-]?\d+(\.\d+)?/g;
468 | let verts = [];
469 | if (lines[0].charAt(0) === "v" && lines[0].charAt(1) === " ") {
470 | curObject = {
471 | faces: [],
472 | name: filename
473 | };
474 | }
475 | for (var x = 0; x < lines.length; x++) {
476 | if (lines[x].charAt(0) === "g" || lines[x].charAt(0) === "o") {
477 | if (curObject != null) {
478 | objects.push(curObject);
479 | }
480 | curObject = {
481 | faces: [],
482 | name: lines[x].substring(2)
483 | };
484 | }
485 | if (lines[x].charAt(0) === "v" && lines[x].charAt(1) === " ") {
486 | const pos = lines[x].match(floatRegex).map((a) => {return parseFloat(a);});
487 | verts.push(pos);
488 | }
489 | if (lines[x].charAt(0) === "f") {
490 | let idxs = []; let lastSpace = false;
491 | for (var i = 0; i < lines[x].length; i++) {
492 | if (lines[x].charAt(i) === " ") {
493 | lastSpace = true;
494 | } else {
495 | lastSpace = false;
496 | }
497 |
498 | if (lastSpace && lines[x].charAt(i + 1) !== " ") {
499 | const newIdx = parseInt(lines[x].substring(i));
500 | if (isFinite(newIdx)) {
501 | idxs.push(newIdx);
502 | }
503 | }
504 | }
505 | curObject.faces.push(idxs);
506 | }
507 | }
508 | if (curObject != null) {
509 | objects.push(curObject);
510 | }
511 | let objs = [];
512 | for (var x in objects) {
513 | let curArr = []; const curObject = objects[x];
514 | for (var y = 0; y < curObject.faces.length; y++) {
515 | for (var i = 0; i < 3; i++) {
516 | for (var j = 0; j < 3; j++) {
517 | curArr.push(verts[curObject.faces[y][i] - 1][j]);
518 | }
519 | }
520 | }
521 | objs.push({
522 | name: curObject.name,
523 | tris: curArr
524 | });
525 | }
526 | return objs;
527 | }
528 |
529 | function initObjectsEditor(changedFlags) {
530 | const listElem = document.querySelector("#objects-list");
531 |
532 | let objectCount = 0;
533 |
534 | //returns an array of objects
535 | function getAllObjects() {
536 | let returned = [];
537 | let objElems = listElem.querySelectorAll(".object");
538 | for (var x = 0; x < objElems.length; x++) {
539 | let obj = {};
540 | obj["position"] = [
541 | parseFloat(objElems[x].querySelector("#object-position-x").value),
542 | parseFloat(objElems[x].querySelector("#object-position-y").value),
543 | parseFloat(objElems[x].querySelector("#object-position-z").value)
544 | ];
545 | obj["scale"] = [
546 | parseFloat(objElems[x].querySelector("#object-scale").value)
547 | ];
548 | obj["mesh"] = [
549 | parseInt(objElems[x].querySelector("#object-mesh").value)
550 | ];
551 | obj["material"] = [
552 | parseInt(objElems[x].querySelector("#object-material").value)
553 | ];
554 | obj["rotation"] = [
555 | 0, 0,
556 | //parseFloat(objElems[x].querySelector("#object-rotation-x").value * Math.PI / 180.),
557 | //parseFloat(objElems[x].querySelector("#object-rotation-y").value * Math.PI / 180.),
558 | parseFloat(objElems[x].querySelector("#object-rotation-z").value * Math.PI / 180.)
559 | ]
560 | returned.push(obj);
561 | }
562 | return returned;
563 | }
564 |
565 | const newObject = () => {
566 | let newElem = document.createElement("div");
567 | newElem.className = "expandable object"; newElem.id = `${objectCount++}`;
568 | newElem.innerHTML =
569 | `
576 |
577 |
578 |
579 |
580 | position x
581 |
582 |
583 |
584 |
585 |
586 | position y
587 |
588 |
589 |
590 |
591 |
592 | position z
593 |
594 |
595 |
596 |
597 |
598 | scale
599 |
600 |
601 |
602 |
603 |
604 | rotation z
605 |
606 |
607 |
608 |
609 |
610 | mesh
611 |
612 |
613 |
614 |
615 |
616 | material
617 |
618 |
619 |
620 |
621 |
`;
622 | /* maybe put in later? too cramped
623 |
624 |
625 | rotation phi
626 |
627 |
628 |
629 |
630 |
631 | rotation phi
632 |
633 |
634 |
635 | */
636 | makeExpandable(newElem);
637 |
638 | listElem.appendChild(newElem);
639 |
640 | document.querySelector("#new-object").onclick = newObject;
641 | changedFlags["scene"] = true;
642 |
643 | const inputs = newElem.querySelectorAll("input");
644 | for (var x = 0; x < inputs.length; x++) {
645 | const copy = x;
646 | if (inputs[copy].getAttribute("type") === "number") {
647 | inputs[copy].addEventListener("keyup", (e) => {
648 | if (e.key === "Enter" || e.keyCode === 13) {
649 | e.preventDefault();
650 | inputs[copy].dispatchEvent(new Event("onchange"));
651 | }
652 | });
653 | inputs[copy].addEventListener("focusout", (e) => {
654 | e.preventDefault();
655 | inputs[copy].dispatchEvent(new Event("onchange"));
656 | });
657 | inputs[copy].addEventListener("onchange", () => {
658 | let value = inputs[copy].value;
659 | if (value === "" || value == null) {
660 | value = 0.;
661 | }
662 | if (inputs[copy].getAttribute("min") != null) {
663 | value = Math.max(inputs[copy].getAttribute("min"), value);
664 | }
665 | if (inputs[copy].getAttribute("max") != null) {
666 | value = Math.min(inputs[copy].getAttribute("max"), value);
667 | }
668 | if (inputs[copy].getAttribute("step") == 1) {
669 | value = Math.floor(value);
670 | }
671 | //flag rebuild of bvh
672 | changedFlags["scene"] = true;
673 | inputs[copy].value = value;
674 | });
675 | }
676 | }
677 | };
678 |
679 | document.querySelector("#new-object").onclick = newObject;
680 |
681 | return getAllObjects;
682 | }
683 |
684 | //for the moveable windows
685 | function initWindow(windowElem) {
686 | let posX = 100; let posY = 100;
687 | let posCenterX = -1e60; let posCenterY = -1e60;
688 | let topbar = null;
689 | for (var x in windowElem.childNodes) {
690 | if (windowElem.childNodes[x].className === "window-bar") {
691 | topbar = windowElem.childNodes[x];
692 | }
693 | }
694 |
695 | windowElem.addEventListener("mousedown", () => {
696 | const windows = document.querySelectorAll(".window");
697 | for (var i = 0; i < windows.length; i++) {
698 | windows[i].style["z-index"] = 10;
699 | }
700 | windowElem.style["z-index"] = 11;
701 | });
702 |
703 | let closeButton = null;
704 | for (var x in topbar.childNodes) {
705 | if (topbar.childNodes[x].tagName === "BUTTON") {
706 | closeButton = topbar.childNodes[x];
707 | }
708 | }
709 | closeButton.onclick = () => {
710 | windowElem.style.display = "none";
711 | };
712 |
713 | new ResizeObserver(() => {
714 | if (posCenterX == -1e60 || posCenterY == -1e60) {
715 | return;
716 | }
717 | posX = window.innerWidth / 2. + posCenterX;
718 | posY = window.innerHeight / 2. + posCenterY;
719 | posX = clamp(posX, 0, window.innerWidth - windowElem.offsetWidth);
720 | posY = clamp(posY, 0, window.innerHeight- windowElem.offsetHeight);
721 | windowElem.style.left = posX + "px";
722 | windowElem.style.top = posY + "px";
723 | }).observe(document.body);
724 |
725 | topbar.addEventListener("mousedown", (e) => {
726 | let offsetX = e.clientX - posX;
727 | let offsetY = e.clientY - posY;
728 | function update (e) {
729 | e.preventDefault();
730 | let newX = e.clientX - offsetX;
731 | let newY = e.clientY - offsetY;
732 |
733 | newX = clamp(newX, 0, window.innerWidth - windowElem.offsetWidth);
734 | newY = clamp(newY, 0, window.innerHeight- windowElem.offsetHeight);
735 |
736 | posCenterX = newX - window.innerWidth / 2.;
737 | posCenterY = newY - window.innerHeight / 2.;
738 |
739 |
740 | windowElem.style.left = newX + "px";
741 | windowElem.style.top = newY + "px";
742 | posX = newX; posY = newY;
743 | };
744 |
745 | window.addEventListener("mousemove", update);
746 | window.addEventListener("mouseup", () => {
747 | window.removeEventListener("mousemove", update);
748 | }, {once: true});
749 | });
750 | }
751 |
752 | function clamp(val, min, max) {
753 | return Math.min(Math.max(min, val), max);
754 | }
755 |
756 | function initOrientationEditor(callback) {
757 | const elem = document.querySelector("#orientation-editor");
758 | const xElem = document.querySelector("#x");
759 | const yElem = document.querySelector("#y");
760 | const zElem = document.querySelector("#z");
761 | const negXElem = document.querySelector("#xneg");
762 | const negYElem = document.querySelector("#yneg");
763 | const negZElem = document.querySelector("#zneg");
764 |
765 | let theta = -Math.PI / 4.; let phi = -Math.PI / 16.;
766 |
767 | function setIfEndOn(thetaVal, phiVal, elem) {
768 | const doThing = () => {
769 | theta = thetaVal; phi = phiVal;
770 | updatePosition();
771 | }
772 | elem.addEventListener("mouseup", doThing);
773 | window.addEventListener("mouseup", (e) => {
774 | elem.removeEventListener("mouseup", doThing);
775 | }, {once: true});
776 | }
777 |
778 | xElem.addEventListener("mousedown", () => {
779 | setIfEndOn(Math.PI, 0, xElem);
780 | });
781 | negXElem.addEventListener("mousedown", () => {
782 | setIfEndOn(0, 0, negXElem);
783 | });
784 | yElem.addEventListener("mousedown", () => {
785 | setIfEndOn(Math.PI * 1.5, 0, yElem);
786 | });
787 | negYElem.addEventListener("mousedown", () => {
788 | setIfEndOn(Math.PI / 2., 0, negYElem);
789 | });
790 | zElem.addEventListener("mousedown", () => {
791 | setIfEndOn(Math.PI / 2., -Math.PI / 2., zElem);
792 | });
793 | negZElem.addEventListener("mousedown", () => {
794 | setIfEndOn(Math.PI / 2., Math.PI / 2., negZElem);
795 | });
796 |
797 | const xWorld = [1., 0., 0.];
798 | const yWorld = [0., 1., 0.];
799 | const zWorld = [0., 0., 1.];
800 | const xWorldNeg = [-1., 0., 0.];
801 | const yWorldNeg = [0., -1., 0.];
802 | const zWorldNeg = [0., 0., -1.];
803 |
804 | function updatePosition () {
805 | callback(theta, phi);
806 | const forward = [
807 | -Math.cos(theta) * Math.cos(phi),
808 | -Math.sin(theta) * Math.cos(phi),
809 | -Math.sin(phi)
810 | ];
811 | const right = [
812 | Math.sin(theta), -Math.cos(theta), 0
813 | ];
814 | const up = [
815 | Math.cos(theta) * Math.cos(phi - Math.PI / 2.),
816 | Math.sin(theta) * Math.cos(phi - Math.PI / 2.),
817 | Math.sin(phi - Math.PI / 2.)
818 | ];
819 | const r = 40;
820 | const c = 50;
821 | const xRel = [
822 | dot(right, xWorld) * r + c - 10.,
823 | dot(up, xWorld) * r + c - 10.,
824 | dot(forward, xWorld)
825 | ];
826 | const yRel = [
827 | dot(right, yWorld) * r + c - 10.,
828 | dot(up, yWorld) * r + c - 10.,
829 | dot(forward, yWorld)
830 | ];
831 | const zRel = [
832 | dot(right, zWorld) * r + c - 10.,
833 | dot(up, zWorld) * r + c - 10.,
834 | dot(forward, zWorld)
835 | ];
836 | const xRelNeg = [
837 | dot(right, xWorldNeg) * r + c - 10.,
838 | dot(up, xWorldNeg) * r + c - 10.,
839 | dot(forward, xWorldNeg)
840 | ];
841 | const yRelNeg = [
842 | dot(right, yWorldNeg) * r + c - 10.,
843 | dot(up, yWorldNeg) * r + c - 10.,
844 | dot(forward, yWorldNeg)
845 | ];
846 | const zRelNeg = [
847 | dot(right, zWorldNeg) * r + c - 10.,
848 | dot(up, zWorldNeg) * r + c - 10.,
849 | dot(forward, zWorldNeg)
850 | ];
851 | xElem.style.left = xRel[0] + "px";
852 | xElem.style.top = xRel[1] + "px";
853 | yElem.style.left = yRel[0] + "px";
854 | yElem.style.top = yRel[1] + "px";
855 | zElem.style.left = zRel[0] + "px";
856 | zElem.style.top = zRel[1] + "px";
857 | negXElem.style.left = xRelNeg[0] + "px";
858 | negXElem.style.top = xRelNeg[1] + "px";
859 | negYElem.style.left = yRelNeg[0] + "px";
860 | negYElem.style.top = yRelNeg[1] + "px";
861 | negZElem.style.left = zRelNeg[0] + "px";
862 | negZElem.style.top = zRelNeg[1] + "px";
863 | if (xRel[2] >= 0.) {
864 | xElem.style["z-index"] = 10;
865 | } else {
866 | xElem.style["z-index"] = -10;
867 | }
868 | if (yRel[2] >= 0.) {
869 | yElem.style["z-index"] = 10;
870 | } else {
871 | yElem.style["z-index"] = -10;
872 | }
873 | if (zRel[2] >= 0.) {
874 | zElem.style["z-index"] = 10;
875 | } else {
876 | zElem.style["z-index"] = -10;
877 | }
878 | if (xRelNeg[2] >= 0.) {
879 | negXElem.style["z-index"] = 10;
880 | } else {
881 | negXElem.style["z-index"] = -10;
882 | }
883 | if (yRelNeg[2] >= 0.) {
884 | negYElem.style["z-index"] = 10;
885 | } else {
886 | negYElem.style["z-index"] = -10;
887 | }
888 | if (zRelNeg[2] >= 0.) {
889 | negZElem.style["z-index"] = 10;
890 | } else {
891 | negZElem.style["z-index"] = -10;
892 | }
893 | };
894 |
895 | updatePosition();
896 |
897 | elem.addEventListener("mousedown", (e) => {
898 | const startX = e.clientX;
899 | const startY = e.clientY;
900 | const startTheta = theta;
901 | const startPhi = phi;
902 |
903 | const onMouseMove = (e) => {
904 | e.preventDefault();
905 | theta = (e.clientX - startX) * -.01 + startTheta;
906 | phi = (e.clientY - startY) * -.01 + startPhi;
907 | updatePosition();
908 | };
909 |
910 | document.addEventListener("mousemove", onMouseMove);
911 | document.addEventListener("mouseup", () => {
912 | document.removeEventListener("mousemove", onMouseMove);
913 | }, {once: true});
914 |
915 | });
916 |
917 | function dot(v1, v2) {
918 | let returned = 0.;
919 | for (var x = 0; x < 3; x++) {
920 | returned += v1[x] * v2[x];
921 | }
922 | return returned;
923 | }
924 | }
925 |
926 | function makeExpandable(elem) {
927 | let button = null;
928 | for (var x in elem.childNodes) {
929 | if (elem.childNodes[x].tagName === "BUTTON") {
930 | button = elem.childNodes[x];
931 | }
932 | }
933 | let opened = false;
934 | button.onclick = () => {
935 | opened = !opened;
936 | let arrow;
937 | let content;
938 | for (var x in button.childNodes) {
939 | if (button.childNodes[x].className === "expandable-arrow-container") {
940 | for (var y in button.childNodes[x].childNodes) {
941 | if (button.childNodes[x].childNodes[y].className === "expandable-arrow") {
942 | arrow = button.childNodes[x].childNodes[y];
943 | }
944 | }
945 | }
946 | }
947 | for (var x in elem.childNodes) {
948 | if (elem.childNodes[x].className === "expandable-content") {
949 | content = elem.childNodes[x];
950 | }
951 | }
952 | if (opened) {
953 | content.style.display = "";
954 | arrow.id = "expanded";
955 | } else {
956 | content.style.display = "none";
957 | arrow.id = "";
958 | }
959 | };
960 | button.onclick();
961 | }
--------------------------------------------------------------------------------
/src/shaders/fullscreen.wgsl:
--------------------------------------------------------------------------------
1 | @vertex
2 | fn vs(@builtin(vertex_index) vertexIndex : u32) -> @builtin(position) vec4f {
3 | switch(vertexIndex) {
4 | case 0u: {
5 | return vec4f(1., 1., 0., 1.);}
6 | case 1u: {
7 | return vec4f(-1., 1., 0., 1.);}
8 | case 2u: {
9 | return vec4f(-1., -1., 0., 1.);}
10 | case 3u: {
11 | return vec4f(1., -1., 0., 1.);}
12 | case 4u: {
13 | return vec4f(1., 1., 0., 1.);}
14 | case 5u: {
15 | return vec4f(-1., -1., 0., 1.);}
16 | default: {
17 | return vec4f(0., 0., 0., 0.);}
18 | }
19 | }
20 |
21 | struct Uniforms {
22 | screenWidth : f32, screenHeight : f32
23 | }
24 |
25 | @group(0) @binding(0) var uniforms : Uniforms;
26 | @group(0) @binding(1) var finalColorBuffer : array;
27 |
28 | @fragment
29 | fn fs(@builtin(position) fragCoord : vec4f) -> @location(0) vec4f {
30 | var idx = u32(fragCoord.x) + u32(fragCoord.y) * u32(uniforms.screenWidth);
31 |
32 | return vec4f(toneMap(finalColorBuffer[idx].xyz), 1.);
33 | }
34 |
35 | fn toneMap(z : vec3f) -> vec3f {
36 | return z / vec3f(1. + dot(z, vec3f(.2126, .7152, .0722)));
37 | }
38 |
39 | fn ACESFilm(x : vec3f) -> vec3f {
40 | var a = 2.51;
41 | var b = 0.03;
42 | var c = 2.43;
43 | var d = 0.59;
44 | var e = 0.14;
45 | return pow((x*(a*x+b))/(x*(c*x+d)+e), vec3f(1. / 2.2));
46 | }
--------------------------------------------------------------------------------
/src/shaders/logic.wgsl:
--------------------------------------------------------------------------------
1 | //the "logic" stage of wavefront path tracing
2 |
3 | //memory layout for uniforms
4 | struct UBO {
5 | screen : vec4f,
6 | position : vec4f,//note: the a component of this stores a uint with the offset to know how many more paths to write
7 | forward : vec4f,//note: the a component of this stores a uint with the current frame number
8 | right : vec3f,
9 | focal : f32,
10 | aperture : f32
11 | };
12 | //memory layout for raycast info buffer
13 | struct RaycastInfoBufferSegment {
14 | position : vec4f,
15 | direction : vec4f
16 | };
17 | //memory layout for material result buffer
18 | struct MaterialResult {
19 | brdfpdf : vec4f,
20 | other : vec4u
21 | };
22 | //memory layout for a raycast result
23 | struct RaycastResultBufferSegment {
24 | normaldist : vec4f,
25 | other : vec4f
26 | };
27 |
28 | const Pi = 3.14159265358979323846;
29 | const InvPi = 0.31830988618379067154;
30 | const Inv2Pi = 0.15915494309189533577;
31 | const Inv4Pi = 0.07957747154594766788;
32 | const PiOver2 = 1.57079632679489661923;
33 | const PiOver4 = 0.78539816339744830961;
34 | const Sqrt2 = 1.41421356237309504880;
35 |
36 | @group(0) @binding(0) var uniforms : UBO;
37 | @group(0) @binding(1) var raycastResultBuffer : array;
38 | @group(0) @binding(2) var material : array;
39 | @group(0) @binding(3) var logicBuffer : array;
40 | @group(0) @binding(4) var materialQueueBuffer : array;
41 | @group(0) @binding(5) var newrayQueueBuffer : array;
42 | @group(0) @binding(6) var stageOneQueueCounts : array>;
43 | @group(0) @binding(7) var imageBuffer : array;
44 | @group(0) @binding(8) var iblTexture : texture_2d;
45 | @group(0) @binding(9) var raycastInfoBuffer : array;
46 |
47 | var materialQueueCount : atomic;
48 | var newrayQueueCount : atomic;
49 | var materialQueue : array;
50 | var newrayQueue : array;
51 | var numDone : atomic;
52 |
53 | @compute @workgroup_size(32)
54 | fn main(@builtin(global_invocation_id) global_id : vec3u) {
55 | if (global_id.x >= u32(uniforms.screen.x) * u32(uniforms.screen.y)) {
56 | return;
57 | }
58 |
59 | var materialResult : MaterialResult = material[global_id.x];
60 | var raycastResult : RaycastResultBufferSegment = raycastResultBuffer[global_id.x];
61 | var logicValue : vec4f = logicBuffer[global_id.x];
62 |
63 | var lastMaterialEval : bool = materialResult.other.x == bitcast(uniforms.forward.a);
64 | var hitTri : u32 = bitcast(raycastResult.other.x);
65 | var lastRayHit : bool = hitTri != 4294967295u;
66 | var firstFrame = bitcast(uniforms.forward.a) == 0u;
67 |
68 | var inputThroughput : vec3f = logicValue.xyz;
69 | var curThroughput : vec3f = select(inputThroughput, inputThroughput * materialResult.brdfpdf.xyz, lastMaterialEval);
70 |
71 | var newpath : bool = false;
72 |
73 | if (firstFrame) {
74 | imageBuffer[global_id.x] = vec4f(0.);
75 | newpath = true;
76 | }
77 |
78 | if (all(curThroughput == vec3f(0.)) && !firstFrame) {
79 | newpath = true;
80 | }
81 |
82 | if (!lastRayHit) {
83 | newpath = true;
84 | }
85 |
86 | var q : f32 = min(max(.1, 1. - curThroughput.y), .7);
87 | initSeed(global_id.xy);
88 | var r2 : vec2f = rand2();
89 | if (r2.x < q) {
90 | if (lastRayHit) {
91 | curThroughput = vec3f(0.);
92 | }
93 | newpath = true;
94 | } else {
95 | curThroughput = curThroughput / (1. - q);
96 | }
97 |
98 | var clamped = clamp(curThroughput, vec3f(0.), vec3f(10.));
99 | if (newpath && !firstFrame && all(clamped == curThroughput)) {
100 | var curValue : vec4f = imageBuffer[global_id.x];
101 | var newValue = vec4f(
102 | (curValue.xyz * curValue.a + curThroughput * getIBL(raycastInfoBuffer[global_id.x].other.xyz)) / (curValue.a + 1.),
103 | curValue.a + 1.
104 | );
105 | imageBuffer[global_id.x] = newValue;
106 | }
107 |
108 | var done : bool = atomicAdd(&numDone, 1u) == 30u;
109 | if (newpath) {
110 | var writeToIndex : u32 = atomicAdd(&newrayQueueCount, 1u);
111 | newrayQueue[writeToIndex] = global_id.x;
112 | } else {
113 | var writeToIndex : u32 = atomicAdd(&materialQueueCount, 1u);
114 | materialQueue[writeToIndex] = global_id.x;
115 | }
116 |
117 | logicBuffer[global_id.x] = vec4f(curThroughput.xyz, logicValue.a);
118 |
119 | if (done) {
120 | var matCount : u32 = atomicLoad(&materialQueueCount);
121 | var newCount : u32 = atomicLoad(&newrayQueueCount);
122 | var newpathWriteToIndex = atomicAdd(&stageOneQueueCounts[0], newCount);
123 | for (var i : u32 = 0u; i < newCount; i++) {
124 | newrayQueueBuffer[newpathWriteToIndex + i] = newrayQueue[i];
125 | }
126 | var materialWriteToIndex = atomicAdd(&stageOneQueueCounts[1], matCount);
127 | for (var i : u32 = 0u; i < matCount; i++) {
128 | materialQueueBuffer[materialWriteToIndex + i] = materialQueue[i];
129 | }
130 | }
131 | }
132 |
133 | fn getIBL(dir : vec3f) -> vec3f {
134 | var theta : f32 = atan(dir.y / dir.x) + Pi * .5;
135 | if (dir.x < 0.) {
136 | theta += Pi;
137 | }
138 | var phi = acos(dir.z);
139 | var lonlat : vec2f = vec2f(theta, phi) * vec2f(Inv2Pi, InvPi);
140 | lonlat = uniforms.screen.zw * (lonlat);
141 | return textureLoad(iblTexture, vec2i(lonlat), 0).xyz;
142 | }
143 |
144 | //from: https://www.shadertoy.com/view/XlycWh
145 | var bSeed : f32 = 0.f;
146 |
147 | fn baseHash(p : vec2u) -> u32 {
148 | var p2 : vec2u = 1103515245u*((p >> vec2u(1u))^(p.yx));
149 | var h32 : u32 = 1103515245u*((p2.x)^(p2.y>>3u));
150 | return h32^(h32 >> 16);
151 | }
152 |
153 | fn initSeed(coord : vec2u) {
154 | bSeed = f32(baseHash(coord)) / f32(0xffffffffu) + f32(bitcast(uniforms.forward.a)) * .008;
155 | }
156 |
157 | fn rand2() -> vec2f {
158 | var n : u32 = baseHash(bitcast(vec2f(bSeed + 1., bSeed + 2.)));
159 | bSeed += 2.;
160 | var rz : vec2u = vec2u(n, n * 48271u);
161 | return vec2f(rz.xy & vec2u(0x7fffffffu))/f32(0x7fffffff);
162 | }
--------------------------------------------------------------------------------
/src/shaders/material.wgsl:
--------------------------------------------------------------------------------
1 | //memory layout for uniforms
2 | struct UBO {
3 | screen : vec2f,
4 | position : vec4f,//note: the a component of this stores a uint with the offset to know how many more paths to write
5 | forward : vec4f,//note: the a component of this stores a uint with the current frame number
6 | right : vec3f,
7 | focal : f32,
8 | aperture : f32
9 | };
10 | //memory layout for raycast info buffer
11 | struct RaycastInfoBufferSegment {
12 | position : vec4f,//note: the a component of this stores the index to remap this ray to a path
13 | direction : vec4f
14 | };
15 | //memory layout for material result buffer
16 | struct MaterialResult {
17 | brdfpdf : vec4f,
18 | other : vec4u
19 | };
20 | //memory layout for a raycast result
21 | struct RaycastResultBufferSegment {
22 | normaldist : vec4f,
23 | other : vec4u
24 | };
25 | struct MatParams {
26 | param0 : vec4f,
27 | param1 : vec4f
28 | }
29 | //memory layout for the material information
30 | struct MaterialInfo {
31 | matparams : array,
32 | mattype : array
33 | }
34 | const Pi = 3.14159265358979323846;
35 | const InvPi = 0.31830988618379067154;
36 | const Inv2Pi = 0.15915494309189533577;
37 | const Inv4Pi = 0.07957747154594766788;
38 | const PiOver2 = 1.57079632679489661923;
39 | const PiOver4 = 0.78539816339744830961;
40 | const Sqrt2 = 1.41421356237309504880;
41 | @group(0) @binding(0) var uniforms : UBO;
42 | @group(0) @binding(1) var raycastInfoBuffer : array;
43 | @group(0) @binding(2) var resultBuffer : array;
44 | @group(0) @binding(3) var inQueueBuffer : array;
45 | @group(0) @binding(4) var stageTwoQueueCountsBuffer : array>;
46 | @group(0) @binding(5) var raycastQueueBuffer : array;
47 | @group(0) @binding(6) var raycastResultBuffer : array;
48 | @group(0) @binding(7) var stageOneQueueCountBuffer : array;
49 | @group(0) @binding(8) var materialInfo : MaterialInfo;
50 | var raycastQueueCount : atomic;
51 | var raycastQueue : array;
52 | @compute @workgroup_size(32)
53 | fn main(@builtin(global_invocation_id) global_id : vec3u) {
54 | if (global_id.x >= stageOneQueueCountBuffer[1]) {
55 | return;
56 | }
57 | initSeed(global_id.xy);
58 | var pathIndex = inQueueBuffer[global_id.x];
59 | var lastRaycastResult : RaycastResultBufferSegment = raycastResultBuffer[pathIndex];
60 | var lastRaycastStart : RaycastInfoBufferSegment = raycastInfoBuffer[pathIndex];
61 |
62 | var wo : vec3f = -lastRaycastStart.direction.xyz;
63 | var normal : vec3f = lastRaycastResult.normaldist.xyz;
64 | var brdfGrazingOverPdf : vec3f = vec3f(1.);
65 | var pdf : f32 = 1.;
66 | var wi : vec3f = vec3f(0.);
67 |
68 | var material : u32 = lastRaycastResult.other.z;
69 | var mattypevec : vec4u = materialInfo.mattype[material / 4u];
70 | var mattype : u32;
71 | if (material % 4 == 0) {
72 | mattype = mattypevec.x;
73 | } else if (material % 4 == 1) {
74 | mattype = mattypevec.y;
75 | } else if (material % 4 == 2) {
76 | mattype = mattypevec.z;
77 | } else {
78 | mattype = mattypevec.a;
79 | }
80 | var matparams : MatParams = materialInfo.matparams[material];
81 |
82 | var internal : bool = false;
83 | switch (mattype) {
84 | case 0u: {
85 | var albedo : vec3f = matparams.param0.xyz;
86 | var brdf : vec3f = albedo * InvPi;
87 | var sample : vec3f = cosineSampleHemisphere();
88 | wi = localToWorld(sample, normal);
89 | pdf = abs(dot(wi, normal)) * InvPi;
90 | brdfGrazingOverPdf = brdf * abs(dot(wi, normal)) / pdf;
91 | }
92 | case 1u: {
93 | var attenuationColor : vec3f = matparams.param1.xyz;
94 | var density : f32 = matparams.param1.a;
95 | var attenuationFactor : vec3f = vec3f(1.);
96 |
97 | var eta : f32 = matparams.param0.a;
98 | if (dot(normal, wo) < 0.) {
99 | internal = true; eta = 1. / eta;
100 | attenuationFactor = exp(-(vec3(1.) - attenuationColor) * density * lastRaycastResult.normaldist.a);
101 | }
102 | var R : f32 = FrDialectric(abs(dot(normal, wo)), eta);
103 | var c2 : f32 = rand2().x;
104 | //multiply by volume and surface absorption
105 | brdfGrazingOverPdf *= attenuationFactor;
106 | brdfGrazingOverPdf *= matparams.param0.xyz;
107 | if (c2 <= R || R == -1.) {
108 | pdf = 1.;
109 | wi = reflect(-wo, normal);
110 | } else {
111 | pdf = 1.;
112 | wi = refract(-wo, normal * select(1., -1., internal), 1. / eta);
113 | internal = !internal;
114 | }
115 | }
116 | case 2u: {
117 | brdfGrazingOverPdf *= matparams.param0.xyz;
118 | wi = reflect(-wo, normal);
119 | var pdf = 1.;
120 | }
121 | case 3u: {
122 | var eta : f32 = matparams.param0.a;
123 | var R : f32 = FrDialectric(abs(dot(normal, wo)), eta);
124 | var c2 : f32 = rand2().x;
125 | if (c2 <= R || R == -1.) {
126 | pdf = 1.;
127 | wi = reflect(-wo, normal);
128 | brdfGrazingOverPdf *= matparams.param1.xyz;
129 | } else {
130 | var albedo : vec3f = matparams.param0.xyz;
131 | var brdf : vec3f = albedo * InvPi;
132 |
133 | var sample : vec3f = cosineSampleHemisphere();
134 | wi = localToWorld(sample, normal);
135 |
136 | pdf = abs(dot(wi, normal)) * InvPi;
137 | brdfGrazingOverPdf = brdf * abs(dot(wi, normal)) / pdf;
138 | }
139 | }
140 | default: {
141 | brdfGrazingOverPdf = vec3f(1., 0., 0.);
142 | wi = reflect(-wo, normal);
143 | }
144 | }
145 |
146 | var result : MaterialResult;
147 | result.brdfpdf = vec4f(brdfGrazingOverPdf, pdf);
148 | result.other = vec4u(bitcast(uniforms.forward.a) + 1u, 0u, 0u, 0u);
149 | resultBuffer[pathIndex] = result;
150 |
151 | var newRay : RaycastInfoBufferSegment;
152 | newRay.position = vec4f(lastRaycastResult.normaldist.xyz * select(.00001, -.00001, internal) + lastRaycastStart.position.xyz + lastRaycastResult.normaldist.a * lastRaycastStart.direction.xyz, 1.);
153 | newRay.direction = vec4f(wi, 1.);
154 | raycastInfoBuffer[pathIndex] = newRay;
155 |
156 | var workgroupIndex : u32 = atomicAdd(&raycastQueueCount, 1u);
157 | raycastQueue[workgroupIndex] = pathIndex;
158 |
159 | if (workgroupIndex == 30u) {
160 | var stageTwoQueueWriteToIndex = atomicAdd(&stageTwoQueueCountsBuffer[0], 32u);
161 | for (var i : u32 = 0u; i < 32u; i += 1u) {
162 | raycastQueueBuffer[stageTwoQueueWriteToIndex + i] = raycastQueue[i];
163 | }
164 | }
165 | }
166 | //from: pbrt, eta = transmit to IOR / current IOR
167 | //returns negative 1 if a reflection should be done
168 | fn FrDialectric(iCosTheta : f32, eta : f32) -> f32 {
169 | var iSinTheta2 : f32 = max(0., 1. - iCosTheta * iCosTheta);
170 | var tSinTheta2 : f32 = iSinTheta2 / (eta * eta);
171 | if (tSinTheta2 >= 1.) {
172 | return -1.;
173 | }
174 | var tCosTheta : f32 = sqrt(1. - tSinTheta2);
175 |
176 | var r_par : f32 = (eta * iCosTheta - tCosTheta) / (eta * iCosTheta + tCosTheta);
177 | var r_per : f32 = (iCosTheta - eta * tCosTheta) / (iCosTheta + eta * tCosTheta);
178 |
179 | return (r_par * r_par + r_per * r_per) / 2.;
180 | }
181 | //converts from local (normal z-up) to world
182 | fn localToWorld(sample : vec3f, normal : vec3f) -> vec3f {
183 | var o1 : vec3f;
184 | if (abs(normal.x) > abs(normal.y)) {
185 | o1 = vec3f(-normal.y, normal.x, 0.);
186 | } else {
187 | o1 = vec3f(0., -normal.z, normal.y);
188 | }
189 | o1 = normalize(o1);
190 | var o2 : vec3f = normalize(cross(normal, o1));
191 |
192 | return o1 * sample.x + o2 * sample.y + normal * sample.z;
193 | }
194 |
195 | //from: pbrt
196 | fn uniformSampleDisk() -> vec2f {
197 | var r2 : vec2f = rand2();
198 | var r : f32 = sqrt(r2.x);
199 | var theta : f32 = 2. * Pi * r2.y;
200 | return vec2f(r * cos(theta), r * sin(theta));
201 | }
202 | //from: pbrt
203 | fn concentricSampleDisk() -> vec2f {
204 | var uOffset : vec2f = rand2() * 2. - vec2f(1.);
205 |
206 | if (uOffset.x == 0. && uOffset.y == 0.) {
207 | return vec2f(0.);
208 | }
209 |
210 | var theta : f32; var r : f32;
211 | if (abs(uOffset.x) > abs(uOffset.y)) {
212 | r = uOffset.x;
213 | theta = PiOver4 * (uOffset.y / uOffset.x);
214 | } else {
215 | r = uOffset.y;
216 | theta = PiOver2 - PiOver4 * (uOffset.x / uOffset.y);
217 | }
218 | return r * vec2f(cos(theta), sin(theta));
219 | }
220 | //from: pbrt
221 | fn cosineSampleHemisphere() -> vec3f {
222 | var d : vec2f = uniformSampleDisk();
223 | var z : f32 = sqrt(max(0., 1. - d.x * d.x - d.y * d.y));
224 | return vec3f(d.xy, z);
225 | }
226 | //from: https://www.shadertoy.com/view/XlycWh
227 | var bSeed : f32 = 0.f;
228 | fn baseHash(p : vec2u) -> u32 {
229 | var p2 : vec2u = 1103515245u*((p >> vec2u(1u))^(p.yx));
230 | var h32 : u32 = 1103515245u*((p2.x)^(p2.y>>3u));
231 | return h32^(h32 >> 16);
232 | }
233 | fn initSeed(coord : vec2u) {
234 | bSeed = f32(baseHash(coord)) / f32(0xffffffffu) + f32(bitcast(uniforms.forward.a)) * .008;
235 | }
236 | fn rand2() -> vec2f {
237 | var n : u32 = baseHash(bitcast(vec2f(bSeed + 1., bSeed + 2.)));
238 | bSeed += 2.;
239 | var rz : vec2u = vec2u(n, n * 48271u);
240 | return vec2f(rz.xy & vec2u(0x7fffffffu))/f32(0x7fffffff);
241 | }
--------------------------------------------------------------------------------
/src/shaders/newray.wgsl:
--------------------------------------------------------------------------------
1 | //memory layout for uniforms
2 | struct UBO {
3 | screen : vec2f,
4 | position : vec4f,//note: the a component of this stores a uint with the offset to know how many more paths to write
5 | forward : vec4f,
6 | right : vec4f,
7 | focal : f32,
8 | aperture : f32
9 | };
10 | //memory layout for raycast info buffer
11 | struct RaycastInfoBufferSegment {
12 | position : vec4f,//note: the a component of this stores the index to remap this ray to a path
13 | direction : vec4f
14 | };
15 |
16 | const Pi = 3.14159265358979323846;
17 | const InvPi = 0.31830988618379067154;
18 | const Inv2Pi = 0.15915494309189533577;
19 | const Inv4Pi = 0.07957747154594766788;
20 | const PiOver2 = 1.57079632679489661923;
21 | const PiOver4 = 0.78539816339744830961;
22 | const Sqrt2 = 1.41421356237309504880;
23 |
24 | @group(0) @binding(0) var uniforms : UBO;
25 | @group(0) @binding(1) var raycastInfoBuffer : array;
26 | @group(0) @binding(2) var logicBuffer : array;
27 | @group(0) @binding(3) var newrayQueueBuffer : array;
28 | @group(0) @binding(4) var stageTwoQueueCountsBuffer : array>;
29 | @group(0) @binding(5) var raycastQueueBuffer : array;
30 | @group(0) @binding(6) var stageOneQueueCountBuffer : array;
31 |
32 | var raycastQueueCount : atomic;
33 | var raycastQueue : array;
34 |
35 | @compute @workgroup_size(32)
36 | fn main(@builtin(global_invocation_id) global_id : vec3u) {
37 | if (global_id.x >= stageOneQueueCountBuffer[0]) {
38 | return;
39 | }
40 |
41 | var pathIndex : u32 = newrayQueueBuffer[global_id.x];
42 | var outputIndex : u32 = pathIndex;
43 | logicBuffer[pathIndex] = vec4f(1., 1., 1., bitcast(outputIndex));
44 |
45 | initSeed(global_id.xy);
46 | var spos : vec2f = vec2f(vec2u(outputIndex % u32(uniforms.screen.x), outputIndex / u32(uniforms.screen.x)));
47 | var r2 = rand2(); var theta = 2. * Pi * r2.x;
48 | spos += (r2.y * .5) * (vec2f(cos(theta), sin(theta)));
49 | var sspace : vec2f = (spos - .5 * uniforms.screen) / vec2f(uniforms.screen.y, -uniforms.screen.y);
50 | var up : vec3f = normalize(cross(uniforms.forward.xyz, uniforms.right.xyz));
51 |
52 | var raycastInfo : RaycastInfoBufferSegment;
53 | var apertureSample : vec2f = uniformSampleDisk() * uniforms.aperture;
54 | raycastInfo.position = vec4f(uniforms.position.xyz + apertureSample.x * uniforms.right.xyz + apertureSample.y * up, 1.);
55 | raycastInfo.direction = vec4f(
56 | normalize(uniforms.position.xyz + uniforms.focal * normalize(uniforms.forward.xyz * 1. - uniforms.right.xyz * sspace.x + up * sspace.y) - raycastInfo.position.xyz),
57 | -3.1415//just random packing
58 | );
59 |
60 | raycastInfoBuffer[pathIndex] = raycastInfo;
61 |
62 | var workgroupIndex : u32 = atomicAdd(&raycastQueueCount, 1u);
63 | raycastQueue[workgroupIndex] = pathIndex;
64 |
65 | if (workgroupIndex == 30u) {
66 | var stageTwoQueueWriteToIndex = atomicAdd(&stageTwoQueueCountsBuffer[0], 32u);
67 | for (var i : u32 = 0u; i < 32u; i += 1u) {
68 | raycastQueueBuffer[stageTwoQueueWriteToIndex + i] = raycastQueue[i];
69 | }
70 | }
71 | }
72 |
73 | //from: https://www.shadertoy.com/view/XlycWh
74 | var bSeed : f32 = 0.f;
75 |
76 | fn baseHash(p : vec2u) -> u32 {
77 | var p2 : vec2u = 1103515245u*((p >> vec2u(1u))^(p.yx));
78 | var h32 : u32 = 1103515245u*((p2.x)^(p2.y>>3u));
79 | return h32^(h32 >> 16);
80 | }
81 |
82 | fn initSeed(coord : vec2u) {
83 | bSeed = f32(baseHash(coord)) / f32(0xffffffffu) + f32(bitcast(uniforms.forward.a)) * .008;
84 | }
85 |
86 | fn rand2() -> vec2f {
87 | var n : u32 = baseHash(bitcast(vec2f(bSeed + 1., bSeed + 2.)));
88 | bSeed += 2.;
89 | var rz : vec2u = vec2u(n, n * 48271u);
90 | return vec2f(rz.xy & vec2u(0x7fffffffu))/f32(0x7fffffff);
91 | }
92 | //from: pbrt
93 | fn uniformSampleDisk() -> vec2f {
94 | var r2 : vec2f = rand2();
95 | var r : f32 = sqrt(r2.x);
96 | var theta : f32 = 2. * Pi * r2.y;
97 | return vec2f(r * cos(theta), r * sin(theta));
98 | }
--------------------------------------------------------------------------------
/src/shaders/raycast.wgsl:
--------------------------------------------------------------------------------
1 | //memory layout for uniforms
2 | struct UBO {
3 | screen : vec2f,
4 | position : vec4f,//note: the a component of this stores a uint with the offset to know how many more paths to write
5 | forward : vec4f,//note: the a component of this stores a uint with the current frame number
6 | right : vec3f,
7 | focal : f32,
8 | aperture : f32
9 | };
10 | //memory layout for raycast info buffer
11 | struct RaycastInfoBufferSegment {
12 | position : vec4f,//note: the a component of this stores the index to remap this ray to a path
13 | direction : vec4f
14 | };
15 | //memory layout for result buffer
16 | struct RaycastResultBufferSegment {
17 | normaldist : vec4f,
18 | other : vec4u
19 | }
20 | //memory layout for a bvh node
21 | struct BVHNode {
22 | AABBLLow : vec4f,
23 | AABBLHigh : vec4f,
24 | AABBRLow : vec4f,
25 | AABBRHigh : vec4f
26 | };
27 | //memory layout for a triangle primitive
28 | struct Triangle {
29 | v0 : vec4f,
30 | v1 : vec4f,
31 | v2 : vec4f,
32 | vx : vec4f
33 | };
34 |
35 | @group(0) @binding(0) var uniforms : UBO;
36 | @group(0) @binding(1) var bvh : array;
37 | @group(0) @binding(2) var tris : array;
38 | @group(0) @binding(3) var info : array;
39 | @group(0) @binding(4) var result : array;
40 | @group(0) @binding(5) var queue : array;
41 | @group(0) @binding(6) var stageTwoQueueCountsBuffer : array;
42 |
43 | var stack : array;
44 |
45 | @compute @workgroup_size(32)
46 | fn main(@builtin(global_invocation_id) global_id : vec3u) {
47 | if (global_id.x >= stageTwoQueueCountsBuffer[0]) {
48 | return;
49 | }
50 |
51 | var writeToIndex : u32 = queue[global_id.x];
52 | var input : RaycastInfoBufferSegment = info[writeToIndex];
53 |
54 | var o : vec3f = input.position.xyz;
55 | var d : vec3f = input.direction.xyz;
56 | var hitNormal = vec3f(0.);
57 | var stackptr : u32 = 0u;
58 | var leftHit = 0.; var rightHit = 0.;
59 | var maxDist = 987654321.;
60 | var foundTri : u32 = 4294967295u;
61 | stack[stackptr] = 0u;
62 | stackptr += 1u;
63 | var idx : u32 = 0u;
64 | var primptr = 0u; var primax = 0u;
65 | var isNew = true;
66 | var material : u32 = 0u;
67 |
68 | var curScale : f32 = 1.;
69 | var curPosition : vec3f = vec3f(0.);
70 | var curRotation = 0.;// : vec3f = vec3f(0.);
71 | var primOffset : u32 = 0u;
72 | var bvhOffset : u32 = 0u;
73 | var stackCutOff : u32 = 0u;
74 | var curMaterial : u32 = 0u;
75 |
76 | while (stackptr != 0u) {
77 | var node : BVHNode = bvh[idx];
78 |
79 | //this is a leaf
80 | if (isNew && bitcast(node.AABBLLow.a) == 0u) {
81 | primptr = bitcast(node.AABBLLow.x) + primOffset;
82 | primax = bitcast(node.AABBLLow.y) + primOffset;
83 | isNew = false;
84 | }
85 |
86 | if (stackptr <= stackCutOff) {
87 | //undo the object space transforms
88 | primOffset = 0u;
89 | bvhOffset = 0u;
90 | curMaterial = 0u;
91 | stackCutOff = 0u;
92 | var c = cos(curRotation);
93 | var s = sin(curRotation);
94 | /*o = vec3f(
95 | o.x,
96 | o.y * c.x - o.z * s.x,
97 | o.y * s.x + o.z * c.x
98 | );
99 | d = vec3f(
100 | d.x,
101 | d.y * c.x - d.z * s.x,
102 | d.y * s.x + d.z * c.x
103 | );
104 | o = vec3f(
105 | o.z * s.y + o.x * c.y,
106 | o.y,
107 | o.z * c.y - o.x * s.y
108 | );
109 | d = vec3f(
110 | d.z * s.y + d.x * c.y,
111 | d.y,
112 | d.z * c.y - d.x * s.y
113 | );*/
114 | o = vec3f(
115 | o.x * c - o.y * s,
116 | o.x * s + o.y * c,
117 | o.z
118 | );
119 | d = vec3f(
120 | d.x * c - d.y * s,
121 | d.x * s + d.y * c,
122 | d.z
123 | );
124 | o *= curScale;
125 | o += curPosition;
126 | d = normalize(d * curScale);
127 | curScale = 1.;
128 | curPosition = vec3f(0.);
129 | curRotation = 0.;//vec3f(0.);
130 | }
131 |
132 | if (bitcast(node.AABBLHigh.a) == 0u) {
133 | //apply object space transforms
134 | curPosition = node.AABBLLow.xyz;
135 | curScale = node.AABBRLow.x;
136 | curRotation = node.AABBLHigh.z;//node.AABBLHigh.xyz;
137 | var offsetsmatidx : vec4u = bitcast(node.AABBRHigh);
138 | o = o - curPosition;
139 | o = o / curScale;
140 | d = normalize(d / curScale);
141 | //apply rotation :(
142 | var c = cos(-curRotation);
143 | var s = sin(-curRotation);
144 | o = vec3f(
145 | o.x * c - o.y * s,
146 | o.x * s + o.y * c,
147 | o.z
148 | );
149 | d = vec3f(
150 | d.x * c - d.y * s,
151 | d.x * s + d.y * c,
152 | d.z
153 | );
154 | /*o = vec3f(
155 | o.z * s.y + o.x * c.y,
156 | o.y,
157 | o.z * c.y - o.x * s.y
158 | );
159 | d = vec3f(
160 | d.z * s.y + d.x * c.y,
161 | d.y,
162 | d.z * c.y - d.x * s.y
163 | );
164 | o = vec3f(
165 | o.x,
166 | o.y * c.x - o.z * s.x,
167 | o.y * s.x + o.z * c.x
168 | );
169 | d = vec3f(
170 | d.x,
171 | d.y * c.x - d.z * s.x,
172 | d.y * s.x + d.z * c.x
173 | );*/
174 | primOffset = offsetsmatidx.x;
175 | bvhOffset = offsetsmatidx.y;
176 | curMaterial = offsetsmatidx.z;
177 | idx = offsetsmatidx.y;
178 | stackCutOff = stackptr - 1u;
179 | isNew = true;
180 | continue;
181 | }
182 |
183 | //we need to intersect the triangle
184 | if (primptr < primax) {
185 | var v0 : vec3f = tris[primptr].v0.xyz;
186 | var v1 : vec3f = tris[primptr].v1.xyz;
187 | var v2 : vec3f = tris[primptr].v2.xyz;
188 |
189 | var e0 : vec3f = v1 - v0;
190 | var e1 : vec3f = v2 - v0;
191 | var pv : vec3f = cross(d, e1);
192 | var det : f32 = dot(e0, pv);
193 |
194 | var tv : vec3f = o - v0;
195 | var qv : vec3f = cross(tv, e0);
196 |
197 | var uvt : vec4f;
198 | uvt.x = dot(tv, pv); uvt.y = dot(d, qv); uvt.z = dot(e1, qv);
199 | uvt = vec4f(uvt.xyz / det, uvt.w);
200 | uvt.w = 1. - uvt.x - uvt.y;
201 | uvt.z *= curScale;
202 |
203 | if (all(uvt >= vec4f(0.)) && uvt.z < maxDist) {
204 | //this triangle is hit and is the closest
205 | maxDist = uvt.z;
206 | foundTri = primptr;
207 | material = curMaterial;
208 | hitNormal = normalize(cross(e0, e1));
209 | var c = cos(curRotation);
210 | var s = sin(curRotation);
211 | /*hitNormal = vec3f(
212 | hitNormal.x,
213 | hitNormal.y * c.x - hitNormal.z * s.x,
214 | hitNormal.y * s.x + hitNormal.z * c.x
215 | );
216 | hitNormal = vec3f(
217 | hitNormal.z * s.y + hitNormal.x * c.y,
218 | hitNormal.y,
219 | hitNormal.z * c.y - hitNormal.x * s.y
220 | );*/
221 | hitNormal = vec3f(
222 | hitNormal.x * c - hitNormal.y * s,
223 | hitNormal.x * s + hitNormal.y * c,
224 | hitNormal.z
225 | );
226 | }
227 |
228 | primptr += 1u;
229 | if (primptr >= primax) {
230 | isNew = true;
231 | stackptr -= 1u;
232 | idx = stack[stackptr];
233 | }
234 | } else {
235 | rightHit = AABBIntersect(node.AABBRLow.xyz, node.AABBRHigh.xyz, o, d);
236 | leftHit = AABBIntersect(node.AABBLLow.xyz, node.AABBLHigh.xyz, o, d);
237 |
238 | var lValid : bool = leftHit != -1e30 && curScale * leftHit <= maxDist;
239 | var rValid : bool = rightHit != -1e30 && curScale * rightHit <= maxDist;
240 |
241 | isNew = true;
242 | if (lValid && rValid) {
243 | var deferred : u32 = 0u;
244 |
245 | var leftIndex : u32 = idx + 1u;
246 | var rightIndex : u32 = bitcast(node.AABBLLow.a) + bvhOffset;
247 | if (leftHit < rightHit) {
248 | deferred = rightIndex;
249 | idx = leftIndex;
250 | } else {
251 | deferred = leftIndex;
252 | idx = rightIndex;
253 | }
254 | stack[stackptr] = deferred;
255 | stackptr += 1u;
256 | } else if (lValid) {
257 | //depth first packing means left node is always the next
258 | idx = idx + 1u;
259 | } else if (rValid) {
260 | //otherwise right node is packed into fourth component of
261 | idx = bitcast(node.AABBLLow.a) + bvhOffset;
262 | } else {
263 | stackptr -= 1u;
264 | idx = stack[stackptr];
265 | }
266 | }
267 | }
268 |
269 | var res : RaycastResultBufferSegment;
270 | res.normaldist = vec4f(hitNormal, maxDist);
271 | res.other = vec4u(
272 | u32(foundTri), bitcast(uniforms.forward.a) + 1u, material, 0u
273 | );
274 |
275 | result[writeToIndex] = res;
276 | };
277 |
278 | //for bvh-ray intersection
279 | fn AABBIntersect(low : vec3f, high : vec3f, o : vec3f, d : vec3f) -> f32 {
280 | var iDir = 1. / d;
281 | var f = (high - o) * iDir; var n = (low - o) * iDir;
282 | var tmax = max(f, n); var tmin = min(f, n);
283 | var t0 = max(tmin.x, max(tmin.y, tmin.z));
284 | var t1 = min(tmax.x, min(tmax.y, tmax.z));
285 | return select(-1e30, select(t0, -1e30, t1 < 0.), t1 >= t0);
286 | }
--------------------------------------------------------------------------------
/src/wasm/bvhbuild.js:
--------------------------------------------------------------------------------
1 |
2 | var bvhbuild = (() => {
3 | var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
4 |
5 | return (
6 | function(bvhbuild = {}) {
7 |
8 | var Module=typeof bvhbuild!="undefined"?bvhbuild:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="bvhbuild.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw"failed to load wasm binary file at '"+binaryFile+"'"}return response["arrayBuffer"]()}).catch(()=>getBinary(binaryFile))}}return Promise.resolve().then(()=>getBinary(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>{return WebAssembly.instantiate(binary,imports)}).then(instance=>{return instance}).then(receiver,reason=>{err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}else{return instantiateArrayBuffer(binaryFile,imports,callback)}}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["b"];updateMemoryViews();wasmTable=Module["asm"]["f"];addOnInit(Module["asm"]["c"]);removeRunDependency("wasm-instantiate");return exports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function getHeapMax(){return 2147483648}function emscripten_realloc_buffer(size){var b=wasmMemory.buffer;var pages=size-b.byteLength+65535>>>16;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}function getCFunc(ident){var func=Module["_"+ident];return func}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function stringToUTF8OnStack(str){var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},"array":arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;itype==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return function(){return ccall(ident,returnType,argTypes,arguments,opts)}}var wasmImports={"a":_emscripten_resize_heap};var asm=createWasm();var ___wasm_call_ctors=function(){return(___wasm_call_ctors=Module["asm"]["c"]).apply(null,arguments)};var _bvhbuild=Module["_bvhbuild"]=function(){return(_bvhbuild=Module["_bvhbuild"]=Module["asm"]["d"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["e"]).apply(null,arguments)};var ___errno_location=function(){return(___errno_location=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=function(){return(stackSave=Module["asm"]["g"]).apply(null,arguments)};var stackRestore=function(){return(stackRestore=Module["asm"]["h"]).apply(null,arguments)};var stackAlloc=function(){return(stackAlloc=Module["asm"]["i"]).apply(null,arguments)};Module["ccall"]=ccall;Module["cwrap"]=cwrap;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();
9 |
10 |
11 | return bvhbuild.ready
12 | }
13 |
14 | );
15 | })();
16 | if (typeof exports === 'object' && typeof module === 'object')
17 | module.exports = bvhbuild;
18 | else if (typeof define === 'function' && define['amd'])
19 | define([], function() { return bvhbuild; });
20 | else if (typeof exports === 'object')
21 | exports["bvhbuild"] = bvhbuild;
22 |
--------------------------------------------------------------------------------
/src/wasm/bvhbuild.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddisonPrairie/WebGPU-Path-Tracing/cddae82fee543905b733a312f0be3782f45a1c54/src/wasm/bvhbuild.wasm
--------------------------------------------------------------------------------
/src/wasm/hdrToFloats.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AddisonPrairie/WebGPU-Path-Tracing/cddae82fee543905b733a312f0be3782f45a1c54/src/wasm/hdrToFloats.wasm
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 |
4 | module.exports = {
5 | module: {
6 | rules: [
7 | {
8 | test: /\.wgsl$/i,
9 | use: "raw-loader"
10 | },
11 | {
12 | test: /\.m?js$/,
13 | enforce: 'pre',
14 | use: ['source-map-loader'],
15 | },
16 | {
17 | test: /bvhbuild\.js$/,
18 | loader: `exports-loader`,
19 | options: {
20 | type: `module`,
21 | // this MUST be equivalent to EXPORT_NAME in packages/example-wasm/complile.sh
22 | exports: `bvhbuild`,
23 | },
24 | },
25 | // wasm files should not be processed but just be emitted and we want
26 | // to have their public URL.
27 | {
28 | test: /bvhbuild\.wasm$/,
29 | type: `javascript/auto`,
30 | loader: `file-loader`,
31 | // options: {
32 | // if you add this, wasm request path will be https://domain.com/publicpath/[hash].wasm
33 | // publicPath: `static/`,
34 | // },
35 | },
36 | {
37 | test: /hdrToFloats\.js$/,
38 | loader: `exports-loader`,
39 | options: {
40 | type: `module`,
41 | // this MUST be equivalent to EXPORT_NAME in packages/example-wasm/complile.sh
42 | exports: `hdrToFloats`,
43 | },
44 | },
45 | // wasm files should not be processed but just be emitted and we want
46 | // to have their public URL.
47 | {
48 | test: /hdrToFloats\.wasm$/,
49 | type: `javascript/auto`,
50 | loader: `file-loader`,
51 | // options: {
52 | // if you add this, wasm request path will be https://domain.com/publicpath/[hash].wasm
53 | // publicPath: `static/`,
54 | // },
55 | },
56 | ]
57 | },
58 | context: path.resolve(__dirname, "."),
59 | resolve: {
60 | fallback: {
61 | crypto: false,
62 | fs: false,
63 | path: false
64 | }
65 | },
66 | entry: "./src/js/index.js",
67 | output: {
68 | filename: "main.js",
69 | path: path.resolve(__dirname, "dist"),
70 | },
71 | mode: "production"
72 | };
--------------------------------------------------------------------------------