@tinypoly/gpu-renderer 0.0.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +59 -3
- package/README.md +53 -4
- 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/denoise.d.ts +11 -1
- package/dist/denoise.d.ts.map +1 -1
- package/dist/denoise.js +34 -6
- package/dist/denoise.js.map +1 -1
- package/dist/frameLayout.d.ts.map +1 -1
- package/dist/frameLayout.js +17 -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 +50 -0
- package/dist/lightTree.d.ts.map +1 -0
- package/dist/lightTree.js +146 -0
- package/dist/lightTree.js.map +1 -0
- package/dist/lights.d.ts +12 -0
- package/dist/lights.d.ts.map +1 -1
- package/dist/lights.js +56 -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 +27 -7
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +153 -16
- package/dist/renderer.js.map +1 -1
- package/dist/sceneUpload.d.ts +6 -0
- package/dist/sceneUpload.d.ts.map +1 -1
- package/dist/sceneUpload.js +5 -0
- package/dist/sceneUpload.js.map +1 -1
- package/dist/settings.d.ts +142 -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/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/common.wgsl.d.ts.map +1 -1
- package/dist/wgsl/common.wgsl.js +4 -0
- 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 +301 -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 +237 -31
- 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 +406 -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 +28 -0
- 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 +21 -1
- package/dist/wgsl/surface.wgsl.js.map +1 -1
- package/package.json +1 -1
- package/src/atmosphere.ts +95 -0
- package/src/denoise.ts +44 -6
- package/src/frameLayout.ts +17 -0
- package/src/geometry.ts +6 -2
- package/src/index.ts +13 -0
- package/src/lensPass.ts +299 -0
- package/src/lightTree.ts +218 -0
- package/src/lights.ts +75 -1
- package/src/materialLayout.ts +52 -1
- package/src/pathTracePass.ts +19 -10
- package/src/renderer.ts +206 -24
- package/src/sceneUpload.ts +11 -0
- package/src/settings.ts +240 -0
- package/src/subsurface.ts +153 -0
- package/src/volumeContainment.ts +81 -0
- package/src/wgsl/bsdf.wgsl.ts +35 -3
- package/src/wgsl/common.wgsl.ts +4 -0
- package/src/wgsl/lens.wgsl.ts +300 -0
- package/src/wgsl/lights.wgsl.ts +237 -31
- package/src/wgsl/pathTrace.wgsl.ts +406 -25
- package/src/wgsl/random.wgsl.ts +28 -0
- package/src/wgsl/sampling.wgsl.ts +25 -0
- package/src/wgsl/surface.wgsl.ts +21 -1
package/src/materialLayout.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { layoutStruct, writeStruct, type StructField } from "./structLayout.js";
|
|
2
|
+
import { bevelOf, subsurfaceOf, volumeOf } from "./subsurface.js";
|
|
2
3
|
import { NO_TEXTURE } from "./texturePack.js";
|
|
3
4
|
import type { SerializedMaterial, UniformValue } from "./upstream.js";
|
|
4
5
|
|
|
@@ -59,6 +60,20 @@ export interface MaterialProperties {
|
|
|
59
60
|
combine: number;
|
|
60
61
|
side: number;
|
|
61
62
|
blending: number;
|
|
63
|
+
/**
|
|
64
|
+
* Share of the diffuse layer that scatters light inside the material, from `userData.cpuRenderer.subsurface`; zero
|
|
65
|
+
* for none. Physical and standard materials only.
|
|
66
|
+
*/
|
|
67
|
+
subsurface: number;
|
|
68
|
+
/** Mean free path of red, green and blue inside the material, in world units. */
|
|
69
|
+
subsurfaceRadius: number[];
|
|
70
|
+
subsurfaceAnisotropy: number;
|
|
71
|
+
/** The medium a volume's boundary encloses: its single scattering albedo, density per scene unit and anisotropy. */
|
|
72
|
+
volumeAlbedo: number[];
|
|
73
|
+
volumeDensity: number;
|
|
74
|
+
volumeAnisotropy: number;
|
|
75
|
+
/** The radius over which edges are rounded at shading time, in scene units; zero leaves them sharp. */
|
|
76
|
+
bevel: number;
|
|
62
77
|
/** An index into the texture table per slot, or `NO_TEXTURE`. */
|
|
63
78
|
maps: Record<MaterialMap, number>;
|
|
64
79
|
}
|
|
@@ -137,6 +152,18 @@ export const MATERIAL_ALPHA_HASH = 2048;
|
|
|
137
152
|
/** A physical material, whose specular colour and intensity reach the lobe and whose grazing reflectance follows them. */
|
|
138
153
|
export const MATERIAL_SPECULAR = 4096;
|
|
139
154
|
|
|
155
|
+
/**
|
|
156
|
+
* The mesh only bounds a medium, from `userData.cpuRenderer.volume`: its surface is invisible and the space inside
|
|
157
|
+
* scatters light.
|
|
158
|
+
*/
|
|
159
|
+
export const MATERIAL_VOLUME = 8192;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* An unlit material whose colour is known without running anything: opaque, normal blending, no light map, environment
|
|
163
|
+
* map, alpha map or shader. What it shows is what it emits, so direct sampling can aim at it like an emissive one.
|
|
164
|
+
*/
|
|
165
|
+
export const MATERIAL_UNLIT_LIGHT = 16384;
|
|
166
|
+
|
|
140
167
|
/** What a material's `program` reads as when it has no shader. */
|
|
141
168
|
export const NO_PROGRAM = 0xffffffff;
|
|
142
169
|
|
|
@@ -186,6 +213,11 @@ const FIELDS: StructField[] = [
|
|
|
186
213
|
{ name: "dielectric", type: "f32" },
|
|
187
214
|
{ name: "afterProgram", type: "u32", doc: "What a hook does after <opaque_fragment>, or NO_PROGRAM." },
|
|
188
215
|
{ name: "afterData", type: "u32" },
|
|
216
|
+
{ name: "subsurface", type: "vec4f", doc: "rgb is the mean free path of each colour inside, w the share of the diffuse layer." },
|
|
217
|
+
{ name: "subsurfaceAnisotropy", type: "f32" },
|
|
218
|
+
{ name: "volume", type: "vec4f", doc: "rgb is the medium's single scattering albedo, w its density per scene unit." },
|
|
219
|
+
{ name: "volumeAnisotropy", type: "f32" },
|
|
220
|
+
{ name: "bevel", type: "f32", doc: "The radius edges are rounded over at shading time; zero leaves them sharp." },
|
|
189
221
|
{ name: "normalRow0", type: "vec4f", doc: "The mesh's world normal matrix, for an object space normal map." },
|
|
190
222
|
{ name: "normalRow1", type: "vec4f" },
|
|
191
223
|
{ name: "normalRow2", type: "vec4f" },
|
|
@@ -263,6 +295,8 @@ export function materialProperties(
|
|
|
263
295
|
const sheen = physical ? Math.max(0, number(values.sheen, 0)) : 0;
|
|
264
296
|
const anisotropy = physical ? number(values.anisotropy, 0) : 0;
|
|
265
297
|
const thickness = vector(values.iridescenceThicknessRange, [100, 400], 2);
|
|
298
|
+
const medium = pbr ? subsurfaceOf(material) : null;
|
|
299
|
+
const volume = volumeOf(material);
|
|
266
300
|
|
|
267
301
|
const maps = Object.fromEntries(MATERIAL_MAPS.map(slot => {
|
|
268
302
|
const id = material?.maps?.[slot];
|
|
@@ -285,7 +319,12 @@ export function materialProperties(
|
|
|
285
319
|
| (number(values.normalMapType, 0) === 1 ? MATERIAL_OBJECT_NORMAL_MAP : 0)
|
|
286
320
|
| (shader?.patched ? MATERIAL_PATCHED : 0)
|
|
287
321
|
| (flag(values.alphaHash, false) ? MATERIAL_ALPHA_HASH : 0)
|
|
288
|
-
| (physical ? MATERIAL_SPECULAR : 0)
|
|
322
|
+
| (physical ? MATERIAL_SPECULAR : 0)
|
|
323
|
+
| (volume ? MATERIAL_VOLUME : 0)
|
|
324
|
+
| (type === "MeshBasicMaterial" && !shader && !transparent && !flag(values.alphaHash, false)
|
|
325
|
+
&& !flag(values.wireframe, false) && number(values.blending, 1) === 1 && number(values.opacity, 1) >= 1
|
|
326
|
+
&& number(values.alphaTest, 0) <= 0 && maps.alphaMap === NO_TEXTURE && maps.envMap === NO_TEXTURE
|
|
327
|
+
&& maps.lightMap === NO_TEXTURE && !volume ? MATERIAL_UNLIT_LIGHT : 0);
|
|
289
328
|
|
|
290
329
|
return {
|
|
291
330
|
kind,
|
|
@@ -330,6 +369,13 @@ export function materialProperties(
|
|
|
330
369
|
combine: number(values.combine, 0),
|
|
331
370
|
side: number(values.side, 0),
|
|
332
371
|
blending: number(values.blending, 1),
|
|
372
|
+
subsurface: medium?.weight ?? 0,
|
|
373
|
+
subsurfaceRadius: medium?.radius ?? [1, 1, 1],
|
|
374
|
+
subsurfaceAnisotropy: medium?.anisotropy ?? 0,
|
|
375
|
+
volumeAlbedo: volume?.albedo ?? [1, 1, 1],
|
|
376
|
+
volumeDensity: volume?.density ?? 0,
|
|
377
|
+
volumeAnisotropy: volume?.anisotropy ?? 0,
|
|
378
|
+
bevel: LIT.has(type) ? bevelOf(material) : 0,
|
|
333
379
|
maps,
|
|
334
380
|
};
|
|
335
381
|
}
|
|
@@ -400,6 +446,11 @@ export function writeMaterial(
|
|
|
400
446
|
dielectric: p.dielectric,
|
|
401
447
|
afterProgram: placement.afterProgram,
|
|
402
448
|
afterData: placement.afterData,
|
|
449
|
+
subsurface: [...p.subsurfaceRadius, p.subsurface],
|
|
450
|
+
subsurfaceAnisotropy: p.subsurfaceAnisotropy,
|
|
451
|
+
volume: [...p.volumeAlbedo, p.volumeDensity],
|
|
452
|
+
volumeAnisotropy: p.volumeAnisotropy,
|
|
453
|
+
bevel: p.bevel,
|
|
403
454
|
normalRow0: [n[0], n[3], n[6], 0],
|
|
404
455
|
normalRow1: [n[1], n[4], n[7], 0],
|
|
405
456
|
normalRow2: [n[2], n[5], n[8], 0],
|
package/src/pathTracePass.ts
CHANGED
|
@@ -23,8 +23,12 @@ export type FrameUniforms = Record<keyof typeof FRAME_LAYOUT.offsets & string, n
|
|
|
23
23
|
* declarations. The scene's translated shaders sit between the texture code they call and the surface code that calls
|
|
24
24
|
* them.
|
|
25
25
|
*/
|
|
26
|
-
export function pathTraceSource(
|
|
27
|
-
|
|
26
|
+
export function pathTraceSource(
|
|
27
|
+
programs: ShaderPrograms | null = null, subsurface = true, volumes = true, bevel = true): string {
|
|
28
|
+
// The random walk inside materials and the media inside volumes are compiled in only for a scene that has them: the
|
|
29
|
+
// kernel is bound by its size, and either would cost every other scene a few percent for code that never runs.
|
|
30
|
+
return `const SUBSURFACE_WALK: bool = ${subsurface};\nconst VOLUMES: bool = ${volumes};\nconst BEVEL: bool = ${bevel};\n`
|
|
31
|
+
+ COMMON_WGSL + RANDOM_WGSL + BVH_WGSL + SAMPLING_WGSL + TEXTURE_WGSL + SHADER_SUPPORT_WGSL
|
|
28
32
|
+ programsWgsl(programs) + ENVIRONMENT_WGSL + SURFACE_WGSL + BSDF_WGSL + LIGHTS_WGSL + PATH_TRACE_WGSL;
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -37,26 +41,31 @@ export class PathTracePass {
|
|
|
37
41
|
private readonly uniforms: GPUBuffer;
|
|
38
42
|
private readonly values = new ArrayBuffer(FRAME_BYTES);
|
|
39
43
|
private bindGroup: GPUBindGroup | null = null;
|
|
40
|
-
private programKey = "";
|
|
44
|
+
private programKey = "|false|false|false";
|
|
41
45
|
|
|
42
46
|
constructor(private readonly device: GPUDevice) {
|
|
43
|
-
this.pipeline = this.build(null);
|
|
47
|
+
this.pipeline = this.build(null, false, false, false);
|
|
44
48
|
this.uniforms = device.createBuffer({ size: FRAME_BYTES, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Recompiles the kernel when the scene's shaders changed, or whether it has a material that scatters light inside;
|
|
53
|
+
* the same set keeps the pipeline it has.
|
|
54
|
+
*/
|
|
55
|
+
setPrograms(programs: ShaderPrograms | null, subsurface = false, volumes = false, bevel = false) {
|
|
56
|
+
const key = `${programs?.key ?? ""}|${subsurface}|${volumes}|${bevel}`;
|
|
50
57
|
|
|
51
58
|
if (key === this.programKey) return;
|
|
52
59
|
|
|
53
60
|
this.programKey = key;
|
|
54
|
-
this.pipeline = this.build(programs);
|
|
61
|
+
this.pipeline = this.build(programs, subsurface, volumes, bevel);
|
|
55
62
|
this.bindGroup = null;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
private build(programs: ShaderPrograms | null
|
|
59
|
-
|
|
65
|
+
private build(programs: ShaderPrograms | null, subsurface: boolean, volumes: boolean,
|
|
66
|
+
bevel: boolean): GPUComputePipeline {
|
|
67
|
+
const module = createShaderModule(this.device, "path trace",
|
|
68
|
+
pathTraceSource(programs, subsurface, volumes, bevel));
|
|
60
69
|
|
|
61
70
|
return this.device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main" } });
|
|
62
71
|
}
|
package/src/renderer.ts
CHANGED
|
@@ -12,6 +12,26 @@ import { NO_TEXTURE, planTextures, textureEntry, writeTextures } from "./texture
|
|
|
12
12
|
import { PathTracePass, type FrameUniforms } 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 {
|
|
17
|
+
DEFAULT_GPU_RENDER_SETTINGS,
|
|
18
|
+
effectiveExposure,
|
|
19
|
+
mergeGpuSettings,
|
|
20
|
+
type GpuRenderSettings,
|
|
21
|
+
type GpuRenderSettingsInput,
|
|
22
|
+
} from "./settings.js";
|
|
23
|
+
import {
|
|
24
|
+
bevelOf,
|
|
25
|
+
bevelRadius,
|
|
26
|
+
type BevelMaterial,
|
|
27
|
+
subsurfaceMedium,
|
|
28
|
+
subsurfaceOf,
|
|
29
|
+
volumeMedium,
|
|
30
|
+
volumeOf,
|
|
31
|
+
type SubsurfaceMaterial,
|
|
32
|
+
type VolumeMaterial,
|
|
33
|
+
} from "./subsurface.js";
|
|
34
|
+
import { containingVolume, NO_MATERIAL, volumeTriangles, type VolumeTriangles } from "./volumeContainment.js";
|
|
15
35
|
import {
|
|
16
36
|
CONVERGED_ERROR,
|
|
17
37
|
DEFAULT_DENOISER,
|
|
@@ -24,11 +44,10 @@ import { CaptureResolvePass, planarCaptures, type PlanarCaptures } from "./plana
|
|
|
24
44
|
import { prepareSceneAssets, type SceneAssets } from "./sceneAssets.js";
|
|
25
45
|
import { packVolumetricLights, VOLUMETRIC_WORDS } from "./volumetric.js";
|
|
26
46
|
import { environmentPower, uploadScene, type SceneBuffers } from "./sceneUpload.js";
|
|
47
|
+
import { atmosphereExtinction, averageRadiance, brightestSun, gradientAverage } from "./atmosphere.js";
|
|
27
48
|
import {
|
|
28
49
|
DEFAULT_RENDER_ENVIRONMENT,
|
|
29
|
-
DEFAULT_RENDER_SETTINGS,
|
|
30
50
|
EventEmitter,
|
|
31
|
-
mergeSettings,
|
|
32
51
|
serializeCamera,
|
|
33
52
|
serializeScene,
|
|
34
53
|
srgbToLinear,
|
|
@@ -41,8 +60,6 @@ import {
|
|
|
41
60
|
type RenderPhase,
|
|
42
61
|
type RenderPixels,
|
|
43
62
|
type RenderScene,
|
|
44
|
-
type RenderSettings,
|
|
45
|
-
type RenderSettingsInput,
|
|
46
63
|
type SerializedScene,
|
|
47
64
|
} from "./upstream.js";
|
|
48
65
|
|
|
@@ -74,8 +91,11 @@ export interface GpuRendererOptions {
|
|
|
74
91
|
scene?: RenderScene | SerializedScene;
|
|
75
92
|
/** Defaults to a perspective camera at (5, 5, 5) looking at the origin. */
|
|
76
93
|
camera?: RenderCamera;
|
|
77
|
-
/**
|
|
78
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Merged over `DEFAULT_GPU_RENDER_SETTINGS`, group by group: the CPU renderer's settings, and the groups only this
|
|
96
|
+
* renderer reads (`bloom`, `lens`, `camera`, `subsurface`, the roulette under `globalIllumination`).
|
|
97
|
+
*/
|
|
98
|
+
settings?: GpuRenderSettingsInput;
|
|
79
99
|
/** Defaults to `DEFAULT_RENDER_ENVIRONMENT`. */
|
|
80
100
|
environment?: RenderEnvironment;
|
|
81
101
|
/** Image size in CSS pixels; the render size also applies `pixelRatio` and `settings.renderScale`. */
|
|
@@ -136,9 +156,30 @@ const SHOW_EVERY_MS = 16;
|
|
|
136
156
|
*/
|
|
137
157
|
const BANDS_IN_FLIGHT = 2;
|
|
138
158
|
|
|
139
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Throughput under which Russian roulette may end a path, with the chance of carrying on equal to the throughput. One
|
|
161
|
+
* plays it on every path past the first bounces, as PBRT does: a path off a light wall carries on as long as it
|
|
162
|
+
* carries light, instead of seven bounces before the roulette ever looks at it. The mean stays exact.
|
|
163
|
+
*/
|
|
164
|
+
const ROULETTE_THRESHOLD = 1;
|
|
165
|
+
|
|
166
|
+
/** How often the lens pass grades the canvas again while a frame traces without the denoiser. */
|
|
167
|
+
const LENS_EVERY_MS = 100;
|
|
168
|
+
|
|
169
|
+
/** How often the canvas takes a freshly denoised image while a frame traces, for an image of DENOISE_REFERENCE work. */
|
|
140
170
|
const DENOISE_EVERY_MS = 200;
|
|
141
171
|
|
|
172
|
+
/** The filtering of a 1080p image with three passes, which the interval above was set for. */
|
|
173
|
+
const DENOISE_REFERENCE = 1920 * 1080 * 3;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* How long the canvas waits between denoised images: longer for a larger image or more passes, in proportion, so the
|
|
177
|
+
* filter takes the same share of the device from tracing whatever the size. The finished image is filtered regardless.
|
|
178
|
+
*/
|
|
179
|
+
export function denoiseInterval(width: number, height: number, passes: number): number {
|
|
180
|
+
return DENOISE_EVERY_MS * Math.max(1, width * height * Math.max(1, passes) / DENOISE_REFERENCE);
|
|
181
|
+
}
|
|
182
|
+
|
|
142
183
|
/** The most paths per pixel one dispatch takes, while no pixel can stop yet. */
|
|
143
184
|
const BATCH_PATHS = 4;
|
|
144
185
|
|
|
@@ -201,6 +242,8 @@ interface PackedTarget {
|
|
|
201
242
|
skyData: number;
|
|
202
243
|
volumetricData: number;
|
|
203
244
|
volumetricCount: number;
|
|
245
|
+
/** The boundaries of the scene's volumes, which say whether the camera stands inside one. */
|
|
246
|
+
volumes: VolumeTriangles | null;
|
|
204
247
|
}
|
|
205
248
|
|
|
206
249
|
/**
|
|
@@ -208,7 +251,7 @@ interface PackedTarget {
|
|
|
208
251
|
* `render()` opens a frame with whatever is current and accumulates samples until it reaches `settings.samples`.
|
|
209
252
|
*/
|
|
210
253
|
export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
211
|
-
private current:
|
|
254
|
+
private current: GpuRenderSettings;
|
|
212
255
|
private environment: RenderEnvironment;
|
|
213
256
|
// Serializing walks the whole scene, so it is started by the setter and awaited by the frame that needs it.
|
|
214
257
|
private scene: Promise<SerializedScene> = Promise.resolve(EMPTY_SCENE);
|
|
@@ -248,6 +291,11 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
248
291
|
private planar = false;
|
|
249
292
|
private resolve: CaptureResolvePass | null = null;
|
|
250
293
|
private denoisePass: DenoisePass | null = null;
|
|
294
|
+
private lensPass: LensPass | null = null;
|
|
295
|
+
// What the lens pass last graded, and when: its input only changes when the denoiser runs or the accumulation grows.
|
|
296
|
+
private lensKey = "";
|
|
297
|
+
private lensAt = -Infinity;
|
|
298
|
+
private denoiseVersion = 0;
|
|
251
299
|
private denoiserSettings: DenoiserSettings = { ...DEFAULT_DENOISER };
|
|
252
300
|
// The size of the last frame that finished, which setDenoiser filters again; null while a frame traces.
|
|
253
301
|
private finished: { width: number; height: number } | null = null;
|
|
@@ -257,6 +305,8 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
257
305
|
private denoiseView: DenoiseView = { pixelScale: 1, orthographic: false };
|
|
258
306
|
// Reading an environment image is a fetch, so it is started once and awaited by the frame that needs it.
|
|
259
307
|
private environmentSource = "";
|
|
308
|
+
// The surroundings' radiance averaged over the sphere, which the air glows with; null for the gradient.
|
|
309
|
+
private environmentAverage: [number, number, number] | null = null;
|
|
260
310
|
private environmentImage: Promise<PackedEnvironment | null> = Promise.resolve(null);
|
|
261
311
|
private canvas: HTMLCanvasElement | null = null;
|
|
262
312
|
private context: GPUCanvasContext | null = null;
|
|
@@ -274,7 +324,7 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
274
324
|
this.width = Math.max(1, Math.floor(options.width));
|
|
275
325
|
this.height = Math.max(1, Math.floor(options.height));
|
|
276
326
|
this.pixelRatio = checkedPixelRatio(options.pixelRatio ?? 1);
|
|
277
|
-
this.current =
|
|
327
|
+
this.current = mergeGpuSettings(DEFAULT_GPU_RENDER_SETTINGS, options.settings);
|
|
278
328
|
this.denoiserSettings = mergeDenoiser(DEFAULT_DENOISER, options.denoiser);
|
|
279
329
|
this.environment = DEFAULT_RENDER_ENVIRONMENT;
|
|
280
330
|
this.scheduler = new DispatchScheduler({ maxRows: this.renderHeight, maxSamples: BATCH_PATHS });
|
|
@@ -285,7 +335,7 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
285
335
|
}
|
|
286
336
|
|
|
287
337
|
/** The settings the next frame renders with. */
|
|
288
|
-
get settings(): Readonly<
|
|
338
|
+
get settings(): Readonly<GpuRenderSettings> {
|
|
289
339
|
return this.current;
|
|
290
340
|
}
|
|
291
341
|
|
|
@@ -396,15 +446,16 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
396
446
|
}
|
|
397
447
|
|
|
398
448
|
/**
|
|
399
|
-
* Merges the given fields into the current settings, group by group. Turning the denoiser on or off
|
|
400
|
-
*
|
|
449
|
+
* Merges the given fields into the current settings, group by group. Turning the denoiser on or off, or changing
|
|
450
|
+
* what is done to the light after it is traced (exposure, tone mapping, vignette, bloom, chromatic aberration, white
|
|
451
|
+
* balance), shows the finished image again at once, as `setDenoiser` does, since none of that needs tracing.
|
|
401
452
|
*/
|
|
402
|
-
setSettings(settings:
|
|
403
|
-
const
|
|
453
|
+
setSettings(settings: GpuRenderSettingsInput) {
|
|
454
|
+
const before = developing(this.current);
|
|
404
455
|
|
|
405
|
-
this.current =
|
|
456
|
+
this.current = mergeGpuSettings(this.current, settings);
|
|
406
457
|
|
|
407
|
-
if (this.current
|
|
458
|
+
if (developing(this.current) !== before) this.refilter();
|
|
408
459
|
}
|
|
409
460
|
|
|
410
461
|
setEnvironment(environment: RenderEnvironment) {
|
|
@@ -507,6 +558,8 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
507
558
|
this.buffers?.destroy();
|
|
508
559
|
this.accumulator?.destroy();
|
|
509
560
|
this.denoisePass?.destroy();
|
|
561
|
+
this.lensPass?.destroy();
|
|
562
|
+
this.lensPass = null;
|
|
510
563
|
this.imageResolve?.dispose();
|
|
511
564
|
this.imageResolve = null;
|
|
512
565
|
this.gpu?.device.destroy();
|
|
@@ -685,7 +738,8 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
685
738
|
|
|
686
739
|
// The canvas shows the denoised image, refreshed a few times a second while the bands refine underneath it:
|
|
687
740
|
// filtering the whole image after every pass would cost a cheap scene as much as tracing it.
|
|
688
|
-
if (denoise && this.canvas && this.context && performance.now() - denoisedAt
|
|
741
|
+
if (denoise && this.canvas && this.context && performance.now() - denoisedAt
|
|
742
|
+
>= denoiseInterval(width, height, this.denoiserSettings.passes)) {
|
|
689
743
|
const encoder = gpu.device.createCommandEncoder();
|
|
690
744
|
|
|
691
745
|
this.encodeDenoise(gpu, encoder, width, height);
|
|
@@ -769,10 +823,20 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
769
823
|
height: number,
|
|
770
824
|
image: PackedEnvironment | null) {
|
|
771
825
|
this.pathTrace ??= new PathTracePass(gpu.device);
|
|
772
|
-
|
|
826
|
+
const materials = assets.scene.meshes.flatMap(mesh => mesh.materials);
|
|
827
|
+
|
|
828
|
+
this.pathTrace.setPrograms(assets.programs,
|
|
829
|
+
this.current.subsurface.enabled && materials.some(material =>
|
|
830
|
+
(material.type === "MeshStandardMaterial" || material.type === "MeshPhysicalMaterial")
|
|
831
|
+
&& subsurfaceOf(material) !== null),
|
|
832
|
+
// Even turned off, a volume's boundary has to stay invisible, which takes the same code.
|
|
833
|
+
materials.some(material => volumeOf(material) !== null),
|
|
834
|
+
this.current.bevel.enabled && materials.some(material => bevelOf(material) > 0));
|
|
773
835
|
|
|
774
836
|
// A procedural sky stands in for the renderer's own environment, as it does in the CPU renderer.
|
|
775
837
|
const environment = assets.sky?.environment ?? image;
|
|
838
|
+
|
|
839
|
+
this.environmentAverage = environment ? averageRadiance(environment) : null;
|
|
776
840
|
const matrices = cameraMatrices(camera, width, height);
|
|
777
841
|
const view = `${JSON.stringify(camera)}|${width}|${height}`;
|
|
778
842
|
const moved = (!!this.packed?.cameraDependent || this.planar) && view !== this.packedView;
|
|
@@ -902,6 +966,7 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
902
966
|
skyData,
|
|
903
967
|
volumetricData,
|
|
904
968
|
volumetricCount: Math.min(volumetric.length / VOLUMETRIC_WORDS, 8),
|
|
969
|
+
volumes: volumeTriangles(geometry),
|
|
905
970
|
};
|
|
906
971
|
}
|
|
907
972
|
|
|
@@ -940,6 +1005,10 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
940
1005
|
const uniforms = {
|
|
941
1006
|
...this.frameUniforms(capture.camera, width, height, assets, target, capture.background),
|
|
942
1007
|
lens: [0, 1, 0, 0],
|
|
1008
|
+
lensDistortion: 0,
|
|
1009
|
+
// A capture is a texture of the scene, seen again by the camera, which lays the air over it then.
|
|
1010
|
+
atmosphereColor: [0, 0, 0, 0],
|
|
1011
|
+
cameraVolume: NO_MATERIAL,
|
|
943
1012
|
volumetricCount: 0,
|
|
944
1013
|
clipPlane: capture.clip ?? [0, 0, 0, 0],
|
|
945
1014
|
bandOrigin: [0, 0],
|
|
@@ -993,27 +1062,56 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
993
1062
|
}
|
|
994
1063
|
|
|
995
1064
|
private encodePresent(encoder: GPUCommandEncoder, width: number, height: number) {
|
|
996
|
-
if (!this.context || !this.present) return;
|
|
1065
|
+
if (!this.context || !this.present || !this.gpu) return;
|
|
997
1066
|
|
|
998
1067
|
this.present.encode(encoder, this.context.getCurrentTexture().createView(), {
|
|
999
1068
|
width,
|
|
1000
1069
|
height,
|
|
1001
|
-
exposure: this.current
|
|
1070
|
+
exposure: effectiveExposure(this.current),
|
|
1002
1071
|
toneMapping: this.current.toneMapping,
|
|
1003
1072
|
vignette: this.current.vignette,
|
|
1004
1073
|
grain: this.current.grain,
|
|
1005
1074
|
frameIndex: this.frameIndex,
|
|
1006
1075
|
transparent: this.current.background.mode === "transparent",
|
|
1007
|
-
denoised: this.
|
|
1076
|
+
denoised: this.encodeLens(this.gpu, encoder, width, height,
|
|
1077
|
+
this.denoised ? this.denoisePass?.output ?? null : null),
|
|
1008
1078
|
});
|
|
1009
1079
|
}
|
|
1010
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* The lens and the sensor over the radiance (or the denoiser's output), when the settings ask for any of them: the
|
|
1083
|
+
* buffer of graded colour the display transform reads instead, or the input as it was.
|
|
1084
|
+
*/
|
|
1085
|
+
private encodeLens(gpu: RenderDevice, encoder: GPUCommandEncoder, width: number, height: number,
|
|
1086
|
+
denoised: GPUBuffer | null, force = false): GPUBuffer | null {
|
|
1087
|
+
const effects = lensEffects(this.current);
|
|
1088
|
+
|
|
1089
|
+
if (!effects) return denoised;
|
|
1090
|
+
|
|
1091
|
+
this.lensPass ??= new LensPass(gpu.device);
|
|
1092
|
+
|
|
1093
|
+
// The canvas is drawn about once a display frame, but a denoised image only changes a few times a second: grading
|
|
1094
|
+
// the same one again would take the device from tracing for nothing. Straight from the accumulation, the image
|
|
1095
|
+
// grows with every pass, and is graded again at most every LENS_EVERY_MS.
|
|
1096
|
+
const key = JSON.stringify([this.revision, width, height, denoised ? this.denoiseVersion : -1, effects]);
|
|
1097
|
+
const now = performance.now();
|
|
1098
|
+
const fresh = key === this.lensKey && (denoised !== null || now - this.lensAt < LENS_EVERY_MS);
|
|
1099
|
+
|
|
1100
|
+
if (!force && fresh && this.lensPass.output) return this.lensPass.output;
|
|
1101
|
+
|
|
1102
|
+
this.lensKey = key;
|
|
1103
|
+
this.lensAt = now;
|
|
1104
|
+
|
|
1105
|
+
return this.lensPass.encode(encoder, (this.accumulator as Accumulator).buffer, denoised, width, height, effects);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1011
1108
|
/** Denoises the accumulation as it stands, for the canvas and for the finished image. */
|
|
1012
1109
|
private encodeDenoise(gpu: RenderDevice, encoder: GPUCommandEncoder, width: number, height: number) {
|
|
1013
1110
|
this.denoisePass ??= new DenoisePass(gpu.device);
|
|
1014
1111
|
this.denoisePass.encode(encoder, (this.accumulator as Accumulator).buffer, width, height, this.denoiserSettings,
|
|
1015
1112
|
this.denoiseView);
|
|
1016
1113
|
this.denoised = true;
|
|
1114
|
+
this.denoiseVersion++;
|
|
1017
1115
|
}
|
|
1018
1116
|
|
|
1019
1117
|
private frameUniforms(
|
|
@@ -1041,6 +1139,16 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
1041
1139
|
: 0;
|
|
1042
1140
|
|
|
1043
1141
|
const backgroundMode = background.mode === "color" ? 1 : background.mode === "transparent" ? 2 : 0;
|
|
1142
|
+
const { atmosphere } = this.current;
|
|
1143
|
+
const strength = sky ? sky.intensity : this.environment.intensity ?? 1;
|
|
1144
|
+
|
|
1145
|
+
const airGlow = atmosphere.color
|
|
1146
|
+
? linearColor(atmosphere.color, [0, 0, 0])
|
|
1147
|
+
: (this.environmentAverage ?? gradientAverage(linearColor(gradient?.topColor, [1, 1, 1]),
|
|
1148
|
+
linearColor(gradient?.bottomColor, [0, 0, 0]), gradient?.exponent ?? 2)).map(channel => channel * strength);
|
|
1149
|
+
|
|
1150
|
+
const extinction = atmosphereExtinction(atmosphere);
|
|
1151
|
+
const sun = brightestSun(assets.scene.lights);
|
|
1044
1152
|
|
|
1045
1153
|
return {
|
|
1046
1154
|
cameraToWorld: matrices.cameraWorld,
|
|
@@ -1069,11 +1177,30 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
1069
1177
|
resolution: [width, height],
|
|
1070
1178
|
cameraKind: matrices.orthographic ? 1 : 0,
|
|
1071
1179
|
bounces: Math.max(1, Math.round(this.current.globalIllumination.bounces)),
|
|
1180
|
+
rouletteThreshold: this.current.globalIllumination.russianRoulette === false ? 0 : ROULETTE_THRESHOLD,
|
|
1181
|
+
rouletteStart: Math.max(0, Math.round(this.current.globalIllumination.rouletteStart ?? 0)),
|
|
1182
|
+
lensDistortion: Number.isFinite(this.current.lens.distortion) ? this.current.lens.distortion : 0,
|
|
1183
|
+
subsurfaceEnabled: this.current.subsurface.enabled,
|
|
1184
|
+
subsurfaceDepth: Math.max(0, Math.round(Number(this.current.subsurface.depth ?? 2))),
|
|
1185
|
+
bevelEnabled: this.current.bevel.enabled,
|
|
1186
|
+
volumesEnabled: this.current.volumes.enabled,
|
|
1187
|
+
cameraVolume: this.current.volumes.enabled
|
|
1188
|
+
? containingVolume(target?.volumes ?? null, matrices.cameraWorld.slice(12, 15))
|
|
1189
|
+
: NO_MATERIAL,
|
|
1190
|
+
atmosphereColor: [...airGlow, extinction.some(value => value > 0) ? 1 : 0],
|
|
1191
|
+
atmosphereExtinction: [...extinction, Math.min(1, Math.max(0, Number(atmosphere.haze) || 0))],
|
|
1192
|
+
atmosphereSun: [...sun?.direction ?? [0, 1, 0], 0],
|
|
1193
|
+
atmosphereSunColor: [...sun?.irradiance ?? [0, 0, 0], 0],
|
|
1072
1194
|
triangleCount: buffers?.triangleCount ?? 0,
|
|
1073
1195
|
lightCount: buffers?.lightCount ?? 0,
|
|
1074
1196
|
areaLightCount: buffers?.areaLightCount ?? 0,
|
|
1075
1197
|
lightCdf: buffers?.lightCdf ?? 0,
|
|
1076
1198
|
lightTriangles: buffers?.lightTriangles ?? 0,
|
|
1199
|
+
localLightCount: buffers?.localLightCount ?? 0,
|
|
1200
|
+
localLightPick: buffers?.localLightPick ?? 0,
|
|
1201
|
+
lightTree: this.current.globalIllumination.lightTree === false ? 0 : buffers?.lightTree ?? 0,
|
|
1202
|
+
lightLeaves: buffers?.lightLeaves ?? 0,
|
|
1203
|
+
lightList: buffers?.lightList ?? 0,
|
|
1077
1204
|
clampIndirect: Math.max(0, this.current.globalIllumination.clamp),
|
|
1078
1205
|
// The setting is the angular diameter a light covers, in degrees, as it is for the CPU renderer's shadow blur.
|
|
1079
1206
|
shadowSpread: shadows.enabled ? Math.tan(Math.max(0, shadows.softness) * Math.PI / 360) : 0,
|
|
@@ -1108,13 +1235,20 @@ export class GpuRenderer extends EventEmitter<GpuRendererEvents> {
|
|
|
1108
1235
|
private async readImage(
|
|
1109
1236
|
gpu: RenderDevice, width: number, height: number, denoised: GPUBuffer | null): Promise<RenderImage> {
|
|
1110
1237
|
const accumulator = this.accumulator as Accumulator;
|
|
1111
|
-
const {
|
|
1238
|
+
const { toneMapping, vignette } = this.current;
|
|
1239
|
+
const exposure = effectiveExposure(this.current);
|
|
1112
1240
|
const transparent = this.current.background.mode === "transparent";
|
|
1113
1241
|
|
|
1114
1242
|
// Resolved on the device, so the page only copies the bytes of the image.
|
|
1115
1243
|
this.imageResolve ??= new ImageResolvePass(gpu.device);
|
|
1116
1244
|
|
|
1117
|
-
|
|
1245
|
+
// The lens's effects are encoded on their own, ahead of the resolve, which reads the colour they leave.
|
|
1246
|
+
const encoder = gpu.device.createCommandEncoder({ label: "lens" });
|
|
1247
|
+
const graded = this.encodeLens(gpu, encoder, width, height, denoised, true);
|
|
1248
|
+
|
|
1249
|
+
gpu.device.queue.submit([encoder.finish()]);
|
|
1250
|
+
|
|
1251
|
+
const data = await this.imageResolve.read(accumulator.buffer, graded, width, height,
|
|
1118
1252
|
{ exposure, toneMapping, vignette, transparent });
|
|
1119
1253
|
|
|
1120
1254
|
return { width, height, data };
|
|
@@ -1135,6 +1269,9 @@ const SERIALIZABLE = new Set([
|
|
|
1135
1269
|
*/
|
|
1136
1270
|
async function serializeLive(scene: Scene): Promise<SerializedScene> {
|
|
1137
1271
|
const renamed = new Map<Material, string>();
|
|
1272
|
+
// The snapshot keeps a material's name but not its media, so the name carries a mark to them.
|
|
1273
|
+
const named = new Map<Material, string>();
|
|
1274
|
+
const media: { subsurface?: unknown; volume?: unknown; bevel?: unknown }[] = [];
|
|
1138
1275
|
|
|
1139
1276
|
scene.traverse(object => {
|
|
1140
1277
|
const value = (object as Object3D & { material?: Material | Material[] }).material;
|
|
@@ -1146,15 +1283,60 @@ async function serializeLive(scene: Scene): Promise<SerializedScene> {
|
|
|
1146
1283
|
renamed.set(material, material.type);
|
|
1147
1284
|
material.type = shader.isRawShaderMaterial ? "RawShaderMaterial" : "ShaderMaterial";
|
|
1148
1285
|
}
|
|
1286
|
+
|
|
1287
|
+
const data = material.userData?.cpuRenderer as
|
|
1288
|
+
{ subsurface?: unknown; volume?: unknown; bevel?: unknown } | undefined;
|
|
1289
|
+
|
|
1290
|
+
if (!named.has(material)
|
|
1291
|
+
&& (subsurfaceMedium(data?.subsurface) || volumeMedium(data?.volume) || bevelRadius(data?.bevel) > 0)) {
|
|
1292
|
+
named.set(material, material.name);
|
|
1293
|
+
material.name = `${material.name}${MEDIA_MARK}${media.length}`;
|
|
1294
|
+
media.push({ subsurface: data?.subsurface, volume: data?.volume, bevel: data?.bevel });
|
|
1295
|
+
}
|
|
1149
1296
|
}
|
|
1150
1297
|
});
|
|
1151
1298
|
|
|
1299
|
+
let snapshot: SerializedScene;
|
|
1300
|
+
|
|
1152
1301
|
try {
|
|
1153
1302
|
// The path tracer samples only the base level of a texture: the mip chain would be built and thrown away.
|
|
1154
|
-
|
|
1303
|
+
snapshot = (await serializeScene(scene, { mipmaps: false })).scene;
|
|
1155
1304
|
} finally {
|
|
1156
1305
|
for (const [material, type] of renamed) material.type = type;
|
|
1306
|
+
for (const [material, name] of named) material.name = name;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Materials are shared between the meshes that use them, so each one is unmarked once and seen marked no more.
|
|
1310
|
+
for (const mesh of snapshot.meshes) {
|
|
1311
|
+
for (const material of mesh.materials as (SubsurfaceMaterial & VolumeMaterial & BevelMaterial)[]) {
|
|
1312
|
+
const at = material.name.lastIndexOf(MEDIA_MARK);
|
|
1313
|
+
|
|
1314
|
+
if (at < 0) continue;
|
|
1315
|
+
|
|
1316
|
+
const medium = media[Number(material.name.slice(at + MEDIA_MARK.length))];
|
|
1317
|
+
|
|
1318
|
+
material.name = material.name.slice(0, at) || material.type;
|
|
1319
|
+
if (medium.subsurface !== undefined) material.subsurface = medium.subsurface as SubsurfaceMaterial["subsurface"];
|
|
1320
|
+
if (medium.volume !== undefined) material.volume = medium.volume as VolumeMaterial["volume"];
|
|
1321
|
+
if (medium.bevel !== undefined) material.bevel = medium.bevel as BevelMaterial["bevel"];
|
|
1322
|
+
}
|
|
1157
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
return snapshot;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/** Ends a material's name while it is serialized, followed by the index of its media. */
|
|
1329
|
+
const MEDIA_MARK = "\u0000media:";
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Everything that changes the finished image without tracing it again, as one string: the denoiser switch and what
|
|
1333
|
+
* happens to the light between the radiance and the screen.
|
|
1334
|
+
*/
|
|
1335
|
+
function developing(settings: GpuRenderSettings): string {
|
|
1336
|
+
const { exposure, toneMapping, vignette, bloom, glare, lens, camera, depthOfField, globalIllumination } = settings;
|
|
1337
|
+
|
|
1338
|
+
return JSON.stringify([globalIllumination.denoise, exposure, toneMapping, vignette, bloom, glare,
|
|
1339
|
+
lens.chromaticAberration, camera, camera.physicalExposure ? depthOfField.aperture : 0]);
|
|
1158
1340
|
}
|
|
1159
1341
|
|
|
1160
1342
|
/**
|
package/src/sceneUpload.ts
CHANGED
|
@@ -47,6 +47,12 @@ export interface SceneBuffers {
|
|
|
47
47
|
/** Where the lights' cumulative probabilities and the emissive ones' triangles sit in `textures`. */
|
|
48
48
|
lightCdf: number;
|
|
49
49
|
lightTriangles: number;
|
|
50
|
+
/** The light tree over the lights that sit in the scene: see lightTree.ts. */
|
|
51
|
+
localLightCount: number;
|
|
52
|
+
localLightPick: number;
|
|
53
|
+
lightTree: number;
|
|
54
|
+
lightLeaves: number;
|
|
55
|
+
lightList: number;
|
|
50
56
|
/** Radiance an ambient or hemisphere light adds, which no shadow ray can reach. */
|
|
51
57
|
ambient: PackedLights["ambient"];
|
|
52
58
|
/** Where the surrounding image and its distributions ended up, for the frame uniform. */
|
|
@@ -197,6 +203,11 @@ export function uploadScene(
|
|
|
197
203
|
areaLightCount: lights.areaCount,
|
|
198
204
|
lightCdf: lights.cdfAt,
|
|
199
205
|
lightTriangles: lights.trianglesAt,
|
|
206
|
+
localLightCount: lights.localCount,
|
|
207
|
+
localLightPick: lights.localPick,
|
|
208
|
+
lightTree: lights.treeAt,
|
|
209
|
+
lightLeaves: lights.leavesAt,
|
|
210
|
+
lightList: lights.listAt,
|
|
200
211
|
ambient: lights.ambient,
|
|
201
212
|
environment: {
|
|
202
213
|
texture: environment ? textures.indexOf.get(environment.texture.id) ?? NO_TEXTURE : NO_TEXTURE,
|