three-realtime-rt 0.4.2 → 0.6.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.
@@ -1,17 +1,50 @@
1
1
  import * as THREE from "three";
2
2
 
3
+ // three defines USE_SKINNING and supplies the bindMatrix / bindMatrixInverse /
4
+ // boneTexture uniforms + skinIndex / skinWeight attributes automatically when the
5
+ // rendered object is a SkinnedMesh (this is a ShaderMaterial, not a
6
+ // RawShaderMaterial, so it inherits three's default vertex prefix and #include
7
+ // resolution). For a non-skinned mesh USE_SKINNING is undefined and every chunk
8
+ // below collapses to nothing — the shader source is identical in both cases.
9
+ // The chunks operate on `transformed` (position) and `objectNormal` (normal) in
10
+ // the mesh's LOCAL/bind space, so we map our own variables in and out. The
11
+ // skinned local position/normal then go through modelMatrix / uNormalMatrixWorld
12
+ // exactly like the un-skinned path — matching the CPU BVH skinning in
13
+ // SceneCompiler (both skin to local, then transform to world).
3
14
  const gbufferVert = /* glsl */ `
15
+ #include <skinning_pars_vertex>
16
+
4
17
  out vec3 vWorldPos;
5
18
  out vec3 vWorldNormal;
6
19
  out vec2 vUvCoord;
20
+ out vec3 vColor;
7
21
 
8
22
  uniform mat3 uNormalMatrixWorld;
9
23
 
10
24
  void main() {
11
- vec4 wp = modelMatrix * vec4(position, 1.0);
25
+ vec3 transformed = position;
26
+ vec3 objectNormal = normal;
27
+ #include <skinbase_vertex>
28
+ #include <skinnormal_vertex>
29
+ #include <skinning_vertex>
30
+
31
+ vec4 wp = modelMatrix * vec4(transformed, 1.0);
12
32
  vWorldPos = wp.xyz;
13
- vWorldNormal = normalize(uNormalMatrixWorld * normal);
33
+ vWorldNormal = normalize(uNormalMatrixWorld * objectNormal);
14
34
  vUvCoord = uv;
35
+ // Geometry vertex colours. three's shader prefix declares the built-in
36
+ // \`color\` attribute (vec3 or vec4) and sets USE_COLOR / USE_COLOR_ALPHA only
37
+ // when material.vertexColors is on — which we enable ONLY for meshes whose
38
+ // geometry actually carries a color attribute (see GBufferPass swap). A mesh
39
+ // without one compiles the else branch (white), so its albedo is byte-identical
40
+ // to before this varying existed. 4-component colours use .rgb.
41
+ #if defined( USE_COLOR_ALPHA )
42
+ vColor = color.rgb;
43
+ #elif defined( USE_COLOR )
44
+ vColor = color;
45
+ #else
46
+ vColor = vec3(1.0);
47
+ #endif
15
48
  gl_Position = projectionMatrix * viewMatrix * wp;
16
49
  }
17
50
  `;
@@ -27,11 +60,13 @@ layout(location = 3) out vec4 gEmissive;
27
60
  in vec3 vWorldPos;
28
61
  in vec3 vWorldNormal;
29
62
  in vec2 vUvCoord;
63
+ in vec3 vColor;
30
64
 
31
65
  uniform vec3 uColor;
32
66
  uniform float uRoughness;
33
67
  uniform float uMetalness;
34
68
  uniform float uTransmission;
69
+ uniform float uIor;
35
70
  uniform vec3 uEmissive;
36
71
  uniform sampler2D uMap;
37
72
  uniform bool uHasMap;
@@ -69,6 +104,7 @@ void main() {
69
104
  if (uHasMap) {
70
105
  albedo *= texture(uMap, vUvCoord).rgb;
71
106
  }
107
+ albedo *= vColor; // vertex colours (white when the mesh has no color attribute)
72
108
  vec3 emissive = uEmissive;
73
109
  if (uHasEmissiveMap) {
74
110
  emissive *= texture(uEmissiveMap, vUvCoord).rgb;
@@ -87,16 +123,34 @@ void main() {
87
123
  float metalness = uMetalness;
88
124
  if (uHasMetalnessMap) metalness *= texture(uMetalnessMap, vUvCoord).b;
89
125
  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);
126
+ // .w is a packed material word in disjoint ranges, so the lighting pass reads
127
+ // specular/glass/blend properties without an extra G-buffer sampler (it already
128
+ // sits at the WebGL2 16-sampler minimum — the reason per-material IOR rides
129
+ // here rather than in a third G-buffer texture the lighting pass would have to
130
+ // sample):
131
+ // [0,1] plain metalness
132
+ // (2,3] transmissive glass, PARTIAL: w - 2 = transmission (global rt.ior)
133
+ // [3,4) transmissive glass, FULL (transmission >= ~1): w - 3 = ior - 1
134
+ // [4,5] alpha blend: w - 4 = opacity
135
+ // Blend wins: a transparent surface is kept out of the BVH and composited by
136
+ // the lighting pass, so it must never be read as glass. Every EXISTING consumer
137
+ // decodes clamp(w - 2, 0, 1) as transmission, which saturates to 1.0 across the
138
+ // whole [3,4) band — so full glass keeps reading as fully transmissive there and
139
+ // only the lighting pass additionally recovers the per-material IOR (Task 2).
140
+ float matWord;
141
+ if (uBlend) {
142
+ matWord = 4.0 + uOpacity;
143
+ } else if (uTransmission > 0.0) {
144
+ if (uTransmission >= 0.99) {
145
+ // clamp (ior - 1) to [0, 0.98] so the word stays clear of the 4.0 blend
146
+ // boundary even after fp16 rounding of this channel; covers ior 1.0-1.98.
147
+ matWord = 3.0 + clamp(uIor - 1.0, 0.0, 0.98);
148
+ } else {
149
+ matWord = 2.0 + uTransmission; // partial glass: keep transmission, global ior
150
+ }
151
+ } else {
152
+ matWord = metalness;
153
+ }
100
154
  gNormalMetal = vec4(n, matWord);
101
155
  // .w packs the valid flag AND roughness: 0 = background, 1 + roughness
102
156
  // otherwise. Every consumer only tests w < 0.5, so this stays compatible.
@@ -184,46 +238,54 @@ export class GBufferPass {
184
238
  for (const t of this._targets) t.setSize(width, height);
185
239
  }
186
240
 
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
- }
241
+ _makeGbufferMaterial(mesh) {
242
+ const material = new THREE.ShaderMaterial({
243
+ glslVersion: THREE.GLSL3,
244
+ vertexShader: gbufferVert,
245
+ fragmentShader: gbufferFrag,
246
+ uniforms: {
247
+ uNormalMatrixWorld: { value: new THREE.Matrix3() },
248
+ uColor: { value: new THREE.Color(1, 1, 1) },
249
+ uRoughness: { value: 1.0 },
250
+ uMetalness: { value: 0.0 },
251
+ uTransmission: { value: 0.0 },
252
+ uIor: { value: 1.5 },
253
+ uEmissive: { value: new THREE.Color(0, 0, 0) },
254
+ uMap: { value: null },
255
+ uHasMap: { value: false },
256
+ uEmissiveMap: { value: null },
257
+ uHasEmissiveMap: { value: false },
258
+ uNormalMap: { value: null },
259
+ uHasNormalMap: { value: false },
260
+ uNormalScale: { value: new THREE.Vector2(1, 1) },
261
+ uRoughnessMap: { value: null },
262
+ uHasRoughnessMap: { value: false },
263
+ uMetalnessMap: { value: null },
264
+ uHasMetalnessMap: { value: false },
265
+ uBlend: { value: false },
266
+ uOpacity: { value: 1.0 },
267
+ },
268
+ side: THREE.FrontSide,
269
+ });
270
+ // Enable the vertex-colour path ONLY when this mesh's geometry carries a
271
+ // color attribute. This drives three's USE_COLOR define (see gbufferVert), so
272
+ // a mesh without one writes byte-identical albedo. The material cache is
273
+ // per-mesh, so this per-mesh define variant is safe.
274
+ material.vertexColors = !!(mesh.geometry && mesh.geometry.getAttribute("color"));
275
+ return material;
276
+ }
219
277
 
220
- // Sync properties from the user's material every frame (cheap).
221
- const src = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
278
+ // Sync properties from one user material into one gbuffer material (cheap; run
279
+ // every frame). `mesh` supplies the shared world normal matrix + side.
280
+ _syncGbufferMaterial(material, src, mesh) {
222
281
  const u = material.uniforms;
223
282
  if (src.color) u.uColor.value.copy(src.color);
224
283
  u.uRoughness.value = src.roughness ?? 1.0;
225
284
  u.uMetalness.value = src.metalness ?? 0.0;
226
285
  u.uTransmission.value = src.transmission ?? 0.0; // MeshPhysicalMaterial
286
+ // Per-material IOR (MeshPhysicalMaterial.ior; default 1.5). Encoded into the
287
+ // packed material word for fully-transmissive glass (see gbufferFrag).
288
+ u.uIor.value = src.ior ?? 1.5;
227
289
 
228
290
  if (src.emissive) {
229
291
  u.uEmissive.value
@@ -249,6 +311,30 @@ export class GBufferPass {
249
311
  u.uOpacity.value = src.opacity ?? 1.0;
250
312
  u.uNormalMatrixWorld.value.getNormalMatrix(mesh.matrixWorld);
251
313
  material.side = src.side ?? THREE.FrontSide;
314
+ }
315
+
316
+ // Returns the gbuffer material(s) for a mesh: a single ShaderMaterial, or — for
317
+ // a multi-material mesh (mesh.material is an array + geometry.groups) — an ARRAY
318
+ // of them, one per source material, index-aligned so three renders each group
319
+ // with its own gbuffer material natively.
320
+ _gbufferMaterialFor(mesh) {
321
+ if (Array.isArray(mesh.material)) {
322
+ let cached = this._materialCache.get(mesh);
323
+ if (!Array.isArray(cached) || cached.length !== mesh.material.length) {
324
+ cached = mesh.material.map(() => this._makeGbufferMaterial(mesh));
325
+ this._materialCache.set(mesh, cached);
326
+ }
327
+ for (let i = 0; i < mesh.material.length; i++) {
328
+ this._syncGbufferMaterial(cached[i], mesh.material[i], mesh);
329
+ }
330
+ return cached;
331
+ }
332
+ let material = this._materialCache.get(mesh);
333
+ if (!material || Array.isArray(material)) {
334
+ material = this._makeGbufferMaterial(mesh);
335
+ this._materialCache.set(mesh, material);
336
+ }
337
+ this._syncGbufferMaterial(material, mesh.material, mesh);
252
338
  return material;
253
339
  }
254
340