@meimu/mutracer 0.3.0 → 0.3.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.
- package/CHANGELOG.md +222 -0
- package/LICENSES/FidelityFX-FSR.txt +19 -0
- package/LICENSES/pbrt-v4-Apache-2.0.txt +202 -0
- package/THIRD_PARTY_NOTICES.md +18 -4
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +6260 -5580
- package/docs/api.md +45 -18
- package/docs/materials.md +4 -2
- package/docs/performance.md +7 -0
- package/docs/post-processing.md +62 -9
- package/docs/progressive-rendering.md +3 -0
- package/docs/sampling.md +4 -0
- package/docs/technical/acceleration.md +52 -0
- package/docs/technical/aovs.md +79 -0
- package/docs/technical/caustics.md +109 -0
- package/docs/technical/continuous-spectrum.md +29 -0
- package/docs/technical/display-upscale.md +82 -0
- package/docs/technical/openpbr.md +99 -0
- package/docs/technical/pbrt-import.md +168 -0
- package/docs/technical/tiling.md +74 -0
- package/docs/technical/webgl.md +53 -0
- package/docs/viewer.md +11 -1
- package/lib/Display.d.ts +19 -5
- package/lib/Display.js +10 -7
- package/lib/Display.js.map +1 -1
- package/lib/Engine.d.ts +2 -1
- package/lib/Engine.js +30 -2
- package/lib/Engine.js.map +1 -1
- package/lib/Render.d.ts +20 -11
- package/lib/Render.js +32 -7
- package/lib/Render.js.map +1 -1
- package/lib/main.d.ts +3 -1
- package/lib/main.js +1 -0
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/renderer/AovPrimitiveCache.js +39 -8
- package/lib/renderer/AovPrimitiveCache.js.map +1 -1
- package/lib/renderer/DisplayPostProcess.d.ts +8 -3
- package/lib/renderer/DisplayPostProcess.js +51 -18
- package/lib/renderer/DisplayPostProcess.js.map +1 -1
- package/lib/renderer/MaterialData.js +7 -2
- package/lib/renderer/MaterialData.js.map +1 -1
- package/lib/renderer/RenderingPipeline.d.ts +7 -1
- package/lib/renderer/RenderingPipeline.js.map +1 -1
- package/lib/renderer/TileRender.d.ts +2 -2
- package/lib/renderer/TileRender.js +128 -7
- package/lib/renderer/TileRender.js.map +1 -1
- package/lib/renderer/WGPUAov.d.ts +6 -1
- package/lib/renderer/WGPUAov.js +108 -14
- package/lib/renderer/WGPUAov.js.map +1 -1
- package/lib/renderer/WGPUCanvasSurface.js +12 -1
- package/lib/renderer/WGPUCanvasSurface.js.map +1 -1
- package/lib/renderer/WGPUEnvMapDistribution.js +19 -19
- package/lib/renderer/WGPUFullQuadPass.js +3 -3
- package/lib/renderer/WGPURayTraceOutput.js +12 -12
- package/lib/renderer/WGPURayTracePass.d.ts +17 -4
- package/lib/renderer/WGPURayTracePass.js +194 -71
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +46 -40
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLAov.js +3 -3
- package/lib/renderer/WebGLAov.js.map +1 -1
- package/lib/renderer/WebGLFullQuadPass.d.ts +1 -1
- package/lib/renderer/WebGLFullQuadPass.js +8 -2
- package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
- package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
- package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +14 -10
- package/lib/renderer/WebGLRayTracePass.js +115 -111
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/WebGLRenderingPipeline.js +10 -3
- package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLSkyBaker.js +57 -57
- package/lib/renderer/aov.d.ts +13 -1
- package/lib/renderer/aov.js +21 -2
- package/lib/renderer/aov.js.map +1 -1
- package/lib/renderer/aovDisplay.js +2 -2
- package/lib/renderer/aovDisplay.js.map +1 -1
- package/lib/renderer/binnedSahBvh.d.ts +2 -1
- package/lib/renderer/binnedSahBvh.js +24 -17
- package/lib/renderer/binnedSahBvh.js.map +1 -1
- package/lib/renderer/bvhAccel.d.ts +3 -1
- package/lib/renderer/bvhAccel.js +36 -6
- package/lib/renderer/bvhAccel.js.map +1 -1
- package/lib/renderer/cameraUniform.d.ts +1 -1
- package/lib/renderer/cameraUniform.js +6 -2
- package/lib/renderer/cameraUniform.js.map +1 -1
- package/lib/renderer/compactVertices.d.ts +14 -0
- package/lib/renderer/compactVertices.js +124 -0
- package/lib/renderer/compactVertices.js.map +1 -0
- package/lib/renderer/easu.d.ts +11 -0
- package/lib/renderer/easu.js +156 -0
- package/lib/renderer/easu.js.map +1 -0
- package/lib/renderer/geometryUpdateState.d.ts +1 -0
- package/lib/renderer/geometryUpdateState.js.map +1 -1
- package/lib/renderer/geometryUpdates.js +3 -2
- package/lib/renderer/geometryUpdates.js.map +1 -1
- package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
- package/lib/renderer/glsl/aovReflectance.glsl.js +35 -35
- package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
- package/lib/renderer/glsl/aovTrace.frag.js +44 -36
- package/lib/renderer/glsl/aovTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.js +16 -0
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +1 -1
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -16
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +8 -8
- package/lib/renderer/glsl/chunks/envMap.glsl.js +67 -67
- package/lib/renderer/glsl/chunks/intersect.glsl.js +390 -375
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +120 -120
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +562 -562
- package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/random.glsl.js +4 -4
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +37 -35
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +70 -74
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -1
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +107 -88
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +11 -11
- package/lib/renderer/glsl/chunks/sampling.glsl.js +77 -77
- package/lib/renderer/glsl/chunks/sobol.glsl.js +70 -70
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.d.ts +6 -0
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js +47 -0
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
- package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
- package/lib/renderer/glsl/depthQuery.frag.js +12 -6
- package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
- package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
- package/lib/renderer/glsl/rayTrace.frag.js +233 -170
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +10 -9
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +58 -58
- package/lib/renderer/gpuMemory.js +1 -1
- package/lib/renderer/gpuMemory.js.map +1 -1
- package/lib/renderer/openPbrEnergyLut.d.ts +2 -1
- package/lib/renderer/openPbrEnergyLut.js +18 -1
- package/lib/renderer/openPbrEnergyLut.js.map +1 -1
- package/lib/renderer/pathLimits.d.ts +5 -4
- package/lib/renderer/pathLimits.js +6 -5
- package/lib/renderer/pathLimits.js.map +1 -1
- package/lib/renderer/prepareMeshBVH.d.ts +2 -0
- package/lib/renderer/prepareMeshBVH.js +5 -3
- package/lib/renderer/prepareMeshBVH.js.map +1 -1
- package/lib/renderer/progressiveScheduler.js +1 -1
- package/lib/renderer/progressiveScheduler.js.map +1 -1
- package/lib/renderer/renderPipeline.d.ts +4 -0
- package/lib/renderer/renderPipeline.js +1 -0
- package/lib/renderer/renderPipeline.js.map +1 -1
- package/lib/renderer/researchTransport.d.ts +1 -1
- package/lib/renderer/researchTransport.js.map +1 -1
- package/lib/renderer/sceneAssembly.d.ts +1 -1
- package/lib/renderer/sceneAssembly.js +4 -3
- package/lib/renderer/sceneAssembly.js.map +1 -1
- package/lib/renderer/sceneChangeDetector.d.ts +1 -1
- package/lib/renderer/sceneChangeDetector.js +3 -1
- package/lib/renderer/sceneChangeDetector.js.map +1 -1
- package/lib/renderer/sceneLayoutSchema.d.ts +1 -0
- package/lib/renderer/sceneLayoutSchema.js +2 -2
- package/lib/renderer/sceneLayoutSchema.js.map +1 -1
- package/lib/renderer/scenePreparation.d.ts +3 -2
- package/lib/renderer/scenePreparation.js +18 -2
- package/lib/renderer/scenePreparation.js.map +1 -1
- package/lib/renderer/scratchSampling.js +6 -6
- package/lib/renderer/spectrum.js +2 -2
- package/lib/renderer/staticBvhPreparation.d.ts +9 -0
- package/lib/renderer/staticBvhPreparation.js +8 -2
- package/lib/renderer/staticBvhPreparation.js.map +1 -1
- package/lib/renderer/surfaceHit.d.ts +10 -0
- package/lib/renderer/surfaceHit.js +22 -0
- package/lib/renderer/surfaceHit.js.map +1 -0
- package/lib/renderer/tangentRequirements.d.ts +2 -0
- package/lib/renderer/tangentRequirements.js +7 -0
- package/lib/renderer/tangentRequirements.js.map +1 -0
- package/lib/renderer/textureAtlas.d.ts +4 -0
- package/lib/renderer/textureAtlas.js +48 -2
- package/lib/renderer/textureAtlas.js.map +1 -1
- package/lib/renderer/webgl.js +4 -0
- package/lib/renderer/webgl.js.map +1 -1
- package/lib/renderer/webglVertexAttributes.d.ts +17 -0
- package/lib/renderer/webglVertexAttributes.js +44 -0
- package/lib/renderer/webglVertexAttributes.js.map +1 -0
- package/lib/renderer/wgpuBindlessSource.js +2 -2
- package/lib/renderer/wgpuMaterialConstants.js +4 -3
- package/lib/renderer/wgpuMaterialConstants.js.map +1 -1
- package/lib/renderer/wgpuSceneUniform.js +2 -2
- package/lib/renderer/wgpuSceneUniform.js.map +1 -1
- package/lib/renderer/wgpuShaderSource.d.ts +15 -0
- package/lib/renderer/wgpuShaderSource.js +1 -1
- package/lib/renderer/wgpuShaderSource.js.map +1 -1
- package/lib/renderer/wgsl/aovCompact.wgsl.d.ts +4 -0
- package/lib/renderer/wgsl/aovCompact.wgsl.js +26 -0
- package/lib/renderer/wgsl/aovCompact.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
- package/lib/renderer/wgsl/aovReflectance.wgsl.js +39 -39
- package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +4 -0
- package/lib/renderer/wgsl/aovResolve.wgsl.js +21 -2
- package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js +2 -2
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/aovTrace.wgsl.js +31 -31
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js +56 -0
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
- package/lib/renderer/wgsl/causticLight.compute.d.ts +1 -1
- package/lib/renderer/wgsl/causticLight.compute.js +121 -119
- package/lib/renderer/wgsl/causticLight.compute.js.map +1 -1
- package/lib/renderer/wgsl/chunks/background.wgsl.js +46 -46
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +30 -30
- package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +6 -2
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +550 -542
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +18 -16
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +70 -69
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +593 -593
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +118 -118
- package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +4 -1
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +32 -26
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +114 -114
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +159 -137
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +8 -8
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +74 -74
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +74 -74
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.d.ts +6 -0
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js +66 -0
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js +61 -0
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/compose.frag.wgsl.js +99 -97
- package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/depthQuery.wgsl.js +11 -2
- package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/easu.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/easu.wgsl.js +80 -0
- package/lib/renderer/wgsl/easu.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.d.ts +21 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.js +230 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/jnlm.compute.js +74 -74
- package/lib/renderer/wgsl/jnlm.compute.js.map +1 -1
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
- package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
- package/lib/renderer/wgsl/photonMap.compute.js +89 -89
- package/lib/renderer/wgsl/proceduralSky.compute.js +61 -61
- package/lib/renderer/wgsl/rayTrace.compute.d.ts +14 -0
- package/lib/renderer/wgsl/rayTrace.compute.js +395 -333
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/scene/Scene.js.map +1 -1
- package/lib/settings/render.d.ts +11 -0
- package/lib/settings/render.js +1 -0
- package/lib/settings/render.js.map +1 -1
- package/lib/util/fromTHREE.js +21 -14
- package/lib/util/fromTHREE.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +5 -2
- package/lib/oidnBundledWeights.cdn.d.ts +0 -2
- package/lib/oidnBundledWeights.cdn.js +0 -5
- package/lib/oidnBundledWeights.cdn.js.map +0 -1
- package/lib/renderer/bvh.worker.d.ts +0 -1
- package/lib/renderer/bvh.worker.js +0 -16
- package/lib/renderer/bvh.worker.js.map +0 -1
- package/lib/renderer/bvhWorkerBuild.d.ts +0 -13
- package/lib/renderer/bvhWorkerBuild.js +0 -17
- package/lib/renderer/bvhWorkerBuild.js.map +0 -1
- package/lib/renderer/lightCore.d.ts +0 -12
- package/lib/renderer/lightCore.js +0 -13
- package/lib/renderer/lightCore.js.map +0 -1
- package/lib/renderer/materialCore.d.ts +0 -8
- package/lib/renderer/materialCore.js +0 -9
- package/lib/renderer/materialCore.js.map +0 -1
- package/lib/scene/PixelTexture.d.ts +0 -6
- package/lib/scene/PixelTexture.js +0 -10
- package/lib/scene/PixelTexture.js.map +0 -1
- package/lib/util/studioLighting.d.ts +0 -18
- package/lib/util/studioLighting.js +0 -36
- package/lib/util/studioLighting.js.map +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Display upscale
|
|
2
|
+
|
|
3
|
+
`createEasuPass` enlarges a reduced-resolution render to the canvas, and with
|
|
4
|
+
`albedoDetail` it restores surface texture detail from full-resolution color AOVs.
|
|
5
|
+
Usage, costs and limits are in [post-processing](../post-processing.md); the AOV
|
|
6
|
+
side is in [surface color AOVs](./aovs.md). This note explains the algorithm. The
|
|
7
|
+
code is `src/renderer/easu.ts` with `wgsl/easu.wgsl.ts` and
|
|
8
|
+
`wgsl/easuDetail.wgsl.ts`.
|
|
9
|
+
|
|
10
|
+
## Pipeline position
|
|
11
|
+
|
|
12
|
+
The pass is a display post-process: it runs after composition, tone mapping and
|
|
13
|
+
denoising, on display-encoded RGB. A pass can change the image size through
|
|
14
|
+
`outputSize`, so the upscale is ordinary chain state and needs no special case in
|
|
15
|
+
composition, export or denoising. Beauty, its denoising and the AOVs keep their own
|
|
16
|
+
resolutions; only the displayed image is enlarged.
|
|
17
|
+
|
|
18
|
+
An input at the output size is copied, and a larger input is area-averaged, so one
|
|
19
|
+
registration serves both a reduced preview and a full-resolution final render.
|
|
20
|
+
|
|
21
|
+
## EASU
|
|
22
|
+
|
|
23
|
+
The enlargement is AMD FidelityFX FSR 1 EASU, without RCAS sharpening. It estimates
|
|
24
|
+
a local edge direction and filters along it with a 12-tap lobe. FSR expects inputs
|
|
25
|
+
in [0, 1], so extended-range output normalizes the neighborhood only for the
|
|
26
|
+
direction estimate and filters the original values. EASU cannot invent detail that
|
|
27
|
+
the reduced render does not contain.
|
|
28
|
+
|
|
29
|
+
## Detail compensation
|
|
30
|
+
|
|
31
|
+
Texture detail lost at render resolution is still present in the display-resolution
|
|
32
|
+
`diffuseColor` and `specularColor` AOVs. Compensation transfers it:
|
|
33
|
+
|
|
34
|
+
1. **Low guides.** The full-resolution colors are area-averaged over each render
|
|
35
|
+
pixel's exact footprint, including noninteger scales, so the low guides match
|
|
36
|
+
what beauty integrated. No second AOV allocation is needed.
|
|
37
|
+
2. **Local model.** For each render pixel, a guided-filter regression over a 3×3
|
|
38
|
+
window fits scene-linear radiance as `slope × color + intercept` per channel.
|
|
39
|
+
Diffuse shading follows color (slope), while highlights and reflections do not
|
|
40
|
+
(intercept). Flat colors give a slope near zero, so they are left alone.
|
|
41
|
+
3. **Upsampling.** The low guides are enlarged with exactly the same EASU taps and
|
|
42
|
+
weights as beauty, so beauty and guides share one footprint.
|
|
43
|
+
4. **Ratio.** At each output pixel, the full-resolution color replaces the
|
|
44
|
+
upsampled one inside the model: `(slope × hi + intercept) / (slope × low +
|
|
45
|
+
intercept)`, clamped per channel to [0.7, 1.43]. Without a valid model it falls
|
|
46
|
+
back to the plain color ratio `hi / low`.
|
|
47
|
+
5. **Display transform.** The ratio is scene-linear, but the image is tone-mapped.
|
|
48
|
+
A lookup table built from the current tone mapping and grading maps displayed
|
|
49
|
+
RGB back to scene-linear per channel, applies the ratio and maps it forward, so
|
|
50
|
+
curve shoulders and toes respond as the tone mapper would. Only the change is
|
|
51
|
+
added to the displayed value, which keeps it exact where the ratio is 1.
|
|
52
|
+
|
|
53
|
+
Compensation is skipped, leaving EASU alone, where it would be wrong:
|
|
54
|
+
|
|
55
|
+
- AOV validity or coverage below 0.95 (transmission, cutouts, background,
|
|
56
|
+
unsupported materials);
|
|
57
|
+
- silhouettes, detected from a change in hit coverage or a break in the planarity
|
|
58
|
+
of inverse depth (which is linear in screen space on any plane, so sloped
|
|
59
|
+
surfaces are not silhouettes);
|
|
60
|
+
- color below 10⁻³, treated as black.
|
|
61
|
+
|
|
62
|
+
Clipped highlights cannot be recovered: the inverse tone mapping has no
|
|
63
|
+
information above the display maximum.
|
|
64
|
+
|
|
65
|
+
## Retained alternatives
|
|
66
|
+
|
|
67
|
+
`createEasuPassImpl` keeps three internal variants for comparisons:
|
|
68
|
+
|
|
69
|
+
- **Plain color ratio.** Scaling all displayed radiance by `hi / low` also scaled
|
|
70
|
+
highlights and other light that does not follow color, which limited the usable
|
|
71
|
+
ratio range. The guided model widened it to the current default.
|
|
72
|
+
- **Separate lobes.** Diffuse and specular ratios weighted by lobe shares estimated
|
|
73
|
+
from the low colors; beauty has no separate lobes to weight them exactly.
|
|
74
|
+
- **Silhouette reconstruction.** Front and back colors from depth-matched
|
|
75
|
+
low-resolution neighbors, mixed by full-resolution coverage. It sharpens
|
|
76
|
+
backgrounds but loses detail on thin structures, glass and reflections
|
|
77
|
+
compared with EASU alone.
|
|
78
|
+
|
|
79
|
+
Ratios were first applied to encoded display RGB, which over-darkened dark decals;
|
|
80
|
+
applying them in linear and then through the display transform let the clamp range
|
|
81
|
+
grow. The public tuning options were removed; `EASU_DETAIL` holds the shared
|
|
82
|
+
defaults, which native also uses.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# OpenPBR implementation
|
|
2
|
+
|
|
3
|
+
`OpenPBRMaterial` follows the OpenPBR Surface specification with a compact GPU
|
|
4
|
+
BSDF. Parameters, units and user-facing limits are in the
|
|
5
|
+
[materials guide](../materials.md); this note describes how the implementation
|
|
6
|
+
is organized and the rules for changing it.
|
|
7
|
+
|
|
8
|
+
## Data flow
|
|
9
|
+
|
|
10
|
+
- **Layout table.** `materialLayoutSchema.ts` lists every material buffer row
|
|
11
|
+
(one `vec4` each) with its source field and type. The same table generates the
|
|
12
|
+
TypeScript packing, the WGSL struct (`materialBufferWgsl`) and the GLSL row
|
|
13
|
+
constants (`materialRowsGlsl`), so the three cannot drift. Reserved rows keep
|
|
14
|
+
their legacy contents; reusing one is an intentional migration.
|
|
15
|
+
- **Texture slots.** `materialTextureSlots.ts` lists every map with its target
|
|
16
|
+
parameters, channels, color space, shader define and feature bit. Slot order is
|
|
17
|
+
the atlas index contract. Public map names live in the scene layer
|
|
18
|
+
(`materialTextureNames.ts`).
|
|
19
|
+
- **Feature specialization.** Features that a scene does not use, such as
|
|
20
|
+
anisotropy directions or coat normals, are compiled out by generating different
|
|
21
|
+
WGSL source, not by patching strings after generation.
|
|
22
|
+
|
|
23
|
+
## BSDF
|
|
24
|
+
|
|
25
|
+
The BSDF (`openPbrBsdf.wgsl.ts` and its GLSL counterpart) follows the
|
|
26
|
+
prepare/eval/sample/pdf organization of Adobe's
|
|
27
|
+
[openpbr-bsdf](https://github.com/adobe/openpbr-bsdf) and the
|
|
28
|
+
[OpenPBR viewer](https://github.com/portsmouth/OpenPBR-viewer):
|
|
29
|
+
|
|
30
|
+
- `openpbr_prepare` resolves the layered material once per path vertex. NEE, BSDF
|
|
31
|
+
sampling and MIS reuse the result, which made WebGPU samples about 11–15% faster
|
|
32
|
+
than preparing per evaluation.
|
|
33
|
+
- The lobes are fuzz, coat, metal, dielectric specular, transmission, diffuse and
|
|
34
|
+
subsurface. Their weights and probabilities are stored as arrays indexed by lobe,
|
|
35
|
+
rather than one variable per lobe.
|
|
36
|
+
- Metal uses the F82-tint Fresnel; specular anisotropy uses the specification's
|
|
37
|
+
roughness mapping. The coat roughens the base, changes the IOR ratio below it,
|
|
38
|
+
absorbs, and darkens through internal reflection (`coatDarkening`, default 1).
|
|
39
|
+
- Fuzz uses an analytic approximation. The specification's LTC fuzz (Zeltner et al.
|
|
40
|
+
2022) is not implemented.
|
|
41
|
+
|
|
42
|
+
## Energy compensation
|
|
43
|
+
|
|
44
|
+
GGX loses energy to single scattering as roughness grows. The renderer uses Adobe's
|
|
45
|
+
tables for opaque dielectric base coupling, ideal-metal multiple scattering and
|
|
46
|
+
solid ideal-dielectric reflection and transmission (`openPbrEnergyLut.ts`). Before
|
|
47
|
+
these tables, rough glass lost 5–36% and rough metal 1.5–20% of its energy in white
|
|
48
|
+
furnace tests.
|
|
49
|
+
|
|
50
|
+
The tables are stored as 8-bit samples normalized per row, with 16-bit row bounds,
|
|
51
|
+
differences between consecutive rows and exact 16-bit averages. This keeps the
|
|
52
|
+
module about 44 KB gzipped while adding the multiple-scattering tables. On the GPU
|
|
53
|
+
they occupy about 280 KB.
|
|
54
|
+
|
|
55
|
+
Thin-walled rough transmission has no compensation, as in the reference.
|
|
56
|
+
|
|
57
|
+
## Import decisions
|
|
58
|
+
|
|
59
|
+
- Three.js and glTF clearcoat import with `coatDarkening = 0`, because glTF's
|
|
60
|
+
clearcoat does not darken the base. `userData.mutracerOpenPBR` overrides it.
|
|
61
|
+
- Specification names (`specularRoughnessAnisotropy`, `coatRoughnessAnisotropy`,
|
|
62
|
+
`subsurfaceScatterAnisotropy`, `geometryOpacity`, `geometryThinWalled`) are the
|
|
63
|
+
primary names; older names remain deprecated aliases.
|
|
64
|
+
- glTF anisotropy is converted after texture sampling, selected by
|
|
65
|
+
`specularAnisotropyModel: 'gltf'`.
|
|
66
|
+
|
|
67
|
+
## Not implemented
|
|
68
|
+
|
|
69
|
+
- Statistical mixing of two materials.
|
|
70
|
+
- LTC fuzz.
|
|
71
|
+
- Resampled direct lighting on WebGL. It waits for the cause of a small diffuse
|
|
72
|
+
residual with 8 or 16 WebGPU light candidates; the default of 4 is unaffected.
|
|
73
|
+
|
|
74
|
+
## Known accuracy limits
|
|
75
|
+
|
|
76
|
+
- Rough solid glass remains slightly dark in white furnace tests, under 1% without
|
|
77
|
+
a coat and under 1.5% with one at roughness 0.3. With compensation off it is
|
|
78
|
+
about 2 percentage points darker than the reference, so the remaining loss is in
|
|
79
|
+
the transmission integration, not the tables.
|
|
80
|
+
- Strongly tilted coat normals lose about 5% of their energy, because lobes are
|
|
81
|
+
clipped to the geometric hemisphere. The materials guide documents this.
|
|
82
|
+
- Strong anisotropic metal can retain a small energy error, because the tables use
|
|
83
|
+
isotropic roughness.
|
|
84
|
+
- A coat with a higher IOR than the base can produce spurious total internal
|
|
85
|
+
reflection in the thin film, as in the reference.
|
|
86
|
+
|
|
87
|
+
## Acceptance rules
|
|
88
|
+
|
|
89
|
+
These maintainer rules apply to material and BSDF changes:
|
|
90
|
+
|
|
91
|
+
- A white furnace may deviate from 1 by at most 0.5%, and a coated material from
|
|
92
|
+
its uncoated control by at most 0.5%.
|
|
93
|
+
- Re-encoding the tables may change any material on either backend by at most 0.1%.
|
|
94
|
+
- Measure cold shader compilation with caches invalidated; increases above 10% need
|
|
95
|
+
a maintainer decision.
|
|
96
|
+
- Sparse pixel differences of at most one half-float step from compiler
|
|
97
|
+
reassociation are acceptable.
|
|
98
|
+
- Refactors keep default materials (base weight 1, no coat, isotropic, specular
|
|
99
|
+
color 1) pixel-identical on both backends.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# PBRT import mapping
|
|
2
|
+
|
|
3
|
+
This contributor note describes the viewer/tool importer. Usage and limits are
|
|
4
|
+
in [the tool guide](../../tools/pbrt/README.md).
|
|
5
|
+
|
|
6
|
+
## Coordinates and state
|
|
7
|
+
|
|
8
|
+
The reference is `mmp/pbrt-v4@b0bdf461ba5dd6504b70ebf0932acc0fe7aa744f`.
|
|
9
|
+
PBRT's camera looks along local +Z. With `M = diag(1,1,-1,1)`, geometry uses
|
|
10
|
+
`M * objectToWorld` and the Three camera uses `M * cameraToWorld * M`.
|
|
11
|
+
The world reflection is baked into geometry so emissive triangles have consistent
|
|
12
|
+
world-space winding and normals. The winding changes when the baked transform
|
|
13
|
+
changes handedness, XOR `ReverseOrientation`; explicit normals only change sign
|
|
14
|
+
for `ReverseOrientation`. Triangle winding is then face-forwarded to shading
|
|
15
|
+
normals, matching PBRT meshes with authored N. Instances retain shared geometry
|
|
16
|
+
and use `M * T * M`.
|
|
17
|
+
Loop shapes refine the control mesh and push vertices to the limit surface;
|
|
18
|
+
area-weighted normals approximate the reference limit-surface tangents.
|
|
19
|
+
A reflection is absorbed into a geometry variant for negative instance transforms.
|
|
20
|
+
|
|
21
|
+
`LookAt` sets world-to-camera, so its inverse is captured at `Camera`. `WorldBegin`
|
|
22
|
+
resets the object transform. Include filenames and all asset filenames resolve
|
|
23
|
+
against the main scene directory, as `SetSearchDirectory`/`ResolveFilename` do.
|
|
24
|
+
`Import` inherits graphics state but restores it afterward; definitions and scene
|
|
25
|
+
objects remain. Deprecated transform blocks follow PBRT-v4's attribute-block
|
|
26
|
+
rewrite. Only the start transform is retained for animated scenes.
|
|
27
|
+
|
|
28
|
+
## Camera and microfacets
|
|
29
|
+
|
|
30
|
+
For aspect `a = width/height`, the vertical field of view is `fov` when `a >= 1`,
|
|
31
|
+
otherwise `2 atan(tan(fov/2)/a)`. Lens conversion uses a 24 mm sensor height:
|
|
32
|
+
`focalLength = .024/(2 tan(verticalFov/2))` metres and
|
|
33
|
+
`fStop = focalLength/(2 lensRadius)` for metre scene units.
|
|
34
|
+
The focus distance stays in scene units. The linear exposure multiplier is
|
|
35
|
+
`ISO/100 * (shutterClose-shutterOpen)`. Film exposuretime is not a PBRT-v4
|
|
36
|
+
parameter and is warned and ignored, as is whitebalance. Unsupported sensors
|
|
37
|
+
produce a separate warning.
|
|
38
|
+
|
|
39
|
+
The pinned `TrowbridgeReitzDistribution::RoughnessToAlpha` returns `sqrt(r)`.
|
|
40
|
+
Since OpenPBR uses `alpha = roughness²`, the imported roughness is `r^(1/4)` with
|
|
41
|
+
remapping and `sqrt(r)` without it. For anisotropic alphas, the scalar roughness
|
|
42
|
+
is `((alphaU²+alphaV²)/2)^(1/4)` and anisotropy is
|
|
43
|
+
`1-min(alphaU,alphaV)/max(alphaU,alphaV)` (zero for two zero axes). This inverts
|
|
44
|
+
OpenPBR's RMS-preserving formula for both base and coat. The larger alpha is
|
|
45
|
+
assigned to the tangent axis; rotation is π/2 when v is larger, so u stays dpdu.
|
|
46
|
+
Anisotropic meshes also carry UV-derived tangents for the zero-rotation path.
|
|
47
|
+
|
|
48
|
+
## Colors and light units
|
|
49
|
+
|
|
50
|
+
RGB inputs are linear sRGB. RGB illumination preserves the RGB multiplier;
|
|
51
|
+
PBRT normalizes the illuminant separately in `SpectrumToPhotometric`. The pinned
|
|
52
|
+
implementation does **not** multiply this integral by 683. The Film's CIE sensor
|
|
53
|
+
integrates the emitted spectrum; this cancels the illuminant normalization for
|
|
54
|
+
white light. Consequently a point light's RGB intensity maps directly to the
|
|
55
|
+
zero-radius SphereLight intensity, and an area light's RGB radiance maps to mesh
|
|
56
|
+
emission. Spot cone angles are half angles and use cosine smoothstep on both sides.
|
|
57
|
+
Distant intensity is irradiance facing the light. Sphere emitters retain meshes:
|
|
58
|
+
MuTracer analytic sphere lights currently do not supply visible primary-ray
|
|
59
|
+
emission, and adding a second emissive sampler would count the source twice.
|
|
60
|
+
|
|
61
|
+
Power multiplies point intensity by `power/(4π)`. For a spot it multiplies by
|
|
62
|
+
`power/[2π(1-(cos(inner)+cos(outer))/2)]`. Area-light power divides by
|
|
63
|
+
`π * area * (twosided ? 2 : 1)`. Image emission also requires texture-average
|
|
64
|
+
normalization; that case remains an approximation. Constant environment
|
|
65
|
+
illuminance multiplies radiance by `illuminance/π`. Image illuminance follows
|
|
66
|
+
the pinned renderer's `2π/N * sum(Y(pixel)*max(z,0))` normalization in light space.
|
|
67
|
+
|
|
68
|
+
Named optical data and CIE/D65 tables come from the pinned Apache-2.0 source.
|
|
69
|
+
Conductor normal and edge reflectances integrate Fresnel at cosines 1 and 1/7
|
|
70
|
+
under D65, then convert XYZ to linear sRGB. OpenPBR takes an F82 **tint**,
|
|
71
|
+
so the edge value is divided by `F0+(1-F0)*(6/7)^5` and limited to [0,1]. Glass uses interpolated eta at
|
|
72
|
+
587.56 nm and Abbe `(etaD-1)/(etaF-etaC)`, at 486.13 and 656.27 nm.
|
|
73
|
+
Blackbody emission is normalized to unit luminance before light scaling.
|
|
74
|
+
An RGB renderer and PBRT's spectral renderer can retain material-dependent
|
|
75
|
+
biases; comparisons report them without changing either renderer's algorithms.
|
|
76
|
+
Coated conductor inputs keep `conductor.*` substrate parameters separate from
|
|
77
|
+
`interface.*` coat parameters. Both roughness layers use the same remap rule;
|
|
78
|
+
optical constants are read as curves from named data or SPD files before Fresnel
|
|
79
|
+
integration. Coat IOR and anisotropy use the existing OpenPBR metadata fields.
|
|
80
|
+
|
|
81
|
+
For coefficient-based subsurface materials, each RGB extinction mean free path is
|
|
82
|
+
`1 / (scale * (sigma_a + sigma_s))`. The largest channel becomes the OpenPBR
|
|
83
|
+
radius and channel ratios become radius scales. Single-scattering albedo
|
|
84
|
+
`sigma_s / (sigma_a + sigma_s)` is converted to the artist-facing subsurface color
|
|
85
|
+
by inverting the existing material packer's mapping. Authored diffuse reflectance
|
|
86
|
+
is already an effective scattering color. These mappings preserve the RGB medium
|
|
87
|
+
coefficients but do not reproduce PBRT's spectral diffusion model.
|
|
88
|
+
Interior homogeneous media encode total extinction, not absorption, in
|
|
89
|
+
transmission color: choose depth `d=1/max(1,max(sigma_t))`, set color
|
|
90
|
+
`exp(-sigma_t*d)` and scatter color `sigma_s/sigma_t`, with scaled coefficients.
|
|
91
|
+
This preserves the existing packer's coefficients without hitting its color floor.
|
|
92
|
+
`MediumInterface` orders inside then outside. The geometric normal points toward
|
|
93
|
+
outside, after ReverseOrientation and handedness. The importer rejects scenes
|
|
94
|
+
with a camera medium. If only an outside medium remains, conversion flips
|
|
95
|
+
winding and normals together and encodes it as the material interior. Two
|
|
96
|
+
media cannot be represented: conversion warns and uses inside only.
|
|
97
|
+
Material variants include both names to avoid sharing different boundaries.
|
|
98
|
+
|
|
99
|
+
The extra medium-side reversal reciprocates thick dielectric IOR: PBRT's
|
|
100
|
+
DielectricBxDF exchanges eta across its final normal. ReverseOrientation and
|
|
101
|
+
coordinate handedness already affect both normals and do not independently
|
|
102
|
+
reciprocate IOR. ThinDielectricBxDF uses AbsCosTheta and keeps eta unchanged.
|
|
103
|
+
Base specular IOR below one survives Three conversion and material packing;
|
|
104
|
+
both backends use the ratio for Fresnel, total internal reflection and refraction.
|
|
105
|
+
Coat IOR is clamped to at least one and the coat is exterior-only, so reversed
|
|
106
|
+
coats and coat IOR below one receive an ior-orientation warning and are excluded
|
|
107
|
+
from strict comparison. Reciprocal spectral dispersion retains the original
|
|
108
|
+
Abbe number, but a reciprocal Cauchy curve is not itself the same Cauchy fit;
|
|
109
|
+
this receives an ior-dispersion approximation warning and comparison exclusion.
|
|
110
|
+
|
|
111
|
+
Ordinary PBRT surfaces accept primary back-face hits. One-sided area emission
|
|
112
|
+
uses FrontSide because MuTracer couples primary visibility and emission sidedness;
|
|
113
|
+
its missing back-side surface visibility is warned about.
|
|
114
|
+
|
|
115
|
+
PBRT image textures evaluate `(s,1-t)`. Decoded images use top-down rows and
|
|
116
|
+
Three textures set `flipY=true`. Infinite lights use the equal-area square map,
|
|
117
|
+
reprojected to world-space latitude/longitude. The direct importer uses nearest
|
|
118
|
+
source lookup to match ImageInfiniteLight. Each target texel averages 4×4
|
|
119
|
+
jittered samples uniform in azimuth and cos(theta), then per-channel spherical
|
|
120
|
+
integrals correct residual quadrature energy error. Source texels use area 4π/N;
|
|
121
|
+
target bands use exact cosine differences. This preserves small bright features'
|
|
122
|
+
total energy while approximating their subpixel distribution. The existing glTF
|
|
123
|
+
metadata adapter retains its previous bilinear reprojection.
|
|
124
|
+
|
|
125
|
+
Material displacement uses forward UV finite differences with PBRT's `.0005`
|
|
126
|
+
fallback step, including scalar texture scale and UV repeat. The image-row
|
|
127
|
+
v derivative changes sign for `(s,1-t)`. The result is a tangent normal
|
|
128
|
+
`normalize(-dh/du,-dh/dv,1)`, never a geometric height field. This static bake
|
|
129
|
+
assumes unit tangents and omits footprint-dependent steps and curvature terms;
|
|
130
|
+
it is warned and excluded from strict comparison. Only shape displacement uses
|
|
131
|
+
the height-field mapping. In the pinned `interaction.cpp`, NormalMap and BumpMap
|
|
132
|
+
are alternatives (`if/else`): normalmap takes precedence when both exist.
|
|
133
|
+
|
|
134
|
+
## Comparison and memory
|
|
135
|
+
|
|
136
|
+
Ordinary byte color maps retain their original data and declared color space;
|
|
137
|
+
unmodified linear HDR maps also share decoded pixel data. Scale graphs carry
|
|
138
|
+
separate multipliers. Scalar and transformed maps retain
|
|
139
|
+
floating-point values. Rectangular/non-power-of-two height fields are resampled
|
|
140
|
+
to the existing displacement layout, with a warning.
|
|
141
|
+
|
|
142
|
+
The comparison sets PBRT's pixel filter to box with radius 0.5 to match MuTracer's
|
|
143
|
+
uniform pixel jitter. It overrides spp and output resolution, removes Film crop
|
|
144
|
+
windows and pixel bounds, preserves maxdepth,
|
|
145
|
+
sets an opaque black background when no environment exists, removes Film sensor exposure from the reference HDR and never tone maps numerical
|
|
146
|
+
inputs. AOV material IDs identify approximate surface regions. This primary-hit
|
|
147
|
+
mask does not isolate indirect paths that touch approximate materials. PBRT uses
|
|
148
|
+
seeds 0/1; MuTracer uses disjoint sample blocks starting at 0/N with the same Sobol
|
|
149
|
+
scramble. Their difference gives a diagnostic noise estimate, not independent
|
|
150
|
+
Monte Carlo replicas or statistical proof of equivalence.
|
|
151
|
+
Whole-frame RMSE retains every comparable pixel and its noise margin. Regional
|
|
152
|
+
material/tile tests exclude discontinuities in object, instance or material IDs,
|
|
153
|
+
dilated by one pixel. Their statistic is absolute channel mean bias / standard
|
|
154
|
+
error, with threshold 6. Standard error is sqrt(sum of per-pixel replica-difference
|
|
155
|
+
variances) / pixel count; squaring a region-sum difference instead gives an
|
|
156
|
+
unstable single observation. Whole-frame and regional statuses are separate.
|
|
157
|
+
Each MuTracer run uses a fresh Chrome process to avoid retaining both scenes' caches
|
|
158
|
+
and allocations during memory measurement.
|
|
159
|
+
|
|
160
|
+
Text and gzip Includes are decoded by statements to avoid the single-string size
|
|
161
|
+
limit. The viewer parses them and PLY data in a Worker. Inline P/N/UV/index
|
|
162
|
+
arrays become
|
|
163
|
+
transferable typed views before leaving the Worker; statement locations share
|
|
164
|
+
the same parameter objects. Binary assets are prefetched six at a time before
|
|
165
|
+
conversion; progress sums completed and in-flight bytes. The download preflight
|
|
166
|
+
uses unique encoded input sizes and an 8x expansion estimate against an 8 GiB
|
|
167
|
+
working budget. This estimate is deliberately conservative and cannot predict
|
|
168
|
+
total process/GPU memory or prevent every browser allocation failure.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Tile scheduling
|
|
2
|
+
|
|
3
|
+
A full-frame path-tracing sample can take longer than a display frame and, on slow
|
|
4
|
+
scenes, long enough to stall the browser or trigger a GPU timeout. Renders therefore
|
|
5
|
+
trace each sample as a grid of tiles, sized so that one tile fits `timeBudgetMs`.
|
|
6
|
+
Usage and the related options are in
|
|
7
|
+
[sample batching](../progressive-rendering.md#sample-batching); this note explains how
|
|
8
|
+
the tile grid is chosen. The code is `src/renderer/TileRender.ts`.
|
|
9
|
+
|
|
10
|
+
## Two predictors
|
|
11
|
+
|
|
12
|
+
- **WebGPU with timestamp queries** uses `makeTileRenderWithPredict`, which reads
|
|
13
|
+
the GPU time of each submitted grid.
|
|
14
|
+
- **WebGL 2, and WebGPU without timestamp queries,** use `makeTileRender`, adapted
|
|
15
|
+
from ray-tracing-renderer. It adjusts a pixels-per-tile count from wall-clock
|
|
16
|
+
feedback, never below 8192 pixels. WebGL timer queries are not reliable enough to
|
|
17
|
+
replace it; see [WebGL 2 backend](./webgl.md).
|
|
18
|
+
|
|
19
|
+
The rest of this note describes the WebGPU predictor.
|
|
20
|
+
|
|
21
|
+
## Prediction
|
|
22
|
+
|
|
23
|
+
The first sample uses a probe tile shaped like the frame, large enough that dispatch
|
|
24
|
+
overhead does not dominate it. Each later measurement scales the tile area by the
|
|
25
|
+
ratio of the budget to the measured tile time, limited to 4× (2× per dimension) per
|
|
26
|
+
update so the grid converges without jumping from a small probe to a blocking
|
|
27
|
+
full-frame tile. Tiles are never narrower than one compute workgroup (8 pixels):
|
|
28
|
+
a smaller dispatch occupies the same workgroups and repeats their fixed cost.
|
|
29
|
+
|
|
30
|
+
The grid is interleaved, so each sample still covers every pixel once. When the
|
|
31
|
+
predicted tile exceeds the frame, several samples are traced per frame instead.
|
|
32
|
+
|
|
33
|
+
## Why area-proportional prediction is not enough
|
|
34
|
+
|
|
35
|
+
Tile time is not proportional to tile area. A tile pays a fixed cost for dispatch
|
|
36
|
+
and for its longest path, and small frames leave most of the GPU idle. On a heavy
|
|
37
|
+
scene at a small resolution, 16 tiles can each take more than half the time of the
|
|
38
|
+
whole frame. An area-proportional predictor keeps subdividing toward the budget and
|
|
39
|
+
multiplies the cost of every sample.
|
|
40
|
+
|
|
41
|
+
Two rules handle this:
|
|
42
|
+
|
|
43
|
+
- **Refinement check.** When the predictor subdivides, it compares the next
|
|
44
|
+
measurement with the grid that motivated the change. If the new tile time
|
|
45
|
+
improves by less than half of the area ratio, and still misses the budget by more
|
|
46
|
+
than 10%, the predictor returns to the previous grid and uses its tile size as a
|
|
47
|
+
floor until prediction is invalidated.
|
|
48
|
+
- **Unattainable budgets.** From two measurements at sufficiently different tile
|
|
49
|
+
areas, the predictor fits tile time as `fixed + perPixel × area`. If the fixed
|
|
50
|
+
part alone exceeds the budget by more than 10%, no grid can meet it. The predictor then picks the
|
|
51
|
+
fewest tiles that keep a tile below 350 ms (over 5× below a 2 s GPU timeout) and,
|
|
52
|
+
where possible, keep the repeated fixed cost below one fifth of the sample. This
|
|
53
|
+
decision is retained until prediction is invalidated, because refitting on its
|
|
54
|
+
own grids lets path-cost noise alternate between tile counts.
|
|
55
|
+
|
|
56
|
+
`maxTiles` still caps the grid in both cases. A long tile is a deliberate trade:
|
|
57
|
+
fewer updates while interacting, in exchange for not multiplying the cost of
|
|
58
|
+
every sample. The guide documents the resulting pauses.
|
|
59
|
+
|
|
60
|
+
## Measurements
|
|
61
|
+
|
|
62
|
+
Several samples may be submitted before their timestamps resolve. Each measurement
|
|
63
|
+
is matched to the grid it was submitted with and consumed once, so stale results
|
|
64
|
+
cannot shrink tiles repeatedly. Scene, view and setting changes invalidate the
|
|
65
|
+
prediction (`needsUpdatePredicting`). Photon and light-tracing batches run with the
|
|
66
|
+
first and last tile and are timed separately.
|
|
67
|
+
|
|
68
|
+
## Changing this code
|
|
69
|
+
|
|
70
|
+
- Scheduling changes must update `tests/fixtures/display-schedule.json` and the
|
|
71
|
+
schedule tests, and pass `npm run test:parity`; see `AGENTS.md`.
|
|
72
|
+
- Judge changes by total sample time and by tile time, at both small and normal
|
|
73
|
+
resolutions on a heavy scene. Fixed-seed HDR must stay identical, because tiling
|
|
74
|
+
changes only the order of work.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# WebGL 2 backend
|
|
2
|
+
|
|
3
|
+
The WebGL 2 backend runs the same path tracer as fragment shaders. It is the
|
|
4
|
+
fallback where WebGPU is unavailable, so it favors working everywhere over speed.
|
|
5
|
+
This note lists the constraints that shape it and the open issues known to
|
|
6
|
+
contributors. Feature differences visible to users are documented with each
|
|
7
|
+
feature.
|
|
8
|
+
|
|
9
|
+
## Constraints
|
|
10
|
+
|
|
11
|
+
- **Texture units.** WebGL 2 guarantees 16 fragment texture units. Per-vertex
|
|
12
|
+
attributes (normals, UVs, tangents, colors, material indices) share one
|
|
13
|
+
interleaved RGBA32F texture, positions keep their own for triangle tests, and
|
|
14
|
+
instance records follow the BVH in its texture. `webglSamplerBudget.test.ts`
|
|
15
|
+
keeps every trace pass at or below 14 referenced samplers, leaving two for new
|
|
16
|
+
features. Add a sampler only after checking that budget.
|
|
17
|
+
- **Uniforms.** Large scenes can exceed `MAX_FRAGMENT_UNIFORM_VECTORS` (1024 on
|
|
18
|
+
common devices) and fail to render. The renderer does not yet fail soft there.
|
|
19
|
+
- **Path length.** `bounces` and path events stop at 20 on WebGL, and Intel Macs use
|
|
20
|
+
a shorter event limit. WebGPU allows 50.
|
|
21
|
+
- **Compilation.** ANGLE defers native compilation, so a first draw can block for
|
|
22
|
+
seconds, much longer than WebGPU. Trace programs request parallel compilation
|
|
23
|
+
and are polled before use. Per-program timings do not measure that program's
|
|
24
|
+
compile time.
|
|
25
|
+
- **Timing.** Timer queries can report totals longer than the wall-clock time and
|
|
26
|
+
cannot drive scheduling or performance claims. Tiles are sized from wall-clock
|
|
27
|
+
feedback; see [tile scheduling](./tiling.md). Measure WebGL performance by wall
|
|
28
|
+
time until the HDR readback completes.
|
|
29
|
+
- **WebGPU-only features.** Caustic estimators, display post-processing,
|
|
30
|
+
display-resolution AOVs and custom denoisers fail soft on WebGL. Caustic paths
|
|
31
|
+
fall back to path tracing, so the expected image is unchanged.
|
|
32
|
+
|
|
33
|
+
## Open issues
|
|
34
|
+
|
|
35
|
+
- **Background in some scenes.** On at least one scene, WebGL terminates many
|
|
36
|
+
paths at depth 0 or on a miss where WebGPU continues, and part of the background
|
|
37
|
+
renders black. Primary-miss and environment behavior should be aligned before
|
|
38
|
+
WebGL is used as a cross-backend reference for such scenes.
|
|
39
|
+
- **Long single draws.** With dispersion, one fragment invocation traces all three
|
|
40
|
+
channel suffixes ([shared prefixes](./continuous-spectrum.md#shared-prefixes)).
|
|
41
|
+
Large renders with a single tile have lost the context, likely from a GPU
|
|
42
|
+
timeout. Options are to size tiles for three times the work or to fall back to
|
|
43
|
+
three draws on large images.
|
|
44
|
+
- **Context loss.** Some driver failures lose the context and later reads return
|
|
45
|
+
zeros. Treat an all-black WebGL capture as a failed run, not a result.
|
|
46
|
+
|
|
47
|
+
## Changing this code
|
|
48
|
+
|
|
49
|
+
- Implement material, light and sampling changes in both WGSL and GLSL; see
|
|
50
|
+
`AGENTS.md`.
|
|
51
|
+
- Run GPU comparisons headless, one context at a time. When a WebGL capture fails
|
|
52
|
+
with context loss, rerun it before drawing conclusions, and do not compare its
|
|
53
|
+
output.
|
package/docs/viewer.md
CHANGED
|
@@ -34,7 +34,17 @@ and frames the model.
|
|
|
34
34
|
Reloading the current model restores your saved camera selection.
|
|
35
35
|
|
|
36
36
|
Picking an object in the viewport reveals it in the scene tree and scrolls it
|
|
37
|
-
into view.
|
|
37
|
+
into view. Selection follows the rendered geometry, including
|
|
38
|
+
[displacement on WebGPU](materials.md#displacement) and alpha-cutout holes;
|
|
39
|
+
it becomes available once the scene is ready to render.
|
|
40
|
+
Material rows start collapsed; expand a mesh to see its materials.
|
|
41
|
+
|
|
42
|
+
The viewer caches prepared BVHs to speed up reopening unchanged models.
|
|
43
|
+
Models still need to be loaded and decoded. The cache has a 4 GiB browser-storage
|
|
44
|
+
budget; entries are removed as space is needed. Cached data is reused only when
|
|
45
|
+
geometry, displacement and BVH settings match. Loading still works if storage
|
|
46
|
+
is unavailable or full.
|
|
47
|
+
Clear the site's browser storage to remove the cache.
|
|
38
48
|
|
|
39
49
|
## Sharing viewer settings
|
|
40
50
|
|
package/lib/Display.d.ts
CHANGED
|
@@ -36,11 +36,25 @@ export interface PostProcessContext {
|
|
|
36
36
|
/** Engine device; may change after recovery. */ device: GPUDevice;
|
|
37
37
|
/** Record commands here; the library finishes and submits it. */ encoder: GPUCommandEncoder;
|
|
38
38
|
/** Read-only composed display image, top row first; no additional color conversion. */ input: GPUTexture;
|
|
39
|
-
/** Fully write this image. Same format
|
|
40
|
-
/** Actual render width. */ width: number;
|
|
41
|
-
/** Actual render height. */ height: number;
|
|
39
|
+
/** Fully write this image. Same format as input; dimensions follow outputSize; supports render, storage and copy writes. */ output: GPUTexture;
|
|
40
|
+
/** Actual render width; pass dimensions are input.width and output.width. */ width: number;
|
|
41
|
+
/** Actual render height; pass dimensions are input.height and output.height. */ height: number;
|
|
42
42
|
/** Selected complete render, including a selected fallback. */ render: Render;
|
|
43
43
|
}
|
|
44
|
+
/** Optional dimensions for a display pass. */
|
|
45
|
+
export interface PostProcessOptions {
|
|
46
|
+
/** Output dimensions per invocation, bounded by maxTextureDimension2D. Default undefined preserves input size. Display is the current canvas size, including during export. */
|
|
47
|
+
outputSize?: (input: {
|
|
48
|
+
width: number;
|
|
49
|
+
height: number;
|
|
50
|
+
}, display: {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
}) => {
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
44
58
|
/** Synchronous display pass. Images and encoder are borrowed only during the call; do not retain or destroy them. */
|
|
45
59
|
export type PostProcess = (context: PostProcessContext) => void;
|
|
46
60
|
/** Canvas presentation and shared display settings; obtained from engine.display. */
|
|
@@ -73,11 +87,11 @@ export default class Display {
|
|
|
73
87
|
private readonly _postProcesses;
|
|
74
88
|
private _warnedPostProcess;
|
|
75
89
|
/** Registers a display pass in order and returns an idempotent removal function. Default no passes. WebGPU only; WebGL skips passes with one warning. Runs after composition on every present/toBlob, without tracing or invalidating denoising. A thrown error discards the whole chain, presents the original image and emits engine error. */
|
|
76
|
-
addPostProcess(pass: PostProcess): () => void;
|
|
90
|
+
addPostProcess(pass: PostProcess, options?: PostProcessOptions): () => void;
|
|
77
91
|
private warnPostProcess;
|
|
78
92
|
private compose;
|
|
79
93
|
/** Presents a complete image or fallback. Keeps fallback visible until the first intermediate filter finishes. Does not trace. */
|
|
80
94
|
present(render: Render): Render | undefined;
|
|
81
|
-
/** Encodes
|
|
95
|
+
/** Encodes the display chain at its final dimensions (default render dimensions), cropping fixed renders before passes using these settings; leaves the display canvas unchanged. */
|
|
82
96
|
toBlob(render: Render, options?: BlobOptions): Promise<Blob>;
|
|
83
97
|
}
|
package/lib/Display.js
CHANGED
|
@@ -68,8 +68,8 @@ export default class Display {
|
|
|
68
68
|
_postProcesses = [];
|
|
69
69
|
_warnedPostProcess = false;
|
|
70
70
|
/** Registers a display pass in order and returns an idempotent removal function. Default no passes. WebGPU only; WebGL skips passes with one warning. Runs after composition on every present/toBlob, without tracing or invalidating denoising. A thrown error discards the whole chain, presents the original image and emits engine error. */
|
|
71
|
-
addPostProcess(pass) {
|
|
72
|
-
const entry = { pass };
|
|
71
|
+
addPostProcess(pass, options = {}) {
|
|
72
|
+
const entry = { pass, options: { ...options } };
|
|
73
73
|
this._postProcesses.push(entry);
|
|
74
74
|
if (this.engine.backend === 'webgl')
|
|
75
75
|
this.warnPostProcess();
|
|
@@ -82,7 +82,7 @@ export default class Display {
|
|
|
82
82
|
console.warn('Display post-processing requires WebGPU; passes are ignored on WebGL.');
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
compose(render) {
|
|
85
|
+
compose(render, exporting = false) {
|
|
86
86
|
if (render._engine !== this.engine)
|
|
87
87
|
throw new Error('Render belongs to another engine.');
|
|
88
88
|
const target = render._output;
|
|
@@ -100,9 +100,11 @@ export default class Display {
|
|
|
100
100
|
if (this._postProcesses.length && this.engine.backend === 'webgl')
|
|
101
101
|
this.warnPostProcess();
|
|
102
102
|
try {
|
|
103
|
-
target.applyPostProcesses?.(this._postProcesses.map(entry => entry.pass), render);
|
|
103
|
+
target.applyPostProcesses?.(this._postProcesses.map(entry => entry.pass), render, this._postProcesses.map(entry => entry.options), exporting ? render._exportCrop : undefined);
|
|
104
104
|
}
|
|
105
105
|
catch (error) {
|
|
106
|
+
// Preserve the pre-chain export crop even when a callback or size fails.
|
|
107
|
+
target.applyPostProcesses?.([], render, [], exporting ? render._exportCrop : undefined);
|
|
106
108
|
this.engine._notifyError(error);
|
|
107
109
|
}
|
|
108
110
|
return target;
|
|
@@ -122,12 +124,13 @@ export default class Display {
|
|
|
122
124
|
this.engine._present(target);
|
|
123
125
|
return selected;
|
|
124
126
|
}
|
|
125
|
-
/** Encodes
|
|
127
|
+
/** Encodes the display chain at its final dimensions (default render dimensions), cropping fixed renders before passes using these settings; leaves the display canvas unchanged. */
|
|
126
128
|
async toBlob(render, options = {}) {
|
|
127
|
-
const target = this.compose(render);
|
|
129
|
+
const target = this.compose(render, true);
|
|
128
130
|
if (!target)
|
|
129
131
|
throw new Error('Render has no complete image.');
|
|
130
|
-
const
|
|
132
|
+
const presentation = await target.readPresentation();
|
|
133
|
+
const image = this.engine.backend === 'webgpu' ? presentation : render._cropOutput(presentation);
|
|
131
134
|
const canvas = createCanvas(image.width, image.height, 'convertToBlob' in this.canvas);
|
|
132
135
|
const context = canvas.getContext('2d');
|
|
133
136
|
if (!context)
|