@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.
- package/CHANGELOG.json +127 -0
- package/README.md +1 -1
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +22 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/materialChanges.d.ts +7 -2
- package/dist/render/backend/materialChanges.js +8 -3
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +159 -3
- package/dist/render/backend/webgl2/renderer.js +662 -80
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/causticsPass.js +2 -1
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
- package/dist/render/backend/webgpu/depthPass.js +43 -12
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/filmPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
- package/dist/render/backend/webgpu/flatPass.js +111 -13
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/linePass.js +2 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/panelPass.js +2 -1
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/plumePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +15 -3
- package/dist/render/backend/webgpu/postPass.js +25 -8
- package/dist/render/backend/webgpu/renderer.d.ts +123 -6
- package/dist/render/backend/webgpu/renderer.js +534 -58
- package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/backend/webgpu/textPass.js +2 -1
- package/dist/render/backend/webgpu/waterPass.js +2 -1
- package/dist/render/backend/webgpu/windStreakPass.js +2 -1
- package/dist/render/boltBatch.js +2 -1
- package/dist/render/causticsRenderer.js +2 -1
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +5 -1
- package/dist/render/cutoutCaster.js +6 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/decalPass.js +2 -1
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/lineBatch.js +5 -1
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/oitPass.js +2 -1
- package/dist/render/particleBatch.js +3 -2
- package/dist/render/particlePool.d.ts +6 -2
- package/dist/render/plumeRenderer.js +3 -2
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +29 -2
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneCoverage.d.ts +54 -0
- package/dist/render/sceneCoverage.js +72 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/sdfTextRenderer.js +2 -1
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +45 -8
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +42 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
- package/dist/render/shaders/generated/depth.wgsl.js +66 -24
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
- package/dist/render/shaders/generated/particle.wgsl.js +3 -3
- package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/rush.wgsl.js +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +14 -1
- package/dist/render/shaders/outputTransform.js +23 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
- package/dist/render/shaders/rush.js +13 -2
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shaders/temporalResolve.js +12 -5
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/ssrPass.js +2 -1
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +55 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/textRenderer.js +2 -1
- package/dist/render/vertexDefaults.js +8 -0
- package/dist/render/waterRenderer.js +2 -1
- package/dist/render/windStreakRenderer.js +2 -1
- package/package.json +6 -6
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +53 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/materialChanges.ts +8 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +750 -80
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/causticsPass.ts +2 -1
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +43 -10
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/filmPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +147 -8
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/linePass.ts +2 -1
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/panelPass.ts +2 -1
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/plumePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +25 -6
- package/src/render/backend/webgpu/renderer.ts +701 -52
- package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/backend/webgpu/textPass.ts +2 -1
- package/src/render/backend/webgpu/waterPass.ts +2 -1
- package/src/render/backend/webgpu/windStreakPass.ts +2 -1
- package/src/render/boltBatch.ts +2 -1
- package/src/render/causticsRenderer.ts +2 -1
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +20 -2
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/decalPass.ts +2 -1
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/globalMediumPass.ts +3 -2
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/lineBatch.ts +3 -1
- package/src/render/mesh.ts +23 -0
- package/src/render/oitPass.ts +2 -1
- package/src/render/particleBatch.ts +3 -2
- package/src/render/particlePool.ts +6 -2
- package/src/render/plumeRenderer.ts +3 -2
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +37 -2
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneCoverage.ts +78 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/sdfTextRenderer.ts +2 -1
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +45 -8
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +42 -3
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +66 -24
- package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +3 -3
- package/src/render/shaders/generated/rush.wgsl.ts +1 -1
- package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/outputTransform.ts +24 -1
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
- package/src/render/shaders/rush.ts +13 -2
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shaders/temporalResolve.ts +12 -5
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/ssrPass.ts +2 -1
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +134 -63
- package/src/render/textRenderer.ts +2 -1
- package/src/render/vertexDefaults.ts +8 -0
- package/src/render/waterRenderer.ts +2 -1
- package/src/render/windStreakRenderer.ts +2 -1
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
|
|
31
|
+
/** Schlick's base for a strand at index 1.55: `((1 − n) / (1 + n))²`. */
|
|
32
|
+
export const HAIR_F0 = ((1 - 1.55) / (1 + 1.55)) ** 2;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The narrowest longitudinal width a strand is drawn at, in radians.
|
|
36
|
+
*
|
|
37
|
+
* **Below every measured fibre**: Marschner gives the reflected lobe 5° to 10°, and this is about
|
|
38
|
+
* 3°, so no real hair is clamped. What it stops is a roughness of 0 making a Gaussian narrower than
|
|
39
|
+
* a pixel can sample — the speckle `MIN_LOBE_ALPHA` argues against for the standard lobe.
|
|
40
|
+
*/
|
|
41
|
+
export const HAIR_MIN_BETA = 0.05;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The scatter term's answer to light arriving evenly from every side, per unit of it: the term's
|
|
45
|
+
* shape integrated over the sphere and divided by π, which is how this engine's lamp units add up.
|
|
46
|
+
*
|
|
47
|
+
* Derived: the wrapped half `(N·L + 1) / 4` averages 1/4 over the sphere, `1 − |T·L|` averages 1/2,
|
|
48
|
+
* 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
|
|
49
|
+
* and its integral 4π times that. Over π: **1.33**.
|
|
50
|
+
*/
|
|
51
|
+
export const HAIR_SCATTER_SPHERE = 1.33;
|
|
52
|
+
|
|
53
|
+
/** How much the scatter term leans toward the strand's own direction rather than the pseudo-normal. */
|
|
54
|
+
const KAJIYA_SHARE = 0.33;
|
|
55
|
+
|
|
56
|
+
/** A longitudinal lobe: a normalised Gaussian of width `beta` at `x` from its centre. */
|
|
57
|
+
export function hairLongitudinal(beta: number, x: number): number {
|
|
58
|
+
return Math.exp((-0.5 * x * x) / (beta * beta)) / (Math.sqrt(2 * Math.PI) * beta);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Schlick's Fresnel for the strand at a cosine, clamped to the unit interval. */
|
|
62
|
+
export function hairFresnel(cosine: number): number {
|
|
63
|
+
const c = 1 - Math.min(Math.max(cosine, 0), 1);
|
|
64
|
+
return HAIR_F0 + (1 - HAIR_F0) * c * c * c * c * c;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function dot(a: ArrayLike<number>, b: ArrayLike<number>): number {
|
|
68
|
+
return (
|
|
69
|
+
(a[0] as number) * (b[0] as number) +
|
|
70
|
+
(a[1] as number) * (b[1] as number) +
|
|
71
|
+
(a[2] as number) * (b[2] as number)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const clamp = (x: number, low: number, high: number): number => Math.min(Math.max(x, low), high);
|
|
76
|
+
|
|
77
|
+
/** Where `hairLight` leaves its answer: R, then TT, TRT and the scatter, each a colour. */
|
|
78
|
+
export const HAIR_OUT = { r: 0, tt: 1, trt: 4, scatter: 7, length: 10 } as const;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* One light on a strand, in lamp units and unshadowed, into `out` (`HAIR_OUT`'s layout: R is grey,
|
|
82
|
+
* the rest colours). `tangent`, `toLight` and `toEye` are unit vectors but for a tangent that could
|
|
83
|
+
* not be made one, which comes in as zeros; `fibre` is the base colour.
|
|
84
|
+
*
|
|
85
|
+
* **A lamp with a size widens every lobe by the angle it subtends**, `sourceRadius / 2·dist` as
|
|
86
|
+
* `sphereLobe` takes it, and the lobes are normalised Gaussians, so a wider one carries the same
|
|
87
|
+
* light rather than more. The sun passes zeros.
|
|
88
|
+
*/
|
|
89
|
+
export function hairLight(
|
|
90
|
+
tangent: ArrayLike<number>,
|
|
91
|
+
toLight: ArrayLike<number>,
|
|
92
|
+
toEye: ArrayLike<number>,
|
|
93
|
+
fibre: Vec3,
|
|
94
|
+
roughness: number,
|
|
95
|
+
shift: number,
|
|
96
|
+
scatter: number,
|
|
97
|
+
backlit: number,
|
|
98
|
+
sourceRadius: number,
|
|
99
|
+
dist: number,
|
|
100
|
+
out: Float64Array,
|
|
101
|
+
): void {
|
|
102
|
+
const sinI = clamp(dot(tangent, toLight), -1, 1);
|
|
103
|
+
const sinO = clamp(dot(tangent, toEye), -1, 1);
|
|
104
|
+
/*
|
|
105
|
+
* The half difference angle, rounded to single precision as the device holds it: at its limit,
|
|
106
|
+
* light and eye at opposite ends of the strand, that is π/2 rounded up, whose cosine is −4.4e-8
|
|
107
|
+
* rather than double precision's +6e-17 — and the transmitted powers divide by it. The floor is
|
|
108
|
+
* for the device's number, and the rounding is what lets a test here see it.
|
|
109
|
+
*/
|
|
110
|
+
const cosD = Math.max(
|
|
111
|
+
Math.cos(Math.fround(0.5 * Math.abs(Math.asin(sinO) - Math.asin(sinI)))),
|
|
112
|
+
1e-3,
|
|
113
|
+
);
|
|
114
|
+
let lp2 = 0;
|
|
115
|
+
let vp2 = 0;
|
|
116
|
+
let lpvp = 0;
|
|
117
|
+
for (let c = 0; c < 3; c++) {
|
|
118
|
+
const lp = (toLight[c] as number) - sinI * (tangent[c] as number);
|
|
119
|
+
const vp = (toEye[c] as number) - sinO * (tangent[c] as number);
|
|
120
|
+
lp2 += lp * lp;
|
|
121
|
+
vp2 += vp * vp;
|
|
122
|
+
lpvp += lp * vp;
|
|
123
|
+
}
|
|
124
|
+
const cosPhi = lpvp / Math.sqrt(lp2 * vp2 + 1e-4);
|
|
125
|
+
const cosHalfPhi = Math.sqrt(clamp(0.5 + 0.5 * cosPhi, 0, 1));
|
|
126
|
+
const beta = Math.max(roughness * roughness, HAIR_MIN_BETA);
|
|
127
|
+
const grow = sourceRadius / Math.max(2 * dist, 1e-3);
|
|
128
|
+
const x = sinI + sinO;
|
|
129
|
+
|
|
130
|
+
const r =
|
|
131
|
+
hairLongitudinal(beta + grow, x + 2 * shift) *
|
|
132
|
+
0.25 *
|
|
133
|
+
cosHalfPhi *
|
|
134
|
+
hairFresnel(Math.sqrt(clamp(0.5 + 0.5 * dot(toLight, toEye), 0, 1)));
|
|
135
|
+
|
|
136
|
+
const a = 1 / (1.19 / cosD + 0.36 * cosD);
|
|
137
|
+
const h = cosHalfPhi * (1 + a * (0.6 - 0.8 * cosPhi));
|
|
138
|
+
const fTT = hairFresnel(cosD * Math.sqrt(clamp(1 - h * h, 0, 1)));
|
|
139
|
+
const tt =
|
|
140
|
+
hairLongitudinal(0.5 * beta + grow, x - shift) *
|
|
141
|
+
Math.exp(-3.65 * cosPhi - 3.98) *
|
|
142
|
+
(1 - fTT) *
|
|
143
|
+
(1 - fTT) *
|
|
144
|
+
backlit;
|
|
145
|
+
const ttPower = (0.5 * Math.sqrt(clamp(1 - h * h * a * a, 0, 1))) / cosD;
|
|
146
|
+
|
|
147
|
+
const fTRT = hairFresnel(0.5 * cosD);
|
|
148
|
+
const trt =
|
|
149
|
+
hairLongitudinal(2 * beta + grow, x - 3 * shift) *
|
|
150
|
+
Math.exp(17 * cosPhi - 16.78) *
|
|
151
|
+
(1 - fTRT) *
|
|
152
|
+
(1 - fTRT) *
|
|
153
|
+
fTRT;
|
|
154
|
+
const trtPower = 0.8 / cosD;
|
|
155
|
+
|
|
156
|
+
let across0 = (toEye[0] as number) - (tangent[0] as number) * sinO;
|
|
157
|
+
let across1 = (toEye[1] as number) - (tangent[1] as number) * sinO;
|
|
158
|
+
let across2 = (toEye[2] as number) - (tangent[2] as number) * sinO;
|
|
159
|
+
const acrossLength = Math.hypot(across0, across1, across2);
|
|
160
|
+
if (acrossLength > 1e-4) {
|
|
161
|
+
across0 /= acrossLength;
|
|
162
|
+
across1 /= acrossLength;
|
|
163
|
+
across2 /= acrossLength;
|
|
164
|
+
}
|
|
165
|
+
const wrapped = clamp(
|
|
166
|
+
(across0 * (toLight[0] as number) +
|
|
167
|
+
across1 * (toLight[1] as number) +
|
|
168
|
+
across2 * (toLight[2] as number) +
|
|
169
|
+
1) /
|
|
170
|
+
4,
|
|
171
|
+
0,
|
|
172
|
+
1,
|
|
173
|
+
);
|
|
174
|
+
const shape = wrapped + (1 - Math.abs(sinI) - wrapped) * KAJIYA_SHARE;
|
|
175
|
+
|
|
176
|
+
out[HAIR_OUT.r] = Math.PI * r;
|
|
177
|
+
for (let c = 0; c < 3; c++) {
|
|
178
|
+
/*
|
|
179
|
+
* Above zero, so the device's `pow` never forms the logarithm of zero: an infinity in the
|
|
180
|
+
* middle of an expression is something a shader compiler may assume never happens. Nothing in
|
|
181
|
+
* double precision can show the difference, so no test here does.
|
|
182
|
+
*/
|
|
183
|
+
const colour = Math.max(fibre[c], 1e-4);
|
|
184
|
+
out[HAIR_OUT.tt + c] = Math.PI * tt * colour ** ttPower;
|
|
185
|
+
out[HAIR_OUT.trt + c] = Math.PI * trt * colour ** trtPower;
|
|
186
|
+
out[HAIR_OUT.scatter + c] = Math.sqrt(fibre[c]) * scatter * shape;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -238,6 +238,31 @@ export const POINT_GLASS_TINT_TEXTURE_UNIT = SUN_GLASS_TINT_TEXTURE_UNIT + 1;
|
|
|
238
238
|
*/
|
|
239
239
|
export const SURFACE_EFFECTS_TEXTURE_UNIT = POINT_GLASS_TINT_TEXTURE_UNIT + 1;
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* A cloth binding's three vertex-stage textures — the binding, the particles now, the particles at
|
|
243
|
+
* rest (`clothBinding.ts`). Vertex samplers, so they count against the vertex stage's own sixteen
|
|
244
|
+
* beside the palette and the morph deltas, not the lit stage's; numbered past every fragment unit,
|
|
245
|
+
* inside the combined pool of thirty-two.
|
|
246
|
+
*/
|
|
247
|
+
export const CLOTH_BINDING_TEXTURE_UNIT = SURFACE_EFFECTS_TEXTURE_UNIT + 1;
|
|
248
|
+
export const CLOTH_PARTICLES_TEXTURE_UNIT = CLOTH_BINDING_TEXTURE_UNIT + 1;
|
|
249
|
+
export const CLOTH_REST_TEXTURE_UNIT = CLOTH_PARTICLES_TEXTURE_UNIT + 1;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* A shading model's own channels (`shaders/flat/models.ts`), the lit stage's **sixteenth** fragment
|
|
253
|
+
* sampler — exactly what WebGL2 guarantees a stage, which `textureUnitBudget.test.ts` counts from
|
|
254
|
+
* the source. Numbered past the cloth's vertex units, inside the combined pool of thirty-two.
|
|
255
|
+
*/
|
|
256
|
+
export const MODEL_MAP_TEXTURE_UNIT = CLOTH_REST_TEXTURE_UNIT + 1;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The two units skin's screen-space blur reads on WebGL2 — its target and the frame's depth — past
|
|
260
|
+
* every unit a lit program binds, so spreading skin in the middle of a frame unbinds nothing the
|
|
261
|
+
* draws after it sample. WebGL2 guarantees thirty-two.
|
|
262
|
+
*/
|
|
263
|
+
export const SKIN_BLUR_TEXTURE_UNIT = MODEL_MAP_TEXTURE_UNIT + 1;
|
|
264
|
+
export const SKIN_BLUR_DEPTH_UNIT = SKIN_BLUR_TEXTURE_UNIT + 1;
|
|
265
|
+
|
|
241
266
|
/**
|
|
242
267
|
* A cookie's tile, in texels a side.
|
|
243
268
|
*
|
package/src/render/lineBatch.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
2
2
|
import { LINE_FRAG, LINE_VERT } from './shaders/line.ts';
|
|
3
3
|
import { buildSegmentQuads, expandLineSegments } from './segmentQuads.ts';
|
|
4
4
|
import type { LineSegments } from './linePoints.ts';
|
|
5
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One draw call for a polyline with a real width — an audio waveform, a trail, a plotted
|
|
@@ -102,7 +103,8 @@ export class LineBatch {
|
|
|
102
103
|
drawTo(gl: WebGL2RenderingContext, segmentCount: number, additive = false): void {
|
|
103
104
|
if (segmentCount === 0) return;
|
|
104
105
|
gl.enable(gl.BLEND);
|
|
105
|
-
|
|
106
|
+
if (additive) blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
107
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
106
108
|
gl.depthMask(false);
|
|
107
109
|
// The quad billboards toward the view direction the same way a bolt's does — see
|
|
108
110
|
// `line.ts`'s own `cross(dir, view)` — so its winding can flip with the camera exactly
|
package/src/render/mesh.ts
CHANGED
|
@@ -57,6 +57,12 @@ const ATTR_JOINTS = 11;
|
|
|
57
57
|
const ATTR_CHANNEL = 13;
|
|
58
58
|
|
|
59
59
|
const ATTR_WEIGHTS = 12;
|
|
60
|
+
/*
|
|
61
|
+
* The second four influences, at the last two locations: a skinned draw spends 11, 12 and 13 and an
|
|
62
|
+
* instanced one cannot skin, so 14 and 15 are free exactly where they are read. See skinning.ts.
|
|
63
|
+
*/
|
|
64
|
+
const ATTR_JOINTS2 = 14;
|
|
65
|
+
const ATTR_WEIGHTS2 = 15;
|
|
60
66
|
|
|
61
67
|
/**
|
|
62
68
|
* One absent attribute's constant, at the width the table gave it.
|
|
@@ -136,8 +142,15 @@ export class Mesh {
|
|
|
136
142
|
readonly bounds: Bounds = createBounds();
|
|
137
143
|
/** Whether this mesh carries a rig, which decides which flat program draws it. */
|
|
138
144
|
readonly isSkinned: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Whether its rig moves a vertex by eight influences rather than four, which decides whether the
|
|
147
|
+
* skinned program it takes is built with `SKIN_EIGHT` on. See `skinning.ts`.
|
|
148
|
+
*/
|
|
149
|
+
readonly isSkinnedEight: boolean;
|
|
139
150
|
/** Whether it has texture coordinates, without which a material's maps read one texel. */
|
|
140
151
|
readonly hasUvs: boolean;
|
|
152
|
+
/** How many vertices it has, which a cloth binding is checked against. */
|
|
153
|
+
readonly vertexCount: number;
|
|
141
154
|
|
|
142
155
|
/**
|
|
143
156
|
* This mesh's morph deltas, or null for geometry that does not deform.
|
|
@@ -276,6 +289,7 @@ export class Mesh {
|
|
|
276
289
|
* takes; see `WebGL2Renderer.drawMesh`.
|
|
277
290
|
*/
|
|
278
291
|
this.isSkinned = data.joints !== undefined;
|
|
292
|
+
this.vertexCount = data.positions.length / 3;
|
|
279
293
|
this.morph =
|
|
280
294
|
data.morphTargets === undefined || data.morphTargetCount === undefined
|
|
281
295
|
? null
|
|
@@ -287,6 +301,15 @@ export class Mesh {
|
|
|
287
301
|
this.attachAttribute(gl, ATTR_JOINTS, data.joints, 4);
|
|
288
302
|
this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4);
|
|
289
303
|
}
|
|
304
|
+
/* The fifth to eighth influences, or zeros, which add nothing. See `isSkinnedEight`. */
|
|
305
|
+
this.isSkinnedEight = this.isSkinned && data.joints2 !== undefined;
|
|
306
|
+
if (data.joints2 === undefined || data.weights2 === undefined) {
|
|
307
|
+
this.constants.push({ location: ATTR_JOINTS2, value: ABSENT_ATTRIBUTE['joints2'] });
|
|
308
|
+
this.constants.push({ location: ATTR_WEIGHTS2, value: ABSENT_ATTRIBUTE['weights2'] });
|
|
309
|
+
} else {
|
|
310
|
+
this.attachAttribute(gl, ATTR_JOINTS2, data.joints2, 4);
|
|
311
|
+
this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4);
|
|
312
|
+
}
|
|
290
313
|
|
|
291
314
|
this.hasTangents = data.tangents !== undefined;
|
|
292
315
|
if (data.tangents === undefined) {
|
package/src/render/oitPass.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
2
2
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
|
|
3
3
|
import { OIT_RESOLVE_FRAG } from './shaders/oitResolve.ts';
|
|
4
|
+
import { blendTransmitting } from './sceneCoverage.ts';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Order-independent transparency on WebGL2: two targets, two passes over the same geometry, and a
|
|
@@ -179,7 +180,7 @@ export class OitPass {
|
|
|
179
180
|
gl.depthMask(false);
|
|
180
181
|
gl.disable(gl.CULL_FACE);
|
|
181
182
|
gl.enable(gl.BLEND);
|
|
182
|
-
|
|
183
|
+
blendTransmitting(gl, gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);
|
|
183
184
|
|
|
184
185
|
gl.useProgram(this.program);
|
|
185
186
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -2,6 +2,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
2
2
|
import { particleShaders, type ParticleMaterial } from './particleMaterial.ts';
|
|
3
3
|
import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from './shaders/particle.ts';
|
|
4
4
|
import type { ParticleInstances } from './particlePool.ts';
|
|
5
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One draw call for a live particle pool: one camera-facing quad per particle, or a
|
|
@@ -239,8 +240,8 @@ export class ParticleBatch {
|
|
|
239
240
|
drawTo(gl: WebGL2RenderingContext, count: number): void {
|
|
240
241
|
if (count === 0) return;
|
|
241
242
|
gl.enable(gl.BLEND);
|
|
242
|
-
if (this.blend === 'additive')
|
|
243
|
-
else
|
|
243
|
+
if (this.blend === 'additive') blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
244
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
244
245
|
gl.depthMask(false);
|
|
245
246
|
gl.disable(gl.CULL_FACE);
|
|
246
247
|
|
|
@@ -21,7 +21,11 @@ import type { Vec3 } from '../math/color.ts';
|
|
|
21
21
|
export interface ParticleInstances {
|
|
22
22
|
/** World position, three floats each. */
|
|
23
23
|
positions: Float32Array;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Metres from the centre to an edge, a half-width: the quad spans twice this. Then radians of
|
|
26
|
+
* roll about the particle's own axis. This said metres across until 4.8.3, and a caller who took
|
|
27
|
+
* it at its word drew every particle twice as wide as meant.
|
|
28
|
+
*/
|
|
25
29
|
sizes: Float32Array;
|
|
26
30
|
spins: Float32Array;
|
|
27
31
|
/** Linear colour, three floats each. */
|
|
@@ -72,7 +76,7 @@ export interface ParticlePoolOptions {
|
|
|
72
76
|
capacity: number;
|
|
73
77
|
/** Seconds a particle lives. */
|
|
74
78
|
lifeSec: number;
|
|
75
|
-
/** Size at birth and at death,
|
|
79
|
+
/** Size at birth and at death: metres from the centre to an edge, so the quad is twice as wide. */
|
|
76
80
|
sizeStart: number;
|
|
77
81
|
sizeEnd: number;
|
|
78
82
|
/** Colour at birth and at death. */
|
|
@@ -6,6 +6,7 @@ import { plumeShaders, type PlumeMaterial } from './plumeMaterial.ts';
|
|
|
6
6
|
import { buildPlumeGeometry } from './plumeGeometry.ts';
|
|
7
7
|
import type { Atmosphere } from './atmosphere.ts';
|
|
8
8
|
import type { Vec3 } from '../math/color.ts';
|
|
9
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.ts';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Batched camera-facing quads for volumetric effects — flames, smoke, steam,
|
|
@@ -202,8 +203,8 @@ export class PlumeRenderer {
|
|
|
202
203
|
bindAtmosphere(gl, u, atmosphere, atmosphereCameraY, underwaterEnabled);
|
|
203
204
|
|
|
204
205
|
gl.enable(gl.BLEND);
|
|
205
|
-
if (this.blend === 'additive')
|
|
206
|
-
else
|
|
206
|
+
if (this.blend === 'additive') blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
207
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
207
208
|
gl.depthMask(false);
|
|
208
209
|
gl.disable(gl.CULL_FACE);
|
|
209
210
|
|
|
@@ -85,9 +85,11 @@ export function objectMotion(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* A vertex through a skin palette: the
|
|
88
|
+
* A vertex through a skin palette: the joints' matrices weighted and summed, then applied —
|
|
89
89
|
* `SKINNING_GLSL`'s `skinMatrix()` times the position, in model space, before the model matrix.
|
|
90
|
-
* `palette` is sixteen floats a joint, column-major.
|
|
90
|
+
* `palette` is sixteen floats a joint, column-major. `joints` and `weights` hold four influences, or
|
|
91
|
+
* eight for a mesh with a second set (the first set then the second, as the shader reads them with
|
|
92
|
+
* `SKIN_EIGHT` on). Written into `out`, four values.
|
|
91
93
|
*/
|
|
92
94
|
export function skinVertex(
|
|
93
95
|
palette: ArrayLike<number>,
|
|
@@ -103,7 +105,8 @@ export function skinVertex(
|
|
|
103
105
|
out[1] = 0;
|
|
104
106
|
out[2] = 0;
|
|
105
107
|
out[3] = 0;
|
|
106
|
-
|
|
108
|
+
const influences = Math.min(joints.length, weights.length, 8);
|
|
109
|
+
for (let influence = 0; influence < influences; influence += 1) {
|
|
107
110
|
const weight = weights[influence] as number;
|
|
108
111
|
if (weight === 0) continue;
|
|
109
112
|
const at = (joints[influence] as number) * 16;
|
|
@@ -20,8 +20,14 @@ import { PREFILTER_SAMPLE_COUNTS } from './prefilterEnvMap.ts';
|
|
|
20
20
|
* `aces` adds filmic tone mapping before that, rolling highlights off instead of
|
|
21
21
|
* clipping them flat. Together they are what three.js does by default, which is why a
|
|
22
22
|
* world ported from it looks wrong until this exists.
|
|
23
|
+
*
|
|
24
|
+
* `shoulder` keeps sRGB's midtones exactly and eases only the highlights: past 0.8 the brightest
|
|
25
|
+
* channel rolls toward 1 and the other two are scaled with it, so an overbright red spark stays
|
|
26
|
+
* red where `srgb` clips each channel and turns its core pink and wide. It is the curve a ported
|
|
27
|
+
* game's own resolve applied — for a port that has to match one — and it has none of ACES's toe or
|
|
28
|
+
* desaturation. See `HIGHLIGHT_SHOULDER_GLSL` for the arithmetic.
|
|
23
29
|
*/
|
|
24
|
-
export type OutputTransform = 'none' | 'srgb' | 'aces';
|
|
30
|
+
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder';
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
33
|
* The three settings whose accepted values are a closed set, written as one.
|
|
@@ -40,6 +46,9 @@ export type WaterReflectionFilterTaps = 1 | 5 | 9;
|
|
|
40
46
|
/** How light through glass is coloured: see `RenderQuality.glassShadows`. */
|
|
41
47
|
export type GlassShadows = 'full' | 'half' | 'off';
|
|
42
48
|
|
|
49
|
+
/** How light scattered beneath skin is drawn: see `RenderQuality.skinScattering`. */
|
|
50
|
+
export type SkinScattering = 'pre-integrated' | 'screen-space';
|
|
51
|
+
|
|
43
52
|
export interface RenderQuality {
|
|
44
53
|
/**
|
|
45
54
|
* The transfer curve applied at the end of every shaded pass.
|
|
@@ -69,7 +78,8 @@ export interface RenderQuality {
|
|
|
69
78
|
* chooses is a look rather than a standard — three.js uses 1/0.6 and says so. This
|
|
70
79
|
* engine holds no opinion and takes the number from whoever is authoring the world, so
|
|
71
80
|
* that matching another renderer, or simply preferring a different grade, is a setting
|
|
72
|
-
* rather than a patch.
|
|
81
|
+
* rather than a patch. Scales into `aces` and into `shoulder`, the two curves; ignored by
|
|
82
|
+
* `none` and `srgb`, which have none.
|
|
73
83
|
*/
|
|
74
84
|
readonly outputExposure: number;
|
|
75
85
|
/**
|
|
@@ -203,6 +213,24 @@ export interface RenderQuality {
|
|
|
203
213
|
* lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
204
214
|
*/
|
|
205
215
|
readonly glassShadows: GlassShadows;
|
|
216
|
+
/**
|
|
217
|
+
* How light that scatters beneath skin is drawn, for a material whose model is `skinModel`.
|
|
218
|
+
*
|
|
219
|
+
* **`'pre-integrated'`, the default**: the lit stage's fit alone — a terminator softened and
|
|
220
|
+
* reddened by the surface's curvature, light through thin parts, a penumbra each channel crosses
|
|
221
|
+
* as far as it travels — at no cost beyond the skin pipeline itself. **`'screen-space'`** adds
|
|
222
|
+
* Burley's blur in the picture as well: a skin draw writes its diffuse to a target of its own (it
|
|
223
|
+
* is drawn twice to do so), which is spread across the pixels its scatter distance covers and
|
|
224
|
+
* added back before anything blended is drawn, so light crosses a shadow's edge and a nostril's
|
|
225
|
+
* rim in the picture rather than in a fit. See `skinBlur.ts`.
|
|
226
|
+
*
|
|
227
|
+
* Construction-time: two half-float targets the size of the frame, and a multisampled one under
|
|
228
|
+
* multisampling, made at the first skin draw. It needs the composite (`screenEffects`), which is
|
|
229
|
+
* where the frame is still linear light to add to; without one, skin is pre-integrated and the
|
|
230
|
+
* renderer says so once. What would make the default wrong is a close-up face that reads as wax
|
|
231
|
+
* at a shadow's edge — that is what the blur is for.
|
|
232
|
+
*/
|
|
233
|
+
readonly skinScattering: SkinScattering;
|
|
206
234
|
/** Horizontal world-space reach over which a directional shadow dissolves. */
|
|
207
235
|
readonly directionalShadowMaxDistance: number;
|
|
208
236
|
/** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
|
|
@@ -913,6 +941,7 @@ export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
|
|
|
913
941
|
lightVolumeSamples: 32,
|
|
914
942
|
directionalShadowDepthLayers: 2,
|
|
915
943
|
glassShadows: 'full',
|
|
944
|
+
skinScattering: 'pre-integrated',
|
|
916
945
|
directionalShadowMaxDistance: 6,
|
|
917
946
|
directionalShadowMaxSlope: 3,
|
|
918
947
|
pointShadowRebakeDistance: 0.001,
|
|
@@ -1077,6 +1106,7 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
1077
1106
|
directionalShadowDepthLayers:
|
|
1078
1107
|
options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
|
|
1079
1108
|
glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
|
|
1109
|
+
skinScattering: options.skinScattering ?? DEFAULT_RENDER_QUALITY.skinScattering,
|
|
1080
1110
|
directionalShadowMaxDistance:
|
|
1081
1111
|
options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
|
|
1082
1112
|
directionalShadowMaxSlope:
|
|
@@ -1231,6 +1261,11 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
1231
1261
|
`RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`,
|
|
1232
1262
|
);
|
|
1233
1263
|
}
|
|
1264
|
+
if (quality.skinScattering !== 'pre-integrated' && quality.skinScattering !== 'screen-space') {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
`RenderQuality.skinScattering must be 'pre-integrated' or 'screen-space', got ${String(quality.skinScattering)}`,
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1234
1269
|
if (quality.plumeNoiseOctaves > 3) {
|
|
1235
1270
|
throw new Error(
|
|
1236
1271
|
`RenderQuality.plumeNoiseOctaves must be <= 3, got ${quality.plumeNoiseOctaves}`,
|
|
@@ -104,6 +104,10 @@ export function recordingGl(
|
|
|
104
104
|
NONE: 0,
|
|
105
105
|
ALWAYS: 0x0207,
|
|
106
106
|
LESS: 0x0201,
|
|
107
|
+
EQUAL: 0x0202,
|
|
108
|
+
GREATER: 0x0204,
|
|
109
|
+
GEQUAL: 0x0206,
|
|
110
|
+
LEQUAL: 0x0203,
|
|
107
111
|
TEXTURE_MIN_FILTER: 0x2801,
|
|
108
112
|
TEXTURE_MAG_FILTER: 0x2800,
|
|
109
113
|
TEXTURE_WRAP_S: 0x2802,
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
|
|
41
|
+
/** A blended draw that covers the surface by its alpha: `share * (1 - a)`. */
|
|
42
|
+
export const SCENE_ALPHA_COVERS: GPUBlendComponent = {
|
|
43
|
+
srcFactor: 'zero',
|
|
44
|
+
dstFactor: 'one-minus-src-alpha',
|
|
45
|
+
operation: 'add',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** A draw whose alpha is what gets through it, a transmittance or a revealage: `share * a`. */
|
|
49
|
+
export const SCENE_ALPHA_TRANSMITS: GPUBlendComponent = {
|
|
50
|
+
srcFactor: 'zero',
|
|
51
|
+
dstFactor: 'src-alpha',
|
|
52
|
+
operation: 'add',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Light added to the surface, or a layer of it: the share unchanged. */
|
|
56
|
+
export const SCENE_ALPHA_KEEPS: GPUBlendComponent = {
|
|
57
|
+
srcFactor: 'zero',
|
|
58
|
+
dstFactor: 'one',
|
|
59
|
+
operation: 'add',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The same three on WebGL2, as the alpha half of `blendFuncSeparate` beside the colour half a
|
|
64
|
+
* caller already had. `src` and `dst` are the colour factors.
|
|
65
|
+
*/
|
|
66
|
+
export function blendCovering(gl: WebGL2RenderingContext, src: number, dst: number): void {
|
|
67
|
+
gl.blendFuncSeparate(src, dst, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `SCENE_ALPHA_TRANSMITS` on WebGL2, beside the colour factors `src` and `dst`. */
|
|
71
|
+
export function blendTransmitting(gl: WebGL2RenderingContext, src: number, dst: number): void {
|
|
72
|
+
gl.blendFuncSeparate(src, dst, gl.ZERO, gl.SRC_ALPHA);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `SCENE_ALPHA_KEEPS` on WebGL2, beside the colour factors `src` and `dst`. */
|
|
76
|
+
export function blendKeeping(gl: WebGL2RenderingContext, src: number, dst: number): void {
|
|
77
|
+
gl.blendFuncSeparate(src, dst, gl.ZERO, gl.ONE);
|
|
78
|
+
}
|
|
@@ -497,6 +497,27 @@ export class SceneTarget {
|
|
|
497
497
|
* must reject it in the same place or the accumulation and the revealage cover different
|
|
498
498
|
* fragments.
|
|
499
499
|
*/
|
|
500
|
+
/**
|
|
501
|
+
* The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
|
|
502
|
+
* which tests against it and writes none. The multisampled renderbuffer where the frame is
|
|
503
|
+
* multisampled, the texture otherwise, with the size and sample count a colour beside it needs.
|
|
504
|
+
*/
|
|
505
|
+
sharedDepth(): {
|
|
506
|
+
texture: WebGLTexture | null;
|
|
507
|
+
renderbuffer: WebGLRenderbuffer | null;
|
|
508
|
+
samples: number;
|
|
509
|
+
width: number;
|
|
510
|
+
height: number;
|
|
511
|
+
} {
|
|
512
|
+
return {
|
|
513
|
+
texture: this.msaaDepth === null ? this.depth : null,
|
|
514
|
+
renderbuffer: this.msaaDepth,
|
|
515
|
+
samples: this.samples,
|
|
516
|
+
width: this.width,
|
|
517
|
+
height: this.height,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
500
521
|
depthAttachment(): WebGLTexture | null {
|
|
501
522
|
return this.depth;
|
|
502
523
|
}
|
|
@@ -7,6 +7,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
7
7
|
import { SDF_TEXT_FRAG, SDF_TEXT_VERT } from './shaders/sdfText.ts';
|
|
8
8
|
import type { SurfaceTexture } from './surfaceTexture.ts';
|
|
9
9
|
import { SDF_TEXT_TEXTURE_UNIT } from './lightBudget.ts';
|
|
10
|
+
import { blendCovering } from './sceneCoverage.ts';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Text from a signed distance field, on WebGL2.
|
|
@@ -144,7 +145,7 @@ export class SdfTextRenderer {
|
|
|
144
145
|
*/
|
|
145
146
|
gl.depthMask(false);
|
|
146
147
|
gl.enable(gl.BLEND);
|
|
147
|
-
|
|
148
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
148
149
|
|
|
149
150
|
this.atlas.bind(gl, SDF_TEXT_TEXTURE_UNIT);
|
|
150
151
|
|