@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
|
@@ -350,9 +350,30 @@ export function towardCameraFade(direction: ReadonlyVec3, viewDirection: Readonl
|
|
|
350
350
|
return Math.min(1, Math.max(0, (along + 1) / 2));
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
/**
|
|
354
|
+
* What a `ReflectiveSurface` is made with: its box, which way the surfaces inside it face, and how
|
|
355
|
+
* much and how far they reflect. Every number but the box has a default; see each field.
|
|
356
|
+
*/
|
|
353
357
|
export interface ReflectiveSurfaceOptions extends DecalBox {
|
|
354
|
-
/**
|
|
358
|
+
/**
|
|
359
|
+
* How much of what the ray finds lands on the surface, 0 to 1: one share over the box, or with
|
|
360
|
+
* `fresnel` the surface's reflectance head-on.
|
|
361
|
+
*/
|
|
355
362
|
readonly strength?: number;
|
|
363
|
+
/**
|
|
364
|
+
* Whether `strength` is the surface's reflectance head-on, F0, rather than one share over the
|
|
365
|
+
* box. Each pixel then returns `F0 · A + B` of what its ray finds, the split-sum environment BRDF
|
|
366
|
+
* at its own view and `roughness` — Karis's fit, the one the lit stage reflects its environment
|
|
367
|
+
* by — so a floor returns more toward the horizon than underfoot, and the share follows the
|
|
368
|
+
* camera as Unreal weighs its reflections. **False by default**, the one share as before.
|
|
369
|
+
*
|
|
370
|
+
* **What it gives up**: the pass has no material buffer, so the roughness and F0 are the box's,
|
|
371
|
+
* not each pixel's, and nothing occludes the reflection where the lit stage's ambient occlusion
|
|
372
|
+
* would. A floor of one material is what it is for.
|
|
373
|
+
*/
|
|
374
|
+
readonly fresnel?: boolean;
|
|
375
|
+
/** The surface's roughness, 0 to 1, that `fresnel` weighs by. 0, a polished surface, if absent. */
|
|
376
|
+
readonly roughness?: number;
|
|
356
377
|
/** How far a ray may travel, world metres. */
|
|
357
378
|
readonly reachM?: number;
|
|
358
379
|
/** How deep a depth sample is treated as being. See `ScreenSpaceMarch.thicknessM`. */
|
|
@@ -382,6 +403,9 @@ export class ReflectiveSurface {
|
|
|
382
403
|
readonly tint = new Float32Array(3);
|
|
383
404
|
|
|
384
405
|
strength: number;
|
|
406
|
+
/** See `ReflectiveSurfaceOptions.fresnel`. */
|
|
407
|
+
fresnel: boolean;
|
|
408
|
+
roughness: number;
|
|
385
409
|
reachM: number;
|
|
386
410
|
thicknessM: number;
|
|
387
411
|
steps: number;
|
|
@@ -405,6 +429,8 @@ export class ReflectiveSurface {
|
|
|
405
429
|
/* Half, because a mirror is the rare case: a wet floor, polished stone and a car bonnet all
|
|
406
430
|
return a fraction of what lands on them, and a consumer who wants a mirror asks for one. */
|
|
407
431
|
this.strength = options.strength ?? 0.5;
|
|
432
|
+
this.fresnel = options.fresnel ?? false;
|
|
433
|
+
this.roughness = options.roughness ?? 0;
|
|
408
434
|
/* Eight metres covers a room and a street corner. Further costs steps rather than accuracy,
|
|
409
435
|
since the step is the reach divided by them. */
|
|
410
436
|
this.reachM = options.reachM ?? 8;
|
|
@@ -469,6 +495,8 @@ export interface RecordedReflection {
|
|
|
469
495
|
readonly axis: Float32Array;
|
|
470
496
|
readonly tint: Float32Array;
|
|
471
497
|
strength: number;
|
|
498
|
+
fresnel: boolean;
|
|
499
|
+
roughness: number;
|
|
472
500
|
reachM: number;
|
|
473
501
|
thicknessM: number;
|
|
474
502
|
steps: number;
|
|
@@ -482,6 +510,8 @@ function newRecord(): RecordedReflection {
|
|
|
482
510
|
axis: new Float32Array(3),
|
|
483
511
|
tint: new Float32Array(3),
|
|
484
512
|
strength: 0.5,
|
|
513
|
+
fresnel: false,
|
|
514
|
+
roughness: 0,
|
|
485
515
|
reachM: 8,
|
|
486
516
|
thicknessM: 0.25,
|
|
487
517
|
steps: 24,
|
|
@@ -537,6 +567,8 @@ export class ReflectionQueue {
|
|
|
537
567
|
record.axis.set(surface.axis);
|
|
538
568
|
record.tint.set(surface.tint);
|
|
539
569
|
record.strength = Math.min(1, Math.max(0, surface.strength));
|
|
570
|
+
record.fresnel = surface.fresnel;
|
|
571
|
+
record.roughness = Math.min(1, Math.max(0, surface.roughness));
|
|
540
572
|
record.reachM = Math.max(0, surface.reachM);
|
|
541
573
|
record.thicknessM = Math.max(0, surface.thicknessM);
|
|
542
574
|
record.steps = Math.min(32, Math.max(1, Math.round(surface.steps)));
|
|
@@ -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
|
/**
|
|
@@ -128,6 +130,11 @@ uniform sampler2D uSource;
|
|
|
128
130
|
uniform vec2 uTexel;
|
|
129
131
|
/** In scene units. 1.0 is "brighter than white", which is the useful place to start. */
|
|
130
132
|
uniform float uThreshold;
|
|
133
|
+
/**
|
|
134
|
+
* Scene units over which a colour comes in past the threshold, or 0 to subtract the threshold
|
|
135
|
+
* instead. See \`BloomResponse.ramp\`.
|
|
136
|
+
*/
|
|
137
|
+
uniform float uRamp;
|
|
131
138
|
|
|
132
139
|
out vec4 fragColor;
|
|
133
140
|
|
|
@@ -144,6 +151,8 @@ ${BLOOM_BRIGHTNESS}
|
|
|
144
151
|
vec3 fetch(vec2 uv) {
|
|
145
152
|
vec3 c = min(textureLod(uSource, uv, 0.0).rgb, vec3(BLOOM_CEILING));
|
|
146
153
|
float bright = brightness(c);
|
|
154
|
+
/* A ramp keeps a share of the colour, all of it a ramp's width past the line. */
|
|
155
|
+
if (uRamp > 0.0) return c * clamp((bright - uThreshold) / uRamp, 0.0, 1.0);
|
|
147
156
|
return c * (max(bright - uThreshold, 0.0) / max(bright, 1e-4));
|
|
148
157
|
}
|
|
149
158
|
|
|
@@ -194,7 +203,9 @@ ${BLOOM_BOXES}
|
|
|
194
203
|
/**
|
|
195
204
|
* Stage three: a 3x3 tent, added into the level above.
|
|
196
205
|
*
|
|
197
|
-
* **Additive rather than a mix, and each octave counts the same
|
|
206
|
+
* **Additive rather than a mix, and each octave counts the same** unless a tint says otherwise:
|
|
207
|
+
* the level being added onto is multiplied by its own tint as it is, by the blend, and the deepest
|
|
208
|
+
* by `uScale` on the first draw. White everywhere is exactly the sum. A lone bright pixel arrives
|
|
198
209
|
* at level n divided by four to the n, spread over four to the n pixels, so summing the levels
|
|
199
210
|
* lifts its own pixel by about a third and lays a long shallow tail around it. That falloff is
|
|
200
211
|
* what a real glare looks like; a single gaussian gives a disc with an end to it.
|
|
@@ -209,6 +220,12 @@ precision highp float;
|
|
|
209
220
|
in vec2 vUv;
|
|
210
221
|
|
|
211
222
|
uniform sampler2D uSource;
|
|
223
|
+
/**
|
|
224
|
+
* What the level being read is multiplied by: its tint, on the first draw up from the deepest
|
|
225
|
+
* level, and white on every other — each other level's tint is applied to the level below as it
|
|
226
|
+
* is added onto, by the blend. See \`BloomResponse.tints\`.
|
|
227
|
+
*/
|
|
228
|
+
uniform vec3 uScale;
|
|
212
229
|
/** How far the tent reaches, in UV. One number for the whole chain. */
|
|
213
230
|
uniform float uRadius;
|
|
214
231
|
|
|
@@ -227,6 +244,6 @@ void main() {
|
|
|
227
244
|
vec3 i = textureLod(uSource, vUv + vec2( r, -r), 0.0).rgb;
|
|
228
245
|
|
|
229
246
|
vec3 sum = e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i);
|
|
230
|
-
fragColor = vec4(sum * 0.0625, 1.0);
|
|
247
|
+
fragColor = vec4(sum * 0.0625 * uScale, 1.0);
|
|
231
248
|
}
|
|
232
249
|
`;
|
|
@@ -107,7 +107,13 @@ void main() {
|
|
|
107
107
|
* the glow. A single falloff gives either a hard line with no atmosphere or a
|
|
108
108
|
* soft smudge with no filament.
|
|
109
109
|
*/
|
|
110
|
-
|
|
110
|
+
/*
|
|
111
|
+
* Clamped, because a multisampled target shades a pixel on the quad's edge at its centre, which
|
|
112
|
+
* may lie outside the quad: there 'vSide' runs past one, 'across' goes negative, and 'pow' of a
|
|
113
|
+
* negative number is NaN. One NaN pixel is a dark speck; bloom then spreads it into a white disc
|
|
114
|
+
* the size of the blur. Found by a fight drawing short arcs at 4 samples.
|
|
115
|
+
*/
|
|
116
|
+
float across = clamp(1.0 - abs(vSide), 0.0, 1.0);
|
|
111
117
|
float core = pow(across, 10.0);
|
|
112
118
|
/*
|
|
113
119
|
* A *wide* halo, deliberately. This exponent is the single biggest control over
|
|
@@ -101,6 +101,12 @@ void main() {
|
|
|
101
101
|
#endif
|
|
102
102
|
#if INSTANCED
|
|
103
103
|
mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
|
|
104
|
+
/* The bottom row is the instance's texture cell, rebuilt as the row it stands for: see flat. */
|
|
105
|
+
vec4 instanceCell = vec4(model[0].w, model[1].w, model[2].w, model[3].w);
|
|
106
|
+
model[0].w = 0.0;
|
|
107
|
+
model[1].w = 0.0;
|
|
108
|
+
model[2].w = 0.0;
|
|
109
|
+
model[3].w = 1.0;
|
|
104
110
|
#else
|
|
105
111
|
mat4 model = uModel;
|
|
106
112
|
#endif
|
|
@@ -125,7 +131,12 @@ void main() {
|
|
|
125
131
|
vLightPosition = uLightViewProj * vec4(bent, world.w);
|
|
126
132
|
gl_Position = vLightPosition;
|
|
127
133
|
#if CUTOUT
|
|
134
|
+
#if INSTANCED
|
|
135
|
+
/* A cutout instance's shadow is cut by its own cell, as its picture is. */
|
|
136
|
+
vUv = vec3((aUv.xy * instanceCell.xy + instanceCell.zw) * uUvScale + uUvOffset, aUv.z);
|
|
137
|
+
#else
|
|
128
138
|
vUv = vec3(aUv.xy * uUvScale + uUvOffset, aUv.z);
|
|
139
|
+
#endif
|
|
129
140
|
vAlphaCutout = uAlphaCutout;
|
|
130
141
|
#endif
|
|
131
142
|
#if GLASS
|
|
@@ -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,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
|
+
`;
|
|
@@ -29,7 +29,12 @@ vec4 glassUnmix(vec4 mip, float centreClarity) {
|
|
|
29
29
|
* about which optional code a lit program carries. See `litSwitchesGlsl`.
|
|
30
30
|
*/
|
|
31
31
|
export type LitSwitch =
|
|
32
|
-
|
|
32
|
+
| 'GLASS_SHADOWS'
|
|
33
|
+
| 'CLUSTERED_LIGHTS'
|
|
34
|
+
| 'LIGHT_FIXTURES'
|
|
35
|
+
| 'SURFACE_EFFECTS'
|
|
36
|
+
| 'DRIFT_LIGHT'
|
|
37
|
+
| 'PHYSICAL_SPECULAR';
|
|
33
38
|
|
|
34
39
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
35
40
|
export function noLitSwitches(): Record<LitSwitch, boolean> {
|
|
@@ -39,6 +44,7 @@ export function noLitSwitches(): Record<LitSwitch, boolean> {
|
|
|
39
44
|
LIGHT_FIXTURES: false,
|
|
40
45
|
SURFACE_EFFECTS: false,
|
|
41
46
|
DRIFT_LIGHT: false,
|
|
47
|
+
PHYSICAL_SPECULAR: false,
|
|
42
48
|
};
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -61,7 +67,8 @@ export interface LitSwitches {
|
|
|
61
67
|
* first uses them — a measured profile or a cookie, a material with an effects table, a DriftLight
|
|
62
68
|
* volume — and then on for good; see `PipelineCache.enable` and the WebGL2 renderer's rebuild.
|
|
63
69
|
* Measured on RADV against the lit shader with glass already out: 6,852 instructions and 120
|
|
64
|
-
* registers, 4,641 and 96 with all four out.
|
|
70
|
+
* registers, 4,641 and 96 with all four out. `PHYSICAL_SPECULAR` is a sixth, declared with the
|
|
71
|
+
* models so that theirs keep their ids (`modelsGlsl`).
|
|
65
72
|
*/
|
|
66
73
|
export function litSwitchesGlsl(switches: LitSwitches): string {
|
|
67
74
|
return /* glsl */ `
|
|
@@ -14,7 +14,9 @@ import { CLOTH_BINDING_GLSL } from '../clothBinding.ts';
|
|
|
14
14
|
import { SKINNING_GLSL } from '../skinning.ts';
|
|
15
15
|
import { SURFACE_GLSL } from './surface.ts';
|
|
16
16
|
import { MAIN_GLSL } from './main.ts';
|
|
17
|
-
import {
|
|
17
|
+
import { modelsGlsl } from './models.ts';
|
|
18
|
+
import { AREA_LIGHT_GLSL } from './areaLight.ts';
|
|
19
|
+
import { NORMAL_MAP_GLSL } from './normalMap.ts';
|
|
18
20
|
import { DRIFT_LIGHT_GLSL } from './driftLight.ts';
|
|
19
21
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.ts';
|
|
20
22
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from '../vertexChannel.ts';
|
|
@@ -107,7 +109,17 @@ layout(location = 11) in vec4 aInstanceModel0;
|
|
|
107
109
|
layout(location = 12) in vec4 aInstanceModel1;
|
|
108
110
|
layout(location = 13) in vec4 aInstanceModel2;
|
|
109
111
|
layout(location = 14) in vec4 aInstanceModel3;
|
|
110
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The instance's tint, and its opacity in w: \`MeshInstances.alphas\`, 1 where none was given — or,
|
|
114
|
+
* in a lightmapped batch's pipeline, its region of the page (\`MeshInstances.lightmapRegions\`).
|
|
115
|
+
*/
|
|
116
|
+
layout(location = 15) in vec4 aInstanceTint;
|
|
117
|
+
/*
|
|
118
|
+
* On in the pipeline a lightmapped material draws an instanced batch with, where the lane above is
|
|
119
|
+
* a region and not a tint: the second coordinates leave scaled and offset by it, under a tint and an
|
|
120
|
+
* opacity of one. See lightmap.ts.
|
|
121
|
+
*/
|
|
122
|
+
const bool LIGHTMAP_REGIONS = false; // wgsl:override
|
|
111
123
|
#else
|
|
112
124
|
uniform mat4 uModel;
|
|
113
125
|
/**
|
|
@@ -233,7 +245,17 @@ void main() {
|
|
|
233
245
|
#endif
|
|
234
246
|
#if INSTANCED
|
|
235
247
|
mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
|
|
236
|
-
|
|
248
|
+
/*
|
|
249
|
+
* **The bottom row is the instance's texture cell** (\`MeshInstances.uvRegions\`), which an
|
|
250
|
+
* affine placement leaves free: read, and rebuilt as the \`[0, 0, 0, 1]\` it stands for.
|
|
251
|
+
*/
|
|
252
|
+
vec4 instanceCell = vec4(model[0].w, model[1].w, model[2].w, model[3].w);
|
|
253
|
+
model[0].w = 0.0;
|
|
254
|
+
model[1].w = 0.0;
|
|
255
|
+
model[2].w = 0.0;
|
|
256
|
+
model[3].w = 1.0;
|
|
257
|
+
vec3 tint = aInstanceTint.rgb;
|
|
258
|
+
if (LIGHTMAP_REGIONS) tint = vec3(1.0);
|
|
237
259
|
#else
|
|
238
260
|
mat4 model = uModel;
|
|
239
261
|
vec3 tint = uTint;
|
|
@@ -280,17 +302,31 @@ void main() {
|
|
|
280
302
|
vColor = aColor * tint;
|
|
281
303
|
vEmissive = aEmissive;
|
|
282
304
|
vSpecular = aSpecular;
|
|
305
|
+
#if INSTANCED
|
|
306
|
+
/* The instance's cell first, then the material's scale and offset. */
|
|
307
|
+
vUv = vec3((aUv.xy * instanceCell.xy + instanceCell.zw) * uUvScale + uUvOffset, aUv.z);
|
|
308
|
+
#else
|
|
283
309
|
vUv = vec3(aUv.xy * uUvScale + uUvOffset, aUv.z);
|
|
310
|
+
#endif
|
|
284
311
|
vEmissiveColor = aEmissiveColor;
|
|
285
312
|
vRoughness = aRoughness;
|
|
286
313
|
vGrain = aGrain;
|
|
287
314
|
vRelief = aRelief;
|
|
288
315
|
vTangent = vec4(worldTangent, aTangent.w);
|
|
289
316
|
#if INSTANCED
|
|
290
|
-
/*
|
|
317
|
+
/*
|
|
318
|
+
* No channel on this path; the neutral values are what every mesh read before it existed — but
|
|
319
|
+
* for the opacity, which an instance carries of its own, 1 where none was given.
|
|
320
|
+
*/
|
|
291
321
|
vSkyDirect = 1.0;
|
|
292
|
-
vAlpha =
|
|
322
|
+
vAlpha = aInstanceTint.w;
|
|
293
323
|
vThickness = 1.0;
|
|
324
|
+
if (LIGHTMAP_REGIONS) {
|
|
325
|
+
/* Out of the lane's \`-1 - uv\`, through the region, and back into it. See lightmap.ts. */
|
|
326
|
+
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + aInstanceTint.z);
|
|
327
|
+
vRelief = -1.0 - ((-1.0 - aRelief) * aInstanceTint.y + aInstanceTint.w);
|
|
328
|
+
vAlpha = 1.0;
|
|
329
|
+
}
|
|
294
330
|
#else
|
|
295
331
|
vSkyDirect = aChannel.y;
|
|
296
332
|
vAlpha = aChannel.z;
|
|
@@ -427,6 +463,11 @@ export interface FlatShaderOptions {
|
|
|
427
463
|
readonly surfaceEffects?: boolean;
|
|
428
464
|
/** Whether it reads a DriftLight volume. True when absent. See `litSwitchesGlsl`. */
|
|
429
465
|
readonly driftLight?: boolean;
|
|
466
|
+
/**
|
|
467
|
+
* Whether it carries GGX's own highlight for a material that asks (`physicalSpecular`). True
|
|
468
|
+
* when absent. See `modelsGlsl`.
|
|
469
|
+
*/
|
|
470
|
+
readonly physicalSpecular?: boolean;
|
|
430
471
|
/**
|
|
431
472
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
432
473
|
*
|
|
@@ -563,13 +604,17 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
563
604
|
* and `main` is unconditional by construction.
|
|
564
605
|
*/
|
|
565
606
|
TANGENT_FRAME_GLSL,
|
|
607
|
+
/* After the frame it calls, unconditional for the same reason. See normalMap.ts. */
|
|
608
|
+
NORMAL_MAP_GLSL,
|
|
566
609
|
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
567
610
|
CUTOUT_COVERAGE_GLSL,
|
|
568
611
|
CUTOUT_DITHER_GLSL,
|
|
569
612
|
DRIFT_LIGHT_GLSL,
|
|
570
613
|
SURFACE_EFFECTS_GLSL,
|
|
571
614
|
/* Depth zero and after the tangent frame and the lobes it calls; see models.ts. */
|
|
572
|
-
|
|
615
|
+
modelsGlsl(options.physicalSpecular ?? true),
|
|
616
|
+
/* After the models, whose answer to a rectangle it asks for. See areaLight.ts. */
|
|
617
|
+
AREA_LIGHT_GLSL,
|
|
573
618
|
MAIN_GLSL,
|
|
574
619
|
].join('\n'),
|
|
575
620
|
{
|
|
@@ -30,6 +30,55 @@ export const EMISSIVE_SHADOW_SHARE = 0.55;
|
|
|
30
30
|
*/
|
|
31
31
|
export const MIN_LOBE_ALPHA = 0.01;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Karis's fit to the split-sum environment BRDF, as GLSL: the lit stage's, and the screen-space
|
|
35
|
+
* reflection's when a surface asks for its Fresnel (`ReflectiveSurface.fresnel`). One text, so the
|
|
36
|
+
* two cannot drift.
|
|
37
|
+
*/
|
|
38
|
+
export const ENV_BRDF_GLSL = `/**
|
|
39
|
+
* The other half of the split-sum approximation: what fraction of an environment a surface returns.
|
|
40
|
+
*
|
|
41
|
+
* The convolution decides *what* a surface reflects at a given roughness; this decides *how much*.
|
|
42
|
+
* Before it the amount was fresnel times one-minus-roughness, which is a curve somebody chose
|
|
43
|
+
* standing in for an integral.
|
|
44
|
+
*
|
|
45
|
+
* Karis's analytic fit to the environment BRDF, so there is **no lookup texture and no texture
|
|
46
|
+
* unit**: a few multiply-adds against roughness and the view angle. x scales the surface's own
|
|
47
|
+
* reflectance at normal incidence and y is the additive grazing term, so a smooth dielectric at
|
|
48
|
+
* f0 0.04 returns about 0.046 head-on and approaches 1 at the edge, which is what a dielectric
|
|
49
|
+
* does. At roughness 1 it returns about 0.016 rather than the zero one-minus-roughness gave, which
|
|
50
|
+
* is the substantive difference: a fully rough dielectric does reflect its surroundings.
|
|
51
|
+
*
|
|
52
|
+
* **It replaces the Fresnel factor as well as the thinning**, because the fit already contains the
|
|
53
|
+
* angular dependence; multiplying by a separate Fresnel term would apply it twice.
|
|
54
|
+
*
|
|
55
|
+
* **How much this is worth was measured, and it is worth less than it looks on a dark room.**
|
|
56
|
+
* On demo/dev/ibl.html?ladder=1 the whole ladder moved by at most 0.2 of 255 when the old term was
|
|
57
|
+
* replaced. That is not the term failing: at high roughness the surface reads the coarsest levels
|
|
58
|
+
* of the chain, and in a room of five dark walls and one bright one those levels are dark, so a
|
|
59
|
+
* weight three times larger against a near-zero radiance is still near zero. It is worth much more
|
|
60
|
+
* against a bright environment, which is what a loaded sky is and what no captured room here is.
|
|
61
|
+
*
|
|
62
|
+
* **A warning about reading that page, paid for once.** The ladder falls about five-fold from
|
|
63
|
+
* mirror to rough, and that reads as energy being lost. It is not: a mirror concentrates the one
|
|
64
|
+
* bright wall into a sharp image and a rough surface averages it against five dark ones, so the
|
|
65
|
+
* falloff is the room's own content. The normalised falloff also happens to track one-minus-
|
|
66
|
+
* roughness closely, which made the old term look like the cause — and removing that term entirely
|
|
67
|
+
* changed nothing, which is what settled it. Perturb before believing a match.
|
|
68
|
+
*
|
|
69
|
+
* **What it costs** is a fit rather than the integral: the published error is around one per cent
|
|
70
|
+
* of the tabulated term, far below what an eight-bit frame can show. **What would make it wrong**
|
|
71
|
+
* is a material model this shader does not have — a clearcoat or a sheen lobe carries its own BRDF
|
|
72
|
+
* and its own integral, and this fit is Trowbridge-Reitz with Smith masking and nothing else.
|
|
73
|
+
*/
|
|
74
|
+
vec2 envBrdfApprox(float ndv, float roughness) {
|
|
75
|
+
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
|
76
|
+
const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
|
77
|
+
vec4 r = roughness * c0 + c1;
|
|
78
|
+
float a004 = min(r.x * r.x, exp2(-9.28 * ndv)) * r.x + r.y;
|
|
79
|
+
return vec2(-1.04, 1.04) * a004 + r.zw;
|
|
80
|
+
}`;
|
|
81
|
+
|
|
33
82
|
export const LOBES_GLSL = `float shadowReach(float distance, float maxDistance) {
|
|
34
83
|
return 1.0 - smoothstep(
|
|
35
84
|
maxDistance * ${POINT_SHADOW_FADE_START.toFixed(2)},
|
|
@@ -156,49 +205,7 @@ float sphereLobe(float ndh, float roughness, float sourceRadius, float dist) {
|
|
|
156
205
|
return energy * (w2 * w2) / max(d * d, 1e-8);
|
|
157
206
|
}
|
|
158
207
|
|
|
159
|
-
|
|
160
|
-
* The other half of the split-sum approximation: what fraction of an environment a surface returns.
|
|
161
|
-
*
|
|
162
|
-
* The convolution decides *what* a surface reflects at a given roughness; this decides *how much*.
|
|
163
|
-
* Before it the amount was fresnel times one-minus-roughness, which is a curve somebody chose
|
|
164
|
-
* standing in for an integral.
|
|
165
|
-
*
|
|
166
|
-
* Karis's analytic fit to the environment BRDF, so there is **no lookup texture and no texture
|
|
167
|
-
* unit**: a few multiply-adds against roughness and the view angle. x scales the surface's own
|
|
168
|
-
* reflectance at normal incidence and y is the additive grazing term, so a smooth dielectric at
|
|
169
|
-
* f0 0.04 returns about 0.046 head-on and approaches 1 at the edge, which is what a dielectric
|
|
170
|
-
* does. At roughness 1 it returns about 0.016 rather than the zero one-minus-roughness gave, which
|
|
171
|
-
* is the substantive difference: a fully rough dielectric does reflect its surroundings.
|
|
172
|
-
*
|
|
173
|
-
* **It replaces the Fresnel factor as well as the thinning**, because the fit already contains the
|
|
174
|
-
* angular dependence; multiplying by a separate Fresnel term would apply it twice.
|
|
175
|
-
*
|
|
176
|
-
* **How much this is worth was measured, and it is worth less than it looks on a dark room.**
|
|
177
|
-
* On demo/dev/ibl.html?ladder=1 the whole ladder moved by at most 0.2 of 255 when the old term was
|
|
178
|
-
* replaced. That is not the term failing: at high roughness the surface reads the coarsest levels
|
|
179
|
-
* of the chain, and in a room of five dark walls and one bright one those levels are dark, so a
|
|
180
|
-
* weight three times larger against a near-zero radiance is still near zero. It is worth much more
|
|
181
|
-
* against a bright environment, which is what a loaded sky is and what no captured room here is.
|
|
182
|
-
*
|
|
183
|
-
* **A warning about reading that page, paid for once.** The ladder falls about five-fold from
|
|
184
|
-
* mirror to rough, and that reads as energy being lost. It is not: a mirror concentrates the one
|
|
185
|
-
* bright wall into a sharp image and a rough surface averages it against five dark ones, so the
|
|
186
|
-
* falloff is the room's own content. The normalised falloff also happens to track one-minus-
|
|
187
|
-
* roughness closely, which made the old term look like the cause — and removing that term entirely
|
|
188
|
-
* changed nothing, which is what settled it. Perturb before believing a match.
|
|
189
|
-
*
|
|
190
|
-
* **What it costs** is a fit rather than the integral: the published error is around one per cent
|
|
191
|
-
* of the tabulated term, far below what an eight-bit frame can show. **What would make it wrong**
|
|
192
|
-
* is a material model this shader does not have — a clearcoat or a sheen lobe carries its own BRDF
|
|
193
|
-
* and its own integral, and this fit is Trowbridge-Reitz with Smith masking and nothing else.
|
|
194
|
-
*/
|
|
195
|
-
vec2 envBrdfApprox(float ndv, float roughness) {
|
|
196
|
-
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
|
197
|
-
const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
|
198
|
-
vec4 r = roughness * c0 + c1;
|
|
199
|
-
float a004 = min(r.x * r.x, exp2(-9.28 * ndv)) * r.x + r.y;
|
|
200
|
-
return vec2(-1.04, 1.04) * a004 + r.zw;
|
|
201
|
-
}
|
|
208
|
+
${ENV_BRDF_GLSL}
|
|
202
209
|
|
|
203
210
|
/**
|
|
204
211
|
* How much of a prefiltered environment a surface returns, with the light that bounces more than
|