@driftengine/core 4.8.2 → 4.8.4

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 (259) hide show
  1. package/CHANGELOG.json +127 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/meshConcat.js +2 -0
  4. package/dist/index.d.ts +11 -4
  5. package/dist/index.js +22 -1
  6. package/dist/math/scalar.js +1 -1
  7. package/dist/render/anisotropicLobe.d.ts +7 -0
  8. package/dist/render/anisotropicLobe.js +33 -0
  9. package/dist/render/backend/api.d.ts +41 -4
  10. package/dist/render/backend/materialChanges.d.ts +7 -2
  11. package/dist/render/backend/materialChanges.js +8 -3
  12. package/dist/render/backend/select.js +8 -0
  13. package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
  14. package/dist/render/backend/webgl2/clothTextures.js +93 -0
  15. package/dist/render/backend/webgl2/renderer.d.ts +159 -3
  16. package/dist/render/backend/webgl2/renderer.js +662 -80
  17. package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
  18. package/dist/render/backend/webgl2/skinScatter.js +256 -0
  19. package/dist/render/backend/webgpu/buffers.d.ts +25 -11
  20. package/dist/render/backend/webgpu/buffers.js +98 -49
  21. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  22. package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
  23. package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
  24. package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
  25. package/dist/render/backend/webgpu/clothTextures.js +145 -0
  26. package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
  27. package/dist/render/backend/webgpu/compressedUpload.js +38 -0
  28. package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
  29. package/dist/render/backend/webgpu/computeHost.js +5 -0
  30. package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
  31. package/dist/render/backend/webgpu/depthPass.js +43 -12
  32. package/dist/render/backend/webgpu/drawCommand.js +6 -2
  33. package/dist/render/backend/webgpu/filmPass.js +2 -1
  34. package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
  35. package/dist/render/backend/webgpu/flatPass.js +111 -13
  36. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
  37. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
  38. package/dist/render/backend/webgpu/linePass.js +2 -1
  39. package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
  40. package/dist/render/backend/webgpu/motionPass.js +106 -9
  41. package/dist/render/backend/webgpu/panelPass.js +2 -1
  42. package/dist/render/backend/webgpu/particlePass.js +2 -1
  43. package/dist/render/backend/webgpu/plumePass.js +2 -1
  44. package/dist/render/backend/webgpu/postPass.d.ts +15 -3
  45. package/dist/render/backend/webgpu/postPass.js +25 -8
  46. package/dist/render/backend/webgpu/renderer.d.ts +123 -6
  47. package/dist/render/backend/webgpu/renderer.js +534 -58
  48. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  49. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
  50. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
  51. package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
  52. package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
  53. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  54. package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
  55. package/dist/render/backend/webgpu/textPass.js +2 -1
  56. package/dist/render/backend/webgpu/waterPass.js +2 -1
  57. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  58. package/dist/render/boltBatch.js +2 -1
  59. package/dist/render/causticsRenderer.js +2 -1
  60. package/dist/render/clothBindingData.d.ts +41 -0
  61. package/dist/render/clothBindingData.js +72 -0
  62. package/dist/render/clothPlacement.d.ts +25 -0
  63. package/dist/render/clothPlacement.js +85 -0
  64. package/dist/render/compressedSource.d.ts +66 -0
  65. package/dist/render/compressedSource.js +138 -0
  66. package/dist/render/cutoutCaster.d.ts +5 -1
  67. package/dist/render/cutoutCaster.js +6 -0
  68. package/dist/render/cutoutDither.d.ts +51 -0
  69. package/dist/render/cutoutDither.js +90 -0
  70. package/dist/render/decalPass.js +2 -1
  71. package/dist/render/eyeRefraction.d.ts +35 -0
  72. package/dist/render/eyeRefraction.js +40 -0
  73. package/dist/render/glCompressed.d.ts +27 -0
  74. package/dist/render/glCompressed.js +53 -0
  75. package/dist/render/globalMediumPass.js +3 -2
  76. package/dist/render/hairLobes.d.ts +70 -0
  77. package/dist/render/hairLobes.js +120 -0
  78. package/dist/render/lightBudget.d.ts +22 -0
  79. package/dist/render/lightBudget.js +22 -0
  80. package/dist/render/lineBatch.js +5 -1
  81. package/dist/render/mesh.d.ts +7 -0
  82. package/dist/render/mesh.js +24 -0
  83. package/dist/render/oitPass.js +2 -1
  84. package/dist/render/particleBatch.js +3 -2
  85. package/dist/render/particlePool.d.ts +6 -2
  86. package/dist/render/plumeRenderer.js +3 -2
  87. package/dist/render/recon/motionVectors.d.ts +4 -2
  88. package/dist/render/recon/motionVectors.js +6 -3
  89. package/dist/render/renderQuality.d.ts +29 -2
  90. package/dist/render/renderQuality.js +5 -0
  91. package/dist/render/rendererHarness.js +4 -0
  92. package/dist/render/sceneCoverage.d.ts +54 -0
  93. package/dist/render/sceneCoverage.js +72 -0
  94. package/dist/render/sceneTarget.d.ts +12 -0
  95. package/dist/render/sceneTarget.js +14 -0
  96. package/dist/render/sdfTextRenderer.js +2 -1
  97. package/dist/render/shaders/clothBinding.d.ts +22 -0
  98. package/dist/render/shaders/clothBinding.js +80 -0
  99. package/dist/render/shaders/depth.js +45 -8
  100. package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
  101. package/dist/render/shaders/flat/anisotropic.js +68 -0
  102. package/dist/render/shaders/flat/eye.d.ts +1 -0
  103. package/dist/render/shaders/flat/eye.js +81 -0
  104. package/dist/render/shaders/flat/hair.d.ts +1 -0
  105. package/dist/render/shaders/flat/hair.js +101 -0
  106. package/dist/render/shaders/flat/index.js +42 -3
  107. package/dist/render/shaders/flat/main.d.ts +1 -1
  108. package/dist/render/shaders/flat/main.js +113 -17
  109. package/dist/render/shaders/flat/models.d.ts +14 -0
  110. package/dist/render/shaders/flat/models.js +273 -0
  111. package/dist/render/shaders/flat/preamble.js +26 -24
  112. package/dist/render/shaders/flat/skin.d.ts +1 -0
  113. package/dist/render/shaders/flat/skin.js +114 -0
  114. package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
  115. package/dist/render/shaders/generated/depth.wgsl.js +66 -24
  116. package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
  117. package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
  118. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  121. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  122. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
  127. package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
  128. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  130. package/dist/render/shaders/line.d.ts +1 -1
  131. package/dist/render/shaders/outputTransform.d.ts +14 -1
  132. package/dist/render/shaders/outputTransform.js +23 -1
  133. package/dist/render/shaders/particle.d.ts +3 -3
  134. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  135. package/dist/render/shaders/recon/motion.wgsl.js +102 -0
  136. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  137. package/dist/render/shaders/rush.js +13 -2
  138. package/dist/render/shaders/sdfText.d.ts +1 -1
  139. package/dist/render/shaders/skinBlur.d.ts +1 -0
  140. package/dist/render/shaders/skinBlur.js +103 -0
  141. package/dist/render/shaders/skinning.d.ts +3 -1
  142. package/dist/render/shaders/skinning.js +31 -4
  143. package/dist/render/shaders/temporalResolve.js +12 -5
  144. package/dist/render/shadowCasters.d.ts +13 -1
  145. package/dist/render/skinBlur.d.ts +41 -0
  146. package/dist/render/skinBlur.js +49 -0
  147. package/dist/render/skinScatter.d.ts +67 -0
  148. package/dist/render/skinScatter.js +80 -0
  149. package/dist/render/skinnedClothRun.d.ts +26 -0
  150. package/dist/render/skinnedClothRun.js +84 -0
  151. package/dist/render/ssrPass.js +2 -1
  152. package/dist/render/surfaceModel.d.ts +129 -0
  153. package/dist/render/surfaceModel.js +152 -0
  154. package/dist/render/surfaceTexture.d.ts +55 -4
  155. package/dist/render/surfaceTexture.js +60 -46
  156. package/dist/render/textRenderer.js +2 -1
  157. package/dist/render/vertexDefaults.js +8 -0
  158. package/dist/render/waterRenderer.js +2 -1
  159. package/dist/render/windStreakRenderer.js +2 -1
  160. package/package.json +6 -6
  161. package/src/geometry/meshConcat.ts +2 -0
  162. package/src/index.ts +53 -1
  163. package/src/math/scalar.ts +1 -1
  164. package/src/render/anisotropicLobe.ts +41 -0
  165. package/src/render/backend/api.ts +52 -3
  166. package/src/render/backend/materialChanges.ts +8 -3
  167. package/src/render/backend/select.ts +8 -0
  168. package/src/render/backend/webgl2/clothTextures.ts +120 -0
  169. package/src/render/backend/webgl2/renderer.ts +750 -80
  170. package/src/render/backend/webgl2/skinScatter.ts +309 -0
  171. package/src/render/backend/webgpu/buffers.ts +137 -63
  172. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  173. package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
  174. package/src/render/backend/webgpu/clothTextures.ts +194 -0
  175. package/src/render/backend/webgpu/compressedUpload.ts +65 -0
  176. package/src/render/backend/webgpu/computeHost.ts +25 -0
  177. package/src/render/backend/webgpu/depthPass.ts +43 -10
  178. package/src/render/backend/webgpu/drawCommand.ts +6 -2
  179. package/src/render/backend/webgpu/filmPass.ts +2 -1
  180. package/src/render/backend/webgpu/flatPass.ts +147 -8
  181. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
  182. package/src/render/backend/webgpu/linePass.ts +2 -1
  183. package/src/render/backend/webgpu/motionPass.ts +128 -9
  184. package/src/render/backend/webgpu/panelPass.ts +2 -1
  185. package/src/render/backend/webgpu/particlePass.ts +2 -1
  186. package/src/render/backend/webgpu/plumePass.ts +2 -1
  187. package/src/render/backend/webgpu/postPass.ts +25 -6
  188. package/src/render/backend/webgpu/renderer.ts +701 -52
  189. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  190. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
  191. package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
  192. package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
  193. package/src/render/backend/webgpu/textPass.ts +2 -1
  194. package/src/render/backend/webgpu/waterPass.ts +2 -1
  195. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  196. package/src/render/boltBatch.ts +2 -1
  197. package/src/render/causticsRenderer.ts +2 -1
  198. package/src/render/clothBindingData.ts +89 -0
  199. package/src/render/clothPlacement.ts +114 -0
  200. package/src/render/compressedSource.ts +198 -0
  201. package/src/render/cutoutCaster.ts +20 -2
  202. package/src/render/cutoutDither.ts +99 -0
  203. package/src/render/decalPass.ts +2 -1
  204. package/src/render/eyeRefraction.ts +72 -0
  205. package/src/render/glCompressed.ts +86 -0
  206. package/src/render/globalMediumPass.ts +3 -2
  207. package/src/render/hairLobes.ts +188 -0
  208. package/src/render/lightBudget.ts +25 -0
  209. package/src/render/lineBatch.ts +3 -1
  210. package/src/render/mesh.ts +23 -0
  211. package/src/render/oitPass.ts +2 -1
  212. package/src/render/particleBatch.ts +3 -2
  213. package/src/render/particlePool.ts +6 -2
  214. package/src/render/plumeRenderer.ts +3 -2
  215. package/src/render/recon/motionVectors.ts +6 -3
  216. package/src/render/renderQuality.ts +37 -2
  217. package/src/render/rendererHarness.ts +4 -0
  218. package/src/render/sceneCoverage.ts +78 -0
  219. package/src/render/sceneTarget.ts +21 -0
  220. package/src/render/sdfTextRenderer.ts +2 -1
  221. package/src/render/shaders/clothBinding.ts +81 -0
  222. package/src/render/shaders/depth.ts +45 -8
  223. package/src/render/shaders/flat/anisotropic.ts +68 -0
  224. package/src/render/shaders/flat/eye.ts +82 -0
  225. package/src/render/shaders/flat/hair.ts +102 -0
  226. package/src/render/shaders/flat/index.ts +42 -3
  227. package/src/render/shaders/flat/main.ts +113 -17
  228. package/src/render/shaders/flat/models.ts +286 -0
  229. package/src/render/shaders/flat/preamble.ts +26 -24
  230. package/src/render/shaders/flat/skin.ts +116 -0
  231. package/src/render/shaders/generated/depth.wgsl.ts +66 -24
  232. package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
  233. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  234. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  235. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
  238. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  239. package/src/render/shaders/outputTransform.ts +24 -1
  240. package/src/render/shaders/recon/motion.wgsl.ts +102 -0
  241. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  242. package/src/render/shaders/rush.ts +13 -2
  243. package/src/render/shaders/skinBlur.ts +105 -0
  244. package/src/render/shaders/skinning.ts +31 -4
  245. package/src/render/shaders/temporalResolve.ts +12 -5
  246. package/src/render/shadowCasters.ts +13 -0
  247. package/src/render/skinBlur.ts +53 -0
  248. package/src/render/skinScatter.ts +92 -0
  249. package/src/render/skinnedClothRun.ts +128 -0
  250. package/src/render/ssrPass.ts +2 -1
  251. package/src/render/surfaceModel.ts +274 -0
  252. package/src/render/surfaceTexture.ts +134 -63
  253. package/src/render/textRenderer.ts +2 -1
  254. package/src/render/vertexDefaults.ts +8 -0
  255. package/src/render/waterRenderer.ts +2 -1
  256. package/src/render/windStreakRenderer.ts +2 -1
  257. package/dist/math/exact.d.ts +0 -76
  258. package/dist/math/exact.js +0 -327
  259. package/src/math/exact.ts +0 -340
@@ -4,6 +4,7 @@ import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.js';
4
4
  import { compileProgram, uniformLocations } from './shader.js';
5
5
  import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
6
6
  import { MEDIUM_FRAG, MEDIUM_UPSAMPLE_FRAG } from './shaders/globalMedium.js';
7
+ import { blendCovering, blendTransmitting } from './sceneCoverage.js';
7
8
  /**
8
9
  * The global participating medium on WebGL2: a half-res march, then a depth-aware composite.
9
10
  *
@@ -200,7 +201,7 @@ export class GlobalMediumPass {
200
201
  gl.depthMask(false);
201
202
  gl.disable(gl.CULL_FACE);
202
203
  gl.enable(gl.BLEND);
203
- gl.blendFuncSeparate(gl.ONE, gl.SRC_ALPHA, gl.ZERO, gl.ONE);
204
+ blendTransmitting(gl, gl.ONE, gl.SRC_ALPHA);
204
205
  gl.useProgram(this.upsample);
205
206
  gl.bindVertexArray(this.vao);
206
207
  gl.activeTexture(gl.TEXTURE0);
@@ -220,7 +221,7 @@ export class GlobalMediumPass {
220
221
  gl.activeTexture(gl.TEXTURE0);
221
222
  gl.bindTexture(gl.TEXTURE_2D, null);
222
223
  /* Back to the blend every other pass in this renderer assumes it inherits. */
223
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
224
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
224
225
  gl.disable(gl.BLEND);
225
226
  gl.depthMask(true);
226
227
  gl.enable(gl.DEPTH_TEST);
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The hair model's lobes, as `shaders/flat/hair.ts` computes them: the reference its tests hold the
3
+ * arithmetic to, and nothing at run time calls it.
4
+ *
5
+ * **A strand is a rough dielectric cylinder, index 1.55, after Marschner et al. 2003 in the
6
+ * real-time form Karis gave in 2016.** Light leaves it three ways, each a longitudinal Gaussian in
7
+ * `sinθi + sinθo` — the angles of light and eye out of the plane normal to the strand — times an
8
+ * azimuthal term in φ, the turn between them around it:
9
+ *
10
+ * - **R**, reflected off the surface: white, and shifted toward the root by twice the tilt of the
11
+ * cuticle's scales, which point toward the tip;
12
+ * - **TT**, through the strand and out: the glow of a backlit head, shifted toward the tip by the
13
+ * tilt, half as wide, and tinted by the fibre for the path it took;
14
+ * - **TRT**, reflected once inside: the coloured second highlight, three tilts toward the tip and
15
+ * twice as wide;
16
+ *
17
+ * and a fourth, the light scattered through many strands, as a wrapped diffuse against a normal
18
+ * turned toward the eye across the strand, tinted by a root of the fibre's colour.
19
+ *
20
+ * **The tangent runs from root to tip**, and the tilt is positive for a strand authored that way. A
21
+ * card authored the other way round swaps its two highlights' ends, and takes a negative shift.
22
+ *
23
+ * **In this engine's light units.** A lamp's diffuse here is `albedo · cosθ` with no 1/π, so the
24
+ * published lobes, which are per unit irradiance, are multiplied by π; the scatter term's 1/π cancels.
25
+ * **What it gives up** is the fit's own: these are Karis's approximations to the azimuthal
26
+ * integrals, not the integrals, and they do not conserve energy exactly — the tests hold the sum
27
+ * under what a strand receives.
28
+ */
29
+ import type { Vec3 } from '../math/color.ts';
30
+ /** Schlick's base for a strand at index 1.55: `((1 − n) / (1 + n))²`. */
31
+ export declare const HAIR_F0: number;
32
+ /**
33
+ * The narrowest longitudinal width a strand is drawn at, in radians.
34
+ *
35
+ * **Below every measured fibre**: Marschner gives the reflected lobe 5° to 10°, and this is about
36
+ * 3°, so no real hair is clamped. What it stops is a roughness of 0 making a Gaussian narrower than
37
+ * a pixel can sample — the speckle `MIN_LOBE_ALPHA` argues against for the standard lobe.
38
+ */
39
+ export declare const HAIR_MIN_BETA = 0.05;
40
+ /**
41
+ * The scatter term's answer to light arriving evenly from every side, per unit of it: the term's
42
+ * shape integrated over the sphere and divided by π, which is how this engine's lamp units add up.
43
+ *
44
+ * Derived: the wrapped half `(N·L + 1) / 4` averages 1/4 over the sphere, `1 − |T·L|` averages 1/2,
45
+ * and the shape is `mix(wrapped, 1 − |T·L|, 0.33)`, so its mean is 0.67 · 0.25 + 0.33 · 0.5 = 0.3325
46
+ * and its integral 4π times that. Over π: **1.33**.
47
+ */
48
+ export declare const HAIR_SCATTER_SPHERE = 1.33;
49
+ /** A longitudinal lobe: a normalised Gaussian of width `beta` at `x` from its centre. */
50
+ export declare function hairLongitudinal(beta: number, x: number): number;
51
+ /** Schlick's Fresnel for the strand at a cosine, clamped to the unit interval. */
52
+ export declare function hairFresnel(cosine: number): number;
53
+ /** Where `hairLight` leaves its answer: R, then TT, TRT and the scatter, each a colour. */
54
+ export declare const HAIR_OUT: {
55
+ readonly r: 0;
56
+ readonly tt: 1;
57
+ readonly trt: 4;
58
+ readonly scatter: 7;
59
+ readonly length: 10;
60
+ };
61
+ /**
62
+ * One light on a strand, in lamp units and unshadowed, into `out` (`HAIR_OUT`'s layout: R is grey,
63
+ * the rest colours). `tangent`, `toLight` and `toEye` are unit vectors but for a tangent that could
64
+ * not be made one, which comes in as zeros; `fibre` is the base colour.
65
+ *
66
+ * **A lamp with a size widens every lobe by the angle it subtends**, `sourceRadius / 2·dist` as
67
+ * `sphereLobe` takes it, and the lobes are normalised Gaussians, so a wider one carries the same
68
+ * light rather than more. The sun passes zeros.
69
+ */
70
+ export declare function hairLight(tangent: ArrayLike<number>, toLight: ArrayLike<number>, toEye: ArrayLike<number>, fibre: Vec3, roughness: number, shift: number, scatter: number, backlit: number, sourceRadius: number, dist: number, out: Float64Array): void;
@@ -0,0 +1,120 @@
1
+ /** Schlick's base for a strand at index 1.55: `((1 − n) / (1 + n))²`. */
2
+ export const HAIR_F0 = ((1 - 1.55) / (1 + 1.55)) ** 2;
3
+ /**
4
+ * The narrowest longitudinal width a strand is drawn at, in radians.
5
+ *
6
+ * **Below every measured fibre**: Marschner gives the reflected lobe 5° to 10°, and this is about
7
+ * 3°, so no real hair is clamped. What it stops is a roughness of 0 making a Gaussian narrower than
8
+ * a pixel can sample — the speckle `MIN_LOBE_ALPHA` argues against for the standard lobe.
9
+ */
10
+ export const HAIR_MIN_BETA = 0.05;
11
+ /**
12
+ * The scatter term's answer to light arriving evenly from every side, per unit of it: the term's
13
+ * shape integrated over the sphere and divided by π, which is how this engine's lamp units add up.
14
+ *
15
+ * Derived: the wrapped half `(N·L + 1) / 4` averages 1/4 over the sphere, `1 − |T·L|` averages 1/2,
16
+ * and the shape is `mix(wrapped, 1 − |T·L|, 0.33)`, so its mean is 0.67 · 0.25 + 0.33 · 0.5 = 0.3325
17
+ * and its integral 4π times that. Over π: **1.33**.
18
+ */
19
+ export const HAIR_SCATTER_SPHERE = 1.33;
20
+ /** How much the scatter term leans toward the strand's own direction rather than the pseudo-normal. */
21
+ const KAJIYA_SHARE = 0.33;
22
+ /** A longitudinal lobe: a normalised Gaussian of width `beta` at `x` from its centre. */
23
+ export function hairLongitudinal(beta, x) {
24
+ return Math.exp((-0.5 * x * x) / (beta * beta)) / (Math.sqrt(2 * Math.PI) * beta);
25
+ }
26
+ /** Schlick's Fresnel for the strand at a cosine, clamped to the unit interval. */
27
+ export function hairFresnel(cosine) {
28
+ const c = 1 - Math.min(Math.max(cosine, 0), 1);
29
+ return HAIR_F0 + (1 - HAIR_F0) * c * c * c * c * c;
30
+ }
31
+ function dot(a, b) {
32
+ return (a[0] * b[0] +
33
+ a[1] * b[1] +
34
+ a[2] * b[2]);
35
+ }
36
+ const clamp = (x, low, high) => Math.min(Math.max(x, low), high);
37
+ /** Where `hairLight` leaves its answer: R, then TT, TRT and the scatter, each a colour. */
38
+ export const HAIR_OUT = { r: 0, tt: 1, trt: 4, scatter: 7, length: 10 };
39
+ /**
40
+ * One light on a strand, in lamp units and unshadowed, into `out` (`HAIR_OUT`'s layout: R is grey,
41
+ * the rest colours). `tangent`, `toLight` and `toEye` are unit vectors but for a tangent that could
42
+ * not be made one, which comes in as zeros; `fibre` is the base colour.
43
+ *
44
+ * **A lamp with a size widens every lobe by the angle it subtends**, `sourceRadius / 2·dist` as
45
+ * `sphereLobe` takes it, and the lobes are normalised Gaussians, so a wider one carries the same
46
+ * light rather than more. The sun passes zeros.
47
+ */
48
+ export function hairLight(tangent, toLight, toEye, fibre, roughness, shift, scatter, backlit, sourceRadius, dist, out) {
49
+ const sinI = clamp(dot(tangent, toLight), -1, 1);
50
+ const sinO = clamp(dot(tangent, toEye), -1, 1);
51
+ /*
52
+ * The half difference angle, rounded to single precision as the device holds it: at its limit,
53
+ * light and eye at opposite ends of the strand, that is π/2 rounded up, whose cosine is −4.4e-8
54
+ * rather than double precision's +6e-17 — and the transmitted powers divide by it. The floor is
55
+ * for the device's number, and the rounding is what lets a test here see it.
56
+ */
57
+ const cosD = Math.max(Math.cos(Math.fround(0.5 * Math.abs(Math.asin(sinO) - Math.asin(sinI)))), 1e-3);
58
+ let lp2 = 0;
59
+ let vp2 = 0;
60
+ let lpvp = 0;
61
+ for (let c = 0; c < 3; c++) {
62
+ const lp = toLight[c] - sinI * tangent[c];
63
+ const vp = toEye[c] - sinO * tangent[c];
64
+ lp2 += lp * lp;
65
+ vp2 += vp * vp;
66
+ lpvp += lp * vp;
67
+ }
68
+ const cosPhi = lpvp / Math.sqrt(lp2 * vp2 + 1e-4);
69
+ const cosHalfPhi = Math.sqrt(clamp(0.5 + 0.5 * cosPhi, 0, 1));
70
+ const beta = Math.max(roughness * roughness, HAIR_MIN_BETA);
71
+ const grow = sourceRadius / Math.max(2 * dist, 1e-3);
72
+ const x = sinI + sinO;
73
+ const r = hairLongitudinal(beta + grow, x + 2 * shift) *
74
+ 0.25 *
75
+ cosHalfPhi *
76
+ hairFresnel(Math.sqrt(clamp(0.5 + 0.5 * dot(toLight, toEye), 0, 1)));
77
+ const a = 1 / (1.19 / cosD + 0.36 * cosD);
78
+ const h = cosHalfPhi * (1 + a * (0.6 - 0.8 * cosPhi));
79
+ const fTT = hairFresnel(cosD * Math.sqrt(clamp(1 - h * h, 0, 1)));
80
+ const tt = hairLongitudinal(0.5 * beta + grow, x - shift) *
81
+ Math.exp(-3.65 * cosPhi - 3.98) *
82
+ (1 - fTT) *
83
+ (1 - fTT) *
84
+ backlit;
85
+ const ttPower = (0.5 * Math.sqrt(clamp(1 - h * h * a * a, 0, 1))) / cosD;
86
+ const fTRT = hairFresnel(0.5 * cosD);
87
+ const trt = hairLongitudinal(2 * beta + grow, x - 3 * shift) *
88
+ Math.exp(17 * cosPhi - 16.78) *
89
+ (1 - fTRT) *
90
+ (1 - fTRT) *
91
+ fTRT;
92
+ const trtPower = 0.8 / cosD;
93
+ let across0 = toEye[0] - tangent[0] * sinO;
94
+ let across1 = toEye[1] - tangent[1] * sinO;
95
+ let across2 = toEye[2] - tangent[2] * sinO;
96
+ const acrossLength = Math.hypot(across0, across1, across2);
97
+ if (acrossLength > 1e-4) {
98
+ across0 /= acrossLength;
99
+ across1 /= acrossLength;
100
+ across2 /= acrossLength;
101
+ }
102
+ const wrapped = clamp((across0 * toLight[0] +
103
+ across1 * toLight[1] +
104
+ across2 * toLight[2] +
105
+ 1) /
106
+ 4, 0, 1);
107
+ const shape = wrapped + (1 - Math.abs(sinI) - wrapped) * KAJIYA_SHARE;
108
+ out[HAIR_OUT.r] = Math.PI * r;
109
+ for (let c = 0; c < 3; c++) {
110
+ /*
111
+ * Above zero, so the device's `pow` never forms the logarithm of zero: an infinity in the
112
+ * middle of an expression is something a shader compiler may assume never happens. Nothing in
113
+ * double precision can show the difference, so no test here does.
114
+ */
115
+ const colour = Math.max(fibre[c], 1e-4);
116
+ out[HAIR_OUT.tt + c] = Math.PI * tt * colour ** ttPower;
117
+ out[HAIR_OUT.trt + c] = Math.PI * trt * colour ** trtPower;
118
+ out[HAIR_OUT.scatter + c] = Math.sqrt(fibre[c]) * scatter * shape;
119
+ }
120
+ }
@@ -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
  *
@@ -1,6 +1,7 @@
1
1
  import { compileProgram, uniformLocations } from './shader.js';
2
2
  import { LINE_FRAG, LINE_VERT } from './shaders/line.js';
3
3
  import { buildSegmentQuads, expandLineSegments } from './segmentQuads.js';
4
+ import { blendCovering, blendKeeping } from './sceneCoverage.js';
4
5
  /**
5
6
  * One draw call for a polyline with a real width — an audio waveform, a trail, a plotted
6
7
  * curve, anything that is a stroke rather than a surface.
@@ -96,7 +97,10 @@ export class LineBatch {
96
97
  if (segmentCount === 0)
97
98
  return;
98
99
  gl.enable(gl.BLEND);
99
- gl.blendFunc(gl.SRC_ALPHA, additive ? gl.ONE : gl.ONE_MINUS_SRC_ALPHA);
100
+ if (additive)
101
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
102
+ else
103
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
100
104
  gl.depthMask(false);
101
105
  // The quad billboards toward the view direction the same way a bolt's does — see
102
106
  // `line.ts`'s own `cross(dir, view)` — so its winding can flip with the camera exactly
@@ -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);
@@ -1,6 +1,7 @@
1
1
  import { compileProgram, uniformLocations } from './shader.js';
2
2
  import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
3
3
  import { OIT_RESOLVE_FRAG } from './shaders/oitResolve.js';
4
+ import { blendTransmitting } from './sceneCoverage.js';
4
5
  /**
5
6
  * Order-independent transparency on WebGL2: two targets, two passes over the same geometry, and a
6
7
  * resolve.
@@ -163,7 +164,7 @@ export class OitPass {
163
164
  gl.depthMask(false);
164
165
  gl.disable(gl.CULL_FACE);
165
166
  gl.enable(gl.BLEND);
166
- gl.blendFunc(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);
167
+ blendTransmitting(gl, gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);
167
168
  gl.useProgram(this.program);
168
169
  gl.activeTexture(gl.TEXTURE0);
169
170
  gl.bindTexture(gl.TEXTURE_2D, this.accum);
@@ -1,6 +1,7 @@
1
1
  import { compileProgram, uniformLocations } from './shader.js';
2
2
  import { particleShaders } from './particleMaterial.js';
3
3
  import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from './shaders/particle.js';
4
+ import { blendCovering, blendKeeping } from './sceneCoverage.js';
4
5
  /** Corners of one blade, as a triangle-strip-friendly quad. */
5
6
  const CORNERS = [
6
7
  [-1, -1],
@@ -138,9 +139,9 @@ export class ParticleBatch {
138
139
  return;
139
140
  gl.enable(gl.BLEND);
140
141
  if (this.blend === 'additive')
141
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
142
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
142
143
  else
143
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
144
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
144
145
  gl.depthMask(false);
145
146
  gl.disable(gl.CULL_FACE);
146
147
  gl.bindVertexArray(this.vao);
@@ -19,7 +19,11 @@ import type { Vec3 } from '../math/color.ts';
19
19
  export interface ParticleInstances {
20
20
  /** World position, three floats each. */
21
21
  positions: Float32Array;
22
- /** Metres across, and radians of roll about the particle's own axis. */
22
+ /**
23
+ * Metres from the centre to an edge, a half-width: the quad spans twice this. Then radians of
24
+ * roll about the particle's own axis. This said metres across until 4.8.3, and a caller who took
25
+ * it at its word drew every particle twice as wide as meant.
26
+ */
23
27
  sizes: Float32Array;
24
28
  spins: Float32Array;
25
29
  /** Linear colour, three floats each. */
@@ -54,7 +58,7 @@ export interface ParticlePoolOptions {
54
58
  capacity: number;
55
59
  /** Seconds a particle lives. */
56
60
  lifeSec: number;
57
- /** Size at birth and at death, metres. */
61
+ /** Size at birth and at death: metres from the centre to an edge, so the quad is twice as wide. */
58
62
  sizeStart: number;
59
63
  sizeEnd: number;
60
64
  /** Colour at birth and at death. */
@@ -3,6 +3,7 @@ import { compileProgram, uniformLocations } from './shader.js';
3
3
  import { bindAtmosphere } from './atmosphere.js';
4
4
  import { plumeShaders } from './plumeMaterial.js';
5
5
  import { buildPlumeGeometry } from './plumeGeometry.js';
6
+ import { blendCovering, blendKeeping } from './sceneCoverage.js';
6
7
  /* The shared count, so both backends step a plume's vertices the same way. */
7
8
  const VERTS_PER_PLUME = PLUME_VERTS;
8
9
  export class PlumeRenderer {
@@ -123,9 +124,9 @@ export class PlumeRenderer {
123
124
  bindAtmosphere(gl, u, atmosphere, atmosphereCameraY, underwaterEnabled);
124
125
  gl.enable(gl.BLEND);
125
126
  if (this.blend === 'additive')
126
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
127
+ blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
127
128
  else
128
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
129
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
129
130
  gl.depthMask(false);
130
131
  gl.disable(gl.CULL_FACE);
131
132
  gl.bindVertexArray(this.vao);
@@ -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;
@@ -14,8 +14,14 @@
14
14
  * `aces` adds filmic tone mapping before that, rolling highlights off instead of
15
15
  * clipping them flat. Together they are what three.js does by default, which is why a
16
16
  * world ported from it looks wrong until this exists.
17
+ *
18
+ * `shoulder` keeps sRGB's midtones exactly and eases only the highlights: past 0.8 the brightest
19
+ * channel rolls toward 1 and the other two are scaled with it, so an overbright red spark stays
20
+ * red where `srgb` clips each channel and turns its core pink and wide. It is the curve a ported
21
+ * game's own resolve applied — for a port that has to match one — and it has none of ACES's toe or
22
+ * desaturation. See `HIGHLIGHT_SHOULDER_GLSL` for the arithmetic.
17
23
  */
18
- export type OutputTransform = 'none' | 'srgb' | 'aces';
24
+ export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder';
19
25
  /**
20
26
  * The three settings whose accepted values are a closed set, written as one.
21
27
  *
@@ -31,6 +37,8 @@ export type DirectionalShadowDepthLayers = 1 | 2;
31
37
  export type WaterReflectionFilterTaps = 1 | 5 | 9;
32
38
  /** How light through glass is coloured: see `RenderQuality.glassShadows`. */
33
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';
34
42
  export interface RenderQuality {
35
43
  /**
36
44
  * The transfer curve applied at the end of every shaded pass.
@@ -60,7 +68,8 @@ export interface RenderQuality {
60
68
  * chooses is a look rather than a standard — three.js uses 1/0.6 and says so. This
61
69
  * engine holds no opinion and takes the number from whoever is authoring the world, so
62
70
  * that matching another renderer, or simply preferring a different grade, is a setting
63
- * rather than a patch. Ignored when `outputTransform` is not `aces`.
71
+ * rather than a patch. Scales into `aces` and into `shoulder`, the two curves; ignored by
72
+ * `none` and `srgb`, which have none.
64
73
  */
65
74
  readonly outputExposure: number;
66
75
  /**
@@ -194,6 +203,24 @@ export interface RenderQuality {
194
203
  * lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
195
204
  */
196
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;
197
224
  /** Horizontal world-space reach over which a directional shadow dissolves. */
198
225
  readonly directionalShadowMaxDistance: number;
199
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,
@@ -0,0 +1,54 @@
1
+ /**
2
+ * What a blended draw into the scene does to its alpha, which holds how much of each pixel is still
3
+ * the opaque surface — the share ambient occlusion is allowed to darken.
4
+ *
5
+ * **Occlusion is applied in the composite, from the depth the opaque world left**, after every
6
+ * translucent draw has landed, and a particle or a pane writes no depth: so a puff of smoke in front
7
+ * of a wall was darkened by the corner behind it. Reported from a game as walls and two figures
8
+ * standing behind a cloud showing through it as outlines, and nothing at all with occlusion off.
9
+ *
10
+ * So the scene's alpha is a running share. The frame clears it to 1 and an opaque draw writes 1,
11
+ * which is the whole of the surface; then each draw blended in front of it says which of three
12
+ * things it is, and the composite darkens `share` of the pixel and leaves the rest:
13
+ *
14
+ * - **It covers**: glass, smoke, water, a line, text in the world, a panel. The share falls by what
15
+ * the draw lets through, `share * (1 - a)`.
16
+ * - **It transmits**: the medium, the order-independent composite. Its alpha already *is* what gets
17
+ * through, `share * a`.
18
+ * - **It keeps**: light added to the surface or a layer of the surface itself — caustics, a light
19
+ * volume, a bolt, a wet film, a decal, a reflection. The share is unchanged.
20
+ *
21
+ * **What it gives up** is added light in front of an occluded corner: a glow or a spark adds to the
22
+ * pixel and keeps the share, so the composite darkens what it added along with the surface — which
23
+ * is how every blended draw behaved before this, and one multiply cannot tell the two apart. **What
24
+ * would make it wrong** is an opaque pass writing an alpha below 1: its surface would then take less
25
+ * occlusion than it should, which is the safe direction, and is the lit shader's own case at a
26
+ * cutout's edge or under a per-vertex alpha lane.
27
+ *
28
+ * **It survives the temporal passes.** A temporal resolve passes the frame's alpha through, since
29
+ * it is a fact about this frame and takes no history; a reconstruction reads it from the scene it
30
+ * rebuilt into the picture it shows, and the blended draws that land after it cover that picture.
31
+ *
32
+ * **One pipeline does not take part**: the GPU-driven pass blends its own surfaces into a target
33
+ * whose alpha is the mask its blit reads to tell a drawn pixel from an undrawn one, and that blit
34
+ * writes the share back to 1. Its panes still take the occlusion of what they cover. What would
35
+ * close it is a channel for that mask other than alpha.
36
+ *
37
+ * A contributed pass drawing something blended into the scene states the same thing for its own
38
+ * pipeline with `SCENE_ALPHA_COVERS`, `SCENE_ALPHA_TRANSMITS` or `SCENE_ALPHA_KEEPS`.
39
+ */
40
+ /** A blended draw that covers the surface by its alpha: `share * (1 - a)`. */
41
+ export declare const SCENE_ALPHA_COVERS: GPUBlendComponent;
42
+ /** A draw whose alpha is what gets through it, a transmittance or a revealage: `share * a`. */
43
+ export declare const SCENE_ALPHA_TRANSMITS: GPUBlendComponent;
44
+ /** Light added to the surface, or a layer of it: the share unchanged. */
45
+ export declare const SCENE_ALPHA_KEEPS: GPUBlendComponent;
46
+ /**
47
+ * The same three on WebGL2, as the alpha half of `blendFuncSeparate` beside the colour half a
48
+ * caller already had. `src` and `dst` are the colour factors.
49
+ */
50
+ export declare function blendCovering(gl: WebGL2RenderingContext, src: number, dst: number): void;
51
+ /** `SCENE_ALPHA_TRANSMITS` on WebGL2, beside the colour factors `src` and `dst`. */
52
+ export declare function blendTransmitting(gl: WebGL2RenderingContext, src: number, dst: number): void;
53
+ /** `SCENE_ALPHA_KEEPS` on WebGL2, beside the colour factors `src` and `dst`. */
54
+ export declare function blendKeeping(gl: WebGL2RenderingContext, src: number, dst: number): void;