@meimu/mutracer 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +4114 -4015
- package/docs/api.md +11 -9
- package/docs/threejs.md +30 -4
- package/lib/Engine.js +18 -1
- package/lib/Engine.js.map +1 -1
- package/lib/main.d.ts +1 -1
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/oidnBundledWeights.cdn.d.ts +2 -2
- package/lib/renderer/MaterialTable.d.ts +1 -0
- package/lib/renderer/MaterialTable.js +10 -4
- package/lib/renderer/MaterialTable.js.map +1 -1
- package/lib/renderer/WGPUBloomPass.js +18 -18
- package/lib/renderer/WGPUFullQuadPass.js +6 -6
- package/lib/renderer/WGPUJNLMDenoiser.js +69 -69
- package/lib/renderer/WGPURayTraceOutput.js +14 -14
- package/lib/renderer/WGPURayTracePass.d.ts +2 -0
- package/lib/renderer/WGPURayTracePass.js +12 -6
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +15 -15
- package/lib/renderer/WebGLJNLMDenoiser.js +69 -69
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +1 -0
- package/lib/renderer/WebGLRayTracePass.js +7 -1
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/envMapCreation.d.ts +5 -1
- package/lib/renderer/envMapCreation.js +42 -5
- package/lib/renderer/envMapCreation.js.map +1 -1
- package/lib/renderer/geometryUpdates.js +1 -1
- package/lib/renderer/geometryUpdates.js.map +1 -1
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +12 -12
- package/lib/renderer/glsl/chunks/envMap.glsl.js +66 -54
- package/lib/renderer/glsl/chunks/envMap.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/intersect.glsl.js +316 -310
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +151 -119
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +414 -414
- package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/random.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +40 -39
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +69 -69
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +78 -78
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +12 -12
- package/lib/renderer/glsl/chunks/sampling.glsl.js +75 -75
- package/lib/renderer/glsl/chunks/sobol.glsl.js +61 -61
- package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
- package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
- package/lib/renderer/glsl/depthQuery.frag.js +5 -5
- package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
- package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +8 -8
- package/lib/renderer/glsl/rayTrace.frag.js +133 -133
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +9 -9
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +52 -52
- package/lib/renderer/lightsUtil.js +6 -4
- package/lib/renderer/lightsUtil.js.map +1 -1
- package/lib/renderer/materialChanges.js +1 -1
- package/lib/renderer/materialChanges.js.map +1 -1
- package/lib/renderer/materialComparison.generated.js +36 -18
- package/lib/renderer/materialComparison.generated.js.map +1 -1
- package/lib/renderer/prepareMeshBVH.js +2 -2
- package/lib/renderer/prepareMeshBVH.js.map +1 -1
- package/lib/renderer/sceneChangeDetector.js +4 -1
- package/lib/renderer/sceneChangeDetector.js.map +1 -1
- package/lib/renderer/sceneLightChanges.js +3 -2
- package/lib/renderer/sceneLightChanges.js.map +1 -1
- package/lib/renderer/scratchSampling.js +18 -18
- package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/envMap.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +39 -31
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +475 -469
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +8 -8
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +93 -59
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +469 -469
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +117 -120
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +25 -22
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +127 -127
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +136 -136
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +6 -6
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +73 -73
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +62 -62
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/compose.frag.wgsl.js +105 -105
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/depthQuery.wgsl.js +5 -5
- package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +28 -28
- package/lib/renderer/wgsl/rayTrace.compute.js +251 -248
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/scene/EnvironmentLight.d.ts +9 -3
- package/lib/scene/EnvironmentLight.js +9 -2
- package/lib/scene/EnvironmentLight.js.map +1 -1
- package/lib/scene/Scene.d.ts +4 -0
- package/lib/scene/Scene.js.map +1 -1
- package/lib/scene/SpotLight.d.ts +2 -2
- package/lib/scene/SpotLight.js +1 -1
- package/lib/scene/SpotLight.js.map +1 -1
- package/lib/scene/Texture.d.ts +17 -6
- package/lib/scene/Texture.js +13 -4
- package/lib/scene/Texture.js.map +1 -1
- package/lib/util/fromTHREE.js +151 -29
- package/lib/util/fromTHREE.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intersect.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/intersect.wgsl.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kFAAkF;AAElF,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,oBAAoB,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAE9C,MAAM,iBAAiB,GAAG;IACxB,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;;;;;CAmBnB,CAAC;AACF,CAAC,CAAC;AAEF,SAAS,qBAAqB,CAAC,MAAe;IAC5C,OAAO,SAAS,CAAC;;;;;;;;;;;;;;EAcb,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;;CAG9B,CAAC;AACF,CAAC;AAED,MAAM,yBAAyB,GAAG,UAAU,MAAe,EAAE,uBAAgC;IAC3F,OAAO,UAAU,CAAC;;;;;;;oEAOgD,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;oEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;EAiBrG,qBAAqB,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;EAiB7B,iBAAiB,EAAE;;CAEtB,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGrC,CAAC;AAEF,SAAS,kBAAkB,CACzB,aAAqB,EACrB,eAAwB,EACxB,MAAe,EACf,cAAsB,EACtB,uBAAgC,EAChC,gBAAgB,GAAG,EAAE;IAErB,MAAM,kBAAkB,GAAG,UAAU,CAAC;gCACO,oBAAoB;;;;EAIzD,gBAAgB;;;;;;;;mDAQmC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;mDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;mDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;EAIxF,eAAe;QACf,CAAC,CAAC,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,CAAC;QAC5D,CAAC,CAAC,iBAAiB,EAAE;;EAEnB,aAAa;;;CAGxB,CAAC;IACA,OAAO,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAiE2B,oBAAoB;;uBAEhC,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;;;;EAa/C,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgFhC,kBAAkB;;;;;;;;;;;;;;CAc3B,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;EAiBF,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzC,CAAC,CAAC,CAAC,EAAE;EACJ,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgFzC,CAAC,CAAC,CAAC,EAAE;EACJ,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDzC,CAAC,CAAC,CAAC,EAAE;;EAEF,kBAAkB;;;;;;;CAO7B,CAAC;AACF,CAAC;AACD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,eAAe,EACf,uBAAuB,GAAG,IAAI,EAC9B,cAAc,GAAG,KAAK,EACtB,cAAc,GAAG,CAAC,EAMnB;IACC,OAAO,QAAQ,CAAC;;EAEhB,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE;EACvC,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEA8PmB,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;EAS5G,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;2BAiByB,qBAAqB,CAAC,SAAS;;;;;;;;;;iDAUpB,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;iDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;iDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;sDA4BhC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;sDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;sDACrC,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;sBAyBvE,qBAAqB,CAAC,SAAS;;;;EAIrD,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;EACrC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,EAAE;;2JAE4F,cAAc,CAAC,CAAC,CAAC,2EAA2E,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;EAqB5P,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;CAmB7B,CAAC,CAAC,CAAC,EAAE;;;CAGL,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAuL8B,oBAAoB;;;;;;;;;;;;;;uBAcjC,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;uBAGpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;;EAWpD,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,wDAAqE,CAAC,CAAC,CAAC,EAAE;;;;EAItG,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;CAKtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;EAMf,2BAA2B,CAAC,uBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;EAsBlD,kBAAkB,CAClB;;;;;CAKD,EACC,eAAe,EACf,KAAK,EACL,cAAc,EACd,uBAAuB,CACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCC,kBAAkB,CAClB;;;;;CAKC,EACD,eAAe,EACf,KAAK,EACL,cAAc,EACd,uBAAuB,EACvB;;;;CAIC,CACF;;;;;;iEAM0F,oBAAoB;;;;;;;;;;;uBAW7E,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;uBAGpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;uBACpB,oBAAoB;;;;;;;;;;EAUtD,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,wDAAqE,CAAC,CAAC,CAAC,EAAE;;;;EAItG,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;CAKtC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;EAqBb,kBAAkB;IAClB,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBT,EACD,eAAe,EACf,IAAI,EACJ,cAAc,EACd,uBAAuB,CACxB;;;;;CAKF,CAAC;AACF,CAAC","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"]}
|
|
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 (*si).vertexColor = vec3f(1.0);\n if (USE_VERTEX_COLOR) {\n (*si).vertexColor = bary.x * attrBuffers[i0 * ATTR_STRIDE + COLOR_OFF].xyz\n + bary.y * attrBuffers[i1 * ATTR_STRIDE + COLOR_OFF].xyz\n + bary.z * attrBuffers[i2 * ATTR_STRIDE + COLOR_OFF].xyz;\n }\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,20 +8,20 @@ import { materialAlphaModeConstants } from '../../../scene/OpenPBRMaterial.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export function createIntersectionAlphaWgsl(hasAnisotropyDirections = true) {
|
|
10
10
|
return /* wgsl */ `
|
|
11
|
-
fn
|
|
12
|
-
|
|
11
|
+
fn nG(
|
|
12
|
+
pt: i32,
|
|
13
13
|
index: vec3i,
|
|
14
14
|
aB: vec3f,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
eY: vec3f,
|
|
16
|
+
eD: vec3f
|
|
17
17
|
) -> bool {
|
|
18
|
-
var materialIndex =
|
|
18
|
+
var materialIndex = pt;
|
|
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' : ''});
|
|
22
22
|
|
|
23
23
|
if (m0.x > 0 || m1.x > 0 || m2.x > 0) {
|
|
24
|
-
materialIndex =
|
|
24
|
+
materialIndex = fm(m0, m1, m2, aB);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
let ma = materialsBuffer[materialIndex];
|
|
@@ -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 =
|
|
40
|
+
uv = gH(uvProjection, eY, eD);
|
|
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 =
|
|
60
|
+
uv = gH(uvProjection, eY, eD);
|
|
61
61
|
}
|
|
62
62
|
uv *= ma.uvScale;
|
|
63
63
|
if (uvProjection == ${uvProjectionConstants.triplanar}) {
|
|
@@ -20,7 +20,7 @@ else if ((*ma).alphaMode == ${materialAlphaModeConstants.mask}) {
|
|
|
20
20
|
: '(*ma).alpha = 1.0;';
|
|
21
21
|
return /* wgsl */ `
|
|
22
22
|
|
|
23
|
-
${hasAnisotropyDirections ? /* wgsl */ `fn
|
|
23
|
+
${hasAnisotropyDirections ? /* wgsl */ `fn fe(roughness: f32, strength: f32) -> vec2f {
|
|
24
24
|
if (strength == 0.0) { return vec2f(roughness, 0.0); }
|
|
25
25
|
let u = roughness * roughness;
|
|
26
26
|
let a = u + (1.0 - u) * strength * strength;
|
|
@@ -29,43 +29,55 @@ 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
|
|
32
|
+
fn oK(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
|
|
38
|
+
return oK(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
|
|
45
|
+
fn kJ(idx: i32) -> vec2f {
|
|
46
46
|
return mapInfo[idx].xy;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
fn
|
|
49
|
+
fn lk(idx: i32) -> i32 {
|
|
50
50
|
return i32(floor(mapInfo[idx].z));
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
fn
|
|
53
|
+
fn jP(idx: i32) -> vec2f {
|
|
54
54
|
return vec2f(fract(mapInfo[idx].z), mapInfo[idx].w);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
fn
|
|
57
|
+
fn fS(value: f32, mode: f32) -> f32 {
|
|
58
|
+
if (mode < 0.5) { return fract(value); }
|
|
59
|
+
if (mode < 1.5) { return clamp(value, 0.0, 1.0); }
|
|
60
|
+
return 1.0 - abs(1.0 - (value - 2.0 * floor(value * 0.5)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn kv(idx: i32, uv: vec2f) -> vec2f {
|
|
58
64
|
let transform = mapInfo[idx + NUM_MAPS];
|
|
59
|
-
let
|
|
60
|
-
let
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
let iK = mapInfo[idx + NUM_MAPS * 2].zw;
|
|
66
|
+
let value = uv * transform.xy + transform.zw;
|
|
67
|
+
return vec2f(fS(value.x, iK.x), fS(value.y, iK.y));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn sq(idx: i32, uv: vec2f) -> vec2f {
|
|
71
|
+
let qH = kv(idx, uv);
|
|
72
|
+
let jk = 1.0 / vec2f(textureDimensions(map).xy);
|
|
73
|
+
let dZ = jP(idx);
|
|
74
|
+
let kn = kJ(idx) + jk;
|
|
75
|
+
let oN = dZ - 0.5 * jk;
|
|
76
|
+
let lE = dZ + kn - jk;
|
|
65
77
|
return clamp(
|
|
66
|
-
|
|
78
|
+
oN + qH * kn,
|
|
67
79
|
dZ,
|
|
68
|
-
|
|
80
|
+
lE
|
|
69
81
|
);
|
|
70
82
|
}
|
|
71
83
|
|
|
@@ -74,11 +86,32 @@ return select(uv, uv1, mapInfo[idx + NUM_MAPS * 2].x > 0.5);
|
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
fn sampleMap(idx: i32, uv: vec2f) -> vec4f {
|
|
77
|
-
|
|
89
|
+
let size = vec2i(round(kJ(idx) * vec2f(textureDimensions(map).xy) + vec2f(1.0)));
|
|
90
|
+
let eA = kv(idx, uv) * vec2f(size) - vec2f(0.5);
|
|
91
|
+
let iK = mapInfo[idx + NUM_MAPS * 2].zw;
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if ((size.x > 1 && iK.x < 0.5 && (eA.x < 0.0 || eA.x > f32(size.x - 1))) ||
|
|
95
|
+
(size.y > 1 && iK.y < 0.5 && (eA.y < 0.0 || eA.y > f32(size.y - 1)))) {
|
|
96
|
+
let base = vec2i(floor(eA));
|
|
97
|
+
let weight = fract(eA);
|
|
98
|
+
let origin = vec2i(round(jP(idx) * vec2f(textureDimensions(map).xy) - vec2f(0.5)));
|
|
99
|
+
return mix(
|
|
100
|
+
mix(eU(idx, base, size, origin, iK), eU(idx, base + vec2i(1, 0), size, origin, iK), weight.x),
|
|
101
|
+
mix(eU(idx, base + vec2i(0, 1), size, origin, iK), eU(idx, base + vec2i(1, 1), size, origin, iK), weight.x), weight.y);
|
|
102
|
+
}
|
|
103
|
+
return textureSampleLevel(map, mapSampler, sq(idx, uv), lk(idx), 0.0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn eU(idx: i32, jm: vec2i, size: vec2i, origin: vec2i, iK: vec2f) -> vec4f {
|
|
107
|
+
var p = clamp(jm, vec2i(0), size - vec2i(1));
|
|
108
|
+
if (iK.x < 0.5) { p.x = ((jm.x % size.x) + size.x) % size.x; }
|
|
109
|
+
if (iK.y < 0.5) { p.y = ((jm.y % size.y) + size.y) % size.y; }
|
|
110
|
+
return textureLoad(map, origin + p, lk(idx), 0);
|
|
78
111
|
}
|
|
79
112
|
|
|
80
113
|
|
|
81
|
-
fn
|
|
114
|
+
fn lO(materialIndex: i32, uv: vec2f, uv1: vec2f) -> vec3f {
|
|
82
115
|
let matData = materialsBuffer[materialIndex];
|
|
83
116
|
var emission = matData.emission;
|
|
84
117
|
if (USE_EMISSIVE_MAPS) {
|
|
@@ -104,24 +137,25 @@ return emission;
|
|
|
104
137
|
|
|
105
138
|
fn aK(roughness: f32, anisotropy: f32) -> vec2f {
|
|
106
139
|
let alpha = roughness * roughness;
|
|
107
|
-
let
|
|
108
|
-
if (anisotropy == 0.0) { return vec2f(max(
|
|
109
|
-
let
|
|
110
|
-
let
|
|
111
|
-
return max(vec2f(
|
|
140
|
+
let pg = ROUGHNESS_MIN * ROUGHNESS_MIN;
|
|
141
|
+
if (anisotropy == 0.0) { return vec2f(max(pg, alpha)); }
|
|
142
|
+
let di = 1.0 - anisotropy;
|
|
143
|
+
let sh = alpha * sqrt(2.0 / (1.0 + di * di));
|
|
144
|
+
return max(vec2f(pg), vec2f(sh, di * sh));
|
|
112
145
|
}
|
|
113
146
|
|
|
114
|
-
fn
|
|
147
|
+
fn kI(instanceIndex: i32) -> vec3f {
|
|
115
148
|
if (instanceIndex < 0) { return vec3f(1.0); }
|
|
116
149
|
return instancesBuffer[instanceIndex * ${INSTANCE_VEC4_STRIDE} + 9].xyz;
|
|
117
150
|
}
|
|
118
151
|
|
|
119
|
-
fn
|
|
152
|
+
fn eX(
|
|
120
153
|
ma: ptr<function, J>,
|
|
121
154
|
materialIndex: i32,
|
|
122
155
|
uv: vec2f,
|
|
123
156
|
uv1: vec2f,
|
|
124
|
-
instanceIndex: i32
|
|
157
|
+
instanceIndex: i32,
|
|
158
|
+
vertexColor: vec3f
|
|
125
159
|
) {
|
|
126
160
|
let matData = materialsBuffer[materialIndex];
|
|
127
161
|
|
|
@@ -130,7 +164,7 @@ let matData = materialsBuffer[materialIndex];
|
|
|
130
164
|
(*ma).specularRoughness = matData.roughness;
|
|
131
165
|
(*ma).baseMetalness = matData.metallic;
|
|
132
166
|
(*ma).specularAnisotropy = clamp(matData.anisotropy, 0.0, 1.0);
|
|
133
|
-
(*ma).emission =
|
|
167
|
+
(*ma).emission = lO(materialIndex, uv, uv1);
|
|
134
168
|
(*ma).inside = false;
|
|
135
169
|
(*ma).specularIor = matData.ior;
|
|
136
170
|
(*ma).transmissionWeight = matData.transmission;
|
|
@@ -169,19 +203,19 @@ if (USE_DIFFUSE_MAPS) {
|
|
|
169
203
|
let diffuseMapIndex = i32(matData.diffuseMapIndex);
|
|
170
204
|
if (diffuseMapIndex >= 0) {
|
|
171
205
|
|
|
172
|
-
let
|
|
206
|
+
let ob = sampleMap(
|
|
173
207
|
diffuseMapIndex, getMapUvSet(diffuseMapIndex, uv, uv1)
|
|
174
208
|
);
|
|
175
209
|
|
|
176
210
|
|
|
177
|
-
(*ma).baseColor *= decodeMapColor(diffuseMapIndex,
|
|
211
|
+
(*ma).baseColor *= decodeMapColor(diffuseMapIndex, ob.rgb);
|
|
178
212
|
|
|
179
213
|
|
|
180
214
|
if (matData.mapAlphaUsage == ${mapAlphaUsageConstants.transmission}) {
|
|
181
|
-
(*ma).transmissionWeight = 1.0 -
|
|
215
|
+
(*ma).transmissionWeight = 1.0 - ob.a;
|
|
182
216
|
}
|
|
183
217
|
else if (matData.mapAlphaUsage == ${mapAlphaUsageConstants.alpha}) {
|
|
184
|
-
(*ma).alpha *=
|
|
218
|
+
(*ma).alpha *= ob.a;
|
|
185
219
|
}
|
|
186
220
|
}
|
|
187
221
|
}
|
|
@@ -196,11 +230,11 @@ if (USE_METALLIC_ROUGHNESS_MAPS) {
|
|
|
196
230
|
|
|
197
231
|
let mrtMapIndex = i32(matData.mrtMapIndex);
|
|
198
232
|
if (mrtMapIndex >= 0) {
|
|
199
|
-
let
|
|
233
|
+
let fN = sampleMap(
|
|
200
234
|
mrtMapIndex, getMapUvSet(mrtMapIndex, uv, uv1)
|
|
201
235
|
);
|
|
202
|
-
(*ma).baseMetalness *=
|
|
203
|
-
(*ma).specularRoughness *=
|
|
236
|
+
(*ma).baseMetalness *= fN.b;
|
|
237
|
+
(*ma).specularRoughness *= fN.g;
|
|
204
238
|
}
|
|
205
239
|
}
|
|
206
240
|
|
|
@@ -232,16 +266,16 @@ ${hasAnisotropyDirections ? /* wgsl */ `
|
|
|
232
266
|
(*ma).coatAnisotropyRotation = matData.coatAnisotropyRotation;
|
|
233
267
|
if (USE_ANISOTROPY_MAPS && matData.anisotropyMapIndex >= 0.0) {
|
|
234
268
|
let index = i32(matData.anisotropyMapIndex);
|
|
235
|
-
let
|
|
236
|
-
(*ma).specularAnisotropy *=
|
|
237
|
-
let direction =
|
|
269
|
+
let eA = sampleMap(index, getMapUvSet(index, uv, uv1));
|
|
270
|
+
(*ma).specularAnisotropy *= eA.b;
|
|
271
|
+
let direction = eA.rg * 2.0 - 1.0;
|
|
238
272
|
if (dot(direction, direction) > 1e-4) { (*ma).anisotropyDirection = normalize(direction); }
|
|
239
273
|
(*ma).anisotropyDirectionEnabled = true;
|
|
240
274
|
}
|
|
241
275
|
if (matData.specularAnisotropyModel > 0.5 && (*ma).specularAnisotropy > 0.0) {
|
|
242
|
-
let
|
|
243
|
-
(*ma).specularRoughness =
|
|
244
|
-
(*ma).specularAnisotropy =
|
|
276
|
+
let sF = fe(clamp((*ma).specularRoughness, 0.0, 1.0), (*ma).specularAnisotropy);
|
|
277
|
+
(*ma).specularRoughness = sF.x;
|
|
278
|
+
(*ma).specularAnisotropy = sF.y;
|
|
245
279
|
}
|
|
246
280
|
if ((*ma).anisotropyDirectionEnabled) {
|
|
247
281
|
let theta = matData.specularAnisotropyRotation;
|
|
@@ -253,14 +287,14 @@ let d = (*ma).anisotropyDirection;
|
|
|
253
287
|
|
|
254
288
|
|
|
255
289
|
if ((*ma).coatWeight > 0.0 && (*ma).coatRoughness > 0.0) {
|
|
256
|
-
let
|
|
257
|
-
let
|
|
290
|
+
let hM = (*ma).specularRoughness * (*ma).specularRoughness;
|
|
291
|
+
let hN = (*ma).coatRoughness * (*ma).coatRoughness;
|
|
258
292
|
(*ma).specularRoughness = mix((*ma).specularRoughness,
|
|
259
|
-
pow(min(1.0,
|
|
293
|
+
pow(min(1.0, hM * hM + 2.0 * hN * hN), 0.25), (*ma).coatWeight);
|
|
260
294
|
}
|
|
261
|
-
let
|
|
262
|
-
(*ma).baseColor *=
|
|
263
|
-
if ((*ma).materialType == UNLIT) { (*ma).emission *=
|
|
295
|
+
let color = kI(instanceIndex) * vertexColor;
|
|
296
|
+
(*ma).baseColor *= color;
|
|
297
|
+
if ((*ma).materialType == UNLIT) { (*ma).emission *= color; }
|
|
264
298
|
|
|
265
299
|
(*ma).specularRoughness = clamp((*ma).specularRoughness, ROUGHNESS_MIN, 1.0);
|
|
266
300
|
(*ma).baseMetalness = clamp((*ma).baseMetalness, 0.0, 1.0);
|
|
@@ -269,39 +303,39 @@ let specularAlpha = aK((*ma).specularRoughness, (*ma).specularAnisotropy);
|
|
|
269
303
|
(*ma).alphaY = specularAlpha.y;
|
|
270
304
|
}
|
|
271
305
|
|
|
272
|
-
fn
|
|
306
|
+
fn om(materialIndex: i32, uv: vec2f, normal: vec3f, dp1: vec3f, dp2: vec3f, od: vec2f, oe: vec2f) -> vec3f {
|
|
273
307
|
let matData = materialsBuffer[materialIndex];
|
|
274
308
|
let normalMapIndex = i32(matData.normalMapIndex);
|
|
275
309
|
if (normalMapIndex >= 0) {
|
|
276
310
|
|
|
277
311
|
|
|
278
312
|
|
|
279
|
-
let
|
|
280
|
-
let
|
|
281
|
-
var dpdu =
|
|
282
|
-
var
|
|
283
|
-
let
|
|
284
|
-
if (
|
|
285
|
-
let
|
|
286
|
-
dpdu *=
|
|
287
|
-
|
|
313
|
+
let qW = cross(dp2, normal);
|
|
314
|
+
let qV = cross(normal, dp1);
|
|
315
|
+
var dpdu = qW * od.x + qV * oe.x;
|
|
316
|
+
var sn = qW * od.y + qV * oe.y;
|
|
317
|
+
let dT = max(dot(dpdu, dpdu), dot(sn, sn));
|
|
318
|
+
if (dT > 1e-12) {
|
|
319
|
+
let sz = inverseSqrt(dT);
|
|
320
|
+
dpdu *= sz;
|
|
321
|
+
sn *= sz;
|
|
288
322
|
}
|
|
289
323
|
else {
|
|
290
324
|
|
|
291
325
|
|
|
292
|
-
let
|
|
326
|
+
let nt = select(
|
|
293
327
|
vec3f(0.0, 1.0, 0.0),
|
|
294
328
|
vec3f(0.0, 0.0, 1.0),
|
|
295
329
|
abs(normal.z) < 0.999
|
|
296
330
|
);
|
|
297
|
-
dpdu = normalize(cross(
|
|
298
|
-
|
|
331
|
+
dpdu = normalize(cross(nt, normal));
|
|
332
|
+
sn = cross(normal, dpdu);
|
|
299
333
|
}
|
|
300
334
|
|
|
301
335
|
var n = 2.0 * sampleMap(normalMapIndex, uv).rgb - 1.0;
|
|
302
336
|
n = normalize(vec3(n.xy * matData.normalMapScale, n.z));
|
|
303
337
|
|
|
304
|
-
let tbn = mat3x3(dpdu,
|
|
338
|
+
let tbn = mat3x3(dpdu, sn, normal);
|
|
305
339
|
|
|
306
340
|
return normalize(tbn * n);
|
|
307
341
|
} else {
|