@meimu/mutracer 0.2.0 → 0.3.0
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/README.md +3 -0
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +8949 -7915
- package/docs/api.md +42 -21
- package/docs/caustics.md +80 -0
- package/docs/materials.md +5 -4
- package/docs/progressive-rendering.md +50 -9
- package/docs/technical/continuous-spectrum.md +93 -0
- package/docs/updates.md +3 -1
- package/docs/viewer-checklist.md +65 -0
- package/docs/viewer.md +7 -5
- package/lib/Display.js +1 -1
- package/lib/Display.js.map +1 -1
- package/lib/Engine.d.ts +2 -0
- package/lib/Engine.js +47 -12
- package/lib/Engine.js.map +1 -1
- package/lib/Render.d.ts +61 -13
- package/lib/Render.js +169 -19
- package/lib/Render.js.map +1 -1
- package/lib/RenderLoop.d.ts +7 -3
- package/lib/RenderLoop.js +68 -44
- package/lib/RenderLoop.js.map +1 -1
- package/lib/main.d.ts +1 -1
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/oidnBundledWeights.cdn.d.ts +2 -2
- package/lib/renderer/GPUTraceQueue.d.ts +2 -1
- package/lib/renderer/GPUTraceQueue.js +3 -1
- package/lib/renderer/GPUTraceQueue.js.map +1 -1
- package/lib/renderer/RenderingPipeline.d.ts +4 -0
- package/lib/renderer/RenderingPipeline.js.map +1 -1
- package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
- package/lib/renderer/WGPUCausticLightPass.js +217 -0
- package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
- package/lib/renderer/WGPUEnvMapDistribution.js +18 -18
- package/lib/renderer/WGPUFullQuadPass.js +3 -3
- package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
- package/lib/renderer/WGPUPhotonMap.js +191 -0
- package/lib/renderer/WGPUPhotonMap.js.map +1 -0
- package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
- package/lib/renderer/WGPURayTraceOutput.js +122 -28
- package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
- package/lib/renderer/WGPURayTracePass.d.ts +48 -17
- package/lib/renderer/WGPURayTracePass.js +538 -340
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +83 -47
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
- package/lib/renderer/WebGLFullQuadPass.js +4 -0
- package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
- package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +6 -1
- package/lib/renderer/WebGLRayTracePass.js +43 -6
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/WebGLRenderingPipeline.js +10 -14
- package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLSkyBaker.js +60 -60
- package/lib/renderer/accumulationTarget.d.ts +2 -1
- package/lib/renderer/accumulationTarget.js.map +1 -1
- package/lib/renderer/causticDiagnostics.d.ts +31 -0
- package/lib/renderer/causticDiagnostics.js +5 -0
- package/lib/renderer/causticDiagnostics.js.map +1 -0
- package/lib/renderer/causticEmission.d.ts +10 -0
- package/lib/renderer/causticEmission.js +40 -0
- package/lib/renderer/causticEmission.js.map +1 -0
- package/lib/renderer/causticUniform.d.ts +12 -0
- package/lib/renderer/causticUniform.js +22 -0
- package/lib/renderer/causticUniform.js.map +1 -0
- package/lib/renderer/caustics.d.ts +16 -0
- package/lib/renderer/caustics.js +93 -0
- package/lib/renderer/caustics.js.map +1 -0
- package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
- package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
- package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
- package/lib/renderer/glsl/aovTrace.frag.js +33 -33
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
- package/lib/renderer/glsl/chunks/intersect.glsl.js +426 -428
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
- package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +68 -63
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +140 -117
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
- package/lib/renderer/glsl/chunks/sampling.glsl.js +89 -92
- package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
- 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 +2 -2
- package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
- package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
- package/lib/renderer/glsl/rayTrace.frag.js +260 -237
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +26 -26
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
- package/lib/renderer/lightSampling.d.ts +3 -2
- package/lib/renderer/lightSampling.js +14 -8
- package/lib/renderer/lightSampling.js.map +1 -1
- package/lib/renderer/progressiveScheduler.js +4 -3
- package/lib/renderer/progressiveScheduler.js.map +1 -1
- package/lib/renderer/renderPipeline.d.ts +1 -0
- package/lib/renderer/renderPipeline.js.map +1 -1
- package/lib/renderer/researchTransport.d.ts +31 -0
- package/lib/renderer/researchTransport.js +5 -0
- package/lib/renderer/researchTransport.js.map +1 -0
- 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/scratchSampling.js +6 -6
- package/lib/renderer/spectrum.d.ts +21 -0
- package/lib/renderer/spectrum.js +154 -0
- package/lib/renderer/spectrum.js.map +1 -0
- package/lib/renderer/webglLightSampling.d.ts +39 -0
- package/lib/renderer/webglLightSampling.js +15 -0
- package/lib/renderer/webglLightSampling.js.map +1 -0
- package/lib/renderer/wgpuBindlessSource.js +3 -3
- package/lib/renderer/wgpuShaderSource.d.ts +7 -0
- package/lib/renderer/wgpuShaderSource.js +2 -0
- package/lib/renderer/wgpuShaderSource.js.map +1 -1
- package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
- package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
- package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovResolve.wgsl.js +2 -2
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
- package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
- package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
- package/lib/renderer/wgsl/causticLight.compute.js +537 -0
- package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +598 -592
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +14 -14
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +75 -73
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +794 -794
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
- package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +35 -25
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +204 -187
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +89 -93
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/compose.frag.wgsl.js +98 -98
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.js +2 -2
- package/lib/renderer/wgsl/jnlm.compute.js +74 -74
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +25 -25
- package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
- package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
- package/lib/renderer/wgsl/photonMap.compute.js +352 -0
- package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
- package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
- package/lib/renderer/wgsl/rayTrace.compute.d.ts +9 -1
- package/lib/renderer/wgsl/rayTrace.compute.js +448 -522
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/scene/MaterialState.d.ts +2 -2
- package/lib/scene/MaterialState.js +2 -2
- package/lib/scene/MaterialState.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 +2 -2
- 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/llms.txt +2 -1
- package/package.json +3 -3
package/docs/api.md
CHANGED
|
@@ -130,38 +130,40 @@ other sequences retain their completed samples.
|
|
|
130
130
|
| `aovs` / `aovSamples` | `[]` / `16` | Optional [surface outputs](#surface-outputs) / independent positive integer sample cap |
|
|
131
131
|
| `bounces` / `clamping` | `3` / `0` | Scattering depth / per-sample radiance clamp; 0 disables clamping |
|
|
132
132
|
| `samplingPattern` | `'sobol'` | Sobol, ZSobol or random; see [sampling](./sampling.md) |
|
|
133
|
+
| `caustics` | `undefined` | Optional caustics; all estimators default to off. See [caustics](#caustics) |
|
|
133
134
|
| `directLightCandidates` | `4` | WebGPU light candidates, clamped 1–16; ignored on WebGL |
|
|
134
135
|
| `bevel` / `bevelSamples` | `true` / `8` | Bevel shading / probes, clamped 1–32 |
|
|
135
|
-
| `dispersionWavelengths` | `3` |
|
|
136
|
+
| `dispersionWavelengths` | `3` | One continuous wavelength per RGB channel, or one randomly chosen channel |
|
|
136
137
|
| `alphaBlendStrategy` | `'stochastic'` | WebGPU primary BLEND policy; ignored on WebGL |
|
|
137
138
|
| `transmissiveOutputAlphaMin` | `1` | Path-depth alpha rule over transparent backgrounds; changes restart this render |
|
|
138
139
|
| `resolutionScale` | `1` | Relative to display canvas; exclusive with fixed width/height |
|
|
139
|
-
| `width` / `height` | undefined | Paired fixed constructor dimensions; getters
|
|
140
|
+
| `width` / `height` | undefined | Paired fixed constructor dimensions; `toBlob`, `readPixels` and the getters return exactly this size |
|
|
140
141
|
| `adaptiveResolution` | `false` | `{targetFrameMs, minScale}` for scale-based renders; both required, positive, minScale ≤ resolutionScale |
|
|
141
|
-
| `timeBudgetMs` / `maxTiles` | `21` / `Infinity` |
|
|
142
|
-
| `batchSamples` | `false` | Let one refine submit several samples within timeBudgetMs; see [sample batching](./progressive-rendering.md#sample-batching). `allowMultipleSamplesPerFrame` is a deprecated alias |
|
|
142
|
+
| `timeBudgetMs` / `maxTiles` | `21` / `Infinity` | Target tile time / maximum tile grid size per sample (rounded up to a rectangular grid); fewer tiles make larger tiles, without limiting work per frame or call |
|
|
143
|
+
| `batchSamples` | `false` | Let one refine submit several samples within an estimated timeBudgetMs; one sample or tile can exceed it; see [sample batching](./progressive-rendering.md#sample-batching). `allowMultipleSamplesPerFrame` is a deprecated alias |
|
|
143
144
|
| `fallback` | `null` | Single-level Render on the same engine |
|
|
144
145
|
| `denoiser` | `null` | Per-render configuration; read back through the frozen `denoiser` getter and change with `setDenoiser()`; see [denoising](./denoising.md) |
|
|
145
146
|
|
|
146
147
|
`bounces` is rounded and clamped to [1, 20] in both backends; NaN becomes 1.
|
|
147
|
-
|
|
148
|
-
alpha skips and escape checks. Opaque paths also
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
Paths allow at most `min(bounces * 3, 20)` events, including transmission,
|
|
149
|
+
alpha skips and escape checks. Opaque paths also terminate at their scattering
|
|
150
|
+
limit. The event budget can truncate deep transparent paths before the requested
|
|
151
|
+
scattering depth. On Intel Mac WebGL, the event limit is `min(bounces, 3)`, doubled
|
|
152
|
+
for scenes with transmission or alpha, with earlier scattering termination.
|
|
153
|
+
See [caustic path-length limits](./caustics.md#limits) when comparing renderers.
|
|
153
154
|
|
|
154
155
|
| Method / property | Meaning |
|
|
155
156
|
| --- | --- |
|
|
156
157
|
| `refine(channel?, options?)` | Trace beauty or an enabled AOV within this render's budget; returns status |
|
|
158
|
+
| `refineUntil(deadline, {signal}?)` | For custom frame loops: refine once synchronously, then keep sampling until the `performance.now()` deadline or cancellation; returns `Promise<void>`; see [sample batching](progressive-rendering.md#sample-batching) |
|
|
157
159
|
| `converge(options)` | Deterministic offline image; see below |
|
|
158
160
|
| `readPixels(kind = 'hdr')` | Read this render; see [pixel outputs](#pixel-outputs) |
|
|
159
161
|
| `engine` | Owning engine |
|
|
160
162
|
| `denoising` | Queued/running inference, including intermediate updates; default false |
|
|
161
163
|
| `on('sample', listener)` | Complete samples: `{samples, isFirstFrame, frameTimeMs}`; returns unsubscribe |
|
|
162
164
|
| `samples` | Complete samples since restart; stale engine inputs report 0 |
|
|
163
|
-
| `gpuSampleTimeMs` | Latest WebGPU full beauty sample estimate in ms; undefined without timestamps, for stale inputs or during converge |
|
|
164
|
-
| `whenReadyToRefine()` | Wait for
|
|
165
|
+
| `gpuSampleTimeMs` | Deprecated; use `refineUntil` for custom frame loops. Latest WebGPU full beauty sample estimate in ms, extrapolated from tile GPU time when tiled; undefined without timestamps, for stale inputs or during converge |
|
|
166
|
+
| `whenReadyToRefine()` | Deprecated; use `refineUntil`. Wait for WebGPU sampling capacity and return `true` if sampling was queued; return `false` immediately with no sampling, on WebGL, or for only denoising, display or offline convergence work; submits no work; throws after disposal |
|
|
165
167
|
| `converged` | `samples >= maxSamples`, independently of filtering |
|
|
166
168
|
| `status` | Preparing, compiling, converging, denoising, finished or error |
|
|
167
169
|
| `setDenoiser(options)` | Replace the denoiser configuration; resolves when the filter is ready |
|
|
@@ -175,13 +177,10 @@ completed filtering. Synchronous execution failures return `'error'` and set
|
|
|
175
177
|
engine.lastError. Invalid lifecycle operations (disposed render or refine during
|
|
176
178
|
converge) throw.
|
|
177
179
|
|
|
178
|
-
`refine(undefined, {batchSamples
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
capped by that budget and is ignored on WebGL. `deferResolve` (default false,
|
|
183
|
-
WebGPU only) leaves the displayed image unchanged for calls between display
|
|
184
|
-
frames; the next presentation or pixel read updates it once with every new sample.
|
|
180
|
+
`refine(undefined, {batchSamples})` overrides batching for one call without
|
|
181
|
+
changing render settings or discarding samples; it defaults to the render's
|
|
182
|
+
`batchSamples`. The `targetBatchTimeMs` and `deferResolve` refine options are
|
|
183
|
+
deprecated; use `refineUntil`.
|
|
185
184
|
|
|
186
185
|
A fallback gets one sample when the requested render cannot finish its first
|
|
187
186
|
sample within budget and the fallback has no current complete image. A ready
|
|
@@ -206,6 +205,25 @@ Resolves `{samples, averageSamplesPerPixel, pilotSamples, adaptiveSampling,
|
|
|
206
205
|
varianceHistory?}` after filtering. It rejects if engine input revisions change.
|
|
207
206
|
The same render cannot refine concurrently; other renders remain usable.
|
|
208
207
|
|
|
208
|
+
### Caustics
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
render.caustics = {lightTracing: {photonsPerSample: 32768}};
|
|
212
|
+
render.caustics = undefined; // Disable every estimator.
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Changing any caustic option restarts accumulation. All estimators are experimental;
|
|
216
|
+
see the [caustics guide](./caustics.md) for choosing estimators, supported paths,
|
|
217
|
+
rendering cost and image-quality limitations.
|
|
218
|
+
|
|
219
|
+
| Option | Default | Meaning |
|
|
220
|
+
| --- | --- | --- |
|
|
221
|
+
| `lightTracing` | `false` | `true` or an object enables directly visible caustic light paths on WebGPU; ignored on WebGL 2 |
|
|
222
|
+
| `lightTracing.photonsPerSample` | pixel count / 4 | Emitted photons per sample, rounded to a positive integer and capped by the device dispatch limit |
|
|
223
|
+
| `photonMapping` | `false` | `true` or an object enables progressive caustic photon mapping on WebGPU; ignored on WebGL 2. See [PM-only denoising limits](./caustics.md#limits) |
|
|
224
|
+
| `photonMapping.photonsPerSample` | pixel count / 4, at most 262144 | Emitted photons per batch; 28 bytes each plus up to 13 MiB of overhead. Positive finite values are rounded to at least 1 and reduced to fit device limits and a 128 MiB batch budget; other values use the default |
|
|
225
|
+
| `photonMapping.initialRadius` | 3% of the caster bounding sphere radius | Positive first-batch gather radius in scene units, independent of `photonsPerSample`; shrinks as batches accumulate. Smaller radii preserve detail but require more photons |
|
|
226
|
+
|
|
209
227
|
## RenderLoop and Sync
|
|
210
228
|
|
|
211
229
|
`start()` uses requestAnimationFrame when available and a 16 ms setTimeout
|
|
@@ -226,6 +244,7 @@ otherwise. Worker visibility and control forwarding are described in
|
|
|
226
244
|
|
|
227
245
|
`engine`, `display`, `preview` (possibly undefined) and `final` are read-only.
|
|
228
246
|
`changing` defaults false; `samples` counts final beauty samples and reports 0 during preview; `status` starts preparing.
|
|
247
|
+
An exception during a tick, such as from a sync, sets `status` to error and `lastError`; the next successful tick clears them.
|
|
229
248
|
`select({changing})` defaults to changing ? preview : final. `addSync`/`removeSync`
|
|
230
249
|
manage integrations without disposing them. `tick` executes a manual frame;
|
|
231
250
|
`start`/`stop` manage animation frames. `converge` accepts the same options as
|
|
@@ -251,7 +270,7 @@ Use `engine.display`; creation of additional displays is outside this API.
|
|
|
251
270
|
their next refine; fixed renders retain their dimensions.
|
|
252
271
|
`present(render)` only presents: it returns the render or its ready fallback,
|
|
253
272
|
or undefined without changing the canvas when neither is ready.
|
|
254
|
-
`toBlob(render, {type?, quality?})` composes that render at its
|
|
273
|
+
`toBlob(render, {type?, quality?})` composes that render at its output dimensions,
|
|
255
274
|
without resizing or changing the current canvas. MIME defaults to image/png;
|
|
256
275
|
quality defaults to the browser encoder value.
|
|
257
276
|
|
|
@@ -580,7 +599,9 @@ see [denoising](./denoising.md#custom-oidn-networks). The main entry exports
|
|
|
580
599
|
|
|
581
600
|
`render.getOutputTexture(kind)` borrows native HDR or the latest display image;
|
|
582
601
|
`display.addPostProcess(pass)` returns unregister and runs on every present/export.
|
|
583
|
-
Both are WebGPU-only.
|
|
602
|
+
Both are WebGPU-only. Textures are rounded up to multiples of 16 pixels, so a
|
|
603
|
+
fixed render's texture can include extra pixels around the requested image. See
|
|
604
|
+
[formats, lifecycle and overlay example](./post-processing.md).
|
|
584
605
|
|
|
585
606
|
Anisotropy direction options: `anisotropyMap` (default `undefined`) reads linear RG as a tangent direction and B as a multiplier for base specular strength. `specularAnisotropyRotation` (default `0`) rotates that direction counter-clockwise in radians; `coatAnisotropyRotation` (default `0`) rotates the coat relative to the geometry tangent. See [materials](./materials.md#anisotropy-directions) for frame and fallback conventions.
|
|
586
607
|
|
package/docs/caustics.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Caustics
|
|
2
|
+
|
|
3
|
+
Enable caustics on the render that needs them:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
render.caustics = {lightTracing: true, photonMapping: true};
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Choose the estimators for the visible receiver:
|
|
10
|
+
|
|
11
|
+
| Scene | Settings |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Glass or metal casting onto a directly visible surface | `{lightTracing: true}` |
|
|
14
|
+
| Water or glass between the camera and the caustic receiver | `{photonMapping: true}` |
|
|
15
|
+
| A mixture of direct, indirect and glass-covered receivers | `{lightTracing: true, photonMapping: true}` |
|
|
16
|
+
|
|
17
|
+
Light tracing preserves narrow, directly visible caustics without a gather
|
|
18
|
+
radius. Photon mapping covers indirect caustics and surfaces seen through glass,
|
|
19
|
+
with additional memory and gather cost. Combined estimators assign path coverage
|
|
20
|
+
automatically. The viewer's Cornell caustic preset enables both estimators.
|
|
21
|
+
|
|
22
|
+
Set `render.caustics = undefined` to disable every estimator. See
|
|
23
|
+
[API options and defaults](./api.md#caustics).
|
|
24
|
+
|
|
25
|
+
## Options and cost
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
render.caustics = {
|
|
29
|
+
lightTracing: {photonsPerSample: 32768},
|
|
30
|
+
photonMapping: {photonsPerSample: 32768, initialRadius: 0.1}
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Both estimators require WebGPU; WebGL ignores those options. HDR readback,
|
|
35
|
+
display and denoising include the combined result. Changes to any option restart
|
|
36
|
+
accumulation. The native SDK provides the same choices through Rust
|
|
37
|
+
`CausticSettings` and the C `MT_CAUSTIC_*` flags.
|
|
38
|
+
|
|
39
|
+
More emitted photons can reduce noise but cost more time and memory. Photon-map
|
|
40
|
+
storage adds 28 bytes per emitted photon plus up to 13 MiB of overhead. Requests
|
|
41
|
+
above the device limits or the 128 MiB batch budget use fewer photons. Photon
|
|
42
|
+
batches cover the full image even when path tracing uses tiles or adaptive
|
|
43
|
+
sampling. Their time counts toward the frame budget, but adaptive variance measures
|
|
44
|
+
only the path-traced component.
|
|
45
|
+
|
|
46
|
+
## Limits
|
|
47
|
+
|
|
48
|
+
Light tracing follows surfaces with no diffuse, subsurface, fuzz or coat
|
|
49
|
+
contribution and specular roughness at most 0.1. It supports emissive triangles,
|
|
50
|
+
rectangles, spheres, spotlights, directional lights and environment lighting,
|
|
51
|
+
perspective and orthographic cameras, depth of field and dispersion. Photon
|
|
52
|
+
mapping adds caustics throughout the allowed camera path. Volume scattering
|
|
53
|
+
events and shadow catchers remain with ordinary path tracing.
|
|
54
|
+
|
|
55
|
+
Complete caustic light-to-camera paths can be longer than `bounces`. Align total
|
|
56
|
+
path-length limits when comparing radiance with another renderer.
|
|
57
|
+
|
|
58
|
+
Photon mapping starts blurred and shrinks its radius as samples accumulate. A
|
|
59
|
+
small initial radius can be noisy; a large one can blur nearby surfaces and details.
|
|
60
|
+
Distant smooth casters enlarge the automatic radius, so set `initialRadius` if deep
|
|
61
|
+
caustics look too blurred. At the first diffuse surface, the gather radius is capped
|
|
62
|
+
to about two projected pixels to reduce patches after denoising; reflections and
|
|
63
|
+
refractions use an approximate footprint. More photons can help, but not every
|
|
64
|
+
batch improves the image, and more samples do not remove the precision loss in
|
|
65
|
+
stored photon data.
|
|
66
|
+
|
|
67
|
+
With photon mapping alone, directly visible receivers can show fewer cloud-like
|
|
68
|
+
OIDN blotches but grainier raw images. Receivers seen through reflection or
|
|
69
|
+
refraction can still show blotches. Enable light tracing as well for the cleanest
|
|
70
|
+
directly visible receivers.
|
|
71
|
+
|
|
72
|
+
A denoiser can soften narrow caustic lines, so compare raw HDR with denoised output
|
|
73
|
+
when judging detail. Use raw HDR for energy comparisons: displayed or denoised
|
|
74
|
+
brightness can differ even when regional HDR energy agrees.
|
|
75
|
+
|
|
76
|
+
Both estimators fall back to path tracing for the whole scene when a smooth caster
|
|
77
|
+
uses stochastic blend coverage. Cutout coverage remains supported. Environment
|
|
78
|
+
caustics follow path tracing's visible-background behavior through the first smooth
|
|
79
|
+
transmission; an explicit black background suppresses that contribution.
|
|
80
|
+
Quantitative energy agreement with other renderers is not guaranteed.
|
package/docs/materials.md
CHANGED
|
@@ -97,10 +97,11 @@ the substrate through the attenuation term.
|
|
|
97
97
|
**Interior media.** Transmission describes a homogeneous volume:
|
|
98
98
|
`transmissionColor` is the color that survives one `transmissionDepth`, and
|
|
99
99
|
`transmissionScatter` is the scattering albedo. With `transmissionDepth = 0`
|
|
100
|
-
the tint is applied at the surface. Subsurface
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
the tint is applied at the surface. Subsurface has its own color and per-channel
|
|
101
|
+
radius. A positive `transmissionDispersionScale` enables
|
|
102
|
+
wavelength-dependent refraction; values above `1` increase color separation,
|
|
103
|
+
and `0` disables it. Lower `transmissionDispersionAbbeNumber` values also
|
|
104
|
+
increase color separation.
|
|
104
105
|
|
|
105
106
|
## Compatibility names
|
|
106
107
|
|
|
@@ -87,24 +87,65 @@ are refined that frame. See [scene updates](updates.md) for notifications.
|
|
|
87
87
|
|
|
88
88
|
## Sample batching
|
|
89
89
|
|
|
90
|
-
Without batching,
|
|
91
|
-
|
|
90
|
+
Without batching, each call advances one sample or tile, so a fast GPU can idle
|
|
91
|
+
between display frames. With `batchSamples`, sampling works on two levels:
|
|
92
92
|
|
|
93
|
-
- **Per call.** One `refine()` may submit several samples
|
|
94
|
-
`timeBudgetMs` of GPU work
|
|
93
|
+
- **Per call.** One `refine()` may submit several samples within an estimated
|
|
94
|
+
`timeBudgetMs` of GPU work, or one longer sample or tile, and returns without waiting for them.
|
|
95
95
|
Without `batchSamples`, a call traces one sample or tile.
|
|
96
96
|
- **Between frames.** RenderLoop also refines its final render each time the GPU
|
|
97
97
|
finishes queued work, until the next display frame. It keeps about 4 ms of work
|
|
98
|
-
queued, or one sample if that is longer
|
|
99
|
-
|
|
98
|
+
queued, or one sample or tile if that is longer. Expensive tiled samples can
|
|
99
|
+
continue between display frames when each measured tile fits `timeBudgetMs`;
|
|
100
|
+
longer tiles wait for display frames to bound input delay. Presentation still
|
|
101
|
+
follows the display frame rate.
|
|
100
102
|
|
|
101
103
|
Batching is off by default. Enable it on the loop's final render with
|
|
102
104
|
`final: {batchSamples: true}`, or on a Render you supply. RenderLoop stops
|
|
103
105
|
batching while the view is changing.
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
`
|
|
107
|
-
`
|
|
107
|
+
Prefer RenderLoop for between-frame refilling. For a custom loop, use
|
|
108
|
+
`refineUntil(deadline, {signal})`. It synchronously calls `refine()` once, so
|
|
109
|
+
`present()` belongs immediately after the call. On WebGPU it then refills short
|
|
110
|
+
batches until the deadline, cancellation, a hidden page or sampling/display
|
|
111
|
+
conditions prevent more work. WebGL performs only the synchronous call.
|
|
112
|
+
|
|
113
|
+
This example assumes `engine` and `render` are created with
|
|
114
|
+
`render.batchSamples = true` and the final beauty channel selected. Adjust the
|
|
115
|
+
60 Hz deadline to your application's cadence and update controls before
|
|
116
|
+
`engine.update()`.
|
|
117
|
+
|
|
118
|
+
<!-- own-loop-refill -->
|
|
119
|
+
```js
|
|
120
|
+
let refill;
|
|
121
|
+
const stop = () => refill?.abort();
|
|
122
|
+
for (const type of ['pointerdown', 'pointermove', 'pointerup', 'wheel', 'keydown', 'input', 'change'])
|
|
123
|
+
addEventListener(type, stop, {capture: true, passive: true});
|
|
124
|
+
|
|
125
|
+
function frame(timestamp) {
|
|
126
|
+
requestAnimationFrame(frame);
|
|
127
|
+
stop();
|
|
128
|
+
engine.update();
|
|
129
|
+
refill = new AbortController();
|
|
130
|
+
void render.refineUntil(timestamp + 1000 / 60, {signal: refill.signal});
|
|
131
|
+
engine.display.present(render);
|
|
132
|
+
}
|
|
133
|
+
requestAnimationFrame(frame);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Call `stop()` before pausing, exporting or switching to another render, and skip
|
|
137
|
+
`refineUntil()` while paused. A new call automatically ends the previous refill.
|
|
138
|
+
The signal cancels future submissions; work already queued on the GPU completes.
|
|
139
|
+
The returned Promise resolves when refilling stops, including after an
|
|
140
|
+
asynchronous error (reported through `render.status` and `engine.lastError`).
|
|
141
|
+
|
|
142
|
+
The library automatically continues the remaining tiles of the current sample
|
|
143
|
+
between presentations. `samples` increases only after the whole sample;
|
|
144
|
+
presentation keeps the previous complete sample until then. Committed scene or
|
|
145
|
+
camera edits discard an unfinished sample and restart against the new inputs.
|
|
146
|
+
`maxTiles` caps the sample's tile grid (rounded up to a rectangular grid), so a
|
|
147
|
+
smaller value makes larger tiles. It does not cap work per frame or per call;
|
|
148
|
+
use `timeBudgetMs` and the between-frame deadline to control queued work.
|
|
108
149
|
|
|
109
150
|
## Reading and exporting
|
|
110
151
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Continuous-wavelength dispersion
|
|
2
|
+
|
|
3
|
+
MuTracer renders dispersion with continuous wavelengths inside an RGB renderer.
|
|
4
|
+
Wavelength decides how light refracts and how its energy divides between RGB
|
|
5
|
+
channels. Lights, materials and media stay RGB, so this is not spectral rendering.
|
|
6
|
+
The implementation is in `src/renderer/spectrum.ts`, and it runs only when a material
|
|
7
|
+
has `transmissionDispersionScale > 0`.
|
|
8
|
+
|
|
9
|
+
## Spectral response
|
|
10
|
+
|
|
11
|
+
`spectrumRgb` converts a wavelength in 400–700 nm to linear sRGB weights:
|
|
12
|
+
|
|
13
|
+
1. Evaluate the CIE 1931 matching functions with the multi-lobe fit from Wyman, Sloan
|
|
14
|
+
and Shirley, "Simple Analytic Approximations to the CIE XYZ Color Matching
|
|
15
|
+
Functions", JCGT 2(2), 2013.
|
|
16
|
+
2. Convert XYZ to linear sRGB and clamp negative values to zero.
|
|
17
|
+
3. Scale each channel so its mean over a uniform wavelength is 1.
|
|
18
|
+
|
|
19
|
+
The scale keeps white light white on average. The clamp keeps out-of-gamut colors
|
|
20
|
+
saturated instead of colorimetrically exact, and it leaves red with a small violet
|
|
21
|
+
lobe, which is why the short end of a rainbow looks violet instead of blue.
|
|
22
|
+
|
|
23
|
+
## Sampling
|
|
24
|
+
|
|
25
|
+
Both integrators sample from 5 nm inverse CDF tables. They use different estimators
|
|
26
|
+
because they carry different quantities.
|
|
27
|
+
|
|
28
|
+
| | Camera paths | Caustic photons |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Distribution | The response of the path's RGB channel | The sum of the RGB responses |
|
|
31
|
+
| Carries | One RGB channel | RGB flux |
|
|
32
|
+
| Energy weight | None; the distribution is the response | `spectrumRgb(λ) / (pdf(λ) · 300)` |
|
|
33
|
+
| Random number | `CAMERA_WAVELENGTH`, shared by a sample's three paths | Photon emission dimension 69 |
|
|
34
|
+
|
|
35
|
+
Camera paths evaluate no response, so their effective response is a 5 nm histogram of
|
|
36
|
+
the fit. Photons apply the weight exactly. Sharing one random number across a camera
|
|
37
|
+
sample's channels keeps their error correlated with the other common random numbers.
|
|
38
|
+
|
|
39
|
+
`dispersionWavelengths: 3` traces one path per channel in each sample. With
|
|
40
|
+
`dispersionWavelengths: 1`, a dispersive primary hit selects one channel at random
|
|
41
|
+
and multiplies the result by 3; other primary hits trace full RGB. This is not hero
|
|
42
|
+
wavelength sampling, which carries several wavelengths along one path and combines
|
|
43
|
+
them with MIS.
|
|
44
|
+
|
|
45
|
+
## Channels and gathering
|
|
46
|
+
|
|
47
|
+
A path stores its channel and its refracting wavelength separately:
|
|
48
|
+
`spectralChannel` selects the radiance it keeps, and `wavelength` only drives
|
|
49
|
+
`sampling_cauchy_ior`. A channel of 0 keeps full RGB. Red paths can sample violet, so
|
|
50
|
+
the channel cannot be derived from the wavelength.
|
|
51
|
+
|
|
52
|
+
Photon gathering filters photon flux by the camera path's channel. This splits RGB
|
|
53
|
+
across the camera paths and applies no second spectral weight, so energy is shared
|
|
54
|
+
between path tracing and caustics the same way as without dispersion.
|
|
55
|
+
|
|
56
|
+
## Short-wavelength IOR limit
|
|
57
|
+
|
|
58
|
+
Cauchy IOR rises quickly at short wavelengths. At strong dispersion scales, violet
|
|
59
|
+
light reaches IORs that reflect or trap much of it, and wide caustics turn yellow.
|
|
60
|
+
`spectrumIorWavelength` therefore eases the refracting wavelength below 500 nm toward
|
|
61
|
+
475 nm, while color keeps the sampled wavelength. The mapping is monotonic and
|
|
62
|
+
continuous, so the gradient has no hard edge.
|
|
63
|
+
|
|
64
|
+
This limit is an artistic choice and not physically based. It narrows the violet end
|
|
65
|
+
of the spectrum, and strong dispersion scales still lose some blue.
|
|
66
|
+
|
|
67
|
+
## Differences from spectral rendering
|
|
68
|
+
|
|
69
|
+
| Area | Spectral rendering | MuTracer |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Lights | Emission varies with wavelength | RGB, with an implicit broad spectrum; a sodium lamp still disperses into a full rainbow |
|
|
72
|
+
| Materials and textures | Reflectance varies with wavelength, often upsampled from RGB | RGB, so metamerism cannot appear |
|
|
73
|
+
| Media | Absorption and scattering vary with wavelength | Three RGB coefficients |
|
|
74
|
+
| Thin film | Interference is integrated over wavelength | Evaluated at 620, 540 and 450 nm |
|
|
75
|
+
| Refractive index | Often measured curves or Sellmeier equations | Cauchy from IOR and Abbe number, with the short-wavelength limit |
|
|
76
|
+
| Wavelengths per path | Often several, combined with MIS | One per path or photon |
|
|
77
|
+
| Output color | Integrated from the spectrum into the output color space | Fixed clamped sRGB weights, normalized per channel |
|
|
78
|
+
|
|
79
|
+
These differences matter most for narrow-band lights, colored absorption over long
|
|
80
|
+
paths, metamerism and predictive color. Clear glass and crystal under broad lights are
|
|
81
|
+
closer to the spectral result, because their color comes mainly from refraction.
|
|
82
|
+
|
|
83
|
+
Full spectral rendering would change the light, material, texture and medium data
|
|
84
|
+
models, and requires RGB-to-spectrum upsampling in both shader backends and native.
|
|
85
|
+
|
|
86
|
+
## Changing this code
|
|
87
|
+
|
|
88
|
+
- Update WGSL and GLSL together. Both are generated from `spectrum.ts`; caustics are
|
|
89
|
+
WGSL only.
|
|
90
|
+
- `spectralChannel` must be set wherever a path's wavelength is set.
|
|
91
|
+
- `tests/unit/spectrum.test.ts` covers white balance, weight bounds, channel
|
|
92
|
+
distributions and the IOR limit. Check the image result on a real GPU in both
|
|
93
|
+
backends, as described in `AGENTS.md`.
|
package/docs/updates.md
CHANGED
|
@@ -154,7 +154,9 @@ do not recompile the path tracer. Neither do changes to `bounces`,
|
|
|
154
154
|
bevel switch, sample count or seed. Data uploads, atlas/BVH work and accumulation
|
|
155
155
|
restarts may still be needed.
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
A render waiting for a shader keeps its samples and image, and other renders keep
|
|
158
|
+
sampling. Returning to a cached compatible combination reuses its pipeline, even
|
|
159
|
+
while another combination is still compiling. Prepared
|
|
158
160
|
combinations may be discarded after many different edits; recreating the Engine
|
|
159
161
|
or recovering a lost device also requires preparing shaders again. Compilation time depends
|
|
160
162
|
on the feature combination, BVH layout, browser and GPU driver. Denoising and
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Viewer feature checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist with the [Web viewer](https://mutracer.meimu-studio.com/viewer/)
|
|
4
|
+
and [native viewer](../native/viewer/README.md). Start with material spheres,
|
|
5
|
+
then use a glTF with nested objects, cameras, lights and animation. A checked
|
|
6
|
+
item means you exercised the behavior in the version being reviewed; it does
|
|
7
|
+
not promise identical rendered pixels between devices or backends.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
| Check | Action and expected result |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| [ ] Open | Open a GLB, a glTF with external buffers/images, a folder and a model URL; geometry and textures load. Drop files on the viewport. On Web, a fresh load uses the collection's curated view or the file's first camera, including orthographic cameras; restoring a saved view takes precedence. Failed or cancelled loads leave the previous scene usable. |
|
|
14
|
+
| [ ] Examples | Open **Examples** in the toolbar; switch between MuTracer collection and glTF Research Scenes, load a thumbnail and check its camera and authored lighting. |
|
|
15
|
+
| [ ] Scene | Search objects or materials and expand nested groups and meshes; select a material child to open its editor. Select an object from the tree or viewport; selection reveals its ancestors. Hide/show a mesh and a group. Select an authored camera. |
|
|
16
|
+
| [ ] Navigation | Drag to orbit, right-drag to pan, scroll to zoom, **F** to fit. Moving an authored camera continues in Orbit. |
|
|
17
|
+
| [ ] History | Undo/redo material, visibility, lighting and render edits; one continuous drag is one step. Reset all restores defaults. On Web, it also restores the example's render choices and initial camera, or fits the model when no camera is provided. |
|
|
18
|
+
| [ ] Panels | **1–4** opens Lighting, Material, Camera, Render respectively. **[**, **]** toggle scene/inspector; click the active rail tab to close it. **H** hides the interface. Text input must not trigger shortcuts. |
|
|
19
|
+
| [ ] Playback | **Space** plays/pauses animation when present, otherwise pauses/resumes rendering. Choose another clip and seek the timeline. |
|
|
20
|
+
| [ ] Render dock | Change sample target, viewport resolution and denoising; pause/resume and inspect sample progress, elapsed time and completion. **D** toggles denoising. |
|
|
21
|
+
| [ ] Export | **E** opens a preview; save the displayed viewport PNG. Render Viewport/1080p/2K/4K/Square PNG with a separate sample budget, denoising and optional transparency. Cancel an export and continue using the original image. |
|
|
22
|
+
| [ ] Code/help | Open Code and copy a setup for the corresponding SDK; open keyboard help. **⌘O / Ctrl+O** opens files. |
|
|
23
|
+
|
|
24
|
+
## Lighting, materials, camera and rendering
|
|
25
|
+
|
|
26
|
+
| Check | Controls and expected result |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| [ ] Environment | Switch HDRI / Studio and model lighting where available. Load a local HDR or browse Poly Haven; change HDR resolution, intensity and rotation. Failed downloads keep the previous lighting. |
|
|
29
|
+
| [ ] Studio | Choose each rig; adjust intensity, rotation, size, ambient strength and color. |
|
|
30
|
+
| [ ] Background | Switch environment, color, transparent and grounded modes where offered. Adjust projection height/radius; verify auto values and transparency in exported PNG. |
|
|
31
|
+
| [ ] Ground | Enable ground shadow and change opacity; inspect contact with the model. |
|
|
32
|
+
| [ ] Material selection | Pick a mesh/material, check shared-material edits, apply a preset and reset to loaded values. |
|
|
33
|
+
| [ ] Material layers | Exercise base color/weight, metalness, roughness, diffuse roughness, IOR/specular, anisotropy, transmission/volume/thin wall, subsurface, coat, fuzz, thin film, emission, opacity/alpha and double-sided/unlit controls where offered. |
|
|
34
|
+
| [ ] Textures | Upload, replace and remove each exposed slot; change Wrap S/T and Repeat S/T, including negative repeats. Apply a Poly Haven set with optional displacement; cancel/replace downloads and switch materials during download. |
|
|
35
|
+
| [ ] Displacement | Change scale, bias, subdivision and smoothing; verify silhouettes and return to zero displacement. |
|
|
36
|
+
| [ ] MaterialX (Web) | Import a .zip package or .mtlx with images; check converted layers and textures. |
|
|
37
|
+
| [ ] Camera | Switch Perspective/Orthographic, adjust FOV, auto-rotate and Fit. Authored cameras keep their projection/FOV until Orbit resumes. |
|
|
38
|
+
| [ ] Depth of field | Enable DOF, adjust f-number, area autofocus and focus frame; move the frame, manually edit focus or pick a point; choose automatic or explicit scene units. Orthographic views disable DOF. |
|
|
39
|
+
| [ ] Quality | Change max samples, bounces, resolution, sampling pattern and clamping. Web also offers a BVH builder. |
|
|
40
|
+
| [ ] Caustics | Toggle light tracing and photon mapping; compare each alone and together on a glass scene. Web's master Caustics switch gates both. |
|
|
41
|
+
| [ ] Denoiser | Toggle JNLM/OIDN; adjust JNLM strength, search/patch radius, albedo/normal tolerance and passes; choose OIDN Standard/Small/Large and tile size. Reset selected parameters. |
|
|
42
|
+
| [ ] Color | Change AgX/ACES/Cineon/Reinhard/Linear, exposure, contrast, saturation, temperature and tint; verify exported colors. |
|
|
43
|
+
| [ ] Effects | Enable bloom, threshold/radius and vignette; turn them off and verify the image returns. |
|
|
44
|
+
| [ ] Debug | Expand/collapse Debug; inspect Final, Albedo, Normal, Depth, Position, Roughness, Diffuse/Specular reflectance, ID and Motion. Adjust channel ranges and ID component; toggle interactive preview and max preview scale. Overlay gestures must not orbit/select the scene. |
|
|
45
|
+
| [ ] Window size | Check wide and narrow windows, hidden panels and long names; controls remain readable and the viewport remains usable. Web also provides the mobile bottom sheet. |
|
|
46
|
+
|
|
47
|
+
## Platform options and limits
|
|
48
|
+
|
|
49
|
+
| Area | Web | Native |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Rendering device | WebGPU; WebGL 2 fallback reloads the page | HWRT / Software BVH; device availability is reported in the UI |
|
|
52
|
+
| Denoising | WebGL 2 supports JNLM; OIDN needs WebGPU | JNLM/OIDN; optional Windows DLSS RR build |
|
|
53
|
+
| HDR display | WebGPU plus compatible browser/display; falls back to SDR | Viewer window is SDR; HDR toggle is disabled |
|
|
54
|
+
| BVH builder | tinybvh / builtin | No builder selector |
|
|
55
|
+
| MaterialX import | .zip or .mtlx with images | No MaterialX import UI |
|
|
56
|
+
| Caustics controls | Master switch plus two methods; methods require WebGPU | Independent method switches; both off disables caustics |
|
|
57
|
+
| File access | Multi-file/folder picker; URLs need CORS | File/folder picker; folder Open selects a top-level glTF/GLB; resources resolve beside the model |
|
|
58
|
+
| Code | JavaScript setup | Rust SDK setup |
|
|
59
|
+
| Examples browser | Popover with optional pinned panel | Toolbar opens a docked panel |
|
|
60
|
+
| Extra native controls | — | Fixed-pixel render sizes, directional key light |
|
|
61
|
+
| Small screens | Mobile sheet and responsive toolbar | Desktop panels; scene list hides when space is limited |
|
|
62
|
+
|
|
63
|
+
Native and Web use different UI toolkits and fonts. Check icon meaning,
|
|
64
|
+
label order, alignment and control availability; file dialogs, window controls,
|
|
65
|
+
font rasterization and responsive behavior follow each platform.
|
package/docs/viewer.md
CHANGED
|
@@ -21,17 +21,17 @@ resampled to at most 2K width. If a source map is missing, the model still opens
|
|
|
21
21
|
and the Lighting panel explains which map is unavailable; choose an environment
|
|
22
22
|
there to replace it.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
MuTracer collection examples open with a curated view, under the procedural
|
|
25
|
+
sky or a Poly Haven HDRI. A few also set their own tone mapping and bloom, which
|
|
26
|
+
return to the defaults when you leave them. Other models with cameras open in their
|
|
27
|
+
first camera. You can select any
|
|
27
28
|
imported camera in the separate camera toolbar at the bottom right; it appears
|
|
28
29
|
only for models with cameras. Drag, pan or zoom after selecting a camera to
|
|
29
30
|
continue in **Orbit** from that view.
|
|
30
31
|
Hover over a camera in the scene tree and click its camera action to apply it
|
|
31
32
|
directly. **Fit to model** in the bottom toolbar (or **F**) returns to Orbit
|
|
32
33
|
and frames the model.
|
|
33
|
-
Reloading the current model restores your saved camera selection
|
|
34
|
-
showing the prompt again.
|
|
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
37
|
into view. Material rows start collapsed; expand a mesh to see its materials.
|
|
@@ -54,6 +54,8 @@ https://mutracer.meimu-studio.com/viewer/?hdrOutputEnabled=true&maxSamples=128#m
|
|
|
54
54
|
The viewer updates the URL as lighting, render and display settings change.
|
|
55
55
|
Copy the address bar to share those settings and the selected example model.
|
|
56
56
|
Settings also work in the hash, such as `#model=dragon&hdrOutputEnabled=true`.
|
|
57
|
+
A link with only `#model=…` opens the example with its own render settings, as
|
|
58
|
+
the Examples menu does. Settings in a link win over the example's.
|
|
57
59
|
|
|
58
60
|
### Options
|
|
59
61
|
|
package/lib/Display.js
CHANGED
|
@@ -127,7 +127,7 @@ export default class Display {
|
|
|
127
127
|
const target = this.compose(render);
|
|
128
128
|
if (!target)
|
|
129
129
|
throw new Error('Render has no complete image.');
|
|
130
|
-
const image = await target.readPresentation();
|
|
130
|
+
const image = render._cropOutput(await target.readPresentation());
|
|
131
131
|
const canvas = createCanvas(image.width, image.height, 'convertToBlob' in this.canvas);
|
|
132
132
|
const context = canvas.getContext('2d');
|
|
133
133
|
if (!context)
|
package/lib/Display.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Display.js","sourceRoot":"","sources":["../src/Display.ts"],"names":[],"mappings":"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","sourcesContent":["import {displayAovKind, validateAovDisplay, type AovIdComponent} from './renderer/aovDisplay.js';\nexport type {AovIdComponent} from './renderer/aovDisplay.js';\nimport {createCanvas} from './util/canvas.js';\nimport type Engine from './Engine.js';\nimport type Render from './Render.js';\nimport type { ToneMapping, DisplayChannel } from './renderer/RenderingPipeline.js';\nimport type { ColorRGB } from './types.js';\nimport { displaySettingsKey, displayFilterSettingsKey, applyDisplaySettings } from './settings/display.js';\n/** Display image channels. Default final. */\nexport type {DisplayChannel} from './renderer/RenderingPipeline.js';\n/** Image encoding options for Display.toBlob. */\nexport interface BlobOptions {\n /** Browser image MIME type. Default image/png. */ type?: string;\n /** Encoder quality in 0–1. Default undefined uses the browser default. */ quality?: number;\n}\n/** Optional shared display settings; defaults match Display. */\nexport interface DisplaySettings {\n /** Tone mapping transform. Default agx. */ toneMapping?: ToneMapping;\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure?: number;\n /** Contrast multiplier. Default 1. */ contrast?: number;\n /** Saturation multiplier. Default 1. */ saturation?: number;\n /** White balance temperature shift in -1–1. Default 0. */ temperature?: number;\n /** Green/magenta tint in -1–1. Default 0. */ tint?: number;\n /** Vignette strength in 0–1. Default 0. */ vignette?: number;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity?: number;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold?: number;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius?: number;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled?: boolean;\n /** Display image channel. Default final. AOV channels require explicit Render.aovs. */ channel?: DisplayChannel;\n /** Finite float32 scene-unit depths mapped to black/white; 0 <= near < far. Default [0, 10]. */ aovDepthRange?: readonly [number, number];\n /** Finite positive float32 scene-unit half-range around the origin for position RGB. Default 1. */ aovPositionScale?: number;\n /** Finite positive float32 input-pixel displacement mapped to full motion brightness. Default 16. */ aovMotionScale?: number;\n /** Stable ID lane to colorize. Default object. */ aovIdComponent?: AovIdComponent;\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor?: ColorRGB;\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha?: number;\n}\n/** Borrowed WebGPU images for one synchronous display pass. */\nexport interface PostProcessContext {\n /** Engine device; may change after recovery. */ device: GPUDevice;\n /** Record commands here; the library finishes and submits it. */ encoder: GPUCommandEncoder;\n /** Read-only composed display image, top row first; no additional color conversion. */ input: GPUTexture;\n /** Fully write this image. Same format and dimensions as input; supports render, storage and copy writes. */ output: GPUTexture;\n /** Actual render width. */ width: number;\n /** Actual render height. */ height: number;\n /** Selected complete render, including a selected fallback. */ render: Render;\n}\n/** Synchronous display pass. Images and encoder are borrowed only during the call; do not retain or destroy them. */\nexport type PostProcess = (context: PostProcessContext) => void;\n/** Canvas presentation and shared display settings; obtained from engine.display. */\nexport default class Display {\n /** Display canvas. Default the supplied Engine canvas, or a new canvas (OffscreenCanvas without document). */ readonly canvas: HTMLCanvasElement | OffscreenCanvas;\n /** Tone mapping transform. Default agx. */ toneMapping: ToneMapping = 'agx';\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure = 1;\n /** Contrast multiplier. Default 1. */ contrast = 1;\n /** Saturation multiplier. Default 1. */ saturation = 1;\n /** White balance temperature shift in -1–1. Default 0. */ temperature = 0;\n /** Green/magenta tint in -1–1. Default 0. */ tint = 0;\n /** Vignette strength in 0–1. Default 0. */ vignette = 0;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity = 0;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold = 1;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius = 0.6;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled = false;\n /** Display image channel. Default final. AOV channels require explicit Render.aovs. */ channel: DisplayChannel = 'final';\n /** Finite float32 scene-unit depths mapped to black/white; 0 <= near < far. Default [0, 10]. */ aovDepthRange: readonly [number, number] = [0, 10];\n /** Finite positive float32 scene-unit half-range around the origin for position RGB. Default 1. */ aovPositionScale = 1;\n /** Finite positive float32 input-pixel displacement mapped to full motion brightness. Default 16. */ aovMotionScale = 16;\n /** Stable ID lane to colorize. Default object. */ aovIdComponent: AovIdComponent = 'object';\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor: ColorRGB = [0, 0, 0];\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha = 1;\n private constructor(private readonly engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { this.canvas = canvas; }\n /** @internal */ static _create(engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { return new Display(engine, canvas); }\n /** Resizes the drawing buffer; scale-based renders resize at their next refine. */\n setSize(width: number, height: number) {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) throw new Error('Display dimensions must be positive.');\n width = Math.round(width); height = Math.round(height);\n if (this.canvas.width !== width) this.canvas.width = width;\n if (this.canvas.height !== height) this.canvas.height = height;\n }\n private readonly _filterSettings = new WeakMap<Render, string>();\n private readonly settings = new WeakMap<Render, string>();\n /** @internal */ _configure(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target) return;\n const kind = displayAovKind(this.channel);\n if (!kind && !['final','normal','albedo','shadow','shadowCatcher'].includes(this.channel)) throw new RangeError('Unknown display channel');\n if (kind && !render.aovs.includes(kind)) throw new Error(`AOV ${kind} is not enabled on the selected render`);\n validateAovDisplay({depthRange: this.aovDepthRange, positionScale: this.aovPositionScale, motionScale: this.aovMotionScale, idComponent: this.aovIdComponent});\n const key = displaySettingsKey(this);\n const filterKey = displayFilterSettingsKey(this);\n const previous = this.settings.get(render);\n if (previous !== key) {\n this.settings.set(render, key);\n if (previous !== undefined && this._filterSettings.get(render) !== filterKey) render._displayChanged();\n this._filterSettings.set(render, filterKey);\n }\n target.activate();\n applyDisplaySettings(this, target);\n }\n private readonly _postProcesses: {pass: PostProcess}[] = [];\n private _warnedPostProcess = false;\n /** 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. */\n addPostProcess(pass: PostProcess): () => void {\n const entry = {pass};\n this._postProcesses.push(entry);\n if (this.engine.backend === 'webgl') this.warnPostProcess();\n return () => { const index = this._postProcesses.indexOf(entry); if (index >= 0) this._postProcesses.splice(index, 1); };\n }\n private warnPostProcess() {\n if (!this._warnedPostProcess) { this._warnedPostProcess = true; console.warn('Display post-processing requires WebGPU; passes are ignored on WebGL.'); }\n }\n private compose(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target) return;\n const kind = displayAovKind(this.channel);\n if (!kind && !target.presentationComplete) return;\n if (kind && !render.aovs.includes(kind)) throw new Error(`AOV ${kind} is not enabled on the selected render`);\n if (kind && target.aovReady && !target.aovReady(kind)) return;\n this._configure(render);\n target.drawPostProcess();\n if (this._postProcesses.length && this.engine.backend === 'webgl') this.warnPostProcess();\n try { target.applyPostProcesses?.(this._postProcesses.map(entry => entry.pass), render); }\n catch (error) { this.engine._notifyError(error); }\n return target;\n }\n /** Presents a complete image or fallback. Keeps fallback visible until the first intermediate filter finishes. Does not trace. */\n present(render: Render): Render | undefined {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const ready = this.channel === 'final' && render.fallback ? render._presentable : render._completeFor(this.channel);\n const fallback = render.fallback;\n const selected = ready ? render : fallback && (this.channel === 'final' ? fallback._presentable : fallback._completeFor(this.channel)) ? fallback : undefined;\n if (!selected) return;\n const target = this.compose(selected);\n if (!target) return;\n this.engine._present(target);\n return selected;\n }\n /** Encodes this render at its actual dimensions using these settings; leaves the display canvas unchanged. */\n async toBlob(render: Render, options: BlobOptions = {}): Promise<Blob> {\n const target = this.compose(render);\n if (!target) throw new Error('Render has no complete image.');\n const image = await target.readPresentation();\n const canvas = createCanvas(image.width, image.height, 'convertToBlob' in this.canvas);\n const context = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;\n if (!context) throw new Error('The image encoding context is not available.');\n const bytes = new Uint8ClampedArray(image.data.length);\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.round(Math.min(1, Math.max(0, image.data[i])) * 255);\n context.putImageData(new ImageData(bytes, image.width, image.height), 0, 0);\n if ('convertToBlob' in canvas) return canvas.convertToBlob({type: options.type ?? 'image/png', quality: options.quality});\n return new Promise((resolve, reject) => canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('The image could not be encoded')), options.type ?? 'image/png', options.quality));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Display.js","sourceRoot":"","sources":["../src/Display.ts"],"names":[],"mappings":"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","sourcesContent":["import {displayAovKind, validateAovDisplay, type AovIdComponent} from './renderer/aovDisplay.js';\nexport type {AovIdComponent} from './renderer/aovDisplay.js';\nimport {createCanvas} from './util/canvas.js';\nimport type Engine from './Engine.js';\nimport type Render from './Render.js';\nimport type { ToneMapping, DisplayChannel } from './renderer/RenderingPipeline.js';\nimport type { ColorRGB } from './types.js';\nimport { displaySettingsKey, displayFilterSettingsKey, applyDisplaySettings } from './settings/display.js';\n/** Display image channels. Default final. */\nexport type {DisplayChannel} from './renderer/RenderingPipeline.js';\n/** Image encoding options for Display.toBlob. */\nexport interface BlobOptions {\n /** Browser image MIME type. Default image/png. */ type?: string;\n /** Encoder quality in 0–1. Default undefined uses the browser default. */ quality?: number;\n}\n/** Optional shared display settings; defaults match Display. */\nexport interface DisplaySettings {\n /** Tone mapping transform. Default agx. */ toneMapping?: ToneMapping;\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure?: number;\n /** Contrast multiplier. Default 1. */ contrast?: number;\n /** Saturation multiplier. Default 1. */ saturation?: number;\n /** White balance temperature shift in -1–1. Default 0. */ temperature?: number;\n /** Green/magenta tint in -1–1. Default 0. */ tint?: number;\n /** Vignette strength in 0–1. Default 0. */ vignette?: number;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity?: number;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold?: number;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius?: number;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled?: boolean;\n /** Display image channel. Default final. AOV channels require explicit Render.aovs. */ channel?: DisplayChannel;\n /** Finite float32 scene-unit depths mapped to black/white; 0 <= near < far. Default [0, 10]. */ aovDepthRange?: readonly [number, number];\n /** Finite positive float32 scene-unit half-range around the origin for position RGB. Default 1. */ aovPositionScale?: number;\n /** Finite positive float32 input-pixel displacement mapped to full motion brightness. Default 16. */ aovMotionScale?: number;\n /** Stable ID lane to colorize. Default object. */ aovIdComponent?: AovIdComponent;\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor?: ColorRGB;\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha?: number;\n}\n/** Borrowed WebGPU images for one synchronous display pass. */\nexport interface PostProcessContext {\n /** Engine device; may change after recovery. */ device: GPUDevice;\n /** Record commands here; the library finishes and submits it. */ encoder: GPUCommandEncoder;\n /** Read-only composed display image, top row first; no additional color conversion. */ input: GPUTexture;\n /** Fully write this image. Same format and dimensions as input; supports render, storage and copy writes. */ output: GPUTexture;\n /** Actual render width. */ width: number;\n /** Actual render height. */ height: number;\n /** Selected complete render, including a selected fallback. */ render: Render;\n}\n/** Synchronous display pass. Images and encoder are borrowed only during the call; do not retain or destroy them. */\nexport type PostProcess = (context: PostProcessContext) => void;\n/** Canvas presentation and shared display settings; obtained from engine.display. */\nexport default class Display {\n /** Display canvas. Default the supplied Engine canvas, or a new canvas (OffscreenCanvas without document). */ readonly canvas: HTMLCanvasElement | OffscreenCanvas;\n /** Tone mapping transform. Default agx. */ toneMapping: ToneMapping = 'agx';\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure = 1;\n /** Contrast multiplier. Default 1. */ contrast = 1;\n /** Saturation multiplier. Default 1. */ saturation = 1;\n /** White balance temperature shift in -1–1. Default 0. */ temperature = 0;\n /** Green/magenta tint in -1–1. Default 0. */ tint = 0;\n /** Vignette strength in 0–1. Default 0. */ vignette = 0;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity = 0;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold = 1;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius = 0.6;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled = false;\n /** Display image channel. Default final. AOV channels require explicit Render.aovs. */ channel: DisplayChannel = 'final';\n /** Finite float32 scene-unit depths mapped to black/white; 0 <= near < far. Default [0, 10]. */ aovDepthRange: readonly [number, number] = [0, 10];\n /** Finite positive float32 scene-unit half-range around the origin for position RGB. Default 1. */ aovPositionScale = 1;\n /** Finite positive float32 input-pixel displacement mapped to full motion brightness. Default 16. */ aovMotionScale = 16;\n /** Stable ID lane to colorize. Default object. */ aovIdComponent: AovIdComponent = 'object';\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor: ColorRGB = [0, 0, 0];\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha = 1;\n private constructor(private readonly engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { this.canvas = canvas; }\n /** @internal */ static _create(engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { return new Display(engine, canvas); }\n /** Resizes the drawing buffer; scale-based renders resize at their next refine. */\n setSize(width: number, height: number) {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) throw new Error('Display dimensions must be positive.');\n width = Math.round(width); height = Math.round(height);\n if (this.canvas.width !== width) this.canvas.width = width;\n if (this.canvas.height !== height) this.canvas.height = height;\n }\n private readonly _filterSettings = new WeakMap<Render, string>();\n private readonly settings = new WeakMap<Render, string>();\n /** @internal */ _configure(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target) return;\n const kind = displayAovKind(this.channel);\n if (!kind && !['final','normal','albedo','shadow','shadowCatcher'].includes(this.channel)) throw new RangeError('Unknown display channel');\n if (kind && !render.aovs.includes(kind)) throw new Error(`AOV ${kind} is not enabled on the selected render`);\n validateAovDisplay({depthRange: this.aovDepthRange, positionScale: this.aovPositionScale, motionScale: this.aovMotionScale, idComponent: this.aovIdComponent});\n const key = displaySettingsKey(this);\n const filterKey = displayFilterSettingsKey(this);\n const previous = this.settings.get(render);\n if (previous !== key) {\n this.settings.set(render, key);\n if (previous !== undefined && this._filterSettings.get(render) !== filterKey) render._displayChanged();\n this._filterSettings.set(render, filterKey);\n }\n target.activate();\n applyDisplaySettings(this, target);\n }\n private readonly _postProcesses: {pass: PostProcess}[] = [];\n private _warnedPostProcess = false;\n /** 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. */\n addPostProcess(pass: PostProcess): () => void {\n const entry = {pass};\n this._postProcesses.push(entry);\n if (this.engine.backend === 'webgl') this.warnPostProcess();\n return () => { const index = this._postProcesses.indexOf(entry); if (index >= 0) this._postProcesses.splice(index, 1); };\n }\n private warnPostProcess() {\n if (!this._warnedPostProcess) { this._warnedPostProcess = true; console.warn('Display post-processing requires WebGPU; passes are ignored on WebGL.'); }\n }\n private compose(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target) return;\n const kind = displayAovKind(this.channel);\n if (!kind && !target.presentationComplete) return;\n if (kind && !render.aovs.includes(kind)) throw new Error(`AOV ${kind} is not enabled on the selected render`);\n if (kind && target.aovReady && !target.aovReady(kind)) return;\n this._configure(render);\n target.drawPostProcess();\n if (this._postProcesses.length && this.engine.backend === 'webgl') this.warnPostProcess();\n try { target.applyPostProcesses?.(this._postProcesses.map(entry => entry.pass), render); }\n catch (error) { this.engine._notifyError(error); }\n return target;\n }\n /** Presents a complete image or fallback. Keeps fallback visible until the first intermediate filter finishes. Does not trace. */\n present(render: Render): Render | undefined {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const ready = this.channel === 'final' && render.fallback ? render._presentable : render._completeFor(this.channel);\n const fallback = render.fallback;\n const selected = ready ? render : fallback && (this.channel === 'final' ? fallback._presentable : fallback._completeFor(this.channel)) ? fallback : undefined;\n if (!selected) return;\n const target = this.compose(selected);\n if (!target) return;\n this.engine._present(target);\n return selected;\n }\n /** Encodes this render at its actual dimensions using these settings; leaves the display canvas unchanged. */\n async toBlob(render: Render, options: BlobOptions = {}): Promise<Blob> {\n const target = this.compose(render);\n if (!target) throw new Error('Render has no complete image.');\n const image = render._cropOutput(await target.readPresentation());\n const canvas = createCanvas(image.width, image.height, 'convertToBlob' in this.canvas);\n const context = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;\n if (!context) throw new Error('The image encoding context is not available.');\n const bytes = new Uint8ClampedArray(image.data.length);\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.round(Math.min(1, Math.max(0, image.data[i])) * 255);\n context.putImageData(new ImageData(bytes, image.width, image.height), 0, 0);\n if ('convertToBlob' in canvas) return canvas.convertToBlob({type: options.type ?? 'image/png', quality: options.quality});\n return new Promise((resolve, reject) => canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('The image could not be encoded')), options.type ?? 'image/png', options.quality));\n }\n}\n"]}
|
package/lib/Engine.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export default class Engine {
|
|
|
67
67
|
private _bvhWorkerPool?;
|
|
68
68
|
private _bvhPreparationRevision;
|
|
69
69
|
private _bvhPreparation?;
|
|
70
|
+
private _bvhPreparationController?;
|
|
71
|
+
private _bvhFailedRevision;
|
|
70
72
|
private _bvhPreparationTail;
|
|
71
73
|
private _tinyBvhUnavailable;
|
|
72
74
|
private _bvhWorkerUnavailable;
|