@meimu/mutracer 0.1.0 → 0.1.1

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.
@@ -11,7 +11,7 @@ export class CommittedScene {
11
11
  instanceVersions = new WeakMap();
12
12
  value;
13
13
  copying = new WeakSet();
14
- copy(value) {
14
+ copy(value, retainGeometryBvh = false) {
15
15
  if (!value || typeof value !== 'object')
16
16
  return value;
17
17
  const object = value;
@@ -74,7 +74,7 @@ export class CommittedScene {
74
74
  }
75
75
  }
76
76
  else
77
- cloned[key] = value instanceof Texture && key === 'source' ? value.source : this.copy(value[key]);
77
+ cloned[key] = value instanceof Texture && key === 'source' ? value.source : this.copy(value[key], retainGeometryBvh);
78
78
  }
79
79
  }
80
80
  finally {
@@ -89,7 +89,12 @@ export class CommittedScene {
89
89
  });
90
90
  if (value instanceof Geometry) {
91
91
  this.geometryVersions.set(value, value.version);
92
- setGeometryBvh(cloned, getGeometryBvh(value));
92
+ // The renderer builds/refits BVHs on the committed geometry. A source
93
+ // without a precomputed BVH must not erase that tree on every pose edit.
94
+ // Full scene replacements cannot rely on an incremental renderer refit.
95
+ const sourceBvh = getGeometryBvh(value);
96
+ if (sourceBvh || !retainGeometryBvh)
97
+ setGeometryBvh(cloned, sourceBvh);
93
98
  copyGeometryChangeState(value, cloned);
94
99
  }
95
100
  return cloned;
@@ -102,12 +107,12 @@ export class CommittedScene {
102
107
  for (const mesh of changes.transforms) {
103
108
  this.copy(mesh.transform);
104
109
  if (mesh instanceof InstancedMesh)
105
- this.copy(mesh);
110
+ this.copy(mesh, true);
106
111
  }
107
112
  for (const mesh of changes.colors)
108
- this.copy(mesh);
113
+ this.copy(mesh, true);
109
114
  for (const geometry of changes.geometries)
110
- this.copy(geometry);
115
+ this.copy(geometry, true);
111
116
  for (const material of changes.materials)
112
117
  this.copy(material);
113
118
  if (changes.lights)
@@ -1 +1 @@
1
- {"version":3,"file":"committedScene.js","sourceRoot":"","sources":["../../src/renderer/committedScene.ts"],"names":[],"mappings":"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","sourcesContent":["import {createCanvas} from '../util/canvas.js';\nimport Geometry from '../scene/Geometry.js';\nimport InstancedMesh from '../scene/InstancedMesh.js';\nimport Texture from '../scene/Texture.js';\nimport { copyGeometryChangeState } from '../scene/geometryChanges.js';\nimport { getGeometryBvh, setGeometryBvh } from './geometryBvh.js';\nimport type { Scene } from '../scene/Scene.js';\nimport type { SceneChanges } from './sceneChangeDetector.js';\n\n/** Stable engine-owned mirrors isolate deferred GPU uploads from external edits. */\nexport class CommittedScene {\n private readonly copies = new WeakMap<object, any>();\n private readonly geometryVersions = new WeakMap<Geometry, number>();\n private readonly instanceVersions = new WeakMap<InstancedMesh, {transformsVersion: number; transforms: Float32Array; colorsVersion: number; colors?: Float32Array}>();\n private value?: Scene;\n private readonly copying = new WeakSet<object>();\n\n private copy<T>(value: T): T {\n if (!value || typeof value !== 'object') return value;\n const object = value as object;\n if ((typeof HTMLImageElement !== 'undefined' && object instanceof HTMLImageElement) || (typeof HTMLCanvasElement !== 'undefined' && object instanceof HTMLCanvasElement) ||\n (typeof OffscreenCanvas !== 'undefined' && object instanceof OffscreenCanvas) ||\n (typeof ImageBitmap !== 'undefined' && object instanceof ImageBitmap)) {\n const source = object as HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap;\n let canvas = this.copies.get(object) as HTMLCanvasElement | OffscreenCanvas | undefined;\n if (!canvas) { canvas = createCanvas(source.width, source.height); this.copies.set(object, canvas); }\n canvas.width = source.width; canvas.height = source.height;\n const context = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;\n if (!context) throw new Error('Texture snapshot context is not available.');\n context.drawImage(source, 0, 0);\n return canvas as T;\n }\n if (typeof Node !== 'undefined' && object instanceof Node) return value;\n if (value instanceof Geometry && this.geometryVersions.get(value) === value.version) return this.copies.get(value);\n if (ArrayBuffer.isView(value)) {\n const previous = this.copies.get(object);\n if (previous?.constructor === value.constructor && previous.length === (value as any).length) {\n previous.set(value); return previous;\n }\n const cloned = (value as any).slice(); this.copies.set(object, cloned); return cloned;\n }\n if (value instanceof ArrayBuffer) return value.slice(0) as T;\n let cloned = this.copies.get(object);\n if (!cloned) { cloned = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value)); this.copies.set(object, cloned); }\n if (this.copying.has(object)) return cloned;\n this.copying.add(object);\n try {\n for (const key of Object.keys(value)) {\n if (value instanceof InstancedMesh && (key === 'transforms' || key === 'colors')) {\n const previous = this.instanceVersions.get(value);\n const version = key === 'transforms' ? value.transformsVersion : value.colorsVersion;\n const priorVersion = key === 'transforms' ? previous?.transformsVersion : previous?.colorsVersion;\n const array = value[key];\n if (!previous || priorVersion !== version || previous[key] !== array) {\n // Each batch owns its snapshot even when callers share source storage.\n if (!array) cloned[key] = undefined;\n else if (previous?.[key] === array && cloned[key]?.length === array.length) cloned[key].set(array);\n else cloned[key] = array.slice();\n }\n } else cloned[key] = value instanceof Texture && key === 'source' ? value.source : this.copy((value as any)[key]);\n }\n } finally { this.copying.delete(object); }\n if (Array.isArray(value)) cloned.length = value.length;\n if (value instanceof InstancedMesh) this.instanceVersions.set(value, {\n transformsVersion: value.transformsVersion, transforms: value.transforms,\n colorsVersion: value.colorsVersion, colors: value.colors\n });\n if (value instanceof Geometry) {\n this.geometryVersions.set(value, value.version);\n setGeometryBvh(cloned, getGeometryBvh(value));\n copyGeometryChangeState(value, cloned);\n }\n return cloned;\n }\n\n mirror<T extends object>(source: T): T { return this.copies.get(source); }\n\n capture(scene: Scene, changes: SceneChanges): Scene {\n if (!this.value || changes.meshes === 'full') this.value = { meshes: this.copy(scene.meshes), lights: this.copy(scene.lights), background: this.copy(scene.background) };\n else {\n for (const mesh of changes.transforms) {\n this.copy(mesh.transform);\n if (mesh instanceof InstancedMesh) this.copy(mesh);\n }\n for (const mesh of changes.colors) this.copy(mesh);\n for (const geometry of changes.geometries) this.copy(geometry);\n for (const material of changes.materials) this.copy(material);\n if (changes.lights) this.value.lights = this.copy(scene.lights);\n if (changes.background) this.value.background = this.copy(scene.background);\n }\n return this.value;\n }\n}\n"]}
1
+ {"version":3,"file":"committedScene.js","sourceRoot":"","sources":["../../src/renderer/committedScene.ts"],"names":[],"mappings":"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","sourcesContent":["import {createCanvas} from '../util/canvas.js';\nimport Geometry from '../scene/Geometry.js';\nimport InstancedMesh from '../scene/InstancedMesh.js';\nimport Texture from '../scene/Texture.js';\nimport { copyGeometryChangeState } from '../scene/geometryChanges.js';\nimport { getGeometryBvh, setGeometryBvh } from './geometryBvh.js';\nimport type { Scene } from '../scene/Scene.js';\nimport type { SceneChanges } from './sceneChangeDetector.js';\n\n/** Stable engine-owned mirrors isolate deferred GPU uploads from external edits. */\nexport class CommittedScene {\n private readonly copies = new WeakMap<object, any>();\n private readonly geometryVersions = new WeakMap<Geometry, number>();\n private readonly instanceVersions = new WeakMap<InstancedMesh, {transformsVersion: number; transforms: Float32Array; colorsVersion: number; colors?: Float32Array}>();\n private value?: Scene;\n private readonly copying = new WeakSet<object>();\n\n private copy<T>(value: T, retainGeometryBvh = false): T {\n if (!value || typeof value !== 'object') return value;\n const object = value as object;\n if ((typeof HTMLImageElement !== 'undefined' && object instanceof HTMLImageElement) || (typeof HTMLCanvasElement !== 'undefined' && object instanceof HTMLCanvasElement) ||\n (typeof OffscreenCanvas !== 'undefined' && object instanceof OffscreenCanvas) ||\n (typeof ImageBitmap !== 'undefined' && object instanceof ImageBitmap)) {\n const source = object as HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap;\n let canvas = this.copies.get(object) as HTMLCanvasElement | OffscreenCanvas | undefined;\n if (!canvas) { canvas = createCanvas(source.width, source.height); this.copies.set(object, canvas); }\n canvas.width = source.width; canvas.height = source.height;\n const context = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;\n if (!context) throw new Error('Texture snapshot context is not available.');\n context.drawImage(source, 0, 0);\n return canvas as T;\n }\n if (typeof Node !== 'undefined' && object instanceof Node) return value;\n if (value instanceof Geometry && this.geometryVersions.get(value) === value.version) return this.copies.get(value);\n if (ArrayBuffer.isView(value)) {\n const previous = this.copies.get(object);\n if (previous?.constructor === value.constructor && previous.length === (value as any).length) {\n previous.set(value); return previous;\n }\n const cloned = (value as any).slice(); this.copies.set(object, cloned); return cloned;\n }\n if (value instanceof ArrayBuffer) return value.slice(0) as T;\n let cloned = this.copies.get(object);\n if (!cloned) { cloned = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value)); this.copies.set(object, cloned); }\n if (this.copying.has(object)) return cloned;\n this.copying.add(object);\n try {\n for (const key of Object.keys(value)) {\n if (value instanceof InstancedMesh && (key === 'transforms' || key === 'colors')) {\n const previous = this.instanceVersions.get(value);\n const version = key === 'transforms' ? value.transformsVersion : value.colorsVersion;\n const priorVersion = key === 'transforms' ? previous?.transformsVersion : previous?.colorsVersion;\n const array = value[key];\n if (!previous || priorVersion !== version || previous[key] !== array) {\n // Each batch owns its snapshot even when callers share source storage.\n if (!array) cloned[key] = undefined;\n else if (previous?.[key] === array && cloned[key]?.length === array.length) cloned[key].set(array);\n else cloned[key] = array.slice();\n }\n } else cloned[key] = value instanceof Texture && key === 'source' ? value.source : this.copy((value as any)[key], retainGeometryBvh);\n }\n } finally { this.copying.delete(object); }\n if (Array.isArray(value)) cloned.length = value.length;\n if (value instanceof InstancedMesh) this.instanceVersions.set(value, {\n transformsVersion: value.transformsVersion, transforms: value.transforms,\n colorsVersion: value.colorsVersion, colors: value.colors\n });\n if (value instanceof Geometry) {\n this.geometryVersions.set(value, value.version);\n // The renderer builds/refits BVHs on the committed geometry. A source\n // without a precomputed BVH must not erase that tree on every pose edit.\n // Full scene replacements cannot rely on an incremental renderer refit.\n const sourceBvh = getGeometryBvh(value);\n if (sourceBvh || !retainGeometryBvh) setGeometryBvh(cloned, sourceBvh);\n copyGeometryChangeState(value, cloned);\n }\n return cloned;\n }\n\n mirror<T extends object>(source: T): T { return this.copies.get(source); }\n\n capture(scene: Scene, changes: SceneChanges): Scene {\n if (!this.value || changes.meshes === 'full') this.value = { meshes: this.copy(scene.meshes), lights: this.copy(scene.lights), background: this.copy(scene.background) };\n else {\n for (const mesh of changes.transforms) {\n this.copy(mesh.transform);\n if (mesh instanceof InstancedMesh) this.copy(mesh, true);\n }\n for (const mesh of changes.colors) this.copy(mesh, true);\n for (const geometry of changes.geometries) this.copy(geometry, true);\n for (const material of changes.materials) this.copy(material);\n if (changes.lights) this.value.lights = this.copy(scene.lights);\n if (changes.background) this.value.background = this.copy(scene.background);\n }\n return this.value;\n }\n}\n"]}
@@ -120,6 +120,10 @@ ${intelMac ? doubleTransmissionCheckCode : ''}
120
120
  bool isShadowCatcher = mat.materialType == SHADOW_CATCHER;
121
121
  if (isShadowCatcher) {
122
122
  if (aa.fromEye) {
123
+
124
+
125
+ outAux.albedo = vec3(1.0);
126
+ outAux.normal = dot(si.normal, aa.ray.d) > 0.0 ? -si.normal : si.normal;
123
127
  si.hit = false;
124
128
  outAux.isShadowCatcher = true;
125
129
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rayTrace.frag.js","sourceRoot":"","sources":["../../../src/renderer/glsl/rayTrace.frag.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_PATH_EVENTS } from '../pathLimits.js';\nimport cameraRayGlsl from './chunks/cameraRay.glsl.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 commonGlsl from './chunks/common.glsl.js';\nimport rayTraceCore from './chunks/rayTraceCore.glsl.js';\nimport textureLinear from './chunks/textureLinear.glsl.js';\nimport materialBufferGlsl from './chunks/materialBuffer.glsl.js';\nimport intersect from './chunks/intersect.glsl.js';\nimport randGlsl from './chunks/rand.glsl.js';\nimport random from './chunks/random.glsl.js';\nimport envMap from './chunks/envMap.glsl.js';\nimport sampleMaterialGlsl from './chunks/sampleMaterial.glsl.js';\nimport sampleLightGlsl from './chunks/sampleLight.glsl.js';\nimport { FragmentShader, glsl, Shader } from '../shader.js';\nimport openPbrBsdfGlsl from './chunks/openPbrBsdf.glsl.js';\nimport samplingGlsl from './chunks/sampling.glsl.js';\nimport { isIntelMac } from '../util.js';\nimport uvProjectionGlsl from './chunks/uvProjection.glsl.js';\n\nconst doubleTransmissionCheckCode = glsl`\n#if defined(HAS_TRANSMISSION) || defined(HAS_ALPHA)\n if (!si.isTransparency && BOUNCES < 16 && depth * 2 > BOUNCES) {\n path.abort = true;\n return;\n }\n#endif\n`;\n\n// bounce fix on intel mac\n// The intel mac only have one bounce if the loop is not unrolled\n// But windows will have extremely slow shader compilation if the loop is unrolled\n// So here the code are injected separately.\nconst bounceCodeOnIntelMac = glsl`\n// Mark loop can be unrolled\nfor(int _idx_ = 0; _idx_ < BOUNCES; _idx_++) {{\n if (path.abort) {\n return vec4(path.li, path.alpha);\n }\n bounce(path, depth, si, mat, outAux);\n}}\n`;\nconst bounceCodeOnOther = glsl`\nbool isOpaque = !si.isTransparency;\nint activeBounceLimit = min(BOUNCES, bounceLimit);\nfor(int idx = 0; idx < min(BOUNCES * 3, ${MAX_PATH_EVENTS}); idx++) {\n if (isOpaque && idx > activeBounceLimit) {\n path.abort = true;\n }\n if (path.abort) {\n return vec4(path.li, path.alpha);\n }\n bounce(path, depth, si, mat, outAux);\n}\n`;\n\nexport default function createRayTraceFragmentShader() {\n const intelMac = isIntelMac();\n return new FragmentShader({\n name: 'rayTraceFrag',\n outputs: ['color', 'albedo', 'normal', 'moments'],\n defines: {\n BOUNCES: 2\n },\n uniforms: {\n cameraApertureRadius: Shader.uniform('float', 0),\n cameraFocusDistance: Shader.uniform('float', 10),\n\n viewProjectionInv: Shader.uniform('mat4'),\n viewInv: Shader.uniform('mat4'),\n transmissiveOutputAlphaMin: Shader.uniform('float'),\n openPbrEnergyLut: Shader.uniform('sampler2D'),\n\n viewportSize: Shader.uniform('vec2'),\n\n wavelength: Shader.uniform('float', 0),\n bevelEnabled: Shader.uniform('bool', true),\n dispersionWavelengths: Shader.uniform('int', 3),\n bounceLimit: Shader.uniform('int', 8)\n },\n includes: [rayTraceCore, materialBufferGlsl, intersect, random, envMap],\n main: glsl`\n${commonGlsl}\n\n${randGlsl}\n${random.main}\n\n${textureLinear}\n\n${rayTraceCore.main}\n\n${uvProjectionGlsl}\n\n${materialBufferGlsl.main}\n${intersect.main}\n${envMap.main}\n\n${samplingGlsl}\n${openPbrBsdfGlsl}\n\nfloat heroWavelength;\nfloat pathWavelength;\n\n${sampleLightGlsl}\n${sampleMaterialGlsl}\n\n${cameraRayGlsl}\nstruct RayTraceAuxiliary {\n // Denoising guides and auxiliary pixel classification.\n vec3 albedo;\n vec3 normal;\n bool isBlank;\n // The primary ray hit a shadow catcher, whose sample the catcher pass owns.\n bool isShadowCatcher;\n};\n\nvoid bounce(inout Path path, inout int depth, inout SurfaceInteraction si, inout OpenPbrResolvedMaterial mat, inout RayTraceAuxiliary outAux) {\n samplingBounce = uint(max(depth, 0));\n // The bounce will be doubled on the transmission material.\n // For other materials. We will quite early\n // TODO It is only used on Mac with AMD card. If I enabled it on windows.\n // It will cause shader compilation too slow.\n${intelMac ? doubleTransmissionCheckCode : ''}\n\n bool isShadowCatcher = mat.materialType == SHADOW_CATCHER;\n if (isShadowCatcher) {\n if (path.fromEye) {\n si.hit = false;\n outAux.isShadowCatcher = true;\n }\n else {\n // The catcher belongs only to the shadow-mask pass. Keep its default\n // white material out of glossy and transmission reflections.\n#ifdef USE_ENVMAP\n if (path.isNextRefraction) {\n // The invisible catcher can sit between a transmitted camera ray and\n // the grounded skybox. Use the displayed background here instead of\n // terminating early with the raw lighting environment.\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n float backgroundMisWeight = path.scatteringIsDelta ? 1.0\n : sampling_power_heuristic(path.scatteringPdf,\n envMapPdf(cartesianToEquirect(path.ray.d)) * envLightSelectionPmf);\n path.li += backgroundMisWeight * path.beta * bg.rgb * bg.a;\n path.alpha = max(path.alpha, bg.a);\n }\n else {\n vec2 envUv = cartesianToEquirect(path.ray.d);\n vec3 emission = textureLinear(envMap, envUv).rgb;\n emission = sampling_spectral_rgb_filter(emission, pathWavelength);\n path.li += path.beta * emission *\n sampling_power_heuristic(\n path.scatteringPdf,\n envMapPdf(envUv) * envLightSelectionPmf\n );\n }\n#else\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n path.li += path.beta * bg.rgb * bg.a;\n#endif\n path.abort = true;\n return;\n }\n }\n\n if (!si.hit) {\n // Sample from background if first ray not hit anything.\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n // Dispersion renders one additive pass per RGB wavelength. Background\n // radiance has to be split in the same way as lights and emissive\n // surfaces, otherwise the full skybox is accumulated three times.\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n if (path.fromEye) {\n path.li = 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.01;\n }\n else {\n Ray cam = initCameraRay(v_Texcoord + pixelJitter());\n bool towardsCam = dot(cam.d, path.ray.d) > 0.0;\n if (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 !defined(USE_ENVMAP)\n // TODO treat background as a light source\n if (towardsCam) {\n path.li = path.beta * sampling_power_heuristic(path.scatteringPdf, 1.0) * bg.rgb;\n }\n#endif\n }\n path.abort = true;\n return;\n }\n\n bool sampleIgnored = false;\n if ((mat.materialType == STANDARD || mat.materialType == UNLIT)) {\n if (transmissiveOutputAlphaMin < 1.0) {\n // TODO increase the alpha when there are more bounces.\n float alpha = max(1.0 - mat.transmissionWeight, 1.0 - pow(1.0 - transmissiveOutputAlphaMin, float(depth)));\n path.alpha = max(alpha, path.alpha);\n }\n float alpha = mat.alpha;\n if (alpha < 1.0) {\n // Alpha is sampled as stochastic coverage. An accepted surface branch\n // must use full radiance weight; multiplying by alpha again is biased.\n path.alpha = max(path.alpha, alpha);\n if (hashedSample(3u) > alpha) {\n sampleIgnored = true;\n }\n }\n if (!sampleIgnored) {\n SampleMaterial(si, mat, path);\n }\n else {\n path.ray.o = si.position + path.ray.d * EPS;\n }\n }\n\n if (!si.albedoSampled && !sampleIgnored) {\n // Select denoising guides.\n if ((mat.materialType == STANDARD || mat.materialType == UNLIT) &&\n !(mat.transmissionWeight > 0.5 ||\n (mat.specularRoughness <= 0.02 && mat.baseMetalness > 0.98))) {\n // Alpha-blended decals can be skipped stochastically. In that case keep\n // looking for the first useful rough-surface guide behind the decal,\n // even though it is reached after depth zero.\n outAux.albedo = mat.baseColor;\n outAux.normal = dot(si.normal, si.position - vec3(viewInv[3])) > 0.0\n ? -si.normal\n : si.normal;\n si.albedoSampled = true;\n }\n if (depth > 4) {\n si.albedoSampled = true;\n }\n }\n\n\n // Russian Roulette sampling\n#ifdef USE_RR\n if (depth >= RR_MAX && !sampleIgnored) {\n // Max-component survival (pbrt-v4) avoids over-amplifying saturated paths.\n float maxComponent = max(max(path.beta.x, path.beta.y), path.beta.z);\n float q = 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#endif\n\n if (path.abort) {\n return;\n }\n\n // Next ray hit\n intersectScene(path.ray, si, true);\n getMaterial(mat, si.materialIndex, si.uv, si.uv1, si.instanceIndex);\n si.isTransparency = mat.transmissionWeight > 0.001 || mat.subsurfaceWeight > 0.001 || mat.alpha < 0.999;\n\n // Dynamic preview only becomes spectral when the primary visible surface\n // actually disperses transmitted light. Direct background and ordinary\n // opaque materials stay full RGB and therefore remain noise-free.\n if (dispersionWavelengths == 1 && si.hit && mat.transmissionWeight > 0.001 && mat.transmissionDispersionScale > 0.001) {\n pathWavelength = heroWavelength;\n }\n if (mat.transmissionWeight > 0.001 && pathWavelength > 0.0) {\n mat.specularIor = sampling_cauchy_ior(\n pathWavelength,\n mat.specularIor,\n mat.transmissionDispersionScale,\n mat.dispersionAbbeNumber\n );\n }\n\n if (sampleIgnored) {\n return;\n }\n\n bool isAlphaOccluded = hashedSample(3u) < mat.alpha;\n#ifdef NUM_LIGHTS\n // Use light if hit the light\n LightSampleRec lightSample;\n for (int i = 0; i < NUM_LIGHTS; i++) {\n // Fetch light Data\n Light light = fetchLight(i);\n float d = intersectLight(path.ray, light, lightSample);\n bool occluded = si.hit && d > distance(si.position, path.ray.o);\n if (d != INF && !(occluded && isAlphaOccluded)) {\n lightSample.pdf *= light.selectionPmf;\n float misWeight = sampling_power_heuristic(path.scatteringPdf, lightSample.pdf);\n lightSample.emission = sampling_spectral_rgb_filter(lightSample.emission, pathWavelength);\n path.li += misWeight * path.beta * lightSample.emission;\n // TODO set alpha to 1.0 will cause some sharp edges\n path.alpha = 1.0;\n path.abort = true;\n }\n }\n#endif\n\n#ifdef USE_ENVMAP\n if (!(si.hit && isAlphaOccluded)) {\n if (path.isNextRefraction) {\n // Camera-visible transmission must use the displayed background. The\n // raw environment remains the lighting source for non-transmission rays.\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n float backgroundMisWeight = path.scatteringIsDelta ? 1.0\n : sampling_power_heuristic(path.scatteringPdf,\n envMapPdf(cartesianToEquirect(path.ray.d)) * envLightSelectionPmf);\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 vec2 uv = cartesianToEquirect(path.ray.d);\n vec3 emission = textureLinear(envMap, uv).rgb;\n float misWeight = sampling_power_heuristic(\n path.scatteringPdf,\n envMapPdf(uv) * envLightSelectionPmf\n );\n emission = sampling_spectral_rgb_filter(emission, pathWavelength);\n path.li += path.beta * emission * misWeight;\n\n // TODO force the min alpha to be 1 when showing envmap as background\n #if !defined(USE_BACKGROUND)\n path.alpha = 1.0;\n #endif\n path.abort = true;\n return;\n }\n#endif\n\n depth++;\n}\n\n\nvec2 bevelCubicSample(float radius, float xi) {\n float x = 0.25;\n for (int iteration = 0; iteration < 10; iteration++) {\n float x2 = x * x;\n float x3 = x2 * x;\n float oneMinusX = 1.0 - x;\n float value = 10.0 * x2 - 20.0 * x3 + 15.0 * x2 * x2 -\n 4.0 * x2 * x3 - xi;\n float 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 float diskRadius = x * radius;\n return vec2(\n diskRadius,\n sqrt(max(radius * radius - diskRadius * diskRadius, 0.0))\n );\n}\n\nfloat bevelCubicPdf(float radius, float distanceToSource) {\n if (distanceToSource >= radius) return 0.0;\n float falloff = radius - distanceToSource;\n return falloff * falloff * falloff;\n}\n\n// Shader bevel. Usage: docs/materials.md (Renderer extensions)\nvec3 getBevelNormal(SurfaceInteraction source, float radius) {\n vec3 geometricNormal = source.faceNormal;\n if (dot(geometricNormal, source.vertexNormal) < 0.0) geometricNormal = -geometricNormal;\n vec3 tangent;\n vec3 bitangent;\n sampling_orthonormal_basis(geometricNormal, tangent, bitangent);\n vec3 normalSum = vec3(0.0);\n\n for (int sampleIndex = 0; sampleIndex < BEVEL_SAMPLES; sampleIndex++) {\n vec2 bevelRnd = sample2D(CAMERA_BEVEL_BASE + uint(sampleIndex));\n float axisSample = bevelRnd.x;\n float radialSample = bevelRnd.y;\n vec3 diskNormal = geometricNormal;\n vec3 diskTangent = tangent;\n vec3 diskBitangent = bitangent;\n float pickPdfNormal = 0.5;\n float pickPdfTangent = 0.25;\n float pickPdfBitangent = 0.25;\n if (axisSample < 0.5) {\n axisSample *= 2.0;\n }\n else if (axisSample < 0.75) {\n vec3 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 vec3 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 vec2 diskSample = bevelCubicSample(radius, radialSample);\n float phi = TWOPI * axisSample;\n vec3 diskOffset = diskSample.x *\n (cos(phi) * diskTangent + sin(phi) * diskBitangent);\n Ray ray;\n initRay(\n ray,\n source.position + diskNormal * diskSample.y + diskOffset,\n -diskNormal\n );\n SurfaceInteraction hit;\n intersectSceneBevel(\n ray,\n hit,\n source.instanceIndex,\n max(2.0 * diskSample.y, EPS)\n );\n if (!hit.hit) continue;\n\n float pdfNormal = pickPdfNormal * abs(dot(diskNormal, hit.faceNormal));\n float pdfTangent = pickPdfTangent * abs(dot(diskTangent, hit.faceNormal));\n float pdfBitangent = pickPdfBitangent * abs(dot(diskBitangent, hit.faceNormal));\n float 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 vec3 bevelVertexNormal = normalize(normalSum);\n vec3 result = source.normal + bevelVertexNormal - source.vertexNormal;\n return dot(result, result) > 1e-12 ? normalize(result) : source.normal;\n}\n\nvec4 integrateOnePathSample(SurfaceInteraction si, Ray ray, OpenPbrResolvedMaterial mat, inout RayTraceAuxiliary outAux) {\n Path path;\n path.ray = ray;\n path.li = vec3(0);\n path.alpha = transmissiveOutputAlphaMin < 1.0 ? 0.0 : 1.0;\n path.beta = vec3(1.0);\n path.fromEye = true;\n path.scatteringPdf = 0.0;\n path.scatteringIsDelta = false;\n path.isNextRefraction = false;\n path.abort = false;\n int depth = 0;\n${intelMac ? bounceCodeOnIntelMac : bounceCodeOnOther}\n return vec4(path.li, path.alpha);\n}\n\nvec4 integrateOnePath(SurfaceInteraction si, Ray ray, OpenPbrResolvedMaterial mat, inout RayTraceAuxiliary outAux) {\n float coverage = clamp(mat.alpha, 0.0, 1.0);\n if (!si.hit || (mat.materialType != STANDARD && mat.materialType != UNLIT) || coverage >= 1.0) {\n return integrateOnePathSample(si, ray, mat, 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 Ray backRay = ray;\n backRay.o = si.position + ray.d * (EPS * 0.02);\n SurfaceInteraction backSi = si;\n backSi.albedoSampled = false;\n intersectScene(backRay, backSi, false);\n OpenPbrResolvedMaterial backMat;\n getMaterial(backMat, backSi.materialIndex, backSi.uv, backSi.uv1, backSi.instanceIndex);\n RayTraceAuxiliary backAux = outAux;\n if (backSi.hit && (backMat.materialType == STANDARD || backMat.materialType == UNLIT)) {\n backAux.albedo = backMat.transmissionWeight > 0.5\n ? vec3(1.0)\n : backMat.baseColor;\n backAux.normal = dot(\n backSi.normal,\n backSi.position - vec3(viewInv[3])\n ) > 0.0\n ? -backSi.normal\n : backSi.normal;\n backSi.albedoSampled = backMat.transmissionWeight > 0.5;\n }\n vec4 back = integrateOnePathSample(backSi, backRay, backMat, backAux);\n if (coverage <= 0.0) {\n outAux = backAux;\n return back;\n }\n\n SurfaceInteraction frontSi = si;\n OpenPbrResolvedMaterial frontMat = mat;\n frontMat.alpha = 1.0;\n RayTraceAuxiliary frontAux = outAux;\n vec4 front = integrateOnePathSample(frontSi, ray, frontMat, frontAux);\n outAux.albedo = mix(backAux.albedo, frontAux.albedo, coverage);\n vec3 blendedNormal = mix(backAux.normal, frontAux.normal, coverage);\n outAux.normal = dot(blendedNormal, blendedNormal) > 1e-12\n ? normalize(blendedNormal)\n : vec3(0.0);\n outAux.isBlank = backAux.isBlank && frontAux.isBlank;\n return mix(back, front, coverage);\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#\nvec4 integrator(Ray ray, out RayTraceAuxiliary outAux) {\n\n SurfaceInteraction si;\n OpenPbrResolvedMaterial mat;\n\n si.albedoSampled = false;\n#ifdef HAS_MEDIUM\n si.mediumType = 0;\n si.mediumExtinction = vec3(0.0);\n si.mediumScattering = vec3(0.0);\n si.mediumAnisotropy = 0.0;\n#endif\n\n intersectScene(ray, si, false);\n getMaterial(mat, si.materialIndex, si.uv, si.uv1, si.instanceIndex);\n si.isTransparency = mat.transmissionWeight > 0.001 || mat.subsurfaceWeight > 0.001 || mat.alpha < 0.999;\n\n#ifdef HAS_BEVEL\n if (bevelEnabled && mat.bevel > 0.0) {\n si.normal = getBevelNormal(si, mat.bevel);\n }\n#endif\n\n // Keep a clean first-hit fallback for denoising. Glass retains this stable\n // guide; other delta paths may replace it at their first non-delta hit.\n if (si.hit && (mat.materialType == STANDARD || mat.materialType == UNLIT)) {\n outAux.albedo = mat.transmissionWeight > 0.5 ? vec3(1.0) : mat.baseColor;\n outAux.normal = dot(si.normal, si.position - vec3(viewInv[3])) > 0.0\n ? -si.normal\n : si.normal;\n // A refracted/reflected continuation is stochastic and therefore not a\n // clean OIDN feature. Keep the stable primary glass guide instead.\n si.albedoSampled = mat.transmissionWeight > 0.5;\n }\n\n if (mat.transmissionDispersionScale > 0.001) {\n mat.specularIor = sampling_cauchy_ior(\n pathWavelength,\n mat.specularIor,\n mat.transmissionDispersionScale,\n mat.dispersionAbbeNumber\n );\n }\n return integrateOnePath(si, ray, mat, outAux);\n}\n\nvoid main() {\n\n initRandom();\n rng_initialize(v_Texcoord * viewportSize, int(uint(sampleCount) ^ sampleHash(uint(sequenceSeed))));\n\n\n pathWavelength = dispersionWavelengths == 1 ? 0.0 : wavelength;\n heroWavelength = wavelength;\n if (dispersionWavelengths == 1 && wavelength > 0.0) {\n float channel = floor(sample1D(CAMERA_WAVELENGTH) * 3.0);\n heroWavelength = channel < 1.0 ? 650.0 : channel < 2.0 ? 510.0 : 475.0;\n }\n\n // Thin lens model with depth-of-field\n // http://www.pbr-book.org/3ed-2018/Camera_Models/Projective_Camera_Models.html#TheThinLensModelandDepthofField\n Ray cam = primaryCameraRay();\n\n RayTraceAuxiliary outAux;\n outAux.albedo = vec3(0.0);\n outAux.normal = vec3(0.0);\n outAux.isBlank = false;\n outAux.isShadowCatcher = false;\n\n vec4 liAndAlpha = integrator(cam, outAux);\n\n // Clamp at Integration Level to reduce fireflies.\n // TODO Path level configuration?\n#ifdef CLAMPING\n float clamping = float(CLAMPING);\n liAndAlpha.rgb = clamp(liAndAlpha.rgb, -clamping, clamping);\n#endif\n\n // Each sample will be accumulated three times.\n if (pathWavelength > 0.0) {\n // RGB will be normalized using accumated hit in out_albedo.a\n liAndAlpha.rgb *= 3.0;\n if (dispersionWavelengths != 1) {\n // Full spectral samples use three additive passes. Preview only submits\n // one hero wavelength, so its alpha must not be divided.\n liAndAlpha.a /= 3.0;\n }\n }\n if (!(liAndAlpha.x < INF && liAndAlpha.x > -EPS)) {\n liAndAlpha = vec4(0, 0, 0, 1);\n }\n\n out_color = liAndAlpha;\n out_albedo = vec4(outAux.albedo, outAux.isBlank ? 0.0 : 1.0);\n out_normal = vec4(outAux.normal, 1.0);\n // Moments and count of the built-in denoiser's guide value. The catcher\n // pass accumulates its own samples (see shadowCatcher.frag).\n float guideValue = dot(liAndAlpha.rgb, vec3(0.2126, 0.7152, 0.0722));\n out_moments = outAux.isShadowCatcher\n ? vec4(0.0)\n : vec4(guideValue, guideValue * guideValue, 1.0, 0.0);\n\n}`\n });\n}\n"]}
1
+ {"version":3,"file":"rayTrace.frag.js","sourceRoot":"","sources":["../../../src/renderer/glsl/rayTrace.frag.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_PATH_EVENTS } from '../pathLimits.js';\nimport cameraRayGlsl from './chunks/cameraRay.glsl.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 commonGlsl from './chunks/common.glsl.js';\nimport rayTraceCore from './chunks/rayTraceCore.glsl.js';\nimport textureLinear from './chunks/textureLinear.glsl.js';\nimport materialBufferGlsl from './chunks/materialBuffer.glsl.js';\nimport intersect from './chunks/intersect.glsl.js';\nimport randGlsl from './chunks/rand.glsl.js';\nimport random from './chunks/random.glsl.js';\nimport envMap from './chunks/envMap.glsl.js';\nimport sampleMaterialGlsl from './chunks/sampleMaterial.glsl.js';\nimport sampleLightGlsl from './chunks/sampleLight.glsl.js';\nimport { FragmentShader, glsl, Shader } from '../shader.js';\nimport openPbrBsdfGlsl from './chunks/openPbrBsdf.glsl.js';\nimport samplingGlsl from './chunks/sampling.glsl.js';\nimport { isIntelMac } from '../util.js';\nimport uvProjectionGlsl from './chunks/uvProjection.glsl.js';\n\nconst doubleTransmissionCheckCode = glsl`\n#if defined(HAS_TRANSMISSION) || defined(HAS_ALPHA)\n if (!si.isTransparency && BOUNCES < 16 && depth * 2 > BOUNCES) {\n path.abort = true;\n return;\n }\n#endif\n`;\n\n// bounce fix on intel mac\n// The intel mac only have one bounce if the loop is not unrolled\n// But windows will have extremely slow shader compilation if the loop is unrolled\n// So here the code are injected separately.\nconst bounceCodeOnIntelMac = glsl`\n// Mark loop can be unrolled\nfor(int _idx_ = 0; _idx_ < BOUNCES; _idx_++) {{\n if (path.abort) {\n return vec4(path.li, path.alpha);\n }\n bounce(path, depth, si, mat, outAux);\n}}\n`;\nconst bounceCodeOnOther = glsl`\nbool isOpaque = !si.isTransparency;\nint activeBounceLimit = min(BOUNCES, bounceLimit);\nfor(int idx = 0; idx < min(BOUNCES * 3, ${MAX_PATH_EVENTS}); idx++) {\n if (isOpaque && idx > activeBounceLimit) {\n path.abort = true;\n }\n if (path.abort) {\n return vec4(path.li, path.alpha);\n }\n bounce(path, depth, si, mat, outAux);\n}\n`;\n\nexport default function createRayTraceFragmentShader() {\n const intelMac = isIntelMac();\n return new FragmentShader({\n name: 'rayTraceFrag',\n outputs: ['color', 'albedo', 'normal', 'moments'],\n defines: {\n BOUNCES: 2\n },\n uniforms: {\n cameraApertureRadius: Shader.uniform('float', 0),\n cameraFocusDistance: Shader.uniform('float', 10),\n\n viewProjectionInv: Shader.uniform('mat4'),\n viewInv: Shader.uniform('mat4'),\n transmissiveOutputAlphaMin: Shader.uniform('float'),\n openPbrEnergyLut: Shader.uniform('sampler2D'),\n\n viewportSize: Shader.uniform('vec2'),\n\n wavelength: Shader.uniform('float', 0),\n bevelEnabled: Shader.uniform('bool', true),\n dispersionWavelengths: Shader.uniform('int', 3),\n bounceLimit: Shader.uniform('int', 8)\n },\n includes: [rayTraceCore, materialBufferGlsl, intersect, random, envMap],\n main: glsl`\n${commonGlsl}\n\n${randGlsl}\n${random.main}\n\n${textureLinear}\n\n${rayTraceCore.main}\n\n${uvProjectionGlsl}\n\n${materialBufferGlsl.main}\n${intersect.main}\n${envMap.main}\n\n${samplingGlsl}\n${openPbrBsdfGlsl}\n\nfloat heroWavelength;\nfloat pathWavelength;\n\n${sampleLightGlsl}\n${sampleMaterialGlsl}\n\n${cameraRayGlsl}\nstruct RayTraceAuxiliary {\n // Denoising guides and auxiliary pixel classification.\n vec3 albedo;\n vec3 normal;\n bool isBlank;\n // The primary ray hit a shadow catcher, whose sample the catcher pass owns.\n bool isShadowCatcher;\n};\n\nvoid bounce(inout Path path, inout int depth, inout SurfaceInteraction si, inout OpenPbrResolvedMaterial mat, inout RayTraceAuxiliary outAux) {\n samplingBounce = uint(max(depth, 0));\n // The bounce will be doubled on the transmission material.\n // For other materials. We will quite early\n // TODO It is only used on Mac with AMD card. If I enabled it on windows.\n // It will cause shader compilation too slow.\n${intelMac ? doubleTransmissionCheckCode : ''}\n\n bool isShadowCatcher = mat.materialType == SHADOW_CATCHER;\n if (isShadowCatcher) {\n if (path.fromEye) {\n // Match the composed shadow with neutral receiver guides, while keeping\n // background coverage and radiance independent of the invisible ground.\n outAux.albedo = vec3(1.0);\n outAux.normal = dot(si.normal, path.ray.d) > 0.0 ? -si.normal : si.normal;\n si.hit = false;\n outAux.isShadowCatcher = true;\n }\n else {\n // The catcher belongs only to the shadow-mask pass. Keep its default\n // white material out of glossy and transmission reflections.\n#ifdef USE_ENVMAP\n if (path.isNextRefraction) {\n // The invisible catcher can sit between a transmitted camera ray and\n // the grounded skybox. Use the displayed background here instead of\n // terminating early with the raw lighting environment.\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n float backgroundMisWeight = path.scatteringIsDelta ? 1.0\n : sampling_power_heuristic(path.scatteringPdf,\n envMapPdf(cartesianToEquirect(path.ray.d)) * envLightSelectionPmf);\n path.li += backgroundMisWeight * path.beta * bg.rgb * bg.a;\n path.alpha = max(path.alpha, bg.a);\n }\n else {\n vec2 envUv = cartesianToEquirect(path.ray.d);\n vec3 emission = textureLinear(envMap, envUv).rgb;\n emission = sampling_spectral_rgb_filter(emission, pathWavelength);\n path.li += path.beta * emission *\n sampling_power_heuristic(\n path.scatteringPdf,\n envMapPdf(envUv) * envLightSelectionPmf\n );\n }\n#else\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n path.li += path.beta * bg.rgb * bg.a;\n#endif\n path.abort = true;\n return;\n }\n }\n\n if (!si.hit) {\n // Sample from background if first ray not hit anything.\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n // Dispersion renders one additive pass per RGB wavelength. Background\n // radiance has to be split in the same way as lights and emissive\n // surfaces, otherwise the full skybox is accumulated three times.\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n if (path.fromEye) {\n path.li = 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.01;\n }\n else {\n Ray cam = initCameraRay(v_Texcoord + pixelJitter());\n bool towardsCam = dot(cam.d, path.ray.d) > 0.0;\n if (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 !defined(USE_ENVMAP)\n // TODO treat background as a light source\n if (towardsCam) {\n path.li = path.beta * sampling_power_heuristic(path.scatteringPdf, 1.0) * bg.rgb;\n }\n#endif\n }\n path.abort = true;\n return;\n }\n\n bool sampleIgnored = false;\n if ((mat.materialType == STANDARD || mat.materialType == UNLIT)) {\n if (transmissiveOutputAlphaMin < 1.0) {\n // TODO increase the alpha when there are more bounces.\n float alpha = max(1.0 - mat.transmissionWeight, 1.0 - pow(1.0 - transmissiveOutputAlphaMin, float(depth)));\n path.alpha = max(alpha, path.alpha);\n }\n float alpha = mat.alpha;\n if (alpha < 1.0) {\n // Alpha is sampled as stochastic coverage. An accepted surface branch\n // must use full radiance weight; multiplying by alpha again is biased.\n path.alpha = max(path.alpha, alpha);\n if (hashedSample(3u) > alpha) {\n sampleIgnored = true;\n }\n }\n if (!sampleIgnored) {\n SampleMaterial(si, mat, path);\n }\n else {\n path.ray.o = si.position + path.ray.d * EPS;\n }\n }\n\n if (!si.albedoSampled && !sampleIgnored) {\n // Select denoising guides.\n if ((mat.materialType == STANDARD || mat.materialType == UNLIT) &&\n !(mat.transmissionWeight > 0.5 ||\n (mat.specularRoughness <= 0.02 && mat.baseMetalness > 0.98))) {\n // Alpha-blended decals can be skipped stochastically. In that case keep\n // looking for the first useful rough-surface guide behind the decal,\n // even though it is reached after depth zero.\n outAux.albedo = mat.baseColor;\n outAux.normal = dot(si.normal, si.position - vec3(viewInv[3])) > 0.0\n ? -si.normal\n : si.normal;\n si.albedoSampled = true;\n }\n if (depth > 4) {\n si.albedoSampled = true;\n }\n }\n\n\n // Russian Roulette sampling\n#ifdef USE_RR\n if (depth >= RR_MAX && !sampleIgnored) {\n // Max-component survival (pbrt-v4) avoids over-amplifying saturated paths.\n float maxComponent = max(max(path.beta.x, path.beta.y), path.beta.z);\n float q = 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#endif\n\n if (path.abort) {\n return;\n }\n\n // Next ray hit\n intersectScene(path.ray, si, true);\n getMaterial(mat, si.materialIndex, si.uv, si.uv1, si.instanceIndex);\n si.isTransparency = mat.transmissionWeight > 0.001 || mat.subsurfaceWeight > 0.001 || mat.alpha < 0.999;\n\n // Dynamic preview only becomes spectral when the primary visible surface\n // actually disperses transmitted light. Direct background and ordinary\n // opaque materials stay full RGB and therefore remain noise-free.\n if (dispersionWavelengths == 1 && si.hit && mat.transmissionWeight > 0.001 && mat.transmissionDispersionScale > 0.001) {\n pathWavelength = heroWavelength;\n }\n if (mat.transmissionWeight > 0.001 && pathWavelength > 0.0) {\n mat.specularIor = sampling_cauchy_ior(\n pathWavelength,\n mat.specularIor,\n mat.transmissionDispersionScale,\n mat.dispersionAbbeNumber\n );\n }\n\n if (sampleIgnored) {\n return;\n }\n\n bool isAlphaOccluded = hashedSample(3u) < mat.alpha;\n#ifdef NUM_LIGHTS\n // Use light if hit the light\n LightSampleRec lightSample;\n for (int i = 0; i < NUM_LIGHTS; i++) {\n // Fetch light Data\n Light light = fetchLight(i);\n float d = intersectLight(path.ray, light, lightSample);\n bool occluded = si.hit && d > distance(si.position, path.ray.o);\n if (d != INF && !(occluded && isAlphaOccluded)) {\n lightSample.pdf *= light.selectionPmf;\n float misWeight = sampling_power_heuristic(path.scatteringPdf, lightSample.pdf);\n lightSample.emission = sampling_spectral_rgb_filter(lightSample.emission, pathWavelength);\n path.li += misWeight * path.beta * lightSample.emission;\n // TODO set alpha to 1.0 will cause some sharp edges\n path.alpha = 1.0;\n path.abort = true;\n }\n }\n#endif\n\n#ifdef USE_ENVMAP\n if (!(si.hit && isAlphaOccluded)) {\n if (path.isNextRefraction) {\n // Camera-visible transmission must use the displayed background. The\n // raw environment remains the lighting source for non-transmission rays.\n vec4 bg = sampleBackgroundFromRay(path.ray.o, path.ray.d);\n bg.rgb = sampling_spectral_rgb_filter(bg.rgb, pathWavelength);\n float backgroundMisWeight = path.scatteringIsDelta ? 1.0\n : sampling_power_heuristic(path.scatteringPdf,\n envMapPdf(cartesianToEquirect(path.ray.d)) * envLightSelectionPmf);\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 vec2 uv = cartesianToEquirect(path.ray.d);\n vec3 emission = textureLinear(envMap, uv).rgb;\n float misWeight = sampling_power_heuristic(\n path.scatteringPdf,\n envMapPdf(uv) * envLightSelectionPmf\n );\n emission = sampling_spectral_rgb_filter(emission, pathWavelength);\n path.li += path.beta * emission * misWeight;\n\n // TODO force the min alpha to be 1 when showing envmap as background\n #if !defined(USE_BACKGROUND)\n path.alpha = 1.0;\n #endif\n path.abort = true;\n return;\n }\n#endif\n\n depth++;\n}\n\n\nvec2 bevelCubicSample(float radius, float xi) {\n float x = 0.25;\n for (int iteration = 0; iteration < 10; iteration++) {\n float x2 = x * x;\n float x3 = x2 * x;\n float oneMinusX = 1.0 - x;\n float value = 10.0 * x2 - 20.0 * x3 + 15.0 * x2 * x2 -\n 4.0 * x2 * x3 - xi;\n float 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 float diskRadius = x * radius;\n return vec2(\n diskRadius,\n sqrt(max(radius * radius - diskRadius * diskRadius, 0.0))\n );\n}\n\nfloat bevelCubicPdf(float radius, float distanceToSource) {\n if (distanceToSource >= radius) return 0.0;\n float falloff = radius - distanceToSource;\n return falloff * falloff * falloff;\n}\n\n// Shader bevel. Usage: docs/materials.md (Renderer extensions)\nvec3 getBevelNormal(SurfaceInteraction source, float radius) {\n vec3 geometricNormal = source.faceNormal;\n if (dot(geometricNormal, source.vertexNormal) < 0.0) geometricNormal = -geometricNormal;\n vec3 tangent;\n vec3 bitangent;\n sampling_orthonormal_basis(geometricNormal, tangent, bitangent);\n vec3 normalSum = vec3(0.0);\n\n for (int sampleIndex = 0; sampleIndex < BEVEL_SAMPLES; sampleIndex++) {\n vec2 bevelRnd = sample2D(CAMERA_BEVEL_BASE + uint(sampleIndex));\n float axisSample = bevelRnd.x;\n float radialSample = bevelRnd.y;\n vec3 diskNormal = geometricNormal;\n vec3 diskTangent = tangent;\n vec3 diskBitangent = bitangent;\n float pickPdfNormal = 0.5;\n float pickPdfTangent = 0.25;\n float pickPdfBitangent = 0.25;\n if (axisSample < 0.5) {\n axisSample *= 2.0;\n }\n else if (axisSample < 0.75) {\n vec3 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 vec3 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 vec2 diskSample = bevelCubicSample(radius, radialSample);\n float phi = TWOPI * axisSample;\n vec3 diskOffset = diskSample.x *\n (cos(phi) * diskTangent + sin(phi) * diskBitangent);\n Ray ray;\n initRay(\n ray,\n source.position + diskNormal * diskSample.y + diskOffset,\n -diskNormal\n );\n SurfaceInteraction hit;\n intersectSceneBevel(\n ray,\n hit,\n source.instanceIndex,\n max(2.0 * diskSample.y, EPS)\n );\n if (!hit.hit) continue;\n\n float pdfNormal = pickPdfNormal * abs(dot(diskNormal, hit.faceNormal));\n float pdfTangent = pickPdfTangent * abs(dot(diskTangent, hit.faceNormal));\n float pdfBitangent = pickPdfBitangent * abs(dot(diskBitangent, hit.faceNormal));\n float 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 vec3 bevelVertexNormal = normalize(normalSum);\n vec3 result = source.normal + bevelVertexNormal - source.vertexNormal;\n return dot(result, result) > 1e-12 ? normalize(result) : source.normal;\n}\n\nvec4 integrateOnePathSample(SurfaceInteraction si, Ray ray, OpenPbrResolvedMaterial mat, inout RayTraceAuxiliary outAux) {\n Path path;\n path.ray = ray;\n path.li = vec3(0);\n path.alpha = transmissiveOutputAlphaMin < 1.0 ? 0.0 : 1.0;\n path.beta = vec3(1.0);\n path.fromEye = true;\n path.scatteringPdf = 0.0;\n path.scatteringIsDelta = false;\n path.isNextRefraction = false;\n path.abort = false;\n int depth = 0;\n${intelMac ? bounceCodeOnIntelMac : bounceCodeOnOther}\n return vec4(path.li, path.alpha);\n}\n\nvec4 integrateOnePath(SurfaceInteraction si, Ray ray, OpenPbrResolvedMaterial mat, inout RayTraceAuxiliary outAux) {\n float coverage = clamp(mat.alpha, 0.0, 1.0);\n if (!si.hit || (mat.materialType != STANDARD && mat.materialType != UNLIT) || coverage >= 1.0) {\n return integrateOnePathSample(si, ray, mat, 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 Ray backRay = ray;\n backRay.o = si.position + ray.d * (EPS * 0.02);\n SurfaceInteraction backSi = si;\n backSi.albedoSampled = false;\n intersectScene(backRay, backSi, false);\n OpenPbrResolvedMaterial backMat;\n getMaterial(backMat, backSi.materialIndex, backSi.uv, backSi.uv1, backSi.instanceIndex);\n RayTraceAuxiliary backAux = outAux;\n if (backSi.hit && (backMat.materialType == STANDARD || backMat.materialType == UNLIT)) {\n backAux.albedo = backMat.transmissionWeight > 0.5\n ? vec3(1.0)\n : backMat.baseColor;\n backAux.normal = dot(\n backSi.normal,\n backSi.position - vec3(viewInv[3])\n ) > 0.0\n ? -backSi.normal\n : backSi.normal;\n backSi.albedoSampled = backMat.transmissionWeight > 0.5;\n }\n vec4 back = integrateOnePathSample(backSi, backRay, backMat, backAux);\n if (coverage <= 0.0) {\n outAux = backAux;\n return back;\n }\n\n SurfaceInteraction frontSi = si;\n OpenPbrResolvedMaterial frontMat = mat;\n frontMat.alpha = 1.0;\n RayTraceAuxiliary frontAux = outAux;\n vec4 front = integrateOnePathSample(frontSi, ray, frontMat, frontAux);\n outAux.albedo = mix(backAux.albedo, frontAux.albedo, coverage);\n vec3 blendedNormal = mix(backAux.normal, frontAux.normal, coverage);\n outAux.normal = dot(blendedNormal, blendedNormal) > 1e-12\n ? normalize(blendedNormal)\n : vec3(0.0);\n outAux.isBlank = backAux.isBlank && frontAux.isBlank;\n return mix(back, front, coverage);\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#\nvec4 integrator(Ray ray, out RayTraceAuxiliary outAux) {\n\n SurfaceInteraction si;\n OpenPbrResolvedMaterial mat;\n\n si.albedoSampled = false;\n#ifdef HAS_MEDIUM\n si.mediumType = 0;\n si.mediumExtinction = vec3(0.0);\n si.mediumScattering = vec3(0.0);\n si.mediumAnisotropy = 0.0;\n#endif\n\n intersectScene(ray, si, false);\n getMaterial(mat, si.materialIndex, si.uv, si.uv1, si.instanceIndex);\n si.isTransparency = mat.transmissionWeight > 0.001 || mat.subsurfaceWeight > 0.001 || mat.alpha < 0.999;\n\n#ifdef HAS_BEVEL\n if (bevelEnabled && mat.bevel > 0.0) {\n si.normal = getBevelNormal(si, mat.bevel);\n }\n#endif\n\n // Keep a clean first-hit fallback for denoising. Glass retains this stable\n // guide; other delta paths may replace it at their first non-delta hit.\n if (si.hit && (mat.materialType == STANDARD || mat.materialType == UNLIT)) {\n outAux.albedo = mat.transmissionWeight > 0.5 ? vec3(1.0) : mat.baseColor;\n outAux.normal = dot(si.normal, si.position - vec3(viewInv[3])) > 0.0\n ? -si.normal\n : si.normal;\n // A refracted/reflected continuation is stochastic and therefore not a\n // clean OIDN feature. Keep the stable primary glass guide instead.\n si.albedoSampled = mat.transmissionWeight > 0.5;\n }\n\n if (mat.transmissionDispersionScale > 0.001) {\n mat.specularIor = sampling_cauchy_ior(\n pathWavelength,\n mat.specularIor,\n mat.transmissionDispersionScale,\n mat.dispersionAbbeNumber\n );\n }\n return integrateOnePath(si, ray, mat, outAux);\n}\n\nvoid main() {\n\n initRandom();\n rng_initialize(v_Texcoord * viewportSize, int(uint(sampleCount) ^ sampleHash(uint(sequenceSeed))));\n\n\n pathWavelength = dispersionWavelengths == 1 ? 0.0 : wavelength;\n heroWavelength = wavelength;\n if (dispersionWavelengths == 1 && wavelength > 0.0) {\n float channel = floor(sample1D(CAMERA_WAVELENGTH) * 3.0);\n heroWavelength = channel < 1.0 ? 650.0 : channel < 2.0 ? 510.0 : 475.0;\n }\n\n // Thin lens model with depth-of-field\n // http://www.pbr-book.org/3ed-2018/Camera_Models/Projective_Camera_Models.html#TheThinLensModelandDepthofField\n Ray cam = primaryCameraRay();\n\n RayTraceAuxiliary outAux;\n outAux.albedo = vec3(0.0);\n outAux.normal = vec3(0.0);\n outAux.isBlank = false;\n outAux.isShadowCatcher = false;\n\n vec4 liAndAlpha = integrator(cam, outAux);\n\n // Clamp at Integration Level to reduce fireflies.\n // TODO Path level configuration?\n#ifdef CLAMPING\n float clamping = float(CLAMPING);\n liAndAlpha.rgb = clamp(liAndAlpha.rgb, -clamping, clamping);\n#endif\n\n // Each sample will be accumulated three times.\n if (pathWavelength > 0.0) {\n // RGB will be normalized using accumated hit in out_albedo.a\n liAndAlpha.rgb *= 3.0;\n if (dispersionWavelengths != 1) {\n // Full spectral samples use three additive passes. Preview only submits\n // one hero wavelength, so its alpha must not be divided.\n liAndAlpha.a /= 3.0;\n }\n }\n if (!(liAndAlpha.x < INF && liAndAlpha.x > -EPS)) {\n liAndAlpha = vec4(0, 0, 0, 1);\n }\n\n out_color = liAndAlpha;\n out_albedo = vec4(outAux.albedo, outAux.isBlank ? 0.0 : 1.0);\n out_normal = vec4(outAux.normal, 1.0);\n // Moments and count of the built-in denoiser's guide value. The catcher\n // pass accumulates its own samples (see shadowCatcher.frag).\n float guideValue = dot(liAndAlpha.rgb, vec3(0.2126, 0.7152, 0.0722));\n out_moments = outAux.isShadowCatcher\n ? vec4(0.0)\n : vec4(guideValue, guideValue * guideValue, 1.0, 0.0);\n\n}`\n });\n}\n"]}
@@ -6,12 +6,12 @@ export declare const isMac: false | RegExpMatchArray | null;
6
6
  /** Detect the WebGL workaround platform only when the WebGL backend needs it. */
7
7
  export declare function isIntelMac(): boolean;
8
8
  /** Lowest resolution scale interactive preview adapts down to. */
9
- export declare const MIN_PREVIEW_SCALE = 0.25;
9
+ export declare const MIN_PREVIEW_SCALE = 0.125;
10
10
  /**
11
11
  * Next interactive-preview resolution scale from a preview frame time measured
12
12
  * at `measuredScale`. Tracing and denoising cost grow with the pixel count
13
13
  * (scale²), so the scale follows the square root of the time ratio. Steps are
14
- * 1/16 (1/32 below 0.25); a change of less than 15% from `currentScale` keeps
14
+ * 1/16 (1/128 below 0.25); a change of less than 15% from `currentScale` keeps
15
15
  * it, because every resolution change restarts sampling.
16
16
  */
17
17
  export declare function nextPreviewScale(measuredScale: number, frameTimeMs: number, targetTimeMs: number, maxScale: number, currentScale?: number, minScale?: number): number;
@@ -39,17 +39,17 @@ export function isIntelMac() {
39
39
  return cachedIntelMac;
40
40
  }
41
41
  /** Lowest resolution scale interactive preview adapts down to. */
42
- export const MIN_PREVIEW_SCALE = 0.25;
42
+ export const MIN_PREVIEW_SCALE = 0.125;
43
43
  /**
44
44
  * Next interactive-preview resolution scale from a preview frame time measured
45
45
  * at `measuredScale`. Tracing and denoising cost grow with the pixel count
46
46
  * (scale²), so the scale follows the square root of the time ratio. Steps are
47
- * 1/16 (1/32 below 0.25); a change of less than 15% from `currentScale` keeps
47
+ * 1/16 (1/128 below 0.25); a change of less than 15% from `currentScale` keeps
48
48
  * it, because every resolution change restarts sampling.
49
49
  */
50
50
  export function nextPreviewScale(measuredScale, frameTimeMs, targetTimeMs, maxScale, currentScale = measuredScale, minScale = MIN_PREVIEW_SCALE) {
51
51
  const raw = measuredScale * Math.sqrt(targetTimeMs / Math.max(frameTimeMs, 0.1));
52
- const steps = raw < 0.25 ? 32 : 16;
52
+ const steps = raw < 0.25 ? 128 : 16;
53
53
  const next = clamp(Math.round(raw * steps) / steps, minScale, Math.max(minScale, maxScale));
54
54
  return Math.abs(Math.log(next / currentScale)) >= Math.log(1.15) ? next : currentScale;
55
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/renderer/util.ts"],"names":[],"mappings":"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","sourcesContent":["import {createCanvas} from '../util/canvas.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 { WritableArrayLike } from '../types.js';\n\nexport function clamp(x: number, min: number, max: number) {\n return Math.min(Math.max(x, min), max);\n}\n\nexport function shuffle(arr: WritableArrayLike<number>, rand: () => number) {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rand() * (i + 1));\n const x = arr[i];\n arr[i] = arr[j];\n arr[j] = x;\n }\n return arr;\n}\n\nexport function numberArraysEqual(\n a: ArrayLike<number>,\n b: ArrayLike<number>,\n eps = 1e-4\n) {\n for (let i = 0; i < a.length; i++) {\n if (Math.abs(a[i] - b[i]) > eps) {\n return false;\n }\n }\n\n return true;\n}\nexport const isMac =\n typeof navigator !== 'undefined' && navigator.userAgent.match('Macintosh');\n\nlet cachedIntelMac: boolean | undefined;\n\n/** Detect the WebGL workaround platform only when the WebGL backend needs it. */\nexport function isIntelMac() {\n if (cachedIntelMac !== undefined) return cachedIntelMac;\n cachedIntelMac = false;\n if (isMac) {\n try {\n const ctx = createCanvas().getContext('webgl') as WebGLRenderingContext | null;\n const d = ctx && ctx.getExtension('WEBGL_debug_renderer_info');\n const g = (d && ctx.getParameter(d.UNMASKED_RENDERER_WEBGL)) || '';\n if (!(g.match(/Apple M/) || g.match(/Apple GPU/))) cachedIntelMac = true;\n } catch (e) {}\n }\n return cachedIntelMac;\n}\n\n/** Lowest resolution scale interactive preview adapts down to. */\nexport const MIN_PREVIEW_SCALE = 0.25;\n\n/**\n * Next interactive-preview resolution scale from a preview frame time measured\n * at `measuredScale`. Tracing and denoising cost grow with the pixel count\n * (scale²), so the scale follows the square root of the time ratio. Steps are\n * 1/16 (1/32 below 0.25); a change of less than 15% from `currentScale` keeps\n * it, because every resolution change restarts sampling.\n */\nexport function nextPreviewScale(\n measuredScale: number,\n frameTimeMs: number,\n targetTimeMs: number,\n maxScale: number,\n currentScale = measuredScale,\n minScale = MIN_PREVIEW_SCALE\n) {\n const raw = measuredScale * Math.sqrt(targetTimeMs / Math.max(frameTimeMs, 0.1));\n const steps = raw < 0.25 ? 32 : 16;\n const next = clamp(Math.round(raw * steps) / steps, minScale, Math.max(minScale, maxScale));\n return Math.abs(Math.log(next / currentScale)) >= Math.log(1.15) ? next : currentScale;\n}\n"]}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/renderer/util.ts"],"names":[],"mappings":"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","sourcesContent":["import {createCanvas} from '../util/canvas.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 { WritableArrayLike } from '../types.js';\n\nexport function clamp(x: number, min: number, max: number) {\n return Math.min(Math.max(x, min), max);\n}\n\nexport function shuffle(arr: WritableArrayLike<number>, rand: () => number) {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rand() * (i + 1));\n const x = arr[i];\n arr[i] = arr[j];\n arr[j] = x;\n }\n return arr;\n}\n\nexport function numberArraysEqual(\n a: ArrayLike<number>,\n b: ArrayLike<number>,\n eps = 1e-4\n) {\n for (let i = 0; i < a.length; i++) {\n if (Math.abs(a[i] - b[i]) > eps) {\n return false;\n }\n }\n\n return true;\n}\nexport const isMac =\n typeof navigator !== 'undefined' && navigator.userAgent.match('Macintosh');\n\nlet cachedIntelMac: boolean | undefined;\n\n/** Detect the WebGL workaround platform only when the WebGL backend needs it. */\nexport function isIntelMac() {\n if (cachedIntelMac !== undefined) return cachedIntelMac;\n cachedIntelMac = false;\n if (isMac) {\n try {\n const ctx = createCanvas().getContext('webgl') as WebGLRenderingContext | null;\n const d = ctx && ctx.getExtension('WEBGL_debug_renderer_info');\n const g = (d && ctx.getParameter(d.UNMASKED_RENDERER_WEBGL)) || '';\n if (!(g.match(/Apple M/) || g.match(/Apple GPU/))) cachedIntelMac = true;\n } catch (e) {}\n }\n return cachedIntelMac;\n}\n\n/** Lowest resolution scale interactive preview adapts down to. */\nexport const MIN_PREVIEW_SCALE = 0.125;\n\n/**\n * Next interactive-preview resolution scale from a preview frame time measured\n * at `measuredScale`. Tracing and denoising cost grow with the pixel count\n * (scale²), so the scale follows the square root of the time ratio. Steps are\n * 1/16 (1/128 below 0.25); a change of less than 15% from `currentScale` keeps\n * it, because every resolution change restarts sampling.\n */\nexport function nextPreviewScale(\n measuredScale: number,\n frameTimeMs: number,\n targetTimeMs: number,\n maxScale: number,\n currentScale = measuredScale,\n minScale = MIN_PREVIEW_SCALE\n) {\n const raw = measuredScale * Math.sqrt(targetTimeMs / Math.max(frameTimeMs, 0.1));\n const steps = raw < 0.25 ? 128 : 16;\n const next = clamp(Math.round(raw * steps) / steps, minScale, Math.max(minScale, maxScale));\n return Math.abs(Math.log(next / currentScale)) >= Math.log(1.15) ? next : currentScale;\n}\n"]}
@@ -692,6 +692,10 @@ eT(&ma, si.materialIndex, si.uv, si.uv1, si.instanceIndex);
692
692
 
693
693
  if (ma.materialType == SHADOW_CATCHER) {
694
694
  (*outAux).isShadowCatcher = true;
695
+
696
+
697
+ (*outAux).albedo = vec3f(1.0);
698
+ (*outAux).normal = select(si.normal, -si.normal, dot(si.normal, ray.d) > 0.0);
695
699
  var aa: Path;
696
700
  aa.ray = ray;
697
701
  aa.fromEye = true;
@@ -889,8 +893,8 @@ let jo = vec4f(dn.rgb * dn.a, dn.a);
889
893
  outShadowCatcher[idx].mask += vec4f(1.0, dn.a, pU, aN.a);
890
894
  outShadowCatcher[idx].background += jo;
891
895
  outColor[idx] += jo;
892
- outAlbedo[idx] += vec4f(vec3f(0.0), pU);
893
- hH(idx, dn.rgb, vec3f(0.0));
896
+ outAlbedo[idx] += vec4f(outAux.albedo, pU);
897
+ hH(idx, dn.rgb, outAux.normal);
894
898
 
895
899
 
896
900
  let visibility = 1.0 - aN.a;
@@ -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 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(vec3f(0.0), backgroundHit);\n accumulateNormalOrVariance(idx, backgroundSample.rgb, vec3f(0.0));\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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,6BAA6B,MAAM,4CAA4C,CAAC;AACvF,OAAO,cAAc,MAAM,sBAAsB,CAAC;AAClD,qDAAqD;AACrD,sDAAsD;AACtD,kFAAkF;AAElF,OAAO,UAAU,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,gBAAgB,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,sBAAsB,MAAM,4BAA4B,CAAC;AAChE,OAAO,UAAU,MAAM,yBAAyB,CAAC;AACjD,OAAO,QAAQ,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,eAAe,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,YAAY,MAAM,2BAA2B,CAAC;AACrD,OAAO,uBAAuB,MAAM,sCAAsC,CAAC;AAC3E,OAAO,UAAU,MAAM,yBAAyB,CAAC;AACjD,OAAO,gBAAgB,MAAM,+BAA+B,CAAC;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAgBhC,mEAAmE;AACnE,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,eAAe,EACf,uBAAuB,GAAG,KAAK,EAC/B,cAAc,GAAG,KAAK,EACtB,QAAQ,GAAG,IAAI,EACf,aAAa,GAAG,IAAI,EACpB,cAAc,GAAG,CAAC,EACI;IACtB,uBAAuB,KAAK,cAAc,CAAC;IAC3C,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC3C,MAAM,MAAM,GAAG,UAAU,CAAC;;EAE1B,UAAU;;EAEV,gBAAgB,CAAC,EAAE,aAAa,EAAE,uBAAuB,EAAE,cAAc,EAAE,CAAC;;EAE5E,gBAAgB;;EAEhB,wBAAwB,CAAC,EAAE,aAAa,EAAE,uBAAuB,EAAE,cAAc,EAAE,CAAC;;EAEpF,sBAAsB,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,uBAAuB,EAAE,cAAc,EAAE,CAAC;EACpG,QAAQ;EACR,UAAU;EACV,UAAU;;EAEV,YAAY;EACZ,qBAAqB,CAAC,uBAAuB,EAAE,cAAc,CAAC;EAC9D,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDf,wBAAwB,CAAC,uBAAuB,EAAE,cAAc,CAAC;EACjE,uBAAuB;;;;;;;EAOvB,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oDAufuB,eAAe;;;;;;;;;;;;;;EAcjE,yBAAyB,CAAC,CAAC,CAAC,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAyDN,CAAC,CAAC,CAAC,UAAU,CAAC,qCAAyD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAkJjF,cAAc,KAAK,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4F3D,CAAC;IACA,0EAA0E;IAC1E,8DAA8D;IAC9D,4EAA4E;IAC5E,8BAA8B;IAC9B,IAAI,iBAAiB,GAAG,MAAM,GAAG,cAAc,CAAC;IAChD,MAAM,cAAc,GAA2B;QAC7C,cAAc,EAAE,CAAC;QACjB,kBAAkB,EAAE,CAAC;QACrB,uBAAuB,EAAE,CAAC;KAC3B,CAAC;IACF,iBAAiB,GAAG,iBAAiB,CAAC,OAAO,CAC3C,oEAAoE,EACpE,CAAC,IAAI,EAAE,EAAE,CAAC,iCAAiC,cAAc,CAAC,IAAI,CAAC,SAAS,CACzE,CAAC;IACF,iBAAiB,GAAG,iBAAiB,CAAC,OAAO,CAC3C,IAAI,MAAM,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9E,IAAI,CAAC,EAAE,CAAC,mCAAmC,uBAAuB,CAAC,IAAI,CAAC,WAAW,CACpF,CAAC;IACF,OAAO,iBAAiB,CAAC;AAC3B,CAAC","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"]}
@@ -42,8 +42,12 @@ export interface THREEControls {
42
42
  removeEventListener(type: 'start' | 'end', listener: () => void): void;
43
43
  target?: THREE.Vector3;
44
44
  enableDamping?: boolean;
45
+ /** Damping per 60 Hz frame; synchronization adjusts it for elapsed time. Default undefined uses the control's own timing. */
46
+ dampingFactor?: number;
45
47
  autoRotate?: boolean;
46
48
  enabled?: boolean;
49
+ /** Optional OrbitControls polar-angle accessor. Default undefined. */ getPolarAngle?(): number;
50
+ /** Optional OrbitControls azimuthal-angle accessor. Default undefined. */ getAzimuthalAngle?(): number;
47
51
  }
48
52
  /** Three.js sources synchronized on each tick. */
49
53
  export interface THREESyncOptions extends FromTHREESceneOptions {
@@ -71,6 +75,7 @@ export declare class THREESync extends Sync {
71
75
  private _started;
72
76
  private _cameraState;
73
77
  private _lastUpdate?;
78
+ private _idleControlsState?;
74
79
  private _start;
75
80
  private _end;
76
81
  /** Subscribes to controls without converting sources or modifying the engine. */