@driftengine/core 4.8.3 → 4.8.5

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.
Files changed (180) hide show
  1. package/CHANGELOG.json +82 -0
  2. package/README.md +1 -1
  3. package/dist/build/shaderComments.js +0 -0
  4. package/dist/geometry/meshConcat.js +2 -0
  5. package/dist/index.d.ts +10 -4
  6. package/dist/index.js +16 -1
  7. package/dist/math/scalar.js +1 -1
  8. package/dist/render/anisotropicLobe.d.ts +7 -0
  9. package/dist/render/anisotropicLobe.js +33 -0
  10. package/dist/render/backend/api.d.ts +41 -4
  11. package/dist/render/backend/select.js +8 -0
  12. package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
  13. package/dist/render/backend/webgl2/clothTextures.js +93 -0
  14. package/dist/render/backend/webgl2/renderer.d.ts +136 -3
  15. package/dist/render/backend/webgl2/renderer.js +604 -63
  16. package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
  17. package/dist/render/backend/webgl2/skinScatter.js +256 -0
  18. package/dist/render/backend/webgpu/buffers.d.ts +25 -11
  19. package/dist/render/backend/webgpu/buffers.js +98 -49
  20. package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
  21. package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
  22. package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
  23. package/dist/render/backend/webgpu/clothTextures.js +145 -0
  24. package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
  25. package/dist/render/backend/webgpu/compressedUpload.js +38 -0
  26. package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
  27. package/dist/render/backend/webgpu/computeHost.js +5 -0
  28. package/dist/render/backend/webgpu/depthPass.d.ts +16 -6
  29. package/dist/render/backend/webgpu/depthPass.js +39 -9
  30. package/dist/render/backend/webgpu/drawCommand.js +6 -2
  31. package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
  32. package/dist/render/backend/webgpu/flatPass.js +109 -12
  33. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
  34. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
  35. package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
  36. package/dist/render/backend/webgpu/motionPass.js +106 -9
  37. package/dist/render/backend/webgpu/postPass.d.ts +3 -1
  38. package/dist/render/backend/webgpu/postPass.js +6 -3
  39. package/dist/render/backend/webgpu/renderer.d.ts +113 -5
  40. package/dist/render/backend/webgpu/renderer.js +498 -48
  41. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
  42. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
  43. package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
  44. package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
  45. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  46. package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
  47. package/dist/render/clothBindingData.d.ts +41 -0
  48. package/dist/render/clothBindingData.js +72 -0
  49. package/dist/render/clothPlacement.d.ts +25 -0
  50. package/dist/render/clothPlacement.js +85 -0
  51. package/dist/render/compressedSource.d.ts +66 -0
  52. package/dist/render/compressedSource.js +138 -0
  53. package/dist/render/cutoutCaster.d.ts +2 -0
  54. package/dist/render/cutoutCaster.js +2 -0
  55. package/dist/render/cutoutDither.d.ts +51 -0
  56. package/dist/render/cutoutDither.js +90 -0
  57. package/dist/render/eyeRefraction.d.ts +35 -0
  58. package/dist/render/eyeRefraction.js +40 -0
  59. package/dist/render/glCompressed.d.ts +27 -0
  60. package/dist/render/glCompressed.js +53 -0
  61. package/dist/render/hairLobes.d.ts +70 -0
  62. package/dist/render/hairLobes.js +120 -0
  63. package/dist/render/lightBudget.d.ts +22 -0
  64. package/dist/render/lightBudget.js +22 -0
  65. package/dist/render/mesh.d.ts +7 -0
  66. package/dist/render/mesh.js +24 -0
  67. package/dist/render/recon/motionVectors.d.ts +4 -2
  68. package/dist/render/recon/motionVectors.js +6 -3
  69. package/dist/render/renderQuality.d.ts +20 -0
  70. package/dist/render/renderQuality.js +5 -0
  71. package/dist/render/rendererHarness.js +4 -0
  72. package/dist/render/sceneTarget.d.ts +12 -0
  73. package/dist/render/sceneTarget.js +14 -0
  74. package/dist/render/shaders/clothBinding.d.ts +22 -0
  75. package/dist/render/shaders/clothBinding.js +80 -0
  76. package/dist/render/shaders/depth.js +42 -6
  77. package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
  78. package/dist/render/shaders/flat/anisotropic.js +68 -0
  79. package/dist/render/shaders/flat/eye.d.ts +1 -0
  80. package/dist/render/shaders/flat/eye.js +81 -0
  81. package/dist/render/shaders/flat/hair.d.ts +1 -0
  82. package/dist/render/shaders/flat/hair.js +101 -0
  83. package/dist/render/shaders/flat/index.js +36 -2
  84. package/dist/render/shaders/flat/main.d.ts +1 -1
  85. package/dist/render/shaders/flat/main.js +113 -17
  86. package/dist/render/shaders/flat/models.d.ts +14 -0
  87. package/dist/render/shaders/flat/models.js +273 -0
  88. package/dist/render/shaders/flat/preamble.js +26 -24
  89. package/dist/render/shaders/flat/skin.d.ts +1 -0
  90. package/dist/render/shaders/flat/skin.js +114 -0
  91. package/dist/render/shaders/generated/depth.wgsl.d.ts +38 -16
  92. package/dist/render/shaders/generated/depth.wgsl.js +38 -16
  93. package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
  94. package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
  95. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
  96. package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
  97. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  98. package/dist/render/shaders/recon/motion.wgsl.js +102 -0
  99. package/dist/render/shaders/skinBlur.d.ts +1 -0
  100. package/dist/render/shaders/skinBlur.js +103 -0
  101. package/dist/render/shaders/skinning.d.ts +3 -1
  102. package/dist/render/shaders/skinning.js +31 -4
  103. package/dist/render/shadowCasters.d.ts +13 -1
  104. package/dist/render/skinBlur.d.ts +41 -0
  105. package/dist/render/skinBlur.js +49 -0
  106. package/dist/render/skinScatter.d.ts +67 -0
  107. package/dist/render/skinScatter.js +80 -0
  108. package/dist/render/skinnedClothRun.d.ts +26 -0
  109. package/dist/render/skinnedClothRun.js +84 -0
  110. package/dist/render/surfaceModel.d.ts +129 -0
  111. package/dist/render/surfaceModel.js +152 -0
  112. package/dist/render/surfaceTexture.d.ts +44 -4
  113. package/dist/render/surfaceTexture.js +60 -46
  114. package/dist/render/vertexDefaults.js +7 -0
  115. package/package.json +6 -6
  116. package/src/build/shaderComments.ts +0 -0
  117. package/src/geometry/meshConcat.ts +2 -0
  118. package/src/index.ts +40 -1
  119. package/src/math/scalar.ts +1 -1
  120. package/src/render/anisotropicLobe.ts +41 -0
  121. package/src/render/backend/api.ts +52 -3
  122. package/src/render/backend/select.ts +8 -0
  123. package/src/render/backend/webgl2/clothTextures.ts +120 -0
  124. package/src/render/backend/webgl2/renderer.ts +693 -64
  125. package/src/render/backend/webgl2/skinScatter.ts +309 -0
  126. package/src/render/backend/webgpu/buffers.ts +137 -63
  127. package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
  128. package/src/render/backend/webgpu/clothTextures.ts +194 -0
  129. package/src/render/backend/webgpu/compressedUpload.ts +65 -0
  130. package/src/render/backend/webgpu/computeHost.ts +25 -0
  131. package/src/render/backend/webgpu/depthPass.ts +39 -7
  132. package/src/render/backend/webgpu/drawCommand.ts +6 -2
  133. package/src/render/backend/webgpu/flatPass.ts +145 -7
  134. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
  135. package/src/render/backend/webgpu/motionPass.ts +128 -9
  136. package/src/render/backend/webgpu/postPass.ts +5 -1
  137. package/src/render/backend/webgpu/renderer.ts +663 -43
  138. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
  139. package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
  140. package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
  141. package/src/render/clothBindingData.ts +89 -0
  142. package/src/render/clothPlacement.ts +114 -0
  143. package/src/render/compressedSource.ts +198 -0
  144. package/src/render/cutoutCaster.ts +5 -0
  145. package/src/render/cutoutDither.ts +99 -0
  146. package/src/render/eyeRefraction.ts +72 -0
  147. package/src/render/glCompressed.ts +86 -0
  148. package/src/render/hairLobes.ts +188 -0
  149. package/src/render/lightBudget.ts +25 -0
  150. package/src/render/mesh.ts +23 -0
  151. package/src/render/recon/motionVectors.ts +6 -3
  152. package/src/render/renderQuality.ts +28 -0
  153. package/src/render/rendererHarness.ts +4 -0
  154. package/src/render/sceneTarget.ts +21 -0
  155. package/src/render/shaders/clothBinding.ts +81 -0
  156. package/src/render/shaders/depth.ts +42 -6
  157. package/src/render/shaders/flat/anisotropic.ts +68 -0
  158. package/src/render/shaders/flat/eye.ts +82 -0
  159. package/src/render/shaders/flat/hair.ts +102 -0
  160. package/src/render/shaders/flat/index.ts +36 -2
  161. package/src/render/shaders/flat/main.ts +113 -17
  162. package/src/render/shaders/flat/models.ts +286 -0
  163. package/src/render/shaders/flat/preamble.ts +26 -24
  164. package/src/render/shaders/flat/skin.ts +116 -0
  165. package/src/render/shaders/generated/depth.wgsl.ts +38 -16
  166. package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
  167. package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
  168. package/src/render/shaders/recon/motion.wgsl.ts +102 -0
  169. package/src/render/shaders/skinBlur.ts +105 -0
  170. package/src/render/shaders/skinning.ts +31 -4
  171. package/src/render/shadowCasters.ts +13 -0
  172. package/src/render/skinBlur.ts +53 -0
  173. package/src/render/skinScatter.ts +92 -0
  174. package/src/render/skinnedClothRun.ts +128 -0
  175. package/src/render/surfaceModel.ts +274 -0
  176. package/src/render/surfaceTexture.ts +123 -63
  177. package/src/render/vertexDefaults.ts +7 -0
  178. package/dist/math/exact.d.ts +0 -76
  179. package/dist/math/exact.js +0 -327
  180. package/src/math/exact.ts +0 -340
@@ -213,6 +213,28 @@ export declare const POINT_GLASS_TINT_TEXTURE_UNIT: number;
213
213
  * to free the sampler this takes, so the lit stage still keeps one of its sixteen spare.
214
214
  */
215
215
  export declare const SURFACE_EFFECTS_TEXTURE_UNIT: number;
216
+ /**
217
+ * A cloth binding's three vertex-stage textures — the binding, the particles now, the particles at
218
+ * rest (`clothBinding.ts`). Vertex samplers, so they count against the vertex stage's own sixteen
219
+ * beside the palette and the morph deltas, not the lit stage's; numbered past every fragment unit,
220
+ * inside the combined pool of thirty-two.
221
+ */
222
+ export declare const CLOTH_BINDING_TEXTURE_UNIT: number;
223
+ export declare const CLOTH_PARTICLES_TEXTURE_UNIT: number;
224
+ export declare const CLOTH_REST_TEXTURE_UNIT: number;
225
+ /**
226
+ * A shading model's own channels (`shaders/flat/models.ts`), the lit stage's **sixteenth** fragment
227
+ * sampler — exactly what WebGL2 guarantees a stage, which `textureUnitBudget.test.ts` counts from
228
+ * the source. Numbered past the cloth's vertex units, inside the combined pool of thirty-two.
229
+ */
230
+ export declare const MODEL_MAP_TEXTURE_UNIT: number;
231
+ /**
232
+ * The two units skin's screen-space blur reads on WebGL2 — its target and the frame's depth — past
233
+ * every unit a lit program binds, so spreading skin in the middle of a frame unbinds nothing the
234
+ * draws after it sample. WebGL2 guarantees thirty-two.
235
+ */
236
+ export declare const SKIN_BLUR_TEXTURE_UNIT: number;
237
+ export declare const SKIN_BLUR_DEPTH_UNIT: number;
216
238
  /**
217
239
  * A cookie's tile, in texels a side.
218
240
  *
@@ -211,6 +211,28 @@ export const POINT_GLASS_TINT_TEXTURE_UNIT = SUN_GLASS_TINT_TEXTURE_UNIT + 1;
211
211
  * to free the sampler this takes, so the lit stage still keeps one of its sixteen spare.
212
212
  */
213
213
  export const SURFACE_EFFECTS_TEXTURE_UNIT = POINT_GLASS_TINT_TEXTURE_UNIT + 1;
214
+ /**
215
+ * A cloth binding's three vertex-stage textures — the binding, the particles now, the particles at
216
+ * rest (`clothBinding.ts`). Vertex samplers, so they count against the vertex stage's own sixteen
217
+ * beside the palette and the morph deltas, not the lit stage's; numbered past every fragment unit,
218
+ * inside the combined pool of thirty-two.
219
+ */
220
+ export const CLOTH_BINDING_TEXTURE_UNIT = SURFACE_EFFECTS_TEXTURE_UNIT + 1;
221
+ export const CLOTH_PARTICLES_TEXTURE_UNIT = CLOTH_BINDING_TEXTURE_UNIT + 1;
222
+ export const CLOTH_REST_TEXTURE_UNIT = CLOTH_PARTICLES_TEXTURE_UNIT + 1;
223
+ /**
224
+ * A shading model's own channels (`shaders/flat/models.ts`), the lit stage's **sixteenth** fragment
225
+ * sampler — exactly what WebGL2 guarantees a stage, which `textureUnitBudget.test.ts` counts from
226
+ * the source. Numbered past the cloth's vertex units, inside the combined pool of thirty-two.
227
+ */
228
+ export const MODEL_MAP_TEXTURE_UNIT = CLOTH_REST_TEXTURE_UNIT + 1;
229
+ /**
230
+ * The two units skin's screen-space blur reads on WebGL2 — its target and the frame's depth — past
231
+ * every unit a lit program binds, so spreading skin in the middle of a frame unbinds nothing the
232
+ * draws after it sample. WebGL2 guarantees thirty-two.
233
+ */
234
+ export const SKIN_BLUR_TEXTURE_UNIT = MODEL_MAP_TEXTURE_UNIT + 1;
235
+ export const SKIN_BLUR_DEPTH_UNIT = SKIN_BLUR_TEXTURE_UNIT + 1;
214
236
  /**
215
237
  * A cookie's tile, in texels a side.
216
238
  *
@@ -56,8 +56,15 @@ export declare class Mesh {
56
56
  readonly bounds: Bounds;
57
57
  /** Whether this mesh carries a rig, which decides which flat program draws it. */
58
58
  readonly isSkinned: boolean;
59
+ /**
60
+ * Whether its rig moves a vertex by eight influences rather than four, which decides whether the
61
+ * skinned program it takes is built with `SKIN_EIGHT` on. See `skinning.ts`.
62
+ */
63
+ readonly isSkinnedEight: boolean;
59
64
  /** Whether it has texture coordinates, without which a material's maps read one texel. */
60
65
  readonly hasUvs: boolean;
66
+ /** How many vertices it has, which a cloth binding is checked against. */
67
+ readonly vertexCount: number;
61
68
  /**
62
69
  * This mesh's morph deltas, or null for geometry that does not deform.
63
70
  *
@@ -48,6 +48,12 @@ const ATTR_JOINTS = 11;
48
48
  */
49
49
  const ATTR_CHANNEL = 13;
50
50
  const ATTR_WEIGHTS = 12;
51
+ /*
52
+ * The second four influences, at the last two locations: a skinned draw spends 11, 12 and 13 and an
53
+ * instanced one cannot skin, so 14 and 15 are free exactly where they are read. See skinning.ts.
54
+ */
55
+ const ATTR_JOINTS2 = 14;
56
+ const ATTR_WEIGHTS2 = 15;
51
57
  /**
52
58
  * One absent attribute's constant, at the width the table gave it.
53
59
  *
@@ -123,8 +129,15 @@ export class Mesh {
123
129
  bounds = createBounds();
124
130
  /** Whether this mesh carries a rig, which decides which flat program draws it. */
125
131
  isSkinned;
132
+ /**
133
+ * Whether its rig moves a vertex by eight influences rather than four, which decides whether the
134
+ * skinned program it takes is built with `SKIN_EIGHT` on. See `skinning.ts`.
135
+ */
136
+ isSkinnedEight;
126
137
  /** Whether it has texture coordinates, without which a material's maps read one texel. */
127
138
  hasUvs;
139
+ /** How many vertices it has, which a cloth binding is checked against. */
140
+ vertexCount;
128
141
  /**
129
142
  * This mesh's morph deltas, or null for geometry that does not deform.
130
143
  *
@@ -260,6 +273,7 @@ export class Mesh {
260
273
  * takes; see `WebGL2Renderer.drawMesh`.
261
274
  */
262
275
  this.isSkinned = data.joints !== undefined;
276
+ this.vertexCount = data.positions.length / 3;
263
277
  this.morph =
264
278
  data.morphTargets === undefined || data.morphTargetCount === undefined
265
279
  ? null
@@ -272,6 +286,16 @@ export class Mesh {
272
286
  this.attachAttribute(gl, ATTR_JOINTS, data.joints, 4);
273
287
  this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4);
274
288
  }
289
+ /* The fifth to eighth influences, or zeros, which add nothing. See `isSkinnedEight`. */
290
+ this.isSkinnedEight = this.isSkinned && data.joints2 !== undefined;
291
+ if (data.joints2 === undefined || data.weights2 === undefined) {
292
+ this.constants.push({ location: ATTR_JOINTS2, value: ABSENT_ATTRIBUTE['joints2'] });
293
+ this.constants.push({ location: ATTR_WEIGHTS2, value: ABSENT_ATTRIBUTE['weights2'] });
294
+ }
295
+ else {
296
+ this.attachAttribute(gl, ATTR_JOINTS2, data.joints2, 4);
297
+ this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4);
298
+ }
275
299
  this.hasTangents = data.tangents !== undefined;
276
300
  if (data.tangents === undefined) {
277
301
  gl.disableVertexAttribArray(ATTR_TANGENT);
@@ -42,9 +42,11 @@ export declare function cameraMotion(reprojection: ArrayLike<number>, u: number,
42
42
  */
43
43
  export declare function objectMotion(currentClip: ArrayLike<number>, previousClip: ArrayLike<number>, out: Float32Array): boolean;
44
44
  /**
45
- * A vertex through a skin palette: the four joints' matrices weighted and summed, then applied —
45
+ * A vertex through a skin palette: the joints' matrices weighted and summed, then applied —
46
46
  * `SKINNING_GLSL`'s `skinMatrix()` times the position, in model space, before the model matrix.
47
- * `palette` is sixteen floats a joint, column-major. Written into `out`, four values.
47
+ * `palette` is sixteen floats a joint, column-major. `joints` and `weights` hold four influences, or
48
+ * eight for a mesh with a second set (the first set then the second, as the shader reads them with
49
+ * `SKIN_EIGHT` on). Written into `out`, four values.
48
50
  */
49
51
  export declare function skinVertex(palette: ArrayLike<number>, joints: ArrayLike<number>, weights: ArrayLike<number>, position: ArrayLike<number>, out: Float64Array): void;
50
52
  /**
@@ -68,9 +68,11 @@ export function objectMotion(currentClip, previousClip, out) {
68
68
  return true;
69
69
  }
70
70
  /**
71
- * A vertex through a skin palette: the four joints' matrices weighted and summed, then applied —
71
+ * A vertex through a skin palette: the joints' matrices weighted and summed, then applied —
72
72
  * `SKINNING_GLSL`'s `skinMatrix()` times the position, in model space, before the model matrix.
73
- * `palette` is sixteen floats a joint, column-major. Written into `out`, four values.
73
+ * `palette` is sixteen floats a joint, column-major. `joints` and `weights` hold four influences, or
74
+ * eight for a mesh with a second set (the first set then the second, as the shader reads them with
75
+ * `SKIN_EIGHT` on). Written into `out`, four values.
74
76
  */
75
77
  export function skinVertex(palette, joints, weights, position, out) {
76
78
  const px = position[0];
@@ -80,7 +82,8 @@ export function skinVertex(palette, joints, weights, position, out) {
80
82
  out[1] = 0;
81
83
  out[2] = 0;
82
84
  out[3] = 0;
83
- for (let influence = 0; influence < 4; influence += 1) {
85
+ const influences = Math.min(joints.length, weights.length, 8);
86
+ for (let influence = 0; influence < influences; influence += 1) {
84
87
  const weight = weights[influence];
85
88
  if (weight === 0)
86
89
  continue;
@@ -37,6 +37,8 @@ export type DirectionalShadowDepthLayers = 1 | 2;
37
37
  export type WaterReflectionFilterTaps = 1 | 5 | 9;
38
38
  /** How light through glass is coloured: see `RenderQuality.glassShadows`. */
39
39
  export type GlassShadows = 'full' | 'half' | 'off';
40
+ /** How light scattered beneath skin is drawn: see `RenderQuality.skinScattering`. */
41
+ export type SkinScattering = 'pre-integrated' | 'screen-space';
40
42
  export interface RenderQuality {
41
43
  /**
42
44
  * The transfer curve applied at the end of every shaded pass.
@@ -201,6 +203,24 @@ export interface RenderQuality {
201
203
  * lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
202
204
  */
203
205
  readonly glassShadows: GlassShadows;
206
+ /**
207
+ * How light that scatters beneath skin is drawn, for a material whose model is `skinModel`.
208
+ *
209
+ * **`'pre-integrated'`, the default**: the lit stage's fit alone — a terminator softened and
210
+ * reddened by the surface's curvature, light through thin parts, a penumbra each channel crosses
211
+ * as far as it travels — at no cost beyond the skin pipeline itself. **`'screen-space'`** adds
212
+ * Burley's blur in the picture as well: a skin draw writes its diffuse to a target of its own (it
213
+ * is drawn twice to do so), which is spread across the pixels its scatter distance covers and
214
+ * added back before anything blended is drawn, so light crosses a shadow's edge and a nostril's
215
+ * rim in the picture rather than in a fit. See `skinBlur.ts`.
216
+ *
217
+ * Construction-time: two half-float targets the size of the frame, and a multisampled one under
218
+ * multisampling, made at the first skin draw. It needs the composite (`screenEffects`), which is
219
+ * where the frame is still linear light to add to; without one, skin is pre-integrated and the
220
+ * renderer says so once. What would make the default wrong is a close-up face that reads as wax
221
+ * at a shadow's edge — that is what the blur is for.
222
+ */
223
+ readonly skinScattering: SkinScattering;
204
224
  /** Horizontal world-space reach over which a directional shadow dissolves. */
205
225
  readonly directionalShadowMaxDistance: number;
206
226
  /** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
@@ -63,6 +63,7 @@ export const DEFAULT_RENDER_QUALITY = Object.freeze({
63
63
  lightVolumeSamples: 32,
64
64
  directionalShadowDepthLayers: 2,
65
65
  glassShadows: 'full',
66
+ skinScattering: 'pre-integrated',
66
67
  directionalShadowMaxDistance: 6,
67
68
  directionalShadowMaxSlope: 3,
68
69
  pointShadowRebakeDistance: 0.001,
@@ -215,6 +216,7 @@ export function resolveRenderQuality(options = {}) {
215
216
  lightVolumeSamples: options.lightVolumeSamples ?? DEFAULT_RENDER_QUALITY.lightVolumeSamples,
216
217
  directionalShadowDepthLayers: options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
217
218
  glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
219
+ skinScattering: options.skinScattering ?? DEFAULT_RENDER_QUALITY.skinScattering,
218
220
  directionalShadowMaxDistance: options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
219
221
  directionalShadowMaxSlope: options.directionalShadowMaxSlope ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxSlope,
220
222
  pointShadowRebakeDistance: options.pointShadowRebakeDistance ?? DEFAULT_RENDER_QUALITY.pointShadowRebakeDistance,
@@ -312,6 +314,9 @@ export function resolveRenderQuality(options = {}) {
312
314
  quality.glassShadows !== 'off') {
313
315
  throw new Error(`RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`);
314
316
  }
317
+ if (quality.skinScattering !== 'pre-integrated' && quality.skinScattering !== 'screen-space') {
318
+ throw new Error(`RenderQuality.skinScattering must be 'pre-integrated' or 'screen-space', got ${String(quality.skinScattering)}`);
319
+ }
315
320
  if (quality.plumeNoiseOctaves > 3) {
316
321
  throw new Error(`RenderQuality.plumeNoiseOctaves must be <= 3, got ${quality.plumeNoiseOctaves}`);
317
322
  }
@@ -61,6 +61,10 @@ export function recordingGl(options = {}) {
61
61
  NONE: 0,
62
62
  ALWAYS: 0x0207,
63
63
  LESS: 0x0201,
64
+ EQUAL: 0x0202,
65
+ GREATER: 0x0204,
66
+ GEQUAL: 0x0206,
67
+ LEQUAL: 0x0203,
64
68
  TEXTURE_MIN_FILTER: 0x2801,
65
69
  TEXTURE_MAG_FILTER: 0x2800,
66
70
  TEXTURE_WRAP_S: 0x2802,
@@ -195,6 +195,18 @@ export declare class SceneTarget {
195
195
  * must reject it in the same place or the accumulation and the revealage cover different
196
196
  * fragments.
197
197
  */
198
+ /**
199
+ * The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
200
+ * which tests against it and writes none. The multisampled renderbuffer where the frame is
201
+ * multisampled, the texture otherwise, with the size and sample count a colour beside it needs.
202
+ */
203
+ sharedDepth(): {
204
+ texture: WebGLTexture | null;
205
+ renderbuffer: WebGLRenderbuffer | null;
206
+ samples: number;
207
+ width: number;
208
+ height: number;
209
+ };
198
210
  depthAttachment(): WebGLTexture | null;
199
211
  /**
200
212
  * The colour the frame has drawn so far, for a pass that reads the picture rather than the depth.
@@ -432,6 +432,20 @@ export class SceneTarget {
432
432
  * must reject it in the same place or the accumulation and the revealage cover different
433
433
  * fragments.
434
434
  */
435
+ /**
436
+ * The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
437
+ * which tests against it and writes none. The multisampled renderbuffer where the frame is
438
+ * multisampled, the texture otherwise, with the size and sample count a colour beside it needs.
439
+ */
440
+ sharedDepth() {
441
+ return {
442
+ texture: this.msaaDepth === null ? this.depth : null,
443
+ renderbuffer: this.msaaDepth,
444
+ samples: this.samples,
445
+ width: this.width,
446
+ height: this.height,
447
+ };
448
+ }
435
449
  depthAttachment() {
436
450
  return this.depth;
437
451
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The vertex-stage cloth binding: a skinned vertex moved toward the cloth simulation's triangle it
3
+ * is bound to, by a weight painted per vertex — the GLSL of `clothPlacement.ts`, line for line.
4
+ *
5
+ * **Textures read by vertex index, not attributes**, for the reason morph targets are: the skinned
6
+ * variants already use all sixteen attribute locations once eight influences are in. Three
7
+ * textures, each wrapped onto rows at its own width (read back with `textureSize`, so no uniform
8
+ * joins the vertex block and no shared field moves):
9
+ *
10
+ * - **the binding**, two texels a render vertex: the three particle indices and the weight, then
11
+ * the barycentric (u, v) for the second and third particle and the offset along the normal;
12
+ * - **the particles now**, one texel a particle, world space — the simulation's output;
13
+ * - **the particles at rest**, one texel a particle, model space — the rest frames are built from
14
+ * these, so a vertex's own bind-pose normal turns as its triangle has turned.
15
+ *
16
+ * **A pipeline constant, not a permutation**, as `SKIN_EIGHT` is: declared in the skinned variants
17
+ * and folded away where a draw binds no cloth. The textures are bound either way, to stand-ins
18
+ * where there is no cloth.
19
+ */
20
+ export declare const CLOTH_BINDING_GLSL = "\n/** Whether this pipeline places vertices by a cloth binding. */\nconst bool CLOTH_BOUND = false; // wgsl:override\nuniform highp sampler2D uClothBinding;\nuniform highp sampler2D uClothParticles;\nuniform highp sampler2D uClothRest;\n\n/** Texel i of a texture wrapped onto rows of the given width. */\nivec2 clothTexel(int i, int width) {\n return ivec2(i % width, i / width);\n}\n\n/** A triangle's frame: its first edge, the normal crossed with it, and its normal. */\nmat3 clothFrame(vec3 a, vec3 b, vec3 c) {\n vec3 edge = b - a;\n vec3 n = normalize(cross(edge, c - a));\n vec3 t = normalize(edge);\n return mat3(t, cross(n, t), n);\n}\n\n/**\n * Where this vertex's triangle puts it, the turn its normal takes, and how far it follows (the\n * painted weight: 0 skinned, 1 cloth). One particle named three times is a vertex bound to that\n * particle alone: on it, with no turn.\n */\nfloat clothPlace(out vec3 position, out mat3 turn) {\n int bindingWidth = textureSize(uClothBinding, 0).x;\n int particleWidth = textureSize(uClothParticles, 0).x;\n int restWidth = textureSize(uClothRest, 0).x;\n vec4 indices = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2, bindingWidth), 0);\n vec4 at = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2 + 1, bindingWidth), 0);\n int a = int(indices.x);\n int b = int(indices.y);\n int c = int(indices.z);\n vec3 pa = texelFetch(uClothParticles, clothTexel(a, particleWidth), 0).xyz;\n if (a == b && b == c) {\n position = pa;\n turn = mat3(1.0);\n return indices.w;\n }\n vec3 pb = texelFetch(uClothParticles, clothTexel(b, particleWidth), 0).xyz;\n vec3 pc = texelFetch(uClothParticles, clothTexel(c, particleWidth), 0).xyz;\n mat3 now = clothFrame(pa, pb, pc);\n mat3 rest = clothFrame(\n texelFetch(uClothRest, clothTexel(a, restWidth), 0).xyz,\n texelFetch(uClothRest, clothTexel(b, restWidth), 0).xyz,\n texelFetch(uClothRest, clothTexel(c, restWidth), 0).xyz\n );\n position = pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + now[2] * at.z;\n turn = now * transpose(rest);\n return indices.w;\n}\n";
21
+ /** A skinned vertex stage with the cloth binding on: the same source, the constant flipped. */
22
+ export declare function clothBound(source: string): string;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The vertex-stage cloth binding: a skinned vertex moved toward the cloth simulation's triangle it
3
+ * is bound to, by a weight painted per vertex — the GLSL of `clothPlacement.ts`, line for line.
4
+ *
5
+ * **Textures read by vertex index, not attributes**, for the reason morph targets are: the skinned
6
+ * variants already use all sixteen attribute locations once eight influences are in. Three
7
+ * textures, each wrapped onto rows at its own width (read back with `textureSize`, so no uniform
8
+ * joins the vertex block and no shared field moves):
9
+ *
10
+ * - **the binding**, two texels a render vertex: the three particle indices and the weight, then
11
+ * the barycentric (u, v) for the second and third particle and the offset along the normal;
12
+ * - **the particles now**, one texel a particle, world space — the simulation's output;
13
+ * - **the particles at rest**, one texel a particle, model space — the rest frames are built from
14
+ * these, so a vertex's own bind-pose normal turns as its triangle has turned.
15
+ *
16
+ * **A pipeline constant, not a permutation**, as `SKIN_EIGHT` is: declared in the skinned variants
17
+ * and folded away where a draw binds no cloth. The textures are bound either way, to stand-ins
18
+ * where there is no cloth.
19
+ */
20
+ export const CLOTH_BINDING_GLSL = `
21
+ /** Whether this pipeline places vertices by a cloth binding. */
22
+ const bool CLOTH_BOUND = false; // wgsl:override
23
+ uniform highp sampler2D uClothBinding;
24
+ uniform highp sampler2D uClothParticles;
25
+ uniform highp sampler2D uClothRest;
26
+
27
+ /** Texel i of a texture wrapped onto rows of the given width. */
28
+ ivec2 clothTexel(int i, int width) {
29
+ return ivec2(i % width, i / width);
30
+ }
31
+
32
+ /** A triangle's frame: its first edge, the normal crossed with it, and its normal. */
33
+ mat3 clothFrame(vec3 a, vec3 b, vec3 c) {
34
+ vec3 edge = b - a;
35
+ vec3 n = normalize(cross(edge, c - a));
36
+ vec3 t = normalize(edge);
37
+ return mat3(t, cross(n, t), n);
38
+ }
39
+
40
+ /**
41
+ * Where this vertex's triangle puts it, the turn its normal takes, and how far it follows (the
42
+ * painted weight: 0 skinned, 1 cloth). One particle named three times is a vertex bound to that
43
+ * particle alone: on it, with no turn.
44
+ */
45
+ float clothPlace(out vec3 position, out mat3 turn) {
46
+ int bindingWidth = textureSize(uClothBinding, 0).x;
47
+ int particleWidth = textureSize(uClothParticles, 0).x;
48
+ int restWidth = textureSize(uClothRest, 0).x;
49
+ vec4 indices = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2, bindingWidth), 0);
50
+ vec4 at = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2 + 1, bindingWidth), 0);
51
+ int a = int(indices.x);
52
+ int b = int(indices.y);
53
+ int c = int(indices.z);
54
+ vec3 pa = texelFetch(uClothParticles, clothTexel(a, particleWidth), 0).xyz;
55
+ if (a == b && b == c) {
56
+ position = pa;
57
+ turn = mat3(1.0);
58
+ return indices.w;
59
+ }
60
+ vec3 pb = texelFetch(uClothParticles, clothTexel(b, particleWidth), 0).xyz;
61
+ vec3 pc = texelFetch(uClothParticles, clothTexel(c, particleWidth), 0).xyz;
62
+ mat3 now = clothFrame(pa, pb, pc);
63
+ mat3 rest = clothFrame(
64
+ texelFetch(uClothRest, clothTexel(a, restWidth), 0).xyz,
65
+ texelFetch(uClothRest, clothTexel(b, restWidth), 0).xyz,
66
+ texelFetch(uClothRest, clothTexel(c, restWidth), 0).xyz
67
+ );
68
+ position = pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + now[2] * at.z;
69
+ turn = now * transpose(rest);
70
+ return indices.w;
71
+ }
72
+ `;
73
+ /** A skinned vertex stage with the cloth binding on: the same source, the constant flipped. */
74
+ export function clothBound(source) {
75
+ const off = 'const bool CLOTH_BOUND = false;';
76
+ if (!source.includes(off)) {
77
+ throw new Error('clothBound: this vertex stage declares no CLOTH_BOUND constant');
78
+ }
79
+ return source.replace(off, 'const bool CLOTH_BOUND = true;');
80
+ }
@@ -1,7 +1,9 @@
1
1
  import { resolveConditionals } from './conditionals.js';
2
2
  import { SUN_STATIC_LAYER } from '../shadowMap.js';
3
3
  import { CUTOUT_COVERAGE_GLSL } from './cutoutCoverage.js';
4
+ import { CUTOUT_DITHER_GLSL } from '../cutoutDither.js';
4
5
  import { SKINNING_GLSL } from './skinning.js';
6
+ import { CLOTH_BINDING_GLSL } from './clothBinding.js';
5
7
  import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from './vertexChannel.js';
6
8
  /**
7
9
  * Depth-only shadow pass. Its optional peel mode rejects the first recorded
@@ -20,6 +22,11 @@ const DEPTH_VERT_SOURCE = `#version 300 es
20
22
  layout(location = 0) in vec3 aPosition;
21
23
  #if SKINNED
22
24
  ${SKINNING_GLSL}
25
+ /* Not in the glass-tint caster: a garment is not glass, and its layout keeps its bindings. */
26
+ #if GLASS
27
+ #else
28
+ ${CLOTH_BINDING_GLSL}
29
+ #endif
23
30
  #endif
24
31
 
25
32
  uniform mat4 uLightViewProj;
@@ -66,14 +73,15 @@ ${CHANNEL_BEND}
66
73
  layout(location = 5) in vec3 aUv;
67
74
  uniform vec2 uUvScale;
68
75
  uniform vec2 uUvOffset;
69
- uniform float uAlphaCutout;
76
+ /* x the cutoff, y 1 for a dithered edge: see the cutout fragment stages below. */
77
+ uniform vec2 uAlphaCutout;
70
78
  #endif
71
79
 
72
80
  out vec4 vLightPosition;
73
81
  #if CUTOUT
74
82
  /* The texture coordinate and, in z, the texture-array layer the face wears. */
75
83
  out vec3 vUv;
76
- flat out float vAlphaCutout;
84
+ flat out vec2 vAlphaCutout;
77
85
  #endif
78
86
  #if GLASS
79
87
  /* Where on the pane this is, for the tint's own normal and its angle to the light. Last, so the
@@ -100,6 +108,18 @@ void main() {
100
108
  vec3 bent = world.xyz;
101
109
  #else
102
110
  vec3 bent = channelBend(world.xyz, aChannel.x);
111
+ #endif
112
+ #if SKINNED
113
+ #if GLASS
114
+ #else
115
+ /* Toward the cloth exactly as the colour pass moves it, or the shadow parts from the garment. */
116
+ if (CLOTH_BOUND) {
117
+ vec3 clothPosition;
118
+ mat3 turn;
119
+ float follow = clothPlace(clothPosition, turn);
120
+ bent = mix(bent, clothPosition, follow);
121
+ }
122
+ #endif
103
123
  #endif
104
124
  vLightPosition = uLightViewProj * vec4(bent, world.w);
105
125
  gl_Position = vLightPosition;
@@ -171,12 +191,13 @@ export const DEPTH_CUTOUT_FRAG = `#version 300 es
171
191
  precision highp float;
172
192
  in vec4 vLightPosition;
173
193
  in vec3 vUv;
174
- flat in float vAlphaCutout;
194
+ flat in vec2 vAlphaCutout;
175
195
 
176
196
  uniform highp sampler2DArray uPreviousShadowMap;
177
197
  uniform int uPeelShadowLayer;
178
198
  uniform highp sampler2DArray uCutoutMap;
179
199
  ${CUTOUT_COVERAGE_GLSL}
200
+ ${CUTOUT_DITHER_GLSL}
180
201
  void main() {
181
202
  /* Credited for its mip level as the surface's own test is, so a leaf casts the shape it draws. */
182
203
  vec3 at = vec3(vUv.xy, floor(vUv.z + 0.5));
@@ -187,7 +208,16 @@ void main() {
187
208
  float previousDepth = textureLod(uPreviousShadowMap, vec3(uv, ${SUN_STATIC_LAYER}.0), 0.0).r;
188
209
  if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
189
210
  }
190
- if (alpha < vAlphaCutout) discard;
211
+ /*
212
+ * A dithered edge casts the share it covers, with a pattern that does not move: a shadow map has
213
+ * no temporal resolve of its own and a static layer keeps whatever it was drawn with, so a moving
214
+ * pattern would shimmer there. The map's filter averages it into a soft edge.
215
+ */
216
+ if (vAlphaCutout.y > 0.5) {
217
+ if (!cutoutKeeps(cutoutShare(alpha, vAlphaCutout.x), gl_FragCoord.xy, 0.0)) discard;
218
+ } else if (alpha < vAlphaCutout.x) {
219
+ discard;
220
+ }
191
221
  }
192
222
  `;
193
223
  /**
@@ -241,13 +271,14 @@ export const GLASS_TINT_CUTOUT_FRAG = `#version 300 es
241
271
  precision highp float;
242
272
  in vec4 vLightPosition;
243
273
  in vec3 vUv;
244
- flat in float vAlphaCutout;
274
+ flat in vec2 vAlphaCutout;
245
275
  in vec3 vGlassWorld;
246
276
 
247
277
  uniform vec4 uGlassPane;
248
278
  uniform vec4 uGlassLight;
249
279
  uniform highp sampler2DArray uCutoutMap;
250
280
  ${CUTOUT_COVERAGE_GLSL}
281
+ ${CUTOUT_DITHER_GLSL}
251
282
  out vec4 outTint;
252
283
 
253
284
  void main() {
@@ -255,6 +286,11 @@ void main() {
255
286
  vec3 at = vec3(vUv.xy, floor(vUv.z + 0.5));
256
287
  float alpha = cutoutAlpha(texture(uCutoutMap, at).a, vUv.xy * vec2(textureSize(uCutoutMap, 0).xy));
257
288
  ${GLASS_TINT_BODY}
258
- if (alpha < vAlphaCutout) discard;
289
+ /* The depth cutout's own test, so the colour lands exactly where the depth does. */
290
+ if (vAlphaCutout.y > 0.5) {
291
+ if (!cutoutKeeps(cutoutShare(alpha, vAlphaCutout.x), gl_FragCoord.xy, 0.0)) discard;
292
+ } else if (alpha < vAlphaCutout.x) {
293
+ discard;
294
+ }
259
295
  }
260
296
  `;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The anisotropic model in GLSL: GGX stretched along a direction in the surface, after glTF's
3
+ * `KHR_materials_anisotropy` — `anisotropicLobe.ts` is the same arithmetic, and its tests hold it to
4
+ * the standard lobe at strength 0 and to the widths' ratio at its half maximum.
5
+ *
6
+ * **The direction** is the tangent frame's, turned by the material's rotation, and by the model
7
+ * map's red and green where one is bound (a direction encoded −1 to 1, glTF's packing), its blue
8
+ * scaling the strength. **The environment** is reflected about a normal bent toward the stretch, as
9
+ * Filament and three.js do: a streak of sky across a brushed disc rather than a round spot, at the
10
+ * cost of a reflection that is not the exact anisotropic integral.
11
+ *
12
+ * Lamps widen both of the lobe's widths by the source's angle, as `sphereLobe` widens the standard
13
+ * lobe, with the energy kept; at strength 0 that is `sphereLobe` exactly.
14
+ */
15
+ export declare const ANISOTROPIC_GLSL = "\n/* The stretch's frame and strength, gathered once with the surface. */\nvec3 aAlong;\nvec3 aAcross;\nfloat aStrength;\n\nvoid anisotropicSurface() {\n vec2 direction = vec2(1.0, 0.0);\n float strength = uModelParams[0].x;\n if (uModelParams[1].w > 0.0) {\n vec2 mapped = mMap.rg * 2.0 - 1.0;\n direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;\n strength *= mMap.b;\n }\n vec2 turned = vec2(\n direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,\n direction.x * uModelParams[0].z + direction.y * uModelParams[0].y\n );\n aAlong = normalize(mTangent * turned.x + mBitangent * turned.y);\n aAcross = cross(mNormal, aAlong);\n aStrength = strength;\n}\n\n/** The stretched lobe at a half vector, 1 at the mirror direction: anisotropicLobe.ts. */\nfloat anisotropicLobe(vec3 h, float sourceRadius, float dist) {\n float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);\n float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;\n float grow = sourceRadius / max(2.0 * dist, 1e-3);\n float alongWide = clamp(alongWidth + grow, alongWidth, 1.0);\n float acrossWide = clamp(alpha + grow, alpha, 1.0);\n float energy = (alongWidth / alongWide) * (alpha / acrossWide);\n float x = dot(aAlong, h) / alongWide;\n float y = dot(aAcross, h) / acrossWide;\n float z = dot(mNormal, h);\n float d = x * x + y * y + z * z;\n return energy / max(d * d, 1e-8);\n}\n\nvoid anisotropicLight(vec3 l, float sourceRadius, float dist) {\n float ndl = max(dot(mNormal, l), 0.0);\n vec3 h = normalize(l + mToEye);\n vec3 tint = mix(mSpecColor, vec3(1.0), pow(1.0 - max(dot(mToEye, h), 0.0), 5.0) * mMetal);\n mDiffuse = mAlbedo * ndl * (1.0 - mMetal);\n mSpecular = ndl > 0.0 ? tint * anisotropicLobe(h, sourceRadius, dist) : vec3(0.0);\n}\n\n/* The environment's normal, bent toward the stretch by its strength and the surface's roughness. */\nvec3 anisotropicReflectNormal(vec3 n) {\n vec3 across = cross(aAcross, mToEye);\n vec3 bent = cross(across, aAcross);\n float bend = aStrength * clamp(5.0 * mRoughness, 0.0, 1.0);\n return normalize(mix(n, bent, bend));\n}\n";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The anisotropic model in GLSL: GGX stretched along a direction in the surface, after glTF's
3
+ * `KHR_materials_anisotropy` — `anisotropicLobe.ts` is the same arithmetic, and its tests hold it to
4
+ * the standard lobe at strength 0 and to the widths' ratio at its half maximum.
5
+ *
6
+ * **The direction** is the tangent frame's, turned by the material's rotation, and by the model
7
+ * map's red and green where one is bound (a direction encoded −1 to 1, glTF's packing), its blue
8
+ * scaling the strength. **The environment** is reflected about a normal bent toward the stretch, as
9
+ * Filament and three.js do: a streak of sky across a brushed disc rather than a round spot, at the
10
+ * cost of a reflection that is not the exact anisotropic integral.
11
+ *
12
+ * Lamps widen both of the lobe's widths by the source's angle, as `sphereLobe` widens the standard
13
+ * lobe, with the energy kept; at strength 0 that is `sphereLobe` exactly.
14
+ */
15
+ export const ANISOTROPIC_GLSL = /* glsl */ `
16
+ /* The stretch's frame and strength, gathered once with the surface. */
17
+ vec3 aAlong;
18
+ vec3 aAcross;
19
+ float aStrength;
20
+
21
+ void anisotropicSurface() {
22
+ vec2 direction = vec2(1.0, 0.0);
23
+ float strength = uModelParams[0].x;
24
+ if (uModelParams[1].w > 0.0) {
25
+ vec2 mapped = mMap.rg * 2.0 - 1.0;
26
+ direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;
27
+ strength *= mMap.b;
28
+ }
29
+ vec2 turned = vec2(
30
+ direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,
31
+ direction.x * uModelParams[0].z + direction.y * uModelParams[0].y
32
+ );
33
+ aAlong = normalize(mTangent * turned.x + mBitangent * turned.y);
34
+ aAcross = cross(mNormal, aAlong);
35
+ aStrength = strength;
36
+ }
37
+
38
+ /** The stretched lobe at a half vector, 1 at the mirror direction: anisotropicLobe.ts. */
39
+ float anisotropicLobe(vec3 h, float sourceRadius, float dist) {
40
+ float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);
41
+ float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;
42
+ float grow = sourceRadius / max(2.0 * dist, 1e-3);
43
+ float alongWide = clamp(alongWidth + grow, alongWidth, 1.0);
44
+ float acrossWide = clamp(alpha + grow, alpha, 1.0);
45
+ float energy = (alongWidth / alongWide) * (alpha / acrossWide);
46
+ float x = dot(aAlong, h) / alongWide;
47
+ float y = dot(aAcross, h) / acrossWide;
48
+ float z = dot(mNormal, h);
49
+ float d = x * x + y * y + z * z;
50
+ return energy / max(d * d, 1e-8);
51
+ }
52
+
53
+ void anisotropicLight(vec3 l, float sourceRadius, float dist) {
54
+ float ndl = max(dot(mNormal, l), 0.0);
55
+ vec3 h = normalize(l + mToEye);
56
+ vec3 tint = mix(mSpecColor, vec3(1.0), pow(1.0 - max(dot(mToEye, h), 0.0), 5.0) * mMetal);
57
+ mDiffuse = mAlbedo * ndl * (1.0 - mMetal);
58
+ mSpecular = ndl > 0.0 ? tint * anisotropicLobe(h, sourceRadius, dist) : vec3(0.0);
59
+ }
60
+
61
+ /* The environment's normal, bent toward the stretch by its strength and the surface's roughness. */
62
+ vec3 anisotropicReflectNormal(vec3 n) {
63
+ vec3 across = cross(aAcross, mToEye);
64
+ vec3 bent = cross(across, aAcross);
65
+ float bend = aStrength * clamp(5.0 * mRoughness, 0.0, 1.0);
66
+ return normalize(mix(n, bent, bend));
67
+ }
68
+ `;
@@ -0,0 +1 @@
1
+ export declare const EYE_GLSL: string;