@tinypoly/gpu-renderer 0.0.4 → 0.1.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/ARCHITECTURE.md +81 -10
- package/README.md +53 -5
- package/dist/atmosphere.d.ts +21 -0
- package/dist/atmosphere.d.ts.map +1 -0
- package/dist/atmosphere.js +72 -0
- package/dist/atmosphere.js.map +1 -0
- package/dist/bvh.d.ts +11 -6
- package/dist/bvh.d.ts.map +1 -1
- package/dist/bvh.js +64 -11
- package/dist/bvh.js.map +1 -1
- package/dist/denoise.d.ts +17 -2
- package/dist/denoise.d.ts.map +1 -1
- package/dist/denoise.js +95 -27
- package/dist/denoise.js.map +1 -1
- package/dist/dispatchScheduler.d.ts +11 -4
- package/dist/dispatchScheduler.d.ts.map +1 -1
- package/dist/dispatchScheduler.js +14 -7
- package/dist/dispatchScheduler.js.map +1 -1
- package/dist/frameLayout.d.ts.map +1 -1
- package/dist/frameLayout.js +18 -0
- package/dist/frameLayout.js.map +1 -1
- package/dist/geometry.d.ts.map +1 -1
- package/dist/geometry.js +6 -3
- package/dist/geometry.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lensPass.d.ts +60 -0
- package/dist/lensPass.d.ts.map +1 -0
- package/dist/lensPass.js +235 -0
- package/dist/lensPass.js.map +1 -0
- package/dist/lightTree.d.ts +43 -0
- package/dist/lightTree.d.ts.map +1 -0
- package/dist/lightTree.js +107 -0
- package/dist/lightTree.js.map +1 -0
- package/dist/lights.d.ts +19 -1
- package/dist/lights.d.ts.map +1 -1
- package/dist/lights.js +63 -1
- package/dist/lights.js.map +1 -1
- package/dist/materialLayout.d.ts +24 -0
- package/dist/materialLayout.d.ts.map +1 -1
- package/dist/materialLayout.js +36 -1
- package/dist/materialLayout.js.map +1 -1
- package/dist/pathTracePass.d.ts +6 -3
- package/dist/pathTracePass.d.ts.map +1 -1
- package/dist/pathTracePass.js +16 -10
- package/dist/pathTracePass.js.map +1 -1
- package/dist/renderer.d.ts +19 -7
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +198 -36
- package/dist/renderer.js.map +1 -1
- package/dist/sceneUpload.d.ts +19 -1
- package/dist/sceneUpload.d.ts.map +1 -1
- package/dist/sceneUpload.js +25 -3
- package/dist/sceneUpload.js.map +1 -1
- package/dist/settings.d.ts +136 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +100 -0
- package/dist/settings.js.map +1 -0
- package/dist/subsurface.d.ts +76 -0
- package/dist/subsurface.d.ts.map +1 -0
- package/dist/subsurface.js +70 -0
- package/dist/subsurface.js.map +1 -0
- package/dist/texturePack.d.ts +16 -4
- package/dist/texturePack.d.ts.map +1 -1
- package/dist/texturePack.js +61 -8
- package/dist/texturePack.js.map +1 -1
- package/dist/volumeContainment.d.ts +17 -0
- package/dist/volumeContainment.d.ts.map +1 -0
- package/dist/volumeContainment.js +61 -0
- package/dist/volumeContainment.js.map +1 -0
- package/dist/wgsl/bsdf.wgsl.d.ts +1 -1
- package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -1
- package/dist/wgsl/bsdf.wgsl.js +35 -3
- package/dist/wgsl/bsdf.wgsl.js.map +1 -1
- package/dist/wgsl/bvh.wgsl.d.ts +8 -2
- package/dist/wgsl/bvh.wgsl.d.ts.map +1 -1
- package/dist/wgsl/bvh.wgsl.js +167 -96
- package/dist/wgsl/bvh.wgsl.js.map +1 -1
- package/dist/wgsl/common.wgsl.d.ts.map +1 -1
- package/dist/wgsl/common.wgsl.js +24 -7
- package/dist/wgsl/common.wgsl.js.map +1 -1
- package/dist/wgsl/lens.wgsl.d.ts +17 -0
- package/dist/wgsl/lens.wgsl.d.ts.map +1 -0
- package/dist/wgsl/lens.wgsl.js +315 -0
- package/dist/wgsl/lens.wgsl.js.map +1 -0
- package/dist/wgsl/lights.wgsl.d.ts +1 -1
- package/dist/wgsl/lights.wgsl.d.ts.map +1 -1
- package/dist/wgsl/lights.wgsl.js +206 -33
- package/dist/wgsl/lights.wgsl.js.map +1 -1
- package/dist/wgsl/pathTrace.wgsl.d.ts +1 -1
- package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -1
- package/dist/wgsl/pathTrace.wgsl.js +410 -25
- package/dist/wgsl/pathTrace.wgsl.js.map +1 -1
- package/dist/wgsl/random.wgsl.d.ts +1 -1
- package/dist/wgsl/random.wgsl.d.ts.map +1 -1
- package/dist/wgsl/random.wgsl.js +9 -17
- package/dist/wgsl/random.wgsl.js.map +1 -1
- package/dist/wgsl/sampling.wgsl.d.ts +1 -1
- package/dist/wgsl/sampling.wgsl.d.ts.map +1 -1
- package/dist/wgsl/sampling.wgsl.js +25 -0
- package/dist/wgsl/sampling.wgsl.js.map +1 -1
- package/dist/wgsl/surface.wgsl.d.ts +1 -1
- package/dist/wgsl/surface.wgsl.d.ts.map +1 -1
- package/dist/wgsl/surface.wgsl.js +47 -4
- package/dist/wgsl/surface.wgsl.js.map +1 -1
- package/dist/wgsl/texture.wgsl.d.ts +1 -1
- package/dist/wgsl/texture.wgsl.d.ts.map +1 -1
- package/dist/wgsl/texture.wgsl.js +10 -11
- package/dist/wgsl/texture.wgsl.js.map +1 -1
- package/package.json +1 -1
- package/src/atmosphere.ts +95 -0
- package/src/bvh.ts +81 -12
- package/src/denoise.ts +110 -27
- package/src/dispatchScheduler.ts +16 -7
- package/src/frameLayout.ts +18 -0
- package/src/geometry.ts +6 -2
- package/src/index.ts +13 -0
- package/src/lensPass.ts +299 -0
- package/src/lightTree.ts +172 -0
- package/src/lights.ts +90 -2
- package/src/materialLayout.ts +52 -1
- package/src/pathTracePass.ts +19 -10
- package/src/renderer.ts +258 -43
- package/src/sceneUpload.ts +41 -4
- package/src/settings.ts +234 -0
- package/src/subsurface.ts +153 -0
- package/src/texturePack.ts +80 -13
- package/src/volumeContainment.ts +81 -0
- package/src/wgsl/bsdf.wgsl.ts +35 -3
- package/src/wgsl/bvh.wgsl.ts +167 -96
- package/src/wgsl/common.wgsl.ts +24 -7
- package/src/wgsl/lens.wgsl.ts +314 -0
- package/src/wgsl/lights.wgsl.ts +206 -33
- package/src/wgsl/pathTrace.wgsl.ts +410 -25
- package/src/wgsl/random.wgsl.ts +9 -17
- package/src/wgsl/sampling.wgsl.ts +25 -0
- package/src/wgsl/surface.wgsl.ts +47 -4
- package/src/wgsl/texture.wgsl.ts +10 -11
package/dist/pathTracePass.js
CHANGED
|
@@ -18,8 +18,11 @@ import { TEXTURE_WGSL } from "./wgsl/texture.wgsl.js";
|
|
|
18
18
|
* declarations. The scene's translated shaders sit between the texture code they call and the surface code that calls
|
|
19
19
|
* them.
|
|
20
20
|
*/
|
|
21
|
-
export function pathTraceSource(programs = null) {
|
|
22
|
-
|
|
21
|
+
export function pathTraceSource(programs = null, subsurface = true, volumes = true, bevel = true) {
|
|
22
|
+
// The random walk inside materials and the media inside volumes are compiled in only for a scene that has them: the
|
|
23
|
+
// kernel is bound by its size, and either would cost every other scene a few percent for code that never runs.
|
|
24
|
+
return `const SUBSURFACE_WALK: bool = ${subsurface};\nconst VOLUMES: bool = ${volumes};\nconst BEVEL: bool = ${bevel};\n`
|
|
25
|
+
+ COMMON_WGSL + RANDOM_WGSL + BVH_WGSL + SAMPLING_WGSL + TEXTURE_WGSL + SHADER_SUPPORT_WGSL
|
|
23
26
|
+ programsWgsl(programs) + ENVIRONMENT_WGSL + SURFACE_WGSL + BSDF_WGSL + LIGHTS_WGSL + PATH_TRACE_WGSL;
|
|
24
27
|
}
|
|
25
28
|
const FRAME_BYTES = FRAME_WORDS * 4;
|
|
@@ -30,23 +33,26 @@ export class PathTracePass {
|
|
|
30
33
|
uniforms;
|
|
31
34
|
values = new ArrayBuffer(FRAME_BYTES);
|
|
32
35
|
bindGroup = null;
|
|
33
|
-
programKey = "";
|
|
36
|
+
programKey = "|false|false|false";
|
|
34
37
|
constructor(device) {
|
|
35
38
|
this.device = device;
|
|
36
|
-
this.pipeline = this.build(null);
|
|
39
|
+
this.pipeline = this.build(null, false, false, false);
|
|
37
40
|
this.uniforms = device.createBuffer({ size: FRAME_BYTES, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
38
41
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Recompiles the kernel when the scene's shaders changed, or whether it has a material that scatters light inside;
|
|
44
|
+
* the same set keeps the pipeline it has.
|
|
45
|
+
*/
|
|
46
|
+
setPrograms(programs, subsurface = false, volumes = false, bevel = false) {
|
|
47
|
+
const key = `${programs?.key ?? ""}|${subsurface}|${volumes}|${bevel}`;
|
|
42
48
|
if (key === this.programKey)
|
|
43
49
|
return;
|
|
44
50
|
this.programKey = key;
|
|
45
|
-
this.pipeline = this.build(programs);
|
|
51
|
+
this.pipeline = this.build(programs, subsurface, volumes, bevel);
|
|
46
52
|
this.bindGroup = null;
|
|
47
53
|
}
|
|
48
|
-
build(programs) {
|
|
49
|
-
const module = createShaderModule(this.device, "path trace", pathTraceSource(programs));
|
|
54
|
+
build(programs, subsurface, volumes, bevel) {
|
|
55
|
+
const module = createShaderModule(this.device, "path trace", pathTraceSource(programs, subsurface, volumes, bevel));
|
|
50
56
|
return this.device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main" } });
|
|
51
57
|
}
|
|
52
58
|
/** Rebinds after the scene or the accumulation buffer was replaced. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathTracePass.js","sourceRoot":"","sources":["../src/pathTracePass.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAuB,MAAM,oBAAoB,CAAC;AAEvE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAKtD;;;;GAIG;AACH,MAAM,UAAU,eAAe,
|
|
1
|
+
{"version":3,"file":"pathTracePass.js","sourceRoot":"","sources":["../src/pathTracePass.ts"],"names":[],"mappings":"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"}
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BvhWorkerFactory } from "./bvhBuilder.js";
|
|
2
|
+
import { type GpuRenderSettings, type GpuRenderSettingsInput } from "./settings.js";
|
|
2
3
|
import { type DenoiserSettings } from "./denoise.js";
|
|
3
|
-
import { EventEmitter, type Bucket, type RenderCamera, type RenderEnvironment, type RenderImage, type RenderPhase, type RenderPixels, type RenderScene, type
|
|
4
|
+
import { EventEmitter, type Bucket, type RenderCamera, type RenderEnvironment, type RenderImage, type RenderPhase, type RenderPixels, type RenderScene, type SerializedScene } from "./upstream.js";
|
|
4
5
|
export type { RenderCamera, RenderImage, RenderPhase, RenderPixels, RenderScene } from "./upstream.js";
|
|
5
6
|
/**
|
|
6
7
|
* The CPU renderer's events, with the same payloads, so a host listens to either renderer the same way. What differs
|
|
@@ -42,8 +43,11 @@ export interface GpuRendererOptions {
|
|
|
42
43
|
scene?: RenderScene | SerializedScene;
|
|
43
44
|
/** Defaults to a perspective camera at (5, 5, 5) looking at the origin. */
|
|
44
45
|
camera?: RenderCamera;
|
|
45
|
-
/**
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Merged over `DEFAULT_GPU_RENDER_SETTINGS`, group by group: the CPU renderer's settings, and the groups only this
|
|
48
|
+
* renderer reads (`bloom`, `lens`, `camera`, `subsurface`, the roulette under `globalIllumination`).
|
|
49
|
+
*/
|
|
50
|
+
settings?: GpuRenderSettingsInput;
|
|
47
51
|
/** Defaults to `DEFAULT_RENDER_ENVIRONMENT`. */
|
|
48
52
|
environment?: RenderEnvironment;
|
|
49
53
|
/** Image size in CSS pixels; the render size also applies `pixelRatio` and `settings.renderScale`. */
|
|
@@ -102,11 +106,13 @@ export declare class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
102
106
|
private planar;
|
|
103
107
|
private resolve;
|
|
104
108
|
private denoisePass;
|
|
109
|
+
private lensPass;
|
|
105
110
|
private denoiserSettings;
|
|
106
111
|
private finished;
|
|
107
112
|
private denoised;
|
|
108
113
|
private denoiseView;
|
|
109
114
|
private environmentSource;
|
|
115
|
+
private environmentAverage;
|
|
110
116
|
private environmentImage;
|
|
111
117
|
private canvas;
|
|
112
118
|
private context;
|
|
@@ -117,7 +123,7 @@ export declare class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
117
123
|
private bvh;
|
|
118
124
|
constructor(options: GpuRendererOptions);
|
|
119
125
|
/** The settings the next frame renders with. */
|
|
120
|
-
get settings(): Readonly<
|
|
126
|
+
get settings(): Readonly<GpuRenderSettings>;
|
|
121
127
|
/** How the denoiser filters. */
|
|
122
128
|
get denoiser(): Readonly<DenoiserSettings>;
|
|
123
129
|
/**
|
|
@@ -153,10 +159,11 @@ export declare class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
153
159
|
*/
|
|
154
160
|
setCamera(camera: RenderCamera): void;
|
|
155
161
|
/**
|
|
156
|
-
* Merges the given fields into the current settings, group by group. Turning the denoiser on or off
|
|
157
|
-
*
|
|
162
|
+
* Merges the given fields into the current settings, group by group. Turning the denoiser on or off, or changing
|
|
163
|
+
* what is done to the light after it is traced (exposure, tone mapping, vignette, bloom, chromatic aberration, white
|
|
164
|
+
* balance), shows the finished image again at once, as `setDenoiser` does, since none of that needs tracing.
|
|
158
165
|
*/
|
|
159
|
-
setSettings(settings:
|
|
166
|
+
setSettings(settings: GpuRenderSettingsInput): void;
|
|
160
167
|
setEnvironment(environment: RenderEnvironment): void;
|
|
161
168
|
/** Used by `attachCanvas`; pass `null` to detach. */
|
|
162
169
|
setOutputCanvas(canvas: HTMLCanvasElement | null): void;
|
|
@@ -201,6 +208,11 @@ export declare class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
201
208
|
private renderCaptures;
|
|
202
209
|
private configureCanvas;
|
|
203
210
|
private encodePresent;
|
|
211
|
+
/**
|
|
212
|
+
* The lens and the sensor over the radiance (or the denoiser's output), when the settings ask for any of them: the
|
|
213
|
+
* buffer of graded colour the display transform reads instead, or the input as it was.
|
|
214
|
+
*/
|
|
215
|
+
private encodeLens;
|
|
204
216
|
/** Denoises the accumulation as it stands, for the canvas and for the finished image. */
|
|
205
217
|
private encodeDenoise;
|
|
206
218
|
private frameUniforms;
|
package/dist/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
|
package/dist/renderer.js
CHANGED
|
@@ -8,16 +8,21 @@ import { DispatchScheduler } from "./dispatchScheduler.js";
|
|
|
8
8
|
import { packGeometry } from "./geometry.js";
|
|
9
9
|
import { packLights } from "./lights.js";
|
|
10
10
|
import { NO_PROGRAM } from "./materialLayout.js";
|
|
11
|
-
import { NO_TEXTURE, planTextures, writeTextures } from "./texturePack.js";
|
|
11
|
+
import { NO_TEXTURE, planTextures, textureEntry, writeTextures } from "./texturePack.js";
|
|
12
12
|
import { PathTracePass } from "./pathTracePass.js";
|
|
13
13
|
import { ImageResolvePass } from "./imageResolve.js";
|
|
14
14
|
import { PresentPass } from "./presentPass.js";
|
|
15
|
+
import { LensPass, lensEffects } from "./lensPass.js";
|
|
16
|
+
import { DEFAULT_GPU_RENDER_SETTINGS, effectiveExposure, mergeGpuSettings, } from "./settings.js";
|
|
17
|
+
import { bevelOf, bevelRadius, subsurfaceMedium, subsurfaceOf, volumeMedium, volumeOf, } from "./subsurface.js";
|
|
18
|
+
import { containingVolume, NO_MATERIAL, volumeTriangles } from "./volumeContainment.js";
|
|
15
19
|
import { CONVERGED_ERROR, DEFAULT_DENOISER, DenoisePass, mergeDenoiser, } from "./denoise.js";
|
|
16
20
|
import { CaptureResolvePass, planarCaptures } from "./planarCapture.js";
|
|
17
21
|
import { prepareSceneAssets } from "./sceneAssets.js";
|
|
18
22
|
import { packVolumetricLights, VOLUMETRIC_WORDS } from "./volumetric.js";
|
|
19
23
|
import { environmentPower, uploadScene } from "./sceneUpload.js";
|
|
20
|
-
import {
|
|
24
|
+
import { atmosphereExtinction, averageRadiance, brightestSun, gradientAverage } from "./atmosphere.js";
|
|
25
|
+
import { DEFAULT_RENDER_ENVIRONMENT, EventEmitter, serializeCamera, serializeScene, srgbToLinear, } from "./upstream.js";
|
|
21
26
|
function abortError(message) {
|
|
22
27
|
return Object.assign(new Error(message), { name: "AbortError" });
|
|
23
28
|
}
|
|
@@ -38,6 +43,29 @@ const yieldToPage = () => new Promise(resolve => setTimeout(resolve, 0));
|
|
|
38
43
|
* Waiting for an animation frame after every band would leave the device idle for most of each one.
|
|
39
44
|
*/
|
|
40
45
|
const SHOW_EVERY_MS = 16;
|
|
46
|
+
/**
|
|
47
|
+
* Submits on the device at once. Two keep it busy: the next band is queued while the last one runs, and each submit
|
|
48
|
+
* still stays short enough for the watchdog on its own.
|
|
49
|
+
*/
|
|
50
|
+
const BANDS_IN_FLIGHT = 2;
|
|
51
|
+
/** Throughput under which Russian roulette may end a path, with the chance of carrying on equal to the throughput. */
|
|
52
|
+
const ROULETTE_THRESHOLD = 0.2;
|
|
53
|
+
/** How often the canvas takes a freshly denoised image while a frame traces. */
|
|
54
|
+
const DENOISE_EVERY_MS = 200;
|
|
55
|
+
/** The most paths per pixel one dispatch takes, while no pixel can stop yet. */
|
|
56
|
+
const BATCH_PATHS = 4;
|
|
57
|
+
/**
|
|
58
|
+
* Paths per pixel the next pass takes. The first takes one, so the canvas shows something at once. Until adaptive
|
|
59
|
+
* sampling may stop a pixel, every pixel takes every path anyway, so a pass takes several and pays for reading and
|
|
60
|
+
* writing the accumulation once; from then on one at a time, so a pixel stops as soon as it has converged.
|
|
61
|
+
*/
|
|
62
|
+
function pathsPerDispatch(taken, until, adaptive) {
|
|
63
|
+
if (taken === 0)
|
|
64
|
+
return 1;
|
|
65
|
+
if (adaptive && taken >= until)
|
|
66
|
+
return 1;
|
|
67
|
+
return Math.max(1, Math.min(BATCH_PATHS, until - taken));
|
|
68
|
+
}
|
|
41
69
|
const EMPTY_SCENE = { meshes: [], lights: [], textures: [] };
|
|
42
70
|
/** Paths every pixel takes before adaptive sampling may stop it: below this its variance says too little. */
|
|
43
71
|
const ADAPTIVE_MIN = 32;
|
|
@@ -106,6 +134,7 @@ export class GpuRenderer extends EventEmitter {
|
|
|
106
134
|
planar = false;
|
|
107
135
|
resolve = null;
|
|
108
136
|
denoisePass = null;
|
|
137
|
+
lensPass = null;
|
|
109
138
|
denoiserSettings = { ...DEFAULT_DENOISER };
|
|
110
139
|
// The size of the last frame that finished, which setDenoiser filters again; null while a frame traces.
|
|
111
140
|
finished = null;
|
|
@@ -115,6 +144,8 @@ export class GpuRenderer extends EventEmitter {
|
|
|
115
144
|
denoiseView = { pixelScale: 1, orthographic: false };
|
|
116
145
|
// Reading an environment image is a fetch, so it is started once and awaited by the frame that needs it.
|
|
117
146
|
environmentSource = "";
|
|
147
|
+
// The surroundings' radiance averaged over the sphere, which the air glows with; null for the gradient.
|
|
148
|
+
environmentAverage = null;
|
|
118
149
|
environmentImage = Promise.resolve(null);
|
|
119
150
|
canvas = null;
|
|
120
151
|
context = null;
|
|
@@ -129,10 +160,10 @@ export class GpuRenderer extends EventEmitter {
|
|
|
129
160
|
this.width = Math.max(1, Math.floor(options.width));
|
|
130
161
|
this.height = Math.max(1, Math.floor(options.height));
|
|
131
162
|
this.pixelRatio = checkedPixelRatio(options.pixelRatio ?? 1);
|
|
132
|
-
this.current =
|
|
163
|
+
this.current = mergeGpuSettings(DEFAULT_GPU_RENDER_SETTINGS, options.settings);
|
|
133
164
|
this.denoiserSettings = mergeDenoiser(DEFAULT_DENOISER, options.denoiser);
|
|
134
165
|
this.environment = DEFAULT_RENDER_ENVIRONMENT;
|
|
135
|
-
this.scheduler = new DispatchScheduler({ maxRows: this.renderHeight });
|
|
166
|
+
this.scheduler = new DispatchScheduler({ maxRows: this.renderHeight, maxSamples: BATCH_PATHS });
|
|
136
167
|
this.setEnvironment(options.environment ?? DEFAULT_RENDER_ENVIRONMENT);
|
|
137
168
|
if (options.scene)
|
|
138
169
|
this.setScene(options.scene);
|
|
@@ -232,13 +263,14 @@ export class GpuRenderer extends EventEmitter {
|
|
|
232
263
|
this.camera = camera;
|
|
233
264
|
}
|
|
234
265
|
/**
|
|
235
|
-
* Merges the given fields into the current settings, group by group. Turning the denoiser on or off
|
|
236
|
-
*
|
|
266
|
+
* Merges the given fields into the current settings, group by group. Turning the denoiser on or off, or changing
|
|
267
|
+
* what is done to the light after it is traced (exposure, tone mapping, vignette, bloom, chromatic aberration, white
|
|
268
|
+
* balance), shows the finished image again at once, as `setDenoiser` does, since none of that needs tracing.
|
|
237
269
|
*/
|
|
238
270
|
setSettings(settings) {
|
|
239
|
-
const
|
|
240
|
-
this.current =
|
|
241
|
-
if (this.current
|
|
271
|
+
const before = developing(this.current);
|
|
272
|
+
this.current = mergeGpuSettings(this.current, settings);
|
|
273
|
+
if (developing(this.current) !== before)
|
|
242
274
|
this.refilter();
|
|
243
275
|
}
|
|
244
276
|
setEnvironment(environment) {
|
|
@@ -326,6 +358,8 @@ export class GpuRenderer extends EventEmitter {
|
|
|
326
358
|
this.buffers?.destroy();
|
|
327
359
|
this.accumulator?.destroy();
|
|
328
360
|
this.denoisePass?.destroy();
|
|
361
|
+
this.lensPass?.destroy();
|
|
362
|
+
this.lensPass = null;
|
|
329
363
|
this.imageResolve?.dispose();
|
|
330
364
|
this.imageResolve = null;
|
|
331
365
|
this.gpu?.device.destroy();
|
|
@@ -378,25 +412,48 @@ export class GpuRenderer extends EventEmitter {
|
|
|
378
412
|
this.output = { width, height, data: new Uint8ClampedArray(width * height * 4) };
|
|
379
413
|
this.emit("start", { width, height });
|
|
380
414
|
const target = Math.max(1, Math.round(this.current.samples));
|
|
381
|
-
// Adaptive sampling: the frame has the work of `target`
|
|
415
|
+
// Adaptive sampling: the frame has the work of `target` paths over the whole image to spend. Pixels that have
|
|
382
416
|
// converged stop taking samples, and a row with none left stops being dispatched, so what they would have used goes
|
|
383
417
|
// to the rows that still need it, up to a few times `target` paths in the noisiest pixels.
|
|
384
418
|
const adaptive = target > ADAPTIVE_MIN;
|
|
385
|
-
const
|
|
419
|
+
const maxPaths = adaptive ? target * ADAPTIVE_REACH : target;
|
|
420
|
+
// In rows times paths, the unit the scheduler measures work in.
|
|
386
421
|
const budget = target * height;
|
|
387
422
|
const accumulator = this.accumulator;
|
|
388
423
|
let active = Array.from({ length: height }, (_, row) => row);
|
|
389
|
-
|
|
424
|
+
// Paths every pixel still in the running has taken so far, which is also the next sample index.
|
|
425
|
+
let taken = 0;
|
|
426
|
+
let spent = 0, band = 0;
|
|
390
427
|
let shown = performance.now();
|
|
428
|
+
let denoisedAt = -Infinity;
|
|
391
429
|
const denoise = this.current.globalIllumination.denoise;
|
|
430
|
+
// Bands on the device, oldest first. The next one is encoded and queued while the last one runs, so the device
|
|
431
|
+
// never waits for the page to hear that a band finished.
|
|
432
|
+
const flight = [];
|
|
433
|
+
let settledAt = 0;
|
|
434
|
+
const settle = async () => {
|
|
435
|
+
const oldest = flight.shift();
|
|
436
|
+
if (!oldest)
|
|
437
|
+
return;
|
|
438
|
+
await oldest.done;
|
|
439
|
+
const now = performance.now();
|
|
440
|
+
// A band starts on the device when the one before it ends, or when it was submitted if the device was idle. The
|
|
441
|
+
// page may hear of that end late, which would make this band look shorter than it ran; since it was submitted it
|
|
442
|
+
// has waited for at most the bands ahead of it, so its share of that wait bounds it from below.
|
|
443
|
+
const ran = Math.max(now - Math.max(oldest.submitted, settledAt), (now - oldest.submitted) / BANDS_IN_FLIGHT);
|
|
444
|
+
this.scheduler.record(ran, oldest.rows, oldest.samples);
|
|
445
|
+
settledAt = now;
|
|
446
|
+
};
|
|
392
447
|
this.denoised = false;
|
|
393
448
|
this.finished = null;
|
|
394
449
|
const matrices = cameraMatrices(camera, width, height);
|
|
450
|
+
const uniforms = this.frameUniforms(camera, width, height, assets);
|
|
395
451
|
// Two over the projection's vertical scale and the height: one pixel's extent, per unit of depth in perspective.
|
|
396
452
|
this.denoiseView = { pixelScale: 2 / (matrices.projection[5] * height), orthographic: matrices.orthographic };
|
|
397
|
-
while (
|
|
453
|
+
while (taken < maxPaths && spent < budget && active.length) {
|
|
398
454
|
// Only a pass whose pixels may stop needs its rows flagged; before that every row is busy anyway.
|
|
399
|
-
const flagging = adaptive &&
|
|
455
|
+
const flagging = adaptive && taken >= ADAPTIVE_MIN;
|
|
456
|
+
const samples = pathsPerDispatch(taken, adaptive ? ADAPTIVE_MIN : target, adaptive);
|
|
400
457
|
if (flagging) {
|
|
401
458
|
const clear = gpu.device.createCommandEncoder();
|
|
402
459
|
clear.clearBuffer(accumulator.buffer, accumulator.rowFlagsOffset, accumulator.rowFlagsLength);
|
|
@@ -408,16 +465,16 @@ export class GpuRenderer extends EventEmitter {
|
|
|
408
465
|
if (this.isPaused)
|
|
409
466
|
await this.unpaused;
|
|
410
467
|
this.check(revision);
|
|
411
|
-
const rows = this.scheduler.next(end - row);
|
|
468
|
+
const rows = this.scheduler.next(end - row, samples);
|
|
412
469
|
const started = performance.now();
|
|
413
470
|
const encoder = gpu.device.createCommandEncoder();
|
|
414
471
|
this.emit("bucket", { worker: 0, bucket: { x: 0, y: row, width, height: rows, index: band++ }, width, height });
|
|
415
472
|
this.pathTrace?.encode(encoder, {
|
|
416
|
-
...
|
|
473
|
+
...uniforms,
|
|
417
474
|
bandOrigin: [0, row],
|
|
418
475
|
bandSize: [width, rows],
|
|
419
|
-
sampleIndex:
|
|
420
|
-
sampleCount:
|
|
476
|
+
sampleIndex: taken,
|
|
477
|
+
sampleCount: samples,
|
|
421
478
|
adaptiveMin: adaptive ? ADAPTIVE_MIN : 0,
|
|
422
479
|
adaptiveThreshold: ADAPTIVE_THRESHOLD,
|
|
423
480
|
});
|
|
@@ -426,10 +483,11 @@ export class GpuRenderer extends EventEmitter {
|
|
|
426
483
|
if (show)
|
|
427
484
|
this.encodePresent(encoder, width, height);
|
|
428
485
|
gpu.device.queue.submit([encoder.finish()]);
|
|
429
|
-
|
|
430
|
-
|
|
486
|
+
flight.push({ done: gpu.device.queue.onSubmittedWorkDone(), submitted: performance.now(), rows, samples });
|
|
487
|
+
while (flight.length >= BANDS_IN_FLIGHT)
|
|
488
|
+
await settle();
|
|
431
489
|
row += rows;
|
|
432
|
-
spent += rows;
|
|
490
|
+
spent += rows * samples;
|
|
433
491
|
this.emit("progress", { completed: Math.min(spent, budget), total: budget, phase: "shade" });
|
|
434
492
|
if (show) {
|
|
435
493
|
shown = performance.now();
|
|
@@ -437,20 +495,29 @@ export class GpuRenderer extends EventEmitter {
|
|
|
437
495
|
}
|
|
438
496
|
}
|
|
439
497
|
}
|
|
440
|
-
|
|
498
|
+
taken += samples;
|
|
441
499
|
this.frameIndex++;
|
|
442
|
-
// The
|
|
443
|
-
//
|
|
444
|
-
|
|
500
|
+
// The rows this pass still traced in are copied out before anything else is queued, so they can be read while
|
|
501
|
+
// the device goes on with the denoiser.
|
|
502
|
+
const rowsRead = flagging ? this.activeRows(gpu, accumulator) : null;
|
|
503
|
+
// The canvas shows the denoised image, refreshed a few times a second while the bands refine underneath it:
|
|
504
|
+
// filtering the whole image after every pass would cost a cheap scene as much as tracing it.
|
|
505
|
+
if (denoise && this.canvas && this.context && performance.now() - denoisedAt >= DENOISE_EVERY_MS) {
|
|
445
506
|
const encoder = gpu.device.createCommandEncoder();
|
|
446
507
|
this.encodeDenoise(gpu, encoder, width, height);
|
|
447
508
|
this.encodePresent(encoder, width, height);
|
|
448
509
|
gpu.device.queue.submit([encoder.finish()]);
|
|
510
|
+
denoisedAt = performance.now();
|
|
449
511
|
}
|
|
450
512
|
this.emit("sample", { samples: Math.min(target, Math.round(spent / height)), target });
|
|
451
|
-
if (
|
|
452
|
-
|
|
513
|
+
if (rowsRead) {
|
|
514
|
+
while (flight.length)
|
|
515
|
+
await settle();
|
|
516
|
+
active = await rowsRead;
|
|
517
|
+
}
|
|
453
518
|
}
|
|
519
|
+
while (flight.length)
|
|
520
|
+
await settle();
|
|
454
521
|
// Converged early, or spent: either way the image is as finished as this frame makes it.
|
|
455
522
|
this.emit("progress", { completed: budget, total: budget, phase: "shade" });
|
|
456
523
|
this.emit("sample", { samples: target, target });
|
|
@@ -495,9 +562,14 @@ export class GpuRenderer extends EventEmitter {
|
|
|
495
562
|
/** Uploads what changed and makes sure the passes point at the current buffers. */
|
|
496
563
|
async prepare(gpu, revision, assets, camera, width, height, image) {
|
|
497
564
|
this.pathTrace ??= new PathTracePass(gpu.device);
|
|
498
|
-
|
|
565
|
+
const materials = assets.scene.meshes.flatMap(mesh => mesh.materials);
|
|
566
|
+
this.pathTrace.setPrograms(assets.programs, this.current.subsurface.enabled && materials.some(material => (material.type === "MeshStandardMaterial" || material.type === "MeshPhysicalMaterial")
|
|
567
|
+
&& subsurfaceOf(material) !== null),
|
|
568
|
+
// Even turned off, a volume's boundary has to stay invisible, which takes the same code.
|
|
569
|
+
materials.some(material => volumeOf(material) !== null), this.current.bevel.enabled && materials.some(material => bevelOf(material) > 0));
|
|
499
570
|
// A procedural sky stands in for the renderer's own environment, as it does in the CPU renderer.
|
|
500
571
|
const environment = assets.sky?.environment ?? image;
|
|
572
|
+
this.environmentAverage = environment ? averageRadiance(environment) : null;
|
|
501
573
|
const matrices = cameraMatrices(camera, width, height);
|
|
502
574
|
const view = `${JSON.stringify(camera)}|${width}|${height}`;
|
|
503
575
|
const moved = (!!this.packed?.cameraDependent || this.planar) && view !== this.packedView;
|
|
@@ -523,7 +595,7 @@ export class GpuRenderer extends EventEmitter {
|
|
|
523
595
|
const resized = !this.accumulator || this.accumulator.resize(width, height);
|
|
524
596
|
this.accumulator ??= new Accumulator(gpu.device, width, height);
|
|
525
597
|
if (resized)
|
|
526
|
-
this.scheduler = new DispatchScheduler({ maxRows: height });
|
|
598
|
+
this.scheduler = new DispatchScheduler({ maxRows: height, maxSamples: BATCH_PATHS });
|
|
527
599
|
this.pathTrace.setBindings(this.buffers, this.accumulator.buffer);
|
|
528
600
|
this.configureCanvas(gpu, width, height);
|
|
529
601
|
// Every frame starts from nothing: the camera, the scene or the settings may have moved since the last one. The
|
|
@@ -575,7 +647,8 @@ export class GpuRenderer extends EventEmitter {
|
|
|
575
647
|
// Nothing is on the device yet, so a frame replaced while the hierarchy was built has nothing to give back.
|
|
576
648
|
this.check(revision);
|
|
577
649
|
const buffers = uploadScene(gpu.device, geometry, bvh, textures, lights, environment, cdf);
|
|
578
|
-
const textureOffsets = new Map([...textures.indexOf]
|
|
650
|
+
const textureOffsets = new Map([...textures.indexOf]
|
|
651
|
+
.map(([id, index]) => [id, textures.data[textureEntry(index) + 2]]));
|
|
579
652
|
const packed = { cameraDependent: geometry.cameraDependent, textureOffsets };
|
|
580
653
|
if (buffers.bytes.largest > gpu.maxStorageBufferBindingSize) {
|
|
581
654
|
buffers.destroy();
|
|
@@ -587,6 +660,7 @@ export class GpuRenderer extends EventEmitter {
|
|
|
587
660
|
skyData,
|
|
588
661
|
volumetricData,
|
|
589
662
|
volumetricCount: Math.min(volumetric.length / VOLUMETRIC_WORDS, 8),
|
|
663
|
+
volumes: volumeTriangles(geometry),
|
|
590
664
|
};
|
|
591
665
|
}
|
|
592
666
|
/**
|
|
@@ -616,6 +690,10 @@ export class GpuRenderer extends EventEmitter {
|
|
|
616
690
|
const uniforms = {
|
|
617
691
|
...this.frameUniforms(capture.camera, width, height, assets, target, capture.background),
|
|
618
692
|
lens: [0, 1, 0, 0],
|
|
693
|
+
lensDistortion: 0,
|
|
694
|
+
// A capture is a texture of the scene, seen again by the camera, which lays the air over it then.
|
|
695
|
+
atmosphereColor: [0, 0, 0, 0],
|
|
696
|
+
cameraVolume: NO_MATERIAL,
|
|
619
697
|
volumetricCount: 0,
|
|
620
698
|
clipPlane: capture.clip ?? [0, 0, 0, 0],
|
|
621
699
|
bandOrigin: [0, 0],
|
|
@@ -659,20 +737,31 @@ export class GpuRenderer extends EventEmitter {
|
|
|
659
737
|
this.present.setAccumulation(this.accumulator.buffer);
|
|
660
738
|
}
|
|
661
739
|
encodePresent(encoder, width, height) {
|
|
662
|
-
if (!this.context || !this.present)
|
|
740
|
+
if (!this.context || !this.present || !this.gpu)
|
|
663
741
|
return;
|
|
664
742
|
this.present.encode(encoder, this.context.getCurrentTexture().createView(), {
|
|
665
743
|
width,
|
|
666
744
|
height,
|
|
667
|
-
exposure: this.current
|
|
745
|
+
exposure: effectiveExposure(this.current),
|
|
668
746
|
toneMapping: this.current.toneMapping,
|
|
669
747
|
vignette: this.current.vignette,
|
|
670
748
|
grain: this.current.grain,
|
|
671
749
|
frameIndex: this.frameIndex,
|
|
672
750
|
transparent: this.current.background.mode === "transparent",
|
|
673
|
-
denoised: this.denoised ? this.denoisePass?.output : null,
|
|
751
|
+
denoised: this.encodeLens(this.gpu, encoder, width, height, this.denoised ? this.denoisePass?.output ?? null : null),
|
|
674
752
|
});
|
|
675
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* The lens and the sensor over the radiance (or the denoiser's output), when the settings ask for any of them: the
|
|
756
|
+
* buffer of graded colour the display transform reads instead, or the input as it was.
|
|
757
|
+
*/
|
|
758
|
+
encodeLens(gpu, encoder, width, height, denoised) {
|
|
759
|
+
const effects = lensEffects(this.current);
|
|
760
|
+
if (!effects)
|
|
761
|
+
return denoised;
|
|
762
|
+
this.lensPass ??= new LensPass(gpu.device);
|
|
763
|
+
return this.lensPass.encode(encoder, this.accumulator.buffer, denoised, width, height, effects);
|
|
764
|
+
}
|
|
676
765
|
/** Denoises the accumulation as it stands, for the canvas and for the finished image. */
|
|
677
766
|
encodeDenoise(gpu, encoder, width, height) {
|
|
678
767
|
this.denoisePass ??= new DenoisePass(gpu.device);
|
|
@@ -695,6 +784,13 @@ export class GpuRenderer extends EventEmitter {
|
|
|
695
784
|
? focalLength / (2 * Math.max(depthOfField.aperture, 1e-3))
|
|
696
785
|
: 0;
|
|
697
786
|
const backgroundMode = background.mode === "color" ? 1 : background.mode === "transparent" ? 2 : 0;
|
|
787
|
+
const { atmosphere } = this.current;
|
|
788
|
+
const strength = sky ? sky.intensity : this.environment.intensity ?? 1;
|
|
789
|
+
const airGlow = atmosphere.color
|
|
790
|
+
? linearColor(atmosphere.color, [0, 0, 0])
|
|
791
|
+
: (this.environmentAverage ?? gradientAverage(linearColor(gradient?.topColor, [1, 1, 1]), linearColor(gradient?.bottomColor, [0, 0, 0]), gradient?.exponent ?? 2)).map(channel => channel * strength);
|
|
792
|
+
const extinction = atmosphereExtinction(atmosphere);
|
|
793
|
+
const sun = brightestSun(assets.scene.lights);
|
|
698
794
|
return {
|
|
699
795
|
cameraToWorld: matrices.cameraWorld,
|
|
700
796
|
worldToCamera: matrices.view,
|
|
@@ -721,15 +817,35 @@ export class GpuRenderer extends EventEmitter {
|
|
|
721
817
|
resolution: [width, height],
|
|
722
818
|
cameraKind: matrices.orthographic ? 1 : 0,
|
|
723
819
|
bounces: Math.max(1, Math.round(this.current.globalIllumination.bounces)),
|
|
820
|
+
rouletteThreshold: this.current.globalIllumination.russianRoulette === false ? 0 : ROULETTE_THRESHOLD,
|
|
821
|
+
rouletteStart: Math.max(0, Math.round(this.current.globalIllumination.rouletteStart ?? 0)),
|
|
822
|
+
lensDistortion: Number.isFinite(this.current.lens.distortion) ? this.current.lens.distortion : 0,
|
|
823
|
+
subsurfaceEnabled: this.current.subsurface.enabled,
|
|
824
|
+
bevelEnabled: this.current.bevel.enabled,
|
|
825
|
+
volumesEnabled: this.current.volumes.enabled,
|
|
826
|
+
cameraVolume: this.current.volumes.enabled
|
|
827
|
+
? containingVolume(target?.volumes ?? null, matrices.cameraWorld.slice(12, 15))
|
|
828
|
+
: NO_MATERIAL,
|
|
829
|
+
atmosphereColor: [...airGlow, extinction.some(value => value > 0) ? 1 : 0],
|
|
830
|
+
atmosphereExtinction: [...extinction, Math.min(1, Math.max(0, Number(atmosphere.haze) || 0))],
|
|
831
|
+
atmosphereSun: [...sun?.direction ?? [0, 1, 0], 0],
|
|
832
|
+
atmosphereSunColor: [...sun?.irradiance ?? [0, 0, 0], 0],
|
|
724
833
|
triangleCount: buffers?.triangleCount ?? 0,
|
|
725
834
|
lightCount: buffers?.lightCount ?? 0,
|
|
726
835
|
areaLightCount: buffers?.areaLightCount ?? 0,
|
|
836
|
+
lightCdf: buffers?.lightCdf ?? 0,
|
|
837
|
+
lightTriangles: buffers?.lightTriangles ?? 0,
|
|
838
|
+
localLightCount: buffers?.localLightCount ?? 0,
|
|
839
|
+
localLightPick: buffers?.localLightPick ?? 0,
|
|
840
|
+
lightTree: this.current.globalIllumination.lightTree === false ? 0 : buffers?.lightTree ?? 0,
|
|
841
|
+
lightLeaves: buffers?.lightLeaves ?? 0,
|
|
727
842
|
clampIndirect: Math.max(0, this.current.globalIllumination.clamp),
|
|
728
843
|
// The setting is the angular diameter a light covers, in degrees, as it is for the CPU renderer's shadow blur.
|
|
729
844
|
shadowSpread: shadows.enabled ? Math.tan(Math.max(0, shadows.softness) * Math.PI / 360) : 0,
|
|
730
845
|
shadowsEnabled: shadows.enabled,
|
|
731
846
|
uvOffset: buffers?.uvOffset ?? 0,
|
|
732
847
|
orderOffset: buffers?.orderOffset ?? 0,
|
|
848
|
+
leafOffset: buffers?.leafOffset ?? 0,
|
|
733
849
|
colorOffset: buffers?.colorOffset ?? 0,
|
|
734
850
|
hasColors: buffers?.hasColors ?? false,
|
|
735
851
|
varyingOffset: buffers?.varyingOffset ?? 0,
|
|
@@ -755,11 +871,16 @@ export class GpuRenderer extends EventEmitter {
|
|
|
755
871
|
*/
|
|
756
872
|
async readImage(gpu, width, height, denoised) {
|
|
757
873
|
const accumulator = this.accumulator;
|
|
758
|
-
const {
|
|
874
|
+
const { toneMapping, vignette } = this.current;
|
|
875
|
+
const exposure = effectiveExposure(this.current);
|
|
759
876
|
const transparent = this.current.background.mode === "transparent";
|
|
760
877
|
// Resolved on the device, so the page only copies the bytes of the image.
|
|
761
878
|
this.imageResolve ??= new ImageResolvePass(gpu.device);
|
|
762
|
-
|
|
879
|
+
// The lens's effects are encoded on their own, ahead of the resolve, which reads the colour they leave.
|
|
880
|
+
const encoder = gpu.device.createCommandEncoder({ label: "lens" });
|
|
881
|
+
const graded = this.encodeLens(gpu, encoder, width, height, denoised);
|
|
882
|
+
gpu.device.queue.submit([encoder.finish()]);
|
|
883
|
+
const data = await this.imageResolve.read(accumulator.buffer, graded, width, height, { exposure, toneMapping, vignette, transparent });
|
|
763
884
|
return { width, height, data };
|
|
764
885
|
}
|
|
765
886
|
}
|
|
@@ -777,6 +898,9 @@ const SERIALIZABLE = new Set([
|
|
|
777
898
|
*/
|
|
778
899
|
async function serializeLive(scene) {
|
|
779
900
|
const renamed = new Map();
|
|
901
|
+
// The snapshot keeps a material's name but not its media, so the name carries a mark to them.
|
|
902
|
+
const named = new Map();
|
|
903
|
+
const media = [];
|
|
780
904
|
scene.traverse(object => {
|
|
781
905
|
const value = object.material;
|
|
782
906
|
for (const material of Array.isArray(value) ? value : value ? [value] : []) {
|
|
@@ -785,16 +909,54 @@ async function serializeLive(scene) {
|
|
|
785
909
|
renamed.set(material, material.type);
|
|
786
910
|
material.type = shader.isRawShaderMaterial ? "RawShaderMaterial" : "ShaderMaterial";
|
|
787
911
|
}
|
|
912
|
+
const data = material.userData?.cpuRenderer;
|
|
913
|
+
if (!named.has(material)
|
|
914
|
+
&& (subsurfaceMedium(data?.subsurface) || volumeMedium(data?.volume) || bevelRadius(data?.bevel) > 0)) {
|
|
915
|
+
named.set(material, material.name);
|
|
916
|
+
material.name = `${material.name}${MEDIA_MARK}${media.length}`;
|
|
917
|
+
media.push({ subsurface: data?.subsurface, volume: data?.volume, bevel: data?.bevel });
|
|
918
|
+
}
|
|
788
919
|
}
|
|
789
920
|
});
|
|
921
|
+
let snapshot;
|
|
790
922
|
try {
|
|
791
923
|
// The path tracer samples only the base level of a texture: the mip chain would be built and thrown away.
|
|
792
|
-
|
|
924
|
+
snapshot = (await serializeScene(scene, { mipmaps: false })).scene;
|
|
793
925
|
}
|
|
794
926
|
finally {
|
|
795
927
|
for (const [material, type] of renamed)
|
|
796
928
|
material.type = type;
|
|
929
|
+
for (const [material, name] of named)
|
|
930
|
+
material.name = name;
|
|
931
|
+
}
|
|
932
|
+
// Materials are shared between the meshes that use them, so each one is unmarked once and seen marked no more.
|
|
933
|
+
for (const mesh of snapshot.meshes) {
|
|
934
|
+
for (const material of mesh.materials) {
|
|
935
|
+
const at = material.name.lastIndexOf(MEDIA_MARK);
|
|
936
|
+
if (at < 0)
|
|
937
|
+
continue;
|
|
938
|
+
const medium = media[Number(material.name.slice(at + MEDIA_MARK.length))];
|
|
939
|
+
material.name = material.name.slice(0, at) || material.type;
|
|
940
|
+
if (medium.subsurface !== undefined)
|
|
941
|
+
material.subsurface = medium.subsurface;
|
|
942
|
+
if (medium.volume !== undefined)
|
|
943
|
+
material.volume = medium.volume;
|
|
944
|
+
if (medium.bevel !== undefined)
|
|
945
|
+
material.bevel = medium.bevel;
|
|
946
|
+
}
|
|
797
947
|
}
|
|
948
|
+
return snapshot;
|
|
949
|
+
}
|
|
950
|
+
/** Ends a material's name while it is serialized, followed by the index of its media. */
|
|
951
|
+
const MEDIA_MARK = "\u0000media:";
|
|
952
|
+
/**
|
|
953
|
+
* Everything that changes the finished image without tracing it again, as one string: the denoiser switch and what
|
|
954
|
+
* happens to the light between the radiance and the screen.
|
|
955
|
+
*/
|
|
956
|
+
function developing(settings) {
|
|
957
|
+
const { exposure, toneMapping, vignette, bloom, glare, lens, camera, depthOfField, globalIllumination } = settings;
|
|
958
|
+
return JSON.stringify([globalIllumination.denoise, exposure, toneMapping, vignette, bloom, glare,
|
|
959
|
+
lens.chromaticAberration, camera, camera.physicalExposure ? depthOfField.aperture : 0]);
|
|
798
960
|
}
|
|
799
961
|
/**
|
|
800
962
|
* The summed coefficients of every LightProbe the renderer would draw, times its intensity, as Three's WebGLLights sums
|