three-realtime-rt 0.3.0 → 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 +559 -394
- package/package.json +52 -52
- package/src/CompositePass.js +261 -214
- package/src/CopyPass.js +75 -0
- package/src/DenoisePass.js +221 -202
- package/src/GBufferPass.js +290 -228
- package/src/RTLightingPass.js +1124 -680
- package/src/RealtimeRaytracer.js +1106 -730
- package/src/RestirPass.js +31 -4
- package/src/SceneCompiler.js +542 -424
- package/src/TAAPass.js +271 -230
- package/src/VolumetricPass.js +398 -329
- package/src/blueNoise.js +17 -17
- package/src/index.d.ts +107 -6
package/src/GBufferPass.js
CHANGED
|
@@ -1,228 +1,290 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
|
|
3
|
-
const gbufferVert = /* glsl */ `
|
|
4
|
-
out vec3 vWorldPos;
|
|
5
|
-
out vec3 vWorldNormal;
|
|
6
|
-
out vec2 vUvCoord;
|
|
7
|
-
|
|
8
|
-
uniform mat3 uNormalMatrixWorld;
|
|
9
|
-
|
|
10
|
-
void main() {
|
|
11
|
-
vec4 wp = modelMatrix * vec4(position, 1.0);
|
|
12
|
-
vWorldPos = wp.xyz;
|
|
13
|
-
vWorldNormal = normalize(uNormalMatrixWorld * normal);
|
|
14
|
-
vUvCoord = uv;
|
|
15
|
-
gl_Position = projectionMatrix * viewMatrix * wp;
|
|
16
|
-
}
|
|
17
|
-
`;
|
|
18
|
-
|
|
19
|
-
const gbufferFrag = /* glsl */ `
|
|
20
|
-
precision highp float;
|
|
21
|
-
|
|
22
|
-
layout(location = 0) out vec4 gAlbedoRough;
|
|
23
|
-
layout(location = 1) out vec4 gNormalMetal;
|
|
24
|
-
layout(location = 2) out vec4 gWorldPos;
|
|
25
|
-
layout(location = 3) out vec4 gEmissive;
|
|
26
|
-
|
|
27
|
-
in vec3 vWorldPos;
|
|
28
|
-
in vec3 vWorldNormal;
|
|
29
|
-
in vec2 vUvCoord;
|
|
30
|
-
|
|
31
|
-
uniform vec3 uColor;
|
|
32
|
-
uniform float uRoughness;
|
|
33
|
-
uniform float uMetalness;
|
|
34
|
-
uniform float uTransmission;
|
|
35
|
-
uniform vec3 uEmissive;
|
|
36
|
-
uniform sampler2D uMap;
|
|
37
|
-
uniform bool uHasMap;
|
|
38
|
-
uniform sampler2D uEmissiveMap;
|
|
39
|
-
uniform bool uHasEmissiveMap;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
3
|
+
const gbufferVert = /* glsl */ `
|
|
4
|
+
out vec3 vWorldPos;
|
|
5
|
+
out vec3 vWorldNormal;
|
|
6
|
+
out vec2 vUvCoord;
|
|
7
|
+
|
|
8
|
+
uniform mat3 uNormalMatrixWorld;
|
|
9
|
+
|
|
10
|
+
void main() {
|
|
11
|
+
vec4 wp = modelMatrix * vec4(position, 1.0);
|
|
12
|
+
vWorldPos = wp.xyz;
|
|
13
|
+
vWorldNormal = normalize(uNormalMatrixWorld * normal);
|
|
14
|
+
vUvCoord = uv;
|
|
15
|
+
gl_Position = projectionMatrix * viewMatrix * wp;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const gbufferFrag = /* glsl */ `
|
|
20
|
+
precision highp float;
|
|
21
|
+
|
|
22
|
+
layout(location = 0) out vec4 gAlbedoRough;
|
|
23
|
+
layout(location = 1) out vec4 gNormalMetal;
|
|
24
|
+
layout(location = 2) out vec4 gWorldPos;
|
|
25
|
+
layout(location = 3) out vec4 gEmissive;
|
|
26
|
+
|
|
27
|
+
in vec3 vWorldPos;
|
|
28
|
+
in vec3 vWorldNormal;
|
|
29
|
+
in vec2 vUvCoord;
|
|
30
|
+
|
|
31
|
+
uniform vec3 uColor;
|
|
32
|
+
uniform float uRoughness;
|
|
33
|
+
uniform float uMetalness;
|
|
34
|
+
uniform float uTransmission;
|
|
35
|
+
uniform vec3 uEmissive;
|
|
36
|
+
uniform sampler2D uMap;
|
|
37
|
+
uniform bool uHasMap;
|
|
38
|
+
uniform sampler2D uEmissiveMap;
|
|
39
|
+
uniform bool uHasEmissiveMap;
|
|
40
|
+
// PBR texture maps (raster pass has ample sampler headroom, unlike the lighting
|
|
41
|
+
// pass). All guarded by a uHas* flag so a material without a given map writes
|
|
42
|
+
// exactly the same bytes it did before these were added.
|
|
43
|
+
uniform sampler2D uNormalMap;
|
|
44
|
+
uniform bool uHasNormalMap;
|
|
45
|
+
uniform vec2 uNormalScale;
|
|
46
|
+
uniform sampler2D uRoughnessMap;
|
|
47
|
+
uniform bool uHasRoughnessMap;
|
|
48
|
+
uniform sampler2D uMetalnessMap;
|
|
49
|
+
uniform bool uHasMetalnessMap;
|
|
50
|
+
uniform bool uBlend;
|
|
51
|
+
uniform float uOpacity;
|
|
52
|
+
|
|
53
|
+
// Screen-space cotangent frame (Mikkelsen 2010): reconstruct a tangent basis
|
|
54
|
+
// from derivatives of world position and uv, so tangent-space normal maps work
|
|
55
|
+
// without a per-vertex tangent attribute (none is uploaded to the BVH/G-buffer).
|
|
56
|
+
vec3 perturbNormal(vec3 N, vec3 P, vec2 uv, vec3 mapN) {
|
|
57
|
+
vec3 dpdx = dFdx(P);
|
|
58
|
+
vec3 dpdy = dFdy(P);
|
|
59
|
+
vec2 duvdx = dFdx(uv);
|
|
60
|
+
vec2 duvdy = dFdy(uv);
|
|
61
|
+
vec3 t = normalize(dpdx * duvdy.y - dpdy * duvdx.y);
|
|
62
|
+
vec3 b = normalize(cross(N, t));
|
|
63
|
+
mat3 tbn = mat3(t, b, N);
|
|
64
|
+
return normalize(tbn * mapN);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void main() {
|
|
68
|
+
vec3 albedo = uColor;
|
|
69
|
+
if (uHasMap) {
|
|
70
|
+
albedo *= texture(uMap, vUvCoord).rgb;
|
|
71
|
+
}
|
|
72
|
+
vec3 emissive = uEmissive;
|
|
73
|
+
if (uHasEmissiveMap) {
|
|
74
|
+
emissive *= texture(uEmissiveMap, vUvCoord).rgb;
|
|
75
|
+
}
|
|
76
|
+
vec3 n = normalize(vWorldNormal) * (gl_FrontFacing ? 1.0 : -1.0);
|
|
77
|
+
if (uHasNormalMap) {
|
|
78
|
+
// Tangent-space normal in [-1,1], scaled by material.normalScale (x,y).
|
|
79
|
+
vec3 mapN = texture(uNormalMap, vUvCoord).xyz * 2.0 - 1.0;
|
|
80
|
+
mapN.xy *= uNormalScale;
|
|
81
|
+
n = perturbNormal(n, vWorldPos, vUvCoord, mapN);
|
|
82
|
+
}
|
|
83
|
+
// three.js convention: green channel of roughnessMap x scalar roughness,
|
|
84
|
+
// blue channel of metalnessMap x scalar metalness (an ORM texture packs both).
|
|
85
|
+
float roughness = uRoughness;
|
|
86
|
+
if (uHasRoughnessMap) roughness *= texture(uRoughnessMap, vUvCoord).g;
|
|
87
|
+
float metalness = uMetalness;
|
|
88
|
+
if (uHasMetalnessMap) metalness *= texture(uMetalnessMap, vUvCoord).b;
|
|
89
|
+
gAlbedoRough = vec4(albedo, roughness);
|
|
90
|
+
// .w is a packed material word in three disjoint ranges, so the lighting pass
|
|
91
|
+
// reads specular/glass/blend properties without an extra G-buffer sampler (it
|
|
92
|
+
// already sits at the WebGL2 16-sampler minimum):
|
|
93
|
+
// [0,1] plain metalness [2,3] transmissive glass (w - 2 = transmission)
|
|
94
|
+
// [4,5] alpha blend (w - 4 = opacity). Blend wins: a transparent surface is
|
|
95
|
+
// kept out of the BVH and composited by the lighting pass, so it must never be
|
|
96
|
+
// read as glass even if the material also carries a transmission value.
|
|
97
|
+
float matWord = uBlend
|
|
98
|
+
? 4.0 + uOpacity
|
|
99
|
+
: (uTransmission > 0.0 ? 2.0 + uTransmission : metalness);
|
|
100
|
+
gNormalMetal = vec4(n, matWord);
|
|
101
|
+
// .w packs the valid flag AND roughness: 0 = background, 1 + roughness
|
|
102
|
+
// otherwise. Every consumer only tests w < 0.5, so this stays compatible.
|
|
103
|
+
gWorldPos = vec4(vWorldPos, 1.0 + roughness);
|
|
104
|
+
// .a is normally the constant 1.0 (CompositePass reads only .rgb). A blend
|
|
105
|
+
// surface carries its opacity here; the packed word above also encodes it, so
|
|
106
|
+
// the sampler-bound lighting pass reads opacity without a gEmissive fetch.
|
|
107
|
+
gEmissive = vec4(emissive, uBlend ? uOpacity : 1.0);
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Rasterizes the scene into a 4-target G-buffer (all RGBA32F):
|
|
113
|
+
* [0] albedo.rgb + roughness [1] worldNormal.xyz + metalness
|
|
114
|
+
* [2] worldPos.xyz + validFlag [3] emissive.rgb
|
|
115
|
+
*
|
|
116
|
+
* Uses a per-mesh material swap (cached) so each mesh's Standard/Basic/Lambert/Phong
|
|
117
|
+
* material properties flow into the buffer without touching user materials.
|
|
118
|
+
*/
|
|
119
|
+
export class GBufferPass {
|
|
120
|
+
constructor(width, height, { mixedPrecision = true } = {}) {
|
|
121
|
+
// Mixed fp16/fp32 attachments are legal WebGL2 but some implementations
|
|
122
|
+
// (notably Apple's Metal backend) may reject the framebuffer — the caller
|
|
123
|
+
// probes support and passes the verdict here.
|
|
124
|
+
this._mixedPrecision = mixedPrecision;
|
|
125
|
+
// Two G-buffers, ping-ponged each frame: the previous frame's worldPos +
|
|
126
|
+
// normals are needed to validate reprojected history (stage 2).
|
|
127
|
+
this._targets = [
|
|
128
|
+
this._makeTarget(width, height),
|
|
129
|
+
this._makeTarget(width, height),
|
|
130
|
+
];
|
|
131
|
+
this._current = 0;
|
|
132
|
+
|
|
133
|
+
this._materialCache = new WeakMap(); // mesh -> gbuffer ShaderMaterial
|
|
134
|
+
this._swapped = []; // [mesh, originalMaterial] pairs during render
|
|
135
|
+
this._normalMat3 = new THREE.Matrix3();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_makeTarget(width, height) {
|
|
139
|
+
const t = new THREE.WebGLMultipleRenderTargets(width, height, 4, {
|
|
140
|
+
minFilter: THREE.NearestFilter,
|
|
141
|
+
magFilter: THREE.NearestFilter,
|
|
142
|
+
type: THREE.FloatType,
|
|
143
|
+
depthBuffer: true,
|
|
144
|
+
});
|
|
145
|
+
for (const tex of t.texture) tex.generateMipmaps = false;
|
|
146
|
+
// Mixed precision: only world position (reprojection + plane-distance
|
|
147
|
+
// tests) needs fp32. Albedo/normal/emissive are fine in fp16, which halves
|
|
148
|
+
// G-buffer bandwidth on 3 of the 4 targets — a large win on mobile GPUs.
|
|
149
|
+
if (this._mixedPrecision) {
|
|
150
|
+
t.texture[0].type = THREE.HalfFloatType; // albedo + roughness
|
|
151
|
+
t.texture[1].type = THREE.HalfFloatType; // normal + packed material word
|
|
152
|
+
t.texture[3].type = THREE.HalfFloatType; // emissive
|
|
153
|
+
}
|
|
154
|
+
return t;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
get target() {
|
|
158
|
+
return this._targets[this._current];
|
|
159
|
+
}
|
|
160
|
+
get _prev() {
|
|
161
|
+
return this._targets[1 - this._current];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get albedoRough() {
|
|
165
|
+
return this.target.texture[0];
|
|
166
|
+
}
|
|
167
|
+
get normalMetal() {
|
|
168
|
+
return this.target.texture[1];
|
|
169
|
+
}
|
|
170
|
+
get worldPos() {
|
|
171
|
+
return this.target.texture[2];
|
|
172
|
+
}
|
|
173
|
+
get emissive() {
|
|
174
|
+
return this.target.texture[3];
|
|
175
|
+
}
|
|
176
|
+
get prevNormalMetal() {
|
|
177
|
+
return this._prev.texture[1];
|
|
178
|
+
}
|
|
179
|
+
get prevWorldPos() {
|
|
180
|
+
return this._prev.texture[2];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setSize(width, height) {
|
|
184
|
+
for (const t of this._targets) t.setSize(width, height);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_gbufferMaterialFor(mesh) {
|
|
188
|
+
let material = this._materialCache.get(mesh);
|
|
189
|
+
if (!material) {
|
|
190
|
+
material = new THREE.ShaderMaterial({
|
|
191
|
+
glslVersion: THREE.GLSL3,
|
|
192
|
+
vertexShader: gbufferVert,
|
|
193
|
+
fragmentShader: gbufferFrag,
|
|
194
|
+
uniforms: {
|
|
195
|
+
uNormalMatrixWorld: { value: new THREE.Matrix3() },
|
|
196
|
+
uColor: { value: new THREE.Color(1, 1, 1) },
|
|
197
|
+
uRoughness: { value: 1.0 },
|
|
198
|
+
uMetalness: { value: 0.0 },
|
|
199
|
+
uTransmission: { value: 0.0 },
|
|
200
|
+
uEmissive: { value: new THREE.Color(0, 0, 0) },
|
|
201
|
+
uMap: { value: null },
|
|
202
|
+
uHasMap: { value: false },
|
|
203
|
+
uEmissiveMap: { value: null },
|
|
204
|
+
uHasEmissiveMap: { value: false },
|
|
205
|
+
uNormalMap: { value: null },
|
|
206
|
+
uHasNormalMap: { value: false },
|
|
207
|
+
uNormalScale: { value: new THREE.Vector2(1, 1) },
|
|
208
|
+
uRoughnessMap: { value: null },
|
|
209
|
+
uHasRoughnessMap: { value: false },
|
|
210
|
+
uMetalnessMap: { value: null },
|
|
211
|
+
uHasMetalnessMap: { value: false },
|
|
212
|
+
uBlend: { value: false },
|
|
213
|
+
uOpacity: { value: 1.0 },
|
|
214
|
+
},
|
|
215
|
+
side: THREE.FrontSide,
|
|
216
|
+
});
|
|
217
|
+
this._materialCache.set(mesh, material);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Sync properties from the user's material every frame (cheap).
|
|
221
|
+
const src = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
|
|
222
|
+
const u = material.uniforms;
|
|
223
|
+
if (src.color) u.uColor.value.copy(src.color);
|
|
224
|
+
u.uRoughness.value = src.roughness ?? 1.0;
|
|
225
|
+
u.uMetalness.value = src.metalness ?? 0.0;
|
|
226
|
+
u.uTransmission.value = src.transmission ?? 0.0; // MeshPhysicalMaterial
|
|
227
|
+
|
|
228
|
+
if (src.emissive) {
|
|
229
|
+
u.uEmissive.value
|
|
230
|
+
.copy(src.emissive)
|
|
231
|
+
.multiplyScalar(src.emissiveIntensity ?? 1);
|
|
232
|
+
}
|
|
233
|
+
u.uMap.value = src.map ?? null;
|
|
234
|
+
u.uHasMap.value = !!src.map;
|
|
235
|
+
u.uEmissiveMap.value = src.emissiveMap ?? null;
|
|
236
|
+
u.uHasEmissiveMap.value = !!src.emissiveMap;
|
|
237
|
+
u.uNormalMap.value = src.normalMap ?? null;
|
|
238
|
+
u.uHasNormalMap.value = !!src.normalMap;
|
|
239
|
+
if (src.normalScale) u.uNormalScale.value.copy(src.normalScale);
|
|
240
|
+
else u.uNormalScale.value.set(1, 1);
|
|
241
|
+
u.uRoughnessMap.value = src.roughnessMap ?? null;
|
|
242
|
+
u.uHasRoughnessMap.value = !!src.roughnessMap;
|
|
243
|
+
u.uMetalnessMap.value = src.metalnessMap ?? null;
|
|
244
|
+
u.uHasMetalnessMap.value = !!src.metalnessMap;
|
|
245
|
+
// Alpha-blended transparency: primary-visible here (opacity packed into the
|
|
246
|
+
// material word + gEmissive.a), composited against the geometry behind by the
|
|
247
|
+
// lighting pass. opacity 1 renders opaque, matching the old force-opaque path.
|
|
248
|
+
u.uBlend.value = !!src.transparent;
|
|
249
|
+
u.uOpacity.value = src.opacity ?? 1.0;
|
|
250
|
+
u.uNormalMatrixWorld.value.getNormalMatrix(mesh.matrixWorld);
|
|
251
|
+
material.side = src.side ?? THREE.FrontSide;
|
|
252
|
+
return material;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
render(renderer, scene, camera) {
|
|
256
|
+
// Ping-pong: what was "current" becomes "previous".
|
|
257
|
+
this._current = 1 - this._current;
|
|
258
|
+
|
|
259
|
+
// Swap in G-buffer materials. Transparent meshes are written too — as the
|
|
260
|
+
// nearest single layer, depth-tested/written like any surface (overlapping
|
|
261
|
+
// transparent surfaces do not inter-sort). Their opacity rides in the packed
|
|
262
|
+
// material word + gEmissive.a, and the lighting pass blends them against the
|
|
263
|
+
// geometry behind. opacity 1 writes fully opaque, so alpha-textured cases
|
|
264
|
+
// (LittlestTokyo's glass) look exactly as before.
|
|
265
|
+
this._swapped.length = 0;
|
|
266
|
+
scene.traverse((obj) => {
|
|
267
|
+
if (obj.isMesh && obj.geometry && obj.visible) {
|
|
268
|
+
this._swapped.push([obj, obj.material]);
|
|
269
|
+
obj.material = this._gbufferMaterialFor(obj);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const prevBackground = scene.background;
|
|
274
|
+
scene.background = null; // background writes nothing; worldPos.w stays 0
|
|
275
|
+
|
|
276
|
+
renderer.setRenderTarget(this.target);
|
|
277
|
+
renderer.setClearColor(0x000000, 0);
|
|
278
|
+
renderer.clear(true, true, false);
|
|
279
|
+
renderer.render(scene, camera);
|
|
280
|
+
renderer.setRenderTarget(null);
|
|
281
|
+
|
|
282
|
+
scene.background = prevBackground;
|
|
283
|
+
for (const [mesh, mat] of this._swapped) mesh.material = mat;
|
|
284
|
+
this._swapped.length = 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
dispose() {
|
|
288
|
+
for (const t of this._targets) t.dispose();
|
|
289
|
+
}
|
|
290
|
+
}
|