@driftengine/core 4.8.5 → 4.8.7
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 +4392 -4250
- package/README.md +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -2
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +54 -0
- package/dist/render/areaLights.js +55 -0
- package/dist/render/backend/api.d.ts +43 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +120 -11
- package/dist/render/backend/webgl2/renderer.js +356 -53
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +81 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +93 -8
- package/dist/render/backend/webgpu/renderer.js +483 -93
- 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/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +78 -6
- package/dist/render/clusteredLights.js +214 -13
- 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/fixtureAtlas.js +1 -1
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +12 -0
- package/dist/render/instances.d.ts +47 -11
- package/dist/render/instances.js +24 -9
- package/dist/render/lightBudget.d.ts +38 -2
- package/dist/render/lightBudget.js +48 -5
- package/dist/render/lightmap.d.ts +116 -0
- package/dist/render/lightmap.js +140 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +32 -0
- package/dist/render/particlePool.js +9 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/renderQuality.d.ts +8 -7
- 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 +8 -1
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/depth.js +11 -0
- 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 +10 -0
- package/dist/render/shaders/flat/areaLight.js +142 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +3 -1
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +46 -6
- 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 +158 -121
- package/dist/render/shaders/flat/models.d.ts +21 -6
- package/dist/render/shaders/flat/models.js +161 -17
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +84 -40
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
- 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/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
- 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/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- 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/skinBlur.js +25 -3
- package/dist/render/shaders/ssrTrace.js +20 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.js +1 -0
- package/dist/render/surfaceModel.d.ts +49 -8
- package/dist/render/surfaceModel.js +54 -8
- package/dist/render/surfaceTexture.d.ts +38 -1
- package/dist/render/surfaceTexture.js +37 -2
- package/package.json +6 -6
- package/src/index.ts +26 -3
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +97 -0
- package/src/render/backend/api.ts +61 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +470 -59
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +119 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +589 -113
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +259 -12
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +9 -0
- package/src/render/instances.ts +61 -12
- package/src/render/lightBudget.ts +88 -4
- package/src/render/lightmap.ts +243 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +36 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/renderQuality.ts +8 -7
- 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 +18 -1
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +142 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +51 -6
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +158 -121
- package/src/render/shaders/flat/models.ts +170 -21
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +84 -40
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +82 -10
- package/src/render/surfaceTexture.ts +101 -3
|
@@ -684,15 +684,19 @@ fn probeLampsAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
|
|
|
684
684
|
var sum = vec3<f32>(0.0);
|
|
685
685
|
let count = u32(bake.lightCount);
|
|
686
686
|
for (var i = 0u; i < count; i = i + 1u) {
|
|
687
|
-
let at = bounceLights[i *
|
|
688
|
-
let colour = bounceLights[i *
|
|
689
|
-
let aim = bounceLights[i *
|
|
687
|
+
let at = bounceLights[i * 4u];
|
|
688
|
+
let colour = bounceLights[i * 4u + 1u];
|
|
689
|
+
let aim = bounceLights[i * 4u + 2u];
|
|
690
|
+
let lightExponent = bounceLights[i * 4u + 3u].x;
|
|
690
691
|
let lightRadius = at.w;
|
|
691
692
|
let toLight = at.xyz - p;
|
|
692
693
|
let dist = length(toLight);
|
|
693
694
|
let coneFalloff = smoothstep(aim.w, colour.w, dot(-toLight / max(dist, 1e-4), aim.xyz));
|
|
694
695
|
var falloff: f32;
|
|
695
|
-
if (
|
|
696
|
+
if (lightExponent > 0.0) {
|
|
697
|
+
let reach = dist / max(lightRadius, 1e-4);
|
|
698
|
+
falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), lightExponent);
|
|
699
|
+
} else if (bake.falloff == 1.0) {
|
|
696
700
|
let window = clamp(1.0 - pow(dist / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
|
|
697
701
|
falloff = window * window / max(dist * dist, 0.01);
|
|
698
702
|
} else {
|
|
@@ -701,7 +705,7 @@ fn probeLampsAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
|
|
|
701
705
|
if (falloff <= 0.0) { continue; }
|
|
702
706
|
let ndl = max(dot(n, toLight / max(dist, 1e-4)), 0.0);
|
|
703
707
|
if (ndl <= 0.0) { continue; }
|
|
704
|
-
let shape = select(falloff * falloff, falloff, bake.falloff == 1.0) * coneFalloff;
|
|
708
|
+
let shape = select(falloff * falloff, falloff, lightExponent > 0.0 || bake.falloff == 1.0) * coneFalloff;
|
|
705
709
|
if (shape <= 0.0) { continue; }
|
|
706
710
|
let fixture = max(0.3, 2.0 * march.finestStep);
|
|
707
711
|
let seen = probeLampVisible(p, n, toLight / max(dist, 1e-4), dist - fixture);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FOG_GLSL } from './fog.ts';
|
|
2
2
|
import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
|
|
3
|
+
import { glslIsFarDepth, glslSceneDepthToNdc } from '../depthConvention.ts';
|
|
3
4
|
import { MAX_POINT_LIGHTS } from '../lightBudget.ts';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -87,6 +88,11 @@ layout(location = 6) in float aAlpha;
|
|
|
87
88
|
layout(location = 7) in float aAge;
|
|
88
89
|
layout(location = 8) in float aSeed;
|
|
89
90
|
layout(location = 9) in vec3 aVelocity;
|
|
91
|
+
/**
|
|
92
|
+
* A sprite's flipbook frame and its half-height: \`ParticleInstances.frames\` and \`heights\`. The
|
|
93
|
+
* procedural materials read neither, and a height of 0 is a square, its height its width.
|
|
94
|
+
*/
|
|
95
|
+
layout(location = 10) in vec2 aSprite;
|
|
90
96
|
|
|
91
97
|
uniform mat4 uViewProj;
|
|
92
98
|
uniform vec3 uCameraPos;
|
|
@@ -126,6 +132,8 @@ uniform float uStretchSec;
|
|
|
126
132
|
* from everywhere.
|
|
127
133
|
*/
|
|
128
134
|
uniform float uCameraFacing;
|
|
135
|
+
/** How far each particle is moved toward the eye, in metres: \`ParticleBatchOptions.cameraOffset\`. */
|
|
136
|
+
uniform float uCameraOffset;
|
|
129
137
|
|
|
130
138
|
out vec2 vUv;
|
|
131
139
|
out vec3 vColor;
|
|
@@ -134,6 +142,9 @@ out float vAge;
|
|
|
134
142
|
out float vSeed;
|
|
135
143
|
out vec3 vWorldPos;
|
|
136
144
|
out vec3 vNormal;
|
|
145
|
+
/** The flipbook frame, for the sprite material; and the depth from the eye, for its fades. */
|
|
146
|
+
out float vFrame;
|
|
147
|
+
out float vViewDepth;
|
|
137
148
|
|
|
138
149
|
void main() {
|
|
139
150
|
float s = sin(aSpin);
|
|
@@ -150,7 +161,18 @@ void main() {
|
|
|
150
161
|
vec3 across;
|
|
151
162
|
vec3 up;
|
|
152
163
|
float bladeGain;
|
|
153
|
-
|
|
164
|
+
/*
|
|
165
|
+
* **Along its travel**, \`facing: 'velocity'\` (2): the quad's up is the velocity seen across
|
|
166
|
+
* the view, so a streak of light or a stretched spark lies along its motion and still faces the
|
|
167
|
+
* eye. A particle at rest has no such direction and faces the camera as the default does.
|
|
168
|
+
*/
|
|
169
|
+
vec3 viewer = normalize(uCameraPos - aPos);
|
|
170
|
+
vec3 alongView = aVelocity - viewer * dot(aVelocity, viewer);
|
|
171
|
+
if (uCameraFacing > 1.5 && dot(alongView, alongView) > 1e-10) {
|
|
172
|
+
up = normalize(alongView);
|
|
173
|
+
across = cross(up, viewer);
|
|
174
|
+
bladeGain = aBlade < 0.5 ? 1.0 : 0.0;
|
|
175
|
+
} else if (uCameraFacing > 0.5) {
|
|
154
176
|
vec3 toCamera = normalize(uCameraPos - aPos);
|
|
155
177
|
/* Any reference not parallel to the view. World up serves everywhere except looking
|
|
156
178
|
straight up or down it, where the two are the same line and the cross product
|
|
@@ -173,6 +195,16 @@ void main() {
|
|
|
173
195
|
bladeGain = 1.0;
|
|
174
196
|
}
|
|
175
197
|
vec3 offset = (across * rolled.x + up * rolled.y) * aSize;
|
|
198
|
+
/*
|
|
199
|
+
* **A sprite of two sizes** — a half-width and a half-height — rolls as a rectangle: the corner
|
|
200
|
+
* is scaled first and turned after. Only where a height was given, so a square particle keeps
|
|
201
|
+
* the arithmetic it always had to the bit.
|
|
202
|
+
*/
|
|
203
|
+
if (aSprite.y > 0.0) {
|
|
204
|
+
vec2 extent = vec2(aCorner.x * aSize, aCorner.y * aSprite.y);
|
|
205
|
+
vec2 turned = vec2(extent.x * c - extent.y * s, extent.x * s + extent.y * c);
|
|
206
|
+
offset = across * turned.x + up * turned.y;
|
|
207
|
+
}
|
|
176
208
|
|
|
177
209
|
/*
|
|
178
210
|
* Stretched along travel. Applied as an extra displacement on the component of
|
|
@@ -188,7 +220,10 @@ void main() {
|
|
|
188
220
|
/* bladeGain applied here, after the stretch rather than before it, so a dropped blade
|
|
189
221
|
is degenerate whatever the stretch added: all four of its corners land on aPos. */
|
|
190
222
|
vec3 world = aPos + offset * bladeGain;
|
|
223
|
+
/* Toward the eye, so a sprite set into a surface is drawn in front of it. 0 moves nothing. */
|
|
224
|
+
world += viewer * uCameraOffset * bladeGain;
|
|
191
225
|
vUv = aCorner;
|
|
226
|
+
vFrame = aSprite.x;
|
|
192
227
|
vColor = aColor;
|
|
193
228
|
vAlpha = aAlpha;
|
|
194
229
|
vAge = aAge;
|
|
@@ -207,6 +242,8 @@ void main() {
|
|
|
207
242
|
vNormal = dot(face, toEye) < 0.0 ? -face : face;
|
|
208
243
|
|
|
209
244
|
gl_Position = uViewProj * vec4(world, 1.0);
|
|
245
|
+
/* A perspective projection's w is the depth from the eye, which is what the fades measure. */
|
|
246
|
+
vViewDepth = gl_Position.w;
|
|
210
247
|
}
|
|
211
248
|
`;
|
|
212
249
|
|
|
@@ -436,3 +473,99 @@ void main() {
|
|
|
436
473
|
outColor = vec4(applyOutputTransform(color), alpha);
|
|
437
474
|
}
|
|
438
475
|
`;
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* A textured sprite: an image a consumer supplies, a cell of it at a time, at the colour and opacity
|
|
479
|
+
* each particle carries — what another engine's sprite renderer draws, and the material the three
|
|
480
|
+
* procedural ones above cannot be. Unlit: the colour is the particle's times the image's.
|
|
481
|
+
*
|
|
482
|
+
* **A flipbook** of `uCells.x` by `uCells.y` cells, numbered across then down from the top-left,
|
|
483
|
+
* a particle's frame naming one; with `uCells.z` at 1 the frame's fraction blends toward the next,
|
|
484
|
+
* so a slow flipbook does not step.
|
|
485
|
+
*
|
|
486
|
+
* **Two fades, each 0 for off.** A soft edge where the sprite meets the scene, by how far in front
|
|
487
|
+
* of the opaque depth it is over `uFade.x` metres — the hard line a flat card draws where it cuts
|
|
488
|
+
* a floor — and a fade over `uFade.y` metres in front of the eye, so a card the camera passes
|
|
489
|
+
* through does not fill the screen and vanish. The first reads a copy of the frame's depth, which
|
|
490
|
+
* is `uFade.z` at 1; where no copy can be had it is 0 and the edge stays hard.
|
|
491
|
+
*/
|
|
492
|
+
export const PARTICLE_SPRITE_FRAG = `#version 300 es
|
|
493
|
+
precision highp float;
|
|
494
|
+
|
|
495
|
+
/* Every output of the vertex stage, in its order, read or not: the generated stages number their
|
|
496
|
+
locations in declaration order, so a fragment declaring a subset reads the wrong ones. */
|
|
497
|
+
in vec2 vUv;
|
|
498
|
+
in vec3 vColor;
|
|
499
|
+
in float vAlpha;
|
|
500
|
+
in float vAge;
|
|
501
|
+
in float vSeed;
|
|
502
|
+
in vec3 vWorldPos;
|
|
503
|
+
in vec3 vNormal;
|
|
504
|
+
in float vFrame;
|
|
505
|
+
in float vViewDepth;
|
|
506
|
+
|
|
507
|
+
/** The image, a surface texture: its first layer. */
|
|
508
|
+
uniform mediump sampler2DArray uSprite;
|
|
509
|
+
/** The frame's opaque depth, copied, for the soft edge. highp for the reason depth always is. */
|
|
510
|
+
uniform highp sampler2D uDepth;
|
|
511
|
+
/** Columns, rows, and 1 to blend between cells. */
|
|
512
|
+
uniform vec4 uCells;
|
|
513
|
+
/** The soft edge's distance, the eye fade's, and whether the depth copy is there to read. */
|
|
514
|
+
uniform vec4 uFade;
|
|
515
|
+
/** A depth sample to view-space metres, as the skin blur takes it. */
|
|
516
|
+
uniform vec4 uDepthToViewZ;
|
|
517
|
+
/** The target's size in pixels, so a fragment finds its own texel of the depth copy. */
|
|
518
|
+
uniform vec2 uViewport;
|
|
519
|
+
uniform vec3 uCameraPos;
|
|
520
|
+
/** Whether the colour recedes into the medium: \`ParticleBatchOptions.fog\`. */
|
|
521
|
+
uniform int uFogEnabled;
|
|
522
|
+
${FOG_GLSL}
|
|
523
|
+
|
|
524
|
+
out vec4 outColor;
|
|
525
|
+
${OUTPUT_TRANSFORM_GLSL}
|
|
526
|
+
|
|
527
|
+
/** Cell \`cell\` of the flipbook, at \`uv\` inside it. Integer arithmetic: see AGENTS.md. */
|
|
528
|
+
vec2 cellUv(int cell, vec2 uv) {
|
|
529
|
+
int columns = max(int(uCells.x + 0.5), 1);
|
|
530
|
+
int rows = max(int(uCells.y + 0.5), 1);
|
|
531
|
+
int wrapped = cell % (columns * rows);
|
|
532
|
+
int column = wrapped % columns;
|
|
533
|
+
int row = wrapped / columns;
|
|
534
|
+
return (vec2(float(column), float(row)) + uv) / vec2(float(columns), float(rows));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
float viewZ(float depth) {
|
|
538
|
+
float ndc = ${glslSceneDepthToNdc('depth')};
|
|
539
|
+
return (uDepthToViewZ.x * ndc + uDepthToViewZ.y) / (uDepthToViewZ.z * ndc + uDepthToViewZ.w);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
void main() {
|
|
543
|
+
/* The corner's square to the image's: v runs down an image, and up a quad. */
|
|
544
|
+
vec2 uv = vec2(vUv.x * 0.5 + 0.5, 0.5 - vUv.y * 0.5);
|
|
545
|
+
float frame = max(vFrame, 0.0);
|
|
546
|
+
int first = int(floor(frame));
|
|
547
|
+
vec4 image = texture(uSprite, vec3(cellUv(first, uv), 0.0));
|
|
548
|
+
if (uCells.z > 0.5) {
|
|
549
|
+
vec4 next = texture(uSprite, vec3(cellUv(first + 1, uv), 0.0));
|
|
550
|
+
image = mix(image, next, fract(frame));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
float fade = 1.0;
|
|
554
|
+
if (uFade.z > 0.5 && uFade.x > 0.0) {
|
|
555
|
+
float depth = textureLod(uDepth, gl_FragCoord.xy / uViewport, 0.0).r;
|
|
556
|
+
if (!(${glslIsFarDepth('depth')})) {
|
|
557
|
+
fade = clamp((abs(viewZ(depth)) - vViewDepth) / uFade.x, 0.0, 1.0);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (uFade.y > 0.0) fade *= clamp(vViewDepth / uFade.y, 0.0, 1.0);
|
|
561
|
+
|
|
562
|
+
float alpha = vAlpha * image.a * fade;
|
|
563
|
+
if (alpha <= 0.001) discard;
|
|
564
|
+
vec3 color = vColor * image.rgb;
|
|
565
|
+
if (uFogEnabled != 0) {
|
|
566
|
+
float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);
|
|
567
|
+
color = mix(color, mediumColor(), fog);
|
|
568
|
+
}
|
|
569
|
+
outColor = vec4(applyOutputTransform(color), alpha);
|
|
570
|
+
}
|
|
571
|
+
`;
|
|
@@ -248,6 +248,17 @@ fn motionDynamicVert(
|
|
|
248
248
|
return out;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/* An instance's placement with its bottom row, which carries its texture cell, rebuilt as the
|
|
252
|
+
[0, 0, 0, 1] an affine placement has: \`MeshInstances.uvRegions\`. */
|
|
253
|
+
fn placementOf(c0: vec4<f32>, c1: vec4<f32>, c2: vec4<f32>, c3: vec4<f32>) -> mat4x4<f32> {
|
|
254
|
+
return mat4x4<f32>(
|
|
255
|
+
vec4<f32>(c0.xyz, 0.0),
|
|
256
|
+
vec4<f32>(c1.xyz, 0.0),
|
|
257
|
+
vec4<f32>(c2.xyz, 0.0),
|
|
258
|
+
vec4<f32>(c3.xyz, 1.0),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
251
262
|
/*
|
|
252
263
|
* The instanced stage: each instance's matrix this frame and last frame, read from the batch's
|
|
253
264
|
* placement and the copy of last frame's it kept. Slot for slot, so an instance is paired with
|
|
@@ -265,8 +276,8 @@ fn motionInstancedVert(
|
|
|
265
276
|
@location(8) p2: vec4<f32>,
|
|
266
277
|
@location(9) p3: vec4<f32>,
|
|
267
278
|
) -> Varyings {
|
|
268
|
-
let world =
|
|
269
|
-
let wasWorld =
|
|
279
|
+
let world = placementOf(m0, m1, m2, m3) * vec4<f32>(position, 1.0);
|
|
280
|
+
let wasWorld = placementOf(p0, p1, p2, p3) * vec4<f32>(position, 1.0);
|
|
270
281
|
var out: Varyings;
|
|
271
282
|
out.clip = frame.raster * world;
|
|
272
283
|
out.now = frame.viewProj * world;
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* **One pass, run twice**: along x into a target of its own, keeping the profile in the alpha, then
|
|
7
7
|
* along y blended additively into the frame — its colour added, the frame's alpha kept.
|
|
8
8
|
*
|
|
9
|
+
* **What it spreads is light, and the colour is applied after**, from the skin's own colour half at
|
|
10
|
+
* this pixel. Light travels beneath skin; the pigment a brow, a lip line or a freckle is made of
|
|
11
|
+
* does not, so spreading the coloured diffuse — as this did until 4.8.6 — smeared every mark on a
|
|
12
|
+
* face into the skin around it, and the blur's taps laid faint copies of each mark beside it.
|
|
13
|
+
*
|
|
9
14
|
* **Refuses what is not the same skin**: a neighbour that wrote no skin, or one further in depth
|
|
10
15
|
* than four of the profile's widest distances, takes no weight, and what is kept is divided by
|
|
11
16
|
* what was kept, so a cheek's light does not pull toward the black beside its silhouette. A tap
|
|
@@ -34,6 +39,12 @@ uniform vec4 uDepthToViewZ;
|
|
|
34
39
|
uniform float uFocal;
|
|
35
40
|
/** Each profile's scatter distance per channel, in metres. */
|
|
36
41
|
uniform vec4 uProfiles[${SKIN_PROFILES}];
|
|
42
|
+
/**
|
|
43
|
+
* The skin's colour, and its coverage in the alpha: what the spread light is multiplied by on the
|
|
44
|
+
* second axis, where \`uApplyAlbedo\` is 1. Read at this pixel alone, because pigment does not move.
|
|
45
|
+
*/
|
|
46
|
+
uniform sampler2D uAlbedo;
|
|
47
|
+
uniform float uApplyAlbedo;
|
|
37
48
|
|
|
38
49
|
out vec4 fragColor;
|
|
39
50
|
|
|
@@ -50,6 +61,17 @@ vec3 burleyMass(float x, vec3 d) {
|
|
|
50
61
|
return 0.5 - (exp(-x / d) + 3.0 * exp(-x / (3.0 * d))) * 0.125;
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The spread light times the skin's colour here, on the second axis; the light as it is on the
|
|
66
|
+
* first. The colour target holds colour times coverage over coverage, so dividing gives the skin's
|
|
67
|
+
* own colour at an edge, which the light — weighted for coverage already — is then multiplied by.
|
|
68
|
+
*/
|
|
69
|
+
vec3 withAlbedo(vec3 light) {
|
|
70
|
+
if (uApplyAlbedo < 0.5) return light;
|
|
71
|
+
vec4 albedo = textureLod(uAlbedo, vUv, 0.0);
|
|
72
|
+
return light * albedo.rgb / max(albedo.a, 1e-4);
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
void main() {
|
|
54
76
|
vec4 own = textureLod(uSkin, vUv, 0.0);
|
|
55
77
|
/*
|
|
@@ -63,7 +85,7 @@ void main() {
|
|
|
63
85
|
}
|
|
64
86
|
float depth = textureLod(uDepth, vUv, 0.0).r;
|
|
65
87
|
if (${glslIsFarDepth('depth')}) {
|
|
66
|
-
fragColor = own;
|
|
88
|
+
fragColor = vec4(withAlbedo(own.rgb), own.a);
|
|
67
89
|
return;
|
|
68
90
|
}
|
|
69
91
|
/* A part-covered pixel's code is its share of the whole: read it as the nearest, which is the
|
|
@@ -75,7 +97,7 @@ void main() {
|
|
|
75
97
|
/* The widest distance in pixels here: under half of one, there is nothing to spread across. */
|
|
76
98
|
float pixels = widest * uFocal / max(z, 1e-4);
|
|
77
99
|
if (pixels < 0.5) {
|
|
78
|
-
fragColor = own;
|
|
100
|
+
fragColor = vec4(withAlbedo(own.rgb), own.a);
|
|
79
101
|
return;
|
|
80
102
|
}
|
|
81
103
|
vec3 centre = 2.0 * burleyMass(EDGES[0] * widest, d);
|
|
@@ -100,6 +122,6 @@ void main() {
|
|
|
100
122
|
weight += w * (cover * keep);
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
|
-
fragColor = vec4(sum / weight, own.a);
|
|
125
|
+
fragColor = vec4(withAlbedo(sum / weight), own.a);
|
|
104
126
|
}
|
|
105
127
|
`;
|
|
@@ -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,6 +65,11 @@ 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;
|
|
@@ -73,6 +79,8 @@ uniform float uSsrEdgeFade;
|
|
|
73
79
|
|
|
74
80
|
out vec4 fragColor;
|
|
75
81
|
|
|
82
|
+
${ENV_BRDF_GLSL}
|
|
83
|
+
|
|
76
84
|
/** What the frame drew at a screen position, as a world point. */
|
|
77
85
|
vec3 worldAt(vec2 uv, float stored) {
|
|
78
86
|
vec4 world = uSsrDepthToWorld * vec4(uv * 2.0 - 1.0, stored, 1.0);
|
|
@@ -133,7 +141,18 @@ void main() {
|
|
|
133
141
|
mask *= smoothstep(uSsrFacingCos, min(1.0, uSsrFacingCos + 0.25), facing);
|
|
134
142
|
/* Nothing was drawn here, so there is no surface to reflect in. */
|
|
135
143
|
mask *= ${glslIsFarDepth('stored')} ? 0.0 : 1.0;
|
|
136
|
-
|
|
144
|
+
/*
|
|
145
|
+
* **How much the surface returns**: one number over the box, or, where the caller asks, its
|
|
146
|
+
* reflectance head-on carried through the split-sum BRDF at this pixel's own view and the
|
|
147
|
+
* surface's roughness — the lit stage's fit, so the two agree on what a surface reflects. A floor
|
|
148
|
+
* then returns more toward the horizon than underfoot, and the share follows the camera.
|
|
149
|
+
*/
|
|
150
|
+
float returned = uSsrStrength;
|
|
151
|
+
if (uSsrFresnel.x > 0.5) {
|
|
152
|
+
vec2 dfg = envBrdfApprox(clamp(dot(normal, normalize(toEye)), 0.0, 1.0), uSsrFresnel.y);
|
|
153
|
+
returned = clamp(uSsrStrength * dfg.x + dfg.y, 0.0, 1.0);
|
|
154
|
+
}
|
|
155
|
+
mask *= returned;
|
|
137
156
|
|
|
138
157
|
vec3 found = vec3(0.0);
|
|
139
158
|
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 {
|
package/src/render/skinBlur.ts
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* the kernel to, and nothing at run time calls it.
|
|
4
4
|
*
|
|
5
5
|
* **What it spreads.** With `skinScattering: 'screen-space'`, a skin draw writes its specular to the
|
|
6
|
-
* frame
|
|
7
|
-
* profile it scatters by in the alpha
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* frame, the light its diffuse is made of — the light that went beneath the surface, shaded on a
|
|
7
|
+
* white surface — to a target of its own with the profile it scatters by in the alpha, and its
|
|
8
|
+
* colour to a third. Before anything blended is drawn, the light is blurred along the screen's two
|
|
9
|
+
* axes by Burley's normalised diffusion, one width per channel, multiplied by the colour at each
|
|
10
|
+
* pixel and added back: light that entered a cheek leaves it a centimetre away, reddest furthest,
|
|
11
|
+
* across the pixels that centimetre covers at the cheek's distance, and a brow stays where it is
|
|
12
|
+
* drawn. **Until 4.8.6 the coloured diffuse was spread**, which smeared every mark on a face into
|
|
13
|
+
* the skin around it and laid faint copies of each mark at the taps' offsets. The pre-integrated fit stays in the lit
|
|
11
14
|
* stage for what this cannot see — a terminator's curvature — and this carries what that cannot:
|
|
12
15
|
* light crossing a shadow's edge, or a nostril's rim, in the picture rather than in a fit.
|
|
13
16
|
*
|
|
@@ -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
|
@@ -173,6 +173,7 @@ export class SsrPass {
|
|
|
173
173
|
gl.uniform3fv(u['uSsrAxis'] ?? null, surface.axis);
|
|
174
174
|
gl.uniform3fv(u['uSsrTint'] ?? null, surface.tint);
|
|
175
175
|
gl.uniform1f(u['uSsrStrength'] ?? null, surface.strength);
|
|
176
|
+
gl.uniform2f(u['uSsrFresnel'] ?? null, surface.fresnel ? 1 : 0, surface.roughness);
|
|
176
177
|
gl.uniform1f(u['uSsrFacingCos'] ?? null, surface.facingCos);
|
|
177
178
|
gl.uniform1f(u['uSsrReach'] ?? null, surface.reachM);
|
|
178
179
|
gl.uniform1f(u['uSsrThickness'] ?? null, surface.thicknessM);
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* the draw's model matrix and the eye's joint each draw, with `eyeAxisInWorld`.
|
|
19
19
|
*/
|
|
20
20
|
import type { Vec3 } from '../math/color.ts';
|
|
21
|
+
import { lightmapRegionOf } from './lightmap.ts';
|
|
22
|
+
import type { LightmapRegion } from './lightmap.ts';
|
|
21
23
|
|
|
22
24
|
/** Strands of brushed metal, satin or hair-like fibre: a highlight stretched across them. */
|
|
23
25
|
export interface AnisotropicModel {
|
|
@@ -52,7 +54,12 @@ export interface SkinModel {
|
|
|
52
54
|
readonly kind: 'skin';
|
|
53
55
|
/** The colour light keeps travelling beneath the surface — the mean free path, as a colour. */
|
|
54
56
|
readonly scatterColor: Vec3;
|
|
55
|
-
/**
|
|
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
|
+
*/
|
|
56
63
|
readonly radius: number;
|
|
57
64
|
/** 0 to 1: how much a light behind a thin part shows through it. */
|
|
58
65
|
readonly transmission: number;
|
|
@@ -77,8 +84,19 @@ export interface EyeModel {
|
|
|
77
84
|
readonly joint: number;
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
/**
|
|
88
|
+
* A surface whose static light was baked: the standard model, plus a lightmap page's light read at
|
|
89
|
+
* the mesh's second coordinates (`MeshData.lightmapUvs`). The material's `modelMap` is the page,
|
|
90
|
+
* from `createLightmap`; see `lightmap.ts` for what it adds and what it gives up.
|
|
91
|
+
*/
|
|
92
|
+
export interface LightmapModel {
|
|
93
|
+
readonly kind: 'lightmap';
|
|
94
|
+
/** Where this material's surfaces are on the page: `uv2 * scale + bias`. See `LightmapRegion`. */
|
|
95
|
+
readonly region: LightmapRegion;
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
/** Every model a material can name, by its `kind`: see `SurfaceMaterial.model`. */
|
|
81
|
-
export type SurfaceModel = AnisotropicModel | HairModel | SkinModel | EyeModel;
|
|
99
|
+
export type SurfaceModel = AnisotropicModel | HairModel | SkinModel | EyeModel | LightmapModel;
|
|
82
100
|
/** A model's name, which also picks its pipeline switch: `SURFACE_MODEL_SWITCH`. */
|
|
83
101
|
export type SurfaceModelKind = SurfaceModel['kind'];
|
|
84
102
|
|
|
@@ -88,6 +106,7 @@ export const SURFACE_MODEL_KINDS: readonly SurfaceModelKind[] = [
|
|
|
88
106
|
'hair',
|
|
89
107
|
'skin',
|
|
90
108
|
'eye',
|
|
109
|
+
'lightmap',
|
|
91
110
|
];
|
|
92
111
|
|
|
93
112
|
/** The lit stage's switch for each kind. */
|
|
@@ -96,6 +115,7 @@ export const SURFACE_MODEL_SWITCH: Readonly<Record<SurfaceModelKind, string>> =
|
|
|
96
115
|
hair: 'MODEL_HAIR',
|
|
97
116
|
skin: 'MODEL_SKIN',
|
|
98
117
|
eye: 'MODEL_EYE',
|
|
118
|
+
lightmap: 'MODEL_LIGHTMAP',
|
|
99
119
|
};
|
|
100
120
|
|
|
101
121
|
/** Floats a material's model takes: `uModelParams[2]`. */
|
|
@@ -144,7 +164,25 @@ export function hairModel(
|
|
|
144
164
|
});
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
/**
|
|
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. */
|
|
148
186
|
export function skinModel(
|
|
149
187
|
options: { scatterColor?: Vec3; radius?: number; transmission?: number; profile?: number } = {},
|
|
150
188
|
): SkinModel {
|
|
@@ -188,25 +226,51 @@ export function eyeModel(
|
|
|
188
226
|
});
|
|
189
227
|
}
|
|
190
228
|
|
|
229
|
+
/**
|
|
230
|
+
* A baked surface, reading its region of a lightmap page: the whole page where none is named. An
|
|
231
|
+
* instanced batch's regions compose with this one, each instance's applied first: see
|
|
232
|
+
* `MeshInstances.lightmapRegions`.
|
|
233
|
+
*/
|
|
234
|
+
export function lightmapModel(options: { region?: LightmapRegion } = {}): LightmapModel {
|
|
235
|
+
const region = lightmapRegionOf(options.region);
|
|
236
|
+
return Object.freeze({
|
|
237
|
+
kind: 'lightmap',
|
|
238
|
+
region: Object.freeze([region[0], region[1], region[2], region[3]]) as LightmapRegion,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
191
242
|
/**
|
|
192
243
|
* A model's numbers into `out` — eight floats, `uModelParams[0]` then `[1]` — and whether a model
|
|
193
|
-
* map is bound into the last.
|
|
194
|
-
*
|
|
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.
|
|
195
247
|
*
|
|
196
|
-
* -
|
|
248
|
+
* - standard (null): − · −, −, physical, −
|
|
249
|
+
* - anisotropic: strength, cos and sin of the rotation · −, −, physical, map
|
|
197
250
|
* - hair: shift, scatter, backlit, − · −, −, −, map
|
|
198
|
-
* - skin: the scatter colour, radius · transmission, profile, −, map
|
|
251
|
+
* - skin: the scatter colour, radius / `SKIN_MEAN_EXIT` · transmission, profile, −, map
|
|
199
252
|
* - eye: iris radius, iris depth, ior, cornea roughness · the axis, map
|
|
253
|
+
* - lightmap: the region's scale and bias · −, −, physical, map
|
|
200
254
|
*/
|
|
201
|
-
export function packModel(
|
|
255
|
+
export function packModel(
|
|
256
|
+
model: SurfaceModel | null,
|
|
257
|
+
mapped: boolean,
|
|
258
|
+
out: Float32Array,
|
|
259
|
+
physical = false,
|
|
260
|
+
): void {
|
|
202
261
|
out.fill(0);
|
|
203
|
-
|
|
262
|
+
const highlight = physical ? 1 : 0;
|
|
263
|
+
if (model === null) {
|
|
264
|
+
out[6] = highlight;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
204
267
|
out[7] = mapped ? 1 : 0;
|
|
205
268
|
switch (model.kind) {
|
|
206
269
|
case 'anisotropic':
|
|
207
270
|
out[0] = model.strength;
|
|
208
271
|
out[1] = Math.cos(model.rotation);
|
|
209
272
|
out[2] = Math.sin(model.rotation);
|
|
273
|
+
out[6] = highlight;
|
|
210
274
|
return;
|
|
211
275
|
case 'hair':
|
|
212
276
|
out[0] = model.shift;
|
|
@@ -217,7 +281,8 @@ export function packModel(model: SurfaceModel | null, mapped: boolean, out: Floa
|
|
|
217
281
|
out[0] = model.scatterColor[0];
|
|
218
282
|
out[1] = model.scatterColor[1];
|
|
219
283
|
out[2] = model.scatterColor[2];
|
|
220
|
-
|
|
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;
|
|
221
286
|
out[4] = model.transmission;
|
|
222
287
|
out[5] = model.profile;
|
|
223
288
|
return;
|
|
@@ -230,6 +295,13 @@ export function packModel(model: SurfaceModel | null, mapped: boolean, out: Floa
|
|
|
230
295
|
out[5] = model.axis[1];
|
|
231
296
|
out[6] = model.axis[2];
|
|
232
297
|
return;
|
|
298
|
+
case 'lightmap':
|
|
299
|
+
out[0] = model.region[0];
|
|
300
|
+
out[1] = model.region[1];
|
|
301
|
+
out[2] = model.region[2];
|
|
302
|
+
out[3] = model.region[3];
|
|
303
|
+
out[6] = highlight;
|
|
304
|
+
return;
|
|
233
305
|
}
|
|
234
306
|
}
|
|
235
307
|
|