@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.
Files changed (140) hide show
  1. package/ARCHITECTURE.md +81 -10
  2. package/README.md +53 -5
  3. package/dist/atmosphere.d.ts +21 -0
  4. package/dist/atmosphere.d.ts.map +1 -0
  5. package/dist/atmosphere.js +72 -0
  6. package/dist/atmosphere.js.map +1 -0
  7. package/dist/bvh.d.ts +11 -6
  8. package/dist/bvh.d.ts.map +1 -1
  9. package/dist/bvh.js +64 -11
  10. package/dist/bvh.js.map +1 -1
  11. package/dist/denoise.d.ts +17 -2
  12. package/dist/denoise.d.ts.map +1 -1
  13. package/dist/denoise.js +95 -27
  14. package/dist/denoise.js.map +1 -1
  15. package/dist/dispatchScheduler.d.ts +11 -4
  16. package/dist/dispatchScheduler.d.ts.map +1 -1
  17. package/dist/dispatchScheduler.js +14 -7
  18. package/dist/dispatchScheduler.js.map +1 -1
  19. package/dist/frameLayout.d.ts.map +1 -1
  20. package/dist/frameLayout.js +18 -0
  21. package/dist/frameLayout.js.map +1 -1
  22. package/dist/geometry.d.ts.map +1 -1
  23. package/dist/geometry.js +6 -3
  24. package/dist/geometry.js.map +1 -1
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/lensPass.d.ts +60 -0
  30. package/dist/lensPass.d.ts.map +1 -0
  31. package/dist/lensPass.js +235 -0
  32. package/dist/lensPass.js.map +1 -0
  33. package/dist/lightTree.d.ts +43 -0
  34. package/dist/lightTree.d.ts.map +1 -0
  35. package/dist/lightTree.js +107 -0
  36. package/dist/lightTree.js.map +1 -0
  37. package/dist/lights.d.ts +19 -1
  38. package/dist/lights.d.ts.map +1 -1
  39. package/dist/lights.js +63 -1
  40. package/dist/lights.js.map +1 -1
  41. package/dist/materialLayout.d.ts +24 -0
  42. package/dist/materialLayout.d.ts.map +1 -1
  43. package/dist/materialLayout.js +36 -1
  44. package/dist/materialLayout.js.map +1 -1
  45. package/dist/pathTracePass.d.ts +6 -3
  46. package/dist/pathTracePass.d.ts.map +1 -1
  47. package/dist/pathTracePass.js +16 -10
  48. package/dist/pathTracePass.js.map +1 -1
  49. package/dist/renderer.d.ts +19 -7
  50. package/dist/renderer.d.ts.map +1 -1
  51. package/dist/renderer.js +198 -36
  52. package/dist/renderer.js.map +1 -1
  53. package/dist/sceneUpload.d.ts +19 -1
  54. package/dist/sceneUpload.d.ts.map +1 -1
  55. package/dist/sceneUpload.js +25 -3
  56. package/dist/sceneUpload.js.map +1 -1
  57. package/dist/settings.d.ts +136 -0
  58. package/dist/settings.d.ts.map +1 -0
  59. package/dist/settings.js +100 -0
  60. package/dist/settings.js.map +1 -0
  61. package/dist/subsurface.d.ts +76 -0
  62. package/dist/subsurface.d.ts.map +1 -0
  63. package/dist/subsurface.js +70 -0
  64. package/dist/subsurface.js.map +1 -0
  65. package/dist/texturePack.d.ts +16 -4
  66. package/dist/texturePack.d.ts.map +1 -1
  67. package/dist/texturePack.js +61 -8
  68. package/dist/texturePack.js.map +1 -1
  69. package/dist/volumeContainment.d.ts +17 -0
  70. package/dist/volumeContainment.d.ts.map +1 -0
  71. package/dist/volumeContainment.js +61 -0
  72. package/dist/volumeContainment.js.map +1 -0
  73. package/dist/wgsl/bsdf.wgsl.d.ts +1 -1
  74. package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -1
  75. package/dist/wgsl/bsdf.wgsl.js +35 -3
  76. package/dist/wgsl/bsdf.wgsl.js.map +1 -1
  77. package/dist/wgsl/bvh.wgsl.d.ts +8 -2
  78. package/dist/wgsl/bvh.wgsl.d.ts.map +1 -1
  79. package/dist/wgsl/bvh.wgsl.js +167 -96
  80. package/dist/wgsl/bvh.wgsl.js.map +1 -1
  81. package/dist/wgsl/common.wgsl.d.ts.map +1 -1
  82. package/dist/wgsl/common.wgsl.js +24 -7
  83. package/dist/wgsl/common.wgsl.js.map +1 -1
  84. package/dist/wgsl/lens.wgsl.d.ts +17 -0
  85. package/dist/wgsl/lens.wgsl.d.ts.map +1 -0
  86. package/dist/wgsl/lens.wgsl.js +315 -0
  87. package/dist/wgsl/lens.wgsl.js.map +1 -0
  88. package/dist/wgsl/lights.wgsl.d.ts +1 -1
  89. package/dist/wgsl/lights.wgsl.d.ts.map +1 -1
  90. package/dist/wgsl/lights.wgsl.js +206 -33
  91. package/dist/wgsl/lights.wgsl.js.map +1 -1
  92. package/dist/wgsl/pathTrace.wgsl.d.ts +1 -1
  93. package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -1
  94. package/dist/wgsl/pathTrace.wgsl.js +410 -25
  95. package/dist/wgsl/pathTrace.wgsl.js.map +1 -1
  96. package/dist/wgsl/random.wgsl.d.ts +1 -1
  97. package/dist/wgsl/random.wgsl.d.ts.map +1 -1
  98. package/dist/wgsl/random.wgsl.js +9 -17
  99. package/dist/wgsl/random.wgsl.js.map +1 -1
  100. package/dist/wgsl/sampling.wgsl.d.ts +1 -1
  101. package/dist/wgsl/sampling.wgsl.d.ts.map +1 -1
  102. package/dist/wgsl/sampling.wgsl.js +25 -0
  103. package/dist/wgsl/sampling.wgsl.js.map +1 -1
  104. package/dist/wgsl/surface.wgsl.d.ts +1 -1
  105. package/dist/wgsl/surface.wgsl.d.ts.map +1 -1
  106. package/dist/wgsl/surface.wgsl.js +47 -4
  107. package/dist/wgsl/surface.wgsl.js.map +1 -1
  108. package/dist/wgsl/texture.wgsl.d.ts +1 -1
  109. package/dist/wgsl/texture.wgsl.d.ts.map +1 -1
  110. package/dist/wgsl/texture.wgsl.js +10 -11
  111. package/dist/wgsl/texture.wgsl.js.map +1 -1
  112. package/package.json +1 -1
  113. package/src/atmosphere.ts +95 -0
  114. package/src/bvh.ts +81 -12
  115. package/src/denoise.ts +110 -27
  116. package/src/dispatchScheduler.ts +16 -7
  117. package/src/frameLayout.ts +18 -0
  118. package/src/geometry.ts +6 -2
  119. package/src/index.ts +13 -0
  120. package/src/lensPass.ts +299 -0
  121. package/src/lightTree.ts +172 -0
  122. package/src/lights.ts +90 -2
  123. package/src/materialLayout.ts +52 -1
  124. package/src/pathTracePass.ts +19 -10
  125. package/src/renderer.ts +258 -43
  126. package/src/sceneUpload.ts +41 -4
  127. package/src/settings.ts +234 -0
  128. package/src/subsurface.ts +153 -0
  129. package/src/texturePack.ts +80 -13
  130. package/src/volumeContainment.ts +81 -0
  131. package/src/wgsl/bsdf.wgsl.ts +35 -3
  132. package/src/wgsl/bvh.wgsl.ts +167 -96
  133. package/src/wgsl/common.wgsl.ts +24 -7
  134. package/src/wgsl/lens.wgsl.ts +314 -0
  135. package/src/wgsl/lights.wgsl.ts +206 -33
  136. package/src/wgsl/pathTrace.wgsl.ts +410 -25
  137. package/src/wgsl/random.wgsl.ts +9 -17
  138. package/src/wgsl/sampling.wgsl.ts +25 -0
  139. package/src/wgsl/surface.wgsl.ts +47 -4
  140. package/src/wgsl/texture.wgsl.ts +10 -11
@@ -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
- return COMMON_WGSL + RANDOM_WGSL + BVH_WGSL + SAMPLING_WGSL + TEXTURE_WGSL + SHADER_SUPPORT_WGSL
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
- /** Recompiles the kernel when the scene's shaders changed; the same set of programs keeps the pipeline it has. */
40
- setPrograms(programs) {
41
- const key = programs?.key ?? "";
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":"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"}
1
+ {"version":3,"file":"pathTracePass.js","sourceRoot":"","sources":["../src/pathTracePass.ts"],"names":[],"mappings":"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"}
@@ -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 RenderSettings, type RenderSettingsInput, type SerializedScene } from "./upstream.js";
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
- /** Merged over `DEFAULT_RENDER_SETTINGS`, group by group. */
46
- settings?: RenderSettingsInput;
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<RenderSettings>;
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 also filters the
157
- * finished image again at once, as `setDenoiser` does, since that needs no tracing.
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: RenderSettingsInput): void;
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;
@@ -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;AAY5F,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AAKtB,OAAO,EAGL,YAAY,EAKZ,KAAK,MAAM,EAEX,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEvG;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,+FAA+F;IAC/F,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,wFAAwF;IACxF,MAAM,EAAE,YAAY,CAAC;IACrB,qHAAqH;IACrH,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAC;IACnE,iGAAiG;IACjG,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACjF,0FAA0F;IAC1F,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,2BAA2B;IAC3B,QAAQ,EAAE,IAAI,CAAC;IACf,gEAAgE;IAChE,KAAK,EAAE,KAAK,CAAC;CACd;AAED,gGAAgG;AAChG,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,gDAAgD;IAChD,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,sGAAsG;IACtG,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wHAAwH;IACxH,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAWD,gHAAgH;AAChH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,OAAgE,CAAC;AAsE/G;;;GAGG;AACH,qBAAa,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IAC9D,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,WAAW,CAAoB;IAEvC,OAAO,CAAC,KAAK,CAA0D;IAEvE,OAAO,CAAC,QAAQ,CAAgC;IAEhD,OAAO,CAAC,OAAO,CAAgC;IAE/C,OAAO,CAAC,KAAK,CAAmC;IAEhD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAoC;IAEpD,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,UAAU,CAAQ;IAE1B,OAAO,CAAC,MAAM,CAAqC;IAEnD,OAAO,CAAC,UAAU,CAAM;IAExB,OAAO,CAAC,MAAM,CAA6B;IAE3C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,gBAAgB,CAA6C;IAErE,OAAO,CAAC,QAAQ,CAAkD;IAElE,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,WAAW,CAAuD;IAE1E,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAA4D;IACpF,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,GAAG,CAAa;gBAEZ,OAAO,EAAE,kBAAkB;IAkBvC,gDAAgD;IAChD,IAAI,QAAQ,IAAI,QAAQ,CAAC,cAAc,CAAC,CAEvC;IAED,gCAAgC;IAChC,IAAI,QAAQ,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAEzC;IAED;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAK5C,kHAAkH;IAClH,OAAO,CAAC,QAAQ;IA4BhB,uEAAuE;IACvE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;OAGG;IACH,IAAI,KAAK,IAAI,WAAW,GAAG,IAAI,CAE9B;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,6FAA6F;IAC7F,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,SAAkB;IAMnE,aAAa,CAAC,UAAU,EAAE,MAAM;IAIhC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe;IAa7C;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,YAAY;IAI9B;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,mBAAmB;IAQzC,cAAc,CAAC,WAAW,EAAE,iBAAiB;IAc7C,qDAAqD;IACrD,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAKhD;;;;;OAKG;IACH,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC;IAgC9B,yEAAyE;IACzE,KAAK;IAOL,MAAM;IAQN;;;;OAIG;IACH,MAAM;IASN,2EAA2E;IAC3E,OAAO;IAoBP,uFAAuF;IACvF,OAAO,CAAC,MAAM;IAQd,iEAAiE;IACjE,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,QAAQ;YAIF,MAAM;YAWN,UAAU;IAyIxB,6FAA6F;YAC/E,UAAU;IAwBxB,OAAO,CAAC,KAAK;IAIb,mFAAmF;YACrE,OAAO;IA8DrB,iHAAiH;YACnG,SAAS;IA+EvB;;;;OAIG;YACW,cAAc;IA8D5B,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,aAAa;IAgBrB,yFAAyF;IACzF,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAkFrB;;;OAGG;YACW,SAAS;CAcxB"}
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 { DEFAULT_RENDER_ENVIRONMENT, DEFAULT_RENDER_SETTINGS, EventEmitter, mergeSettings, serializeCamera, serializeScene, srgbToLinear, } from "./upstream.js";
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 = mergeSettings(DEFAULT_RENDER_SETTINGS, options.settings);
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 also filters the
236
- * finished image again at once, as `setDenoiser` does, since that needs no tracing.
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 denoise = this.current.globalIllumination.denoise;
240
- this.current = mergeSettings(this.current, settings);
241
- if (this.current.globalIllumination.denoise !== denoise)
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` passes over the whole image to spend. Pixels that have
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 maxPasses = adaptive ? target * ADAPTIVE_REACH : target;
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
- let spent = 0, band = 0, pass = 0;
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 (pass < maxPasses && spent < budget && active.length) {
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 && pass >= ADAPTIVE_MIN;
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
- ...this.frameUniforms(camera, width, height, assets),
473
+ ...uniforms,
417
474
  bandOrigin: [0, row],
418
475
  bandSize: [width, rows],
419
- sampleIndex: pass,
420
- sampleCount: 1,
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
- await gpu.device.queue.onSubmittedWorkDone();
430
- this.scheduler.record(performance.now() - started, rows);
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
- pass++;
498
+ taken += samples;
441
499
  this.frameIndex++;
442
- // The canvas shows the denoised image from the end of each pass on, and the bands of the next pass refine
443
- // underneath it until it is denoised again.
444
- if (denoise && this.canvas && this.context) {
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 (flagging)
452
- active = await this.activeRows(gpu, accumulator);
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
- this.pathTrace.setPrograms(assets.programs);
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].map(([id, index]) => [id, textures.data[index * 16 + 2]]));
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.exposure,
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 { exposure, toneMapping, vignette } = this.current;
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
- const data = await this.imageResolve.read(accumulator.buffer, denoised, width, height, { exposure, toneMapping, vignette, transparent });
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
- return (await serializeScene(scene, { mipmaps: false })).scene;
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