@driftengine/core 4.8.6 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +132 -0
- package/README.md +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -1
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +35 -0
- package/dist/render/areaLights.js +35 -0
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +41 -3
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/webgl2/renderer.d.ts +140 -16
- package/dist/render/backend/webgl2/renderer.js +436 -44
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
- package/dist/render/backend/webgpu/flatPass.js +58 -26
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +166 -3
- package/dist/render/backend/webgpu/renderer.js +738 -83
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/clusteredLights.d.ts +23 -5
- package/dist/render/clusteredLights.js +35 -8
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +5 -0
- package/dist/render/instances.d.ts +19 -5
- package/dist/render/instances.js +9 -3
- package/dist/render/lightBudget.d.ts +12 -1
- package/dist/render/lightBudget.js +23 -3
- package/dist/render/lightmap.d.ts +23 -9
- package/dist/render/lightmap.js +40 -18
- package/dist/render/particlePool.d.ts +21 -0
- package/dist/render/particlePool.js +7 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +47 -15
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +32 -1
- package/dist/render/screenSpaceReflection.js +14 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +1 -1
- package/dist/render/shaders/flat/areaLight.js +46 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +5 -1
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +22 -2
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +85 -22
- package/dist/render/shaders/flat/models.d.ts +5 -2
- package/dist/render/shaders/flat/models.js +100 -16
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +1 -1
- package/dist/render/shaders/flat/preamble.js +10 -3
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
- package/dist/render/shaders/generated/flat.wgsl.js +230 -54
- 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 +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -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 +13 -2
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +46 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +107 -1
- package/dist/render/surfaceModel.d.ts +29 -8
- package/dist/render/surfaceModel.js +33 -9
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +44 -1
- package/dist/render/surfaceTexture.js +96 -5
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +22 -2
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +58 -0
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +58 -2
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/webgl2/renderer.ts +548 -50
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/flatPass.ts +63 -26
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +927 -100
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
- package/src/render/clusteredLights.ts +41 -8
- package/src/render/displayRange.ts +61 -0
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +4 -0
- package/src/render/instances.ts +26 -6
- package/src/render/lightBudget.ts +40 -2
- package/src/render/lightmap.ts +64 -23
- package/src/render/particlePool.ts +23 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +52 -15
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +38 -1
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +46 -0
- package/src/render/shaders/flat/glassTint.ts +13 -2
- package/src/render/shaders/flat/index.ts +37 -2
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +85 -22
- package/src/render/shaders/flat/models.ts +100 -16
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +1 -1
- package/src/render/shaders/flat/preamble.ts +10 -3
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +230 -54
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +46 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +139 -1
- package/src/render/surfaceModel.ts +47 -10
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +136 -9
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { glslIsFarDepth } from '../depthConvention.ts';
|
|
24
|
+
import { ENV_BRDF_GLSL } from './flat/lobes.ts';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* The most samples the march may take. `uSsrSteps` breaks out early.
|
|
@@ -64,15 +65,34 @@ uniform vec3 uSsrEye;
|
|
|
64
65
|
uniform vec3 uSsrAxis;
|
|
65
66
|
uniform vec3 uSsrTint;
|
|
66
67
|
uniform float uSsrStrength;
|
|
68
|
+
/**
|
|
69
|
+
* Whether \`uSsrStrength\` is the surface's reflectance head-on, carried per pixel through the
|
|
70
|
+
* split-sum BRDF (x, 1 or 0), and the roughness it is carried at (y). \`ReflectiveSurface.fresnel\`.
|
|
71
|
+
*/
|
|
72
|
+
uniform vec2 uSsrFresnel;
|
|
67
73
|
uniform float uSsrFacingCos;
|
|
68
74
|
uniform float uSsrReach;
|
|
69
75
|
uniform float uSsrThickness;
|
|
70
76
|
uniform float uSsrSteps;
|
|
71
77
|
/** How much of the frame each side is fade, as a fraction. */
|
|
72
78
|
uniform float uSsrEdgeFade;
|
|
79
|
+
/**
|
|
80
|
+
* **1 where the trace is the frame's materials rather than a box's**: every pixel the lit stage
|
|
81
|
+
* left a reflection for reflects, by its own material, and the box, its axis and its strength are
|
|
82
|
+
* not read. See \`flat/reflectionSurface.ts\` for the two maps it is handed.
|
|
83
|
+
*/
|
|
84
|
+
uniform float uSsrMaterial;
|
|
85
|
+
/** The roughness past which a pixel's reflection is the probes' alone, faded over the last quarter. */
|
|
86
|
+
uniform float uSsrMaxRoughness;
|
|
87
|
+
/** The environment as the frame shows it, and the surface's roughness in its alpha. */
|
|
88
|
+
uniform sampler2D uSsrProbeMap;
|
|
89
|
+
/** What a found reflection is multiplied by to land where the environment did. */
|
|
90
|
+
uniform sampler2D uSsrTintMap;
|
|
73
91
|
|
|
74
92
|
out vec4 fragColor;
|
|
75
93
|
|
|
94
|
+
${ENV_BRDF_GLSL}
|
|
95
|
+
|
|
76
96
|
/** What the frame drew at a screen position, as a world point. */
|
|
77
97
|
vec3 worldAt(vec2 uv, float stored) {
|
|
78
98
|
vec4 world = uSsrDepthToWorld * vec4(uv * 2.0 - 1.0, stored, 1.0);
|
|
@@ -133,7 +153,32 @@ void main() {
|
|
|
133
153
|
mask *= smoothstep(uSsrFacingCos, min(1.0, uSsrFacingCos + 0.25), facing);
|
|
134
154
|
/* Nothing was drawn here, so there is no surface to reflect in. */
|
|
135
155
|
mask *= ${glslIsFarDepth('stored')} ? 0.0 : 1.0;
|
|
136
|
-
|
|
156
|
+
/*
|
|
157
|
+
* **How much the surface returns**: one number over the box, or, where the caller asks, its
|
|
158
|
+
* reflectance head-on carried through the split-sum BRDF at this pixel's own view and the
|
|
159
|
+
* surface's roughness — the lit stage's fit, so the two agree on what a surface reflects. A floor
|
|
160
|
+
* then returns more toward the horizon than underfoot, and the share follows the camera.
|
|
161
|
+
*/
|
|
162
|
+
float returned = uSsrStrength;
|
|
163
|
+
if (uSsrFresnel.x > 0.5) {
|
|
164
|
+
vec2 dfg = envBrdfApprox(clamp(dot(normal, normalize(toEye)), 0.0, 1.0), uSsrFresnel.y);
|
|
165
|
+
returned = clamp(uSsrStrength * dfg.x + dfg.y, 0.0, 1.0);
|
|
166
|
+
}
|
|
167
|
+
mask *= returned;
|
|
168
|
+
/*
|
|
169
|
+
* **Or the pixel's own material**, where the trace is the frame's: reflective wherever the lit
|
|
170
|
+
* stage left a reflection to swap (any tint above zero), and the probes' alone past the
|
|
171
|
+
* roughness ceiling. How much it returns is the tint, which the resolve multiplies by, so the
|
|
172
|
+
* mask here is only whether to march and how much to trust a hit.
|
|
173
|
+
*/
|
|
174
|
+
if (uSsrMaterial > 0.5) {
|
|
175
|
+
vec3 tint = textureLod(uSsrTintMap, vUv, 0.0).rgb;
|
|
176
|
+
float roughness = textureLod(uSsrProbeMap, vUv, 0.0).a;
|
|
177
|
+
float reflective = max(max(tint.r, tint.g), tint.b) > 0.0 ? 1.0 : 0.0;
|
|
178
|
+
float ceiling = max(uSsrMaxRoughness, 1.0e-4);
|
|
179
|
+
mask = reflective * (1.0 - smoothstep(ceiling * 0.75, ceiling, roughness));
|
|
180
|
+
mask *= ${glslIsFarDepth('stored')} ? 0.0 : 1.0;
|
|
181
|
+
}
|
|
137
182
|
|
|
138
183
|
vec3 found = vec3(0.0);
|
|
139
184
|
float weight = 0.0;
|
|
@@ -177,8 +177,11 @@ uniform vec3 uAmbient;
|
|
|
177
177
|
* is the single strongest cue that a surface *is* water switched off because no
|
|
178
178
|
* directional source existed to make it. A lantern on a jetty threw nothing.
|
|
179
179
|
*
|
|
180
|
-
* Same slots, same weights and the
|
|
181
|
-
* lamp
|
|
180
|
+
* Same slots, same weights and the surface shader's falloffs, a light's own exponent among
|
|
181
|
+
* them, so a lamp reaches as far over water as over the ground beside it. **What it gives up**:
|
|
182
|
+
* a spot's cone, which water does not read, so a spot lights the water around it as a lamp
|
|
183
|
+
* would; and the smooth falloff's square, which the surface applies and this has never, so under
|
|
184
|
+
* that falloff a lamp's pool on water is the wider of the two.
|
|
182
185
|
*/
|
|
183
186
|
#define MAX_LIGHTS ${MAX_POINT_LIGHTS}
|
|
184
187
|
uniform int uLightCount;
|
|
@@ -187,6 +190,8 @@ uniform vec3 uLightColor[MAX_LIGHTS];
|
|
|
187
190
|
uniform float uLightRadius[MAX_LIGHTS];
|
|
188
191
|
uniform float uLightWeight[MAX_LIGHTS];
|
|
189
192
|
uniform int uLightFalloff;
|
|
193
|
+
/* The surface shader's cone record, of which water reads z alone: a light's own falloff exponent. */
|
|
194
|
+
uniform vec4 uLightCone[MAX_LIGHTS];
|
|
190
195
|
${FOG_GLSL}
|
|
191
196
|
uniform vec3 uCameraPos;
|
|
192
197
|
uniform sampler2D uReflectionMap;
|
|
@@ -243,7 +248,11 @@ void main() {
|
|
|
243
248
|
vec3 toLight = uLightPos[i] - vWorldPos;
|
|
244
249
|
float lightDist = length(toLight);
|
|
245
250
|
float falloff;
|
|
246
|
-
|
|
251
|
+
float exponent = uLightCone[i].z;
|
|
252
|
+
if (exponent > 0.0) {
|
|
253
|
+
float reach = lightDist / max(uLightRadius[i], 1e-4);
|
|
254
|
+
falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), exponent);
|
|
255
|
+
} else if (uLightFalloff == 1) {
|
|
247
256
|
float window = clamp(1.0 - pow(lightDist / max(uLightRadius[i], 1e-4), 4.0), 0.0, 1.0);
|
|
248
257
|
falloff = window * window / max(lightDist * lightDist, 0.01);
|
|
249
258
|
} else {
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* neighbours' `N·L` as well as its own: the clamped cosine averaged around a ring of the surface's
|
|
8
8
|
* curvature, weighted by the diffusion profile at each neighbour's distance. The profile is Burley's
|
|
9
9
|
* normalised diffusion, one per channel, at a scatter distance `d` — the model's `radius` times its
|
|
10
|
-
* scatter colour
|
|
10
|
+
* scatter colour, over `SKIN_MEAN_EXIT`, since `radius` is how far light travels and the profile
|
|
11
|
+
* carries it two and a half `d` — projected onto the ring's plane, as a cylinder's cross-section
|
|
12
|
+
* sees it. The
|
|
11
13
|
* result depends on `N·L` and on one number per channel, `k = d × curvature`, and at `k = 0` it is
|
|
12
14
|
* Lambert's.
|
|
13
15
|
*
|
package/src/render/ssrPass.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.ts';
|
|
|
6
6
|
import type { ReflectionQueue } from './screenSpaceReflection.ts';
|
|
7
7
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
8
8
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
|
|
9
|
-
import { SSR_RESOLVE_FRAG } from './shaders/ssrResolve.ts';
|
|
9
|
+
import { SSR_MATERIAL_RESOLVE_FRAG, SSR_RESOLVE_FRAG } from './shaders/ssrResolve.ts';
|
|
10
|
+
import type { ResolvedFrameReflections } from './frameReflections.ts';
|
|
10
11
|
import { SSR_TRACE_FRAG } from './shaders/ssrTrace.ts';
|
|
11
12
|
import { blendKeeping } from './sceneCoverage.ts';
|
|
12
13
|
|
|
@@ -36,6 +37,10 @@ export class SsrPass {
|
|
|
36
37
|
private height = 0;
|
|
37
38
|
|
|
38
39
|
/** `inverse(viewProjection) * DEPTH_01_TO_CLIP`, rebuilt once a frame rather than per surface. */
|
|
40
|
+
/** The frame's materials' resolve, compiled the first time a frame traces them. */
|
|
41
|
+
private materialResolve: WebGLProgram | null = null;
|
|
42
|
+
private materialUniforms: Record<string, WebGLUniformLocation | null> = {};
|
|
43
|
+
|
|
39
44
|
private readonly depthToWorld = new Float32Array(16);
|
|
40
45
|
private readonly scissor = new Int32Array(4);
|
|
41
46
|
|
|
@@ -150,6 +155,11 @@ export class SsrPass {
|
|
|
150
155
|
gl.activeTexture(gl.TEXTURE1);
|
|
151
156
|
gl.bindTexture(gl.TEXTURE_2D, scene);
|
|
152
157
|
gl.uniform1i(u['uSsrScene'] ?? null, 1);
|
|
158
|
+
/* The materials' maps unread in this mode, and pointed at a bound texture all the same: a
|
|
159
|
+
driver may fetch a sampler's descriptor before the branch that skips it. */
|
|
160
|
+
gl.uniform1i(u['uSsrProbeMap'] ?? null, 0);
|
|
161
|
+
gl.uniform1i(u['uSsrTintMap'] ?? null, 0);
|
|
162
|
+
gl.uniform1f(u['uSsrMaterial'] ?? null, 0);
|
|
153
163
|
gl.uniformMatrix4fv(u['uSsrDepthToWorld'] ?? null, false, this.depthToWorld);
|
|
154
164
|
/* No Y flip on this backend: `gl.scissor` and the framebuffer both count rows from the bottom,
|
|
155
165
|
which is the space the raw matrix already emits. See `CLIP_Y_FLIP`. */
|
|
@@ -173,6 +183,7 @@ export class SsrPass {
|
|
|
173
183
|
gl.uniform3fv(u['uSsrAxis'] ?? null, surface.axis);
|
|
174
184
|
gl.uniform3fv(u['uSsrTint'] ?? null, surface.tint);
|
|
175
185
|
gl.uniform1f(u['uSsrStrength'] ?? null, surface.strength);
|
|
186
|
+
gl.uniform2f(u['uSsrFresnel'] ?? null, surface.fresnel ? 1 : 0, surface.roughness);
|
|
176
187
|
gl.uniform1f(u['uSsrFacingCos'] ?? null, surface.facingCos);
|
|
177
188
|
gl.uniform1f(u['uSsrReach'] ?? null, surface.reachM);
|
|
178
189
|
gl.uniform1f(u['uSsrThickness'] ?? null, surface.thicknessM);
|
|
@@ -194,6 +205,131 @@ export class SsrPass {
|
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
/** Composite what the trace found over whatever framebuffer is bound. */
|
|
208
|
+
/**
|
|
209
|
+
* Every pixel the lit stage left a reflection for, marched against the frame's depth by the
|
|
210
|
+
* trace's material mode, into this pass's own target; `resolveMaterials` then swaps the probe's
|
|
211
|
+
* share for it. See `frameReflections.ts`. Ends on the default framebuffer, as the surfaces' does.
|
|
212
|
+
*/
|
|
213
|
+
traceMaterials(
|
|
214
|
+
scene: WebGLTexture,
|
|
215
|
+
depth: WebGLTexture,
|
|
216
|
+
probe: WebGLTexture,
|
|
217
|
+
tint: WebGLTexture,
|
|
218
|
+
viewProjection: ReadonlyMat4,
|
|
219
|
+
eye: ArrayLike<number>,
|
|
220
|
+
width: number,
|
|
221
|
+
height: number,
|
|
222
|
+
edgeFade: number,
|
|
223
|
+
reflections: ResolvedFrameReflections,
|
|
224
|
+
): void {
|
|
225
|
+
const { gl } = this;
|
|
226
|
+
const u = this.traceUniforms;
|
|
227
|
+
this.ensureSize(width, height);
|
|
228
|
+
mat4.invert(this.depthToWorld, viewProjection);
|
|
229
|
+
mat4.multiply(this.depthToWorld, this.depthToWorld, DEPTH_01_TO_CLIP);
|
|
230
|
+
|
|
231
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
232
|
+
gl.viewport(0, 0, width, height);
|
|
233
|
+
gl.disable(gl.DEPTH_TEST);
|
|
234
|
+
gl.depthMask(false);
|
|
235
|
+
gl.disable(gl.CULL_FACE);
|
|
236
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
237
|
+
gl.disable(gl.BLEND);
|
|
238
|
+
gl.clearBufferfv(gl.COLOR, 0, [0, 0, 0, 0]);
|
|
239
|
+
|
|
240
|
+
gl.useProgram(this.trace);
|
|
241
|
+
gl.bindVertexArray(this.vao);
|
|
242
|
+
const textures = [depth, scene, probe, tint];
|
|
243
|
+
const names = ['uSsrDepth', 'uSsrScene', 'uSsrProbeMap', 'uSsrTintMap'];
|
|
244
|
+
for (let unit = 0; unit < 4; unit++) {
|
|
245
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
246
|
+
gl.bindTexture(gl.TEXTURE_2D, textures[unit] ?? null);
|
|
247
|
+
gl.uniform1i(u[names[unit] ?? ''] ?? null, unit);
|
|
248
|
+
}
|
|
249
|
+
gl.uniformMatrix4fv(u['uSsrDepthToWorld'] ?? null, false, this.depthToWorld);
|
|
250
|
+
gl.uniformMatrix4fv(u['uSsrViewProj'] ?? null, false, viewProjection as Float32Array);
|
|
251
|
+
gl.uniform3f(u['uSsrEye'] ?? null, eye[0] ?? 0, eye[1] ?? 0, eye[2] ?? 0);
|
|
252
|
+
gl.uniform1f(u['uSsrEdgeFade'] ?? null, edgeFade);
|
|
253
|
+
gl.uniform3f(u['uSsrAxis'] ?? null, 0, 0, 0);
|
|
254
|
+
gl.uniform3f(u['uSsrTint'] ?? null, 1, 1, 1);
|
|
255
|
+
gl.uniform1f(u['uSsrStrength'] ?? null, 1);
|
|
256
|
+
gl.uniform2f(u['uSsrFresnel'] ?? null, 0, 0);
|
|
257
|
+
gl.uniform1f(u['uSsrFacingCos'] ?? null, -1);
|
|
258
|
+
gl.uniform1f(u['uSsrReach'] ?? null, reflections.reachM);
|
|
259
|
+
gl.uniform1f(u['uSsrThickness'] ?? null, reflections.thicknessM);
|
|
260
|
+
gl.uniform1f(u['uSsrSteps'] ?? null, reflections.steps);
|
|
261
|
+
gl.uniform1f(u['uSsrMaterial'] ?? null, 1);
|
|
262
|
+
gl.uniform1f(u['uSsrMaxRoughness'] ?? null, reflections.maxRoughness);
|
|
263
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
264
|
+
/* Back to the surfaces' mode, which every other trace in the frame is. */
|
|
265
|
+
gl.uniform1f(u['uSsrMaterial'] ?? null, 0);
|
|
266
|
+
|
|
267
|
+
gl.bindVertexArray(null);
|
|
268
|
+
for (let unit = 3; unit >= 0; unit--) {
|
|
269
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
270
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
271
|
+
}
|
|
272
|
+
gl.depthMask(true);
|
|
273
|
+
gl.enable(gl.DEPTH_TEST);
|
|
274
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The probe's share of each pixel swapped for what `traceMaterials` found, blurred by the
|
|
279
|
+
* surface's roughness, added onto whatever framebuffer is bound — the scene's, with the two maps
|
|
280
|
+
* already detached from it. See `SSR_MATERIAL_RESOLVE_FRAG`.
|
|
281
|
+
*/
|
|
282
|
+
resolveMaterials(
|
|
283
|
+
probe: WebGLTexture,
|
|
284
|
+
tint: WebGLTexture,
|
|
285
|
+
width: number,
|
|
286
|
+
height: number,
|
|
287
|
+
reflections: ResolvedFrameReflections,
|
|
288
|
+
): void {
|
|
289
|
+
const { gl } = this;
|
|
290
|
+
if (this.reflection === null) return;
|
|
291
|
+
if (this.materialResolve === null) {
|
|
292
|
+
this.materialResolve = compileProgram(
|
|
293
|
+
gl,
|
|
294
|
+
FULLSCREEN_VERT,
|
|
295
|
+
SSR_MATERIAL_RESOLVE_FRAG,
|
|
296
|
+
'ssrMaterialResolve',
|
|
297
|
+
);
|
|
298
|
+
this.materialUniforms = uniformLocations(gl, this.materialResolve, 'ssrMaterialResolve');
|
|
299
|
+
}
|
|
300
|
+
const u = this.materialUniforms;
|
|
301
|
+
gl.disable(gl.DEPTH_TEST);
|
|
302
|
+
gl.depthMask(false);
|
|
303
|
+
gl.disable(gl.CULL_FACE);
|
|
304
|
+
gl.enable(gl.BLEND);
|
|
305
|
+
/* Added, a negative addend subtracting, and the scene's alpha kept. */
|
|
306
|
+
blendKeeping(gl, gl.ONE, gl.ONE);
|
|
307
|
+
gl.useProgram(this.materialResolve);
|
|
308
|
+
const textures = [this.reflection, probe, tint];
|
|
309
|
+
const names = ['uSsrReflection', 'uSsrProbeMap', 'uSsrTintMap'];
|
|
310
|
+
for (let unit = 0; unit < 3; unit++) {
|
|
311
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
312
|
+
gl.bindTexture(gl.TEXTURE_2D, textures[unit] ?? null);
|
|
313
|
+
gl.uniform1i(u[names[unit] ?? ''] ?? null, unit);
|
|
314
|
+
}
|
|
315
|
+
gl.uniform3f(
|
|
316
|
+
u['uSsrBlur'] ?? null,
|
|
317
|
+
1 / Math.max(width, 1),
|
|
318
|
+
1 / Math.max(height, 1),
|
|
319
|
+
reflections.blur * height,
|
|
320
|
+
);
|
|
321
|
+
gl.bindVertexArray(this.vao);
|
|
322
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
323
|
+
gl.bindVertexArray(null);
|
|
324
|
+
for (let unit = 2; unit >= 0; unit--) {
|
|
325
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
326
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
327
|
+
}
|
|
328
|
+
gl.disable(gl.BLEND);
|
|
329
|
+
gl.depthMask(true);
|
|
330
|
+
gl.enable(gl.DEPTH_TEST);
|
|
331
|
+
}
|
|
332
|
+
|
|
197
333
|
resolveReflections(): void {
|
|
198
334
|
const { gl } = this;
|
|
199
335
|
if (this.reflection === null) return;
|
|
@@ -233,6 +369,8 @@ export class SsrPass {
|
|
|
233
369
|
if (this.framebuffer !== null) gl.deleteFramebuffer(this.framebuffer);
|
|
234
370
|
gl.deleteProgram(this.trace);
|
|
235
371
|
gl.deleteProgram(this.resolve);
|
|
372
|
+
if (this.materialResolve !== null) gl.deleteProgram(this.materialResolve);
|
|
373
|
+
this.materialResolve = null;
|
|
236
374
|
gl.deleteVertexArray(this.vao);
|
|
237
375
|
this.reflection = null;
|
|
238
376
|
this.framebuffer = null;
|
|
@@ -54,7 +54,12 @@ export interface SkinModel {
|
|
|
54
54
|
readonly kind: 'skin';
|
|
55
55
|
/** The colour light keeps travelling beneath the surface — the mean free path, as a colour. */
|
|
56
56
|
readonly scatterColor: Vec3;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Metres: how far light travels beneath it, on average, before it leaves — from where it entered
|
|
59
|
+
* to where it leaves, for a channel whose share of `scatterColor` is 1. Burley's profile, which
|
|
60
|
+
* the lit stage and the screen-space blur both scatter by, does that at a distance `d` two fifths
|
|
61
|
+
* of it, and carries a tail of light out to about three and a half times it.
|
|
62
|
+
*/
|
|
58
63
|
readonly radius: number;
|
|
59
64
|
/** 0 to 1: how much a light behind a thin part shows through it. */
|
|
60
65
|
readonly transmission: number;
|
|
@@ -159,7 +164,25 @@ export function hairModel(
|
|
|
159
164
|
});
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* How far Burley's normalised diffusion carries light on average, in units of its own distance
|
|
169
|
+
* `d`: its radial density `(e^(−r/d) + e^(−r/3d)) / 4d` has the mean `(d² + 9d²) / 4d = 2.5 d`. So a
|
|
170
|
+
* skin whose light travels `radius` scatters by a profile at `d = radius / 2.5`.
|
|
171
|
+
*
|
|
172
|
+
* **Until 4.8.7 `radius` was `d` itself**, so light travelled two and a half times as far as the
|
|
173
|
+
* model said, and the screen-space blur — whose taps reach nine `d` — spread a face at arm's length
|
|
174
|
+
* over sixty pixels: a nose, lips and brows washed into one blur where a centimetre covers a few.
|
|
175
|
+
* The lit stage's pre-integrated fit, transmission and penumbra take the same `d`, so the two paths
|
|
176
|
+
* still agree with each other.
|
|
177
|
+
*/
|
|
178
|
+
export const SKIN_MEAN_EXIT = 2.5;
|
|
179
|
+
|
|
180
|
+
/** Burley's distance `d` for one channel of a skin, in metres: see `SKIN_MEAN_EXIT`. */
|
|
181
|
+
export function skinProfileDistance(model: SkinModel, channel: 0 | 1 | 2): number {
|
|
182
|
+
return ((model.scatterColor[channel] as number) * model.radius) / SKIN_MEAN_EXIT;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Skin. The defaults are a fair skin's: red light leaving a little over a centimetre away. */
|
|
163
186
|
export function skinModel(
|
|
164
187
|
options: { scatterColor?: Vec3; radius?: number; transmission?: number; profile?: number } = {},
|
|
165
188
|
): SkinModel {
|
|
@@ -218,24 +241,36 @@ export function lightmapModel(options: { region?: LightmapRegion } = {}): Lightm
|
|
|
218
241
|
|
|
219
242
|
/**
|
|
220
243
|
* A model's numbers into `out` — eight floats, `uModelParams[0]` then `[1]` — and whether a model
|
|
221
|
-
* map is bound into the last.
|
|
222
|
-
*
|
|
244
|
+
* map is bound into the last. An eye's axis goes in with the descriptor's own until the draw turns
|
|
245
|
+
* it: see `eyeAxisInWorld`. `physical` is `SurfaceMaterial.physicalSpecular`, in the lane the
|
|
246
|
+
* models that read it leave free; skin, hair and the eye shade their own highlights and never do.
|
|
223
247
|
*
|
|
224
|
-
* -
|
|
248
|
+
* - standard (null): − · −, −, physical, −
|
|
249
|
+
* - anisotropic: strength, cos and sin of the rotation · −, −, physical, map
|
|
225
250
|
* - hair: shift, scatter, backlit, − · −, −, −, map
|
|
226
|
-
* - skin: the scatter colour, radius · transmission, profile, −, map
|
|
251
|
+
* - skin: the scatter colour, radius / `SKIN_MEAN_EXIT` · transmission, profile, −, map
|
|
227
252
|
* - eye: iris radius, iris depth, ior, cornea roughness · the axis, map
|
|
228
|
-
* - lightmap: the region's scale and bias · −, −,
|
|
253
|
+
* - lightmap: the region's scale and bias · −, −, physical, map
|
|
229
254
|
*/
|
|
230
|
-
export function packModel(
|
|
255
|
+
export function packModel(
|
|
256
|
+
model: SurfaceModel | null,
|
|
257
|
+
mapped: boolean,
|
|
258
|
+
out: Float32Array,
|
|
259
|
+
physical = false,
|
|
260
|
+
): void {
|
|
231
261
|
out.fill(0);
|
|
232
|
-
|
|
262
|
+
const highlight = physical ? 1 : 0;
|
|
263
|
+
if (model === null) {
|
|
264
|
+
out[6] = highlight;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
233
267
|
out[7] = mapped ? 1 : 0;
|
|
234
268
|
switch (model.kind) {
|
|
235
269
|
case 'anisotropic':
|
|
236
270
|
out[0] = model.strength;
|
|
237
271
|
out[1] = Math.cos(model.rotation);
|
|
238
272
|
out[2] = Math.sin(model.rotation);
|
|
273
|
+
out[6] = highlight;
|
|
239
274
|
return;
|
|
240
275
|
case 'hair':
|
|
241
276
|
out[0] = model.shift;
|
|
@@ -246,7 +281,8 @@ export function packModel(model: SurfaceModel | null, mapped: boolean, out: Floa
|
|
|
246
281
|
out[0] = model.scatterColor[0];
|
|
247
282
|
out[1] = model.scatterColor[1];
|
|
248
283
|
out[2] = model.scatterColor[2];
|
|
249
|
-
|
|
284
|
+
/* Burley's distance for a channel of share 1, which the shader scales by each share. */
|
|
285
|
+
out[3] = model.radius / SKIN_MEAN_EXIT;
|
|
250
286
|
out[4] = model.transmission;
|
|
251
287
|
out[5] = model.profile;
|
|
252
288
|
return;
|
|
@@ -264,6 +300,7 @@ export function packModel(model: SurfaceModel | null, mapped: boolean, out: Floa
|
|
|
264
300
|
out[1] = model.region[1];
|
|
265
301
|
out[2] = model.region[2];
|
|
266
302
|
out[3] = model.region[3];
|
|
303
|
+
out[6] = highlight;
|
|
267
304
|
return;
|
|
268
305
|
}
|
|
269
306
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a draw lays over its surface beyond its material: a rim of light, an emissive overlay that
|
|
3
|
+
* dissolves the surface away, and a second normal map blended in by region. Set per draw with
|
|
4
|
+
* `RendererApi.setSurfaceOverlay`, so one character's whole set of materials changes together
|
|
5
|
+
* while another's does not.
|
|
6
|
+
*
|
|
7
|
+
* **Per draw and held, like `setEnvironmentGain`**: set before a character's draws, cleared after,
|
|
8
|
+
* and every draw between them wears it. **Images come from one atlas** (`maps`), each named by the
|
|
9
|
+
* rectangle it occupies — a scale and an offset in the atlas's uv — because the lit stage binds every
|
|
10
|
+
* sampler a device is guaranteed and has none left for a texture of its own: the atlas is bound in
|
|
11
|
+
* the slot the frame's refraction copy takes, which an opaque surface never reads. A draw that both
|
|
12
|
+
* refracts and wears an overlay's images keeps its refraction and loses the images, said once.
|
|
13
|
+
*
|
|
14
|
+
* **Fifteen vectors of numbers**, in the material's uniform block. WebGPU has the room; WebGL2 has
|
|
15
|
+
* room where its device reports more than the lit stage's 254 vectors, and refuses the overlay in
|
|
16
|
+
* words where it does not. See `packSurfaceOverlay` for the layout.
|
|
17
|
+
*/
|
|
18
|
+
import type { Vec3 } from '../math/color.ts';
|
|
19
|
+
import type { SurfaceTextureHandle } from './backend/api.ts';
|
|
20
|
+
|
|
21
|
+
/** Where an image sits in the atlas: its size and its corner, each in the atlas's uv. */
|
|
22
|
+
export interface OverlayRegion {
|
|
23
|
+
readonly scale: readonly [number, number];
|
|
24
|
+
readonly offset: readonly [number, number];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Light along the silhouette: the edge `(1 - N·V)^falloff`, leaning toward normals that face up,
|
|
29
|
+
* raised to a contrast, times a noise laid in screen space, a slow pulse and a mask, in a colour.
|
|
30
|
+
* Added to what the surface emits, and not gated on the night as emission is: it is a glow the
|
|
31
|
+
* surface wears, not light it gives off.
|
|
32
|
+
*/
|
|
33
|
+
export interface SurfaceRim {
|
|
34
|
+
readonly colour: Vec3;
|
|
35
|
+
/** How bright, 0 for none. Multiplied with `alpha`. */
|
|
36
|
+
readonly intensity: number;
|
|
37
|
+
readonly alpha?: number;
|
|
38
|
+
/** The edge's exponent: larger keeps the rim nearer the silhouette. 1.5 unless given. */
|
|
39
|
+
readonly falloff?: number;
|
|
40
|
+
/**
|
|
41
|
+
* How far the rim leans toward normals that face up: the weight is `(N·up · ½ + ½)^upward`, so
|
|
42
|
+
* 0 is no lean. 0.4 unless given.
|
|
43
|
+
*/
|
|
44
|
+
readonly upward?: number;
|
|
45
|
+
/** The exponent the whole edge is raised to. 1 unless given. */
|
|
46
|
+
readonly contrast?: number;
|
|
47
|
+
/** A noise image laid in screen space and scrolled, the rim taken times it, clamped to 1. */
|
|
48
|
+
readonly noise?: {
|
|
49
|
+
readonly region: OverlayRegion;
|
|
50
|
+
/** Screens a second, along x and y. */
|
|
51
|
+
readonly scroll?: readonly [number, number];
|
|
52
|
+
/** Repeats across the screen's height. 1 unless given. */
|
|
53
|
+
readonly tiling?: number;
|
|
54
|
+
};
|
|
55
|
+
/** A pulse `½ + ½ sin(rate · t)`, held between `low` and `high`, on `Environment.surfaceTime`. */
|
|
56
|
+
readonly pulse?: { readonly rate: number; readonly low: number; readonly high: number };
|
|
57
|
+
/** A mask in the mesh's uv where the rim is erased: the rim times `1 - weight · mask`. */
|
|
58
|
+
readonly mask?: { readonly region: OverlayRegion; readonly weight: number };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The surface cut away where a noise in its uv falls under `threshold`, with a glowing band just
|
|
63
|
+
* above it, and a colour laid over the surface through the same noise.
|
|
64
|
+
*/
|
|
65
|
+
export interface SurfaceDissolve {
|
|
66
|
+
readonly noise: { readonly region: OverlayRegion; readonly tiling?: readonly [number, number] };
|
|
67
|
+
/** 0 keeps everything; 1 cuts everything. */
|
|
68
|
+
readonly threshold: number;
|
|
69
|
+
/** The band's width, in the noise's own units. 0.05 unless given. */
|
|
70
|
+
readonly edge?: number;
|
|
71
|
+
/** The band's light. */
|
|
72
|
+
readonly edgeColour?: Vec3;
|
|
73
|
+
readonly edgeIntensity?: number;
|
|
74
|
+
/** A colour laid over the surface through the noise, as light, and how much of it. */
|
|
75
|
+
readonly overlay?: { readonly colour: Vec3; readonly amount: number };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A second normal map blended into the surface's by six region weights, read through two masks:
|
|
80
|
+
* the first mask's red, green and blue carry regions 1 to 3, the second's regions 4 to 6. A face's
|
|
81
|
+
* expressions, or veins brought up all over.
|
|
82
|
+
*/
|
|
83
|
+
export interface SurfaceWrinkle {
|
|
84
|
+
readonly normal: OverlayRegion;
|
|
85
|
+
readonly masks: readonly [OverlayRegion, OverlayRegion];
|
|
86
|
+
readonly weights: readonly [number, number, number, number, number, number];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* What a draw lays over its surface beyond its material, for `setSurfaceOverlay`: a rim, a dissolve
|
|
91
|
+
* and wrinkles, each optional, and the atlas their images are regions of.
|
|
92
|
+
*/
|
|
93
|
+
export interface SurfaceOverlay {
|
|
94
|
+
/**
|
|
95
|
+
* The atlas every image above is a region of. Absent: no image is read whatever regions are
|
|
96
|
+
* named — rims are smooth, nothing dissolves and nothing wrinkles — because the slot then holds a
|
|
97
|
+
* stand-in whose colour would read as a noise and a mask.
|
|
98
|
+
*/
|
|
99
|
+
readonly maps?: SurfaceTextureHandle;
|
|
100
|
+
readonly rim?: SurfaceRim;
|
|
101
|
+
readonly dissolve?: SurfaceDissolve;
|
|
102
|
+
readonly wrinkle?: SurfaceWrinkle;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Vectors the overlay takes in the material's block: `uOverlay[OVERLAY_VECTORS]`. */
|
|
106
|
+
export const OVERLAY_VECTORS = 15;
|
|
107
|
+
/** Floats `packSurfaceOverlay` fills. */
|
|
108
|
+
export const OVERLAY_FLOATS = OVERLAY_VECTORS * 4;
|
|
109
|
+
|
|
110
|
+
const finite = (value: number | undefined, fallback: number): number =>
|
|
111
|
+
value !== undefined && Number.isFinite(value) ? value : fallback;
|
|
112
|
+
|
|
113
|
+
function region(out: Float32Array, at: number, r: OverlayRegion | undefined | null): void {
|
|
114
|
+
out[at] = finite(r?.scale[0], 0);
|
|
115
|
+
out[at + 1] = finite(r?.scale[1], 0);
|
|
116
|
+
out[at + 2] = finite(r?.offset[0], 0);
|
|
117
|
+
out[at + 3] = finite(r?.offset[1], 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An overlay as the shader reads it, into `out` (`OVERLAY_FLOATS`); all zeros for `null`, which is
|
|
122
|
+
* no overlay. A region with a zero scale is an image not given, and its term reads as none — a
|
|
123
|
+
* noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
|
|
124
|
+
*
|
|
125
|
+
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
|
|
126
|
+
* - 1: falloff, upward lean, contrast, mask weight
|
|
127
|
+
* - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
|
|
128
|
+
* - 4: the pulse's low and high (1 and 1 where none)
|
|
129
|
+
* - 5: the rim's mask region
|
|
130
|
+
* - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
|
|
131
|
+
* - 8: the edge's light and its strength; 9: the laid-over colour and its amount
|
|
132
|
+
* - 10: the wrinkle normal's region; 11 and 12: the two masks'; 13 and 14: the six weights
|
|
133
|
+
*/
|
|
134
|
+
export function packSurfaceOverlay(overlay: SurfaceOverlay | null, out: Float32Array): void {
|
|
135
|
+
out.fill(0);
|
|
136
|
+
if (overlay === null) return;
|
|
137
|
+
/* No atlas, no region: see `SurfaceOverlay.maps`. */
|
|
138
|
+
const images = overlay.maps !== undefined;
|
|
139
|
+
const rim = overlay.rim;
|
|
140
|
+
if (rim !== undefined) {
|
|
141
|
+
out[0] = finite(rim.colour[0], 0);
|
|
142
|
+
out[1] = finite(rim.colour[1], 0);
|
|
143
|
+
out[2] = finite(rim.colour[2], 0);
|
|
144
|
+
out[3] = Math.max(0, finite(rim.intensity, 0) * finite(rim.alpha, 1));
|
|
145
|
+
out[4] = Math.max(0, finite(rim.falloff, 1.5));
|
|
146
|
+
out[5] = Math.max(0, finite(rim.upward, 0.4));
|
|
147
|
+
out[6] = Math.max(0, finite(rim.contrast, 1));
|
|
148
|
+
out[7] = rim.mask === undefined ? 0 : Math.min(Math.max(finite(rim.mask.weight, 0), 0), 1);
|
|
149
|
+
region(out, 8, images ? rim.noise?.region : null);
|
|
150
|
+
out[12] = finite(rim.noise?.scroll?.[0], 0);
|
|
151
|
+
out[13] = finite(rim.noise?.scroll?.[1], 0);
|
|
152
|
+
out[14] = Math.max(0, finite(rim.noise?.tiling, 1));
|
|
153
|
+
out[15] = finite(rim.pulse?.rate, 0);
|
|
154
|
+
out[16] = rim.pulse === undefined ? 1 : finite(rim.pulse.low, 1);
|
|
155
|
+
out[17] = rim.pulse === undefined ? 1 : finite(rim.pulse.high, 1);
|
|
156
|
+
region(out, 20, images ? rim.mask?.region : null);
|
|
157
|
+
}
|
|
158
|
+
const dissolve = overlay.dissolve;
|
|
159
|
+
if (dissolve !== undefined) {
|
|
160
|
+
region(out, 24, images ? dissolve.noise.region : null);
|
|
161
|
+
out[28] = finite(dissolve.noise.tiling?.[0], 1);
|
|
162
|
+
out[29] = finite(dissolve.noise.tiling?.[1], 1);
|
|
163
|
+
out[30] = Math.min(Math.max(finite(dissolve.threshold, 0), 0), 1);
|
|
164
|
+
out[31] = Math.max(1e-4, finite(dissolve.edge, 0.05));
|
|
165
|
+
const edge = dissolve.edgeColour ?? [0, 0, 0];
|
|
166
|
+
out[32] = finite(edge[0], 0);
|
|
167
|
+
out[33] = finite(edge[1], 0);
|
|
168
|
+
out[34] = finite(edge[2], 0);
|
|
169
|
+
out[35] = Math.max(0, finite(dissolve.edgeIntensity, 1));
|
|
170
|
+
const laid = dissolve.overlay;
|
|
171
|
+
if (laid !== undefined) {
|
|
172
|
+
out[36] = finite(laid.colour[0], 0);
|
|
173
|
+
out[37] = finite(laid.colour[1], 0);
|
|
174
|
+
out[38] = finite(laid.colour[2], 0);
|
|
175
|
+
out[39] = Math.max(0, finite(laid.amount, 0));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const wrinkle = overlay.wrinkle;
|
|
179
|
+
if (wrinkle !== undefined) {
|
|
180
|
+
region(out, 40, images ? wrinkle.normal : null);
|
|
181
|
+
region(out, 44, images ? wrinkle.masks[0] : null);
|
|
182
|
+
region(out, 48, images ? wrinkle.masks[1] : null);
|
|
183
|
+
for (let i = 0; i < 6; i++) {
|
|
184
|
+
out[52 + i] = Math.min(Math.max(finite(wrinkle.weights[i], 0), 0), 1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Whether an overlay lays anything at all, so a renderer can leave the lit switch off. */
|
|
190
|
+
export function overlayLays(overlay: SurfaceOverlay | null): boolean {
|
|
191
|
+
return (
|
|
192
|
+
overlay !== null &&
|
|
193
|
+
(overlay.rim !== undefined || overlay.dissolve !== undefined || overlay.wrinkle !== undefined)
|
|
194
|
+
);
|
|
195
|
+
}
|