@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
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
* 3. **Upsample**, additively back up the chain through a tent filter.
|
|
28
28
|
*
|
|
29
29
|
* Deliberately absent, because they were asked against by name: lens dirt, anamorphic streaks
|
|
30
|
-
* and ghosting. One threshold, one radius, one strength.
|
|
30
|
+
* and ghosting. One threshold, one radius, one strength — and, since 4.8.6, how a colour comes in
|
|
31
|
+
* past the threshold and a tint for each octave, which is how another engine's stage is matched
|
|
32
|
+
* rather than how this one looks: see `BloomResponse` in `bloomChain.ts`.
|
|
31
33
|
*/
|
|
32
34
|
/**
|
|
33
35
|
* The thirteen taps every downsampling stage shares, decomposed into five overlapping boxes.
|
|
@@ -125,6 +127,11 @@ uniform sampler2D uSource;
|
|
|
125
127
|
uniform vec2 uTexel;
|
|
126
128
|
/** In scene units. 1.0 is "brighter than white", which is the useful place to start. */
|
|
127
129
|
uniform float uThreshold;
|
|
130
|
+
/**
|
|
131
|
+
* Scene units over which a colour comes in past the threshold, or 0 to subtract the threshold
|
|
132
|
+
* instead. See \`BloomResponse.ramp\`.
|
|
133
|
+
*/
|
|
134
|
+
uniform float uRamp;
|
|
128
135
|
|
|
129
136
|
out vec4 fragColor;
|
|
130
137
|
|
|
@@ -141,6 +148,8 @@ ${BLOOM_BRIGHTNESS}
|
|
|
141
148
|
vec3 fetch(vec2 uv) {
|
|
142
149
|
vec3 c = min(textureLod(uSource, uv, 0.0).rgb, vec3(BLOOM_CEILING));
|
|
143
150
|
float bright = brightness(c);
|
|
151
|
+
/* A ramp keeps a share of the colour, all of it a ramp's width past the line. */
|
|
152
|
+
if (uRamp > 0.0) return c * clamp((bright - uThreshold) / uRamp, 0.0, 1.0);
|
|
144
153
|
return c * (max(bright - uThreshold, 0.0) / max(bright, 1e-4));
|
|
145
154
|
}
|
|
146
155
|
|
|
@@ -189,7 +198,9 @@ ${BLOOM_BOXES}
|
|
|
189
198
|
/**
|
|
190
199
|
* Stage three: a 3x3 tent, added into the level above.
|
|
191
200
|
*
|
|
192
|
-
* **Additive rather than a mix, and each octave counts the same
|
|
201
|
+
* **Additive rather than a mix, and each octave counts the same** unless a tint says otherwise:
|
|
202
|
+
* the level being added onto is multiplied by its own tint as it is, by the blend, and the deepest
|
|
203
|
+
* by `uScale` on the first draw. White everywhere is exactly the sum. A lone bright pixel arrives
|
|
193
204
|
* at level n divided by four to the n, spread over four to the n pixels, so summing the levels
|
|
194
205
|
* lifts its own pixel by about a third and lays a long shallow tail around it. That falloff is
|
|
195
206
|
* what a real glare looks like; a single gaussian gives a disc with an end to it.
|
|
@@ -204,6 +215,12 @@ precision highp float;
|
|
|
204
215
|
in vec2 vUv;
|
|
205
216
|
|
|
206
217
|
uniform sampler2D uSource;
|
|
218
|
+
/**
|
|
219
|
+
* What the level being read is multiplied by: its tint, on the first draw up from the deepest
|
|
220
|
+
* level, and white on every other — each other level's tint is applied to the level below as it
|
|
221
|
+
* is added onto, by the blend. See \`BloomResponse.tints\`.
|
|
222
|
+
*/
|
|
223
|
+
uniform vec3 uScale;
|
|
207
224
|
/** How far the tent reaches, in UV. One number for the whole chain. */
|
|
208
225
|
uniform float uRadius;
|
|
209
226
|
|
|
@@ -222,6 +239,6 @@ void main() {
|
|
|
222
239
|
vec3 i = textureLod(uSource, vUv + vec2( r, -r), 0.0).rgb;
|
|
223
240
|
|
|
224
241
|
vec3 sum = e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i);
|
|
225
|
-
fragColor = vec4(sum * 0.0625, 1.0);
|
|
242
|
+
fragColor = vec4(sum * 0.0625 * uScale, 1.0);
|
|
226
243
|
}
|
|
227
244
|
`;
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
* gives an arc its three-dimensionality is the path, and the path is world-space.
|
|
15
15
|
*/
|
|
16
16
|
export declare const BOLT_VERT = "#version 300 es\nlayout(location = 0) in vec3 aFrom;\nlayout(location = 1) in vec3 aTo;\n/** Along the segment (0 or 1), and which side of it (-1 or +1). */\nlayout(location = 2) in vec2 aCorner;\n/** Where this segment sits along its arc, the arc's envelope, its seed, its gain. */\nlayout(location = 3) in vec4 aArc;\n\nuniform mat4 uViewProj;\nuniform vec3 uCameraPos;\n/** Core half-width in metres, and how much of a pixel a far arc must still cover. */\nuniform float uWidth;\nuniform float uMinWidthPerMetre;\n\nout float vSide;\nout float vAlong;\nout float vFade;\nout float vSeed;\nout float vGain;\nout vec3 vWorldPos;\n\nvoid main() {\n vec3 mid = mix(aFrom, aTo, aCorner.x);\n vec3 dir = aTo - aFrom;\n float len = length(dir);\n dir = len > 1e-6 ? dir / len : vec3(0.0, 1.0, 0.0);\n\n vec3 view = mid - uCameraPos;\n float dist = length(view);\n view = dist > 1e-6 ? view / dist : vec3(0.0, 0.0, 1.0);\n\n /*\n * Across the segment and across the line of sight. Degenerate exactly when the\n * segment points at the viewer, where any perpendicular is as good as another \u2014\n * and where the segment covers almost no screen anyway.\n */\n vec3 across = cross(dir, view);\n float span = length(across);\n across = span > 1e-4 ? across / span : normalize(cross(dir, vec3(0.0, 1.0, 0.0)) + vec3(1e-3));\n\n /*\n * A floor on the world-space width that grows with distance, so a filament\n * never thins below a pixel. Under a pixel a bright thin line does not get\n * fainter, it *flickers* \u2014 the rasteriser catches it on some frames and not\n * others \u2014 and a strobing artefact reads as a bug, not as lightning.\n */\n float halfWidth = max(uWidth, dist * uMinWidthPerMetre);\n vec3 world = mid + across * aCorner.y * halfWidth;\n\n vSide = aCorner.y;\n vAlong = aArc.x;\n vFade = aArc.y;\n vSeed = aArc.z;\n vGain = aArc.w;\n vWorldPos = world;\n gl_Position = uViewProj * vec4(world, 1.0);\n}\n";
|
|
17
|
-
export declare const BOLT_FRAG = "#version 300 es\nprecision highp float;\n\nin float vSide;\nin float vAlong;\nin float vFade;\nin float vSeed;\nin float vGain;\nin vec3 vWorldPos;\n\nuniform float uTime;\n/** The saturating centre, and the glow it sits inside. */\nuniform vec3 uCoreColor;\nuniform vec3 uEdgeColor;\n/** How far past white the core is allowed to go. */\nuniform float uCoreGain;\n\nuniform vec3 uFogColor;\n/** Extinction per metre **at the camera's own height**, not at the base. */\nuniform float uFogDensity;\n/** Reciprocal scale height of the haze, per metre. 0 is a uniform medium. */\nuniform float uFogHeightFalloff;\n/** The camera's height, so a fragment's own height is one subtraction away. */\nuniform float uFogEyeY;\nuniform vec3 uUnderwaterColor;\nuniform float uUnderwaterFogDensity;\nuniform float uUnderwaterFactor;\n/** 0 integrates a medium (Beer-Lambert); 1 ramps linearly between near and far. */\nuniform int uFogMode;\n/** Linear mode only: where haze begins, and where it is total. */\nuniform float uFogNear;\nuniform float uFogFar;\n\n/** What distance converges toward: the day's haze, or the water column. */\nvec3 mediumColor() {\n return mix(uFogColor, uUnderwaterColor, uUnderwaterFactor);\n}\n\n/**\n * How much of a surface standing at height pointY, dist metres away, is lost to\n * the medium in between. 0 is perfectly clear, 1 is the medium's own colour.\n */\nfloat mediumFog(float dist, float pointY) {\n /*\n * A depth ramp rather than a medium, for worlds authored against one.\n *\n * Beer-Lambert is the honest model and stays the default: it integrates something\n * physically there, so extinction begins at the eye and never stops. A linear near/far\n * fog is not that \u2014 it is a *look*, with a hard start distance before which the air is\n * perfectly clear, and it is what the fixed-function pipeline offered and what several\n * renderers still expose. Approximating one with the other is not a matter of tuning:\n * matched at half occlusion, the exponential curve still lays haze over everything\n * inside near, where the ramp lays none, and near-field colour desaturates toward the\n * fog colour with nothing to show for it.\n *\n * Height falloff has no meaning here and is ignored: a ramp is defined on distance\n * alone.\n */\n if (uFogMode == 1) {\n float span = max(uFogFar - uFogNear, 1e-4);\n float ramp = clamp((dist - uFogNear) / span, 0.0, 1.0);\n if (uUnderwaterFactor <= 0.0) return ramp;\n float wetLinear = uUnderwaterFogDensity * dist;\n return mix(ramp, 1.0 - exp2(-wetLinear * wetLinear * 1.442695), uUnderwaterFactor);\n }\n\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n // (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than\n // branched: at t = 0 the quotient is 0/0, and 1e-4 of a scale height is 8 mm.\n float denom = abs(t) < 1e-4 ? 1e-4 : t;\n float air = 1.0 - exp(-uFogDensity * dist * (1.0 - exp(-denom)) / denom);\n if (uUnderwaterFactor <= 0.0) return air;\n // Water is its own medium and keeps its own curve, so crossing the surface\n // changes nothing about how the water column reads.\n float wet = uUnderwaterFogDensity * dist;\n return mix(air, 1.0 - exp2(-wet * wet * 1.442695), uUnderwaterFactor);\n}\n\nuniform vec3 uCameraPos;\n\nout vec4 outColor;\n\nfloat hash11(float p) {\n return fract(sin(p * 12.9898) * 43758.5453123);\n}\n\nvoid main() {\n /*\n * Across the filament: 1 at the centre line, 0 at the quad's edge. Two powers\n * of it are the entire look \u2014 a very tight one for the core and a loose one for\n * the glow. A single falloff gives either a hard line with no atmosphere or a\n * soft smudge with no filament.\n */\n float across = 1.0 - abs(vSide);\n float core = pow(across, 10.0);\n /*\n * A *wide* halo, deliberately. This exponent is the single biggest control over\n * whether the effect reads as lightning or as a drawn line: a tight falloff gives a\n * crisp filament with nothing around it, which is a wire, while a broad one bleeds\n * light into the air the way an over-exposed discharge does on film. It is the\n * closest thing to bloom available without a second pass.\n */\n float glow = pow(across, 1.1);\n\n /*\n * Stutter. An arc is not a steady light: it is re-established many times a\n * second, and the eye reads a *constant* bright line as neon. Two frequencies\n * so the pattern does not read as a sine, and per-arc phase from the seed so\n * several arcs never blink together.\n */\n /*\n * Stutter, but shallow. An arc is re-established many times a second and a perfectly\n * steady bright line reads as neon \u2014 but too *much* flicker reads as a broken effect\n * rather than as a discharge, and at the brief lifetime these now have, deep flicker\n * mostly hides the bolt. The path itself is already being redrawn (see 'BoltPool'),\n * which carries most of the crackle; this only keeps the brightness alive.\n */\n float t = uTime * 47.0 + vSeed * 17.0;\n float stutter = 0.78 + 0.22 * (hash11(floor(t)) * 0.6 + hash11(floor(t * 2.37)) * 0.4);\n\n /*\n * The tip tapers. An arc that ends in a squared-off quad reads as a drawn\n * segment; thinning toward the far end is what makes it read as a discharge\n * dissipating into the air.\n */\n float taper = 1.0 - vAlong * 0.55;\n\n float energy = vFade * stutter * taper * vGain;\n vec3 lit = uEdgeColor * glow + uCoreColor * core * uCoreGain;\n\n /*\n * Distance dims it rather than tinting it toward the fog. A discharge emits;\n * mixing it into the medium's colour is what a *surface* does, and doing that\n * here turned a far bolt grey instead of faint.\n */\n float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);\n /*\n * Alpha is 1, and the energy is already in the colour.\n *\n * The additive blend is 'SRC_ALPHA, ONE', so what reaches the framebuffer is\n * 'rgb * a'. Putting the energy in both \u2014 which the first version did \u2014 squares it,\n * and squaring a number below one is how an effect authored as blinding arrives as\n * faint: at a typical energy of 0.5 it delivered a quarter of what it asked for.\n * Every emissive material here has to pick one channel and mean it.\n */\n outColor = vec4(lit * energy * (1.0 - fog), 1.0);\n}\n";
|
|
17
|
+
export declare const BOLT_FRAG = "#version 300 es\nprecision highp float;\n\nin float vSide;\nin float vAlong;\nin float vFade;\nin float vSeed;\nin float vGain;\nin vec3 vWorldPos;\n\nuniform float uTime;\n/** The saturating centre, and the glow it sits inside. */\nuniform vec3 uCoreColor;\nuniform vec3 uEdgeColor;\n/** How far past white the core is allowed to go. */\nuniform float uCoreGain;\n\nuniform vec3 uFogColor;\n/** Extinction per metre **at the camera's own height**, not at the base. */\nuniform float uFogDensity;\n/** Reciprocal scale height of the haze, per metre. 0 is a uniform medium. */\nuniform float uFogHeightFalloff;\n/** The camera's height, so a fragment's own height is one subtraction away. */\nuniform float uFogEyeY;\nuniform vec3 uUnderwaterColor;\nuniform float uUnderwaterFogDensity;\nuniform float uUnderwaterFactor;\n/** 0 integrates a medium (Beer-Lambert); 1 ramps linearly between near and far. */\nuniform int uFogMode;\n/** Linear mode only: where haze begins, and where it is total. */\nuniform float uFogNear;\nuniform float uFogFar;\n\n/** What distance converges toward: the day's haze, or the water column. */\nvec3 mediumColor() {\n return mix(uFogColor, uUnderwaterColor, uUnderwaterFactor);\n}\n\n/**\n * How much of a surface standing at height pointY, dist metres away, is lost to\n * the medium in between. 0 is perfectly clear, 1 is the medium's own colour.\n */\nfloat mediumFog(float dist, float pointY) {\n /*\n * A depth ramp rather than a medium, for worlds authored against one.\n *\n * Beer-Lambert is the honest model and stays the default: it integrates something\n * physically there, so extinction begins at the eye and never stops. A linear near/far\n * fog is not that \u2014 it is a *look*, with a hard start distance before which the air is\n * perfectly clear, and it is what the fixed-function pipeline offered and what several\n * renderers still expose. Approximating one with the other is not a matter of tuning:\n * matched at half occlusion, the exponential curve still lays haze over everything\n * inside near, where the ramp lays none, and near-field colour desaturates toward the\n * fog colour with nothing to show for it.\n *\n * Height falloff has no meaning here and is ignored: a ramp is defined on distance\n * alone.\n */\n if (uFogMode == 1) {\n float span = max(uFogFar - uFogNear, 1e-4);\n float ramp = clamp((dist - uFogNear) / span, 0.0, 1.0);\n if (uUnderwaterFactor <= 0.0) return ramp;\n float wetLinear = uUnderwaterFogDensity * dist;\n return mix(ramp, 1.0 - exp2(-wetLinear * wetLinear * 1.442695), uUnderwaterFactor);\n }\n\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n // (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than\n // branched: at t = 0 the quotient is 0/0, and 1e-4 of a scale height is 8 mm.\n float denom = abs(t) < 1e-4 ? 1e-4 : t;\n float air = 1.0 - exp(-uFogDensity * dist * (1.0 - exp(-denom)) / denom);\n if (uUnderwaterFactor <= 0.0) return air;\n // Water is its own medium and keeps its own curve, so crossing the surface\n // changes nothing about how the water column reads.\n float wet = uUnderwaterFogDensity * dist;\n return mix(air, 1.0 - exp2(-wet * wet * 1.442695), uUnderwaterFactor);\n}\n\nuniform vec3 uCameraPos;\n\nout vec4 outColor;\n\nfloat hash11(float p) {\n return fract(sin(p * 12.9898) * 43758.5453123);\n}\n\nvoid main() {\n /*\n * Across the filament: 1 at the centre line, 0 at the quad's edge. Two powers\n * of it are the entire look \u2014 a very tight one for the core and a loose one for\n * the glow. A single falloff gives either a hard line with no atmosphere or a\n * soft smudge with no filament.\n */\n /*\n * Clamped, because a multisampled target shades a pixel on the quad's edge at its centre, which\n * may lie outside the quad: there 'vSide' runs past one, 'across' goes negative, and 'pow' of a\n * negative number is NaN. One NaN pixel is a dark speck; bloom then spreads it into a white disc\n * the size of the blur. Found by a fight drawing short arcs at 4 samples.\n */\n float across = clamp(1.0 - abs(vSide), 0.0, 1.0);\n float core = pow(across, 10.0);\n /*\n * A *wide* halo, deliberately. This exponent is the single biggest control over\n * whether the effect reads as lightning or as a drawn line: a tight falloff gives a\n * crisp filament with nothing around it, which is a wire, while a broad one bleeds\n * light into the air the way an over-exposed discharge does on film. It is the\n * closest thing to bloom available without a second pass.\n */\n float glow = pow(across, 1.1);\n\n /*\n * Stutter. An arc is not a steady light: it is re-established many times a\n * second, and the eye reads a *constant* bright line as neon. Two frequencies\n * so the pattern does not read as a sine, and per-arc phase from the seed so\n * several arcs never blink together.\n */\n /*\n * Stutter, but shallow. An arc is re-established many times a second and a perfectly\n * steady bright line reads as neon \u2014 but too *much* flicker reads as a broken effect\n * rather than as a discharge, and at the brief lifetime these now have, deep flicker\n * mostly hides the bolt. The path itself is already being redrawn (see 'BoltPool'),\n * which carries most of the crackle; this only keeps the brightness alive.\n */\n float t = uTime * 47.0 + vSeed * 17.0;\n float stutter = 0.78 + 0.22 * (hash11(floor(t)) * 0.6 + hash11(floor(t * 2.37)) * 0.4);\n\n /*\n * The tip tapers. An arc that ends in a squared-off quad reads as a drawn\n * segment; thinning toward the far end is what makes it read as a discharge\n * dissipating into the air.\n */\n float taper = 1.0 - vAlong * 0.55;\n\n float energy = vFade * stutter * taper * vGain;\n vec3 lit = uEdgeColor * glow + uCoreColor * core * uCoreGain;\n\n /*\n * Distance dims it rather than tinting it toward the fog. A discharge emits;\n * mixing it into the medium's colour is what a *surface* does, and doing that\n * here turned a far bolt grey instead of faint.\n */\n float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);\n /*\n * Alpha is 1, and the energy is already in the colour.\n *\n * The additive blend is 'SRC_ALPHA, ONE', so what reaches the framebuffer is\n * 'rgb * a'. Putting the energy in both \u2014 which the first version did \u2014 squares it,\n * and squaring a number below one is how an effect authored as blinding arrives as\n * faint: at a typical energy of 0.5 it delivered a quarter of what it asked for.\n * Every emissive material here has to pick one channel and mean it.\n */\n outColor = vec4(lit * energy * (1.0 - fog), 1.0);\n}\n";
|
|
@@ -104,7 +104,13 @@ void main() {
|
|
|
104
104
|
* the glow. A single falloff gives either a hard line with no atmosphere or a
|
|
105
105
|
* soft smudge with no filament.
|
|
106
106
|
*/
|
|
107
|
-
|
|
107
|
+
/*
|
|
108
|
+
* Clamped, because a multisampled target shades a pixel on the quad's edge at its centre, which
|
|
109
|
+
* may lie outside the quad: there 'vSide' runs past one, 'across' goes negative, and 'pow' of a
|
|
110
|
+
* negative number is NaN. One NaN pixel is a dark speck; bloom then spreads it into a white disc
|
|
111
|
+
* the size of the blur. Found by a fight drawing short arcs at 4 samples.
|
|
112
|
+
*/
|
|
113
|
+
float across = clamp(1.0 - abs(vSide), 0.0, 1.0);
|
|
108
114
|
float core = pow(across, 10.0);
|
|
109
115
|
/*
|
|
110
116
|
* A *wide* halo, deliberately. This exponent is the single biggest control over
|
|
@@ -100,6 +100,12 @@ void main() {
|
|
|
100
100
|
#endif
|
|
101
101
|
#if INSTANCED
|
|
102
102
|
mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
|
|
103
|
+
/* The bottom row is the instance's texture cell, rebuilt as the row it stands for: see flat. */
|
|
104
|
+
vec4 instanceCell = vec4(model[0].w, model[1].w, model[2].w, model[3].w);
|
|
105
|
+
model[0].w = 0.0;
|
|
106
|
+
model[1].w = 0.0;
|
|
107
|
+
model[2].w = 0.0;
|
|
108
|
+
model[3].w = 1.0;
|
|
103
109
|
#else
|
|
104
110
|
mat4 model = uModel;
|
|
105
111
|
#endif
|
|
@@ -124,7 +130,12 @@ void main() {
|
|
|
124
130
|
vLightPosition = uLightViewProj * vec4(bent, world.w);
|
|
125
131
|
gl_Position = vLightPosition;
|
|
126
132
|
#if CUTOUT
|
|
133
|
+
#if INSTANCED
|
|
134
|
+
/* A cutout instance's shadow is cut by its own cell, as its picture is. */
|
|
135
|
+
vUv = vec3((aUv.xy * instanceCell.xy + instanceCell.zw) * uUvScale + uUvOffset, aUv.z);
|
|
136
|
+
#else
|
|
127
137
|
vUv = vec3(aUv.xy * uUvScale + uUvOffset, aUv.z);
|
|
138
|
+
#endif
|
|
128
139
|
vAlphaCutout = uAlphaCutout;
|
|
129
140
|
#endif
|
|
130
141
|
#if GLASS
|
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
* Lamps widen both of the lobe's widths by the source's angle, as `sphereLobe` widens the standard
|
|
13
13
|
* lobe, with the energy kept; at strength 0 that is `sphereLobe` exactly.
|
|
14
14
|
*/
|
|
15
|
-
export declare const ANISOTROPIC_GLSL = "\n/* The stretch's frame and strength, gathered once with the surface. */\nvec3 aAlong;\nvec3 aAcross;\nfloat aStrength;\n\nvoid anisotropicSurface() {\n vec2 direction = vec2(1.0, 0.0);\n float strength = uModelParams[0].x;\n if (uModelParams[1].w > 0.0) {\n vec2 mapped = mMap.rg * 2.0 - 1.0;\n direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;\n strength *= mMap.b;\n }\n vec2 turned = vec2(\n direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,\n direction.x * uModelParams[0].z + direction.y * uModelParams[0].y\n );\n
|
|
15
|
+
export declare const ANISOTROPIC_GLSL = "\n/* The stretch's frame and strength, gathered once with the surface. */\nvec3 aAlong;\nvec3 aAcross;\nfloat aStrength;\n\nvoid anisotropicSurface() {\n vec2 direction = vec2(1.0, 0.0);\n float strength = uModelParams[0].x;\n if (uModelParams[1].w > 0.0) {\n vec2 mapped = mMap.rg * 2.0 - 1.0;\n direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;\n strength *= mMap.b;\n }\n vec2 turned = vec2(\n direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,\n direction.x * uModelParams[0].z + direction.y * uModelParams[0].y\n );\n vec3 along = mTangent * turned.x + mBitangent * turned.y;\n /*\n * A mesh with neither tangents nor texture coordinates has no frame to take a direction from,\n * and normalising the zero it gives was a NaN that drew the whole surface black. Any direction in\n * the surface stands in, the one across the world's up, so the highlight is stretched somewhere.\n */\n if (dot(along, along) < 1e-12) {\n along = cross(abs(mNormal.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0), mNormal);\n }\n aAlong = normalize(along);\n aAcross = cross(mNormal, aAlong);\n aStrength = strength;\n}\n\n/** The stretched lobe at a half vector, 1 at the mirror direction: anisotropicLobe.ts. */\nfloat anisotropicLobe(vec3 h, float sourceRadius, float dist) {\n float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);\n float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;\n float grow = sourceRadius / max(2.0 * dist, 1e-3);\n float alongWide = clamp(alongWidth + grow, alongWidth, 1.0);\n float acrossWide = clamp(alpha + grow, alpha, 1.0);\n float energy = (alongWidth / alongWide) * (alpha / acrossWide);\n float x = dot(aAlong, h) / alongWide;\n float y = dot(aAcross, h) / acrossWide;\n float z = dot(mNormal, h);\n float d = x * x + y * y + z * z;\n return energy / max(d * d, 1e-8);\n}\n\n/**\n * The same stretch as GGX's own BRDF, `\u03C0 \u00B7 D \u00B7 Vis \u00B7 N\u00B7L`, for a surface whose highlight is\n * physical: the distribution normalised, and Smith's height-correlated masking stretched with it\n * (Heitz 2014, as Filament writes it). `anisotropicPhysicalLobe` in anisotropicLobe.ts.\n */\nfloat anisotropicPhysical(vec3 l, vec3 h, float ndl, float sourceRadius, float dist) {\n float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);\n float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;\n float grow = sourceRadius / max(2.0 * dist, 1e-3);\n float along = clamp(alongWidth + grow, alongWidth, 1.0);\n float across = clamp(alpha + grow, alpha, 1.0);\n float x = dot(aAlong, h) / along;\n float y = dot(aAcross, h) / across;\n float z = dot(mNormal, h);\n float d = x * x + y * y + z * z;\n float distribution = 1.0 / max(3.14159265 * along * across * d * d, 1e-8);\n float ndv = max(dot(mNormal, mToEye), 1e-4);\n float viewed = ndl * length(vec3(along * dot(aAlong, mToEye), across * dot(aAcross, mToEye), ndv));\n float lit = ndv * length(vec3(along * dot(aAlong, l), across * dot(aAcross, l), ndl));\n return 3.14159265 * distribution * (0.5 / max(viewed + lit, 1e-5)) * ndl;\n}\n\nvoid anisotropicLight(vec3 l, float sourceRadius, float dist) {\n float ndl = max(dot(mNormal, l), 0.0);\n vec3 h = normalize(l + mToEye);\n vec3 tint = highlightTint(mSpecColor, max(dot(mToEye, h), 0.0), mMetal);\n mDiffuse = mAlbedo * ndl * (1.0 - mMetal);\n float lobe = physicalSpecular()\n ? anisotropicPhysical(l, h, ndl, sourceRadius, dist)\n : anisotropicLobe(h, sourceRadius, dist);\n mSpecular = ndl > 0.0 ? tint * lobe : vec3(0.0);\n}\n\n/* The environment's normal, bent toward the stretch by its strength and the surface's roughness. */\nvec3 anisotropicReflectNormal(vec3 n) {\n vec3 across = cross(aAcross, mToEye);\n vec3 bent = cross(across, aAcross);\n float bend = aStrength * clamp(5.0 * mRoughness, 0.0, 1.0);\n return normalize(mix(n, bent, bend));\n}\n";
|
|
@@ -30,7 +30,16 @@ void anisotropicSurface() {
|
|
|
30
30
|
direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,
|
|
31
31
|
direction.x * uModelParams[0].z + direction.y * uModelParams[0].y
|
|
32
32
|
);
|
|
33
|
-
|
|
33
|
+
vec3 along = mTangent * turned.x + mBitangent * turned.y;
|
|
34
|
+
/*
|
|
35
|
+
* A mesh with neither tangents nor texture coordinates has no frame to take a direction from,
|
|
36
|
+
* and normalising the zero it gives was a NaN that drew the whole surface black. Any direction in
|
|
37
|
+
* the surface stands in, the one across the world's up, so the highlight is stretched somewhere.
|
|
38
|
+
*/
|
|
39
|
+
if (dot(along, along) < 1e-12) {
|
|
40
|
+
along = cross(abs(mNormal.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0), mNormal);
|
|
41
|
+
}
|
|
42
|
+
aAlong = normalize(along);
|
|
34
43
|
aAcross = cross(mNormal, aAlong);
|
|
35
44
|
aStrength = strength;
|
|
36
45
|
}
|
|
@@ -50,12 +59,37 @@ float anisotropicLobe(vec3 h, float sourceRadius, float dist) {
|
|
|
50
59
|
return energy / max(d * d, 1e-8);
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The same stretch as GGX's own BRDF, \`π · D · Vis · N·L\`, for a surface whose highlight is
|
|
64
|
+
* physical: the distribution normalised, and Smith's height-correlated masking stretched with it
|
|
65
|
+
* (Heitz 2014, as Filament writes it). \`anisotropicPhysicalLobe\` in anisotropicLobe.ts.
|
|
66
|
+
*/
|
|
67
|
+
float anisotropicPhysical(vec3 l, vec3 h, float ndl, float sourceRadius, float dist) {
|
|
68
|
+
float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);
|
|
69
|
+
float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;
|
|
70
|
+
float grow = sourceRadius / max(2.0 * dist, 1e-3);
|
|
71
|
+
float along = clamp(alongWidth + grow, alongWidth, 1.0);
|
|
72
|
+
float across = clamp(alpha + grow, alpha, 1.0);
|
|
73
|
+
float x = dot(aAlong, h) / along;
|
|
74
|
+
float y = dot(aAcross, h) / across;
|
|
75
|
+
float z = dot(mNormal, h);
|
|
76
|
+
float d = x * x + y * y + z * z;
|
|
77
|
+
float distribution = 1.0 / max(3.14159265 * along * across * d * d, 1e-8);
|
|
78
|
+
float ndv = max(dot(mNormal, mToEye), 1e-4);
|
|
79
|
+
float viewed = ndl * length(vec3(along * dot(aAlong, mToEye), across * dot(aAcross, mToEye), ndv));
|
|
80
|
+
float lit = ndv * length(vec3(along * dot(aAlong, l), across * dot(aAcross, l), ndl));
|
|
81
|
+
return 3.14159265 * distribution * (0.5 / max(viewed + lit, 1e-5)) * ndl;
|
|
82
|
+
}
|
|
83
|
+
|
|
53
84
|
void anisotropicLight(vec3 l, float sourceRadius, float dist) {
|
|
54
85
|
float ndl = max(dot(mNormal, l), 0.0);
|
|
55
86
|
vec3 h = normalize(l + mToEye);
|
|
56
|
-
vec3 tint =
|
|
87
|
+
vec3 tint = highlightTint(mSpecColor, max(dot(mToEye, h), 0.0), mMetal);
|
|
57
88
|
mDiffuse = mAlbedo * ndl * (1.0 - mMetal);
|
|
58
|
-
|
|
89
|
+
float lobe = physicalSpecular()
|
|
90
|
+
? anisotropicPhysical(l, h, ndl, sourceRadius, dist)
|
|
91
|
+
: anisotropicLobe(h, sourceRadius, dist);
|
|
92
|
+
mSpecular = ndl > 0.0 ? tint * lobe : vec3(0.0);
|
|
59
93
|
}
|
|
60
94
|
|
|
61
95
|
/* The environment's normal, bent toward the stretch by its strength and the surface's roughness. */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One rectangle's light on a fragment: its form factor, and what it adds once its occlusion is
|
|
3
|
+
* known. **The one statement of it**, called by the fixed loop over the first `maxAreaLights`
|
|
4
|
+
* rectangles and by the froxel table's for the rest, so the two arms shade a rectangle with one
|
|
5
|
+
* body rather than two transcriptions of it — the zero-pixel gate between them means something
|
|
6
|
+
* only if that is so.
|
|
7
|
+
*
|
|
8
|
+
* Depth zero and after the models, whose `modelLight` it calls; see `flatFrag`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AREA_LIGHT_GLSL = "\n/**\n * The rectangle's signed form factor at this fragment: its corners **wound so a surface on the\n * emitting side sees them counter-clockwise**, which is what makes it positive there. The natural\n * order \u2014 minus, plus, plus, minus \u2014 winds the other way, and every one-sided light then clamped to\n * zero and the frame was black. The sign is the sidedness: a one-sided rectangle takes the positive\n * part and a two-sided one the magnitude.\n */\nfloat areaSignedForm(vec3 n, vec3 centre, vec3 right, vec3 up, vec2 halfSize) {\n vec3 c0 = centre - right * halfSize.x + up * halfSize.y;\n vec3 c1 = centre + right * halfSize.x + up * halfSize.y;\n vec3 c2 = centre + right * halfSize.x - up * halfSize.y;\n vec3 c3 = centre - right * halfSize.x - up * halfSize.y;\n return quadFormFactor(n, vWorldPos, c0, c1, c2, c3);\n}\n\n/**\n * How far along one of a rectangle's axes a fragment still sees it past the barn door on that axis's\n * positive edge: `p` the fragment's place along the axis, `height` its height above the\n * rectangle's plane, `halfSize` the rectangle's half extent, and the door's tip `tipOut` beyond\n * the edge and `tipUp` above the plane. Points of the rectangle past the answer are hidden.\n *\n * Above the tip, the tip's shadow on the plane as seen from the fragment is the limit. Below it, a\n * fragment beside the door and outside its line is hidden from all of it, and one inside the door's\n * reach is hidden from none of it.\n */\nfloat barnDoorLimit(float p, float height, float halfSize, float tipOut, float tipUp) {\n float tipAt = halfSize + tipOut;\n if (height > tipUp) return (height * tipAt - tipUp * p) / (height - tipUp);\n if (p > halfSize && height * tipOut < tipUp * (p - halfSize)) return -1e9;\n return 1e9;\n}\n\n/**\n * The part of a rectangle a fragment sees past its barn doors, written into `centre` and\n * `halfSize` as the smaller rectangle that is left, or false where the doors hide all of it.\n * `doors` is the cosine of their angle from the normal and their length; a length of 0 is no doors.\n * Each axis is clipped by its own two doors, which is what makes this four projections and a min.\n */\nbool areaBarnDoors(inout vec3 centre, vec3 right, vec3 up, inout vec2 halfSize, vec2 doors) {\n if (doors.y <= 0.0) return true;\n vec3 toFragment = vWorldPos - centre;\n float height = dot(toFragment, cross(right, up));\n /* Behind the emitting side: nothing to clip, and the form factor already answers it. */\n if (height <= 0.0) return true;\n float tipOut = doors.y * sqrt(max(1.0 - doors.x * doors.x, 0.0));\n float tipUp = doors.y * doors.x;\n vec2 p = vec2(dot(toFragment, right), dot(toFragment, up));\n vec2 hi = vec2(\n min(halfSize.x, barnDoorLimit(p.x, height, halfSize.x, tipOut, tipUp)),\n min(halfSize.y, barnDoorLimit(p.y, height, halfSize.y, tipOut, tipUp))\n );\n vec2 lo = vec2(\n max(-halfSize.x, -barnDoorLimit(-p.x, height, halfSize.x, tipOut, tipUp)),\n max(-halfSize.y, -barnDoorLimit(-p.y, height, halfSize.y, tipOut, tipUp))\n );\n if (hi.x <= lo.x || hi.y <= lo.y) return false;\n centre += right * (0.5 * (lo.x + hi.x)) + up * (0.5 * (lo.y + hi.y));\n halfSize = 0.5 * (hi - lo);\n return true;\n}\n\n/**\n * What one rectangle of colour `color` and form factor `form` adds to the lamps' two sums, open\n * and shadowed by `occl` and `glass`.\n *\n * **A model answers a rectangle as it answers a lamp at its centre**, carried at the rectangle's\n * own energy: the diffuse by the exact form factor over the centre's cosine, the specular by the\n * form factor. An approximation, stated: a model's lobe is not integrated over the rectangle as the\n * standard lobe is, so a glossy model under a large softbox shows a lobe from its centre rather than\n * its shape.\n *\n * **The standard lobe is integrated over the rectangle**, rather than sampled at one point on it.\n * What stood here once handed the closest point on the rectangle to `sphereLobe` and scaled it by\n * `form`, the *diffuse* form factor. Measured against a brute-force integral by\n * `scripts/areaSpecular.mjs`, that lost the reflection rather than blurring it: on polished metal\n * facing a softbox it returned 0.000233 where the integral is 0.9207, and head-on on a rough surface\n * it was 3.7 times too bright. Worst 287% either way; this is 40.1% where the light is what the\n * surface reflects and 79.0% for one overhead. `quadCoverage` says what fraction of the specular\n * lobe the rectangle covers and the environment BRDF says how much the surface returns, the split\n * the probe path uses. **`dfg.x + dfg.y` and not `envSpecularEnergy`**, deliberately: the\n * compensation returns the multiply-scattered share, and every direct lobe in this shader is\n * single-scattering, so adding it here returns energy the light never had. Measured at 211%\n * against 40.1%.\n */\nvoid areaLightAdd(\n vec3 centre,\n vec3 right,\n vec3 up,\n vec2 halfSize,\n vec3 color,\n float form,\n float occl,\n vec3 glass,\n vec3 n,\n vec3 albedo,\n float metal,\n vec3 specColor,\n float roughness,\n bool modelled,\n inout vec3 lampOpen,\n inout vec3 lampShadowed\n) {\n if (modelled) {\n vec3 areaL = normalize(centre - vWorldPos);\n modelLight(areaL, 0.0, 0.0);\n vec3 areaModel = (mDiffuse * (form / max(dot(n, areaL), 1e-3)) + mSpecular * form) * color;\n lampOpen += areaModel;\n lampShadowed += areaModel * occl * glass;\n return;\n }\n /* Held back with the point lamps', and shadowed by the same term it always was. */\n vec3 areaDiffuse = albedo * color * form * (1.0 - metal);\n lampOpen += areaDiffuse;\n lampShadowed += areaDiffuse * occl * glass;\n\n if (vSpecular > 0.0 || metal > 0.0) {\n vec3 c0 = centre - right * halfSize.x + up * halfSize.y;\n vec3 c1 = centre + right * halfSize.x + up * halfSize.y;\n vec3 c2 = centre + right * halfSize.x - up * halfSize.y;\n vec3 c3 = centre - right * halfSize.x - up * halfSize.y;\n vec3 toEyeArea = normalize(uCameraPos - vWorldPos);\n float coverage = quadCoverage(n, vWorldPos, toEyeArea, roughness, c0, c1, c2, c3);\n if (coverage > 0.0) {\n vec2 areaDfg = envBrdfApprox(max(dot(n, toEyeArea), 0.0), roughness);\n vec3 areaSpec = specColor * areaDfg.x + vec3(areaDfg.y);\n vec3 areaHighlight = areaSpec * color * coverage;\n lampOpen += areaHighlight;\n lampShadowed += areaHighlight * occl * glass;\n }\n }\n}\n";
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One rectangle's light on a fragment: its form factor, and what it adds once its occlusion is
|
|
3
|
+
* known. **The one statement of it**, called by the fixed loop over the first `maxAreaLights`
|
|
4
|
+
* rectangles and by the froxel table's for the rest, so the two arms shade a rectangle with one
|
|
5
|
+
* body rather than two transcriptions of it — the zero-pixel gate between them means something
|
|
6
|
+
* only if that is so.
|
|
7
|
+
*
|
|
8
|
+
* Depth zero and after the models, whose `modelLight` it calls; see `flatFrag`.
|
|
9
|
+
*/
|
|
10
|
+
export const AREA_LIGHT_GLSL = /* glsl */ `
|
|
11
|
+
/**
|
|
12
|
+
* The rectangle's signed form factor at this fragment: its corners **wound so a surface on the
|
|
13
|
+
* emitting side sees them counter-clockwise**, which is what makes it positive there. The natural
|
|
14
|
+
* order — minus, plus, plus, minus — winds the other way, and every one-sided light then clamped to
|
|
15
|
+
* zero and the frame was black. The sign is the sidedness: a one-sided rectangle takes the positive
|
|
16
|
+
* part and a two-sided one the magnitude.
|
|
17
|
+
*/
|
|
18
|
+
float areaSignedForm(vec3 n, vec3 centre, vec3 right, vec3 up, vec2 halfSize) {
|
|
19
|
+
vec3 c0 = centre - right * halfSize.x + up * halfSize.y;
|
|
20
|
+
vec3 c1 = centre + right * halfSize.x + up * halfSize.y;
|
|
21
|
+
vec3 c2 = centre + right * halfSize.x - up * halfSize.y;
|
|
22
|
+
vec3 c3 = centre - right * halfSize.x - up * halfSize.y;
|
|
23
|
+
return quadFormFactor(n, vWorldPos, c0, c1, c2, c3);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* How far along one of a rectangle's axes a fragment still sees it past the barn door on that axis's
|
|
28
|
+
* positive edge: \`p\` the fragment's place along the axis, \`height\` its height above the
|
|
29
|
+
* rectangle's plane, \`halfSize\` the rectangle's half extent, and the door's tip \`tipOut\` beyond
|
|
30
|
+
* the edge and \`tipUp\` above the plane. Points of the rectangle past the answer are hidden.
|
|
31
|
+
*
|
|
32
|
+
* Above the tip, the tip's shadow on the plane as seen from the fragment is the limit. Below it, a
|
|
33
|
+
* fragment beside the door and outside its line is hidden from all of it, and one inside the door's
|
|
34
|
+
* reach is hidden from none of it.
|
|
35
|
+
*/
|
|
36
|
+
float barnDoorLimit(float p, float height, float halfSize, float tipOut, float tipUp) {
|
|
37
|
+
float tipAt = halfSize + tipOut;
|
|
38
|
+
if (height > tipUp) return (height * tipAt - tipUp * p) / (height - tipUp);
|
|
39
|
+
if (p > halfSize && height * tipOut < tipUp * (p - halfSize)) return -1e9;
|
|
40
|
+
return 1e9;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The part of a rectangle a fragment sees past its barn doors, written into \`centre\` and
|
|
45
|
+
* \`halfSize\` as the smaller rectangle that is left, or false where the doors hide all of it.
|
|
46
|
+
* \`doors\` is the cosine of their angle from the normal and their length; a length of 0 is no doors.
|
|
47
|
+
* Each axis is clipped by its own two doors, which is what makes this four projections and a min.
|
|
48
|
+
*/
|
|
49
|
+
bool areaBarnDoors(inout vec3 centre, vec3 right, vec3 up, inout vec2 halfSize, vec2 doors) {
|
|
50
|
+
if (doors.y <= 0.0) return true;
|
|
51
|
+
vec3 toFragment = vWorldPos - centre;
|
|
52
|
+
float height = dot(toFragment, cross(right, up));
|
|
53
|
+
/* Behind the emitting side: nothing to clip, and the form factor already answers it. */
|
|
54
|
+
if (height <= 0.0) return true;
|
|
55
|
+
float tipOut = doors.y * sqrt(max(1.0 - doors.x * doors.x, 0.0));
|
|
56
|
+
float tipUp = doors.y * doors.x;
|
|
57
|
+
vec2 p = vec2(dot(toFragment, right), dot(toFragment, up));
|
|
58
|
+
vec2 hi = vec2(
|
|
59
|
+
min(halfSize.x, barnDoorLimit(p.x, height, halfSize.x, tipOut, tipUp)),
|
|
60
|
+
min(halfSize.y, barnDoorLimit(p.y, height, halfSize.y, tipOut, tipUp))
|
|
61
|
+
);
|
|
62
|
+
vec2 lo = vec2(
|
|
63
|
+
max(-halfSize.x, -barnDoorLimit(-p.x, height, halfSize.x, tipOut, tipUp)),
|
|
64
|
+
max(-halfSize.y, -barnDoorLimit(-p.y, height, halfSize.y, tipOut, tipUp))
|
|
65
|
+
);
|
|
66
|
+
if (hi.x <= lo.x || hi.y <= lo.y) return false;
|
|
67
|
+
centre += right * (0.5 * (lo.x + hi.x)) + up * (0.5 * (lo.y + hi.y));
|
|
68
|
+
halfSize = 0.5 * (hi - lo);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What one rectangle of colour \`color\` and form factor \`form\` adds to the lamps' two sums, open
|
|
74
|
+
* and shadowed by \`occl\` and \`glass\`.
|
|
75
|
+
*
|
|
76
|
+
* **A model answers a rectangle as it answers a lamp at its centre**, carried at the rectangle's
|
|
77
|
+
* own energy: the diffuse by the exact form factor over the centre's cosine, the specular by the
|
|
78
|
+
* form factor. An approximation, stated: a model's lobe is not integrated over the rectangle as the
|
|
79
|
+
* standard lobe is, so a glossy model under a large softbox shows a lobe from its centre rather than
|
|
80
|
+
* its shape.
|
|
81
|
+
*
|
|
82
|
+
* **The standard lobe is integrated over the rectangle**, rather than sampled at one point on it.
|
|
83
|
+
* What stood here once handed the closest point on the rectangle to \`sphereLobe\` and scaled it by
|
|
84
|
+
* \`form\`, the *diffuse* form factor. Measured against a brute-force integral by
|
|
85
|
+
* \`scripts/areaSpecular.mjs\`, that lost the reflection rather than blurring it: on polished metal
|
|
86
|
+
* facing a softbox it returned 0.000233 where the integral is 0.9207, and head-on on a rough surface
|
|
87
|
+
* it was 3.7 times too bright. Worst 287% either way; this is 40.1% where the light is what the
|
|
88
|
+
* surface reflects and 79.0% for one overhead. \`quadCoverage\` says what fraction of the specular
|
|
89
|
+
* lobe the rectangle covers and the environment BRDF says how much the surface returns, the split
|
|
90
|
+
* the probe path uses. **\`dfg.x + dfg.y\` and not \`envSpecularEnergy\`**, deliberately: the
|
|
91
|
+
* compensation returns the multiply-scattered share, and every direct lobe in this shader is
|
|
92
|
+
* single-scattering, so adding it here returns energy the light never had. Measured at 211%
|
|
93
|
+
* against 40.1%.
|
|
94
|
+
*/
|
|
95
|
+
void areaLightAdd(
|
|
96
|
+
vec3 centre,
|
|
97
|
+
vec3 right,
|
|
98
|
+
vec3 up,
|
|
99
|
+
vec2 halfSize,
|
|
100
|
+
vec3 color,
|
|
101
|
+
float form,
|
|
102
|
+
float occl,
|
|
103
|
+
vec3 glass,
|
|
104
|
+
vec3 n,
|
|
105
|
+
vec3 albedo,
|
|
106
|
+
float metal,
|
|
107
|
+
vec3 specColor,
|
|
108
|
+
float roughness,
|
|
109
|
+
bool modelled,
|
|
110
|
+
inout vec3 lampOpen,
|
|
111
|
+
inout vec3 lampShadowed
|
|
112
|
+
) {
|
|
113
|
+
if (modelled) {
|
|
114
|
+
vec3 areaL = normalize(centre - vWorldPos);
|
|
115
|
+
modelLight(areaL, 0.0, 0.0);
|
|
116
|
+
vec3 areaModel = (mDiffuse * (form / max(dot(n, areaL), 1e-3)) + mSpecular * form) * color;
|
|
117
|
+
lampOpen += areaModel;
|
|
118
|
+
lampShadowed += areaModel * occl * glass;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
/* Held back with the point lamps', and shadowed by the same term it always was. */
|
|
122
|
+
vec3 areaDiffuse = albedo * color * form * (1.0 - metal);
|
|
123
|
+
lampOpen += areaDiffuse;
|
|
124
|
+
lampShadowed += areaDiffuse * occl * glass;
|
|
125
|
+
|
|
126
|
+
if (vSpecular > 0.0 || metal > 0.0) {
|
|
127
|
+
vec3 c0 = centre - right * halfSize.x + up * halfSize.y;
|
|
128
|
+
vec3 c1 = centre + right * halfSize.x + up * halfSize.y;
|
|
129
|
+
vec3 c2 = centre + right * halfSize.x - up * halfSize.y;
|
|
130
|
+
vec3 c3 = centre - right * halfSize.x - up * halfSize.y;
|
|
131
|
+
vec3 toEyeArea = normalize(uCameraPos - vWorldPos);
|
|
132
|
+
float coverage = quadCoverage(n, vWorldPos, toEyeArea, roughness, c0, c1, c2, c3);
|
|
133
|
+
if (coverage > 0.0) {
|
|
134
|
+
vec2 areaDfg = envBrdfApprox(max(dot(n, toEyeArea), 0.0), roughness);
|
|
135
|
+
vec3 areaSpec = specColor * areaDfg.x + vec3(areaDfg.y);
|
|
136
|
+
vec3 areaHighlight = areaSpec * color * coverage;
|
|
137
|
+
lampOpen += areaHighlight;
|
|
138
|
+
lampShadowed += areaHighlight * occl * glass;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
@@ -8,7 +8,7 @@ export declare function glassTintGlsl(glassShadows: boolean): string;
|
|
|
8
8
|
* The lit stage's switches, by the name of the constant each is: the decision both backends make
|
|
9
9
|
* about which optional code a lit program carries. See `litSwitchesGlsl`.
|
|
10
10
|
*/
|
|
11
|
-
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT';
|
|
11
|
+
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR';
|
|
12
12
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
13
13
|
export declare function noLitSwitches(): Record<LitSwitch, boolean>;
|
|
14
14
|
/** Which of the lit stage's optional features a build reads. See `litSwitchesGlsl`. */
|
|
@@ -29,6 +29,7 @@ export interface LitSwitches {
|
|
|
29
29
|
* first uses them — a measured profile or a cookie, a material with an effects table, a DriftLight
|
|
30
30
|
* volume — and then on for good; see `PipelineCache.enable` and the WebGL2 renderer's rebuild.
|
|
31
31
|
* Measured on RADV against the lit shader with glass already out: 6,852 instructions and 120
|
|
32
|
-
* registers, 4,641 and 96 with all four out.
|
|
32
|
+
* registers, 4,641 and 96 with all four out. `PHYSICAL_SPECULAR` is a sixth, declared with the
|
|
33
|
+
* models so that theirs keep their ids (`modelsGlsl`).
|
|
33
34
|
*/
|
|
34
35
|
export declare function litSwitchesGlsl(switches: LitSwitches): string;
|
|
@@ -31,6 +31,7 @@ export function noLitSwitches() {
|
|
|
31
31
|
LIGHT_FIXTURES: false,
|
|
32
32
|
SURFACE_EFFECTS: false,
|
|
33
33
|
DRIFT_LIGHT: false,
|
|
34
|
+
PHYSICAL_SPECULAR: false,
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
@@ -44,7 +45,8 @@ export function noLitSwitches() {
|
|
|
44
45
|
* first uses them — a measured profile or a cookie, a material with an effects table, a DriftLight
|
|
45
46
|
* volume — and then on for good; see `PipelineCache.enable` and the WebGL2 renderer's rebuild.
|
|
46
47
|
* Measured on RADV against the lit shader with glass already out: 6,852 instructions and 120
|
|
47
|
-
* registers, 4,641 and 96 with all four out.
|
|
48
|
+
* registers, 4,641 and 96 with all four out. `PHYSICAL_SPECULAR` is a sixth, declared with the
|
|
49
|
+
* models so that theirs keep their ids (`modelsGlsl`).
|
|
48
50
|
*/
|
|
49
51
|
export function litSwitchesGlsl(switches) {
|
|
50
52
|
return /* glsl */ `
|
|
@@ -104,6 +104,11 @@ export interface FlatShaderOptions {
|
|
|
104
104
|
readonly surfaceEffects?: boolean;
|
|
105
105
|
/** Whether it reads a DriftLight volume. True when absent. See `litSwitchesGlsl`. */
|
|
106
106
|
readonly driftLight?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Whether it carries GGX's own highlight for a material that asks (`physicalSpecular`). True
|
|
109
|
+
* when absent. See `modelsGlsl`.
|
|
110
|
+
*/
|
|
111
|
+
readonly physicalSpecular?: boolean;
|
|
107
112
|
/**
|
|
108
113
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
109
114
|
*
|
|
@@ -14,7 +14,9 @@ import { CLOTH_BINDING_GLSL } from '../clothBinding.js';
|
|
|
14
14
|
import { SKINNING_GLSL } from '../skinning.js';
|
|
15
15
|
import { SURFACE_GLSL } from './surface.js';
|
|
16
16
|
import { MAIN_GLSL } from './main.js';
|
|
17
|
-
import {
|
|
17
|
+
import { modelsGlsl } from './models.js';
|
|
18
|
+
import { AREA_LIGHT_GLSL } from './areaLight.js';
|
|
19
|
+
import { NORMAL_MAP_GLSL } from './normalMap.js';
|
|
18
20
|
import { DRIFT_LIGHT_GLSL } from './driftLight.js';
|
|
19
21
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.js';
|
|
20
22
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from '../vertexChannel.js';
|
|
@@ -106,7 +108,17 @@ layout(location = 11) in vec4 aInstanceModel0;
|
|
|
106
108
|
layout(location = 12) in vec4 aInstanceModel1;
|
|
107
109
|
layout(location = 13) in vec4 aInstanceModel2;
|
|
108
110
|
layout(location = 14) in vec4 aInstanceModel3;
|
|
109
|
-
|
|
111
|
+
/**
|
|
112
|
+
* The instance's tint, and its opacity in w: \`MeshInstances.alphas\`, 1 where none was given — or,
|
|
113
|
+
* in a lightmapped batch's pipeline, its region of the page (\`MeshInstances.lightmapRegions\`).
|
|
114
|
+
*/
|
|
115
|
+
layout(location = 15) in vec4 aInstanceTint;
|
|
116
|
+
/*
|
|
117
|
+
* On in the pipeline a lightmapped material draws an instanced batch with, where the lane above is
|
|
118
|
+
* a region and not a tint: the second coordinates leave scaled and offset by it, under a tint and an
|
|
119
|
+
* opacity of one. See lightmap.ts.
|
|
120
|
+
*/
|
|
121
|
+
const bool LIGHTMAP_REGIONS = false; // wgsl:override
|
|
110
122
|
#else
|
|
111
123
|
uniform mat4 uModel;
|
|
112
124
|
/**
|
|
@@ -232,7 +244,17 @@ void main() {
|
|
|
232
244
|
#endif
|
|
233
245
|
#if INSTANCED
|
|
234
246
|
mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
|
|
235
|
-
|
|
247
|
+
/*
|
|
248
|
+
* **The bottom row is the instance's texture cell** (\`MeshInstances.uvRegions\`), which an
|
|
249
|
+
* affine placement leaves free: read, and rebuilt as the \`[0, 0, 0, 1]\` it stands for.
|
|
250
|
+
*/
|
|
251
|
+
vec4 instanceCell = vec4(model[0].w, model[1].w, model[2].w, model[3].w);
|
|
252
|
+
model[0].w = 0.0;
|
|
253
|
+
model[1].w = 0.0;
|
|
254
|
+
model[2].w = 0.0;
|
|
255
|
+
model[3].w = 1.0;
|
|
256
|
+
vec3 tint = aInstanceTint.rgb;
|
|
257
|
+
if (LIGHTMAP_REGIONS) tint = vec3(1.0);
|
|
236
258
|
#else
|
|
237
259
|
mat4 model = uModel;
|
|
238
260
|
vec3 tint = uTint;
|
|
@@ -279,17 +301,31 @@ void main() {
|
|
|
279
301
|
vColor = aColor * tint;
|
|
280
302
|
vEmissive = aEmissive;
|
|
281
303
|
vSpecular = aSpecular;
|
|
304
|
+
#if INSTANCED
|
|
305
|
+
/* The instance's cell first, then the material's scale and offset. */
|
|
306
|
+
vUv = vec3((aUv.xy * instanceCell.xy + instanceCell.zw) * uUvScale + uUvOffset, aUv.z);
|
|
307
|
+
#else
|
|
282
308
|
vUv = vec3(aUv.xy * uUvScale + uUvOffset, aUv.z);
|
|
309
|
+
#endif
|
|
283
310
|
vEmissiveColor = aEmissiveColor;
|
|
284
311
|
vRoughness = aRoughness;
|
|
285
312
|
vGrain = aGrain;
|
|
286
313
|
vRelief = aRelief;
|
|
287
314
|
vTangent = vec4(worldTangent, aTangent.w);
|
|
288
315
|
#if INSTANCED
|
|
289
|
-
/*
|
|
316
|
+
/*
|
|
317
|
+
* No channel on this path; the neutral values are what every mesh read before it existed — but
|
|
318
|
+
* for the opacity, which an instance carries of its own, 1 where none was given.
|
|
319
|
+
*/
|
|
290
320
|
vSkyDirect = 1.0;
|
|
291
|
-
vAlpha =
|
|
321
|
+
vAlpha = aInstanceTint.w;
|
|
292
322
|
vThickness = 1.0;
|
|
323
|
+
if (LIGHTMAP_REGIONS) {
|
|
324
|
+
/* Out of the lane's \`-1 - uv\`, through the region, and back into it. See lightmap.ts. */
|
|
325
|
+
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + aInstanceTint.z);
|
|
326
|
+
vRelief = -1.0 - ((-1.0 - aRelief) * aInstanceTint.y + aInstanceTint.w);
|
|
327
|
+
vAlpha = 1.0;
|
|
328
|
+
}
|
|
293
329
|
#else
|
|
294
330
|
vSkyDirect = aChannel.y;
|
|
295
331
|
vAlpha = aChannel.z;
|
|
@@ -426,13 +462,17 @@ export function flatFrag(options) {
|
|
|
426
462
|
* and `main` is unconditional by construction.
|
|
427
463
|
*/
|
|
428
464
|
TANGENT_FRAME_GLSL,
|
|
465
|
+
/* After the frame it calls, unconditional for the same reason. See normalMap.ts. */
|
|
466
|
+
NORMAL_MAP_GLSL,
|
|
429
467
|
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
430
468
|
CUTOUT_COVERAGE_GLSL,
|
|
431
469
|
CUTOUT_DITHER_GLSL,
|
|
432
470
|
DRIFT_LIGHT_GLSL,
|
|
433
471
|
SURFACE_EFFECTS_GLSL,
|
|
434
472
|
/* Depth zero and after the tangent frame and the lobes it calls; see models.ts. */
|
|
435
|
-
|
|
473
|
+
modelsGlsl(options.physicalSpecular ?? true),
|
|
474
|
+
/* After the models, whose answer to a rectangle it asks for. See areaLight.ts. */
|
|
475
|
+
AREA_LIGHT_GLSL,
|
|
436
476
|
MAIN_GLSL,
|
|
437
477
|
].join('\n'), {
|
|
438
478
|
POINT_SHADOWS: options.pointShadows,
|
|
@@ -25,4 +25,10 @@ export declare const EMISSIVE_SHADOW_SHARE = 0.55;
|
|
|
25
25
|
* deliberately not this.
|
|
26
26
|
*/
|
|
27
27
|
export declare const MIN_LOBE_ALPHA = 0.01;
|
|
28
|
+
/**
|
|
29
|
+
* Karis's fit to the split-sum environment BRDF, as GLSL: the lit stage's, and the screen-space
|
|
30
|
+
* reflection's when a surface asks for its Fresnel (`ReflectiveSurface.fresnel`). One text, so the
|
|
31
|
+
* two cannot drift.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ENV_BRDF_GLSL = "/**\n * The other half of the split-sum approximation: what fraction of an environment a surface returns.\n *\n * The convolution decides *what* a surface reflects at a given roughness; this decides *how much*.\n * Before it the amount was fresnel times one-minus-roughness, which is a curve somebody chose\n * standing in for an integral.\n *\n * Karis's analytic fit to the environment BRDF, so there is **no lookup texture and no texture\n * unit**: a few multiply-adds against roughness and the view angle. x scales the surface's own\n * reflectance at normal incidence and y is the additive grazing term, so a smooth dielectric at\n * f0 0.04 returns about 0.046 head-on and approaches 1 at the edge, which is what a dielectric\n * does. At roughness 1 it returns about 0.016 rather than the zero one-minus-roughness gave, which\n * is the substantive difference: a fully rough dielectric does reflect its surroundings.\n *\n * **It replaces the Fresnel factor as well as the thinning**, because the fit already contains the\n * angular dependence; multiplying by a separate Fresnel term would apply it twice.\n *\n * **How much this is worth was measured, and it is worth less than it looks on a dark room.**\n * On demo/dev/ibl.html?ladder=1 the whole ladder moved by at most 0.2 of 255 when the old term was\n * replaced. That is not the term failing: at high roughness the surface reads the coarsest levels\n * of the chain, and in a room of five dark walls and one bright one those levels are dark, so a\n * weight three times larger against a near-zero radiance is still near zero. It is worth much more\n * against a bright environment, which is what a loaded sky is and what no captured room here is.\n *\n * **A warning about reading that page, paid for once.** The ladder falls about five-fold from\n * mirror to rough, and that reads as energy being lost. It is not: a mirror concentrates the one\n * bright wall into a sharp image and a rough surface averages it against five dark ones, so the\n * falloff is the room's own content. The normalised falloff also happens to track one-minus-\n * roughness closely, which made the old term look like the cause \u2014 and removing that term entirely\n * changed nothing, which is what settled it. Perturb before believing a match.\n *\n * **What it costs** is a fit rather than the integral: the published error is around one per cent\n * of the tabulated term, far below what an eight-bit frame can show. **What would make it wrong**\n * is a material model this shader does not have \u2014 a clearcoat or a sheen lobe carries its own BRDF\n * and its own integral, and this fit is Trowbridge-Reitz with Smith masking and nothing else.\n */\nvec2 envBrdfApprox(float ndv, float roughness) {\n const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);\n const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);\n vec4 r = roughness * c0 + c1;\n float a004 = min(r.x * r.x, exp2(-9.28 * ndv)) * r.x + r.y;\n return vec2(-1.04, 1.04) * a004 + r.zw;\n}";
|
|
28
34
|
export declare const LOBES_GLSL: string;
|