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/GBufferPass.js
CHANGED
|
@@ -37,6 +37,32 @@ uniform sampler2D uMap;
|
|
|
37
37
|
uniform bool uHasMap;
|
|
38
38
|
uniform sampler2D uEmissiveMap;
|
|
39
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
|
+
}
|
|
40
66
|
|
|
41
67
|
void main() {
|
|
42
68
|
vec3 albedo = uColor;
|
|
@@ -48,16 +74,37 @@ void main() {
|
|
|
48
74
|
emissive *= texture(uEmissiveMap, vUvCoord).rgb;
|
|
49
75
|
}
|
|
50
76
|
vec3 n = normalize(vWorldNormal) * (gl_FrontFacing ? 1.0 : -1.0);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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);
|
|
57
101
|
// .w packs the valid flag AND roughness: 0 = background, 1 + roughness
|
|
58
102
|
// otherwise. Every consumer only tests w < 0.5, so this stays compatible.
|
|
59
|
-
gWorldPos = vec4(vWorldPos, 1.0 +
|
|
60
|
-
|
|
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);
|
|
61
108
|
}
|
|
62
109
|
`;
|
|
63
110
|
|
|
@@ -155,6 +202,15 @@ export class GBufferPass {
|
|
|
155
202
|
uHasMap: { value: false },
|
|
156
203
|
uEmissiveMap: { value: null },
|
|
157
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 },
|
|
158
214
|
},
|
|
159
215
|
side: THREE.FrontSide,
|
|
160
216
|
});
|
|
@@ -178,6 +234,19 @@ export class GBufferPass {
|
|
|
178
234
|
u.uHasMap.value = !!src.map;
|
|
179
235
|
u.uEmissiveMap.value = src.emissiveMap ?? null;
|
|
180
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;
|
|
181
250
|
u.uNormalMatrixWorld.value.getNormalMatrix(mesh.matrixWorld);
|
|
182
251
|
material.side = src.side ?? THREE.FrontSide;
|
|
183
252
|
return material;
|
|
@@ -187,19 +256,15 @@ export class GBufferPass {
|
|
|
187
256
|
// Ping-pong: what was "current" becomes "previous".
|
|
188
257
|
this._current = 1 - this._current;
|
|
189
258
|
|
|
190
|
-
// Swap in G-buffer materials.
|
|
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.
|
|
191
265
|
this._swapped.length = 0;
|
|
192
266
|
scene.traverse((obj) => {
|
|
193
267
|
if (obj.isMesh && obj.geometry && obj.visible) {
|
|
194
|
-
// The G-buffer has no alpha blending — a nearly invisible surface
|
|
195
|
-
// (glass display case, ghost meshes) would write itself opaque over
|
|
196
|
-
// everything behind it. Hide it from the raster pass instead.
|
|
197
|
-
const m = Array.isArray(obj.material) ? obj.material[0] : obj.material;
|
|
198
|
-
if (m.transparent && (m.opacity ?? 1) < 0.5) {
|
|
199
|
-
obj.visible = false;
|
|
200
|
-
this._swapped.push([obj, null]); // restore visibility after render
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
268
|
this._swapped.push([obj, obj.material]);
|
|
204
269
|
obj.material = this._gbufferMaterialFor(obj);
|
|
205
270
|
}
|
|
@@ -215,10 +280,7 @@ export class GBufferPass {
|
|
|
215
280
|
renderer.setRenderTarget(null);
|
|
216
281
|
|
|
217
282
|
scene.background = prevBackground;
|
|
218
|
-
for (const [mesh, mat] of this._swapped)
|
|
219
|
-
if (mat === null) mesh.visible = true; // was hidden, not material-swapped
|
|
220
|
-
else mesh.material = mat;
|
|
221
|
-
}
|
|
283
|
+
for (const [mesh, mat] of this._swapped) mesh.material = mat;
|
|
222
284
|
this._swapped.length = 0;
|
|
223
285
|
}
|
|
224
286
|
|