three-realtime-rt 0.3.2 → 0.4.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.
@@ -40,8 +40,11 @@ export class CompiledScene {
40
40
  this.dynamicMerged = null;
41
41
  this.dynamicPacked = null; // Float32Array + BufferAttribute for re-baking
42
42
  this.dynamicPackedAttr = null;
43
- this.dynamic = []; // [{ mesh, start, count, localPos, localNorm }]
43
+ this.dynamic = []; // [{ mesh, start, count, localPos, localNorm, deforming, ... }]
44
44
  this.hasDynamic = false;
45
+ // True when any dynamic segment is CPU-deformed (rtDeforming) — such segments
46
+ // read their live geometry every frame and force a per-frame normal upload.
47
+ this.hasDeforming = false;
45
48
 
46
49
  this.materialsTex = null;
47
50
  this.materials = [];
@@ -75,32 +78,82 @@ export class CompiledScene {
75
78
  seg.mesh.updateWorldMatrix(true, false);
76
79
  const m = seg.mesh.matrixWorld.elements;
77
80
  const nm = this._m3.getNormalMatrix(seg.mesh.matrixWorld).elements;
78
- const lp = seg.localPos;
79
- const ln = seg.localNorm;
80
81
  let o = seg.start * 3;
81
82
  let p = seg.start * 4;
82
- for (let i = 0; i < seg.count; i++) {
83
- const x = lp[i * 3], y = lp[i * 3 + 1], z = lp[i * 3 + 2];
84
- const wx = m[0] * x + m[4] * y + m[8] * z + m[12];
85
- const wy = m[1] * x + m[5] * y + m[9] * z + m[13];
86
- const wz = m[2] * x + m[6] * y + m[10] * z + m[14];
87
- pos[o] = wx;
88
- pos[o + 1] = wy;
89
- pos[o + 2] = wz;
90
- if (wx < minX) minX = wx; if (wx > maxX) maxX = wx;
91
- if (wy < minY) minY = wy; if (wy > maxY) maxY = wy;
92
- if (wz < minZ) minZ = wz; if (wz > maxZ) maxZ = wz;
93
- const nx = ln[i * 3], ny = ln[i * 3 + 1], nz = ln[i * 3 + 2];
94
- const tx = nm[0] * nx + nm[3] * ny + nm[6] * nz;
95
- const ty = nm[1] * nx + nm[4] * ny + nm[7] * nz;
96
- const tz = nm[2] * nx + nm[5] * ny + nm[8] * nz;
97
- const il = 1.0 / (Math.hypot(tx, ty, tz) || 1);
98
- packed[p] = tx * il;
99
- packed[p + 1] = ty * il;
100
- packed[p + 2] = tz * il;
101
- // packed[p + 3] (matIndex) never changes
102
- o += 3;
103
- p += 4;
83
+
84
+ if (seg.deforming) {
85
+ // CPU-deformed mesh (water/cloth): read the LIVE geometry every frame
86
+ // and expand it back to the merged de-indexed layout through the mapping
87
+ // snapshotted at compile time. `indexMap` (the source geometry's index
88
+ // buffer, or null for an already-non-indexed source) maps each merged
89
+ // triangle-soup vertex slot to its source-vertex index; the source
90
+ // attributes carry the shared, deformed values.
91
+ const livePosAttr = seg.liveGeometry.getAttribute("position");
92
+ if (livePosAttr.count !== seg.srcVertexCount) {
93
+ throw new Error(
94
+ "three-realtime-rt: deforming mesh vertex count changed since " +
95
+ `compile (${seg.srcVertexCount} -> ${livePosAttr.count}); the merged ` +
96
+ "BVH layout is fixed at compile time call compileScene() again."
97
+ );
98
+ }
99
+ const sp = livePosAttr.array;
100
+ const snAttr = seg.liveGeometry.getAttribute("normal");
101
+ const sn = snAttr ? snAttr.array : null;
102
+ const map = seg.indexMap; // null = identity (non-indexed source)
103
+ const ln = seg.localNorm; // fallback if the app never recomputed normals
104
+ for (let i = 0; i < seg.count; i++) {
105
+ const sv = map ? map[i] : i;
106
+ const x = sp[sv * 3], y = sp[sv * 3 + 1], z = sp[sv * 3 + 2];
107
+ const wx = m[0] * x + m[4] * y + m[8] * z + m[12];
108
+ const wy = m[1] * x + m[5] * y + m[9] * z + m[13];
109
+ const wz = m[2] * x + m[6] * y + m[10] * z + m[14];
110
+ pos[o] = wx;
111
+ pos[o + 1] = wy;
112
+ pos[o + 2] = wz;
113
+ if (wx < minX) minX = wx; if (wx > maxX) maxX = wx;
114
+ if (wy < minY) minY = wy; if (wy > maxY) maxY = wy;
115
+ if (wz < minZ) minZ = wz; if (wz > maxZ) maxZ = wz;
116
+ let nx, ny, nz;
117
+ if (sn) { nx = sn[sv * 3]; ny = sn[sv * 3 + 1]; nz = sn[sv * 3 + 2]; }
118
+ else { nx = ln[i * 3]; ny = ln[i * 3 + 1]; nz = ln[i * 3 + 2]; }
119
+ const tx = nm[0] * nx + nm[3] * ny + nm[6] * nz;
120
+ const ty = nm[1] * nx + nm[4] * ny + nm[7] * nz;
121
+ const tz = nm[2] * nx + nm[5] * ny + nm[8] * nz;
122
+ const il = 1.0 / (Math.hypot(tx, ty, tz) || 1);
123
+ packed[p] = tx * il;
124
+ packed[p + 1] = ty * il;
125
+ packed[p + 2] = tz * il;
126
+ // packed[p + 3] (matIndex) never changes
127
+ o += 3;
128
+ p += 4;
129
+ }
130
+ } else {
131
+ // Rigid mover: transform the frozen local snapshot by the world matrix.
132
+ const lp = seg.localPos;
133
+ const ln = seg.localNorm;
134
+ for (let i = 0; i < seg.count; i++) {
135
+ const x = lp[i * 3], y = lp[i * 3 + 1], z = lp[i * 3 + 2];
136
+ const wx = m[0] * x + m[4] * y + m[8] * z + m[12];
137
+ const wy = m[1] * x + m[5] * y + m[9] * z + m[13];
138
+ const wz = m[2] * x + m[6] * y + m[10] * z + m[14];
139
+ pos[o] = wx;
140
+ pos[o + 1] = wy;
141
+ pos[o + 2] = wz;
142
+ if (wx < minX) minX = wx; if (wx > maxX) maxX = wx;
143
+ if (wy < minY) minY = wy; if (wy > maxY) maxY = wy;
144
+ if (wz < minZ) minZ = wz; if (wz > maxZ) maxZ = wz;
145
+ const nx = ln[i * 3], ny = ln[i * 3 + 1], nz = ln[i * 3 + 2];
146
+ const tx = nm[0] * nx + nm[3] * ny + nm[6] * nz;
147
+ const ty = nm[1] * nx + nm[4] * ny + nm[7] * nz;
148
+ const tz = nm[2] * nx + nm[5] * ny + nm[8] * nz;
149
+ const il = 1.0 / (Math.hypot(tx, ty, tz) || 1);
150
+ packed[p] = tx * il;
151
+ packed[p + 1] = ty * il;
152
+ packed[p + 2] = tz * il;
153
+ // packed[p + 3] (matIndex) never changes
154
+ o += 3;
155
+ p += 4;
156
+ }
104
157
  }
105
158
  }
106
159
  posAttr.needsUpdate = true;
@@ -124,8 +177,11 @@ export class CompiledScene {
124
177
  this.dynamicBvh.refit();
125
178
  }
126
179
  this.dynamicBvhUniform.updateFrom(this.dynamicBvh);
127
- // Normals only feed GI-bounce shading off movers — amortize their upload.
128
- if (this._normalFrame++ % 8 === 0) {
180
+ // Normals only feed GI-bounce shading off movers — amortize their upload for
181
+ // rigid movers. Deforming meshes change shape (not just orientation) every
182
+ // frame, so their normals must go up every frame or the shading lags the
183
+ // silhouette; one deforming segment forces the whole (shared) upload.
184
+ if (this.hasDeforming || this._normalFrame++ % 8 === 0) {
129
185
  this.dynamicAttrTex.updateFrom(this.dynamicPackedAttr);
130
186
  }
131
187
  }
@@ -142,9 +198,8 @@ export class CompiledScene {
142
198
  }
143
199
 
144
200
  function extractMeshGeometry(mesh, materialIndex) {
145
- const src = mesh.geometry.index
146
- ? mesh.geometry.toNonIndexed()
147
- : mesh.geometry.clone();
201
+ const indexed = mesh.geometry.index;
202
+ const src = indexed ? mesh.geometry.toNonIndexed() : mesh.geometry.clone();
148
203
 
149
204
  if (!src.getAttribute("normal")) src.computeVertexNormals();
150
205
  const localPos = src.getAttribute("position").array.slice();
@@ -158,7 +213,17 @@ function extractMeshGeometry(mesh, materialIndex) {
158
213
  const count = geo.getAttribute("position").count;
159
214
  const mi = new Float32Array(count).fill(materialIndex);
160
215
  geo.setAttribute("materialIndex", new THREE.BufferAttribute(mi, 1));
161
- return { geo, localPos, localNorm, count };
216
+
217
+ // For CPU-deformed (rtDeforming) meshes we re-read the LIVE geometry each
218
+ // frame. The merged BVH is de-indexed triangle soup, so record how to expand
219
+ // the live (source) vertices back into that layout: `indexMap[i]` is the
220
+ // source-vertex index feeding merged slot `i` (a snapshot of the index
221
+ // buffer), or null when the source was already non-indexed (identity map).
222
+ // `srcVertexCount` is the live position count at compile time — used to catch
223
+ // a topology change that would invalidate this mapping.
224
+ const indexMap = indexed ? mesh.geometry.index.array.slice() : null;
225
+ const srcVertexCount = mesh.geometry.getAttribute("position").count;
226
+ return { geo, localPos, localNorm, count, indexMap, srcVertexCount };
162
227
  }
163
228
 
164
229
  // Effective emissive colour (already scaled by intensity), or null if the
@@ -174,12 +239,18 @@ function emissiveColor(mat) {
174
239
  // Row 1: emissive triangles for NEE, 4 texels each:
175
240
  // [v0.xyz | area] [e1.xyz | emit.r] [e2.xyz | emit.g] [n.xyz | emit.b]
176
241
  // Rows 2..65: a 64x64 RGBA blue-noise tile for low-discrepancy sampling.
242
+ // Row 66 (2 + BLUE_NOISE_SIZE): the emissive power CDF, 1 texel per triangle:
243
+ // [cdf | prob | 0 | 0] — cumulative and individual pick probability, both
244
+ // proportional to area x emitted luminance. Lets NEE importance-sample WHICH
245
+ // triangle to shoot at instead of picking uniformly (a big/bright panel gets
246
+ // sampled proportionally more than a tiny dim strip), which is the main
247
+ // variance lever for emissive lighting outside of ReSTIR.
177
248
  // All packed into ONE texture because the lighting pass already sits at the
178
249
  // WebGL2-guaranteed 16-sampler limit — extra samplers are not available.
179
250
  function buildSceneDataTexture(materials, emissiveTris) {
180
251
  const bn = decodeBlueNoise();
181
252
  const width = Math.max(materials.length * 2, emissiveTris.length * 4, BLUE_NOISE_SIZE);
182
- const height = 2 + BLUE_NOISE_SIZE;
253
+ const height = 2 + BLUE_NOISE_SIZE + 1;
183
254
  const data = new Float32Array(width * height * 4);
184
255
  materials.forEach((mat, i) => {
185
256
  const o = i * 8;
@@ -209,6 +280,22 @@ function buildSceneDataTexture(materials, emissiveTris) {
209
280
  data[o + i] = (bn[src + i] + 0.5) / 256.0;
210
281
  }
211
282
  }
283
+ // Emissive power CDF (see the layout comment above). Weight = area x
284
+ // emitted luminance; degenerate totals fall back to the uniform pick.
285
+ if (emissiveTris.length > 0) {
286
+ const w = emissiveTris.map(
287
+ (t) => t.area * (0.2126 * t.emit[0] + 0.7152 * t.emit[1] + 0.0722 * t.emit[2])
288
+ );
289
+ const total = w.reduce((a, b) => a + b, 0);
290
+ const cdfRow = (2 + BLUE_NOISE_SIZE) * row;
291
+ let cum = 0;
292
+ for (let i = 0; i < emissiveTris.length; i++) {
293
+ const p = total > 0 ? w[i] / total : 1 / emissiveTris.length;
294
+ cum += p;
295
+ data[cdfRow + i * 4 + 0] = i === emissiveTris.length - 1 ? 1.0 : cum;
296
+ data[cdfRow + i * 4 + 1] = p;
297
+ }
298
+ }
212
299
  const tex = new THREE.DataTexture(data, width, height, THREE.RGBAFormat, THREE.FloatType);
213
300
  tex.minFilter = THREE.NearestFilter;
214
301
  tex.magFilter = THREE.NearestFilter;
@@ -310,12 +397,20 @@ export function compileScene(scene, options = {}) {
310
397
  }
311
398
  if (isDynamic) {
312
399
  dynamicGeoms.push(extracted.geo);
400
+ // Opt-in CPU deformation: the mesh must be BOTH in dynamicMeshes AND carry
401
+ // userData.rtDeforming, and its live geometry is read every frame.
402
+ const deforming = obj.userData.rtDeforming === true;
403
+ if (deforming) compiled.hasDeforming = true;
313
404
  compiled.dynamic.push({
314
405
  mesh: obj,
315
406
  start: dynVertexOffset,
316
407
  count: extracted.count,
317
408
  localPos: extracted.localPos,
318
409
  localNorm: extracted.localNorm,
410
+ deforming,
411
+ liveGeometry: deforming ? obj.geometry : null,
412
+ indexMap: deforming ? extracted.indexMap : null,
413
+ srcVertexCount: deforming ? extracted.srcVertexCount : 0,
319
414
  });
320
415
  dynVertexOffset += extracted.count;
321
416
  } else {
package/src/TAAPass.js CHANGED
@@ -101,12 +101,17 @@ void main() {
101
101
  }
102
102
  `;
103
103
 
104
+ // Copies the resolved frame out. When overscan is active the resolve target is
105
+ // PADDED (larger than the canvas); uCrop (scale.xy, offset.zw) samples its central
106
+ // canvas-sized region so the padded edges — where disocclusion noise is born —
107
+ // stay off-screen. Identity (1,1,0,0) when overscan is 0.
104
108
  const copyFrag = /* glsl */ `
105
109
  precision highp float;
106
110
  layout(location = 0) out vec4 outColor;
107
111
  in vec2 vUv;
108
112
  uniform sampler2D uTex;
109
- void main() { outColor = vec4(texture(uTex, vUv).rgb, 1.0); }
113
+ uniform vec4 uCrop;
114
+ void main() { outColor = vec4(texture(uTex, vUv * uCrop.xy + uCrop.zw).rgb, 1.0); }
110
115
  `;
111
116
 
112
117
  /**
@@ -145,7 +150,10 @@ export class TAAPass {
145
150
  glslVersion: THREE.GLSL3,
146
151
  vertexShader: fullscreenVert,
147
152
  fragmentShader: copyFrag,
148
- uniforms: { uTex: { value: null } },
153
+ uniforms: {
154
+ uTex: { value: null },
155
+ uCrop: { value: new THREE.Vector4(1, 1, 0, 0) },
156
+ },
149
157
  depthTest: false,
150
158
  depthWrite: false,
151
159
  });
@@ -217,8 +225,12 @@ export class TAAPass {
217
225
  * @param prevJitterUv last frame's projection jitter in UV space
218
226
  * @param outputTarget where the resolved frame goes (null = screen); lets a
219
227
  * post pass (god rays) run after the resolve
228
+ * @param crop overscan central-crop transform (THREE.Vector4 scale.xy /
229
+ * offset.zw) applied by the out-copy; null = identity. The
230
+ * resolve itself runs in full padded space; only the copy
231
+ * to `outputTarget` crops to the canvas.
220
232
  */
221
- render(renderer, currentColor, gbuffer, prevViewProj, jitterUv, prevJitterUv, blend, outputTarget = null) {
233
+ render(renderer, currentColor, gbuffer, prevViewProj, jitterUv, prevJitterUv, blend, outputTarget = null, crop = null) {
222
234
  const u = this.material.uniforms;
223
235
  u.uCurrent.value = currentColor;
224
236
  u.uHistory.value = this.targetB.texture; // previous resolved
@@ -234,9 +246,13 @@ export class TAAPass {
234
246
  renderer.setRenderTarget(this.targetA);
235
247
  renderer.render(this.scene, this.camera);
236
248
 
237
- // Copy the resolved frame out (screen, or a post-pass input target).
249
+ // Copy the resolved frame out (screen, or a post-pass input target). The
250
+ // overscan crop lives here — the resolve above stays in full padded space so
251
+ // history reprojection and the neighbourhood clamp see the whole padded image.
238
252
  this.quad.material = this.copyMaterial;
239
253
  this.copyMaterial.uniforms.uTex.value = this.targetA.texture;
254
+ if (crop) this.copyMaterial.uniforms.uCrop.value.copy(crop);
255
+ else this.copyMaterial.uniforms.uCrop.value.set(1, 1, 0, 0);
240
256
  renderer.setRenderTarget(outputTarget);
241
257
  renderer.render(this.scene, this.camera);
242
258
 
package/src/index.d.ts CHANGED
@@ -10,6 +10,24 @@ import type {
10
10
  /** Capability tier used to pick sensible defaults. */
11
11
  export type Tier = "none" | "mid" | "high";
12
12
 
13
+ /** Result of {@link RealtimeRaytracer.probeGPUTier}. */
14
+ export interface GPUTierProbe {
15
+ /** Chosen capability tier. */
16
+ tier: Tier;
17
+ /**
18
+ * Where the tier came from: `"webgpu"` (real adapter limits inspected),
19
+ * `"webgl"` (WebGL `detectTier` heuristic on the supplied renderer), or
20
+ * `"fallback"` (pure user-agent guess — no WebGPU and no renderer given).
21
+ */
22
+ source: "webgpu" | "webgl" | "fallback";
23
+ /**
24
+ * Diagnostics behind the decision: the adapter limits read, any masked
25
+ * `adapter.info` fields, the computed `screenPixels` demand factor, and a
26
+ * human-readable `reason`. Shape varies by `source`; treat as informational.
27
+ */
28
+ details: Record<string, unknown>;
29
+ }
30
+
13
31
  /** Procedural-sky configuration (background + ambient light for escaping GI rays). */
14
32
  export interface SkyOptions {
15
33
  /** Enable the procedural sky as background and ambient GI source. */
@@ -70,6 +88,14 @@ export interface RealtimeRaytracerOptions {
70
88
  * denoiser reconstruct the difference. Set 1.0 for maximum quality.
71
89
  */
72
90
  renderScale?: number;
91
+ /**
92
+ * Overscan: render internally at a padded resolution with a proportionally
93
+ * widened field of view, then crop the centre to the canvas on the final draw.
94
+ * Fraction of padding PER EDGE (clamped 0–0.25). Pushes the disocclusion
95
+ * convergence noise at the leading screen edge off-screen during camera
96
+ * motion. `0.1` renders 1.44× the pixels; 0.05–0.1 recommended. Default `0`.
97
+ */
98
+ overscan?: number;
73
99
  /** À-trous denoise iterations (steps 1, 2, 4, ...). */
74
100
  denoiseIterations?: number;
75
101
  /**
@@ -105,10 +131,31 @@ export interface RealtimeRaytracerOptions {
105
131
  * Dramatically less noise than waiting for GI rays to hit the emitter.
106
132
  */
107
133
  emissiveNEE?: boolean;
134
+ /**
135
+ * Importance-sample WHICH emissive triangle NEE shoots at, proportional to
136
+ * area x emitted luminance (compile-time power CDF) instead of a uniform
137
+ * 1-of-N pick. Same mean, far less sparkle when emitters differ in
138
+ * size/brightness. Default true; false restores the legacy uniform pick.
139
+ */
140
+ emissiveImportance?: boolean;
141
+ /**
142
+ * PBR direct specular: Cook-Torrance GGX highlights for every surface, kept in
143
+ * a separate specular buffer the composite adds WITHOUT the albedo multiply
144
+ * (dielectric highlights are white, F0 ~= 0.04). Metals' albedo-tinted specular
145
+ * rides the reflection path instead. Default true; false restores the old
146
+ * Lambert-only diffuse look.
147
+ */
148
+ specular?: boolean;
108
149
  /** Traced mirror/glossy reflections on metallic surfaces. */
109
150
  reflections?: boolean;
110
151
  /** Traced refraction for transmissive (MeshPhysicalMaterial.transmission) surfaces. */
111
152
  refraction?: boolean;
153
+ /**
154
+ * Alpha-blended transparency: `transparent: true` meshes are primary-visible
155
+ * and composited against the geometry behind them (weighted by `opacity`).
156
+ * Default true. Off = blend surfaces render fully opaque.
157
+ */
158
+ transparency?: boolean;
112
159
  /**
113
160
  * ReSTIR direct lighting: per-pixel reservoirs converge onto the light that
114
161
  * matters most to each pixel. Cost is flat in light count.
@@ -176,7 +223,19 @@ export interface VolumetricState {
176
223
 
177
224
  /** Options accepted by {@link RealtimeRaytracer.compileScene} and {@link compileScene}. */
178
225
  export interface CompileSceneOptions {
179
- /** Meshes whose transforms change every frame; drive them with updateDynamic(). */
226
+ /**
227
+ * Meshes whose transforms change every frame; drive them with updateDynamic().
228
+ *
229
+ * By default a dynamic mesh is treated as **rigid** — its compile-time vertices
230
+ * are snapshotted and only re-transformed by `mesh.matrixWorld` each frame.
231
+ * To trace a mesh whose *vertices* move on the CPU (water, cloth, morph
232
+ * targets), also set `mesh.userData.rtDeforming = true`. Such a mesh has its
233
+ * live `position` (and `normal`) attributes re-read every frame, so the traced
234
+ * shadows/reflections follow the actual deformed shape. The app must keep the
235
+ * normal attribute current (e.g. call `geometry.computeVertexNormals()` after
236
+ * deforming). The live vertex count is fixed at compile time — changing it
237
+ * throws; call `compileScene()` again after a topology change.
238
+ */
180
239
  dynamicMeshes?: Object3D[];
181
240
  }
182
241
 
@@ -210,8 +269,16 @@ export class RealtimeRaytracer {
210
269
 
211
270
  /** Can this renderer run the ray tracing pipeline at all (WebGL2 + float RTs on hardware GPU)? */
212
271
  static isSupported(renderer: WebGLRenderer): boolean;
213
- /** Rough capability tier for choosing defaults. */
272
+ /** Rough capability tier for choosing defaults (synchronous WebGL heuristic). */
214
273
  static detectTier(renderer?: WebGLRenderer): Tier;
274
+ /**
275
+ * Optional async GPU tier probe: inspects real WebGPU adapter limits when
276
+ * available (honest heuristic — WebGPU does NOT expose VRAM, so it uses
277
+ * `maxBufferSize`/texture limits as a proxy and factors screen resolution),
278
+ * else falls back to {@link detectTier}. The constructor stays synchronous;
279
+ * feed the result to {@link recommendedOptions}.
280
+ */
281
+ static probeGPUTier(renderer?: WebGLRenderer): Promise<GPUTierProbe>;
215
282
  /** Sensible constructor options for a tier (spread them, then override). */
216
283
  static recommendedOptions(tier: Tier): RealtimeRaytracerOptions;
217
284
 
@@ -225,13 +292,20 @@ export class RealtimeRaytracer {
225
292
  compiled: CompiledScene | null;
226
293
  /** Accumulated frame counter. */
227
294
  frame: number;
228
- /** Debug view: 0 composite, 1 albedo, 2 normal, 3 irradiance, 4 worldPos, 5 emissive. */
295
+ /** Debug view: 0 composite, 1 albedo, 2 normal, 3 irradiance, 4 worldPos, 5 emissive, 6 specular. */
229
296
  outputMode: number;
230
297
 
231
298
  /** Resolution scale for the ray traced lighting; assigning reallocates targets. */
232
299
  get renderScale(): number;
233
300
  set renderScale(v: number);
234
301
 
302
+ /**
303
+ * Overscan padding fraction per edge (0–0.25). Assigning reallocates every
304
+ * pass at the new padded size and hard-resets accumulation (settings-time).
305
+ */
306
+ get overscan(): number;
307
+ set overscan(v: number);
308
+
235
309
  /** Environment (sky) color for GI rays that miss + composite background. */
236
310
  envColor: Color;
237
311
  /** Environment intensity multiplier. */
@@ -248,10 +322,16 @@ export class RealtimeRaytracer {
248
322
  gi: boolean;
249
323
  /** Sample static emissive meshes as area lights (NEE). */
250
324
  emissiveNEE: boolean;
325
+ /** Importance-sample emissive triangles by power (see the option of the same name). */
326
+ emissiveImportance: boolean;
327
+ /** PBR direct specular (Cook-Torrance GGX) into a separate additive buffer. */
328
+ specular: boolean;
251
329
  /** Traced mirror/glossy reflections on metallic surfaces. */
252
330
  reflections: boolean;
253
331
  /** Traced refraction for transmissive surfaces. */
254
332
  refraction: boolean;
333
+ /** Alpha-blended transparency: composite `transparent` meshes over the geometry behind them. */
334
+ transparency: boolean;
255
335
  /** Index of refraction used for transmissive surfaces. */
256
336
  ior: number;
257
337
  /** One stochastic direct shadow ray per pixel per frame instead of one per light. */
@@ -292,7 +372,10 @@ export class RealtimeRaytracer {
292
372
  updateLights(scene: Scene): void;
293
373
  /** Discard temporal history and restart accumulation. */
294
374
  resetAccumulation(): void;
295
- /** Resize all internal render targets. */
375
+ /**
376
+ * Resize all internal render targets. Pass the CANVAS (drawing-buffer) size;
377
+ * the internal targets are the overscan-padded size derived from it.
378
+ */
296
379
  setSize(width: number, height: number): void;
297
380
  /** Render one frame (call instead of renderer.render). */
298
381
  render(scene: Scene, camera: Camera): void;