@meimu/mutracer 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/assets/bvh.worker.js +1 -1
  2. package/dist/mutracer.umd.js +3525 -3484
  3. package/docs/materials.md +27 -7
  4. package/docs/threejs.md +13 -2
  5. package/docs/viewer.md +53 -0
  6. package/lib/oidn.d.ts +1 -1
  7. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  8. package/lib/renderer/MaterialTable.d.ts +1 -0
  9. package/lib/renderer/MaterialTable.js +2 -1
  10. package/lib/renderer/MaterialTable.js.map +1 -1
  11. package/lib/renderer/WGPUBloomPass.js +18 -18
  12. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  13. package/lib/renderer/WGPUJNLMDenoiser.js +62 -62
  14. package/lib/renderer/WGPURayTraceOutput.js +14 -12
  15. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  16. package/lib/renderer/WGPURayTracePass.js +2 -2
  17. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  18. package/lib/renderer/WGPURenderingPipeline.js +20 -18
  19. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  20. package/lib/renderer/WebGLJNLMDenoiser.js +61 -61
  21. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  22. package/lib/renderer/WebGLRayTracePass.js +3 -1
  23. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  24. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
  25. package/lib/renderer/glsl/chunks/envMap.glsl.js +45 -45
  26. package/lib/renderer/glsl/chunks/intersect.glsl.js +262 -262
  27. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +111 -111
  28. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +436 -436
  29. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  30. package/lib/renderer/glsl/chunks/random.glsl.js +3 -3
  31. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +32 -32
  32. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +54 -54
  33. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +71 -71
  34. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +12 -12
  35. package/lib/renderer/glsl/chunks/sampling.glsl.js +64 -64
  36. package/lib/renderer/glsl/chunks/sobol.glsl.js +49 -49
  37. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  38. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +5 -5
  39. package/lib/renderer/glsl/depthQuery.frag.js +2 -2
  40. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +8 -8
  41. package/lib/renderer/glsl/rayTrace.frag.js +128 -128
  42. package/lib/renderer/glsl/shadowCatcher.frag.js +12 -12
  43. package/lib/renderer/glsl/webglPipeline.frag.js +49 -49
  44. package/lib/renderer/lightsUtil.js +1 -1
  45. package/lib/renderer/lightsUtil.js.map +1 -1
  46. package/lib/renderer/materialComparison.generated.js +5 -0
  47. package/lib/renderer/materialComparison.generated.js.map +1 -1
  48. package/lib/renderer/materialLayout.js +2 -1
  49. package/lib/renderer/materialLayout.js.map +1 -1
  50. package/lib/renderer/prepareMeshBVH.d.ts +4 -2
  51. package/lib/renderer/prepareMeshBVH.js +44 -20
  52. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  53. package/lib/renderer/scratchSampling.js +6 -6
  54. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  55. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +29 -29
  56. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +376 -379
  57. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  58. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +5 -5
  59. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +51 -51
  60. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +453 -453
  61. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.d.ts +1 -1
  62. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +122 -117
  63. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js.map +1 -1
  64. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  65. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +13 -13
  66. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +94 -94
  67. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +119 -119
  68. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +5 -5
  69. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +62 -62
  70. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.d.ts +3 -3
  71. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js +2 -2
  72. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js.map +1 -1
  73. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +53 -53
  74. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +5 -5
  75. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -2
  76. package/lib/renderer/wgsl/compose.frag.wgsl.js +120 -92
  77. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  78. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  79. package/lib/renderer/wgsl/depthQuery.wgsl.js +3 -3
  80. package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
  81. package/lib/renderer/wgsl/rayTrace.compute.js +220 -211
  82. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  83. package/lib/scene/OpenPBRMaterial.d.ts +4 -2
  84. package/lib/scene/OpenPBRMaterial.js +4 -2
  85. package/lib/scene/OpenPBRMaterial.js.map +1 -1
  86. package/lib/scene/openPbrSchema.d.ts +7 -2
  87. package/lib/scene/openPbrSchema.js +2 -1
  88. package/lib/scene/openPbrSchema.js.map +1 -1
  89. package/lib/util/fromTHREE.d.ts +1 -0
  90. package/lib/util/fromTHREE.js +24 -6
  91. package/lib/util/fromTHREE.js.map +1 -1
  92. package/lib/version.d.ts +1 -1
  93. package/lib/version.js +1 -1
  94. package/lib/version.js.map +1 -1
  95. package/package.json +5 -4
@@ -1 +1 @@
1
- {"version":3,"file":"intersect.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/intersect.wgsl.ts"],"names":[],"mappings":"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","sourcesContent":["// Portions adapted from ray-tracing-renderer@0.10.15\n// (https://github.com/hoverinc/ray-tracing-renderer).\n// Copyright (c) 2019 HOVER, MIT license; see LICENSES/RayTracingRenderer-MIT.txt.\n\nimport { uvProjectionConstants } from '../../MaterialTable.js';\nimport { INSTANCE_VEC4_STRIDE } from '../../bvhAccel.js';\nimport pdm_displacementWgsl from './pdm_displacement.wgsl.js';\nimport { createIntersectionAlphaWgsl } from './intersectionAlpha.wgsl.js';\n\nconst displacementWgsl = pdm_displacementWgsl;\n\nconst flatIntersectCode = function () {\n return /* wgsl */ `\nlet hit = intersectTriangle(o, d, tri, maxDim, shear, tMax);\nif (hit.t > 0.0) {\n let e1 = tri.p1 - tri.p0;\n let e2 = tri.p2 - tri.p0;\n hitNormal = normalize(cross(e1, e2));\n let sideMaterialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n let materialDoubleSided = materialsBuffer[sideMaterialIndex].doubleSided > 0.5;\n if (!doubleSided && !materialDoubleSided && dot(hitNormal, d) > 0.0) {\n hitNormal = vec3f(0.0);\n continue;\n }\n\n tMax = hit.t;\n isHit = true;\n isHitCurrent = true;\n hitPosition = o + tMax * d;\n hitBarycentric = hit.barycentric;\n}\n`;\n};\n\nfunction getPDMDisplacmentCode(anyHit: boolean) {\n return /*wgsl */ `\n let microTriangle = intersectDisplacement(\n o, d, tMax,\n tri.p0, tri.p1, tri.p2,\n n0, n1, n2,\n uv0, uv1, uv2,\n displacementMapIndex,\n getMapSize(displacementMapIndex),\n minH, maxH,\n hScale,\n matData.displacementStepSize,\n i32(matData.displacementUvProjection),\n displacementUvProjectionScale,\n ${anyHit ? 'true' : 'false'},\n isRefractionRay\n );\n`;\n}\n\nconst displacementIntersectCode = function (anyHit: boolean, hasAnisotropyDirections: boolean) {\n return /* wgsl */ `\n\nlet hScale = insData.w;\nlet minMaxH = bitcast<i32>(r3.w);\nif (abs(hScale) > 0.0 && minMaxH > 0) {\n // Scale normal by displacement scale\n let n0 = normalize(attrBuffers[index.x * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let n1 = normalize(attrBuffers[index.y * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let n2 = normalize(attrBuffers[index.z * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n\n let minH = f32(minMaxH >> 8) / 255.0;\n let maxH = f32(minMaxH & 0xff) / 255.0;\n\n let pA = tri.p0 + n0 * hScale * minH;\n let pB = tri.p1 + n1 * hScale * minH;\n let pC = tri.p2 + n2 * hScale * minH;\n let pD = tri.p0 + n0 * hScale * maxH;\n let pE = tri.p1 + n1 * hScale * maxH;\n let pF = tri.p2 + n2 * hScale * maxH;\n // TODO not support material blending\n let materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n let matData = materialsBuffer[materialIndex];\n let displacementUvProjectionScale = vec2f(matData.displacementUvProjectionScale);\n let displacementMapIndex = i32(matData.displacementMapIndex);\n\n // TODO duplicate reading of normal and uv\n var uv0 = attrBuffers[index.x * ATTR_STRIDE + UV_OFF].xy;\n var uv1 = attrBuffers[index.y * ATTR_STRIDE + UV_OFF].xy;\n var uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;\n\n ${getPDMDisplacmentCode(anyHit)}\n\n if (microTriangle.t < tMax) {\n hitNormal = microTriangle.normal;\n // TODO support double sided to improve non watertight triangles.\n\n tMax = microTriangle.t;\n // TODO barycentric is for micro triangle\n hitBarycentric = microTriangle.barycentric;\n hitPosition = o + tMax * d;\n\n isHitMicroTriangle = true;\n isHit = true;\n isHitCurrent = true;\n }\n}\nelse {\n ${flatIntersectCode()}\n}\n`;\n};\n\nconst cwbvhTraversalWgsl = /* wgsl */ `\nfn unpackCwbvhBytes(value: u32) -> vec4f {\n return vec4f(\n f32(value & 0xffu),\n f32((value >> 8u) & 0xffu),\n f32((value >> 16u) & 0xffu),\n f32(value >> 24u)\n );\n}\n\n// CWBVH calls these helpers only with nonzero 8/24-bit masks. Every such\n// integer is exactly representable as f32, whose unbiased IEEE-754 exponent\n// gives the highest set bit. This avoids both the full-width firstLeadingBit\n// polyfill on some shader backends and a serial bit-spread/popcount chain.\n// Do not use this conversion for full 32-bit masks: rounding near a power of\n// two can change their highest bit. Zero is also outside this contract.\nfn cwbvhHighestBitIndex8(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn cwbvhHighestBitIndex24(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn intersectCwbvhInterior(\n node: i32,\n bufferOffset: i32,\n n0: vec4f,\n o: vec3f,\n invD: vec3f,\n tMax: f32\n) -> u32 {\n let i = node + bufferOffset;\n let n2 = bvhBuffer[i + 2];\n let n3 = bvhBuffer[i + 3];\n let n4 = bvhBuffer[i + 4];\n let header = bitcast<u32>(n0.w);\n let exponentX = bitcast<i32>((header & 0xffu) << 24u) >> 24;\n let exponentY = bitcast<i32>(((header >> 8u) & 0xffu) << 24u) >> 24;\n let exponentZ = bitcast<i32>(((header >> 16u) & 0xffu) << 24u) >> 24;\n let quantizationScale = vec3f(\n bitcast<f32>(u32(exponentX + 127) << 23u),\n bitcast<f32>(u32(exponentY + 127) << 23u),\n bitcast<f32>(u32(exponentZ + 127) << 23u)\n );\n let padding = depthSlabPadding(invD, o);\n let originTerm = (n0.xyz - o) * invD;\n let adjustedInvD = quantizationScale * invD;\n let minX0 = unpackCwbvhBytes(bitcast<u32>(n2.x));\n let minX1 = unpackCwbvhBytes(bitcast<u32>(n2.y));\n let minY0 = unpackCwbvhBytes(bitcast<u32>(n2.z));\n let minY1 = unpackCwbvhBytes(bitcast<u32>(n2.w));\n let minZ0 = unpackCwbvhBytes(bitcast<u32>(n3.x));\n let minZ1 = unpackCwbvhBytes(bitcast<u32>(n3.y));\n let maxX0 = unpackCwbvhBytes(bitcast<u32>(n3.z));\n let maxX1 = unpackCwbvhBytes(bitcast<u32>(n3.w));\n let maxY0 = unpackCwbvhBytes(bitcast<u32>(n4.x));\n let maxY1 = unpackCwbvhBytes(bitcast<u32>(n4.y));\n let maxZ0 = unpackCwbvhBytes(bitcast<u32>(n4.z));\n let maxZ1 = unpackCwbvhBytes(bitcast<u32>(n4.w));\n\n let minTx0 = minX0 * adjustedInvD.x + originTerm.x;\n let minTy0 = minY0 * adjustedInvD.y + originTerm.y;\n let minTz0 = minZ0 * adjustedInvD.z + originTerm.z;\n let maxTx0 = maxX0 * adjustedInvD.x + originTerm.x;\n let maxTy0 = maxY0 * adjustedInvD.y + originTerm.y;\n let maxTz0 = maxZ0 * adjustedInvD.z + originTerm.z;\n let near0 = max(\n max((min(minTx0, maxTx0) - vec4f(padding.x)), (min(minTy0, maxTy0) - vec4f(padding.y))),\n (min(minTz0, maxTz0) - vec4f(padding.z))\n );\n let far0 = min(\n min((max(minTx0, maxTx0) + vec4f(padding.x)), (max(minTy0, maxTy0) + vec4f(padding.y))),\n (max(minTz0, maxTz0) + vec4f(padding.z))\n );\n\n let minTx1 = minX1 * adjustedInvD.x + originTerm.x;\n let minTy1 = minY1 * adjustedInvD.y + originTerm.y;\n let minTz1 = minZ1 * adjustedInvD.z + originTerm.z;\n let maxTx1 = maxX1 * adjustedInvD.x + originTerm.x;\n let maxTy1 = maxY1 * adjustedInvD.y + originTerm.y;\n let maxTz1 = maxZ1 * adjustedInvD.z + originTerm.z;\n let near1 = max(\n max((min(minTx1, maxTx1) - vec4f(padding.x)), (min(minTy1, maxTy1) - vec4f(padding.y))),\n (min(minTz1, maxTz1) - vec4f(padding.z))\n );\n let far1 = min(\n min((max(minTx1, maxTx1) + vec4f(padding.x)), (max(minTy1, maxTy1) + vec4f(padding.y))),\n (max(minTz1, maxTz1) + vec4f(padding.z))\n );\n\n let hits0 = (near0 <= min(far0, vec4f(tMax))) & (far0 > vec4f(0.0));\n let hits1 = (near1 <= min(far1, vec4f(tMax))) & (far1 > vec4f(0.0));\n return select(0u, 1u, hits0.x)\n | select(0u, 2u, hits0.y)\n | select(0u, 4u, hits0.z)\n | select(0u, 8u, hits0.w)\n | select(0u, 16u, hits1.x)\n | select(0u, 32u, hits1.y)\n | select(0u, 64u, hits1.z)\n | select(0u, 128u, hits1.w);\n}\n\n`;\n\nfunction getBVHTraverseCode(\n handleHitCode: string,\n hasDisplacement: boolean,\n anyHit: boolean,\n blasLayoutMask: number,\n hasAnisotropyDirections: boolean,\n skipTriangleCode = ''\n) {\n const intersectTriangles = /* wgsl */ `\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let vertexOffset = vec3i(bitcast<i32>(insData.y));\n for (var k = 0; k < primitiveCount; k++) {\n let triangleIndex = primitiveOffset + k;\n ${skipTriangleCode}\n if (bounces > 0 && triangleIndex == currentTriangleIndex) {\n continue;\n }\n \n let r3 = bvhBuffer[triangleIndex];\n let index = bitcast<vec3i>(r3.xyz) + vertexOffset;\n var tri = Triangle(\n attrBuffers[index.x * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.y * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.z * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''}\n );\n var isHitCurrent = false;\n\n ${hasDisplacement\n ? displacementIntersectCode(anyHit, hasAnisotropyDirections)\n : flatIntersectCode()}\n if (isHitCurrent) {\n ${handleHitCode}\n }\n }\n`;\n return /* wgsl */ `\nvar d = ray.d;\nvar o = ray.o;\nvar tlasStack: array<i32, TLAS_STACK_SIZE>;\nvar tlasTop = -1;\nvar invD = traversalInverse(d);\nvar isHitMicroTriangle = false;\nvar isHit = false;\nvar hitPosition: vec3f;\nvar hitNormal: vec3f;\nvar hitBarycentric: vec3f;\n\n\nif (intersectBox(o, bvhBuffer[0].xyz, bvhBuffer[1].xyz, invD, tMax) > 0.0) {\n tlasTop = 0;\n tlasStack[0] = 0;\n}\nwhile (tlasTop >= 0) {\n let tlasNode = tlasStack[tlasTop];\n tlasTop--;\n o = ray.o;\n d = ray.d;\n invD = traversalInverse(d);\n\n let tlasR1 = bvhBuffer[tlasNode];\n let tlasPointerOrPrimitiveCount = bitcast<i32>(tlasR1.w);\n if (tlasPointerOrPrimitiveCount >= 0) {\n let left = tlasNode + 2;\n let right = tlasPointerOrPrimitiveCount;\n let d0 = intersectBox(\n o,\n bvhBuffer[left].xyz,\n bvhBuffer[left + 1].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right].xyz,\n bvhBuffer[right + 1].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n }\n else {\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n }\n }\n else {\n let tlasPrimitiveCount = -tlasPointerOrPrimitiveCount;\n // The previous unified LIFO stack visited leaf instances in reverse order.\n // Preserve that order for alpha-tested any-hit rays and deterministic RNG.\n for (\n var instanceOffset = tlasPrimitiveCount - 1;\n instanceOffset >= 0;\n instanceOffset--\n ) {\n let instanceRecord = bvhBuffer[tlasNode + 2 + instanceOffset];\n let insIdx = bitcast<i32>(instanceRecord.x);\n let blasRoot = bitcast<i32>(instanceRecord.y);\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let w2l = mat4x4<f32>(\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n o = (w2l * vec4f(ray.o, 1.0)).xyz;\n d = (w2l * vec4f(ray.d, 0.0)).xyz;\n invD = traversalInverse(d);\n maxDim = maxDimension(abs(d));\n shear = getShear(d, maxDim);\n\n let encodedBlasOffset = bitcast<u32>(insData.x);\n let blasLayout = encodedBlasOffset >> 30u;\n let blasOffset = i32(encodedBlasOffset & 0x3fffffffu);\n let bufferOff = rtUniform.blasOffset;\n\n ${(blasLayoutMask & 8) !== 0 ? /* wgsl */ `\n if (blasLayout == 3u) {\n // TinyBVH's official CWBVH traversal keeps compact node groups on the\n // stack. The upper 8 bits identify intersected interior slots and the\n // lower 8 bits preserve the node's interior-slot mask for popcount\n // addressing. Triangle leaves are merged into a separate 24-bit mask.\n var cwbvhStack: array<vec2u, BLAS_STACK_SIZE>;\n var cwbvhTop = -1;\n var cwbvhVisits = 0u;\n var nodeGroupBase = 0u;\n // A synthetic slot-7 hit selects relative node zero for the root.\n var nodeGroupMask = 0x80000000u;\n let octant = select(0u, 4u, d.x < 0.0)\n | select(0u, 2u, d.y < 0.0)\n | select(0u, 1u, d.z < 0.0);\n let octantInverse = 7u - octant;\n\n // Keep a statically visible trip bound. DXC can otherwise miscompile\n // the dynamic stack-top loop into a non-terminating shader on D3D12.\n // Validated scene rays stay far below this conservative safety cap.\n while (cwbvhVisits < 4096u) {\n cwbvhVisits++;\n \n let internalMask = nodeGroupMask & 0xffu;\n let childBitIndex =\n cwbvhHighestBitIndex8(nodeGroupMask >> 24u) + 24u;\n nodeGroupMask &= ~(1u << childBitIndex);\n if ((nodeGroupMask & 0xff000000u) != 0u) {\n cwbvhTop++;\n \n cwbvhStack[cwbvhTop] = vec2u(nodeGroupBase, nodeGroupMask);\n }\n\n let slot = (childBitIndex - 24u) ^ octantInverse;\n let precedingMask = (1u << slot) - 1u;\n let relativeIndex = countOneBits(internalMask & precedingMask);\n let childNodeIndex = nodeGroupBase + relativeIndex;\n let node = blasOffset + i32(childNodeIndex * 5u);\n let compressed0 = bvhBuffer[node + bufferOff];\n let slotHits = intersectCwbvhInterior(\n node, bufferOff, compressed0, o, invD, tMax\n );\n // Empty child hits need neither metadata nor leaf processing. Clear\n // the current group so the common tail restores pending siblings.\n nodeGroupMask = 0u;\n if (slotHits != 0u) {\n let compressed1 = bvhBuffer[node + bufferOff + 1];\n let nodeInternalMask = bitcast<u32>(compressed0.w) >> 24u;\n var hitMask = 0u;\n var remainingSlotHits = slotHits;\n while (remainingSlotHits != 0u) {\n let childSlot = cwbvhHighestBitIndex8(remainingSlotHits);\n remainingSlotHits &= ~(1u << childSlot);\n let metaWord = select(\n bitcast<u32>(compressed1.z),\n bitcast<u32>(compressed1.w),\n childSlot >= 4u\n );\n let childMeta =\n (metaWord >> ((childSlot & 3u) * 8u)) & 0xffu;\n let localOffset = childMeta & 0x1fu;\n let bitIndex = select(\n localOffset,\n localOffset ^ octantInverse,\n localOffset >= 24u\n );\n let childBits = (childMeta >> 5u) & 0x7u;\n hitMask |= childBits << bitIndex;\n }\n\n nodeGroupBase = bitcast<u32>(compressed1.x);\n nodeGroupMask = (hitMask & 0xff000000u) | nodeInternalMask;\n let triangleBase = bitcast<u32>(compressed1.y);\n var triangleMask = hitMask & 0x00ffffffu;\n while (triangleMask != 0u) {\n let triangleBitIndex = cwbvhHighestBitIndex24(triangleMask);\n triangleMask &= ~(1u << triangleBitIndex);\n let primitiveCount = 1;\n let primitiveOffset = bufferOff + blasOffset\n + i32(triangleBase + triangleBitIndex);\n ${intersectTriangles}\n }\n }\n\n if ((nodeGroupMask & 0xff000000u) != 0u) { continue; }\n if (cwbvhTop >= 0) {\n let restoredGroup = cwbvhStack[cwbvhTop];\n nodeGroupBase = restoredGroup.x;\n nodeGroupMask = restoredGroup.y;\n cwbvhTop--;\n }\n else { break; }\n }\n }\n ` : ''}\n\n var blasStack: array<i32, BLAS_STACK_SIZE>;\n var blasTop = -1;\n if (blasLayout != 3u) {\n blasTop = 0;\n \n blasStack[0] = blasRoot;\n }\n\n while (blasTop >= 0) {\n \n let node = blasStack[blasTop];\n blasTop--;\n var primitiveCount = 0;\n var primitiveOffset = 0;\n\n ${(blasLayoutMask & 4) !== 0 ? /* wgsl */ `\n if (blasLayout == 2u) {\n let i = node + bufferOff;\n let gpu0 = bvhBuffer[i];\n let gpu1 = bvhBuffer[i + 1];\n let gpu2 = bvhBuffer[i + 2];\n let gpu3 = bvhBuffer[i + 3];\n primitiveCount = i32(bitcast<u32>(gpu2.w));\n if (primitiveCount > 0) {\n primitiveOffset = bufferOff + blasOffset\n + i32(bitcast<u32>(gpu3.w));\n }\n else {\n let left = blasOffset + i32(bitcast<u32>(gpu0.w) * 4u);\n let right = blasOffset + i32(bitcast<u32>(gpu1.w) * 4u);\n let leftDistance = intersectBox(\n o, gpu0.xyz, gpu1.xyz, invD, tMax\n );\n let rightDistance = intersectBox(\n o, gpu2.xyz, gpu3.xyz, invD, tMax\n );\n if (leftDistance > rightDistance) {\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n }\n else {\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 2) !== 0 ? /* wgsl */ `\n if (blasLayout == 1u) {\n let nodeInfo = bitcast<u32>(node);\n if ((nodeInfo & 0x80000000u) != 0u) {\n primitiveCount = i32((nodeInfo >> 23u) & 0xffu);\n primitiveOffset = bufferOff + blasOffset\n + i32(nodeInfo & 0x007fffffu);\n }\n else {\n let i = node + bufferOff;\n let wide0 = bvhBuffer[i];\n let wide1 = bvhBuffer[i + 1];\n let wide2 = bvhBuffer[i + 2];\n let wide3 = bvhBuffer[i + 3];\n let nodeMin = wide0.xyz;\n let nodeExtent = vec3f(wide0.w, wide1.x, wide1.y);\n let childInfos = array<u32, 4>(\n bitcast<u32>(wide1.z), bitcast<u32>(wide1.w),\n bitcast<u32>(wide2.x), bitcast<u32>(wide2.y)\n );\n let childMinX = unpack4x8unorm(bitcast<u32>(wide2.z));\n let childMinY = unpack4x8unorm(bitcast<u32>(wide2.w));\n let childMinZ = unpack4x8unorm(bitcast<u32>(wide3.x));\n let childMaxX = unpack4x8unorm(bitcast<u32>(wide3.y));\n let childMaxY = unpack4x8unorm(bitcast<u32>(wide3.z));\n let childMaxZ = unpack4x8unorm(bitcast<u32>(wide3.w));\n let tx0 = (nodeMin.x + childMinX * nodeExtent.x - o.x) * invD.x;\n let tx1 = (nodeMin.x + childMaxX * nodeExtent.x - o.x) * invD.x;\n let ty0 = (nodeMin.y + childMinY * nodeExtent.y - o.y) * invD.y;\n let ty1 = (nodeMin.y + childMaxY * nodeExtent.y - o.y) * invD.y;\n let tz0 = (nodeMin.z + childMinZ * nodeExtent.z - o.z) * invD.z;\n let tz1 = (nodeMin.z + childMaxZ * nodeExtent.z - o.z) * invD.z;\n let padding = depthSlabPadding(invD, o);\n let tNear = max(\n max((min(tx0, tx1) - vec4f(padding.x)), (min(ty0, ty1) - vec4f(padding.y))),\n (min(tz0, tz1) - vec4f(padding.z))\n );\n let tFar = min(\n min((max(tx0, tx1) + vec4f(padding.x)), (max(ty0, ty1) + vec4f(padding.y))),\n (max(tz0, tz1) + vec4f(padding.z))\n );\n var distance = select(tFar, tNear, tNear > vec4f(0.0));\n distance = select(\n distance,\n vec4f(-1.0),\n tNear > min(tFar, vec4f(tMax))\n );\n var childDistances = array<f32, 4>(\n distance.x,\n distance.y,\n distance.z,\n distance.w\n );\n\n // Push far-to-near so that the closest child is popped first.\n for (var order = 0; order < 4; order++) {\n var farthestDistance = -1.0;\n var farthestChild = -1;\n for (var child = 0; child < 4; child++) {\n if (\n childInfos[child] != 0u &&\n childDistances[child] > farthestDistance\n ) {\n farthestDistance = childDistances[child];\n farthestChild = child;\n }\n }\n if (farthestChild >= 0 && farthestDistance > 0.0) {\n let childInfo = childInfos[farthestChild];\n var childAddress = bitcast<i32>(childInfo);\n if ((childInfo & 0x80000000u) == 0u) {\n childAddress += blasOffset;\n }\n blasTop++;\n blasStack[blasTop] = childAddress;\n childDistances[farthestChild] = -1.0;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 1) !== 0 ? /* wgsl */ `\n if (blasLayout == 0u) {\n let i = node + bufferOff;\n let r1 = bvhBuffer[i];\n let pointerToRightOrNumPrims = bitcast<i32>(r1.w);\n\n if (pointerToRightOrNumPrims >= 0) {\n let left = node + 2;\n let right = pointerToRightOrNumPrims + blasOffset;\n let d0 = intersectBox(\n o,\n bvhBuffer[left + bufferOff].xyz,\n bvhBuffer[left + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right + bufferOff].xyz,\n bvhBuffer[right + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n }\n else {\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n }\n }\n else {\n primitiveCount = -pointerToRightOrNumPrims;\n primitiveOffset = i + 2;\n }\n }\n ` : ''}\n if (primitiveCount > 0) {\n ${intersectTriangles}\n }\n }\n }\n }\n}\n\n`;\n}\nexport default function getIntersectCode({\n hasDisplacement,\n hasAnisotropyDirections = true,\n hasCoatNormals = false,\n blasLayoutMask = 1\n}: {\n hasDisplacement: boolean;\n hasAnisotropyDirections?: boolean;\n hasCoatNormals?: boolean;\n blasLayoutMask?: number;\n}) {\n return /*wgsl*/ `\n\n${hasDisplacement ? displacementWgsl : ''}\n${(blasLayoutMask & 8) !== 0 ? cwbvhTraversalWgsl : ''}\n\nstruct Triangle {\n p0: vec3f,\n p1: vec3f,\n p2: vec3f,\n};\n\nfn intersectSphere(rad: f32, pos: vec3f, r: Ray) -> f32 {\n let op: vec3f = pos - r.o;\n let eps: f32 = 0.001;\n let b: f32 = dot(op, r.d);\n var det: f32 = b * b - dot(op, op) + rad * rad;\n if (det < 0.0) {\n return INF;\n }\n\n det = sqrt(det);\n let t1: f32 = b - det;\n if (t1 > eps) {\n return t1;\n }\n\n let t2: f32 = b + det;\n if (t2 > eps) {\n return t2;\n }\n\n return INF;\n}\n\nfn intersectRect(pos: vec3f, u: vec3f, v: vec3f, n: vec3f, r: Ray) -> f32 {\n let dt: f32 = dot(r.d, n);\n let t: f32 = (dot(n, pos) - dot(n, r.o)) / dt;\n\n if (t > EPS) {\n let p: vec3f = r.o + r.d * t;\n let vi: vec3f = p - pos;\n let a1: f32 = dot(u / dot(u, u), vi);\n if (a1 >= 0.0 && a1 <= 1.0) {\n let a2: f32 = dot(v / dot(v, v), vi);\n if (a2 >= 0.0 && a2 <= 1.0) {\n return t;\n }\n }\n }\n\n return INF;\n}\n\nfn sphereSolidAnglePdf(origin: vec3f, center: vec3f, radius: f32) -> f32 {\n let centerDistance = distance(origin, center);\n if (centerDistance <= radius) { return INV4PI; }\n let sinThetaMaxSquared = min(1.0, radius * radius / (centerDistance * centerDistance));\n let cosThetaMax = sqrt(max(0.0, 1.0 - sinThetaMaxSquared));\n return 1.0 / max(TWOPI * (1.0 - cosThetaMax), 1e-8);\n}\n\nfn intersectLight(r: Ray, light: Light, lightSample: ptr<function, LightSampleRec>) -> f32 {\n // Analytic continuation hits use the same conditional light density as NEE.\n // Initialize every field because this record is reused across the light loop.\n var d: f32 = INF;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = INF;\n (*lightSample).pdf = 0.0;\n (*lightSample).emission = vec3f(0.0);\n (*lightSample).isDelta = false;\n var u = light.u;\n var v = light.v;\n var lightType = i32(light.litType);\n\n if (lightType == RECT_LIGHT) {\n // Adjust the position from center to start corner\n var pos = light.position - u * 0.5 - v * 0.5;\n var normal = normalize(cross(u, v));\n if (dot(normal, r.d) > 0.0) { // Hide backfacing quad light\n return INF;\n }\n\n d = intersectRect(pos, u, v, normal, r);\n if (d < 0.0) {\n d = INF;\n }\n else {\n var cosTheta: f32 = dot(-r.d, normal);\n let spread = light.radiusOrSpread;\n if (cosTheta >= cos(spread * PI / 2.0)) {\n (*lightSample).direction = r.d;\n (*lightSample).normal = normal;\n (*lightSample).dist = d;\n (*lightSample).pdf = (d * d) / (light.area * cosTheta);\n (*lightSample).emission = light.emission;\n (*lightSample).isDelta = false;\n }\n else {\n d = INF;\n }\n }\n }\n\n else if (lightType == SPHERE_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n if (d > 0.0 && d != INF) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(\n r.o,\n light.position,\n light.radiusOrSpread\n );\n }\n }\n if (d < 0.0) {\n d = INF;\n }\n else {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).emission = select(\n light.emission,\n light.emission / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType == SPOT_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n }\n if (d != INF) {\n let hitPt = r.o + d * r.d;\n let multiplier = evaluateSpotEmissionMultiplier(light, normalize(r.o - hitPt));\n if (multiplier <= 0.0) { return INF; }\n if (light.radiusOrSpread > 0.0) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(r.o, light.position, light.radiusOrSpread);\n }\n (*lightSample).emission = light.emission * multiplier * select(\n 1.0,\n 1.0 / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType != RECT_LIGHT && lightType != SPHERE_LIGHT && lightType != SPOT_LIGHT) {\n // Unsupported light\n d = INF;\n }\n\n return d;\n}\n\nfn randomPickMaterial(m0: vec3i, m1: vec3i, m2: vec3i, bary: vec3f) -> i32 {\n let m00 = unpackMaterialIndex(m0.x);\n let m01 = unpackMaterialIndex(m0.y);\n let m02 = unpackMaterialIndex(m0.z);\n let m10 = unpackMaterialIndex(m1.x);\n let m11 = unpackMaterialIndex(m1.y);\n let m12 = unpackMaterialIndex(m1.z);\n let m20 = unpackMaterialIndex(m2.x);\n let m21 = unpackMaterialIndex(m2.y);\n let m22 = unpackMaterialIndex(m2.z);\n var w00 = m00.y * bary.x;\n var w01 = m01.y * bary.x;\n var w02 = m02.y * bary.x;\n var w10 = m10.y * bary.y;\n var w11 = m11.y * bary.y;\n var w12 = m12.y * bary.y;\n var w20 = m20.y * bary.z;\n var w21 = m21.y * bary.z;\n var w22 = m22.y * bary.z;\n\n var wTotal = w00 + w01 + w02 + w10 + w11 + w12 + w20 + w21 + w22;\n // Accumulate weights\n w00 = w00 / wTotal;\n w01 = w01 / wTotal + w00;\n w02 = w02 / wTotal + w01;\n w10 = w10 / wTotal + w02;\n w11 = w11 / wTotal + w10;\n w12 = w12 / wTotal + w11;\n w20 = w20 / wTotal + w12;\n w21 = w21 / wTotal + w20;\n w22 = w22 / wTotal + w21;\n\n let rand = hashedSample(0u);\n var mIdx: f32;\n if (rand < w00) {\n mIdx = m00.x;\n } else if (rand < w01) {\n mIdx = m01.x;\n } else if (rand < w02) {\n mIdx = m02.x;\n } else if (rand < w10) {\n mIdx = m10.x;\n } else if (rand < w11) {\n mIdx = m11.x;\n } else if (rand < w12) {\n mIdx = m12.x;\n } else if (rand < w20) {\n mIdx = m20.x;\n } else if (rand < w21) {\n mIdx = m21.x;\n } else {\n mIdx = m22.x;\n }\n return i32(mIdx);\n}\n\nfn resolveSurfaceMaterialIndex(\n fallbackMaterialIndex: i32,\n index: vec3i,\n bary: vec3f\n) -> i32 {\n let m0 = vec3i(attrBuffers[index.x * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m1 = vec3i(attrBuffers[index.y * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m2 = vec3i(attrBuffers[index.z * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n if (m0.x > 0 || m1.x > 0 || m2.x > 0) {\n return randomPickMaterial(m0, m1, m2, bary);\n }\n return fallbackMaterialIndex;\n}\n\n\n\n${hasAnisotropyDirections ? /* wgsl */ `\nfn anisotropy_axes(normal: vec3f, dp1: vec3f, dp2: vec3f, duv1: vec2f, duv2: vec2f, input: vec4f) -> mat2x3f {\n let determinant = duv1.x * duv2.y - duv1.y * duv2.x;\n var t = input.xyz;\n var b = vec3f(0.0);\n // Internal W=2 marks an authored XYZ tangent whose sign is derived from UVs.\n var sign = select(input.w, 0.0, abs(input.w) > 1.5);\n if (abs(determinant) > 1e-12) {\n let dt = (dp1 * duv2.y - dp2 * duv1.y) / determinant;\n let db = (dp2 * duv1.x - dp1 * duv2.x) / determinant;\n if (dot(t, t) < 1e-8) { t = dt; }\n if (sign == 0.0) { sign = select(1.0, -1.0, dot(cross(normal, dt), db) < 0.0); }\n }\n t -= normal * dot(normal, t);\n if (dot(t, t) < 1e-8) {\n let signZ = select(-1.0, 1.0, normal.z >= 0.0);\n let factor = -1.0 / (signZ + normal.z);\n let xy = normal.x * normal.y * factor;\n t = vec3f(1.0 + signZ * normal.x * normal.x * factor, signZ * xy, -signZ * normal.x);\n b = vec3f(xy, signZ + normal.y * normal.y * factor, -normal.y);\n }\n else { t = normalize(t); b = normalize(cross(normal, t)); }\n return mat2x3f(t, b * select(1.0, -1.0, sign < 0.0));\n}\n\n` : /* wgsl */ ``}\nfn fetchAttributes(\n si: ptr<function, SurfaceInteraction>,\n tri: Triangle,\n bary: vec3f,\n index: vec3i,\n isHitMicroTriangle: bool,\n resolvedMaterialIndex: i32\n) {\n (*si).hit = true;\n // Get projected uv\n let material = materialsBuffer[(*si).materialIndex];\n let uvProjection = i32(material.uvProjection);\n\n let i0 = index.x;\n let i1 = index.y;\n let i2 = index.z;\n let isSphericalProj = uvProjection == ${uvProjectionConstants.spherical};\n\n var normal: vec3f = (*si).normal;\n var normalForSphericalProj: vec3f;\n var n0 = vec3f(0.0);\n var n1 = vec3f(0.0);\n var n2 = vec3f(0.0);\n // Otherwise use the normal from the microtriangle\n // The exception is spherical projection, we need the original normal to project the uv. Or the case like earth has wrong normal.\n if (!isHitMicroTriangle || isSphericalProj) {\n n0 = attrBuffers[i0 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n1 = attrBuffers[i1 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n2 = attrBuffers[i2 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n normalForSphericalProj = normalize(bary.x * n0 + bary.y * n1 + bary.z * n2);\n if (!isHitMicroTriangle) {\n normal = normalForSphericalProj;\n }\n }\n\n var uv: vec2f;\n var uv0: vec2f;\n var uv1: vec2f;\n var uv2: vec2f;\n var secondaryUv = vec2f(0.0);\n if (USE_UV) {\n uv0 = attrBuffers[i0 * ATTR_STRIDE + UV_OFF].xy;\n uv1 = attrBuffers[i1 * ATTR_STRIDE + UV_OFF].xy;\n uv2 = attrBuffers[i2 * ATTR_STRIDE + UV_OFF].xy;\n uv = bary.x * uv0 + bary.y * uv1 + bary.z * uv2;\n if (USE_UV1) {\n let secondaryUv0 = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv1 = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv2 = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n secondaryUv = bary.x * secondaryUv0\n + bary.y * secondaryUv1\n + bary.z * secondaryUv2;\n }\n }\n\n if (USE_TANGENT) {\n let t0 = attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t1 = attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t2 = attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n (*si).tangent = normalize(bary.x * t0 + bary.y * t1 + bary.z * t2);\n }\n\n if (resolvedMaterialIndex >= 0) {\n (*si).materialIndex = resolvedMaterialIndex;\n }\n else {\n (*si).materialIndex = resolveSurfaceMaterialIndex(\n (*si).materialIndex,\n index,\n bary\n );\n }\n\n if (uvProjection > 0) {\n // NOTE: position and normal are not transformed to world space yet.\n uv = projectUV(uvProjection, (*si).position, select(normal, normalForSphericalProj, isSphericalProj));\n secondaryUv = uv;\n }\n\n uv = uv * material.uvScale;\n secondaryUv = secondaryUv * material.uvScale;\n\n // Force repeating UVs for triplanar mapping\n if (uvProjection == ${uvProjectionConstants.triplanar}) {\n uv = fract(uv);\n }\n\n${hasAnisotropyDirections ? /* wgsl */ `\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normal;` : ''}\n let directionMaterial = materialsBuffer[(*si).materialIndex];\n if (directionMaterial.anisotropyMapIndex >= 0.0 || directionMaterial.specularAnisotropyRotation != 0.0 || directionMaterial.coatAnisotropyRotation != 0.0${hasCoatNormals ? \" || (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0)\" : \"\"}) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n var inputTangent = vec4f(0.0);\n if (USE_TANGENT) {\n inputTangent = bary.x * attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]\n + bary.y * attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]\n + bary.z * attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF];\n }\n let geometryAxes = anisotropy_axes(normal, dp1, dp2, uv0-uv2, uv1-uv2, inputTangent);\n (*si).geometryTangent = geometryAxes[0];\n (*si).geometryBitangent = geometryAxes[1];\n var directionAxes = geometryAxes;\n if (USE_UV1 && directionMaterial.anisotropyMapIndex >= 0.0 && mapInfo[i32(directionMaterial.anisotropyMapIndex) + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n directionAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c, select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n (*si).directionTangent = directionAxes[0];\n (*si).directionBitangent = directionAxes[1];\n${hasCoatNormals ? /* wgsl */ `\n if (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0) {\n let coatIndex = i32(directionMaterial.coatNormalMapIndex);\n var coatAxes = geometryAxes;\n if (USE_UV1 && mapInfo[coatIndex + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n coatAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c,\n select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n // Procedural projection overrides both UV sets. UV0 has already applied\n // triplanar wrapping, so use it for either texture channel in that case.\n let decoded = sampleMap(coatIndex, getMapUvSet(coatIndex, uv,\n select(secondaryUv, uv, uvProjection > 0))).rgb * 2.0 - 1.0;\n let mapped = coatAxes[0] * decoded.x * directionMaterial.coatNormalScale.x\n + coatAxes[1] * decoded.y * directionMaterial.coatNormalScale.y + normal * decoded.z;\n if (dot(mapped, mapped) > 1e-12) { (*si).coatNormal = normalize(mapped); }\n }\n` : ''}\n }\n\n` : /* wgsl */ ``}\n (*si).normal = normal;\n (*si).vertexNormal = normal;\n (*si).shadowOffset = vec3f(0.0);\n if (!isHitMicroTriangle) {\n // Smooth-limit geometry offset for direct-light shadow rays. This follows\n // the Cycles shadow terminator construction while keeping the result in\n // local space until the instance transform is available.\n let geometricNormal = normalize(cross(tri.p1 - tri.p0, tri.p2 - tri.p0));\n let smoothNormal = bary.x * n0 + bary.y * n1 + bary.z * n2;\n let a = dot(n2 - n0, tri.p0 - tri.p2);\n let b = dot(n2 - n1, tri.p1 - tri.p2);\n let c = dot(n1 - n0, tri.p1 - tri.p0);\n var h = a * bary.x * (bary.x - 1.0)\n + (a + b + c) * bary.x * bary.y\n + b * bary.y * (bary.y - 1.0);\n if (dot(smoothNormal, geometricNormal) > 0.0) {\n var h0 = max(max(dot(tri.p1 - tri.p0, n0), dot(tri.p2 - tri.p0, n0)), 0.0);\n var h1 = max(max(dot(tri.p0 - tri.p1, n1), dot(tri.p2 - tri.p1, n1)), 0.0);\n var h2 = max(max(dot(tri.p0 - tri.p2, n2), dot(tri.p1 - tri.p2, n2)), 0.0);\n h0 = max(dot(tri.p0 - (*si).position, n0) + h0, 0.0);\n h1 = max(dot(tri.p1 - (*si).position, n1) + h1, 0.0);\n h2 = max(dot(tri.p2 - (*si).position, n2) + h2, 0.0);\n h = max(min(min(h0, h1), h2), h * 0.5);\n }\n else {\n var h0 = max(max(dot(tri.p0 - tri.p1, n0), dot(tri.p0 - tri.p2, n0)), 0.0);\n var h1 = max(max(dot(tri.p1 - tri.p0, n1), dot(tri.p1 - tri.p2, n1)), 0.0);\n var h2 = max(max(dot(tri.p2 - tri.p0, n2), dot(tri.p2 - tri.p1, n2)), 0.0);\n h0 = max(dot((*si).position - tri.p0, n0) + h0, 0.0);\n h1 = max(dot((*si).position - tri.p1, n1) + h1, 0.0);\n h2 = max(dot((*si).position - tri.p2, n2) + h2, 0.0);\n h = min(-min(min(h0, h1), h2), h * 0.5);\n }\n (*si).shadowOffset = smoothNormal * h;\n }\n (*si).uv = uv;\n (*si).uv1 = secondaryUv;\n if (USE_NORMAL_MAPS) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n let duv1 = uv0 - uv2;\n let duv2 = uv1 - uv2;\n (*si).normal = getMatNormal(\n (*si).materialIndex, uv, normal, dp1, dp2, duv1, duv2\n );\n }\n}\n\nstruct TriangleIntersect {\n t: f32,\n barycentric: vec3f,\n};\n\n\n// Triangle-ray intersection\n// Faster than the classic Möller–Trumbore intersection algorithm\n// http://www.pbr-book.org/3ed-2018/Shapes/Triangle_Meshes.html#TriangleIntersection\nfn intersectTriangle(o: vec3f, d: vec3f, tri: Triangle, maxDim: i32, shear: vec3f, tMax: f32) -> TriangleIntersect {\n var ti: TriangleIntersect;\n\n // translate vertices based on ray origin\n var p0t = tri.p0 - o;\n var p1t = tri.p1 - o;\n var p2t = tri.p2 - o;\n\n // permute components of triangle vertices\n if (maxDim == 0) {\n p0t = p0t.yzx;\n p1t = p1t.yzx;\n p2t = p2t.yzx;\n } else if (maxDim == 1) {\n p0t = p0t.zxy;\n p1t = p1t.zxy;\n p2t = p2t.zxy;\n }\n\n // apply shear transformation to translated vertex positions\n // TODO swizzled assignment is not supported in wgsl\n p0t = vec3f(p0t.xy + shear.xy * p0t.z, p0t.z);\n p1t = vec3f(p1t.xy + shear.xy * p1t.z, p1t.z);\n p2t = vec3f(p2t.xy + shear.xy * p2t.z, p2t.z);\n\n // compute edge function coefficients\n var e = vec3f(\n p1t.x * p2t.y - p1t.y * p2t.x,\n p2t.x * p0t.y - p2t.y * p0t.x,\n p0t.x * p1t.y - p0t.y * p1t.x\n );\n\n if (DEPTH_QUERY_MODE) {\n // Exact grid rays can land on shared edges. Roundoff in the two edge\n // products must not turn the edge into a crack between both triangles.\n let edgeScale = abs(vec3f(p1t.x * p2t.y, p2t.x * p0t.y, p0t.x * p1t.y))\n + abs(vec3f(p1t.y * p2t.x, p2t.y * p0t.x, p0t.y * p1t.x));\n e = select(e, vec3f(0.0), abs(e) <= edgeScale * 1e-6);\n }\n\n // check if intersection is inside triangle\n if (any(e < vec3f(0)) && any(e > vec3f(0))) {\n return ti;\n }\n\n let det = e.x + e.y + e.z;\n if (DEPTH_QUERY_MODE && det == 0.0) { return ti; }\n\n p0t.z *= shear.z;\n p1t.z *= shear.z;\n p2t.z *= shear.z;\n let tScaled = (e.x * p0t.z + e.y * p1t.z + e.z * p2t.z);\n\n // check if closer intersection already exists\n if (abs(tScaled) > abs(tMax * det)) {\n return ti;\n }\n\n let invDet = 1. / det;\n ti.t = tScaled * invDet;\n ti.barycentric = e * invDet;\n\n return ti;\n}\n\n// Pinhole depth queries can lie exactly on a grid vertex or a flat slab.\n// Specialize conservative slabs for that entry point; beauty retains its\n// original traversal arithmetic and random streams.\noverride DEPTH_QUERY_MODE: bool = false;\n\nfn traversalInverse(d: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return 1.0 / d; }\n return 1.0 / select(d, select(vec3f(1e-20), vec3f(-1e-20), d < vec3f(0.0)), abs(d) < vec3f(1e-20));\n}\n\nfn depthSlabPadding(invD: vec3f, o: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return vec3f(0.0); }\n return abs(invD) * max(vec3f(1.0), abs(o)) * 1e-6;\n}\n\n// Branchless ray/box intersection\n// https://tavianator.com/fast-branchless-raybounding-box-intersections/\nfn intersectBox(o: vec3f, bmin: vec3f, bmax: vec3f, invD: vec3f, tMax: f32) -> f32 {\n let tBot = (bmin - o) * invD;\n let tTop = (bmax - o) * invD;\n let padding = depthSlabPadding(invD, o);\n let tNear = min(tBot, tTop) - padding;\n let tFar = max(tBot, tTop) + padding;\n let t0 = max(tNear.x, max(tNear.y, tNear.z));\n let t1 = min(tFar.x, min(tFar.y, tFar.z));\n return select(select(t1, t0, t0 > 0.0), -1.0, (t0 > t1 || t0 > tMax));\n}\n\nfn maxDimension(v: vec3f) -> i32 {\n return select(select(2, 1, v.y > v.z), select(2, 0, v.x > v.z), v.x > v.y);\n}\n\nfn getShear(d: vec3f, maxDim: i32) -> vec3f {\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n if (maxDim == 0) {\n return vec3f(-d.y, -d.z, 1.0) / d.x;\n } else if (maxDim == 1) {\n return vec3f(-d.z, -d.x, 1.0) / d.y;\n } else {\n return vec3f(-d.x, -d.y, 1.0) / d.z;\n }\n}\n\nfn completeSurfaceInteraction(\n si: ptr<function, SurfaceInteraction>,\n hitTri: Triangle,\n hitBarycentric: vec3f,\n hitIndex: vec3i,\n isHitMicroTriangle: bool,\n hitInsIdx: i32,\n hitNormal: vec3f,\n hitPosition: vec3f,\n resolvedMaterialIndex: i32\n) {\n (*si).instanceIndex = hitInsIdx;\n (*si).barycentrics = hitBarycentric.xy;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(\n bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)\n ).x);\n // NOTE: Must be called before transforming normal/position to world space.\n // UV projection depends on the local position.\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n resolvedMaterialIndex\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n}\n\n\n${createIntersectionAlphaWgsl(hasAnisotropyDirections)}\n\n\n\n// Traverse BVH, find closest triangle intersection, and return surface information\nfn intersectScene(ray: Ray, si: ptr<function, SurfaceInteraction>, bounces: i32, doubleSided: bool, isRefractionRay: bool) {\n\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n var tMax = RAY_MAX_DISTANCE;\n var maxDim: i32;\n var shear: vec3f;\n\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = (*si).triangleIndex;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n if (isHit) {\n completeSurfaceInteraction(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n hitInsIdx,\n hitNormal,\n hitPosition,\n -1\n );\n }\n}\n\n// Local intersection helper for shader bevel.\nfn intersectSceneBevel(ray: Ray, si: ptr<function, SurfaceInteraction>, instanceIndex: i32, maxDistance: f32) {\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n let bounces = 0;\n let doubleSided = true;\n let isRefractionRay = false;\n var tMax = maxDistance;\n var maxDim: i32;\n var shear: vec3f;\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = -1;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections,\n `\n if (insIdx != instanceIndex) {\n continue;\n }\n `\n )}\n\n if (isHit) {\n (*si).instanceIndex = hitInsIdx;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)).x);\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n -1\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n }\n}\n\nfn intersectSceneShadow(ray: Ray, lightDistance: f32, currTriangleIndex: i32) -> bool {\n if (rtUniform.isEmpty > 0) {\n return false;\n }\n\n let doubleSided = true;\n let shadowRayTMax = min(max(lightDistance, 0.0), RAY_MAX_DISTANCE);\n var tMax = shadowRayTMax;\n var maxDim: i32;\n var shear: vec3f;\n\n let bounces = 1;\n var isRefractionRay = false;\n var currentTriangleIndex: i32 = currTriangleIndex;\n\n ${getBVHTraverseCode(\n /*wgsl */ `\n// Traversal is already clipped to the sampled light. Affine instance\n// transforms preserve the ray parameter because local directions are not\n// renormalized, so no local-to-world hit transform or distance() is needed.\nvar isOccluded = true;\n\nif (isOccluded) {\n if (HAS_ALPHA) {\n let materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n if (!intersectionAlphaPasses(\n materialIndex,\n index,\n hitBarycentric,\n hitPosition,\n hitNormal\n )) {\n // Continue looking for an opaque hit, but never traverse beyond the light.\n tMax = shadowRayTMax;\n isOccluded = false;\n }\n }\n}\nif (isOccluded) {\n return true;\n}\n `,\n hasDisplacement,\n true,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n return false;\n}\n\n`;\n}\n"]}
1
+ {"version":3,"file":"intersect.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/intersect.wgsl.ts"],"names":[],"mappings":"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","sourcesContent":["// Portions adapted from ray-tracing-renderer@0.10.15\n// (https://github.com/hoverinc/ray-tracing-renderer).\n// Copyright (c) 2019 HOVER, MIT license; see LICENSES/RayTracingRenderer-MIT.txt.\n\nimport { uvProjectionConstants } from '../../MaterialTable.js';\nimport { INSTANCE_VEC4_STRIDE } from '../../bvhAccel.js';\nimport pdm_displacementWgsl from './pdm_displacement.wgsl.js';\nimport { createIntersectionAlphaWgsl } from './intersectionAlpha.wgsl.js';\n\nconst displacementWgsl = pdm_displacementWgsl;\n\nconst flatIntersectCode = function () {\n return /* wgsl */ `\nlet hit = intersectTriangle(o, d, tri, maxDim, shear, tMax);\nif (hit.t > 0.0) {\n let e1 = tri.p1 - tri.p0;\n let e2 = tri.p2 - tri.p0;\n hitNormal = normalize(cross(e1, e2));\n let sideMaterialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n let materialDoubleSided = materialsBuffer[sideMaterialIndex].doubleSided > 0.5;\n if (!doubleSided && !materialDoubleSided && dot(hitNormal, d) > 0.0) {\n hitNormal = vec3f(0.0);\n continue;\n }\n\n tMax = hit.t;\n isHit = true;\n isHitCurrent = true;\n hitPosition = o + tMax * d;\n hitBarycentric = hit.barycentric;\n}\n`;\n};\n\nfunction getPDMDisplacmentCode(anyHit: boolean) {\n return /*wgsl */ `\n let microTriangle = intersectDisplacement(\n o, d, tMax,\n tri.p0, tri.p1, tri.p2,\n n0, n1, n2,\n uv0, uv1, uv2,\n displacementMapIndex,\n getMapSize(displacementMapIndex),\n minH, maxH,\n hScale,\n matData.displacementBias,\n matData.displacementStepSize,\n i32(matData.displacementUvProjection),\n displacementUvProjectionScale,\n ${anyHit ? 'true' : 'false'},\n isRefractionRay\n );\n`;\n}\n\nconst displacementIntersectCode = function (anyHit: boolean, hasAnisotropyDirections: boolean) {\n return /* wgsl */ `\n\nlet hScale = insData.w;\nlet minMaxH = bitcast<i32>(r3.w);\n// Displacement currently uses the first material of the instance.\nlet materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\nlet matData = materialsBuffer[materialIndex];\nlet n0 = normalize(attrBuffers[index.x * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nlet n1 = normalize(attrBuffers[index.y * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nlet n2 = normalize(attrBuffers[index.z * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\nif (matData.displacementMapIndex >= 0.0 && matData.displacementBias != 0.0) {\n tri.p0 += n0 * matData.displacementBias;\n tri.p1 += n1 * matData.displacementBias;\n tri.p2 += n2 * matData.displacementBias;\n}\nif (abs(hScale) > 0.0 && minMaxH > 0) {\n let minH = f32(minMaxH >> 8) / 255.0;\n let maxH = f32(minMaxH & 0xff) / 255.0;\n let displacementUvProjectionScale = vec2f(matData.displacementUvProjectionScale);\n let displacementMapIndex = i32(matData.displacementMapIndex);\n\n // TODO duplicate reading of normal and uv\n var uv0 = attrBuffers[index.x * ATTR_STRIDE + UV_OFF].xy;\n var uv1 = attrBuffers[index.y * ATTR_STRIDE + UV_OFF].xy;\n var uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;\n\n ${getPDMDisplacmentCode(anyHit)}\n\n if (microTriangle.t < tMax) {\n hitNormal = microTriangle.normal;\n // TODO support double sided to improve non watertight triangles.\n\n tMax = microTriangle.t;\n // TODO barycentric is for micro triangle\n hitBarycentric = microTriangle.barycentric;\n hitPosition = o + tMax * d;\n\n isHitMicroTriangle = true;\n isHit = true;\n isHitCurrent = true;\n }\n}\nelse {\n ${flatIntersectCode()}\n}\n`;\n};\n\nconst cwbvhTraversalWgsl = /* wgsl */ `\nfn unpackCwbvhBytes(value: u32) -> vec4f {\n return vec4f(\n f32(value & 0xffu),\n f32((value >> 8u) & 0xffu),\n f32((value >> 16u) & 0xffu),\n f32(value >> 24u)\n );\n}\n\n// CWBVH calls these helpers only with nonzero 8/24-bit masks. Every such\n// integer is exactly representable as f32, whose unbiased IEEE-754 exponent\n// gives the highest set bit. This avoids both the full-width firstLeadingBit\n// polyfill on some shader backends and a serial bit-spread/popcount chain.\n// Do not use this conversion for full 32-bit masks: rounding near a power of\n// two can change their highest bit. Zero is also outside this contract.\nfn cwbvhHighestBitIndex8(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn cwbvhHighestBitIndex24(value: u32) -> u32 {\n return (bitcast<u32>(f32(value)) >> 23u) - 127u;\n}\n\nfn intersectCwbvhInterior(\n node: i32,\n bufferOffset: i32,\n n0: vec4f,\n o: vec3f,\n invD: vec3f,\n tMax: f32\n) -> u32 {\n let i = node + bufferOffset;\n let n2 = bvhBuffer[i + 2];\n let n3 = bvhBuffer[i + 3];\n let n4 = bvhBuffer[i + 4];\n let header = bitcast<u32>(n0.w);\n let exponentX = bitcast<i32>((header & 0xffu) << 24u) >> 24;\n let exponentY = bitcast<i32>(((header >> 8u) & 0xffu) << 24u) >> 24;\n let exponentZ = bitcast<i32>(((header >> 16u) & 0xffu) << 24u) >> 24;\n let quantizationScale = vec3f(\n bitcast<f32>(u32(exponentX + 127) << 23u),\n bitcast<f32>(u32(exponentY + 127) << 23u),\n bitcast<f32>(u32(exponentZ + 127) << 23u)\n );\n let padding = depthSlabPadding(invD, o);\n let originTerm = (n0.xyz - o) * invD;\n let adjustedInvD = quantizationScale * invD;\n let minX0 = unpackCwbvhBytes(bitcast<u32>(n2.x));\n let minX1 = unpackCwbvhBytes(bitcast<u32>(n2.y));\n let minY0 = unpackCwbvhBytes(bitcast<u32>(n2.z));\n let minY1 = unpackCwbvhBytes(bitcast<u32>(n2.w));\n let minZ0 = unpackCwbvhBytes(bitcast<u32>(n3.x));\n let minZ1 = unpackCwbvhBytes(bitcast<u32>(n3.y));\n let maxX0 = unpackCwbvhBytes(bitcast<u32>(n3.z));\n let maxX1 = unpackCwbvhBytes(bitcast<u32>(n3.w));\n let maxY0 = unpackCwbvhBytes(bitcast<u32>(n4.x));\n let maxY1 = unpackCwbvhBytes(bitcast<u32>(n4.y));\n let maxZ0 = unpackCwbvhBytes(bitcast<u32>(n4.z));\n let maxZ1 = unpackCwbvhBytes(bitcast<u32>(n4.w));\n\n let minTx0 = minX0 * adjustedInvD.x + originTerm.x;\n let minTy0 = minY0 * adjustedInvD.y + originTerm.y;\n let minTz0 = minZ0 * adjustedInvD.z + originTerm.z;\n let maxTx0 = maxX0 * adjustedInvD.x + originTerm.x;\n let maxTy0 = maxY0 * adjustedInvD.y + originTerm.y;\n let maxTz0 = maxZ0 * adjustedInvD.z + originTerm.z;\n let near0 = max(\n max((min(minTx0, maxTx0) - vec4f(padding.x)), (min(minTy0, maxTy0) - vec4f(padding.y))),\n (min(minTz0, maxTz0) - vec4f(padding.z))\n );\n let far0 = min(\n min((max(minTx0, maxTx0) + vec4f(padding.x)), (max(minTy0, maxTy0) + vec4f(padding.y))),\n (max(minTz0, maxTz0) + vec4f(padding.z))\n );\n\n let minTx1 = minX1 * adjustedInvD.x + originTerm.x;\n let minTy1 = minY1 * adjustedInvD.y + originTerm.y;\n let minTz1 = minZ1 * adjustedInvD.z + originTerm.z;\n let maxTx1 = maxX1 * adjustedInvD.x + originTerm.x;\n let maxTy1 = maxY1 * adjustedInvD.y + originTerm.y;\n let maxTz1 = maxZ1 * adjustedInvD.z + originTerm.z;\n let near1 = max(\n max((min(minTx1, maxTx1) - vec4f(padding.x)), (min(minTy1, maxTy1) - vec4f(padding.y))),\n (min(minTz1, maxTz1) - vec4f(padding.z))\n );\n let far1 = min(\n min((max(minTx1, maxTx1) + vec4f(padding.x)), (max(minTy1, maxTy1) + vec4f(padding.y))),\n (max(minTz1, maxTz1) + vec4f(padding.z))\n );\n\n let hits0 = (near0 <= min(far0, vec4f(tMax))) & (far0 > vec4f(0.0));\n let hits1 = (near1 <= min(far1, vec4f(tMax))) & (far1 > vec4f(0.0));\n return select(0u, 1u, hits0.x)\n | select(0u, 2u, hits0.y)\n | select(0u, 4u, hits0.z)\n | select(0u, 8u, hits0.w)\n | select(0u, 16u, hits1.x)\n | select(0u, 32u, hits1.y)\n | select(0u, 64u, hits1.z)\n | select(0u, 128u, hits1.w);\n}\n\n`;\n\nfunction getBVHTraverseCode(\n handleHitCode: string,\n hasDisplacement: boolean,\n anyHit: boolean,\n blasLayoutMask: number,\n hasAnisotropyDirections: boolean,\n skipTriangleCode = ''\n) {\n const intersectTriangles = /* wgsl */ `\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let vertexOffset = vec3i(bitcast<i32>(insData.y));\n for (var k = 0; k < primitiveCount; k++) {\n let triangleIndex = primitiveOffset + k;\n ${skipTriangleCode}\n if (bounces > 0 && triangleIndex == currentTriangleIndex) {\n continue;\n }\n \n let r3 = bvhBuffer[triangleIndex];\n let index = bitcast<vec3i>(r3.xyz) + vertexOffset;\n var tri = Triangle(\n attrBuffers[index.x * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.y * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''},\n attrBuffers[index.z * ATTR_STRIDE + POSITION_OFF]${hasAnisotropyDirections ? '.xyz' : ''}\n );\n var isHitCurrent = false;\n\n ${hasDisplacement\n ? displacementIntersectCode(anyHit, hasAnisotropyDirections)\n : flatIntersectCode()}\n if (isHitCurrent) {\n ${handleHitCode}\n }\n }\n`;\n return /* wgsl */ `\nvar d = ray.d;\nvar o = ray.o;\nvar tlasStack: array<i32, TLAS_STACK_SIZE>;\nvar tlasTop = -1;\nvar invD = traversalInverse(d);\nvar isHitMicroTriangle = false;\nvar isHit = false;\nvar hitPosition: vec3f;\nvar hitNormal: vec3f;\nvar hitBarycentric: vec3f;\n\n\nif (intersectBox(o, bvhBuffer[0].xyz, bvhBuffer[1].xyz, invD, tMax) > 0.0) {\n tlasTop = 0;\n tlasStack[0] = 0;\n}\nwhile (tlasTop >= 0) {\n let tlasNode = tlasStack[tlasTop];\n tlasTop--;\n o = ray.o;\n d = ray.d;\n invD = traversalInverse(d);\n\n let tlasR1 = bvhBuffer[tlasNode];\n let tlasPointerOrPrimitiveCount = bitcast<i32>(tlasR1.w);\n if (tlasPointerOrPrimitiveCount >= 0) {\n let left = tlasNode + 2;\n let right = tlasPointerOrPrimitiveCount;\n let d0 = intersectBox(\n o,\n bvhBuffer[left].xyz,\n bvhBuffer[left + 1].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right].xyz,\n bvhBuffer[right + 1].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n }\n else {\n if (d1 > 0.0) { tlasTop++; tlasStack[tlasTop] = right; }\n if (d0 > 0.0) { tlasTop++; tlasStack[tlasTop] = left; }\n }\n }\n else {\n let tlasPrimitiveCount = -tlasPointerOrPrimitiveCount;\n // The previous unified LIFO stack visited leaf instances in reverse order.\n // Preserve that order for alpha-tested any-hit rays and deterministic RNG.\n for (\n var instanceOffset = tlasPrimitiveCount - 1;\n instanceOffset >= 0;\n instanceOffset--\n ) {\n let instanceRecord = bvhBuffer[tlasNode + 2 + instanceOffset];\n let insIdx = bitcast<i32>(instanceRecord.x);\n let blasRoot = bitcast<i32>(instanceRecord.y);\n let insData = instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE}];\n let w2l = mat4x4<f32>(\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[insIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n o = (w2l * vec4f(ray.o, 1.0)).xyz;\n d = (w2l * vec4f(ray.d, 0.0)).xyz;\n invD = traversalInverse(d);\n maxDim = maxDimension(abs(d));\n shear = getShear(d, maxDim);\n\n let encodedBlasOffset = bitcast<u32>(insData.x);\n let blasLayout = encodedBlasOffset >> 30u;\n let blasOffset = i32(encodedBlasOffset & 0x3fffffffu);\n let bufferOff = rtUniform.blasOffset;\n\n ${(blasLayoutMask & 8) !== 0 ? /* wgsl */ `\n if (blasLayout == 3u) {\n // TinyBVH's official CWBVH traversal keeps compact node groups on the\n // stack. The upper 8 bits identify intersected interior slots and the\n // lower 8 bits preserve the node's interior-slot mask for popcount\n // addressing. Triangle leaves are merged into a separate 24-bit mask.\n var cwbvhStack: array<vec2u, BLAS_STACK_SIZE>;\n var cwbvhTop = -1;\n var cwbvhVisits = 0u;\n var nodeGroupBase = 0u;\n // A synthetic slot-7 hit selects relative node zero for the root.\n var nodeGroupMask = 0x80000000u;\n let octant = select(0u, 4u, d.x < 0.0)\n | select(0u, 2u, d.y < 0.0)\n | select(0u, 1u, d.z < 0.0);\n let octantInverse = 7u - octant;\n\n // Keep a statically visible trip bound. DXC can otherwise miscompile\n // the dynamic stack-top loop into a non-terminating shader on D3D12.\n // Validated scene rays stay far below this conservative safety cap.\n while (cwbvhVisits < 4096u) {\n cwbvhVisits++;\n \n let internalMask = nodeGroupMask & 0xffu;\n let childBitIndex =\n cwbvhHighestBitIndex8(nodeGroupMask >> 24u) + 24u;\n nodeGroupMask &= ~(1u << childBitIndex);\n if ((nodeGroupMask & 0xff000000u) != 0u) {\n cwbvhTop++;\n \n cwbvhStack[cwbvhTop] = vec2u(nodeGroupBase, nodeGroupMask);\n }\n\n let slot = (childBitIndex - 24u) ^ octantInverse;\n let precedingMask = (1u << slot) - 1u;\n let relativeIndex = countOneBits(internalMask & precedingMask);\n let childNodeIndex = nodeGroupBase + relativeIndex;\n let node = blasOffset + i32(childNodeIndex * 5u);\n let compressed0 = bvhBuffer[node + bufferOff];\n let slotHits = intersectCwbvhInterior(\n node, bufferOff, compressed0, o, invD, tMax\n );\n // Empty child hits need neither metadata nor leaf processing. Clear\n // the current group so the common tail restores pending siblings.\n nodeGroupMask = 0u;\n if (slotHits != 0u) {\n let compressed1 = bvhBuffer[node + bufferOff + 1];\n let nodeInternalMask = bitcast<u32>(compressed0.w) >> 24u;\n var hitMask = 0u;\n var remainingSlotHits = slotHits;\n while (remainingSlotHits != 0u) {\n let childSlot = cwbvhHighestBitIndex8(remainingSlotHits);\n remainingSlotHits &= ~(1u << childSlot);\n let metaWord = select(\n bitcast<u32>(compressed1.z),\n bitcast<u32>(compressed1.w),\n childSlot >= 4u\n );\n let childMeta =\n (metaWord >> ((childSlot & 3u) * 8u)) & 0xffu;\n let localOffset = childMeta & 0x1fu;\n let bitIndex = select(\n localOffset,\n localOffset ^ octantInverse,\n localOffset >= 24u\n );\n let childBits = (childMeta >> 5u) & 0x7u;\n hitMask |= childBits << bitIndex;\n }\n\n nodeGroupBase = bitcast<u32>(compressed1.x);\n nodeGroupMask = (hitMask & 0xff000000u) | nodeInternalMask;\n let triangleBase = bitcast<u32>(compressed1.y);\n var triangleMask = hitMask & 0x00ffffffu;\n while (triangleMask != 0u) {\n let triangleBitIndex = cwbvhHighestBitIndex24(triangleMask);\n triangleMask &= ~(1u << triangleBitIndex);\n let primitiveCount = 1;\n let primitiveOffset = bufferOff + blasOffset\n + i32(triangleBase + triangleBitIndex);\n ${intersectTriangles}\n }\n }\n\n if ((nodeGroupMask & 0xff000000u) != 0u) { continue; }\n if (cwbvhTop >= 0) {\n let restoredGroup = cwbvhStack[cwbvhTop];\n nodeGroupBase = restoredGroup.x;\n nodeGroupMask = restoredGroup.y;\n cwbvhTop--;\n }\n else { break; }\n }\n }\n ` : ''}\n\n var blasStack: array<i32, BLAS_STACK_SIZE>;\n var blasTop = -1;\n if (blasLayout != 3u) {\n blasTop = 0;\n \n blasStack[0] = blasRoot;\n }\n\n while (blasTop >= 0) {\n \n let node = blasStack[blasTop];\n blasTop--;\n var primitiveCount = 0;\n var primitiveOffset = 0;\n\n ${(blasLayoutMask & 4) !== 0 ? /* wgsl */ `\n if (blasLayout == 2u) {\n let i = node + bufferOff;\n let gpu0 = bvhBuffer[i];\n let gpu1 = bvhBuffer[i + 1];\n let gpu2 = bvhBuffer[i + 2];\n let gpu3 = bvhBuffer[i + 3];\n primitiveCount = i32(bitcast<u32>(gpu2.w));\n if (primitiveCount > 0) {\n primitiveOffset = bufferOff + blasOffset\n + i32(bitcast<u32>(gpu3.w));\n }\n else {\n let left = blasOffset + i32(bitcast<u32>(gpu0.w) * 4u);\n let right = blasOffset + i32(bitcast<u32>(gpu1.w) * 4u);\n let leftDistance = intersectBox(\n o, gpu0.xyz, gpu1.xyz, invD, tMax\n );\n let rightDistance = intersectBox(\n o, gpu2.xyz, gpu3.xyz, invD, tMax\n );\n if (leftDistance > rightDistance) {\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n }\n else {\n if (rightDistance > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n }\n if (leftDistance > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 2) !== 0 ? /* wgsl */ `\n if (blasLayout == 1u) {\n let nodeInfo = bitcast<u32>(node);\n if ((nodeInfo & 0x80000000u) != 0u) {\n primitiveCount = i32((nodeInfo >> 23u) & 0xffu);\n primitiveOffset = bufferOff + blasOffset\n + i32(nodeInfo & 0x007fffffu);\n }\n else {\n let i = node + bufferOff;\n let wide0 = bvhBuffer[i];\n let wide1 = bvhBuffer[i + 1];\n let wide2 = bvhBuffer[i + 2];\n let wide3 = bvhBuffer[i + 3];\n let nodeMin = wide0.xyz;\n let nodeExtent = vec3f(wide0.w, wide1.x, wide1.y);\n let childInfos = array<u32, 4>(\n bitcast<u32>(wide1.z), bitcast<u32>(wide1.w),\n bitcast<u32>(wide2.x), bitcast<u32>(wide2.y)\n );\n let childMinX = unpack4x8unorm(bitcast<u32>(wide2.z));\n let childMinY = unpack4x8unorm(bitcast<u32>(wide2.w));\n let childMinZ = unpack4x8unorm(bitcast<u32>(wide3.x));\n let childMaxX = unpack4x8unorm(bitcast<u32>(wide3.y));\n let childMaxY = unpack4x8unorm(bitcast<u32>(wide3.z));\n let childMaxZ = unpack4x8unorm(bitcast<u32>(wide3.w));\n let tx0 = (nodeMin.x + childMinX * nodeExtent.x - o.x) * invD.x;\n let tx1 = (nodeMin.x + childMaxX * nodeExtent.x - o.x) * invD.x;\n let ty0 = (nodeMin.y + childMinY * nodeExtent.y - o.y) * invD.y;\n let ty1 = (nodeMin.y + childMaxY * nodeExtent.y - o.y) * invD.y;\n let tz0 = (nodeMin.z + childMinZ * nodeExtent.z - o.z) * invD.z;\n let tz1 = (nodeMin.z + childMaxZ * nodeExtent.z - o.z) * invD.z;\n let padding = depthSlabPadding(invD, o);\n let tNear = max(\n max((min(tx0, tx1) - vec4f(padding.x)), (min(ty0, ty1) - vec4f(padding.y))),\n (min(tz0, tz1) - vec4f(padding.z))\n );\n let tFar = min(\n min((max(tx0, tx1) + vec4f(padding.x)), (max(ty0, ty1) + vec4f(padding.y))),\n (max(tz0, tz1) + vec4f(padding.z))\n );\n var distance = select(tFar, tNear, tNear > vec4f(0.0));\n distance = select(\n distance,\n vec4f(-1.0),\n tNear > min(tFar, vec4f(tMax))\n );\n var childDistances = array<f32, 4>(\n distance.x,\n distance.y,\n distance.z,\n distance.w\n );\n\n // Push far-to-near so that the closest child is popped first.\n for (var order = 0; order < 4; order++) {\n var farthestDistance = -1.0;\n var farthestChild = -1;\n for (var child = 0; child < 4; child++) {\n if (\n childInfos[child] != 0u &&\n childDistances[child] > farthestDistance\n ) {\n farthestDistance = childDistances[child];\n farthestChild = child;\n }\n }\n if (farthestChild >= 0 && farthestDistance > 0.0) {\n let childInfo = childInfos[farthestChild];\n var childAddress = bitcast<i32>(childInfo);\n if ((childInfo & 0x80000000u) == 0u) {\n childAddress += blasOffset;\n }\n blasTop++;\n blasStack[blasTop] = childAddress;\n childDistances[farthestChild] = -1.0;\n }\n }\n }\n }\n ` : ''}\n ${(blasLayoutMask & 1) !== 0 ? /* wgsl */ `\n if (blasLayout == 0u) {\n let i = node + bufferOff;\n let r1 = bvhBuffer[i];\n let pointerToRightOrNumPrims = bitcast<i32>(r1.w);\n\n if (pointerToRightOrNumPrims >= 0) {\n let left = node + 2;\n let right = pointerToRightOrNumPrims + blasOffset;\n let d0 = intersectBox(\n o,\n bvhBuffer[left + bufferOff].xyz,\n bvhBuffer[left + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n let d1 = intersectBox(\n o,\n bvhBuffer[right + bufferOff].xyz,\n bvhBuffer[right + 1 + bufferOff].xyz,\n invD,\n tMax\n );\n\n if (d0 > d1) {\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n }\n else {\n if (d1 > 0.0) {\n blasTop++; blasStack[blasTop] = right;\n \n }\n if (d0 > 0.0) {\n blasTop++; blasStack[blasTop] = left;\n \n }\n }\n }\n else {\n primitiveCount = -pointerToRightOrNumPrims;\n primitiveOffset = i + 2;\n }\n }\n ` : ''}\n if (primitiveCount > 0) {\n ${intersectTriangles}\n }\n }\n }\n }\n}\n\n`;\n}\nexport default function getIntersectCode({\n hasDisplacement,\n hasAnisotropyDirections = true,\n hasCoatNormals = false,\n blasLayoutMask = 1\n}: {\n hasDisplacement: boolean;\n hasAnisotropyDirections?: boolean;\n hasCoatNormals?: boolean;\n blasLayoutMask?: number;\n}) {\n return /*wgsl*/ `\n\n${hasDisplacement ? displacementWgsl : ''}\n${(blasLayoutMask & 8) !== 0 ? cwbvhTraversalWgsl : ''}\n\nstruct Triangle {\n p0: vec3f,\n p1: vec3f,\n p2: vec3f,\n};\n\nfn intersectSphere(rad: f32, pos: vec3f, r: Ray) -> f32 {\n let op: vec3f = pos - r.o;\n let eps: f32 = 0.001;\n let b: f32 = dot(op, r.d);\n var det: f32 = b * b - dot(op, op) + rad * rad;\n if (det < 0.0) {\n return INF;\n }\n\n det = sqrt(det);\n let t1: f32 = b - det;\n if (t1 > eps) {\n return t1;\n }\n\n let t2: f32 = b + det;\n if (t2 > eps) {\n return t2;\n }\n\n return INF;\n}\n\nfn intersectRect(pos: vec3f, u: vec3f, v: vec3f, n: vec3f, r: Ray) -> f32 {\n let dt: f32 = dot(r.d, n);\n let t: f32 = (dot(n, pos) - dot(n, r.o)) / dt;\n\n if (t > EPS) {\n let p: vec3f = r.o + r.d * t;\n let vi: vec3f = p - pos;\n let a1: f32 = dot(u / dot(u, u), vi);\n if (a1 >= 0.0 && a1 <= 1.0) {\n let a2: f32 = dot(v / dot(v, v), vi);\n if (a2 >= 0.0 && a2 <= 1.0) {\n return t;\n }\n }\n }\n\n return INF;\n}\n\nfn sphereSolidAnglePdf(origin: vec3f, center: vec3f, radius: f32) -> f32 {\n let centerDistance = distance(origin, center);\n if (centerDistance <= radius) { return INV4PI; }\n let sinThetaMaxSquared = min(1.0, radius * radius / (centerDistance * centerDistance));\n let cosThetaMax = sqrt(max(0.0, 1.0 - sinThetaMaxSquared));\n return 1.0 / max(TWOPI * (1.0 - cosThetaMax), 1e-8);\n}\n\nfn intersectLight(r: Ray, light: Light, lightSample: ptr<function, LightSampleRec>) -> f32 {\n // Analytic continuation hits use the same conditional light density as NEE.\n // Initialize every field because this record is reused across the light loop.\n var d: f32 = INF;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = INF;\n (*lightSample).pdf = 0.0;\n (*lightSample).emission = vec3f(0.0);\n (*lightSample).isDelta = false;\n var u = light.u;\n var v = light.v;\n var lightType = i32(light.litType);\n\n if (lightType == RECT_LIGHT) {\n // Adjust the position from center to start corner\n var pos = light.position - u * 0.5 - v * 0.5;\n var normal = normalize(cross(u, v));\n if (dot(normal, r.d) > 0.0) { // Hide backfacing quad light\n return INF;\n }\n\n d = intersectRect(pos, u, v, normal, r);\n if (d < 0.0) {\n d = INF;\n }\n else {\n var cosTheta: f32 = dot(-r.d, normal);\n let spread = light.radiusOrSpread;\n if (cosTheta >= cos(spread * PI / 2.0)) {\n (*lightSample).direction = r.d;\n (*lightSample).normal = normal;\n (*lightSample).dist = d;\n (*lightSample).pdf = (d * d) / (light.area * cosTheta);\n (*lightSample).emission = light.emission;\n (*lightSample).isDelta = false;\n }\n else {\n d = INF;\n }\n }\n }\n\n else if (lightType == SPHERE_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n if (d > 0.0 && d != INF) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(\n r.o,\n light.position,\n light.radiusOrSpread\n );\n }\n }\n if (d < 0.0) {\n d = INF;\n }\n else {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).emission = select(\n light.emission,\n light.emission / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType == SPOT_LIGHT) {\n if (light.radiusOrSpread <= 0.0) {\n let toPoint = light.position - r.o;\n let projection = dot(toPoint, r.d);\n let perpendicular = toPoint - projection * r.d;\n if (projection <= EPS || dot(perpendicular, perpendicular) > 1e-8) {\n return INF;\n }\n d = projection;\n (*lightSample).direction = r.d;\n (*lightSample).normal = -r.d;\n (*lightSample).dist = d;\n (*lightSample).pdf = 1.0;\n (*lightSample).isDelta = true;\n }\n else {\n d = intersectSphere(light.radiusOrSpread, light.position, r);\n (*lightSample).isDelta = false;\n }\n if (d != INF) {\n let hitPt = r.o + d * r.d;\n let multiplier = evaluateSpotEmissionMultiplier(light, normalize(r.o - hitPt));\n if (multiplier <= 0.0) { return INF; }\n if (light.radiusOrSpread > 0.0) {\n (*lightSample).direction = r.d;\n (*lightSample).dist = d;\n (*lightSample).normal = normalize(\n r.o + r.d * d - light.position\n );\n (*lightSample).pdf = sphereSolidAnglePdf(r.o, light.position, light.radiusOrSpread);\n }\n (*lightSample).emission = light.emission * multiplier * select(\n 1.0,\n 1.0 / max(d * d, 1e-8),\n light.radiusOrSpread <= 0.0\n );\n }\n }\n\n else if (lightType != RECT_LIGHT && lightType != SPHERE_LIGHT && lightType != SPOT_LIGHT) {\n // Unsupported light\n d = INF;\n }\n\n return d;\n}\n\nfn randomPickMaterial(m0: vec3i, m1: vec3i, m2: vec3i, bary: vec3f) -> i32 {\n let m00 = unpackMaterialIndex(m0.x);\n let m01 = unpackMaterialIndex(m0.y);\n let m02 = unpackMaterialIndex(m0.z);\n let m10 = unpackMaterialIndex(m1.x);\n let m11 = unpackMaterialIndex(m1.y);\n let m12 = unpackMaterialIndex(m1.z);\n let m20 = unpackMaterialIndex(m2.x);\n let m21 = unpackMaterialIndex(m2.y);\n let m22 = unpackMaterialIndex(m2.z);\n var w00 = m00.y * bary.x;\n var w01 = m01.y * bary.x;\n var w02 = m02.y * bary.x;\n var w10 = m10.y * bary.y;\n var w11 = m11.y * bary.y;\n var w12 = m12.y * bary.y;\n var w20 = m20.y * bary.z;\n var w21 = m21.y * bary.z;\n var w22 = m22.y * bary.z;\n\n var wTotal = w00 + w01 + w02 + w10 + w11 + w12 + w20 + w21 + w22;\n // Accumulate weights\n w00 = w00 / wTotal;\n w01 = w01 / wTotal + w00;\n w02 = w02 / wTotal + w01;\n w10 = w10 / wTotal + w02;\n w11 = w11 / wTotal + w10;\n w12 = w12 / wTotal + w11;\n w20 = w20 / wTotal + w12;\n w21 = w21 / wTotal + w20;\n w22 = w22 / wTotal + w21;\n\n let rand = hashedSample(0u);\n var mIdx: f32;\n if (rand < w00) {\n mIdx = m00.x;\n } else if (rand < w01) {\n mIdx = m01.x;\n } else if (rand < w02) {\n mIdx = m02.x;\n } else if (rand < w10) {\n mIdx = m10.x;\n } else if (rand < w11) {\n mIdx = m11.x;\n } else if (rand < w12) {\n mIdx = m12.x;\n } else if (rand < w20) {\n mIdx = m20.x;\n } else if (rand < w21) {\n mIdx = m21.x;\n } else {\n mIdx = m22.x;\n }\n return i32(mIdx);\n}\n\nfn resolveSurfaceMaterialIndex(\n fallbackMaterialIndex: i32,\n index: vec3i,\n bary: vec3f\n) -> i32 {\n let m0 = vec3i(attrBuffers[index.x * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m1 = vec3i(attrBuffers[index.y * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n let m2 = vec3i(attrBuffers[index.z * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});\n if (m0.x > 0 || m1.x > 0 || m2.x > 0) {\n return randomPickMaterial(m0, m1, m2, bary);\n }\n return fallbackMaterialIndex;\n}\n\n\n\n${hasAnisotropyDirections ? /* wgsl */ `\nfn anisotropy_axes(normal: vec3f, dp1: vec3f, dp2: vec3f, duv1: vec2f, duv2: vec2f, input: vec4f) -> mat2x3f {\n let determinant = duv1.x * duv2.y - duv1.y * duv2.x;\n var t = input.xyz;\n var b = vec3f(0.0);\n // Internal W=2 marks an authored XYZ tangent whose sign is derived from UVs.\n var sign = select(input.w, 0.0, abs(input.w) > 1.5);\n if (abs(determinant) > 1e-12) {\n let dt = (dp1 * duv2.y - dp2 * duv1.y) / determinant;\n let db = (dp2 * duv1.x - dp1 * duv2.x) / determinant;\n if (dot(t, t) < 1e-8) { t = dt; }\n if (sign == 0.0) { sign = select(1.0, -1.0, dot(cross(normal, dt), db) < 0.0); }\n }\n t -= normal * dot(normal, t);\n if (dot(t, t) < 1e-8) {\n let signZ = select(-1.0, 1.0, normal.z >= 0.0);\n let factor = -1.0 / (signZ + normal.z);\n let xy = normal.x * normal.y * factor;\n t = vec3f(1.0 + signZ * normal.x * normal.x * factor, signZ * xy, -signZ * normal.x);\n b = vec3f(xy, signZ + normal.y * normal.y * factor, -normal.y);\n }\n else { t = normalize(t); b = normalize(cross(normal, t)); }\n return mat2x3f(t, b * select(1.0, -1.0, sign < 0.0));\n}\n\n` : /* wgsl */ ``}\nfn fetchAttributes(\n si: ptr<function, SurfaceInteraction>,\n tri: Triangle,\n bary: vec3f,\n index: vec3i,\n isHitMicroTriangle: bool,\n resolvedMaterialIndex: i32\n) {\n (*si).hit = true;\n // Get projected uv\n let material = materialsBuffer[(*si).materialIndex];\n let uvProjection = i32(material.uvProjection);\n\n let i0 = index.x;\n let i1 = index.y;\n let i2 = index.z;\n let isSphericalProj = uvProjection == ${uvProjectionConstants.spherical};\n\n var normal: vec3f = (*si).normal;\n var normalForSphericalProj: vec3f;\n var n0 = vec3f(0.0);\n var n1 = vec3f(0.0);\n var n2 = vec3f(0.0);\n // Otherwise use the normal from the microtriangle\n // The exception is spherical projection, we need the original normal to project the uv. Or the case like earth has wrong normal.\n if (!isHitMicroTriangle || isSphericalProj) {\n n0 = attrBuffers[i0 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n1 = attrBuffers[i1 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n n2 = attrBuffers[i2 * ATTR_STRIDE + NORMAL_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n normalForSphericalProj = normalize(bary.x * n0 + bary.y * n1 + bary.z * n2);\n if (!isHitMicroTriangle) {\n normal = normalForSphericalProj;\n }\n }\n\n var uv: vec2f;\n var uv0: vec2f;\n var uv1: vec2f;\n var uv2: vec2f;\n var secondaryUv = vec2f(0.0);\n if (USE_UV) {\n uv0 = attrBuffers[i0 * ATTR_STRIDE + UV_OFF].xy;\n uv1 = attrBuffers[i1 * ATTR_STRIDE + UV_OFF].xy;\n uv2 = attrBuffers[i2 * ATTR_STRIDE + UV_OFF].xy;\n uv = bary.x * uv0 + bary.y * uv1 + bary.z * uv2;\n if (USE_UV1) {\n let secondaryUv0 = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv1 = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let secondaryUv2 = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n secondaryUv = bary.x * secondaryUv0\n + bary.y * secondaryUv1\n + bary.z * secondaryUv2;\n }\n }\n\n if (USE_TANGENT) {\n let t0 = attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t1 = attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n let t2 = attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF]${hasAnisotropyDirections ? '.xyz' : ''};\n (*si).tangent = normalize(bary.x * t0 + bary.y * t1 + bary.z * t2);\n }\n\n if (resolvedMaterialIndex >= 0) {\n (*si).materialIndex = resolvedMaterialIndex;\n }\n else {\n (*si).materialIndex = resolveSurfaceMaterialIndex(\n (*si).materialIndex,\n index,\n bary\n );\n }\n\n if (uvProjection > 0) {\n // NOTE: position and normal are not transformed to world space yet.\n uv = projectUV(uvProjection, (*si).position, select(normal, normalForSphericalProj, isSphericalProj));\n secondaryUv = uv;\n }\n\n uv = uv * material.uvScale;\n secondaryUv = secondaryUv * material.uvScale;\n\n // Force repeating UVs for triplanar mapping\n if (uvProjection == ${uvProjectionConstants.triplanar}) {\n uv = fract(uv);\n }\n\n${hasAnisotropyDirections ? /* wgsl */ `\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normal;` : ''}\n let directionMaterial = materialsBuffer[(*si).materialIndex];\n if (directionMaterial.anisotropyMapIndex >= 0.0 || directionMaterial.specularAnisotropyRotation != 0.0 || directionMaterial.coatAnisotropyRotation != 0.0${hasCoatNormals ? \" || (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0)\" : \"\"}) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n var inputTangent = vec4f(0.0);\n if (USE_TANGENT) {\n inputTangent = bary.x * attrBuffers[i0 * ATTR_STRIDE + TANGENT_OFF]\n + bary.y * attrBuffers[i1 * ATTR_STRIDE + TANGENT_OFF]\n + bary.z * attrBuffers[i2 * ATTR_STRIDE + TANGENT_OFF];\n }\n let geometryAxes = anisotropy_axes(normal, dp1, dp2, uv0-uv2, uv1-uv2, inputTangent);\n (*si).geometryTangent = geometryAxes[0];\n (*si).geometryBitangent = geometryAxes[1];\n var directionAxes = geometryAxes;\n if (USE_UV1 && directionMaterial.anisotropyMapIndex >= 0.0 && mapInfo[i32(directionMaterial.anisotropyMapIndex) + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n directionAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c, select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n (*si).directionTangent = directionAxes[0];\n (*si).directionBitangent = directionAxes[1];\n${hasCoatNormals ? /* wgsl */ `\n if (USE_COAT_NORMAL_MAPS && directionMaterial.coatNormalMapIndex >= 0.0) {\n let coatIndex = i32(directionMaterial.coatNormalMapIndex);\n var coatAxes = geometryAxes;\n if (USE_UV1 && mapInfo[coatIndex + NUM_MAPS * 2].x > 0.5) {\n let a = attrBuffers[i0 * ATTR_STRIDE + UV1_OFF].xy;\n let b = attrBuffers[i1 * ATTR_STRIDE + UV1_OFF].xy;\n let c = attrBuffers[i2 * ATTR_STRIDE + UV1_OFF].xy;\n coatAxes = anisotropy_axes(normal, dp1, dp2, a-c, b-c,\n select(inputTangent, vec4f(0.0), inputTangent.w == 0.0));\n }\n // Procedural projection overrides both UV sets. UV0 has already applied\n // triplanar wrapping, so use it for either texture channel in that case.\n let decoded = sampleMap(coatIndex, getMapUvSet(coatIndex, uv,\n select(secondaryUv, uv, uvProjection > 0))).rgb * 2.0 - 1.0;\n let mapped = coatAxes[0] * decoded.x * directionMaterial.coatNormalScale.x\n + coatAxes[1] * decoded.y * directionMaterial.coatNormalScale.y + normal * decoded.z;\n if (dot(mapped, mapped) > 1e-12) { (*si).coatNormal = normalize(mapped); }\n }\n` : ''}\n }\n\n` : /* wgsl */ ``}\n (*si).normal = normal;\n (*si).vertexNormal = normal;\n (*si).shadowOffset = vec3f(0.0);\n if (!isHitMicroTriangle) {\n // Smooth-limit geometry offset for direct-light shadow rays. This follows\n // the Cycles shadow terminator construction while keeping the result in\n // local space until the instance transform is available.\n let geometricNormal = normalize(cross(tri.p1 - tri.p0, tri.p2 - tri.p0));\n let smoothNormal = bary.x * n0 + bary.y * n1 + bary.z * n2;\n let a = dot(n2 - n0, tri.p0 - tri.p2);\n let b = dot(n2 - n1, tri.p1 - tri.p2);\n let c = dot(n1 - n0, tri.p1 - tri.p0);\n var h = a * bary.x * (bary.x - 1.0)\n + (a + b + c) * bary.x * bary.y\n + b * bary.y * (bary.y - 1.0);\n if (dot(smoothNormal, geometricNormal) > 0.0) {\n var h0 = max(max(dot(tri.p1 - tri.p0, n0), dot(tri.p2 - tri.p0, n0)), 0.0);\n var h1 = max(max(dot(tri.p0 - tri.p1, n1), dot(tri.p2 - tri.p1, n1)), 0.0);\n var h2 = max(max(dot(tri.p0 - tri.p2, n2), dot(tri.p1 - tri.p2, n2)), 0.0);\n h0 = max(dot(tri.p0 - (*si).position, n0) + h0, 0.0);\n h1 = max(dot(tri.p1 - (*si).position, n1) + h1, 0.0);\n h2 = max(dot(tri.p2 - (*si).position, n2) + h2, 0.0);\n h = max(min(min(h0, h1), h2), h * 0.5);\n }\n else {\n var h0 = max(max(dot(tri.p0 - tri.p1, n0), dot(tri.p0 - tri.p2, n0)), 0.0);\n var h1 = max(max(dot(tri.p1 - tri.p0, n1), dot(tri.p1 - tri.p2, n1)), 0.0);\n var h2 = max(max(dot(tri.p2 - tri.p0, n2), dot(tri.p2 - tri.p1, n2)), 0.0);\n h0 = max(dot((*si).position - tri.p0, n0) + h0, 0.0);\n h1 = max(dot((*si).position - tri.p1, n1) + h1, 0.0);\n h2 = max(dot((*si).position - tri.p2, n2) + h2, 0.0);\n h = min(-min(min(h0, h1), h2), h * 0.5);\n }\n (*si).shadowOffset = smoothNormal * h;\n }\n (*si).uv = uv;\n (*si).uv1 = secondaryUv;\n if (USE_NORMAL_MAPS) {\n let dp1 = tri.p0 - tri.p2;\n let dp2 = tri.p1 - tri.p2;\n let duv1 = uv0 - uv2;\n let duv2 = uv1 - uv2;\n (*si).normal = getMatNormal(\n (*si).materialIndex, uv, normal, dp1, dp2, duv1, duv2\n );\n }\n}\n\nstruct TriangleIntersect {\n t: f32,\n barycentric: vec3f,\n};\n\n\n// Triangle-ray intersection\n// Faster than the classic Möller–Trumbore intersection algorithm\n// http://www.pbr-book.org/3ed-2018/Shapes/Triangle_Meshes.html#TriangleIntersection\nfn intersectTriangle(o: vec3f, d: vec3f, tri: Triangle, maxDim: i32, shear: vec3f, tMax: f32) -> TriangleIntersect {\n var ti: TriangleIntersect;\n\n // translate vertices based on ray origin\n var p0t = tri.p0 - o;\n var p1t = tri.p1 - o;\n var p2t = tri.p2 - o;\n\n // permute components of triangle vertices\n if (maxDim == 0) {\n p0t = p0t.yzx;\n p1t = p1t.yzx;\n p2t = p2t.yzx;\n } else if (maxDim == 1) {\n p0t = p0t.zxy;\n p1t = p1t.zxy;\n p2t = p2t.zxy;\n }\n\n // apply shear transformation to translated vertex positions\n // TODO swizzled assignment is not supported in wgsl\n p0t = vec3f(p0t.xy + shear.xy * p0t.z, p0t.z);\n p1t = vec3f(p1t.xy + shear.xy * p1t.z, p1t.z);\n p2t = vec3f(p2t.xy + shear.xy * p2t.z, p2t.z);\n\n // compute edge function coefficients\n var e = vec3f(\n p1t.x * p2t.y - p1t.y * p2t.x,\n p2t.x * p0t.y - p2t.y * p0t.x,\n p0t.x * p1t.y - p0t.y * p1t.x\n );\n\n if (DEPTH_QUERY_MODE) {\n // Exact grid rays can land on shared edges. Roundoff in the two edge\n // products must not turn the edge into a crack between both triangles.\n let edgeScale = abs(vec3f(p1t.x * p2t.y, p2t.x * p0t.y, p0t.x * p1t.y))\n + abs(vec3f(p1t.y * p2t.x, p2t.y * p0t.x, p0t.y * p1t.x));\n e = select(e, vec3f(0.0), abs(e) <= edgeScale * 1e-6);\n }\n\n // check if intersection is inside triangle\n if (any(e < vec3f(0)) && any(e > vec3f(0))) {\n return ti;\n }\n\n let det = e.x + e.y + e.z;\n if (DEPTH_QUERY_MODE && det == 0.0) { return ti; }\n\n p0t.z *= shear.z;\n p1t.z *= shear.z;\n p2t.z *= shear.z;\n let tScaled = (e.x * p0t.z + e.y * p1t.z + e.z * p2t.z);\n\n // check if closer intersection already exists\n if (abs(tScaled) > abs(tMax * det)) {\n return ti;\n }\n\n let invDet = 1. / det;\n ti.t = tScaled * invDet;\n ti.barycentric = e * invDet;\n\n return ti;\n}\n\n// Pinhole depth queries can lie exactly on a grid vertex or a flat slab.\n// Specialize conservative slabs for that entry point; beauty retains its\n// original traversal arithmetic and random streams.\noverride DEPTH_QUERY_MODE: bool = false;\n\nfn traversalInverse(d: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return 1.0 / d; }\n return 1.0 / select(d, select(vec3f(1e-20), vec3f(-1e-20), d < vec3f(0.0)), abs(d) < vec3f(1e-20));\n}\n\nfn depthSlabPadding(invD: vec3f, o: vec3f) -> vec3f {\n if (!DEPTH_QUERY_MODE) { return vec3f(0.0); }\n return abs(invD) * max(vec3f(1.0), abs(o)) * 1e-6;\n}\n\n// Branchless ray/box intersection\n// https://tavianator.com/fast-branchless-raybounding-box-intersections/\nfn intersectBox(o: vec3f, bmin: vec3f, bmax: vec3f, invD: vec3f, tMax: f32) -> f32 {\n let tBot = (bmin - o) * invD;\n let tTop = (bmax - o) * invD;\n let padding = depthSlabPadding(invD, o);\n let tNear = min(tBot, tTop) - padding;\n let tFar = max(tBot, tTop) + padding;\n let t0 = max(tNear.x, max(tNear.y, tNear.z));\n let t1 = min(tFar.x, min(tFar.y, tFar.z));\n return select(select(t1, t0, t0 > 0.0), -1.0, (t0 > t1 || t0 > tMax));\n}\n\nfn maxDimension(v: vec3f) -> i32 {\n return select(select(2, 1, v.y > v.z), select(2, 0, v.x > v.z), v.x > v.y);\n}\n\nfn getShear(d: vec3f, maxDim: i32) -> vec3f {\n // Permute space so that the z dimension is the one where the absolute value of the ray's direction is largest.\n // Then create a shear transformation that aligns ray direction with the +z axis\n if (maxDim == 0) {\n return vec3f(-d.y, -d.z, 1.0) / d.x;\n } else if (maxDim == 1) {\n return vec3f(-d.z, -d.x, 1.0) / d.y;\n } else {\n return vec3f(-d.x, -d.y, 1.0) / d.z;\n }\n}\n\nfn completeSurfaceInteraction(\n si: ptr<function, SurfaceInteraction>,\n hitTri: Triangle,\n hitBarycentric: vec3f,\n hitIndex: vec3i,\n isHitMicroTriangle: bool,\n hitInsIdx: i32,\n hitNormal: vec3f,\n hitPosition: vec3f,\n resolvedMaterialIndex: i32\n) {\n (*si).instanceIndex = hitInsIdx;\n (*si).barycentrics = hitBarycentric.xy;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(\n bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)\n ).x);\n // NOTE: Must be called before transforming normal/position to world space.\n // UV projection depends on the local position.\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n resolvedMaterialIndex\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n}\n\n\n${createIntersectionAlphaWgsl(hasAnisotropyDirections)}\n\n\n\n// Traverse BVH, find closest triangle intersection, and return surface information\nfn intersectScene(ray: Ray, si: ptr<function, SurfaceInteraction>, bounces: i32, doubleSided: bool, isRefractionRay: bool) {\n\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n var tMax = RAY_MAX_DISTANCE;\n var maxDim: i32;\n var shear: vec3f;\n\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = (*si).triangleIndex;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n if (isHit) {\n completeSurfaceInteraction(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n hitInsIdx,\n hitNormal,\n hitPosition,\n -1\n );\n }\n}\n\n// Local intersection helper for shader bevel.\nfn intersectSceneBevel(ray: Ray, si: ptr<function, SurfaceInteraction>, instanceIndex: i32, maxDistance: f32) {\n (*si).hit = false;\n\n if (rtUniform.isEmpty > 0) {\n return;\n }\n\n let bounces = 0;\n let doubleSided = true;\n let isRefractionRay = false;\n var tMax = maxDistance;\n var maxDim: i32;\n var shear: vec3f;\n var hitIndex: vec3i;\n var hitTri: Triangle;\n var hitInsIdx: i32;\n var currentTriangleIndex: i32 = -1;\n\n ${getBVHTraverseCode(\n `\n(*si).triangleIndex = triangleIndex;\nhitTri = tri;\nhitIndex = index;\nhitInsIdx = insIdx;\n `,\n hasDisplacement,\n false,\n blasLayoutMask,\n hasAnisotropyDirections,\n `\n if (insIdx != instanceIndex) {\n continue;\n }\n `\n )}\n\n if (isHit) {\n (*si).instanceIndex = hitInsIdx;\n (*si).normal = hitNormal;\n (*si).position = hitPosition;\n (*si).materialIndex = i32(unpackMaterialIndex(bitcast<i32>(instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE}].z)).x);\n fetchAttributes(\n si,\n hitTri,\n hitBarycentric,\n hitIndex,\n isHitMicroTriangle,\n -1\n );\n\n let l2w = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 5],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 6],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 7],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 8]\n );\n let w2l = mat4x4<f32>(\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 1],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 2],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 3],\n instancesBuffer[hitInsIdx * ${INSTANCE_VEC4_STRIDE} + 4]\n );\n let normalTransform = transpose(mat3x3<f32>(\n w2l[0].xyz,\n w2l[1].xyz,\n w2l[2].xyz\n ));\n (*si).faceNormal = normalize(normalTransform * hitNormal);\n (*si).vertexNormal = normalize(normalTransform * (*si).vertexNormal);\n (*si).normal = normalize(normalTransform * (*si).normal);\n${hasCoatNormals ? /* wgsl */ ` (*si).coatNormal = normalize(normalTransform * (*si).coatNormal);` : ''}\n if (USE_TANGENT && dot((*si).tangent, (*si).tangent) > 0.0) {\n (*si).tangent = normalize((l2w * vec4f((*si).tangent, 0.0)).xyz);\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n (*si).directionTangent = (l2w * vec4f((*si).directionTangent, 0.0)).xyz;\n (*si).directionBitangent = (l2w * vec4f((*si).directionBitangent, 0.0)).xyz;\n (*si).geometryTangent = (l2w * vec4f((*si).geometryTangent, 0.0)).xyz;\n (*si).geometryBitangent = (l2w * vec4f((*si).geometryBitangent, 0.0)).xyz;\n` : /* wgsl */ ``}\n (*si).shadowOffset = (l2w * vec4f((*si).shadowOffset, 0.0)).xyz;\n (*si).position = (l2w * vec4((*si).position, 1.0)).xyz;\n }\n}\n\nfn intersectSceneShadow(ray: Ray, lightDistance: f32, currTriangleIndex: i32) -> bool {\n if (rtUniform.isEmpty > 0) {\n return false;\n }\n\n let doubleSided = true;\n let shadowRayTMax = min(max(lightDistance, 0.0), RAY_MAX_DISTANCE);\n var tMax = shadowRayTMax;\n var maxDim: i32;\n var shear: vec3f;\n\n let bounces = 1;\n var isRefractionRay = false;\n var currentTriangleIndex: i32 = currTriangleIndex;\n\n ${getBVHTraverseCode(\n /*wgsl */ `\n// Traversal is already clipped to the sampled light. Affine instance\n// transforms preserve the ray parameter because local directions are not\n// renormalized, so no local-to-world hit transform or distance() is needed.\nvar isOccluded = true;\n\nif (isOccluded) {\n if (HAS_ALPHA) {\n let materialIndex = i32(unpackMaterialIndex(bitcast<i32>(insData.z)).x);\n if (!intersectionAlphaPasses(\n materialIndex,\n index,\n hitBarycentric,\n hitPosition,\n hitNormal\n )) {\n // Continue looking for an opaque hit, but never traverse beyond the light.\n tMax = shadowRayTMax;\n isOccluded = false;\n }\n }\n}\nif (isOccluded) {\n return true;\n}\n `,\n hasDisplacement,\n true,\n blasLayoutMask,\n hasAnisotropyDirections\n )}\n\n return false;\n}\n\n`;\n}\n"]}
@@ -8,14 +8,14 @@ import { materialAlphaModeConstants } from '../../../scene/OpenPBRMaterial.js';
8
8
  */
9
9
  export function createIntersectionAlphaWgsl(hasAnisotropyDirections = true) {
10
10
  return /* wgsl */ `
11
- fn nl(
12
- ph: i32,
11
+ fn nr(
12
+ pl: i32,
13
13
  index: vec3i,
14
14
  aB: vec3f,
15
15
  eU: vec3f,
16
16
  eC: vec3f
17
17
  ) -> bool {
18
- var materialIndex = ph;
18
+ var materialIndex = pl;
19
19
  let m0 = vec3i(attrBuffers[index.x * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});
20
20
  let m1 = vec3i(attrBuffers[index.y * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});
21
21
  let m2 = vec3i(attrBuffers[index.z * ATTR_STRIDE + MATERIALINDEX_OFF]${hasAnisotropyDirections ? '.xyz' : ''});
@@ -37,7 +37,7 @@ let uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;
37
37
  var uv = aB.x * uv0 +
38
38
  aB.y * uv1 + aB.z * uv2;
39
39
  if (uvProjection > 0) {
40
- uv = gz(uvProjection, eU, eC);
40
+ uv = gC(uvProjection, eU, eC);
41
41
  }
42
42
  uv *= ma.uvScale;
43
43
  if (uvProjection == ${uvProjectionConstants.triplanar}) {
@@ -57,7 +57,7 @@ let uv2 = attrBuffers[index.z * ATTR_STRIDE + UV_OFF].xy;
57
57
  var uv = aB.x * uv0 +
58
58
  aB.y * uv1 + aB.z * uv2;
59
59
  if (uvProjection > 0) {
60
- uv = gz(uvProjection, eU, eC);
60
+ uv = gC(uvProjection, eU, eC);
61
61
  }
62
62
  uv *= ma.uvScale;
63
63
  if (uvProjection == ${uvProjectionConstants.triplanar}) {
@@ -29,43 +29,43 @@ return vec2f(pow((a*a + u*u) * 0.5, 0.25), clamp(1.0 - u/a, 0.0, 1.0));
29
29
  }
30
30
  ` : /* wgsl */ ``}
31
31
 
32
- fn oy(c: vec3f) -> vec3f {
32
+ fn oB(c: vec3f) -> vec3f {
33
33
  return mix(pow(c * 0.9478672986 + 0.0521327014, vec3f(2.4)), c * 0.0773993808, vec3f(c < vec3f(0.04045)));
34
34
  }
35
35
 
36
36
  fn decodeMapColor(idx: i32, c: vec3f) -> vec3f {
37
37
  if (mapInfo[idx + NUM_MAPS * 2].y > 0.5) { return c; }
38
- return oy(c);
38
+ return oB(c);
39
39
  }
40
40
 
41
41
  fn ae(val: i32) -> vec2f {
42
42
  return vec2f(f32(val >> 8), f32(val & 0xff) / 255.0);
43
43
  }
44
44
 
45
- fn nX(idx: i32) -> vec2f {
45
+ fn oa(idx: i32) -> vec2f {
46
46
  return mapInfo[idx].xy;
47
47
  }
48
48
 
49
- fn pf(idx: i32) -> i32 {
49
+ fn pj(idx: i32) -> i32 {
50
50
  return i32(floor(mapInfo[idx].z));
51
51
  }
52
52
 
53
- fn nW(idx: i32) -> vec2f {
53
+ fn nZ(idx: i32) -> vec2f {
54
54
  return vec2f(fract(mapInfo[idx].z), mapInfo[idx].w);
55
55
  }
56
56
 
57
- fn sb(idx: i32, uv: vec2f) -> vec2f {
57
+ fn sg(idx: i32, uv: vec2f) -> vec2f {
58
58
  let transform = mapInfo[idx + NUM_MAPS];
59
- let qv = fract(uv * transform.xy + transform.zw);
60
- let je = 1.0 / vec2f(textureDimensions(map).xy);
61
- let dZ = nW(idx);
62
- let ke = nX(idx) + je;
63
- let oB = dZ - 0.5 * je;
64
- let lo = dZ + ke - je;
59
+ let qz = fract(uv * transform.xy + transform.zw);
60
+ let jg = 1.0 / vec2f(textureDimensions(map).xy);
61
+ let dZ = nZ(idx);
62
+ let kg = oa(idx) + jg;
63
+ let oE = dZ - 0.5 * jg;
64
+ let lr = dZ + kg - jg;
65
65
  return clamp(
66
- oB + qv * ke,
66
+ oE + qz * kg,
67
67
  dZ,
68
- lo
68
+ lr
69
69
  );
70
70
  }
71
71
 
@@ -74,11 +74,11 @@ return select(uv, uv1, mapInfo[idx + NUM_MAPS * 2].x > 0.5);
74
74
  }
75
75
 
76
76
  fn sampleMap(idx: i32, uv: vec2f) -> vec4f {
77
- return textureSampleLevel(map, mapSampler, sb(idx, uv), pf(idx), 0.0);
77
+ return textureSampleLevel(map, mapSampler, sg(idx, uv), pj(idx), 0.0);
78
78
  }
79
79
 
80
80
 
81
- fn lB(materialIndex: i32, uv: vec2f, uv1: vec2f) -> vec3f {
81
+ fn lE(materialIndex: i32, uv: vec2f, uv1: vec2f) -> vec3f {
82
82
  let matData = materialsBuffer[materialIndex];
83
83
  var emission = matData.emission;
84
84
  if (USE_EMISSIVE_MAPS) {
@@ -102,16 +102,16 @@ return emission;
102
102
  }
103
103
 
104
104
 
105
- fn aJ(roughness: f32, anisotropy: f32) -> vec2f {
105
+ fn aK(roughness: f32, anisotropy: f32) -> vec2f {
106
106
  let alpha = roughness * roughness;
107
- let oT = ROUGHNESS_MIN * ROUGHNESS_MIN;
108
- if (anisotropy == 0.0) { return vec2f(max(oT, alpha)); }
107
+ let oX = ROUGHNESS_MIN * ROUGHNESS_MIN;
108
+ if (anisotropy == 0.0) { return vec2f(max(oX, alpha)); }
109
109
  let dg = 1.0 - anisotropy;
110
- let rU = alpha * sqrt(2.0 / (1.0 + dg * dg));
111
- return max(vec2f(oT), vec2f(rU, dg * rU));
110
+ let rZ = alpha * sqrt(2.0 / (1.0 + dg * dg));
111
+ return max(vec2f(oX), vec2f(rZ, dg * rZ));
112
112
  }
113
113
 
114
- fn kx(instanceIndex: i32) -> vec3f {
114
+ fn kA(instanceIndex: i32) -> vec3f {
115
115
  if (instanceIndex < 0) { return vec3f(1.0); }
116
116
  return instancesBuffer[instanceIndex * ${INSTANCE_VEC4_STRIDE} + 9].xyz;
117
117
  }
@@ -130,7 +130,7 @@ let matData = materialsBuffer[materialIndex];
130
130
  (*ma).specularRoughness = matData.roughness;
131
131
  (*ma).baseMetalness = matData.metallic;
132
132
  (*ma).specularAnisotropy = clamp(matData.anisotropy, 0.0, 1.0);
133
- (*ma).emission = lB(materialIndex, uv, uv1);
133
+ (*ma).emission = lE(materialIndex, uv, uv1);
134
134
  (*ma).inside = false;
135
135
  (*ma).specularIor = matData.ior;
136
136
  (*ma).transmissionWeight = matData.transmission;
@@ -169,19 +169,19 @@ if (USE_DIFFUSE_MAPS) {
169
169
  let diffuseMapIndex = i32(matData.diffuseMapIndex);
170
170
  if (diffuseMapIndex >= 0) {
171
171
 
172
- let nN = sampleMap(
172
+ let nQ = sampleMap(
173
173
  diffuseMapIndex, getMapUvSet(diffuseMapIndex, uv, uv1)
174
174
  );
175
175
 
176
176
 
177
- (*ma).baseColor *= decodeMapColor(diffuseMapIndex, nN.rgb);
177
+ (*ma).baseColor *= decodeMapColor(diffuseMapIndex, nQ.rgb);
178
178
 
179
179
 
180
180
  if (matData.mapAlphaUsage == ${mapAlphaUsageConstants.transmission}) {
181
- (*ma).transmissionWeight = 1.0 - nN.a;
181
+ (*ma).transmissionWeight = 1.0 - nQ.a;
182
182
  }
183
183
  else if (matData.mapAlphaUsage == ${mapAlphaUsageConstants.alpha}) {
184
- (*ma).alpha *= nN.a;
184
+ (*ma).alpha *= nQ.a;
185
185
  }
186
186
  }
187
187
  }
@@ -232,16 +232,16 @@ ${hasAnisotropyDirections ? /* wgsl */ `
232
232
  (*ma).coatAnisotropyRotation = matData.coatAnisotropyRotation;
233
233
  if (USE_ANISOTROPY_MAPS && matData.anisotropyMapIndex >= 0.0) {
234
234
  let index = i32(matData.anisotropyMapIndex);
235
- let iW = sampleMap(index, getMapUvSet(index, uv, uv1));
236
- (*ma).specularAnisotropy *= iW.b;
237
- let direction = iW.rg * 2.0 - 1.0;
235
+ let iX = sampleMap(index, getMapUvSet(index, uv, uv1));
236
+ (*ma).specularAnisotropy *= iX.b;
237
+ let direction = iX.rg * 2.0 - 1.0;
238
238
  if (dot(direction, direction) > 1e-4) { (*ma).anisotropyDirection = normalize(direction); }
239
239
  (*ma).anisotropyDirectionEnabled = true;
240
240
  }
241
241
  if (matData.specularAnisotropyModel > 0.5 && (*ma).specularAnisotropy > 0.0) {
242
- let sp = fb(clamp((*ma).specularRoughness, 0.0, 1.0), (*ma).specularAnisotropy);
243
- (*ma).specularRoughness = sp.x;
244
- (*ma).specularAnisotropy = sp.y;
242
+ let sv = fb(clamp((*ma).specularRoughness, 0.0, 1.0), (*ma).specularAnisotropy);
243
+ (*ma).specularRoughness = sv.x;
244
+ (*ma).specularAnisotropy = sv.y;
245
245
  }
246
246
  if ((*ma).anisotropyDirectionEnabled) {
247
247
  let theta = matData.specularAnisotropyRotation;
@@ -253,55 +253,55 @@ let d = (*ma).anisotropyDirection;
253
253
 
254
254
 
255
255
  if ((*ma).coatWeight > 0.0 && (*ma).coatRoughness > 0.0) {
256
- let hJ = (*ma).specularRoughness * (*ma).specularRoughness;
257
- let hK = (*ma).coatRoughness * (*ma).coatRoughness;
256
+ let hI = (*ma).specularRoughness * (*ma).specularRoughness;
257
+ let hJ = (*ma).coatRoughness * (*ma).coatRoughness;
258
258
  (*ma).specularRoughness = mix((*ma).specularRoughness,
259
- pow(min(1.0, hJ * hJ + 2.0 * hK * hK), 0.25), (*ma).coatWeight);
259
+ pow(min(1.0, hI * hI + 2.0 * hJ * hJ), 0.25), (*ma).coatWeight);
260
260
  }
261
- let instanceColor = kx(instanceIndex);
261
+ let instanceColor = kA(instanceIndex);
262
262
  (*ma).baseColor *= instanceColor;
263
263
  if ((*ma).materialType == UNLIT) { (*ma).emission *= instanceColor; }
264
264
 
265
265
  (*ma).specularRoughness = clamp((*ma).specularRoughness, ROUGHNESS_MIN, 1.0);
266
266
  (*ma).baseMetalness = clamp((*ma).baseMetalness, 0.0, 1.0);
267
- let specularAlpha = aJ((*ma).specularRoughness, (*ma).specularAnisotropy);
267
+ let specularAlpha = aK((*ma).specularRoughness, (*ma).specularAnisotropy);
268
268
  (*ma).alphaX = specularAlpha.x;
269
269
  (*ma).alphaY = specularAlpha.y;
270
270
  }
271
271
 
272
- fn nY(materialIndex: i32, uv: vec2f, normal: vec3f, dp1: vec3f, dp2: vec3f, nP: vec2f, nQ: vec2f) -> vec3f {
272
+ fn ob(materialIndex: i32, uv: vec2f, normal: vec3f, dp1: vec3f, dp2: vec3f, nS: vec2f, nT: vec2f) -> vec3f {
273
273
  let matData = materialsBuffer[materialIndex];
274
274
  let normalMapIndex = i32(matData.normalMapIndex);
275
275
  if (normalMapIndex >= 0) {
276
276
 
277
277
 
278
278
 
279
- let qJ = cross(dp2, normal);
280
- let qI = cross(normal, dp1);
281
- var dpdu = qJ * nP.x + qI * nQ.x;
282
- var rZ = qJ * nP.y + qI * nQ.y;
283
- let dS = max(dot(dpdu, dpdu), dot(rZ, rZ));
279
+ let qO = cross(dp2, normal);
280
+ let qM = cross(normal, dp1);
281
+ var dpdu = qO * nS.x + qM * nT.x;
282
+ var se = qO * nS.y + qM * nT.y;
283
+ let dS = max(dot(dpdu, dpdu), dot(se, se));
284
284
  if (dS > 1e-12) {
285
- let sh = inverseSqrt(dS);
286
- dpdu *= sh;
287
- rZ *= sh;
285
+ let sn = inverseSqrt(dS);
286
+ dpdu *= sn;
287
+ se *= sn;
288
288
  }
289
289
  else {
290
290
 
291
291
 
292
- let mZ = select(
292
+ let nf = select(
293
293
  vec3f(0.0, 1.0, 0.0),
294
294
  vec3f(0.0, 0.0, 1.0),
295
295
  abs(normal.z) < 0.999
296
296
  );
297
- dpdu = normalize(cross(mZ, normal));
298
- rZ = cross(normal, dpdu);
297
+ dpdu = normalize(cross(nf, normal));
298
+ se = cross(normal, dpdu);
299
299
  }
300
300
 
301
301
  var n = 2.0 * sampleMap(normalMapIndex, uv).rgb - 1.0;
302
302
  n = normalize(vec3(n.xy * matData.normalMapScale, n.z));
303
303
 
304
- let tbn = mat3x3(dpdu, rZ, normal);
304
+ let tbn = mat3x3(dpdu, se, normal);
305
305
 
306
306
  return normalize(tbn * n);
307
307
  } else {