@meimu/mutracer 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/assets/bvh.worker.js +1 -1
  2. package/dist/mutracer.umd.js +3525 -3484
  3. package/docs/materials.md +27 -7
  4. package/docs/threejs.md +13 -2
  5. package/docs/viewer.md +53 -0
  6. package/lib/oidn.d.ts +1 -1
  7. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  8. package/lib/renderer/MaterialTable.d.ts +1 -0
  9. package/lib/renderer/MaterialTable.js +2 -1
  10. package/lib/renderer/MaterialTable.js.map +1 -1
  11. package/lib/renderer/WGPUBloomPass.js +18 -18
  12. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  13. package/lib/renderer/WGPUJNLMDenoiser.js +62 -62
  14. package/lib/renderer/WGPURayTraceOutput.js +14 -12
  15. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  16. package/lib/renderer/WGPURayTracePass.js +2 -2
  17. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  18. package/lib/renderer/WGPURenderingPipeline.js +20 -18
  19. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  20. package/lib/renderer/WebGLJNLMDenoiser.js +61 -61
  21. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  22. package/lib/renderer/WebGLRayTracePass.js +3 -1
  23. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  24. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
  25. package/lib/renderer/glsl/chunks/envMap.glsl.js +45 -45
  26. package/lib/renderer/glsl/chunks/intersect.glsl.js +262 -262
  27. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +111 -111
  28. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +436 -436
  29. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  30. package/lib/renderer/glsl/chunks/random.glsl.js +3 -3
  31. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +32 -32
  32. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +54 -54
  33. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +71 -71
  34. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +12 -12
  35. package/lib/renderer/glsl/chunks/sampling.glsl.js +64 -64
  36. package/lib/renderer/glsl/chunks/sobol.glsl.js +49 -49
  37. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  38. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +5 -5
  39. package/lib/renderer/glsl/depthQuery.frag.js +2 -2
  40. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +8 -8
  41. package/lib/renderer/glsl/rayTrace.frag.js +128 -128
  42. package/lib/renderer/glsl/shadowCatcher.frag.js +12 -12
  43. package/lib/renderer/glsl/webglPipeline.frag.js +49 -49
  44. package/lib/renderer/lightsUtil.js +1 -1
  45. package/lib/renderer/lightsUtil.js.map +1 -1
  46. package/lib/renderer/materialComparison.generated.js +5 -0
  47. package/lib/renderer/materialComparison.generated.js.map +1 -1
  48. package/lib/renderer/materialLayout.js +2 -1
  49. package/lib/renderer/materialLayout.js.map +1 -1
  50. package/lib/renderer/prepareMeshBVH.d.ts +4 -2
  51. package/lib/renderer/prepareMeshBVH.js +44 -20
  52. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  53. package/lib/renderer/scratchSampling.js +6 -6
  54. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  55. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +29 -29
  56. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +376 -379
  57. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  58. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +5 -5
  59. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +51 -51
  60. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +453 -453
  61. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.d.ts +1 -1
  62. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +122 -117
  63. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js.map +1 -1
  64. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  65. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +13 -13
  66. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +94 -94
  67. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +119 -119
  68. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +5 -5
  69. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +62 -62
  70. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.d.ts +3 -3
  71. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js +2 -2
  72. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js.map +1 -1
  73. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +53 -53
  74. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +5 -5
  75. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -2
  76. package/lib/renderer/wgsl/compose.frag.wgsl.js +120 -92
  77. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  78. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  79. package/lib/renderer/wgsl/depthQuery.wgsl.js +3 -3
  80. package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
  81. package/lib/renderer/wgsl/rayTrace.compute.js +220 -211
  82. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  83. package/lib/scene/OpenPBRMaterial.d.ts +4 -2
  84. package/lib/scene/OpenPBRMaterial.js +4 -2
  85. package/lib/scene/OpenPBRMaterial.js.map +1 -1
  86. package/lib/scene/openPbrSchema.d.ts +7 -2
  87. package/lib/scene/openPbrSchema.js +2 -1
  88. package/lib/scene/openPbrSchema.js.map +1 -1
  89. package/lib/util/fromTHREE.d.ts +1 -0
  90. package/lib/util/fromTHREE.js +24 -6
  91. package/lib/util/fromTHREE.js.map +1 -1
  92. package/lib/version.d.ts +1 -1
  93. package/lib/version.js +1 -1
  94. package/lib/version.js.map +1 -1
  95. package/package.json +5 -4
@@ -1 +1 @@
1
- {"version":3,"file":"rayTrace.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/rayTrace.compute.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_PATH_EVENTS } from '../pathLimits.js';\nimport shadowCatcherAccumulationWGSL from './chunks/shadowCatcherAccumulation.wgsl.js';\nimport depthQueryWGSL from './depthQuery.wgsl.js';\n// 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 commonWgsl from './chunks/common.wgsl.js';\nimport { WGPU_SCENE_FEATURE_BITS } from '../WGPUSceneFeatures.js';\nimport rayTraceCoreWgsl from './chunks/rayTraceCore.wgsl.js';\nimport { createMaterialBufferWgsl } from './chunks/materialBuffer.wgsl.js';\nimport createIntersectionWgsl from './chunks/intersect.wgsl.js';\nimport randomWgsl from './chunks/random.wgsl.js';\nimport randWgsl from './chunks/rand.wgsl.js';\nimport { createSampleMaterialWgsl } from './chunks/sampleMaterial.wgsl.js';\nimport sampleLightWgsl from './chunks/sampleLight.wgsl.js';\nimport { createOpenPbrBsdfWgsl } from './chunks/openPbrBsdf.wgsl.js';\nimport samplingWgsl from './chunks/sampling.wgsl.js';\nimport sampleShadowCatcherWgsl from './chunks/sampleShadowCatcher.wgsl.js';\nimport envMapWgsl from './chunks/envMap.wgsl.js';\nimport uvProjectionWgsl from './chunks/uvProjection.wgsl.js';\nexport const WORKGROUP_SIZE = 8;\n\nexport interface RayTraceShaderOptions {\n hasDisplacement: boolean;\n /** Include direction frames only when used, preserving the legacy shader arithmetic otherwise. */\n hasAnisotropyDirections?: boolean;\n /** Include independent coat normals only when a material uses that slot. */\n hasCoatNormals?: boolean;\n /** Split glTF BLEND primary hits deterministically instead of stochastically. */\n hasAlpha?: boolean;\n /** Any scene material uses alpha mask or blend. */\n hasSceneAlpha?: boolean;\n /** Bit mask of the BLAS layouts present in the scene (bvh2, bvh4, bvh-gpu, cwbvh). */\n blasLayoutMask?: number;\n}\n\n/** Generates the WebGPU megakernel path tracing compute shader. */\nexport default function rayTraceCompute({\n hasDisplacement,\n hasAnisotropyDirections = false,\n hasCoatNormals = false,\n hasAlpha = true,\n hasSceneAlpha = true,\n blasLayoutMask = 1\n}: RayTraceShaderOptions) {\n hasAnisotropyDirections ||= hasCoatNormals;\n const deterministicPrimaryAlpha = hasAlpha;\n const source = /* wgsl */ `\n\n${commonWgsl}\n\n${rayTraceCoreWgsl({ hasSceneAlpha, hasAnisotropyDirections, hasCoatNormals })}\n\n${uvProjectionWgsl}\n\n${createMaterialBufferWgsl({ hasSceneAlpha, hasAnisotropyDirections, hasCoatNormals })}\n\n${createIntersectionWgsl({ hasDisplacement, blasLayoutMask, hasAnisotropyDirections, hasCoatNormals })}\n${randWgsl}\n${randomWgsl}\n${envMapWgsl}\n\n${samplingWgsl}\n${createOpenPbrBsdfWgsl(hasAnisotropyDirections, hasCoatNormals)}\n${sampleLightWgsl}\n\n// Index of the path vertex currently being shaded.\nvar<private> bounceIndex: u32 = 0u;\n\n// Number of light candidates resampled for each NEE estimate. Interactive\n// preview caps the budget at two.\nfn neeCandidateCount() -> u32 {\n return u32(max(rtUniform.directLightCandidates, 1));\n}\n\n// Hash-based random number used to select among light candidates. It is\n// independent of the path sample stream so the candidate count does not\n// change the dimensions consumed by later bounces.\nfn lightCandidateRandom(salt: u32) -> f32 {\n // Reservoir replacement needs the full [0, 1) interval: sample1D's EPS\n // remapping makes small positive replacement probabilities unreachable.\n // Preserve the legacy sequential RNG draw in the random sampling mode so\n // subsequent path samples retain their stream; selection uses the hash.\n if (SAMPLING_PATTERN == 2) { _ = rand(); }\n let pixelID = samplingPixel.x + samplingPixel.y * u32(rtUniform.outputSize.x);\n let key = sampleSeed(sampleSeed(pixelID, pixelSampleIndex), rtUniform.sequenceSeed);\n let dimension = sampleSeed(samplingBounce, salt) ^ 0xa511e9b3u;\n return f32(sampleHash(key ^ dimension) >> 8u) * (1.0 / 16777216.0);\n}\n\n// MIS weight of a light-sampled contribution. With several resampled\n// candidates the NEE technique is drawn neeCandidateCount() times.\nfn neeMISWeight(lightPdf: f32, scatteringPdf: f32) -> f32 {\n let candidateCount = neeCandidateCount();\n if (candidateCount <= 1u) {\n return sampling_power_heuristic(lightPdf, scatteringPdf);\n }\n let weightedLightPdf = f32(candidateCount) * lightPdf;\n return weightedLightPdf /\n max(weightedLightPdf + scatteringPdf, 1e-20);\n}\n\n// MIS weight of a BSDF-sampled ray that hits a light.\nfn bsdfMISWeight(scatteringPdf: f32, lightPdf: f32) -> f32 {\n let candidateCount = neeCandidateCount();\n if (candidateCount <= 1u) {\n return sampling_power_heuristic(scatteringPdf, lightPdf);\n }\n let weightedLightPdf = f32(candidateCount) * lightPdf;\n return scatteringPdf /\n max(scatteringPdf + weightedLightPdf, 1e-20);\n}\n\n${createSampleMaterialWgsl(hasAnisotropyDirections, hasCoatNormals)}\n${sampleShadowCatcherWgsl}\n\n// Output\n@group(3) @binding(0) var<storage, read_write> outColor: array<vec4f>;\n@group(3) @binding(1) var<storage, read_write> outNormal: array<vec4f>;\n@group(3) @binding(2) var<storage, read_write> outAlbedo: array<vec4f>;\n\n${shadowCatcherAccumulationWGSL}\n@group(3) @binding(3) var<storage, read_write> outShadowCatcher: array<ShadowCatcherAccumulation>;\n\n\nfn accumulateNormalOrVariance(\n pixelIndex: u32,\n radiance: vec3f,\n normal: vec3f\n) {\n if (rtUniform.varianceMoments != 0) {\n let value = dot(radiance, luminance);\n outNormal[pixelIndex] += vec4f(value * value, 0.0, 0.0, 1.0);\n }\n else {\n outNormal[pixelIndex] += vec4f(normal, 1.0);\n }\n}\n\n// Moments of the per-sample value the built-in denoiser compares, used to\n// estimate the variance of each pixel's mean.\nfn accumulateDenoiseMoments(pixelIndex: u32, value: f32) {\n outShadowCatcher[pixelIndex].moments += vec4f(value, value * value, 0.0, 0.0);\n}\n\n\nstruct RayTraceAuxiliary {\n // Denoising guides and auxiliary pixel classification.\n albedo: vec3f,\n isShadowCatcher: bool,\n normal: vec3f,\n isBlank: bool,\n}\n\nfn groundedSurfaceDistance(point: vec3f, height: f32, radius: f32) -> f32 {\n let pointLength = length(point);\n if (pointLength < 0.00001) {\n return -radius;\n }\n\n let originalY = radius * point.y / pointLength;\n let transitionY = -1.5 * height;\n var surfaceRadius: f32;\n if (originalY >= 0.0) {\n surfaceRadius = radius;\n }\n else if (originalY < transitionY) {\n surfaceRadius = -height * pointLength / point.y;\n }\n else {\n let factor = 1.0\n - originalY * originalY / (3.0 * transitionY * transitionY);\n surfaceRadius = radius * factor;\n }\n return pointLength - surfaceRadius;\n}\n\nfn groundedBackgroundDirection(origin: vec3f, direction: vec3f) -> vec3f {\n if (!USE_GROUNDED_BACKGROUND) {\n return direction;\n }\n let params = rtUniform.groundedBackgroundParams;\n let rayDirection = normalize(direction);\n let center = vec3f(\n rtUniform.groundedBackgroundCenter.x,\n params.z + params.x,\n rtUniform.groundedBackgroundCenter.y\n );\n let localOrigin = origin - center;\n let projectedOrigin = dot(localOrigin, rayDirection);\n let discriminant = projectedOrigin * projectedOrigin\n - dot(localOrigin, localOrigin) + params.y * params.y;\n if (\n discriminant <= 0.0\n || groundedSurfaceDistance(localOrigin, params.x, params.y) > 0.0\n ) {\n return direction;\n }\n\n var farDistance = -projectedOrigin + sqrt(discriminant);\n if (farDistance <= 0.0) {\n return direction;\n }\n\n let sphereHit = localOrigin + rayDirection * farDistance;\n if (sphereHit.y >= 0.0) {\n return normalize(sphereHit);\n }\n\n if (rayDirection.y < -0.00001 && params.y > 1.5 * params.x) {\n let groundDistance = (-params.x - localOrigin.y) / rayDirection.y;\n let groundHit = localOrigin + rayDirection * groundDistance;\n let groundRadius = (2.0 / 3.0)\n * sqrt(params.y * params.y - 2.25 * params.x * params.x);\n if (groundDistance > 0.0 && length(groundHit.xz) <= groundRadius) {\n return normalize(groundHit);\n }\n }\n\n var nearDistance = 0.0;\n for (var i = 0; i < 14; i++) {\n let middleDistance = (nearDistance + farDistance) * 0.5;\n let middlePoint = localOrigin + rayDirection * middleDistance;\n if (groundedSurfaceDistance(middlePoint, params.x, params.y) > 0.0) {\n farDistance = middleDistance;\n }\n else {\n nearDistance = middleDistance;\n }\n }\n let hit = localOrigin + rayDirection * ((nearDistance + farDistance) * 0.5);\n return normalize(hit);\n}\n\nfn sampleSceneBackground(ray: Ray) -> vec4f {\n var bg = vec4f(0.0);\n if (USE_BACKGROUND_MAP) {\n let backgroundDirection = groundedBackgroundDirection(ray.o, ray.d);\n bg = textureLinear(backgroundMap, cartesianToEquirect(backgroundDirection));\n }\n else if (USE_BACKGROUND) {\n bg = rtUniform.backgroundColor;\n }\n else if (USE_ENVMAP) {\n bg = textureLinear(envMap, cartesianToEquirect(ray.d));\n }\n return bg;\n}\n\nfn sampleSpectralSceneBackground(ray: Ray, wavelength: f32) -> vec4f {\n let bg = sampleSceneBackground(ray);\n if (SPECTRAL) {\n return vec4f(sampling_spectral_rgb_filter(bg.rgb, wavelength), bg.a);\n }\n return bg;\n}\n\nfn bounce(uv: vec2f, path: ptr<function, Path>, depth: ptr<function, i32>, si: ptr<function, SurfaceInteraction>, ma: ptr<function, OpenPbrResolvedMaterial>, outAux: ptr<function, RayTraceAuxiliary>) {\n samplingBounce = u32(max(*depth, 0));\n let matType = (*ma).materialType;\n let isShadowCatcher = matType == SHADOW_CATCHER;\n if (isShadowCatcher) {\n if ((*path).fromEye) {\n (*si).hit = false;\n }\n else {\n // The catcher only contributes to the separate shadow mask. Secondary\n // rays must see the environment behind it instead of its default white\n // material, otherwise it appears in reflections.\n if (USE_ENVMAP) {\n if ((*path).isNextRefraction) {\n // A camera-visible transmitted ray can hit the invisible catcher\n // before the grounded skybox. Preserve the displayed projection.\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n let backgroundMisWeight = select(bsdfMISWeight((*path).scatteringPdf,\n envMapPdf(cartesianToEquirect((*path).ray.d)) * rtUniform.envLightPmf),\n 1.0, (*path).scatteringIsDelta);\n (*path).li += backgroundMisWeight * (*path).beta * bg.rgb * bg.a;\n (*path).alpha = max((*path).alpha, bg.a);\n }\n else {\n let envUv = cartesianToEquirect((*path).ray.d);\n var emission = textureLinear(envMap, envUv).rgb;\n if (SPECTRAL) {\n emission = sampling_spectral_rgb_filter(emission, (*si).wavelength);\n }\n (*path).li += (*path).beta * emission *\n bsdfMISWeight((*path).scatteringPdf, envMapPdf(envUv));\n }\n }\n else {\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n (*path).li += (*path).beta * bg.rgb * bg.a;\n }\n (*path).abort = true;\n return;\n }\n }\n if (!(*si).hit) {\n // The spectral integrator traces red, green, and blue paths separately.\n // Split background radiance by wavelength as well so the three paths\n // reconstruct the skybox instead of adding three full RGB copies.\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n if ((*path).fromEye) {\n // Sample from background if first ray not hit anything.\n (*path).li = (*path).beta * bg.rgb * bg.a;\n (*path).alpha = bg.a;\n // Only treat as blank pixels when the background is transparent\n (*outAux).isBlank = bg.a < 0.001 ||\n (USE_BACKGROUND && !USE_BACKGROUND_MAP);\n }\n else {\n let cam = initCameraRay(uv);\n let towardsCam = dot(cam.d, (*path).ray.d) > 0.0;\n if (rtUniform.transmissiveOutputAlphaMin < 1.0) {\n if (towardsCam) {\n (*path).alpha = max((*path).alpha, bg.a);\n } else {\n (*path).alpha = 1.0;\n }\n }\n if (!USE_ENVMAP) {\n // A display background is not an illumination environment, but a\n // visible secondary ray (e.g. through glass) still resolves it.\n if (towardsCam) {\n // This is a terminal BSDF-hit contribution, not a replacement for\n // NEE accumulated at earlier vertices on the same path.\n (*path).li += (*path).beta *\n bsdfMISWeight((*path).scatteringPdf, 1.0) * bg.rgb;\n }\n }\n }\n (*path).abort = true;\n return;\n }\n\n var sampleIgnored = false;\n if ((matType == STANDARD || matType == UNLIT)) {\n let alpha = clamp((*ma).alpha, 0.0, 1.0);\n \n if (alpha < 1.0) {\n // Alpha is stochastic coverage. Keep accepted surface radiance at full\n // weight instead of multiplying it by alpha a second time.\n if (alpha <= 0.0 || hashedSample(3u) > alpha) {\n sampleIgnored = true;\n }\n }\n if (!sampleIgnored && rtUniform.transmissiveOutputAlphaMin < 1.0) {\n // A selected stochastic coverage sample is fully covered. Ignored\n // samples leave output alpha untouched and continue to the back layer.\n let outputAlpha = max(1.0 - (*ma).transmissionWeight, 1.0 - pow(1.0 - rtUniform.transmissiveOutputAlphaMin, f32(*depth)));\n (*path).alpha = max(outputAlpha, (*path).alpha);\n }\n if (!sampleIgnored) {\n bounceIndex = u32(max(*depth, 0));\n SampleMaterial(si, ma, path);\n }\n else {\n // Alpha-null traversal does not consume the receiver event. The first\n // accepted surface behind it is still the receiver for NEE/AOVs.\n (*path).ray.o = (*si).position + (*path).ray.d * EPS;\n }\n }\n\n if (!(*si).albedoSampled && !sampleIgnored) {\n // Select denoising guides.\n if ((matType == STANDARD || matType == UNLIT)) {\n if (!((*ma).transmissionWeight > 0.5 ||\n ((*ma).specularRoughness <= 0.02 && (*ma).baseMetalness > 0.98))) {\n (*outAux).albedo = (*ma).baseColor;\n (*outAux).normal = select(\n (*si).normal,\n -(*si).normal,\n dot(\n (*si).normal,\n (*si).position - cameraUniform.viewInv[3].xyz\n ) > 0.0\n );\n (*si).albedoSampled = true;\n }\n // TODO pick albedo and normal on the surface if ray is too deep and can't hit any rough or opaque surfaces.\n if (*depth > 4) {\n (*si).albedoSampled = true;\n }\n }\n }\n\n // Russian roulette with throughput compensation.\n if (USE_RR) {\n if (*depth >= RR_MAX && !sampleIgnored) {\n let rouletteBeta = (*path).beta;\n // Max-component survival (pbrt-v4) avoids over-amplifying saturated paths.\n let maxComponent = max(max(rouletteBeta.x, rouletteBeta.y), rouletteBeta.z);\n let q: f32 = max(0.0, 1.0 - maxComponent);\n if (sample1D(bounceDim(RUSSIAN_ROULETTE)) < q) {\n // TODO we force use alpha 1.0 here.\n // To avoid the glass pixels is transparent even if it's on an opaque object.\n (*path).alpha = 1.0;\n (*path).abort = true;\n }\n else {\n (*path).beta /= 1.0 - q;\n }\n }\n }\n if ((*path).abort) {\n return;\n }\n\n intersectScene((*path).ray, si, *depth, true, (*path).isNextRefraction);\n getMaterial(ma, (*si).materialIndex, (*si).uv, (*si).uv1, (*si).instanceIndex);\n\n if (SPECTRAL) {\n // Is glass\n if ((*ma).transmissionWeight > 0.001) {\n (*ma).specularIor = sampling_cauchy_ior(\n (*si).wavelength,\n (*ma).specularIor,\n (*ma).transmissionDispersionScale,\n (*ma).dispersionAbbeNumber\n );\n }\n }\n\n if (sampleIgnored) {\n return;\n }\n\n // Use light if hit the light\n var lightSample: LightSampleRec;\n let isAlphaOccluded = hashedSample(3u) < (*ma).alpha;\n for (var i = 0; i < rtUniform.lightsCount; i++) {\n // Fetch light Data\n let light: Light = analyticLights[i];\n let d = intersectLight((*path).ray, light, &lightSample);\n lightSample.pdf *= light.selectionPmf;\n let occluded = (*si).hit && d > distance((*si).position, (*path).ray.o);\n if (d != INF && !(occluded && isAlphaOccluded)) {\n let misWeight = bsdfMISWeight((*path).scatteringPdf, lightSample.pdf);\n if (SPECTRAL) {\n lightSample.emission = sampling_spectral_rgb_filter(lightSample.emission, (*si).wavelength);\n }\n (*path).li += misWeight * (*path).beta * lightSample.emission;\n (*path).alpha = 1.0;\n (*path).abort = true;\n }\n }\n\n if (USE_ENVMAP) {\n if (!((*si).hit && isAlphaOccluded)) {\n if ((*path).isNextRefraction) {\n // Camera-visible transmission follows the displayed background,\n // including grounded projection. Other rays use the raw environment\n // map as the physically sampled lighting source.\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n let backgroundMisWeight = select(bsdfMISWeight((*path).scatteringPdf,\n envMapPdf(cartesianToEquirect((*path).ray.d)) * rtUniform.envLightPmf),\n 1.0, (*path).scatteringIsDelta);\n (*path).li += backgroundMisWeight * (*path).beta * bg.rgb * bg.a;\n (*path).alpha = max((*path).alpha, bg.a);\n (*path).abort = true;\n return;\n }\n let _uv = cartesianToEquirect((*path).ray.d);\n var emission = textureLinear(envMap, _uv).rgb;\n let misWeight = bsdfMISWeight(\n (*path).scatteringPdf,\n envMapPdf(_uv) * rtUniform.envLightPmf\n );\n if (SPECTRAL) {\n emission = sampling_spectral_rgb_filter(emission, (*si).wavelength);\n }\n // TODO force the min alpha to be 1 when showing envmap as background\n if (!USE_BACKGROUND) {\n (*path).alpha = 1.0;\n }\n (*path).li += (*path).beta * emission * misWeight;\n (*path).abort = true;\n }\n }\n\n *depth = *depth + 1;\n}\n\nfn initCameraRay(coord: vec2f) -> Ray {\n let origin = cameraUniform.viewProjectionInv * vec4f(coord * 2.0 - 1.0, -1.0, 1.0);\n let center = cameraUniform.viewProjectionInv * vec4f(coord * 2.0 - 1.0, 1.0, 1.0);\n var cam: Ray;\n initRay(&cam, origin.xyz / origin.w, normalize(center.xyz * origin.w - origin.xyz * center.w));\n\n return cam;\n}\n\n\nfn bevelCubicSample(radius: f32, xi: f32) -> vec2f {\n var x = 0.25;\n for (var iteration = 0; iteration < 10; iteration++) {\n let x2 = x * x;\n let x3 = x2 * x;\n let oneMinusX = 1.0 - x;\n let value = 10.0 * x2 - 20.0 * x3 + 15.0 * x2 * x2 -\n 4.0 * x2 * x3 - xi;\n let derivative = 20.0 * x * oneMinusX * oneMinusX * oneMinusX;\n if (abs(value) < 1e-6 || derivative == 0.0) { break; }\n x = clamp(x - value / derivative, 0.0, 1.0);\n }\n let diskRadius = x * radius;\n return vec2f(diskRadius, sqrt(max(radius * radius - diskRadius * diskRadius, 0.0)));\n}\n\nfn bevelCubicPdf(radius: f32, distanceToSource: f32) -> f32 {\n if (distanceToSource >= radius) { return 0.0; }\n let falloff = radius - distanceToSource;\n return falloff * falloff * falloff;\n}\n\n// Shader bevel. Usage: docs/materials.md (Renderer extensions)\nfn getBevelNormal(source: SurfaceInteraction, radius: f32) -> vec3f {\n var geometricNormal = source.faceNormal;\n if (dot(geometricNormal, source.vertexNormal) < 0.0) {\n geometricNormal = -geometricNormal;\n }\n var tangent: vec3f;\n var bitangent: vec3f;\n sampling_orthonormal_basis(geometricNormal, &tangent, &bitangent);\n var normalSum = vec3f(0.0);\n\n for (var sampleIndex = 0; sampleIndex < BEVEL_SAMPLES; sampleIndex++) {\n let bevelRnd = sample2D(CAMERA_BEVEL_BASE + u32(sampleIndex));\n var axisSample = bevelRnd.x;\n let radialSample = bevelRnd.y;\n var diskNormal = geometricNormal;\n var diskTangent = tangent;\n var diskBitangent = bitangent;\n var pickPdfNormal = 0.5;\n var pickPdfTangent = 0.25;\n var pickPdfBitangent = 0.25;\n if (axisSample < 0.5) {\n axisSample *= 2.0;\n }\n else if (axisSample < 0.75) {\n let swapAxis = diskNormal;\n diskNormal = diskTangent;\n diskTangent = swapAxis;\n pickPdfNormal = 0.25;\n pickPdfTangent = 0.5;\n axisSample = (axisSample - 0.5) * 4.0;\n }\n else {\n let swapAxis = diskNormal;\n diskNormal = diskBitangent;\n diskBitangent = swapAxis;\n pickPdfNormal = 0.25;\n pickPdfBitangent = 0.5;\n axisSample = (axisSample - 0.75) * 4.0;\n }\n\n let diskSample = bevelCubicSample(radius, radialSample);\n let phi = TWOPI * axisSample;\n let diskOffset = diskSample.x *\n (cos(phi) * diskTangent + sin(phi) * diskBitangent);\n var ray: Ray;\n initRay(\n &ray,\n source.position + diskNormal * diskSample.y + diskOffset,\n -diskNormal\n );\n var hit: SurfaceInteraction;\n intersectSceneBevel(\n ray,\n &hit,\n source.instanceIndex,\n max(2.0 * diskSample.y, EPS)\n );\n if (!hit.hit) { continue; }\n\n let pdfNormal = pickPdfNormal * abs(dot(diskNormal, hit.faceNormal));\n let pdfTangent = pickPdfTangent * abs(dot(diskTangent, hit.faceNormal));\n let pdfBitangent = pickPdfBitangent * abs(dot(diskBitangent, hit.faceNormal));\n var weight = pdfNormal / max(\n pdfNormal * pdfNormal + pdfTangent * pdfTangent +\n pdfBitangent * pdfBitangent,\n 1e-8\n );\n weight *= bevelCubicPdf(radius, distance(hit.position, source.position)) /\n max(bevelCubicPdf(radius, diskSample.x), 1e-8);\n normalSum += weight * hit.vertexNormal;\n }\n\n if (dot(normalSum, normalSum) <= 1e-12) { return source.normal; }\n let bevelVertexNormal = normalize(normalSum);\n let result = source.normal + bevelVertexNormal - source.vertexNormal;\n if (dot(result, result) > 1e-12) { return normalize(result); }\n return source.normal;\n}\n\nfn integrateOnePathSample(uv: vec2f, si: ptr<function, SurfaceInteraction>, ray: Ray, ma: ptr<function, OpenPbrResolvedMaterial>, outAux: ptr<function, RayTraceAuxiliary>) -> vec4f {\n var path: Path;\n path.ray = ray;\n path.li = vec3f(0.0);\n path.alpha = select(1.0, 0.0, rtUniform.transmissiveOutputAlphaMin < 1.0);\n path.beta = vec3f(1.0);\n path.fromEye = true;\n path.scatteringPdf = 0.0;\n path.scatteringIsDelta = false;\n path.isNextRefraction = false;\n path.abort = false;\n\n let isNotTransparent = (*ma).transmissionWeight < 0.001 && (*ma).subsurfaceWeight < 0.001 && (*ma).alpha > 0.99;\n let bounceLimit = max(rtUniform.bounces, 0);\n var depth = 0;\n // Transparent paths may take up to three times the configured bounces.\n for (var idx: i32 = 0; idx < min(bounceLimit * 3, ${MAX_PATH_EVENTS}); idx = idx + 1) {\n if (isNotTransparent && idx > bounceLimit) {\n path.abort = true;\n }\n if (path.abort) {\n break;\n }\n bounce(uv, &path, &depth, si, ma, outAux);\n }\n\n return vec4f(path.li, path.alpha);\n}\n\nfn integrateOnePath(uv: vec2f, si: ptr<function, SurfaceInteraction>, ray: Ray, ma: ptr<function, OpenPbrResolvedMaterial>, outAux: ptr<function, RayTraceAuxiliary>) -> vec4f {\n ${deterministicPrimaryAlpha ? /* wgsl */ `let coverage = clamp((*ma).alpha, 0.0, 1.0);\n if (!(*si).hit || ((*ma).materialType != STANDARD && (*ma).materialType != UNLIT) || coverage >= 1.0) {\n return integrateOnePathSample(uv, si, ray, ma, outAux);\n }\n\n // glTF BLEND is source-over opacity, not a physical glass boundary. Split\n // the primary ray so reflected radiance is attenuated by alpha every sample\n // while the complementary branch continues straight to the scene behind it.\n // Secondary alpha hits remain stochastic to avoid exponential path growth.\n var backRay = ray;\n backRay.o = (*si).position + ray.d * (EPS * 0.02);\n var backSi = *si;\n backSi.albedoSampled = false;\n intersectScene(backRay, &backSi, 0, false, false);\n var backMa: OpenPbrResolvedMaterial;\n getMaterial(&backMa, backSi.materialIndex, backSi.uv, backSi.uv1, backSi.instanceIndex);\n var backAux = *outAux;\n if (backSi.hit && backMa.materialType == STANDARD) {\n backAux.albedo = select(\n backMa.baseColor,\n vec3f(1.0),\n backMa.transmissionWeight > 0.5\n );\n backAux.normal = select(\n backSi.normal,\n -backSi.normal,\n dot(\n backSi.normal,\n backSi.position - cameraUniform.viewInv[3].xyz\n ) > 0.0\n );\n backSi.albedoSampled = backMa.transmissionWeight > 0.5;\n }\n let back = integrateOnePathSample(\n uv, &backSi, backRay, &backMa, &backAux\n );\n if (coverage <= 0.0) {\n *outAux = backAux;\n return back;\n }\n\n var frontSi = *si;\n var frontMa = *ma;\n frontMa.alpha = 1.0;\n var frontAux = *outAux;\n let front = integrateOnePathSample(\n uv, &frontSi, ray, &frontMa, &frontAux\n );\n (*outAux).albedo = mix(backAux.albedo, frontAux.albedo, coverage);\n let blendedNormal = mix(backAux.normal, frontAux.normal, coverage);\n (*outAux).normal = select(\n vec3f(0.0),\n normalize(blendedNormal),\n dot(blendedNormal, blendedNormal) > 1e-12\n );\n (*outAux).isBlank = backAux.isBlank && frontAux.isBlank;\n (*outAux).isShadowCatcher = backAux.isShadowCatcher && frontAux.isShadowCatcher;\n return mix(back, front, coverage);` : /* wgsl */ `return integrateOnePathSample(uv, si, ray, ma, outAux);`}\n}\n\n// Path tracing integrator as described in\n// http://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Path_Tracing.html#\nfn integrator(uv: vec2f, ray: Ray, outAux: ptr<function, RayTraceAuxiliary>) -> vec4f {\n var si: SurfaceInteraction;\n var ma: OpenPbrResolvedMaterial;\n si.albedoSampled = false;\n si.mediumType = 0;\n si.mediumExtinction = vec3f(0.0);\n si.mediumScattering = vec3f(0.0);\n si.mediumAnisotropy = 0.0;\n si.barycentrics = vec2f(0.0);\n\n // TODO not put tMax in ray. So we don't need to use pointer of ray here.\n intersectScene(ray, &si, 0, false, false);\n getMaterial(&ma, si.materialIndex, si.uv, si.uv1, si.instanceIndex);\n\n if (ma.materialType == SHADOW_CATCHER) {\n (*outAux).isShadowCatcher = true;\n // The composed denoising input includes this receiver's shadow. Supply\n // neutral reflectance and its shading normal, independent of visibility.\n (*outAux).albedo = vec3f(1.0);\n (*outAux).normal = select(si.normal, -si.normal, dot(si.normal, ray.d) > 0.0);\n var path: Path;\n path.ray = ray;\n path.fromEye = true;\n sampleShadowCatcher(si, 0, &path);\n // RGB stores primary catcher coverage independently from the stochastic\n // visibility in alpha. Composition uses this raw coverage to clip any\n // shadow-mask denoising bleed at model silhouettes.\n return vec4f(vec3f(1.0), path.alpha);\n }\n else {\n if (HAS_BEVEL && rtUniform.bevel != 0) {\n if (ma.bevel > 0.0) {\n si.normal = getBevelNormal(si, ma.bevel);\n }\n }\n\n // Store a clean first-hit fallback. Glass retains this stable guide;\n // other delta paths may replace it at their first non-delta hit.\n if (si.hit && (ma.materialType == STANDARD || ma.materialType == UNLIT)) {\n (*outAux).albedo = select(ma.baseColor, vec3f(1.0), ma.transmissionWeight > 0.5);\n (*outAux).normal = select(\n si.normal,\n -si.normal,\n dot(si.normal, si.position - cameraUniform.viewInv[3].xyz) > 0.0\n );\n\n // Do not replace a glass guide with a randomly selected downstream\n // refraction/reflection hit; OIDN auxiliary features must stay clean.\n si.albedoSampled = ma.transmissionWeight > 0.5;\n }\n\n (*outAux).isShadowCatcher = false;\n\n if (SPECTRAL) {\n // Keep one source-level integrateOnePath call for both interactive\n // preview and offline RGB splitting. A runtime loop bound prevents the\n // WGSL compiler from cloning the full path graph three times while the\n // uniform still selects the exact existing preview/offline behavior.\n let rgbWaveLengths = array<f32, 3>(650.0, 510.0, 475.0);\n var colAccum = vec3f(0.0);\n var alpha = 0.0;\n var accumulatedGuideAlbedo = vec3f(0.0);\n var accumulatedGuideNormal = vec3f(0.0);\n var allBlank = true;\n var si2 = si;\n var ma2 = ma;\n let wavelengthRngState = s0;\n let wavelengthHashedCounter = hashedCounter;\n let singleWavelength = rtUniform.dispersionWavelengths == 1;\n let dispersivePrimary = si.hit &&\n ma.transmissionWeight > 0.001 &&\n ma.transmissionDispersionScale > 0.001;\n let wavelengthCount = select(3, 1, singleWavelength);\n var heroWavelengthIndex = 0;\n if (singleWavelength && dispersivePrimary) {\n // Stochastic hero wavelength: one RGB channel per pixel keeps the\n // preview spectral at one-third of the full three-path cost.\n heroWavelengthIndex = min(i32(floor(sample1D(CAMERA_WAVELENGTH) * 3.0)), 2);\n }\n for (var i = 0; i < wavelengthCount; i++) {\n // Common random numbers keep Monte Carlo error correlated between\n // RGB wavelengths. This removes much of the colored variance that\n // otherwise turns into dirty chromatic patches after OIDN.\n if (!singleWavelength) {\n s0 = wavelengthRngState;\n hashedCounter = wavelengthHashedCounter;\n samplingBounce = 0u;\n }\n si.wavelength = 0.0;\n if (!singleWavelength || dispersivePrimary) {\n si.wavelength = rgbWaveLengths[select(i, heroWavelengthIndex, singleWavelength)];\n ma.specularIor = sampling_cauchy_ior(\n si.wavelength,\n ma.specularIor,\n ma.transmissionDispersionScale,\n ma.dispersionAbbeNumber\n );\n }\n var wavelengthAux: RayTraceAuxiliary;\n if (singleWavelength) {\n wavelengthAux = *outAux;\n } else {\n wavelengthAux.albedo = (*outAux).albedo;\n wavelengthAux.normal = (*outAux).normal;\n wavelengthAux.isBlank = false;\n wavelengthAux.isShadowCatcher = false;\n }\n let res = integrateOnePath(uv, &si, ray, &ma, &wavelengthAux);\n if (singleWavelength) {\n *outAux = wavelengthAux;\n let scale = select(1.0, 3.0, dispersivePrimary);\n return vec4f(res.rgb * scale, res.a);\n }\n colAccum += res.rgb;\n alpha = max(alpha, res.a);\n accumulatedGuideAlbedo += wavelengthAux.albedo;\n accumulatedGuideNormal += wavelengthAux.normal;\n allBlank = allBlank && wavelengthAux.isBlank;\n si = si2;\n ma = ma2;\n }\n (*outAux).albedo = accumulatedGuideAlbedo / 3.0;\n (*outAux).normal = accumulatedGuideNormal / 3.0;\n (*outAux).isBlank = allBlank;\n return vec4f(colAccum, alpha);\n }\n else {\n return integrateOnePath(uv, &si, ray, &ma, outAux);\n }\n }\n}\n\nfn sampleCircle(ip: vec2f) -> vec2f {\n let p = 2.0 * ip - 1.0;\n let greater = abs(p.x) > abs(p.y);\n let r = select(p.y, p.x, greater);\n let theta = select(PI * (0.5 - 0.25 * p.x / p.y), 0.25 * PI * p.y / p.x, greater);\n return r * vec2f(cos(theta), sin(theta));\n}\n\n\n@compute @workgroup_size(${WORKGROUP_SIZE}, ${WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) globalId: vec3u) {\n let outputSize = rtUniform.outputSize;\n let scaleOffset = rtUniform.scaleOffset;\n var jitter = rtUniform.jitter;\n // TODO if outputSize is not multiple of 8?\n let coord = globalId.xy * vec2u(scaleOffset.xy) + vec2u(scaleOffset.zw);\n if (coord.x >= u32(outputSize.x) || coord.y >= u32(outputSize.y)) {\n return;\n }\n let idx = coord.x + coord.y * u32(outputSize.x);\n\n if (\n rtUniform.adaptiveSampling != 0 &&\n outNormal[idx].a >= textureLoad(\n adaptiveSampleTargets,\n vec2i(coord),\n 0\n ).r\n ) {\n return;\n }\n\n // Match WebGL's rasterized v_Texcoord, whose sample is at the center of\n // each pixel. Keeping the same origin is material relevant: a half-pixel\n // shift moves a narrow metal highlight across several pixels.\n let uv = (vec2<f32>(f32(coord.x), f32(coord.y)) + vec2f(0.5)) / outputSize;\n\n samplingPixel = coord;\n pixelSampleIndex = u32(outNormal[idx].a) + rtUniform.sampleSequenceOffset;\n samplingBounce = 0u; hashedCounter = 0u;\n if (SAMPLING_PATTERN == 0 || SAMPLING_PATTERN == 1) {\n jitter = (sample2D(CAMERA_PIXEL) - 0.5) / outputSize;\n }\n rng_initialize(coord, i32(pixelSampleIndex ^ sampleHash(rtUniform.sequenceSeed)));\n var cam = initCameraRay(uv + jitter);\n\n if (cameraUniform.apertureRadius > 0.0) {\n // Thin lens: the focus plane is perpendicular to the view direction.\n let eye = cameraUniform.viewInv[3].xyz;\n let forward = -normalize(cameraUniform.viewInv[2].xyz);\n let focusPoint = eye + cam.d * (cameraUniform.focusDistance / dot(cam.d, forward));\n let lensPoint = cameraUniform.apertureRadius * sampleCircle(sample2D(CAMERA_LENS));\n let origin = (cameraUniform.viewInv * vec4f(lensPoint, 0.0, 1.0)).xyz;\n let dir = normalize(focusPoint - origin);\n initRay(&cam, origin, dir);\n }\n\n var outAux: RayTraceAuxiliary;\n outAux.albedo = vec3f(0.0);\n outAux.normal = vec3f(0.0);\n outAux.isBlank = false;\n outAux.isShadowCatcher = false;\n var liAndAlpha = integrator(uv + jitter, cam, &outAux);\n\n // Clamp at Integration Level to reduce fireflies.\n // TODO Path level configuration?\n if (rtUniform.clamping > 0.0) {\n let clamping = rtUniform.clamping;\n liAndAlpha = vec4(clamp(liAndAlpha.rgb, -vec3f(clamping), vec3f(clamping)), liAndAlpha.a);\n }\n if (!(liAndAlpha.x < INF && liAndAlpha.x > -EPS)) {\n liAndAlpha = vec4f(0.0, 0.0, 0.0, 1.0);\n }\n\n if (outAux.isShadowCatcher) {\n // The catcher is invisible, so the main color at this pixel must still\n // contain whatever the camera ray would have seen behind it.\n let backgroundSample = sampleSceneBackground(cam);\n let backgroundHit = select(0.0, 1.0, backgroundSample.a >= 0.01);\n let backgroundPremultiplied = vec4f(backgroundSample.rgb * backgroundSample.a, backgroundSample.a);\n // Keep the background contribution separate from geometry at silhouettes.\n // mask: catcher coverage, background alpha, background hits, shadow opacity.\n outShadowCatcher[idx].mask += vec4f(1.0, backgroundSample.a, backgroundHit, liAndAlpha.a);\n outShadowCatcher[idx].background += backgroundPremultiplied;\n outColor[idx] += backgroundPremultiplied;\n outAlbedo[idx] += vec4f(outAux.albedo, backgroundHit);\n accumulateNormalOrVariance(idx, backgroundSample.rgb, outAux.normal);\n // The composed catcher is the background darkened by the shadow. Over a\n // transparent background the shadow is only its opacity.\n let visibility = 1.0 - liAndAlpha.a;\n accumulateDenoiseMoments(idx, visibility * select(\n 1.0, dot(backgroundSample.rgb, luminance), backgroundSample.a >= 0.01\n ));\n }\n else {\n outColor[idx] += liAndAlpha;\n outAlbedo[idx] += vec4f(outAux.albedo, select(1.0, 0.0, outAux.isBlank));\n accumulateNormalOrVariance(idx, liAndAlpha.rgb, outAux.normal);\n accumulateDenoiseMoments(idx, dot(liAndAlpha.rgb, luminance));\n }\n}\n`;\n // The query entry point shares the exact BVH and material implementation.\n // It has no effect on the beauty entry point or its bindings.\n // Gate optional material layers on scene-level feature flags so drivers can\n // skip the unused BSDF lobes.\n let specializedSource = source + depthQueryWGSL;\n const backgroundBits: Record<string, number> = {\n USE_BACKGROUND: 1,\n USE_BACKGROUND_MAP: 2,\n USE_GROUNDED_BACKGROUND: 4\n };\n specializedSource = specializedSource.replace(\n /\\b(?:USE_BACKGROUND|USE_BACKGROUND_MAP|USE_GROUNDED_BACKGROUND)\\b/g,\n (name) => `((rtUniform.backgroundFlags & ${backgroundBits[name]}) != 0)`\n );\n specializedSource = specializedSource.replace(\n new RegExp(`\\\\b(?:${Object.keys(WGPU_SCENE_FEATURE_BITS).join('|')})\\\\b`, 'g'),\n name => `((rtUniform.sceneFeatureFlags & ${WGPU_SCENE_FEATURE_BITS[name]}u) != 0u)`\n );\n return specializedSource;\n}\n"]}
1
+ {"version":3,"file":"rayTrace.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/rayTrace.compute.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_PATH_EVENTS } from '../pathLimits.js';\nimport shadowCatcherAccumulationWGSL from './chunks/shadowCatcherAccumulation.wgsl.js';\nimport depthQueryWGSL from './depthQuery.wgsl.js';\n// 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 commonWgsl from './chunks/common.wgsl.js';\nimport { WGPU_SCENE_FEATURE_BITS } from '../WGPUSceneFeatures.js';\nimport rayTraceCoreWgsl from './chunks/rayTraceCore.wgsl.js';\nimport { createMaterialBufferWgsl } from './chunks/materialBuffer.wgsl.js';\nimport createIntersectionWgsl from './chunks/intersect.wgsl.js';\nimport randomWgsl from './chunks/random.wgsl.js';\nimport randWgsl from './chunks/rand.wgsl.js';\nimport { createSampleMaterialWgsl } from './chunks/sampleMaterial.wgsl.js';\nimport sampleLightWgsl from './chunks/sampleLight.wgsl.js';\nimport { createOpenPbrBsdfWgsl } from './chunks/openPbrBsdf.wgsl.js';\nimport samplingWgsl from './chunks/sampling.wgsl.js';\nimport sampleShadowCatcherWgsl from './chunks/sampleShadowCatcher.wgsl.js';\nimport envMapWgsl from './chunks/envMap.wgsl.js';\nimport uvProjectionWgsl from './chunks/uvProjection.wgsl.js';\nexport const WORKGROUP_SIZE = 8;\n\nexport interface RayTraceShaderOptions {\n hasDisplacement: boolean;\n /** Include direction frames only when used, preserving the legacy shader arithmetic otherwise. */\n hasAnisotropyDirections?: boolean;\n /** Include independent coat normals only when a material uses that slot. */\n hasCoatNormals?: boolean;\n /** Split glTF BLEND primary hits deterministically instead of stochastically. */\n hasAlpha?: boolean;\n /** Any scene material uses alpha mask or blend. */\n hasSceneAlpha?: boolean;\n /** Bit mask of the BLAS layouts present in the scene (bvh2, bvh4, bvh-gpu, cwbvh). */\n blasLayoutMask?: number;\n}\n\n/** Generates the WebGPU megakernel path tracing compute shader. */\nexport default function rayTraceCompute({\n hasDisplacement,\n hasAnisotropyDirections = false,\n hasCoatNormals = false,\n hasAlpha = true,\n hasSceneAlpha = true,\n blasLayoutMask = 1\n}: RayTraceShaderOptions) {\n hasAnisotropyDirections ||= hasCoatNormals;\n const deterministicPrimaryAlpha = hasAlpha;\n const source = /* wgsl */ `\n\n${commonWgsl}\n\n${rayTraceCoreWgsl({ hasSceneAlpha, hasAnisotropyDirections, hasCoatNormals })}\n\n${uvProjectionWgsl}\n\n${createMaterialBufferWgsl({ hasSceneAlpha, hasAnisotropyDirections, hasCoatNormals })}\n\n${createIntersectionWgsl({ hasDisplacement, blasLayoutMask, hasAnisotropyDirections, hasCoatNormals })}\n${randWgsl}\n${randomWgsl}\n${envMapWgsl}\n\n${samplingWgsl}\n${createOpenPbrBsdfWgsl(hasAnisotropyDirections, hasCoatNormals)}\n${sampleLightWgsl}\n\n// Index of the path vertex currently being shaded.\nvar<private> bounceIndex: u32 = 0u;\n\n// Number of light candidates resampled for each NEE estimate. Interactive\n// preview caps the budget at two.\nfn neeCandidateCount() -> u32 {\n return u32(max(rtUniform.directLightCandidates, 1));\n}\n\n// Hash-based random number used to select among light candidates. It is\n// independent of the path sample stream so the candidate count does not\n// change the dimensions consumed by later bounces.\nfn lightCandidateRandom(salt: u32) -> f32 {\n // Reservoir replacement needs the full [0, 1) interval: sample1D's EPS\n // remapping makes small positive replacement probabilities unreachable.\n // Preserve the legacy sequential RNG draw in the random sampling mode so\n // subsequent path samples retain their stream; selection uses the hash.\n if (SAMPLING_PATTERN == 2) { _ = rand(); }\n let pixelID = samplingPixel.x + samplingPixel.y * u32(rtUniform.outputSize.x);\n let key = sampleSeed(sampleSeed(pixelID, pixelSampleIndex), rtUniform.sequenceSeed);\n let dimension = sampleSeed(samplingBounce, salt) ^ 0xa511e9b3u;\n return f32(sampleHash(key ^ dimension) >> 8u) * (1.0 / 16777216.0);\n}\n\n// MIS weight of a light-sampled contribution. With several resampled\n// candidates the NEE technique is drawn neeCandidateCount() times.\nfn neeMISWeight(lightPdf: f32, scatteringPdf: f32) -> f32 {\n let candidateCount = neeCandidateCount();\n if (candidateCount <= 1u) {\n return sampling_power_heuristic(lightPdf, scatteringPdf);\n }\n let weightedLightPdf = f32(candidateCount) * lightPdf;\n return weightedLightPdf /\n max(weightedLightPdf + scatteringPdf, 1e-20);\n}\n\n// MIS weight of a BSDF-sampled ray that hits a light.\nfn bsdfMISWeight(scatteringPdf: f32, lightPdf: f32) -> f32 {\n let candidateCount = neeCandidateCount();\n if (candidateCount <= 1u) {\n return sampling_power_heuristic(scatteringPdf, lightPdf);\n }\n let weightedLightPdf = f32(candidateCount) * lightPdf;\n return scatteringPdf /\n max(scatteringPdf + weightedLightPdf, 1e-20);\n}\n\n${createSampleMaterialWgsl(hasAnisotropyDirections, hasCoatNormals)}\n${sampleShadowCatcherWgsl}\n\n// Output\n@group(3) @binding(0) var<storage, read_write> outColor: array<vec4f>;\n@group(3) @binding(1) var<storage, read_write> outNormal: array<vec4f>;\n@group(3) @binding(2) var<storage, read_write> outAlbedo: array<vec4f>;\n\n${shadowCatcherAccumulationWGSL}\n@group(3) @binding(3) var<storage, read_write> outShadowCatcher: array<ShadowCatcherAccumulation>;\n\n\nfn accumulateNormalOrVariance(\n pixelIndex: u32,\n radiance: vec3f,\n normal: vec3f\n) {\n if (rtUniform.varianceMoments != 0) {\n let value = dot(radiance, luminance);\n outNormal[pixelIndex] += vec4f(value * value, 0.0, 0.0, 1.0);\n }\n else {\n outNormal[pixelIndex] += vec4f(normal, 1.0);\n }\n}\n\n// Moments of the per-sample value the built-in denoiser compares, used to\n// estimate the variance of each pixel's mean.\nfn accumulateDenoiseMoments(pixelIndex: u32, value: f32) {\n outShadowCatcher[pixelIndex].moments += vec4f(value, value * value, 0.0, 0.0);\n}\n\n\nstruct RayTraceAuxiliary {\n // Denoising guides and auxiliary pixel classification.\n albedo: vec3f,\n isShadowCatcher: bool,\n normal: vec3f,\n isBlank: bool,\n}\n\nfn groundedSurfaceDistance(point: vec3f, height: f32, radius: f32) -> f32 {\n let pointLength = length(point);\n if (pointLength < 0.00001) {\n return -radius;\n }\n\n let originalY = radius * point.y / pointLength;\n let transitionY = -1.5 * height;\n var surfaceRadius: f32;\n if (originalY >= 0.0) {\n surfaceRadius = radius;\n }\n else if (originalY < transitionY) {\n surfaceRadius = -height * pointLength / point.y;\n }\n else {\n let factor = 1.0\n - originalY * originalY / (3.0 * transitionY * transitionY);\n surfaceRadius = radius * factor;\n }\n return pointLength - surfaceRadius;\n}\n\nfn groundedBackgroundDirection(origin: vec3f, direction: vec3f) -> vec3f {\n if (!USE_GROUNDED_BACKGROUND) {\n return direction;\n }\n let params = rtUniform.groundedBackgroundParams;\n let rayDirection = normalize(direction);\n let center = vec3f(\n rtUniform.groundedBackgroundCenter.x,\n params.z + params.x,\n rtUniform.groundedBackgroundCenter.y\n );\n let localOrigin = origin - center;\n let projectedOrigin = dot(localOrigin, rayDirection);\n let discriminant = projectedOrigin * projectedOrigin\n - dot(localOrigin, localOrigin) + params.y * params.y;\n if (\n discriminant <= 0.0\n || groundedSurfaceDistance(localOrigin, params.x, params.y) > 0.0\n ) {\n return direction;\n }\n\n var farDistance = -projectedOrigin + sqrt(discriminant);\n if (farDistance <= 0.0) {\n return direction;\n }\n\n let sphereHit = localOrigin + rayDirection * farDistance;\n if (sphereHit.y >= 0.0) {\n return normalize(sphereHit);\n }\n\n if (rayDirection.y < -0.00001 && params.y > 1.5 * params.x) {\n let groundDistance = (-params.x - localOrigin.y) / rayDirection.y;\n let groundHit = localOrigin + rayDirection * groundDistance;\n let groundRadius = (2.0 / 3.0)\n * sqrt(params.y * params.y - 2.25 * params.x * params.x);\n if (groundDistance > 0.0 && length(groundHit.xz) <= groundRadius) {\n return normalize(groundHit);\n }\n }\n\n var nearDistance = 0.0;\n for (var i = 0; i < 14; i++) {\n let middleDistance = (nearDistance + farDistance) * 0.5;\n let middlePoint = localOrigin + rayDirection * middleDistance;\n if (groundedSurfaceDistance(middlePoint, params.x, params.y) > 0.0) {\n farDistance = middleDistance;\n }\n else {\n nearDistance = middleDistance;\n }\n }\n let hit = localOrigin + rayDirection * ((nearDistance + farDistance) * 0.5);\n return normalize(hit);\n}\n\nfn sampleSceneBackground(ray: Ray) -> vec4f {\n var bg = vec4f(0.0);\n if (USE_BACKGROUND_MAP) {\n let backgroundDirection = groundedBackgroundDirection(ray.o, ray.d);\n bg = textureLinear(backgroundMap, cartesianToEquirect(backgroundDirection));\n }\n else if (USE_BACKGROUND) {\n bg = rtUniform.backgroundColor;\n }\n else if (USE_ENVMAP) {\n bg = textureLinear(envMap, cartesianToEquirect(ray.d));\n }\n return bg;\n}\n\nfn sampleSpectralSceneBackground(ray: Ray, wavelength: f32) -> vec4f {\n let bg = sampleSceneBackground(ray);\n if (SPECTRAL) {\n return vec4f(sampling_spectral_rgb_filter(bg.rgb, wavelength), bg.a);\n }\n return bg;\n}\n\nfn bounce(uv: vec2f, path: ptr<function, Path>, depth: ptr<function, i32>, si: ptr<function, SurfaceInteraction>, ma: ptr<function, OpenPbrResolvedMaterial>, outAux: ptr<function, RayTraceAuxiliary>) {\n samplingBounce = u32(max(*depth, 0));\n let matType = (*ma).materialType;\n let isShadowCatcher = matType == SHADOW_CATCHER;\n if (isShadowCatcher) {\n if ((*path).fromEye) {\n (*si).hit = false;\n }\n else {\n // The catcher only contributes to the separate shadow mask. Secondary\n // rays must see the environment behind it instead of its default white\n // material, otherwise it appears in reflections.\n if (USE_ENVMAP) {\n if ((*path).isNextRefraction) {\n // A camera-visible transmitted ray can hit the invisible catcher\n // before the grounded skybox. Preserve the displayed projection.\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n let backgroundMisWeight = select(bsdfMISWeight((*path).scatteringPdf,\n envMapPdf(cartesianToEquirect((*path).ray.d)) * rtUniform.envLightPmf),\n 1.0, (*path).scatteringIsDelta);\n (*path).li += backgroundMisWeight * (*path).beta * bg.rgb * bg.a;\n (*path).alpha = max((*path).alpha, bg.a);\n }\n else {\n let envUv = cartesianToEquirect((*path).ray.d);\n var emission = textureLinear(envMap, envUv).rgb;\n if (SPECTRAL) {\n emission = sampling_spectral_rgb_filter(emission, (*si).wavelength);\n }\n (*path).li += (*path).beta * emission *\n bsdfMISWeight((*path).scatteringPdf, envMapPdf(envUv));\n }\n }\n else {\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n (*path).li += (*path).beta * bg.rgb * bg.a;\n }\n (*path).abort = true;\n return;\n }\n }\n if (!(*si).hit) {\n // The spectral integrator traces red, green, and blue paths separately.\n // Split background radiance by wavelength as well so the three paths\n // reconstruct the skybox instead of adding three full RGB copies.\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n if ((*path).fromEye) {\n // Sample from background if first ray not hit anything.\n (*path).li = (*path).beta * bg.rgb * bg.a;\n (*path).alpha = bg.a;\n // Only treat as blank pixels when the background is transparent\n (*outAux).isBlank = bg.a < 0.001 ||\n (USE_BACKGROUND && !USE_BACKGROUND_MAP);\n }\n else {\n let cam = initCameraRay(uv);\n let towardsCam = dot(cam.d, (*path).ray.d) > 0.0;\n if (rtUniform.transmissiveOutputAlphaMin < 1.0) {\n if (towardsCam) {\n (*path).alpha = max((*path).alpha, bg.a);\n } else {\n (*path).alpha = 1.0;\n }\n }\n if (!USE_ENVMAP) {\n // A display background is not an illumination environment, but a\n // visible secondary ray (e.g. through glass) still resolves it.\n if (towardsCam) {\n // This is a terminal BSDF-hit contribution, not a replacement for\n // NEE accumulated at earlier vertices on the same path.\n (*path).li += (*path).beta *\n bsdfMISWeight((*path).scatteringPdf, 1.0) * bg.rgb;\n }\n }\n }\n (*path).abort = true;\n return;\n }\n\n var sampleIgnored = false;\n if ((matType == STANDARD || matType == UNLIT)) {\n let alpha = clamp((*ma).alpha, 0.0, 1.0);\n \n if (alpha < 1.0) {\n // Alpha is stochastic coverage. Keep accepted surface radiance at full\n // weight instead of multiplying it by alpha a second time.\n if (alpha <= 0.0 || hashedSample(3u) > alpha) {\n sampleIgnored = true;\n }\n }\n if (!sampleIgnored && rtUniform.transmissiveOutputAlphaMin < 1.0) {\n // A selected stochastic coverage sample is fully covered. Ignored\n // samples leave output alpha untouched and continue to the back layer.\n let outputAlpha = max(1.0 - (*ma).transmissionWeight, 1.0 - pow(1.0 - rtUniform.transmissiveOutputAlphaMin, f32(*depth)));\n (*path).alpha = max(outputAlpha, (*path).alpha);\n }\n if (!sampleIgnored) {\n bounceIndex = u32(max(*depth, 0));\n SampleMaterial(si, ma, path);\n }\n else {\n // Alpha-null traversal does not consume the receiver event. The first\n // accepted surface behind it is still the receiver for NEE/AOVs.\n (*path).ray.o = (*si).position + (*path).ray.d * EPS;\n }\n }\n\n if (!(*si).albedoSampled && !sampleIgnored) {\n // Select denoising guides.\n if ((matType == STANDARD || matType == UNLIT)) {\n if (!((*ma).transmissionWeight > 0.5 ||\n ((*ma).specularRoughness <= 0.02 && (*ma).baseMetalness > 0.98))) {\n (*outAux).albedo = (*ma).baseColor;\n (*outAux).normal = select(\n (*si).normal,\n -(*si).normal,\n dot(\n (*si).normal,\n (*si).position - cameraUniform.viewInv[3].xyz\n ) > 0.0\n );\n (*si).albedoSampled = true;\n }\n // TODO pick albedo and normal on the surface if ray is too deep and can't hit any rough or opaque surfaces.\n if (*depth > 4) {\n (*si).albedoSampled = true;\n }\n }\n }\n\n // Russian roulette with throughput compensation.\n if (USE_RR) {\n if (*depth >= RR_MAX && !sampleIgnored) {\n let rouletteBeta = (*path).beta;\n // Max-component survival (pbrt-v4) avoids over-amplifying saturated paths.\n let maxComponent = max(max(rouletteBeta.x, rouletteBeta.y), rouletteBeta.z);\n let q: f32 = max(0.0, 1.0 - maxComponent);\n if (sample1D(bounceDim(RUSSIAN_ROULETTE)) < q) {\n // TODO we force use alpha 1.0 here.\n // To avoid the glass pixels is transparent even if it's on an opaque object.\n (*path).alpha = 1.0;\n (*path).abort = true;\n }\n else {\n (*path).beta /= 1.0 - q;\n }\n }\n }\n if ((*path).abort) {\n return;\n }\n\n intersectScene((*path).ray, si, *depth, true, (*path).isNextRefraction);\n getMaterial(ma, (*si).materialIndex, (*si).uv, (*si).uv1, (*si).instanceIndex);\n\n if (SPECTRAL) {\n // Is glass\n if ((*ma).transmissionWeight > 0.001) {\n (*ma).specularIor = sampling_cauchy_ior(\n (*si).wavelength,\n (*ma).specularIor,\n (*ma).transmissionDispersionScale,\n (*ma).dispersionAbbeNumber\n );\n }\n }\n\n if (sampleIgnored) {\n return;\n }\n\n // Use light if hit the light\n var lightSample: LightSampleRec;\n let isAlphaOccluded = hashedSample(3u) < (*ma).alpha;\n for (var i = 0; i < rtUniform.lightsCount; i++) {\n // Fetch light Data\n let light: Light = analyticLights[i];\n let d = intersectLight((*path).ray, light, &lightSample);\n lightSample.pdf *= light.selectionPmf;\n let occluded = (*si).hit && d > distance((*si).position, (*path).ray.o);\n if (d != INF && !(occluded && isAlphaOccluded)) {\n let misWeight = bsdfMISWeight((*path).scatteringPdf, lightSample.pdf);\n if (SPECTRAL) {\n lightSample.emission = sampling_spectral_rgb_filter(lightSample.emission, (*si).wavelength);\n }\n (*path).li += misWeight * (*path).beta * lightSample.emission;\n (*path).alpha = 1.0;\n (*path).abort = true;\n }\n }\n\n if (USE_ENVMAP) {\n if (!((*si).hit && isAlphaOccluded)) {\n if ((*path).isNextRefraction) {\n // Camera-visible transmission follows the displayed background,\n // including grounded projection. Other rays use the raw environment\n // map as the physically sampled lighting source.\n let bg = sampleSpectralSceneBackground(\n (*path).ray,\n (*si).wavelength\n );\n let backgroundMisWeight = select(bsdfMISWeight((*path).scatteringPdf,\n envMapPdf(cartesianToEquirect((*path).ray.d)) * rtUniform.envLightPmf),\n 1.0, (*path).scatteringIsDelta);\n (*path).li += backgroundMisWeight * (*path).beta * bg.rgb * bg.a;\n (*path).alpha = max((*path).alpha, bg.a);\n (*path).abort = true;\n return;\n }\n let _uv = cartesianToEquirect((*path).ray.d);\n var emission = textureLinear(envMap, _uv).rgb;\n let misWeight = bsdfMISWeight(\n (*path).scatteringPdf,\n envMapPdf(_uv) * rtUniform.envLightPmf\n );\n if (SPECTRAL) {\n emission = sampling_spectral_rgb_filter(emission, (*si).wavelength);\n }\n // TODO force the min alpha to be 1 when showing envmap as background\n if (!USE_BACKGROUND) {\n (*path).alpha = 1.0;\n }\n (*path).li += (*path).beta * emission * misWeight;\n (*path).abort = true;\n }\n }\n\n *depth = *depth + 1;\n}\n\nfn initCameraRay(coord: vec2f) -> Ray {\n let origin = cameraUniform.viewProjectionInv * vec4f(coord * 2.0 - 1.0, -1.0, 1.0);\n let center = cameraUniform.viewProjectionInv * vec4f(coord * 2.0 - 1.0, 1.0, 1.0);\n var cam: Ray;\n initRay(&cam, origin.xyz / origin.w, normalize(center.xyz * origin.w - origin.xyz * center.w));\n\n return cam;\n}\n\n\nfn bevelCubicSample(radius: f32, xi: f32) -> vec2f {\n var x = 0.25;\n for (var iteration = 0; iteration < 10; iteration++) {\n let x2 = x * x;\n let x3 = x2 * x;\n let oneMinusX = 1.0 - x;\n let value = 10.0 * x2 - 20.0 * x3 + 15.0 * x2 * x2 -\n 4.0 * x2 * x3 - xi;\n let derivative = 20.0 * x * oneMinusX * oneMinusX * oneMinusX;\n if (abs(value) < 1e-6 || derivative == 0.0) { break; }\n x = clamp(x - value / derivative, 0.0, 1.0);\n }\n let diskRadius = x * radius;\n return vec2f(diskRadius, sqrt(max(radius * radius - diskRadius * diskRadius, 0.0)));\n}\n\nfn bevelCubicPdf(radius: f32, distanceToSource: f32) -> f32 {\n if (distanceToSource >= radius) { return 0.0; }\n let falloff = radius - distanceToSource;\n return falloff * falloff * falloff;\n}\n\n// Shader bevel. Usage: docs/materials.md (Renderer extensions)\nfn getBevelNormal(source: SurfaceInteraction, radius: f32) -> vec3f {\n var geometricNormal = source.faceNormal;\n if (dot(geometricNormal, source.vertexNormal) < 0.0) {\n geometricNormal = -geometricNormal;\n }\n var tangent: vec3f;\n var bitangent: vec3f;\n sampling_orthonormal_basis(geometricNormal, &tangent, &bitangent);\n var normalSum = vec3f(0.0);\n\n for (var sampleIndex = 0; sampleIndex < BEVEL_SAMPLES; sampleIndex++) {\n let bevelRnd = sample2D(CAMERA_BEVEL_BASE + u32(sampleIndex));\n var axisSample = bevelRnd.x;\n let radialSample = bevelRnd.y;\n var diskNormal = geometricNormal;\n var diskTangent = tangent;\n var diskBitangent = bitangent;\n var pickPdfNormal = 0.5;\n var pickPdfTangent = 0.25;\n var pickPdfBitangent = 0.25;\n if (axisSample < 0.5) {\n axisSample *= 2.0;\n }\n else if (axisSample < 0.75) {\n let swapAxis = diskNormal;\n diskNormal = diskTangent;\n diskTangent = swapAxis;\n pickPdfNormal = 0.25;\n pickPdfTangent = 0.5;\n axisSample = (axisSample - 0.5) * 4.0;\n }\n else {\n let swapAxis = diskNormal;\n diskNormal = diskBitangent;\n diskBitangent = swapAxis;\n pickPdfNormal = 0.25;\n pickPdfBitangent = 0.5;\n axisSample = (axisSample - 0.75) * 4.0;\n }\n\n let diskSample = bevelCubicSample(radius, radialSample);\n let phi = TWOPI * axisSample;\n let diskOffset = diskSample.x *\n (cos(phi) * diskTangent + sin(phi) * diskBitangent);\n var ray: Ray;\n initRay(\n &ray,\n source.position + diskNormal * diskSample.y + diskOffset,\n -diskNormal\n );\n var hit: SurfaceInteraction;\n intersectSceneBevel(\n ray,\n &hit,\n source.instanceIndex,\n max(2.0 * diskSample.y, EPS)\n );\n if (!hit.hit) { continue; }\n\n let pdfNormal = pickPdfNormal * abs(dot(diskNormal, hit.faceNormal));\n let pdfTangent = pickPdfTangent * abs(dot(diskTangent, hit.faceNormal));\n let pdfBitangent = pickPdfBitangent * abs(dot(diskBitangent, hit.faceNormal));\n var weight = pdfNormal / max(\n pdfNormal * pdfNormal + pdfTangent * pdfTangent +\n pdfBitangent * pdfBitangent,\n 1e-8\n );\n weight *= bevelCubicPdf(radius, distance(hit.position, source.position)) /\n max(bevelCubicPdf(radius, diskSample.x), 1e-8);\n normalSum += weight * hit.vertexNormal;\n }\n\n if (dot(normalSum, normalSum) <= 1e-12) { return source.normal; }\n let bevelVertexNormal = normalize(normalSum);\n let result = source.normal + bevelVertexNormal - source.vertexNormal;\n if (dot(result, result) > 1e-12) { return normalize(result); }\n return source.normal;\n}\n\nfn integrateOnePathSample(uv: vec2f, si: ptr<function, SurfaceInteraction>, ray: Ray, ma: ptr<function, OpenPbrResolvedMaterial>, outAux: ptr<function, RayTraceAuxiliary>) -> vec4f {\n var path: Path;\n path.ray = ray;\n path.li = vec3f(0.0);\n path.alpha = select(1.0, 0.0, rtUniform.transmissiveOutputAlphaMin < 1.0);\n path.beta = vec3f(1.0);\n path.fromEye = true;\n path.scatteringPdf = 0.0;\n path.scatteringIsDelta = false;\n path.isNextRefraction = false;\n path.abort = false;\n\n let isNotTransparent = (*ma).transmissionWeight < 0.001 && (*ma).subsurfaceWeight < 0.001 && (*ma).alpha > 0.99;\n let bounceLimit = max(rtUniform.bounces, 0);\n var depth = 0;\n // Transparent paths may take up to three times the configured bounces.\n for (var idx: i32 = 0; idx < min(bounceLimit * 3, ${MAX_PATH_EVENTS}); idx = idx + 1) {\n if (isNotTransparent && idx > bounceLimit) {\n path.abort = true;\n }\n if (path.abort) {\n break;\n }\n bounce(uv, &path, &depth, si, ma, outAux);\n }\n\n return vec4f(path.li, path.alpha);\n}\n\nfn integrateOnePath(uv: vec2f, si: ptr<function, SurfaceInteraction>, ray: Ray, ma: ptr<function, OpenPbrResolvedMaterial>, outAux: ptr<function, RayTraceAuxiliary>) -> vec4f {\n ${deterministicPrimaryAlpha ? /* wgsl */ `let coverage = clamp((*ma).alpha, 0.0, 1.0);\n if (!(*si).hit || ((*ma).materialType != STANDARD && (*ma).materialType != UNLIT) || coverage >= 1.0) {\n return integrateOnePathSample(uv, si, ray, ma, outAux);\n }\n\n // glTF BLEND is source-over opacity, not a physical glass boundary. Split\n // the primary ray so reflected radiance is attenuated by alpha every sample\n // while the complementary branch continues straight to the scene behind it.\n // Secondary alpha hits remain stochastic to avoid exponential path growth.\n var backRay = ray;\n backRay.o = (*si).position + ray.d * (EPS * 0.02);\n var backSi = *si;\n backSi.albedoSampled = false;\n intersectScene(backRay, &backSi, 0, false, false);\n var backMa: OpenPbrResolvedMaterial;\n getMaterial(&backMa, backSi.materialIndex, backSi.uv, backSi.uv1, backSi.instanceIndex);\n var backAux = *outAux;\n if (backSi.hit && backMa.materialType == STANDARD) {\n backAux.albedo = select(\n backMa.baseColor,\n vec3f(1.0),\n backMa.transmissionWeight > 0.5\n );\n backAux.normal = select(\n backSi.normal,\n -backSi.normal,\n dot(\n backSi.normal,\n backSi.position - cameraUniform.viewInv[3].xyz\n ) > 0.0\n );\n backSi.albedoSampled = backMa.transmissionWeight > 0.5;\n }\n let back = integrateOnePathSample(\n uv, &backSi, backRay, &backMa, &backAux\n );\n if (coverage <= 0.0) {\n *outAux = backAux;\n return back;\n }\n\n var frontSi = *si;\n var frontMa = *ma;\n frontMa.alpha = 1.0;\n var frontAux = *outAux;\n let front = integrateOnePathSample(\n uv, &frontSi, ray, &frontMa, &frontAux\n );\n (*outAux).albedo = mix(backAux.albedo, frontAux.albedo, coverage);\n let blendedNormal = mix(backAux.normal, frontAux.normal, coverage);\n (*outAux).normal = select(\n vec3f(0.0),\n normalize(blendedNormal),\n dot(blendedNormal, blendedNormal) > 1e-12\n );\n (*outAux).isBlank = backAux.isBlank && frontAux.isBlank;\n (*outAux).isShadowCatcher = backAux.isShadowCatcher && frontAux.isShadowCatcher;\n return mix(back, front, coverage);` : /* wgsl */ `return integrateOnePathSample(uv, si, ray, ma, outAux);`}\n}\n\n// Path tracing integrator as described in\n// http://www.pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Path_Tracing.html#\nfn integrator(uv: vec2f, ray: Ray, outAux: ptr<function, RayTraceAuxiliary>) -> vec4f {\n var si: SurfaceInteraction;\n var ma: OpenPbrResolvedMaterial;\n si.albedoSampled = false;\n si.mediumType = 0;\n si.mediumExtinction = vec3f(0.0);\n si.mediumScattering = vec3f(0.0);\n si.mediumAnisotropy = 0.0;\n si.barycentrics = vec2f(0.0);\n\n // TODO not put tMax in ray. So we don't need to use pointer of ray here.\n intersectScene(ray, &si, 0, false, false);\n getMaterial(&ma, si.materialIndex, si.uv, si.uv1, si.instanceIndex);\n\n if (si.hit && ma.materialType == SHADOW_CATCHER) {\n (*outAux).isShadowCatcher = true;\n // The composed denoising input includes this receiver's shadow. Supply\n // neutral reflectance and its shading normal, independent of visibility.\n (*outAux).albedo = vec3f(1.0);\n (*outAux).normal = select(si.normal, -si.normal, dot(si.normal, ray.d) > 0.0);\n var path: Path;\n path.ray = ray;\n path.fromEye = true;\n sampleShadowCatcher(si, 0, &path);\n // RGB stores primary catcher coverage independently from the stochastic\n // visibility in alpha. Composition uses this raw coverage to clip any\n // shadow-mask denoising bleed at model silhouettes.\n return vec4f(vec3f(1.0), path.alpha);\n }\n else {\n if (HAS_BEVEL && rtUniform.bevel != 0) {\n if (ma.bevel > 0.0) {\n si.normal = getBevelNormal(si, ma.bevel);\n }\n }\n\n // Store a clean first-hit fallback. Glass retains this stable guide;\n // other delta paths may replace it at their first non-delta hit.\n if (si.hit && (ma.materialType == STANDARD || ma.materialType == UNLIT)) {\n (*outAux).albedo = select(ma.baseColor, vec3f(1.0), ma.transmissionWeight > 0.5);\n (*outAux).normal = select(\n si.normal,\n -si.normal,\n dot(si.normal, si.position - cameraUniform.viewInv[3].xyz) > 0.0\n );\n\n // Do not replace a glass guide with a randomly selected downstream\n // refraction/reflection hit; OIDN auxiliary features must stay clean.\n si.albedoSampled = ma.transmissionWeight > 0.5;\n }\n\n (*outAux).isShadowCatcher = false;\n\n if (SPECTRAL) {\n // Keep one source-level integrateOnePath call for both interactive\n // preview and offline RGB splitting. A runtime loop bound prevents the\n // WGSL compiler from cloning the full path graph three times while the\n // uniform still selects the exact existing preview/offline behavior.\n let rgbWaveLengths = array<f32, 3>(650.0, 510.0, 475.0);\n var colAccum = vec3f(0.0);\n var alpha = 0.0;\n var accumulatedGuideAlbedo = vec3f(0.0);\n var accumulatedGuideNormal = vec3f(0.0);\n var allBlank = true;\n var si2 = si;\n var ma2 = ma;\n let wavelengthRngState = s0;\n let wavelengthHashedCounter = hashedCounter;\n let singleWavelength = rtUniform.dispersionWavelengths == 1;\n let dispersivePrimary = si.hit &&\n ma.transmissionWeight > 0.001 &&\n ma.transmissionDispersionScale > 0.001;\n let wavelengthCount = select(3, 1, singleWavelength);\n var heroWavelengthIndex = 0;\n if (singleWavelength && dispersivePrimary) {\n // Stochastic hero wavelength: one RGB channel per pixel keeps the\n // preview spectral at one-third of the full three-path cost.\n heroWavelengthIndex = min(i32(floor(sample1D(CAMERA_WAVELENGTH) * 3.0)), 2);\n }\n for (var i = 0; i < wavelengthCount; i++) {\n // Common random numbers keep Monte Carlo error correlated between\n // RGB wavelengths. This removes much of the colored variance that\n // otherwise turns into dirty chromatic patches after OIDN.\n if (!singleWavelength) {\n s0 = wavelengthRngState;\n hashedCounter = wavelengthHashedCounter;\n samplingBounce = 0u;\n }\n si.wavelength = 0.0;\n if (!singleWavelength || dispersivePrimary) {\n si.wavelength = rgbWaveLengths[select(i, heroWavelengthIndex, singleWavelength)];\n ma.specularIor = sampling_cauchy_ior(\n si.wavelength,\n ma.specularIor,\n ma.transmissionDispersionScale,\n ma.dispersionAbbeNumber\n );\n }\n var wavelengthAux: RayTraceAuxiliary;\n if (singleWavelength) {\n wavelengthAux = *outAux;\n } else {\n wavelengthAux.albedo = (*outAux).albedo;\n wavelengthAux.normal = (*outAux).normal;\n wavelengthAux.isBlank = false;\n wavelengthAux.isShadowCatcher = false;\n }\n let res = integrateOnePath(uv, &si, ray, &ma, &wavelengthAux);\n if (singleWavelength) {\n *outAux = wavelengthAux;\n let scale = select(1.0, 3.0, dispersivePrimary);\n return vec4f(res.rgb * scale, res.a);\n }\n colAccum += res.rgb;\n alpha = max(alpha, res.a);\n accumulatedGuideAlbedo += wavelengthAux.albedo;\n accumulatedGuideNormal += wavelengthAux.normal;\n allBlank = allBlank && wavelengthAux.isBlank;\n si = si2;\n ma = ma2;\n }\n (*outAux).albedo = accumulatedGuideAlbedo / 3.0;\n (*outAux).normal = accumulatedGuideNormal / 3.0;\n (*outAux).isBlank = allBlank;\n return vec4f(colAccum, alpha);\n }\n else {\n return integrateOnePath(uv, &si, ray, &ma, outAux);\n }\n }\n}\n\nfn sampleCircle(ip: vec2f) -> vec2f {\n let p = 2.0 * ip - 1.0;\n let greater = abs(p.x) > abs(p.y);\n let r = select(p.y, p.x, greater);\n let theta = select(PI * (0.5 - 0.25 * p.x / p.y), 0.25 * PI * p.y / p.x, greater);\n return r * vec2f(cos(theta), sin(theta));\n}\n\n\n@compute @workgroup_size(${WORKGROUP_SIZE}, ${WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) globalId: vec3u) {\n let outputSize = rtUniform.outputSize;\n let scaleOffset = rtUniform.scaleOffset;\n var jitter = rtUniform.jitter;\n // TODO if outputSize is not multiple of 8?\n let coord = globalId.xy * vec2u(scaleOffset.xy) + vec2u(scaleOffset.zw);\n if (coord.x >= u32(outputSize.x) || coord.y >= u32(outputSize.y)) {\n return;\n }\n let idx = coord.x + coord.y * u32(outputSize.x);\n\n if (\n rtUniform.adaptiveSampling != 0 &&\n outNormal[idx].a >= textureLoad(\n adaptiveSampleTargets,\n vec2i(coord),\n 0\n ).r\n ) {\n return;\n }\n\n // Match WebGL's rasterized v_Texcoord, whose sample is at the center of\n // each pixel. Keeping the same origin is material relevant: a half-pixel\n // shift moves a narrow metal highlight across several pixels.\n let uv = (vec2<f32>(f32(coord.x), f32(coord.y)) + vec2f(0.5)) / outputSize;\n\n samplingPixel = coord;\n pixelSampleIndex = u32(outNormal[idx].a) + rtUniform.sampleSequenceOffset;\n samplingBounce = 0u; hashedCounter = 0u;\n if (SAMPLING_PATTERN == 0 || SAMPLING_PATTERN == 1) {\n jitter = (sample2D(CAMERA_PIXEL) - 0.5) / outputSize;\n }\n rng_initialize(coord, i32(pixelSampleIndex ^ sampleHash(rtUniform.sequenceSeed)));\n var cam = initCameraRay(uv + jitter);\n\n if (cameraUniform.apertureRadius > 0.0) {\n // Thin lens: the focus plane is perpendicular to the view direction.\n let eye = cameraUniform.viewInv[3].xyz;\n let forward = -normalize(cameraUniform.viewInv[2].xyz);\n let focusPoint = eye + cam.d * (cameraUniform.focusDistance / dot(cam.d, forward));\n let lensPoint = cameraUniform.apertureRadius * sampleCircle(sample2D(CAMERA_LENS));\n let origin = (cameraUniform.viewInv * vec4f(lensPoint, 0.0, 1.0)).xyz;\n let dir = normalize(focusPoint - origin);\n initRay(&cam, origin, dir);\n }\n\n var outAux: RayTraceAuxiliary;\n outAux.albedo = vec3f(0.0);\n outAux.normal = vec3f(0.0);\n outAux.isBlank = false;\n outAux.isShadowCatcher = false;\n var liAndAlpha = integrator(uv + jitter, cam, &outAux);\n\n // Clamp at Integration Level to reduce fireflies.\n // TODO Path level configuration?\n if (rtUniform.clamping > 0.0) {\n let clamping = rtUniform.clamping;\n liAndAlpha = vec4(clamp(liAndAlpha.rgb, -vec3f(clamping), vec3f(clamping)), liAndAlpha.a);\n }\n if (!(liAndAlpha.x < INF && liAndAlpha.x > -EPS)) {\n liAndAlpha = vec4f(0.0, 0.0, 0.0, 1.0);\n }\n\n if (outAux.isShadowCatcher) {\n // The catcher is invisible, so the main color at this pixel must still\n // contain whatever the camera ray would have seen behind it.\n let backgroundSample = sampleSceneBackground(cam);\n let backgroundHit = select(0.0, 1.0, backgroundSample.a >= 0.01);\n let backgroundPremultiplied = vec4f(backgroundSample.rgb * backgroundSample.a, backgroundSample.a);\n // Keep the background contribution separate from geometry at silhouettes.\n // mask: catcher coverage, background alpha, background hits, shadow opacity.\n outShadowCatcher[idx].mask += vec4f(1.0, backgroundSample.a, backgroundHit, liAndAlpha.a);\n outShadowCatcher[idx].background += backgroundPremultiplied;\n outColor[idx] += backgroundPremultiplied;\n outAlbedo[idx] += vec4f(outAux.albedo, backgroundHit);\n accumulateNormalOrVariance(idx, backgroundSample.rgb, outAux.normal);\n // The composed catcher is the background darkened by the shadow. Over a\n // transparent background the shadow is only its opacity.\n let visibility = 1.0 - liAndAlpha.a;\n accumulateDenoiseMoments(idx, visibility * select(\n 1.0, dot(backgroundSample.rgb, luminance), backgroundSample.a >= 0.01\n ));\n }\n else {\n if (HAS_SHADOW_CATCHER && outAux.isBlank && USE_BACKGROUND && !USE_BACKGROUND_MAP && rtUniform.backgroundColor.a >= 0.99999) {\n // A primary miss sees the same opaque display backdrop as a catcher\n // hit, but must never receive its shadow. Keep that radiance and alpha\n // out of the geometry layer before hit-count normalization/tone mapping.\n // Red/blue still count only actual catcher hits; green counts all\n // background alpha removed from the foreground during normalization.\n outShadowCatcher[idx].background += liAndAlpha;\n outShadowCatcher[idx].mask.g += liAndAlpha.a;\n }\n outColor[idx] += liAndAlpha;\n outAlbedo[idx] += vec4f(outAux.albedo, select(1.0, 0.0, outAux.isBlank));\n accumulateNormalOrVariance(idx, liAndAlpha.rgb, outAux.normal);\n accumulateDenoiseMoments(idx, dot(liAndAlpha.rgb, luminance));\n }\n}\n`;\n // The query entry point shares the exact BVH and material implementation.\n // It has no effect on the beauty entry point or its bindings.\n // Gate optional material layers on scene-level feature flags so drivers can\n // skip the unused BSDF lobes.\n let specializedSource = source + depthQueryWGSL;\n const backgroundBits: Record<string, number> = {\n USE_BACKGROUND: 1,\n USE_BACKGROUND_MAP: 2,\n USE_GROUNDED_BACKGROUND: 4\n };\n specializedSource = specializedSource.replace(\n /\\b(?:USE_BACKGROUND|USE_BACKGROUND_MAP|USE_GROUNDED_BACKGROUND)\\b/g,\n (name) => `((rtUniform.backgroundFlags & ${backgroundBits[name]}) != 0)`\n );\n specializedSource = specializedSource.replace(\n new RegExp(`\\\\b(?:${Object.keys(WGPU_SCENE_FEATURE_BITS).join('|')})\\\\b`, 'g'),\n name => `((rtUniform.sceneFeatureFlags & ${WGPU_SCENE_FEATURE_BITS[name]}u) != 0u)`\n );\n return specializedSource;\n}\n"]}
@@ -207,9 +207,11 @@ export default class OpenPBRMaterial {
207
207
  displacementUvProjection: UVProjection;
208
208
  /** UV scale used with `displacementUvProjection`. Default `1`. */
209
209
  displacementUvProjectionScale: number;
210
- /** Height multiplier in mesh-local coordinate units. Default `0.1`. */
210
+ /** Height multiplier in mesh-local units; 0 removes height variation. Default `0.1`. */
211
211
  displacementScale: number;
212
- /** Ray-marching step for projective displacement. Default `0.01`. */
212
+ /** Offset added after scaling the height map, along the normal in mesh-local units; requires a displacement map. Default `0`. */
213
+ displacementBias: number;
214
+ /** Ray-marching step in mesh-local units; 0 automatically uses abs(displacementScale) / 32. Default `0`. */
213
215
  displacementStepSize: number;
214
216
  }
215
217
  export declare const materialAlphaModeConstants: {
@@ -214,9 +214,11 @@ export default class OpenPBRMaterial {
214
214
  displacementUvProjection = materialDefault('displacementUvProjection');
215
215
  /** UV scale used with `displacementUvProjection`. Default `1`. */
216
216
  displacementUvProjectionScale = materialDefault('displacementUvProjectionScale');
217
- /** Height multiplier in mesh-local coordinate units. Default `0.1`. */
217
+ /** Height multiplier in mesh-local units; 0 removes height variation. Default `0.1`. */
218
218
  displacementScale = materialDefault('displacementScale');
219
- /** Ray-marching step for projective displacement. Default `0.01`. */
219
+ /** Offset added after scaling the height map, along the normal in mesh-local units; requires a displacement map. Default `0`. */
220
+ displacementBias = materialDefault('displacementBias');
221
+ /** Ray-marching step in mesh-local units; 0 automatically uses abs(displacementScale) / 32. Default `0`. */
220
222
  displacementStepSize = materialDefault('displacementStepSize');
221
223
  }
222
224
  export const materialAlphaModeConstants = {
@@ -1 +1 @@
1
- {"version":3,"file":"OpenPBRMaterial.js","sourceRoot":"","sources":["../../src/scene/OpenPBRMaterial.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Vec2 } from '../types.js';\nimport { ColorRGB } from '../types.js';\nimport type Texture from './Texture.js';\nimport type { ThinFilmMapChannel } from './thinFilmParameters.js';\nimport { materialDefault, applyMaterialParams } from './openPbrSchema.js';\n\nexport type UVProjection = 'none' | 'triplanar' | 'spherical' | 'cylindrical';\nexport type MaterialAlphaMode = 'auto' | 'opaque' | 'mask' | 'blend';\n\nexport type { MaterialTextureName } from './materialTextureNames.js';\n\n/**\n * OpenPBR 1.1 surface material.\n *\n * Property names are camelCase OpenPBR inputs plus documented compatibility names\n * (https://academysoftwarefoundation.github.io/OpenPBR/). Colors are linear\n * RGB. Length-valued inputs (`transmissionDepth`, `subsurfaceRadius`) use\n * scene units; `thinFilmThickness` is in micrometres as in OpenPBR.\n *\n * ```ts\n * const gold = new OpenPBRMaterial({\n * baseColor: [1.0, 0.78, 0.34], baseMetalness: 1, specularRoughness: 0.2\n * });\n * ```\n *\n * Edit parameters in place; the next `engine.update()` detects the changes.\n */\nexport default class OpenPBRMaterial {\n /** Creates a material. Omitted properties use their documented defaults. */\n constructor(params: OpenPBRMaterialParams = {}) {\n applyMaterialParams(this, params);\n }\n\n // Base\n /** Multiplier of diffuse reflection and metallic normal-incidence color; does not scale dielectric specular, transmission or subsurface. Default `1`. */\n baseWeight: number = materialDefault('baseWeight');\n /** Diffuse/metal color. Default `[0.8, 0.8, 0.8]`. */\n baseColor: ColorRGB = materialDefault('baseColor');\n /** Oren-Nayar style roughness of the diffuse lobe. Default `0`. */\n baseDiffuseRoughness: number = materialDefault('baseDiffuseRoughness');\n /** `0` dielectric, `1` metal. Default `0`. */\n baseMetalness: number = materialDefault('baseMetalness');\n\n // Specular\n /** Dielectric specular strength (via IOR) and overall metallic Fresnel multiplier. Default `1`. */\n specularWeight: number = materialDefault('specularWeight');\n /** Dielectric specular tint and metallic F82 near-grazing tint. Default `[1, 1, 1]`. */\n specularColor: ColorRGB = materialDefault('specularColor');\n /** Microfacet roughness, `0` mirror to `1` rough; retains source perceptual roughness in `gltf` anisotropy mode. Default `0.3`. */\n specularRoughness: number = materialDefault('specularRoughness');\n /** Index of refraction. Default `1.5`. */\n specularIor: number = materialDefault('specularIor');\n /**\n * GGX anisotropy strength, 0 to 1 (requires tangents); native OpenPBR or source glTF strength according to specularAnisotropyModel. Default `0`.\n * @deprecated Use specularRoughnessAnisotropy.\n */\n specularAnisotropy: number = materialDefault('specularAnisotropy');\n\n // Transmission\n /** `1` makes the dielectric base fully transmissive (glass). Default `0`. */\n transmissionWeight: number = materialDefault('transmissionWeight');\n /** Transmission tint, or the medium color at `transmissionDepth`. Default `[1, 1, 1]`. */\n transmissionColor: ColorRGB = materialDefault('transmissionColor');\n /** Depth at which `transmissionColor` is reached; `0` tints at the surface. Default `0`. */\n transmissionDepth: number = materialDefault('transmissionDepth');\n /** Volume scattering color of the transmissive medium. Default `[0, 0, 0]`. */\n transmissionScatter: ColorRGB = materialDefault('transmissionScatter');\n /** Henyey-Greenstein anisotropy of the medium, -1 to 1. Default `0`. */\n transmissionScatterAnisotropy: number = materialDefault('transmissionScatterAnisotropy');\n /** Dispersion strength; `> 0` enables spectral rendering. Default `0`. */\n transmissionDispersionScale: number = materialDefault('transmissionDispersionScale');\n /** Abbe number controlling dispersion. Default `20`. */\n transmissionDispersionAbbeNumber: number = materialDefault('transmissionDispersionAbbeNumber');\n\n // Subsurface\n /** Subsurface scattering weight. Default `0`. */\n subsurfaceWeight: number = materialDefault('subsurfaceWeight');\n /** Subsurface color. Default `[0.8, 0.8, 0.8]`. */\n subsurfaceColor: ColorRGB = materialDefault('subsurfaceColor');\n /** Mean free path in scene units. Default `1`. */\n subsurfaceRadius: number = materialDefault('subsurfaceRadius');\n /** Per-channel multiplier of `subsurfaceRadius`. Default `[1, 0.5, 0.25]`. */\n subsurfaceRadiusScale: ColorRGB = materialDefault('subsurfaceRadiusScale');\n /**\n * Scattering anisotropy, -1 to 1. Default `0`.\n * @deprecated Use subsurfaceScatterAnisotropy.\n */\n subsurfaceAnisotropy: number = materialDefault('subsurfaceAnisotropy');\n\n // Fuzz (sheen)\n /** Fuzz (sheen) layer weight. Default `0`. */\n fuzzWeight: number = materialDefault('fuzzWeight');\n /** Fuzz reflection color; substrate attenuation uses white fuzz albedo. Default `[1, 1, 1]`. */\n fuzzColor: ColorRGB = materialDefault('fuzzColor');\n /** Fuzz roughness; imported from Three.js `sheenRoughness`. Default `0.5`. */\n fuzzRoughness: number = materialDefault('fuzzRoughness');\n\n // Coat\n /** Clear coat weight. Default `0`. */\n coatWeight: number = materialDefault('coatWeight');\n /** Squared coat transmittance at normal incidence; colors the base and emission, not coat reflection. Default `[1, 1, 1]`. */\n coatColor: ColorRGB = materialDefault('coatColor');\n /** Coat roughness. Default `0`. */\n coatRoughness: number = materialDefault('coatRoughness');\n /**\n * OpenPBR GGX coat anisotropy, 0 to 1; uses the base geometry tangent. Default `0`.\n * @deprecated Use coatRoughnessAnisotropy.\n */\n coatAnisotropy: number = materialDefault('coatAnisotropy');\n /** Coat index of refraction. Default `1.6`. */\n coatIor: number = materialDefault('coatIor');\n /** Internal-reflection darkening: 0 compensates darkening, 1 is physical; absorption remains at 0. Default `1`. */\n coatDarkening: number = materialDefault('coatDarkening');\n\n // Thin film\n /** Thin-film interference weight (iridescence). Default `0`. */\n thinFilmWeight: number = materialDefault('thinFilmWeight');\n /** Film thickness in micrometres; maximum endpoint when thinFilmMap is present. Default `0.5`. */\n thinFilmThickness: number = materialDefault('thinFilmThickness');\n /** Film index of refraction. Default `1.4`. */\n thinFilmIor: number = materialDefault('thinFilmIor');\n\n /** Thickness at a thinFilmMap sample of zero, in micrometres; ignored without a map. May exceed thinFilmThickness. Default `0`. */\n thinFilmThicknessMinimum: number = materialDefault('thinFilmThicknessMinimum');\n /** Linear channel sampled by thinFilmMap. Default `r`. */\n thinFilmMapChannel: ThinFilmMapChannel = materialDefault('thinFilmMapChannel');\n\n // Emission and geometry\n /** Emission color. Default `[1, 1, 1]`. */\n emissionColor: ColorRGB = materialDefault('emissionColor');\n /** Emitted radiance scale; `0` disables emission. Default `0`. */\n emissionLuminance: number = materialDefault('emissionLuminance');\n /**\n * Coverage, `1` opaque. See `alphaMode`. Default `1`.\n * @deprecated Use geometryOpacity.\n */\n opacity: number = materialDefault('opacity');\n /**\n * Treat the surface as an infinitely thin shell (leaves, paper). Default `false`.\n * @deprecated Use geometryThinWalled.\n */\n thinWalled: boolean = materialDefault('thinWalled');\n /** Disables geometric back-face culling for this material. Default `false`. */\n doubleSided: boolean = materialDefault('doubleSided');\n\n // Textures\n /** Base color texture, multiplied with `baseColor` using Texture color encoding. Default `undefined`. */\n map?: Texture;\n /** Standalone opacity texture sampled from its green channel, matching Three.js. Default `undefined`. */\n alphaMap?: Texture;\n /**\n * Coverage for surface and shadow rays: `opaque` ignores alpha inputs,\n * `mask` uses binary alpha testing, `blend` retains fractional coverage.\n * `auto` derives the mode from the alpha inputs. Default `auto`.\n */\n alphaMode: MaterialAlphaMode = materialDefault('alphaMode');\n /** Alpha-test threshold for `mask` mode; equality is fully covered. Default `0`. */\n alphaCutoff: number = materialDefault('alphaCutoff');\n /** Emission texture multiplied with `emissionColor`. Default `undefined`. */\n emissiveMap?: Texture;\n /** Tangent-space normal map. */\n normalMap?: Texture;\n /** Normal map strength per axis. Default `[1, 1]`. */\n normalScale: Vec2 = materialDefault('normalScale');\n /** glTF-compatible B=metalness, G=specular roughness texture. Default `undefined`. */\n metallicRoughnessMap?: Texture;\n /** Standalone roughness texture sampled from its green channel. Default `undefined`. */\n roughnessMap?: Texture;\n /** Standalone metalness texture sampled from its blue channel. Default `undefined`. */\n metalnessMap?: Texture;\n /** Multiplies transmission weight using the texture's red channel. Default `undefined`. */\n transmissionMap?: Texture;\n /** Linear thickness-range texture; R and zero minimum preserve multiplication semantics. Default `undefined`. */\n thinFilmMap?: Texture;\n\n /** Multiplies coatWeight by the linear red channel. Default `undefined`. */\n coatWeightMap?: Texture;\n /** Multiplies coatRoughness by the linear green channel. Default `undefined`. */\n coatRoughnessMap?: Texture;\n /** Multiplies fuzzColor by RGB channels using Texture color encoding. Default `undefined`. */\n fuzzColorMap?: Texture;\n /** Multiplies fuzzRoughness by the linear alpha channel. Default `undefined`. */\n fuzzRoughnessMap?: Texture;\n /** Multiplies specularWeight by the linear alpha channel. Default `undefined`. */\n specularWeightMap?: Texture;\n /** Multiplies specularColor by RGB channels using Texture color encoding. Default `undefined`. */\n specularColorMap?: Texture;\n\n\n /** Source convention for base specular anisotropy. `gltf` converts textured roughness and strength before coat roughening; `openpbr` uses native parameters. Default `openpbr`. */\n specularAnisotropyModel: 'openpbr' | 'gltf' = 'openpbr';\n /** Linear RG tangent direction and B strength multiplier for the base specular layer. Default `undefined`. */\n anisotropyMap?: Texture;\n /** Independent tangent-space coat normal in linear RGB. Without a map, coat shares the existing shading normal. Default `undefined`. */\n coatNormalMap?: Texture;\n /** XY strength of coatNormalMap; independent of normalScale. Default `[1, 1]`. */\n coatNormalScale: Vec2 = materialDefault('coatNormalScale');\n /** Base direction rotation in radians, counter-clockwise in tangent/bitangent space after RG decoding. Default `0`. */\n specularAnisotropyRotation: number = 0;\n /** Coat direction rotation in radians relative to the geometry tangent. Default `0`. */\n coatAnisotropyRotation: number = 0;\n\n /** GGX roughness anisotropy in [0, 1]; native OpenPBR or source glTF strength according to specularAnisotropyModel. Requires tangents. Default `0`. */\n get specularRoughnessAnisotropy(): number { return this.specularAnisotropy; }\n set specularRoughnessAnisotropy(value: number) { this.specularAnisotropy = value; }\n\n /** OpenPBR coat GGX roughness anisotropy in [0, 1]. Default `0`. */\n get coatRoughnessAnisotropy(): number { return this.coatAnisotropy; }\n set coatRoughnessAnisotropy(value: number) { this.coatAnisotropy = value; }\n\n /** Subsurface scattering anisotropy in [-1, 1]. Default `0`. */\n get subsurfaceScatterAnisotropy(): number { return this.subsurfaceAnisotropy; }\n set subsurfaceScatterAnisotropy(value: number) { this.subsurfaceAnisotropy = value; }\n\n /** Surface coverage; alphaMode controls testing and blending. Default `1`. */\n get geometryOpacity(): number { return this.opacity; }\n set geometryOpacity(value: number) { this.opacity = value; }\n\n /** Treat the surface as an infinitely thin shell. Default `false`. */\n get geometryThinWalled(): boolean { return this.thinWalled; }\n set geometryThinWalled(value: boolean) { this.thinWalled = value; }\n\n // Renderer extensions\n /**\n * Invisible ground that only receives shadows, composited over the\n * background. Default `false`.\n */\n shadowCatcher: boolean = materialDefault('shadowCatcher');\n /** Displays emission without scattering or illuminating other surfaces. Default `false`. */\n unlit: boolean = materialDefault('unlit');\n /** Radius of rounded-edge shading (shader bevel) in scene units. `0` disables it. Default `0`. */\n bevelSize: number = materialDefault('bevelSize');\n /** How map alpha is used: ignored, opacity or transmission. Default `opaque`. */\n mapAlphaUsage: 'opaque' | 'alpha' | 'transmission' = materialDefault('mapAlphaUsage');\n /** Procedural UV projection for textures without (good) UVs. Default `none`. */\n uvProjection: UVProjection = materialDefault('uvProjection');\n /** UV scale used with `uvProjection`. Default `[1, 1]`. */\n uvProjectionScale: Vec2 = materialDefault('uvProjectionScale');\n\n // Displacement\n /** Height texture (red channel) for displacement. Default `undefined`. */\n displacementMap?: Texture;\n /** Procedural UV projection for the displacement map. Default `none`. */\n displacementUvProjection: UVProjection = materialDefault('displacementUvProjection');\n /** UV scale used with `displacementUvProjection`. Default `1`. */\n displacementUvProjectionScale: number = materialDefault('displacementUvProjectionScale');\n /** Height multiplier in mesh-local coordinate units. Default `0.1`. */\n displacementScale: number = materialDefault('displacementScale');\n /** Ray-marching step for projective displacement. Default `0.01`. */\n displacementStepSize: number = materialDefault('displacementStepSize');\n\n}\nexport const materialAlphaModeConstants = {\n opaque: 0,\n mask: 1,\n blend: 2\n} as const;\n\nexport type ResolvedMaterialAlphaMode = Exclude<MaterialAlphaMode, 'auto'>;\n\nexport function resolveMaterialAlphaMode(\n material: OpenPBRMaterial\n): ResolvedMaterialAlphaMode {\n if (material.alphaMode !== 'auto') return material.alphaMode;\n if (material.alphaCutoff > 0) return 'mask';\n if (\n material.opacity < 1 ||\n material.mapAlphaUsage === \"alpha\" ||\n material.alphaMap\n ) {\n return 'blend';\n }\n return 'opaque';\n}\n\nexport { materialKeys, materialTextureKeys, materialGeometryKeys, materialParamKeys, getMaterialHashKey, resetMaterialTextureId } from './openPbrSchema.js';\n\n/** Optional material properties; defaults are documented on OpenPBRMaterial. */\nexport type OpenPBRMaterialParams = Partial<OpenPBRMaterial>;\n"]}
1
+ {"version":3,"file":"OpenPBRMaterial.js","sourceRoot":"","sources":["../../src/scene/OpenPBRMaterial.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Vec2 } from '../types.js';\nimport { ColorRGB } from '../types.js';\nimport type Texture from './Texture.js';\nimport type { ThinFilmMapChannel } from './thinFilmParameters.js';\nimport { materialDefault, applyMaterialParams } from './openPbrSchema.js';\n\nexport type UVProjection = 'none' | 'triplanar' | 'spherical' | 'cylindrical';\nexport type MaterialAlphaMode = 'auto' | 'opaque' | 'mask' | 'blend';\n\nexport type { MaterialTextureName } from './materialTextureNames.js';\n\n/**\n * OpenPBR 1.1 surface material.\n *\n * Property names are camelCase OpenPBR inputs plus documented compatibility names\n * (https://academysoftwarefoundation.github.io/OpenPBR/). Colors are linear\n * RGB. Length-valued inputs (`transmissionDepth`, `subsurfaceRadius`) use\n * scene units; `thinFilmThickness` is in micrometres as in OpenPBR.\n *\n * ```ts\n * const gold = new OpenPBRMaterial({\n * baseColor: [1.0, 0.78, 0.34], baseMetalness: 1, specularRoughness: 0.2\n * });\n * ```\n *\n * Edit parameters in place; the next `engine.update()` detects the changes.\n */\nexport default class OpenPBRMaterial {\n /** Creates a material. Omitted properties use their documented defaults. */\n constructor(params: OpenPBRMaterialParams = {}) {\n applyMaterialParams(this, params);\n }\n\n // Base\n /** Multiplier of diffuse reflection and metallic normal-incidence color; does not scale dielectric specular, transmission or subsurface. Default `1`. */\n baseWeight: number = materialDefault('baseWeight');\n /** Diffuse/metal color. Default `[0.8, 0.8, 0.8]`. */\n baseColor: ColorRGB = materialDefault('baseColor');\n /** Oren-Nayar style roughness of the diffuse lobe. Default `0`. */\n baseDiffuseRoughness: number = materialDefault('baseDiffuseRoughness');\n /** `0` dielectric, `1` metal. Default `0`. */\n baseMetalness: number = materialDefault('baseMetalness');\n\n // Specular\n /** Dielectric specular strength (via IOR) and overall metallic Fresnel multiplier. Default `1`. */\n specularWeight: number = materialDefault('specularWeight');\n /** Dielectric specular tint and metallic F82 near-grazing tint. Default `[1, 1, 1]`. */\n specularColor: ColorRGB = materialDefault('specularColor');\n /** Microfacet roughness, `0` mirror to `1` rough; retains source perceptual roughness in `gltf` anisotropy mode. Default `0.3`. */\n specularRoughness: number = materialDefault('specularRoughness');\n /** Index of refraction. Default `1.5`. */\n specularIor: number = materialDefault('specularIor');\n /**\n * GGX anisotropy strength, 0 to 1 (requires tangents); native OpenPBR or source glTF strength according to specularAnisotropyModel. Default `0`.\n * @deprecated Use specularRoughnessAnisotropy.\n */\n specularAnisotropy: number = materialDefault('specularAnisotropy');\n\n // Transmission\n /** `1` makes the dielectric base fully transmissive (glass). Default `0`. */\n transmissionWeight: number = materialDefault('transmissionWeight');\n /** Transmission tint, or the medium color at `transmissionDepth`. Default `[1, 1, 1]`. */\n transmissionColor: ColorRGB = materialDefault('transmissionColor');\n /** Depth at which `transmissionColor` is reached; `0` tints at the surface. Default `0`. */\n transmissionDepth: number = materialDefault('transmissionDepth');\n /** Volume scattering color of the transmissive medium. Default `[0, 0, 0]`. */\n transmissionScatter: ColorRGB = materialDefault('transmissionScatter');\n /** Henyey-Greenstein anisotropy of the medium, -1 to 1. Default `0`. */\n transmissionScatterAnisotropy: number = materialDefault('transmissionScatterAnisotropy');\n /** Dispersion strength; `> 0` enables spectral rendering. Default `0`. */\n transmissionDispersionScale: number = materialDefault('transmissionDispersionScale');\n /** Abbe number controlling dispersion. Default `20`. */\n transmissionDispersionAbbeNumber: number = materialDefault('transmissionDispersionAbbeNumber');\n\n // Subsurface\n /** Subsurface scattering weight. Default `0`. */\n subsurfaceWeight: number = materialDefault('subsurfaceWeight');\n /** Subsurface color. Default `[0.8, 0.8, 0.8]`. */\n subsurfaceColor: ColorRGB = materialDefault('subsurfaceColor');\n /** Mean free path in scene units. Default `1`. */\n subsurfaceRadius: number = materialDefault('subsurfaceRadius');\n /** Per-channel multiplier of `subsurfaceRadius`. Default `[1, 0.5, 0.25]`. */\n subsurfaceRadiusScale: ColorRGB = materialDefault('subsurfaceRadiusScale');\n /**\n * Scattering anisotropy, -1 to 1. Default `0`.\n * @deprecated Use subsurfaceScatterAnisotropy.\n */\n subsurfaceAnisotropy: number = materialDefault('subsurfaceAnisotropy');\n\n // Fuzz (sheen)\n /** Fuzz (sheen) layer weight. Default `0`. */\n fuzzWeight: number = materialDefault('fuzzWeight');\n /** Fuzz reflection color; substrate attenuation uses white fuzz albedo. Default `[1, 1, 1]`. */\n fuzzColor: ColorRGB = materialDefault('fuzzColor');\n /** Fuzz roughness; imported from Three.js `sheenRoughness`. Default `0.5`. */\n fuzzRoughness: number = materialDefault('fuzzRoughness');\n\n // Coat\n /** Clear coat weight. Default `0`. */\n coatWeight: number = materialDefault('coatWeight');\n /** Squared coat transmittance at normal incidence; colors the base and emission, not coat reflection. Default `[1, 1, 1]`. */\n coatColor: ColorRGB = materialDefault('coatColor');\n /** Coat roughness. Default `0`. */\n coatRoughness: number = materialDefault('coatRoughness');\n /**\n * OpenPBR GGX coat anisotropy, 0 to 1; uses the base geometry tangent. Default `0`.\n * @deprecated Use coatRoughnessAnisotropy.\n */\n coatAnisotropy: number = materialDefault('coatAnisotropy');\n /** Coat index of refraction. Default `1.6`. */\n coatIor: number = materialDefault('coatIor');\n /** Internal-reflection darkening: 0 compensates darkening, 1 is physical; absorption remains at 0. Default `1`. */\n coatDarkening: number = materialDefault('coatDarkening');\n\n // Thin film\n /** Thin-film interference weight (iridescence). Default `0`. */\n thinFilmWeight: number = materialDefault('thinFilmWeight');\n /** Film thickness in micrometres; maximum endpoint when thinFilmMap is present. Default `0.5`. */\n thinFilmThickness: number = materialDefault('thinFilmThickness');\n /** Film index of refraction. Default `1.4`. */\n thinFilmIor: number = materialDefault('thinFilmIor');\n\n /** Thickness at a thinFilmMap sample of zero, in micrometres; ignored without a map. May exceed thinFilmThickness. Default `0`. */\n thinFilmThicknessMinimum: number = materialDefault('thinFilmThicknessMinimum');\n /** Linear channel sampled by thinFilmMap. Default `r`. */\n thinFilmMapChannel: ThinFilmMapChannel = materialDefault('thinFilmMapChannel');\n\n // Emission and geometry\n /** Emission color. Default `[1, 1, 1]`. */\n emissionColor: ColorRGB = materialDefault('emissionColor');\n /** Emitted radiance scale; `0` disables emission. Default `0`. */\n emissionLuminance: number = materialDefault('emissionLuminance');\n /**\n * Coverage, `1` opaque. See `alphaMode`. Default `1`.\n * @deprecated Use geometryOpacity.\n */\n opacity: number = materialDefault('opacity');\n /**\n * Treat the surface as an infinitely thin shell (leaves, paper). Default `false`.\n * @deprecated Use geometryThinWalled.\n */\n thinWalled: boolean = materialDefault('thinWalled');\n /** Disables geometric back-face culling for this material. Default `false`. */\n doubleSided: boolean = materialDefault('doubleSided');\n\n // Textures\n /** Base color texture, multiplied with `baseColor` using Texture color encoding. Default `undefined`. */\n map?: Texture;\n /** Standalone opacity texture sampled from its green channel, matching Three.js. Default `undefined`. */\n alphaMap?: Texture;\n /**\n * Coverage for surface and shadow rays: `opaque` ignores alpha inputs,\n * `mask` uses binary alpha testing, `blend` retains fractional coverage.\n * `auto` derives the mode from the alpha inputs. Default `auto`.\n */\n alphaMode: MaterialAlphaMode = materialDefault('alphaMode');\n /** Alpha-test threshold for `mask` mode; equality is fully covered. Default `0`. */\n alphaCutoff: number = materialDefault('alphaCutoff');\n /** Emission texture multiplied with `emissionColor`. Default `undefined`. */\n emissiveMap?: Texture;\n /** Tangent-space normal map. */\n normalMap?: Texture;\n /** Normal map strength per axis. Default `[1, 1]`. */\n normalScale: Vec2 = materialDefault('normalScale');\n /** glTF-compatible B=metalness, G=specular roughness texture. Default `undefined`. */\n metallicRoughnessMap?: Texture;\n /** Standalone roughness texture sampled from its green channel. Default `undefined`. */\n roughnessMap?: Texture;\n /** Standalone metalness texture sampled from its blue channel. Default `undefined`. */\n metalnessMap?: Texture;\n /** Multiplies transmission weight using the texture's red channel. Default `undefined`. */\n transmissionMap?: Texture;\n /** Linear thickness-range texture; R and zero minimum preserve multiplication semantics. Default `undefined`. */\n thinFilmMap?: Texture;\n\n /** Multiplies coatWeight by the linear red channel. Default `undefined`. */\n coatWeightMap?: Texture;\n /** Multiplies coatRoughness by the linear green channel. Default `undefined`. */\n coatRoughnessMap?: Texture;\n /** Multiplies fuzzColor by RGB channels using Texture color encoding. Default `undefined`. */\n fuzzColorMap?: Texture;\n /** Multiplies fuzzRoughness by the linear alpha channel. Default `undefined`. */\n fuzzRoughnessMap?: Texture;\n /** Multiplies specularWeight by the linear alpha channel. Default `undefined`. */\n specularWeightMap?: Texture;\n /** Multiplies specularColor by RGB channels using Texture color encoding. Default `undefined`. */\n specularColorMap?: Texture;\n\n\n /** Source convention for base specular anisotropy. `gltf` converts textured roughness and strength before coat roughening; `openpbr` uses native parameters. Default `openpbr`. */\n specularAnisotropyModel: 'openpbr' | 'gltf' = 'openpbr';\n /** Linear RG tangent direction and B strength multiplier for the base specular layer. Default `undefined`. */\n anisotropyMap?: Texture;\n /** Independent tangent-space coat normal in linear RGB. Without a map, coat shares the existing shading normal. Default `undefined`. */\n coatNormalMap?: Texture;\n /** XY strength of coatNormalMap; independent of normalScale. Default `[1, 1]`. */\n coatNormalScale: Vec2 = materialDefault('coatNormalScale');\n /** Base direction rotation in radians, counter-clockwise in tangent/bitangent space after RG decoding. Default `0`. */\n specularAnisotropyRotation: number = 0;\n /** Coat direction rotation in radians relative to the geometry tangent. Default `0`. */\n coatAnisotropyRotation: number = 0;\n\n /** GGX roughness anisotropy in [0, 1]; native OpenPBR or source glTF strength according to specularAnisotropyModel. Requires tangents. Default `0`. */\n get specularRoughnessAnisotropy(): number { return this.specularAnisotropy; }\n set specularRoughnessAnisotropy(value: number) { this.specularAnisotropy = value; }\n\n /** OpenPBR coat GGX roughness anisotropy in [0, 1]. Default `0`. */\n get coatRoughnessAnisotropy(): number { return this.coatAnisotropy; }\n set coatRoughnessAnisotropy(value: number) { this.coatAnisotropy = value; }\n\n /** Subsurface scattering anisotropy in [-1, 1]. Default `0`. */\n get subsurfaceScatterAnisotropy(): number { return this.subsurfaceAnisotropy; }\n set subsurfaceScatterAnisotropy(value: number) { this.subsurfaceAnisotropy = value; }\n\n /** Surface coverage; alphaMode controls testing and blending. Default `1`. */\n get geometryOpacity(): number { return this.opacity; }\n set geometryOpacity(value: number) { this.opacity = value; }\n\n /** Treat the surface as an infinitely thin shell. Default `false`. */\n get geometryThinWalled(): boolean { return this.thinWalled; }\n set geometryThinWalled(value: boolean) { this.thinWalled = value; }\n\n // Renderer extensions\n /**\n * Invisible ground that only receives shadows, composited over the\n * background. Default `false`.\n */\n shadowCatcher: boolean = materialDefault('shadowCatcher');\n /** Displays emission without scattering or illuminating other surfaces. Default `false`. */\n unlit: boolean = materialDefault('unlit');\n /** Radius of rounded-edge shading (shader bevel) in scene units. `0` disables it. Default `0`. */\n bevelSize: number = materialDefault('bevelSize');\n /** How map alpha is used: ignored, opacity or transmission. Default `opaque`. */\n mapAlphaUsage: 'opaque' | 'alpha' | 'transmission' = materialDefault('mapAlphaUsage');\n /** Procedural UV projection for textures without (good) UVs. Default `none`. */\n uvProjection: UVProjection = materialDefault('uvProjection');\n /** UV scale used with `uvProjection`. Default `[1, 1]`. */\n uvProjectionScale: Vec2 = materialDefault('uvProjectionScale');\n\n // Displacement\n /** Height texture (red channel) for displacement. Default `undefined`. */\n displacementMap?: Texture;\n /** Procedural UV projection for the displacement map. Default `none`. */\n displacementUvProjection: UVProjection = materialDefault('displacementUvProjection');\n /** UV scale used with `displacementUvProjection`. Default `1`. */\n displacementUvProjectionScale: number = materialDefault('displacementUvProjectionScale');\n /** Height multiplier in mesh-local units; 0 removes height variation. Default `0.1`. */\n displacementScale: number = materialDefault('displacementScale');\n /** Offset added after scaling the height map, along the normal in mesh-local units; requires a displacement map. Default `0`. */\n displacementBias: number = materialDefault('displacementBias');\n /** Ray-marching step in mesh-local units; 0 automatically uses abs(displacementScale) / 32. Default `0`. */\n displacementStepSize: number = materialDefault('displacementStepSize');\n\n}\nexport const materialAlphaModeConstants = {\n opaque: 0,\n mask: 1,\n blend: 2\n} as const;\n\nexport type ResolvedMaterialAlphaMode = Exclude<MaterialAlphaMode, 'auto'>;\n\nexport function resolveMaterialAlphaMode(\n material: OpenPBRMaterial\n): ResolvedMaterialAlphaMode {\n if (material.alphaMode !== 'auto') return material.alphaMode;\n if (material.alphaCutoff > 0) return 'mask';\n if (\n material.opacity < 1 ||\n material.mapAlphaUsage === \"alpha\" ||\n material.alphaMap\n ) {\n return 'blend';\n }\n return 'opaque';\n}\n\nexport { materialKeys, materialTextureKeys, materialGeometryKeys, materialParamKeys, getMaterialHashKey, resetMaterialTextureId } from './openPbrSchema.js';\n\n/** Optional material properties; defaults are documented on OpenPBRMaterial. */\nexport type OpenPBRMaterialParams = Partial<OpenPBRMaterial>;\n"]}
@@ -311,7 +311,7 @@ export declare const openPbrSchema: readonly [{
311
311
  }, {
312
312
  readonly key: "displacementStepSize";
313
313
  readonly type: "float";
314
- readonly default: 0.01;
314
+ readonly default: 0;
315
315
  readonly category: "geometry";
316
316
  }, {
317
317
  readonly key: "thinFilmThicknessMinimum";
@@ -383,6 +383,11 @@ export declare const openPbrSchema: readonly [{
383
383
  readonly type: "vec2";
384
384
  readonly default: [1, 1];
385
385
  readonly category: "param";
386
+ }, {
387
+ readonly key: "displacementBias";
388
+ readonly type: "float";
389
+ readonly default: 0;
390
+ readonly category: "geometry";
386
391
  }];
387
392
  type Keys<T extends readonly {
388
393
  key: PropertyKey;
@@ -391,7 +396,7 @@ type Keys<T extends readonly {
391
396
  };
392
397
  export declare const materialKeys: Keys<typeof openPbrSchema>;
393
398
  export declare const materialTextureKeys: readonly ["map", "alphaMap", "emissiveMap", "normalMap", "metallicRoughnessMap", "roughnessMap", "metalnessMap", "transmissionMap", "thinFilmMap", "displacementMap", "coatWeightMap", "coatRoughnessMap", "fuzzColorMap", "fuzzRoughnessMap", "specularWeightMap", "specularColorMap", "anisotropyMap", "coatNormalMap"];
394
- export declare const materialGeometryKeys: readonly ["displacementUvProjection", "displacementUvProjectionScale", "displacementScale", "displacementStepSize"];
399
+ export declare const materialGeometryKeys: readonly ["displacementUvProjection", "displacementUvProjectionScale", "displacementScale", "displacementStepSize", "displacementBias"];
395
400
  export declare const materialParamKeys: readonly ["baseWeight", "baseColor", "baseDiffuseRoughness", "baseMetalness", "specularWeight", "specularColor", "specularRoughness", "specularIor", "specularAnisotropy", "transmissionWeight", "transmissionColor", "transmissionDepth", "transmissionScatter", "transmissionScatterAnisotropy", "transmissionDispersionScale", "transmissionDispersionAbbeNumber", "subsurfaceWeight", "subsurfaceColor", "subsurfaceRadius", "subsurfaceRadiusScale", "subsurfaceAnisotropy", "fuzzWeight", "fuzzColor", "fuzzRoughness", "coatWeight", "coatColor", "coatRoughness", "coatAnisotropy", "coatIor", "coatDarkening", "thinFilmWeight", "thinFilmThickness", "thinFilmIor", "emissionColor", "emissionLuminance", "opacity", "thinWalled", "doubleSided", "alphaMode", "alphaCutoff", "normalScale", "shadowCatcher", "unlit", "bevelSize", "mapAlphaUsage", "uvProjection", "uvProjectionScale", "thinFilmThicknessMinimum", "thinFilmMapChannel", "specularAnisotropyRotation", "coatAnisotropyRotation", "specularAnisotropyModel", "coatNormalScale"];
396
401
  export declare function materialDefault<K extends MaterialKey>(key: K): OpenPBRMaterial[K];
397
402
  export declare function applyMaterialParams(material: OpenPBRMaterial, input: OpenPBRMaterialParams): void;
@@ -85,7 +85,7 @@ export const openPbrSchema = [
85
85
  { key: 'displacementUvProjectionScale', type: 'float', default: 1, category: 'geometry' },
86
86
  { key: 'displacementMap', type: 'texture', default: undefined, category: 'texture' },
87
87
  { key: 'displacementScale', type: 'float', default: 0.1, category: 'geometry' },
88
- { key: 'displacementStepSize', type: 'float', default: 0.01, category: 'geometry' },
88
+ { key: 'displacementStepSize', type: 'float', default: 0, category: 'geometry' },
89
89
  { key: 'thinFilmThicknessMinimum', type: 'float', default: 0, category: 'param' },
90
90
  { key: 'thinFilmMapChannel', type: 'enum', default: 'r', category: 'param' },
91
91
  { key: 'coatWeightMap', type: 'texture', default: undefined, category: 'texture' },
@@ -100,6 +100,7 @@ export const openPbrSchema = [
100
100
  { key: 'specularAnisotropyModel', type: 'enum', default: 'openpbr', category: 'param' },
101
101
  { key: 'coatNormalMap', type: 'texture', default: undefined, category: 'texture' },
102
102
  { key: 'coatNormalScale', type: 'vec2', default: [1, 1], category: 'param' },
103
+ { key: 'displacementBias', type: 'float', default: 0, category: 'geometry' },
103
104
  ];
104
105
  const textureNamesMatch = true;
105
106
  void textureNamesMatch;
@@ -1 +1 @@
1
- {"version":3,"file":"openPbrSchema.js","sourceRoot":"","sources":["../../src/scene/openPbrSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { default as OpenPBRMaterial, OpenPBRMaterialParams } from './OpenPBRMaterial.js';\nimport Texture from './Texture.js';\nimport type { MaterialTextureName } from './materialTextureNames.js';\nimport { specularAnisotropyModelIndex } from './anisotropyParameters.js';\nimport { thinFilmMapChannelIndex } from './thinFilmParameters.js';\n\nexport const materialAliases = {\n specularRoughnessAnisotropy: 'specularAnisotropy',\n coatRoughnessAnisotropy: 'coatAnisotropy',\n subsurfaceScatterAnisotropy: 'subsurfaceAnisotropy',\n geometryOpacity: 'opacity',\n geometryThinWalled: 'thinWalled'\n} as const;\ntype MaterialKey = Exclude<keyof OpenPBRMaterial, keyof typeof materialAliases>;\n// Constructor and Three.js metadata share conflict handling before either mutates storage.\nexport function normalizeMaterialParams(params: OpenPBRMaterialParams | Record<string, unknown>): Record<string, unknown> {\n const normalized: Record<string, unknown> = {...params};\n for (const alias of Object.keys(materialAliases) as (keyof typeof materialAliases)[]) {\n const old = materialAliases[alias];\n if (params[alias] === undefined) continue;\n if (params[old] !== undefined && params[old] !== params[alias]) {\n throw new Error(`Conflicting OpenPBR material parameters: ${alias} and ${old}`);\n }\n Object.assign(normalized, {[old]: params[alias]});\n delete normalized[alias];\n }\n return normalized;\n}\ntype Field = {[K in MaterialKey]: {key: K; type: 'float' | 'color' | 'bool' | 'enum' | 'vec2' | 'texture'; default: OpenPBRMaterial[K]; category: 'param' | 'texture' | 'geometry'}}[MaterialKey];\n// Order is part of the existing deduplication hash contract.\nexport const openPbrSchema = [\n {key: 'baseWeight', type: 'float', default: 1, category: 'param'},\n {key: 'baseColor', type: 'color', default: [0.8, 0.8, 0.8], category: 'param'},\n {key: 'baseDiffuseRoughness', type: 'float', default: 0, category: 'param'},\n {key: 'baseMetalness', type: 'float', default: 0, category: 'param'},\n {key: 'specularWeight', type: 'float', default: 1, category: 'param'},\n {key: 'specularColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'specularRoughness', type: 'float', default: 0.3, category: 'param'},\n {key: 'specularIor', type: 'float', default: 1.5, category: 'param'},\n {key: 'specularAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionWeight', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'transmissionDepth', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionScatter', type: 'color', default: [0, 0, 0], category: 'param'},\n {key: 'transmissionScatterAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionDispersionScale', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionDispersionAbbeNumber', type: 'float', default: 20, category: 'param'},\n {key: 'subsurfaceWeight', type: 'float', default: 0, category: 'param'},\n {key: 'subsurfaceColor', type: 'color', default: [0.8, 0.8, 0.8], category: 'param'},\n {key: 'subsurfaceRadius', type: 'float', default: 1, category: 'param'},\n {key: 'subsurfaceRadiusScale', type: 'color', default: [1, 0.5, 0.25], category: 'param'},\n {key: 'subsurfaceAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'fuzzWeight', type: 'float', default: 0, category: 'param'},\n {key: 'fuzzColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'fuzzRoughness', type: 'float', default: 0.5, category: 'param'},\n {key: 'coatWeight', type: 'float', default: 0, category: 'param'},\n {key: 'coatColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'coatRoughness', type: 'float', default: 0, category: 'param'},\n {key: 'coatAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'coatIor', type: 'float', default: 1.6, category: 'param'},\n {key: 'coatDarkening', type: 'float', default: 1, category: 'param'},\n {key: 'thinFilmWeight', type: 'float', default: 0, category: 'param'},\n {key: 'thinFilmThickness', type: 'float', default: 0.5, category: 'param'},\n {key: 'thinFilmIor', type: 'float', default: 1.4, category: 'param'},\n {key: 'emissionColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'emissionLuminance', type: 'float', default: 0, category: 'param'},\n {key: 'opacity', type: 'float', default: 1, category: 'param'},\n {key: 'thinWalled', type: 'bool', default: false, category: 'param'},\n {key: 'doubleSided', type: 'bool', default: false, category: 'param'},\n {key: 'map', type: 'texture', default: undefined, category: 'texture'},\n {key: 'alphaMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'alphaMode', type: 'enum', default: 'auto', category: 'param'},\n {key: 'alphaCutoff', type: 'float', default: 0, category: 'param'},\n {key: 'emissiveMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'normalMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'normalScale', type: 'vec2', default: [1, 1], category: 'param'},\n {key: 'metallicRoughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'roughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'metalnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'transmissionMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'thinFilmMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'shadowCatcher', type: 'bool', default: false, category: 'param'},\n {key: 'unlit', type: 'bool', default: false, category: 'param'},\n {key: 'bevelSize', type: 'float', default: 0, category: 'param'},\n {key: 'mapAlphaUsage', type: 'enum', default: 'opaque', category: 'param'},\n {key: 'uvProjection', type: 'enum', default: 'none', category: 'param'},\n {key: 'uvProjectionScale', type: 'vec2', default: [1, 1], category: 'param'},\n {key: 'displacementUvProjection', type: 'enum', default: 'none', category: 'geometry'},\n {key: 'displacementUvProjectionScale', type: 'float', default: 1, category: 'geometry'},\n {key: 'displacementMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'displacementScale', type: 'float', default: 0.1, category: 'geometry'},\n {key: 'displacementStepSize', type: 'float', default: 0.01, category: 'geometry'},\n {key: 'thinFilmThicknessMinimum', type: 'float', default: 0, category: 'param'},\n {key: 'thinFilmMapChannel', type: 'enum', default: 'r', category: 'param'},\n {key: 'coatWeightMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'coatRoughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'fuzzColorMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'fuzzRoughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'specularWeightMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'specularColorMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'anisotropyMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'specularAnisotropyRotation', type: 'float', default: 0, category: 'param'},\n {key: 'coatAnisotropyRotation', type: 'float', default: 0, category: 'param'},\n {key: 'specularAnisotropyModel', type: 'enum', default: 'openpbr', category: 'param'},\n {key: 'coatNormalMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'coatNormalScale', type: 'vec2', default: [1, 1], category: 'param'},\n] as const satisfies readonly Field[];\ntype SchemaTextureName = Extract<typeof openPbrSchema[number], {type: 'texture'}>['key'];\nconst textureNamesMatch: [Exclude<MaterialTextureName, SchemaTextureName> | Exclude<SchemaTextureName, MaterialTextureName>] extends [never] ? true : never = true;\nvoid textureNamesMatch;\ntype Keys<T extends readonly {key: PropertyKey}[]> = {readonly [I in keyof T]: T[I]['key']};\ntype CategoryKeys<T extends readonly Field[], C extends Field['category'], R extends readonly MaterialKey[] = []> =\n T extends readonly [infer H extends Field, ...infer Rest extends readonly Field[]]\n ? CategoryKeys<Rest, C, H['category'] extends C ? readonly [...R, H['key']] : R> : R;\nexport const materialKeys = openPbrSchema.map(field => field.key) as unknown as Keys<typeof openPbrSchema>;\nfunction categoryKeys<C extends Field['category']>(category: C) {\n return openPbrSchema.filter(field => field.category === category).map(field => field.key) as unknown as CategoryKeys<typeof openPbrSchema, C>;\n}\nexport const materialTextureKeys = categoryKeys('texture');\nexport const materialGeometryKeys = categoryKeys('geometry');\nexport const materialParamKeys = categoryKeys('param');\nconst defaults = new Map<MaterialKey, unknown>(openPbrSchema.map(field => [field.key, field.default]));\nexport function materialDefault<K extends MaterialKey>(key: K): OpenPBRMaterial[K] {\n const value = defaults.get(key);\n return (Array.isArray(value) ? value.slice() : value) as OpenPBRMaterial[K];\n}\nexport function applyMaterialParams(material: OpenPBRMaterial, input: OpenPBRMaterialParams) {\n const params = normalizeMaterialParams(input);\n if (params.thinFilmMapChannel !== undefined) thinFilmMapChannelIndex(params.thinFilmMapChannel);\n if (params.specularAnisotropyModel !== undefined) specularAnisotropyModelIndex(params.specularAnisotropyModel);\n for (const {key} of openPbrSchema) {\n if (params[key] !== undefined) Object.assign(material, {[key]: params[key]});\n }\n}\nlet textureId = 1;\nconst textureWeakMap = new WeakMap<Texture, number>();\nexport function resetMaterialTextureId() { textureId = 1; }\nexport function getMaterialHashKey(material: OpenPBRMaterial) {\n return openPbrSchema.map(({key}) => {\n const value = material[key];\n if (value == null) return '$';\n if (value instanceof Texture) {\n if (!textureWeakMap.has(value)) textureWeakMap.set(value, textureId++);\n return `${textureWeakMap.get(value)!}@${value.version}`;\n }\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && 'length' in value) return Array.from(value as ArrayLike<number>).join('_');\n return value;\n }).join('_');\n}\n"]}
1
+ {"version":3,"file":"openPbrSchema.js","sourceRoot":"","sources":["../../src/scene/openPbrSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { default as OpenPBRMaterial, OpenPBRMaterialParams } from './OpenPBRMaterial.js';\nimport Texture from './Texture.js';\nimport type { MaterialTextureName } from './materialTextureNames.js';\nimport { specularAnisotropyModelIndex } from './anisotropyParameters.js';\nimport { thinFilmMapChannelIndex } from './thinFilmParameters.js';\n\nexport const materialAliases = {\n specularRoughnessAnisotropy: 'specularAnisotropy',\n coatRoughnessAnisotropy: 'coatAnisotropy',\n subsurfaceScatterAnisotropy: 'subsurfaceAnisotropy',\n geometryOpacity: 'opacity',\n geometryThinWalled: 'thinWalled'\n} as const;\ntype MaterialKey = Exclude<keyof OpenPBRMaterial, keyof typeof materialAliases>;\n// Constructor and Three.js metadata share conflict handling before either mutates storage.\nexport function normalizeMaterialParams(params: OpenPBRMaterialParams | Record<string, unknown>): Record<string, unknown> {\n const normalized: Record<string, unknown> = {...params};\n for (const alias of Object.keys(materialAliases) as (keyof typeof materialAliases)[]) {\n const old = materialAliases[alias];\n if (params[alias] === undefined) continue;\n if (params[old] !== undefined && params[old] !== params[alias]) {\n throw new Error(`Conflicting OpenPBR material parameters: ${alias} and ${old}`);\n }\n Object.assign(normalized, {[old]: params[alias]});\n delete normalized[alias];\n }\n return normalized;\n}\ntype Field = {[K in MaterialKey]: {key: K; type: 'float' | 'color' | 'bool' | 'enum' | 'vec2' | 'texture'; default: OpenPBRMaterial[K]; category: 'param' | 'texture' | 'geometry'}}[MaterialKey];\n// Order is part of the existing deduplication hash contract.\nexport const openPbrSchema = [\n {key: 'baseWeight', type: 'float', default: 1, category: 'param'},\n {key: 'baseColor', type: 'color', default: [0.8, 0.8, 0.8], category: 'param'},\n {key: 'baseDiffuseRoughness', type: 'float', default: 0, category: 'param'},\n {key: 'baseMetalness', type: 'float', default: 0, category: 'param'},\n {key: 'specularWeight', type: 'float', default: 1, category: 'param'},\n {key: 'specularColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'specularRoughness', type: 'float', default: 0.3, category: 'param'},\n {key: 'specularIor', type: 'float', default: 1.5, category: 'param'},\n {key: 'specularAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionWeight', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'transmissionDepth', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionScatter', type: 'color', default: [0, 0, 0], category: 'param'},\n {key: 'transmissionScatterAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionDispersionScale', type: 'float', default: 0, category: 'param'},\n {key: 'transmissionDispersionAbbeNumber', type: 'float', default: 20, category: 'param'},\n {key: 'subsurfaceWeight', type: 'float', default: 0, category: 'param'},\n {key: 'subsurfaceColor', type: 'color', default: [0.8, 0.8, 0.8], category: 'param'},\n {key: 'subsurfaceRadius', type: 'float', default: 1, category: 'param'},\n {key: 'subsurfaceRadiusScale', type: 'color', default: [1, 0.5, 0.25], category: 'param'},\n {key: 'subsurfaceAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'fuzzWeight', type: 'float', default: 0, category: 'param'},\n {key: 'fuzzColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'fuzzRoughness', type: 'float', default: 0.5, category: 'param'},\n {key: 'coatWeight', type: 'float', default: 0, category: 'param'},\n {key: 'coatColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'coatRoughness', type: 'float', default: 0, category: 'param'},\n {key: 'coatAnisotropy', type: 'float', default: 0, category: 'param'},\n {key: 'coatIor', type: 'float', default: 1.6, category: 'param'},\n {key: 'coatDarkening', type: 'float', default: 1, category: 'param'},\n {key: 'thinFilmWeight', type: 'float', default: 0, category: 'param'},\n {key: 'thinFilmThickness', type: 'float', default: 0.5, category: 'param'},\n {key: 'thinFilmIor', type: 'float', default: 1.4, category: 'param'},\n {key: 'emissionColor', type: 'color', default: [1, 1, 1], category: 'param'},\n {key: 'emissionLuminance', type: 'float', default: 0, category: 'param'},\n {key: 'opacity', type: 'float', default: 1, category: 'param'},\n {key: 'thinWalled', type: 'bool', default: false, category: 'param'},\n {key: 'doubleSided', type: 'bool', default: false, category: 'param'},\n {key: 'map', type: 'texture', default: undefined, category: 'texture'},\n {key: 'alphaMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'alphaMode', type: 'enum', default: 'auto', category: 'param'},\n {key: 'alphaCutoff', type: 'float', default: 0, category: 'param'},\n {key: 'emissiveMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'normalMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'normalScale', type: 'vec2', default: [1, 1], category: 'param'},\n {key: 'metallicRoughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'roughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'metalnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'transmissionMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'thinFilmMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'shadowCatcher', type: 'bool', default: false, category: 'param'},\n {key: 'unlit', type: 'bool', default: false, category: 'param'},\n {key: 'bevelSize', type: 'float', default: 0, category: 'param'},\n {key: 'mapAlphaUsage', type: 'enum', default: 'opaque', category: 'param'},\n {key: 'uvProjection', type: 'enum', default: 'none', category: 'param'},\n {key: 'uvProjectionScale', type: 'vec2', default: [1, 1], category: 'param'},\n {key: 'displacementUvProjection', type: 'enum', default: 'none', category: 'geometry'},\n {key: 'displacementUvProjectionScale', type: 'float', default: 1, category: 'geometry'},\n {key: 'displacementMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'displacementScale', type: 'float', default: 0.1, category: 'geometry'},\n {key: 'displacementStepSize', type: 'float', default: 0, category: 'geometry'},\n {key: 'thinFilmThicknessMinimum', type: 'float', default: 0, category: 'param'},\n {key: 'thinFilmMapChannel', type: 'enum', default: 'r', category: 'param'},\n {key: 'coatWeightMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'coatRoughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'fuzzColorMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'fuzzRoughnessMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'specularWeightMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'specularColorMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'anisotropyMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'specularAnisotropyRotation', type: 'float', default: 0, category: 'param'},\n {key: 'coatAnisotropyRotation', type: 'float', default: 0, category: 'param'},\n {key: 'specularAnisotropyModel', type: 'enum', default: 'openpbr', category: 'param'},\n {key: 'coatNormalMap', type: 'texture', default: undefined, category: 'texture'},\n {key: 'coatNormalScale', type: 'vec2', default: [1, 1], category: 'param'},\n {key: 'displacementBias', type: 'float', default: 0, category: 'geometry'},\n] as const satisfies readonly Field[];\ntype SchemaTextureName = Extract<typeof openPbrSchema[number], {type: 'texture'}>['key'];\nconst textureNamesMatch: [Exclude<MaterialTextureName, SchemaTextureName> | Exclude<SchemaTextureName, MaterialTextureName>] extends [never] ? true : never = true;\nvoid textureNamesMatch;\ntype Keys<T extends readonly {key: PropertyKey}[]> = {readonly [I in keyof T]: T[I]['key']};\ntype CategoryKeys<T extends readonly Field[], C extends Field['category'], R extends readonly MaterialKey[] = []> =\n T extends readonly [infer H extends Field, ...infer Rest extends readonly Field[]]\n ? CategoryKeys<Rest, C, H['category'] extends C ? readonly [...R, H['key']] : R> : R;\nexport const materialKeys = openPbrSchema.map(field => field.key) as unknown as Keys<typeof openPbrSchema>;\nfunction categoryKeys<C extends Field['category']>(category: C) {\n return openPbrSchema.filter(field => field.category === category).map(field => field.key) as unknown as CategoryKeys<typeof openPbrSchema, C>;\n}\nexport const materialTextureKeys = categoryKeys('texture');\nexport const materialGeometryKeys = categoryKeys('geometry');\nexport const materialParamKeys = categoryKeys('param');\nconst defaults = new Map<MaterialKey, unknown>(openPbrSchema.map(field => [field.key, field.default]));\nexport function materialDefault<K extends MaterialKey>(key: K): OpenPBRMaterial[K] {\n const value = defaults.get(key);\n return (Array.isArray(value) ? value.slice() : value) as OpenPBRMaterial[K];\n}\nexport function applyMaterialParams(material: OpenPBRMaterial, input: OpenPBRMaterialParams) {\n const params = normalizeMaterialParams(input);\n if (params.thinFilmMapChannel !== undefined) thinFilmMapChannelIndex(params.thinFilmMapChannel);\n if (params.specularAnisotropyModel !== undefined) specularAnisotropyModelIndex(params.specularAnisotropyModel);\n for (const {key} of openPbrSchema) {\n if (params[key] !== undefined) Object.assign(material, {[key]: params[key]});\n }\n}\nlet textureId = 1;\nconst textureWeakMap = new WeakMap<Texture, number>();\nexport function resetMaterialTextureId() { textureId = 1; }\nexport function getMaterialHashKey(material: OpenPBRMaterial) {\n return openPbrSchema.map(({key}) => {\n const value = material[key];\n if (value == null) return '$';\n if (value instanceof Texture) {\n if (!textureWeakMap.has(value)) textureWeakMap.set(value, textureId++);\n return `${textureWeakMap.get(value)!}@${value.version}`;\n }\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && 'length' in value) return Array.from(value as ArrayLike<number>).join('_');\n return value;\n }).join('_');\n}\n"]}
@@ -73,6 +73,7 @@ export declare class THREESync extends Sync {
73
73
  private _store;
74
74
  private _disposed;
75
75
  private _started;
76
+ private _gestureChanged;
76
77
  private _cameraState;
77
78
  private _lastUpdate?;
78
79
  private _idleControlsState?;
@@ -139,13 +139,19 @@ function convertMaterial(threeMaterial, textureCache) {
139
139
  material.emissiveMap = convertTexture(standardMaterial.emissiveMap, textureCache);
140
140
  material.normalMap = convertTexture(standardMaterial.normalMap, textureCache);
141
141
  material.displacementMap = convertTexture(standardMaterial.displacementMap, textureCache);
142
+ const displacementBias = Number(standardMaterial.displacementBias);
143
+ if (Number.isFinite(displacementBias))
144
+ material.displacementBias = displacementBias;
142
145
  const displacementScale = Number(standardMaterial.displacementScale);
143
- material.displacementScale = Number.isFinite(displacementScale)
146
+ // Three.js stores 1 even without a height map. Do not turn that inactive
147
+ // default into MuTracer's height when a texture is added after conversion.
148
+ material.displacementScale = Number.isFinite(displacementScale) &&
149
+ (standardMaterial.displacementMap != null || displacementScale !== 1)
144
150
  ? displacementScale
145
151
  : material.displacementScale;
146
152
  const displacementStepSize = Number(standardMaterial.userData.mutracerDisplacementStepSize);
147
153
  if (Number.isFinite(displacementStepSize)) {
148
- material.displacementStepSize = Math.max(0.000001, displacementStepSize);
154
+ material.displacementStepSize = Math.max(0, displacementStepSize);
149
155
  }
150
156
  const uvProjection = standardMaterial.userData
151
157
  .mutracerDisplacementUvProjection;
@@ -255,6 +261,12 @@ function convertMaterial(threeMaterial, textureCache) {
255
261
  : fallback;
256
262
  };
257
263
  if (openPbr) {
264
+ for (const key of ['displacementScale', 'displacementBias', 'displacementStepSize']) {
265
+ const value = openPbr[key];
266
+ if (typeof value === 'number' && Number.isFinite(value)) {
267
+ material[key] = key === 'displacementStepSize' ? Math.max(0, value) : value;
268
+ }
269
+ }
258
270
  if (openPbr.specularAnisotropyModel !== undefined) {
259
271
  specularAnisotropyModelIndex(openPbr.specularAnisotropyModel);
260
272
  material.specularAnisotropyModel = openPbr.specularAnisotropyModel;
@@ -944,7 +956,8 @@ function orbitControlsState(camera, controls) {
944
956
  const state = [camera, controls.update, camera.parent, ...camera.position.toArray(), ...camera.quaternion.toArray(),
945
957
  ...camera.scale.toArray(), ...camera.up.toArray(), camera.zoom, ...(camera.parent?.matrixWorld.elements ?? [])];
946
958
  for (const [key, value] of Object.entries(controls)) {
947
- if (key.startsWith('_'))
959
+ // Pointer-down changes the gesture state without changing the view.
960
+ if (key.startsWith('_') || key === 'state')
948
961
  continue;
949
962
  const vector = value;
950
963
  if (vector?.isVector3)
@@ -971,10 +984,11 @@ export class THREESync extends Sync {
971
984
  _store = createConversionStore();
972
985
  _disposed = false;
973
986
  _started = false;
987
+ _gestureChanged = false;
974
988
  _cameraState = [];
975
989
  _lastUpdate;
976
990
  _idleControlsState;
977
- _start = () => { this._started = true; this._idleControlsState = undefined; this.setActive(true); };
991
+ _start = () => { this._started = true; this._gestureChanged = false; };
978
992
  _end = () => { this._started = false; };
979
993
  /** Subscribes to controls without converting sources or modifying the engine. */
980
994
  constructor(engine, _options = {}) {
@@ -996,7 +1010,9 @@ export class THREESync extends Sync {
996
1010
  let moving = false, settled = false;
997
1011
  if (controls && this._options.updateControls !== false) {
998
1012
  const orbitInputs = orbitControlsState(sourceCamera, controls);
999
- const idle = !this._started && !controls.autoRotate && orbitInputs && this._idleControlsState && snapshotsEqual(orbitInputs, this._idleControlsState);
1013
+ // OrbitControls applies pointer movement before synchronization. Keep a
1014
+ // settled camera untouched on pointer-down until its inputs actually change.
1015
+ const idle = (!this._started || !this._gestureChanged) && !controls.autoRotate && orbitInputs && this._idleControlsState && snapshotsEqual(orbitInputs, this._idleControlsState);
1000
1016
  if (!idle) {
1001
1017
  this._idleControlsState = undefined;
1002
1018
  const position = sourceCamera?.position.clone(), rotation = sourceCamera?.quaternion.clone(), zoom = sourceCamera?.zoom;
@@ -1063,7 +1079,9 @@ export class THREESync extends Sync {
1063
1079
  this._camera.copy(next);
1064
1080
  this.engine.camera = this._camera;
1065
1081
  }
1066
- this.setActive(this._started || moving || this._store.poseChanged || (cameraChanged && !settled));
1082
+ if (this._started && (moving || cameraChanged))
1083
+ this._gestureChanged = true;
1084
+ this.setActive((this._started && this._gestureChanged) || moving || this._store.poseChanged || (cameraChanged && !settled));
1067
1085
  }
1068
1086
  /** Removes control subscriptions and caches. The last converted objects remain usable. */
1069
1087
  dispose() {