├── .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 | ![Hannibal](https://live.staticflickr.com/65535/53023267964_7fe35e0a4b_c.jpg)

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 | 357 | 389 | 450 | 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 | 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 | `; 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 | `; 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 | }; --------------------------------------------------------------------------------