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.
- package/README.md +187 -19
- package/package.json +1 -1
- package/src/CompositePass.js +59 -21
- package/src/DenoisePass.js +16 -2
- package/src/GBufferPass.js +84 -22
- package/src/RTLightingPass.js +411 -17
- package/src/RealtimeRaytracer.js +288 -16
- package/src/RestirPass.js +20 -3
- package/src/SceneCompiler.js +127 -32
- package/src/TAAPass.js +20 -4
- package/src/index.d.ts +87 -4
package/src/SceneCompiler.js
CHANGED
|
@@ -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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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;
|