@driftengine/core 4.8.7 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +123 -11
- package/README.md +43 -38
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +19 -4
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +180 -4
- package/dist/render/backend/webgl2/renderer.js +550 -40
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
- package/dist/render/backend/webgpu/flatPass.js +191 -43
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +283 -37
- package/dist/render/backend/webgpu/renderer.js +1363 -202
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +39 -8
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +7 -1
- package/dist/render/screenSpaceReflection.js +5 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +2 -0
- package/dist/render/shaders/flat/index.d.ts +16 -0
- package/dist/render/shaders/flat/index.js +33 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +43 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +3 -3
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
- package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
- package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/water.wgsl.js +1 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +26 -0
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +106 -1
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +57 -7
- package/dist/render/surfaceTexture.js +125 -17
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +24 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +22 -3
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +672 -37
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +250 -40
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +1655 -230
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/displayRange.ts +61 -0
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +44 -8
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +7 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/glassTint.ts +5 -1
- package/src/render/shaders/flat/index.ts +49 -5
- package/src/render/shaders/flat/main.ts +43 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +3 -3
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
- package/src/render/shaders/generated/water.wgsl.ts +1 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +26 -0
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/ssrPass.ts +138 -1
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +161 -27
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -71,10 +71,10 @@ export interface PointLightSource {
|
|
|
71
71
|
inLightField?: boolean;
|
|
72
72
|
/**
|
|
73
73
|
* How the light fades with distance when it names its own way: `(1 - (d/R)^2)^n` with no distance
|
|
74
|
-
* term, `R` its radius and `n` this.
|
|
75
|
-
* so a rig authored
|
|
76
|
-
* the space it was authored for rather than one that ends a few metres out. Absent or
|
|
77
|
-
* frame's own falloff (`pointLightFalloff`), which is every light before 4.8.7.
|
|
74
|
+
* term, `R` its radius and `n` this. The usual falloff for a light without inverse-square
|
|
75
|
+
* falloff, so a rig authored that way — a fill of brightness 160 across 164 m, a key at exponent
|
|
76
|
+
* 8 — lights the space it was authored for rather than one that ends a few metres out. Absent or
|
|
77
|
+
* 0, the frame's own falloff (`pointLightFalloff`), which is every light before 4.8.7.
|
|
78
78
|
*/
|
|
79
79
|
falloffExponent?: number;
|
|
80
80
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class ReflectionSurfaceTargets {
|
|
2
|
+
private readonly gl;
|
|
3
|
+
private probe;
|
|
4
|
+
private tint;
|
|
5
|
+
private width;
|
|
6
|
+
private height;
|
|
7
|
+
private readonly all;
|
|
8
|
+
private readonly colourAlone;
|
|
9
|
+
constructor(gl: WebGL2RenderingContext);
|
|
10
|
+
/** The environment as the frame shows it, and the roughness in alpha. */
|
|
11
|
+
get probeTexture(): WebGLTexture | null;
|
|
12
|
+
/** What a found reflection is multiplied by. */
|
|
13
|
+
get tintTexture(): WebGLTexture | null;
|
|
14
|
+
/**
|
|
15
|
+
* At the frame's start, with the scene's framebuffer bound: both at its size, attached, cleared to
|
|
16
|
+
* nothing reflected, and left disabled.
|
|
17
|
+
*/
|
|
18
|
+
begin(width: number, height: number): void;
|
|
19
|
+
/** Around an opaque lit draw into the frame: its two extra outputs land. */
|
|
20
|
+
on(): void;
|
|
21
|
+
/** And after it: only the colour does. */
|
|
22
|
+
off(): void;
|
|
23
|
+
/** With the scene's framebuffer bound, after the trace has read them: see the header. */
|
|
24
|
+
detach(): void;
|
|
25
|
+
dispose(): void;
|
|
26
|
+
private make;
|
|
27
|
+
private release;
|
|
28
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frame's materials' reflection maps on WebGL2: two half-float textures on the scene's own
|
|
3
|
+
* framebuffer, beside its colour, written by the opaque lit draws as they are drawn. What the two
|
|
4
|
+
* hold is `shaders/flat/reflectionSurface.ts`'s to say; this is where they live on this backend.
|
|
5
|
+
*
|
|
6
|
+
* **Written in the frame's own pass, where WebGPU replays the draws in a second one.** A WebGL2
|
|
7
|
+
* program writing an output its framebuffer has no buffer enabled for loses that output and nothing
|
|
8
|
+
* else, so the maps are enabled around an opaque lit draw (`on`, `off`) and stay disabled for every
|
|
9
|
+
* other: the sky, glass, text and particles write no material, and a draw into a mirror, a probe or
|
|
10
|
+
* a capture is into another framebuffer, whose draw buffers this never touches.
|
|
11
|
+
*
|
|
12
|
+
* **Detached once the trace has read them** (`detach`), because the resolve then draws into the
|
|
13
|
+
* scene's framebuffer while sampling both, and a texture sampled while attached to the framebuffer
|
|
14
|
+
* being drawn is a feedback loop whether or not its buffer is enabled. `begin` attaches them again.
|
|
15
|
+
*/
|
|
16
|
+
const ZERO = new Float32Array(4);
|
|
17
|
+
export class ReflectionSurfaceTargets {
|
|
18
|
+
gl;
|
|
19
|
+
probe = null;
|
|
20
|
+
tint = null;
|
|
21
|
+
width = 0;
|
|
22
|
+
height = 0;
|
|
23
|
+
all;
|
|
24
|
+
colourAlone;
|
|
25
|
+
constructor(gl) {
|
|
26
|
+
this.gl = gl;
|
|
27
|
+
this.all = [gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2];
|
|
28
|
+
this.colourAlone = [gl.COLOR_ATTACHMENT0];
|
|
29
|
+
}
|
|
30
|
+
/** The environment as the frame shows it, and the roughness in alpha. */
|
|
31
|
+
get probeTexture() {
|
|
32
|
+
return this.probe;
|
|
33
|
+
}
|
|
34
|
+
/** What a found reflection is multiplied by. */
|
|
35
|
+
get tintTexture() {
|
|
36
|
+
return this.tint;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* At the frame's start, with the scene's framebuffer bound: both at its size, attached, cleared to
|
|
40
|
+
* nothing reflected, and left disabled.
|
|
41
|
+
*/
|
|
42
|
+
begin(width, height) {
|
|
43
|
+
const { gl } = this;
|
|
44
|
+
if (this.probe === null || this.width !== width || this.height !== height) {
|
|
45
|
+
this.release();
|
|
46
|
+
this.probe = this.make(width, height);
|
|
47
|
+
this.tint = this.make(width, height);
|
|
48
|
+
this.width = width;
|
|
49
|
+
this.height = height;
|
|
50
|
+
}
|
|
51
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.probe, 0);
|
|
52
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, this.tint, 0);
|
|
53
|
+
gl.drawBuffers(this.all);
|
|
54
|
+
gl.clearBufferfv(gl.COLOR, 1, ZERO);
|
|
55
|
+
gl.clearBufferfv(gl.COLOR, 2, ZERO);
|
|
56
|
+
gl.drawBuffers(this.colourAlone);
|
|
57
|
+
}
|
|
58
|
+
/** Around an opaque lit draw into the frame: its two extra outputs land. */
|
|
59
|
+
on() {
|
|
60
|
+
this.gl.drawBuffers(this.all);
|
|
61
|
+
}
|
|
62
|
+
/** And after it: only the colour does. */
|
|
63
|
+
off() {
|
|
64
|
+
this.gl.drawBuffers(this.colourAlone);
|
|
65
|
+
}
|
|
66
|
+
/** With the scene's framebuffer bound, after the trace has read them: see the header. */
|
|
67
|
+
detach() {
|
|
68
|
+
const { gl } = this;
|
|
69
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, null, 0);
|
|
70
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, null, 0);
|
|
71
|
+
}
|
|
72
|
+
dispose() {
|
|
73
|
+
this.release();
|
|
74
|
+
}
|
|
75
|
+
make(width, height) {
|
|
76
|
+
const { gl } = this;
|
|
77
|
+
const texture = gl.createTexture();
|
|
78
|
+
if (texture === null)
|
|
79
|
+
throw new Error('ReflectionSurfaceTargets: createTexture failed');
|
|
80
|
+
/* Unit 0, chosen rather than inherited, for the reason `SsrPass` gives. */
|
|
81
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
82
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
83
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA16F, width, height);
|
|
84
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
85
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
86
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
87
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
88
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
89
|
+
return texture;
|
|
90
|
+
}
|
|
91
|
+
release() {
|
|
92
|
+
if (this.probe !== null)
|
|
93
|
+
this.gl.deleteTexture(this.probe);
|
|
94
|
+
if (this.tint !== null)
|
|
95
|
+
this.gl.deleteTexture(this.tint);
|
|
96
|
+
this.probe = null;
|
|
97
|
+
this.tint = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Construction-time GPU quality controls shared by every renderer resource. */
|
|
2
|
+
import type { FrameReflections } from './frameReflections.ts';
|
|
2
3
|
/**
|
|
3
4
|
* What happens to a shaded colour on its way to the framebuffer.
|
|
4
5
|
*
|
|
@@ -20,8 +21,14 @@
|
|
|
20
21
|
* red where `srgb` clips each channel and turns its core pink and wide. It is the curve a ported
|
|
21
22
|
* game's own resolve applied — for a port that has to match one — and it has none of ACES's toe or
|
|
22
23
|
* desaturation. See `HIGHLIGHT_SHOULDER_GLSL` for the arithmetic.
|
|
24
|
+
*
|
|
25
|
+
* `filmic` is a film curve shaped by five numbers — a slope, a toe, a shoulder and how far black
|
|
26
|
+
* and white clip — that holds a mid grey of 0.18 at 0.18 where `aces` gives about 0.11, so a look
|
|
27
|
+
* graded against such a curve is matched by its numbers (`setFilmicCurve`) rather than by a lookup
|
|
28
|
+
* table. Applied by the composite alone, so it needs `screenEffects` and `hdrScene`; without them
|
|
29
|
+
* every pass grades with `aces` and the renderer says so once. See `filmicCurve.ts`.
|
|
23
30
|
*/
|
|
24
|
-
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder';
|
|
31
|
+
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder' | 'filmic';
|
|
25
32
|
/**
|
|
26
33
|
* The three settings whose accepted values are a closed set, written as one.
|
|
27
34
|
*
|
|
@@ -345,6 +352,18 @@ export interface RenderQuality {
|
|
|
345
352
|
* eight-bit and this buys only the consistent grading.
|
|
346
353
|
*/
|
|
347
354
|
readonly hdrScene: boolean;
|
|
355
|
+
/**
|
|
356
|
+
* Ask for a high dynamic range display: a canvas whose highlights run past paper white to as
|
|
357
|
+
* far as the display allows, where it reports one. Off by default.
|
|
358
|
+
*
|
|
359
|
+
* **Asked for, not assumed.** It needs WebGPU, a display reporting a high dynamic range, and a
|
|
360
|
+
* composite that holds the light to give it (`screenEffects` and `hdrScene`); where any of those
|
|
361
|
+
* is missing the frame goes out as it always did and the renderer says which in
|
|
362
|
+
* `displayRangeReason`. `displayRange` is the answer a game reads to choose a look graded for the
|
|
363
|
+
* output it got. `setDisplayLuminance` sets how far above paper white the highlights may reach.
|
|
364
|
+
* WebGL2 has no extended-range canvas to draw into, so there it is always the standard range.
|
|
365
|
+
*/
|
|
366
|
+
readonly highDynamicRange: boolean;
|
|
348
367
|
/** Camera-depth water tint and scattering across every visible pass. */
|
|
349
368
|
readonly underwaterAtmosphere: boolean;
|
|
350
369
|
/** Procedural noise octaves evaluated by plume shaders. */
|
|
@@ -512,14 +531,14 @@ export interface RenderQuality {
|
|
|
512
531
|
*/
|
|
513
532
|
readonly sceneSamples: number;
|
|
514
533
|
/**
|
|
515
|
-
*
|
|
534
|
+
* Motion blur, 0 to 1. Off by default.
|
|
516
535
|
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* needs only the previous view-projection,
|
|
522
|
-
*
|
|
536
|
+
* By the camera's motion everywhere, and **by each drawn object's own motion where a
|
|
537
|
+
* reconstruction draws one** (DriftTR, WebGPU): a mover's pixels smear along its path while a
|
|
538
|
+
* still stage stays sharp. Elsewhere a velocity buffer would be every mesh writing its motion into
|
|
539
|
+
* a second target — a structural change to a forward renderer — so those frames blur by the
|
|
540
|
+
* camera alone, which needs only the previous view-projection, and say so once when a draw names
|
|
541
|
+
* where it was. `setCameraMotionBlur` is the per-frame dial and the longest smear.
|
|
523
542
|
*
|
|
524
543
|
* Runs inside the composite pass, so it costs a depth sample and eight taps on the pixels
|
|
525
544
|
* that are moving and nothing on a still frame. Needs `screenEffects`, since without the
|
|
@@ -570,6 +589,18 @@ export interface RenderQuality {
|
|
|
570
589
|
* Needs `screenEffects`, since the resolve composites over the finished scene.
|
|
571
590
|
*/
|
|
572
591
|
readonly orderIndependent: boolean;
|
|
592
|
+
/**
|
|
593
|
+
* Every opaque lit surface reflects the frame by its own material: a ray marched against the
|
|
594
|
+
* frame's depth swaps the probes' share of each pixel for what it finds, at the pixel's own
|
|
595
|
+
* roughness and reflectance, and a surface past `maxRoughness` keeps the probes alone. Off by
|
|
596
|
+
* default; `true` for the defaults, or the numbers. See `frameReflections.ts`.
|
|
597
|
+
*
|
|
598
|
+
* Needs `screenEffects` and `hdrScene`, since the swap is made in scene light, and a frame of one
|
|
599
|
+
* sample; it says once where it cannot be drawn. What it costs on WebGPU is every opaque lit draw
|
|
600
|
+
* a second time without its lamps, into two half-float targets, and a march per reflective pixel;
|
|
601
|
+
* on WebGL2 the same two targets, written beside the colour by the opaque lit draws themselves.
|
|
602
|
+
*/
|
|
603
|
+
readonly screenSpaceReflections: boolean | FrameReflections;
|
|
573
604
|
/**
|
|
574
605
|
* Depth of field: how wide the blur may reach, as a fraction of the frame's height. Off by
|
|
575
606
|
* default.
|
|
@@ -85,9 +85,11 @@ export const DEFAULT_RENDER_QUALITY = Object.freeze({
|
|
|
85
85
|
sceneSamples: 1,
|
|
86
86
|
planarReflections: false,
|
|
87
87
|
hdrScene: false,
|
|
88
|
+
highDynamicRange: false,
|
|
88
89
|
cameraMotionBlur: 0,
|
|
89
90
|
temporalAa: false,
|
|
90
91
|
orderIndependent: false,
|
|
92
|
+
screenSpaceReflections: false,
|
|
91
93
|
depthOfField: 0,
|
|
92
94
|
reconstruction: 0,
|
|
93
95
|
indirectLight: false,
|
|
@@ -227,9 +229,11 @@ export function resolveRenderQuality(options = {}) {
|
|
|
227
229
|
sceneSamples: Math.max(1, Math.round(options.sceneSamples ?? DEFAULT_RENDER_QUALITY.sceneSamples)),
|
|
228
230
|
planarReflections: options.planarReflections ?? DEFAULT_RENDER_QUALITY.planarReflections,
|
|
229
231
|
hdrScene: options.hdrScene ?? DEFAULT_RENDER_QUALITY.hdrScene,
|
|
232
|
+
highDynamicRange: options.highDynamicRange ?? DEFAULT_RENDER_QUALITY.highDynamicRange,
|
|
230
233
|
cameraMotionBlur: Math.min(1, Math.max(0, options.cameraMotionBlur ?? DEFAULT_RENDER_QUALITY.cameraMotionBlur)),
|
|
231
234
|
temporalAa: options.temporalAa ?? DEFAULT_RENDER_QUALITY.temporalAa,
|
|
232
235
|
orderIndependent: options.orderIndependent ?? DEFAULT_RENDER_QUALITY.orderIndependent,
|
|
236
|
+
screenSpaceReflections: options.screenSpaceReflections ?? DEFAULT_RENDER_QUALITY.screenSpaceReflections,
|
|
233
237
|
/* Off, or a ratio at least 1.3 and at most 2 — never a number between zero and the range. */
|
|
234
238
|
reconstruction: resolveReconstruction(options.reconstruction),
|
|
235
239
|
indirectLight: options.indirectLight ?? DEFAULT_RENDER_QUALITY.indirectLight,
|
|
@@ -89,6 +89,8 @@ export declare class SceneTarget {
|
|
|
89
89
|
private gradeStrength;
|
|
90
90
|
/** The lens and the print, held like the grade. See `filmLook.ts`. */
|
|
91
91
|
private vignette;
|
|
92
|
+
/** The lens's colour fringe: red's and green's pull and where it begins. See `fringe.ts`. */
|
|
93
|
+
private readonly fringe;
|
|
92
94
|
private grain;
|
|
93
95
|
private grainSeed;
|
|
94
96
|
/** Eye adaptation: how far, and this frame's time step. See `Renderer.setAutoExposure`. */
|
|
@@ -277,6 +279,8 @@ export declare class SceneTarget {
|
|
|
277
279
|
*/
|
|
278
280
|
/** The lens's corner falloff, held until changed. See `Renderer.setVignette`. */
|
|
279
281
|
setVignette(strength: number): void;
|
|
282
|
+
/** The lens's colour fringe, held until changed. See `Renderer.setChromaticAberration`. */
|
|
283
|
+
setChromaticAberration(intensity: number, start: number): void;
|
|
280
284
|
/** The print's grain and this frame's seed, held until changed. See `Renderer.setFilmGrain`. */
|
|
281
285
|
setFilmGrain(strength: number, seed: number): void;
|
|
282
286
|
/** How far the eye adapts, and the time since the last frame. See `Renderer.setAutoExposure`. */
|
|
@@ -298,6 +302,7 @@ export declare class SceneTarget {
|
|
|
298
302
|
resolve(strength: number, reachUv: number, grade?: {
|
|
299
303
|
readonly transform: number;
|
|
300
304
|
readonly exposure: number;
|
|
305
|
+
readonly film?: Float32Array;
|
|
301
306
|
}, motion?: {
|
|
302
307
|
readonly reprojection: Float32Array;
|
|
303
308
|
readonly strength: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { glSceneDepthFormat } from './depthConvention.js';
|
|
2
2
|
import { clampGrain, clampVignette, grainSeed } from './shaders/filmLook.js';
|
|
3
|
+
import { FRINGE_FLOATS, resolveFringe } from './shaders/fringe.js';
|
|
3
4
|
import { AmbientOcclusionPass } from './ambientOcclusionPass.js';
|
|
4
5
|
import { ExposurePass } from './exposurePass.js';
|
|
5
6
|
import { clampAutoExposure } from './shaders/exposure.js';
|
|
@@ -93,6 +94,8 @@ export class SceneTarget {
|
|
|
93
94
|
gradeStrength = 0;
|
|
94
95
|
/** The lens and the print, held like the grade. See `filmLook.ts`. */
|
|
95
96
|
vignette = 0;
|
|
97
|
+
/** The lens's colour fringe: red's and green's pull and where it begins. See `fringe.ts`. */
|
|
98
|
+
fringe = new Float32Array(FRINGE_FLOATS);
|
|
96
99
|
grain = 0;
|
|
97
100
|
grainSeed = 0;
|
|
98
101
|
/** Eye adaptation: how far, and this frame's time step. See `Renderer.setAutoExposure`. */
|
|
@@ -605,6 +608,10 @@ export class SceneTarget {
|
|
|
605
608
|
setVignette(strength) {
|
|
606
609
|
this.vignette = clampVignette(strength);
|
|
607
610
|
}
|
|
611
|
+
/** The lens's colour fringe, held until changed. See `Renderer.setChromaticAberration`. */
|
|
612
|
+
setChromaticAberration(intensity, start) {
|
|
613
|
+
resolveFringe(intensity, start, this.fringe);
|
|
614
|
+
}
|
|
608
615
|
/** The print's grain and this frame's seed, held until changed. See `Renderer.setFilmGrain`. */
|
|
609
616
|
setFilmGrain(strength, seed) {
|
|
610
617
|
this.grain = clampGrain(strength);
|
|
@@ -654,7 +661,10 @@ export class SceneTarget {
|
|
|
654
661
|
this.gradeSize = lut.size;
|
|
655
662
|
}
|
|
656
663
|
resolve(strength, reachUv,
|
|
657
|
-
/*
|
|
664
|
+
/*
|
|
665
|
+
* The grade, applied here because this is the last pass. See RUSH_FRAG's own note. `film` is
|
|
666
|
+
* the `filmic` curve's eight constants (`resolveFilmicCurve`), read only under transform 4.
|
|
667
|
+
*/
|
|
658
668
|
grade = {
|
|
659
669
|
transform: 0,
|
|
660
670
|
exposure: 1,
|
|
@@ -809,6 +819,12 @@ export class SceneTarget {
|
|
|
809
819
|
gl.uniform1f(this.uniforms['uReach'] ?? null, reachUv);
|
|
810
820
|
gl.uniform1i(this.uniforms['uOutputTransform'] ?? null, grade.transform);
|
|
811
821
|
gl.uniform1f(this.uniforms['uOutputExposure'] ?? null, grade.exposure);
|
|
822
|
+
if (grade.film !== undefined) {
|
|
823
|
+
gl.uniform4fv(this.uniforms['uFilmA'] ?? null, grade.film, 0, 4);
|
|
824
|
+
gl.uniform4fv(this.uniforms['uFilmB'] ?? null, grade.film, 4, 4);
|
|
825
|
+
}
|
|
826
|
+
/* A standard range always here: see `Renderer.displayRange`. */
|
|
827
|
+
gl.uniform1f(this.uniforms['uDisplayHeadroom'] ?? null, 1);
|
|
812
828
|
/*
|
|
813
829
|
* Depth on its own unit, and the reprojection with it. Bound every frame rather than
|
|
814
830
|
* once, because a texture unit is context state and any pass between two frames is free
|
|
@@ -894,6 +910,12 @@ export class SceneTarget {
|
|
|
894
910
|
gl.bindTexture(gl.TEXTURE_2D, localGrid ?? scene);
|
|
895
911
|
gl.uniform1i(this.uniforms['uExposureLocal'] ?? null, 6);
|
|
896
912
|
gl.uniform1f(this.uniforms['uLocalExposure'] ?? null, localGrid === null ? 0 : this.localExposure);
|
|
913
|
+
/* Each object's own motion is WebGPU's under a reconstruction; here the scene stands in on
|
|
914
|
+
unit 7 and the flag says not to read it, so the camera's motion alone blurs. */
|
|
915
|
+
gl.activeTexture(gl.TEXTURE7);
|
|
916
|
+
gl.bindTexture(gl.TEXTURE_2D, scene);
|
|
917
|
+
gl.uniform1i(this.uniforms['uMotion'] ?? null, 7);
|
|
918
|
+
gl.uniform1f(this.uniforms['uObjectMotion'] ?? null, 0);
|
|
897
919
|
gl.activeTexture(gl.TEXTURE0);
|
|
898
920
|
/* No unit and no texture: a flat colour, not a sample. Uploaded unconditionally — two
|
|
899
921
|
floats and a triplet cost nothing next to the triangle this pass already draws — and it
|
|
@@ -902,6 +924,7 @@ export class SceneTarget {
|
|
|
902
924
|
gl.uniform3fv(this.uniforms['uVeilColor'] ?? null, veilColor);
|
|
903
925
|
gl.uniform1f(this.uniforms['uVeilAlpha'] ?? null, veilAlpha);
|
|
904
926
|
gl.uniform1f(this.uniforms['uVignette'] ?? null, this.vignette);
|
|
927
|
+
gl.uniform3fv(this.uniforms['uFringe'] ?? null, this.fringe);
|
|
905
928
|
gl.uniform2f(this.uniforms['uGrain'] ?? null, this.grain, this.grainSeed);
|
|
906
929
|
gl.bindVertexArray(this.vao);
|
|
907
930
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
@@ -24,6 +24,11 @@ import type { DecalBox } from './decalProjector.ts';
|
|
|
24
24
|
* which is the same price the temporal row declined for a velocity buffer. A caller declares a
|
|
25
25
|
* `ReflectiveSurface` instead: a box, and a facing rule for which surfaces inside it reflect. It is
|
|
26
26
|
* the shape `DecalProjector` already has, for the same reason.
|
|
27
|
+
*
|
|
28
|
+
* **Or every surface by its own material**, where the renderer was built with
|
|
29
|
+
* `screenSpaceReflections`: the lit stage leaves two maps of what it reflected — not a G-buffer, the
|
|
30
|
+
* environment's share and its tint, which is all a swap needs — and the same trace runs in a
|
|
31
|
+
* material mode over every pixel. See `frameReflections.ts`.
|
|
27
32
|
*/
|
|
28
33
|
/**
|
|
29
34
|
* Clip Y negated, for a backend whose framebuffer rows run downward.
|
|
@@ -174,7 +179,8 @@ export interface ReflectiveSurfaceOptions extends DecalBox {
|
|
|
174
179
|
* box. Each pixel then returns `F0 · A + B` of what its ray finds, the split-sum environment BRDF
|
|
175
180
|
* at its own view and `roughness` — Karis's fit, the one the lit stage reflects its environment
|
|
176
181
|
* by — so a floor returns more toward the horizon than underfoot, and the share follows the
|
|
177
|
-
* camera as
|
|
182
|
+
* camera as a physically based renderer weighs its reflections. **False by default**, the one
|
|
183
|
+
* share as before.
|
|
178
184
|
*
|
|
179
185
|
* **What it gives up**: the pass has no material buffer, so the roughness and F0 are the box's,
|
|
180
186
|
* not each pixel's, and nothing occludes the reflection where the lit stage's ambient occlusion
|
|
@@ -23,6 +23,11 @@ import { decalToWorldMatrix, worldToDecalMatrix } from './decalProjector.js';
|
|
|
23
23
|
* which is the same price the temporal row declined for a velocity buffer. A caller declares a
|
|
24
24
|
* `ReflectiveSurface` instead: a box, and a facing rule for which surfaces inside it reflect. It is
|
|
25
25
|
* the shape `DecalProjector` already has, for the same reason.
|
|
26
|
+
*
|
|
27
|
+
* **Or every surface by its own material**, where the renderer was built with
|
|
28
|
+
* `screenSpaceReflections`: the lit stage leaves two maps of what it reflected — not a G-buffer, the
|
|
29
|
+
* environment's share and its tint, which is all a swap needs — and the same trace runs in a
|
|
30
|
+
* material mode over every pixel. See `frameReflections.ts`.
|
|
26
31
|
*/
|
|
27
32
|
/**
|
|
28
33
|
* Clip Y negated, for a backend whose framebuffer rows run downward.
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
* gets motion without direction. That is what separates a spell from a lamp on a
|
|
17
17
|
* dimmer.
|
|
18
18
|
*/
|
|
19
|
-
export declare const ARCANE_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\nin float vSeed;\nin float vDistance;\nin vec3 vWorldPos;\n\nuniform float uTime;\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(
|
|
19
|
+
export declare const ARCANE_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\nin float vSeed;\nin float vDistance;\nin vec3 vWorldPos;\n\nuniform float uTime;\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 // Where the medium begins along the ray: uFogNear, the linear mode's slot, which this mode\n // never read before Atmosphere.fogStart. The ray is integrated from there on, at the density\n // of that point; at a start of 0 every term is the one it always was. See render/fog.ts.\n float start = min(uFogNear, dist);\n float before = dist > 0.0 ? start / dist : 0.0;\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n float rest = t * (1.0 - before);\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(rest) < 1e-4 ? 1e-4 : rest;\n float air =\n 1.0 - exp(-uFogDensity * exp(-t * before) * (dist - start) * (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 int uNoiseOctaves;\nuniform vec4 uClipPlane;\nuniform int uClipEnabled;\n/** The caller's colour, so an aura shifts with the world's own palette. */\nuniform vec3 uTint;\n\nout vec4 outColor;\n\nfloat hash21(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\nfloat valueNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash21(i), hash21(i + vec2(1.0, 0.0)), u.x),\n mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0, 1.0)), u.x),\n u.y\n );\n}\n\nvoid main() {\n if (uClipEnabled != 0 && dot(vec4(vWorldPos, 1.0), uClipPlane) < 0.0) discard;\n\n // Polar about the quad centre. This is the line that makes it an orb rather\n // than a column; everything downstream reads radius and angle, never height.\n float radius = length(vUv - 0.5) * 2.0;\n if (radius >= 1.0) discard;\n\n vec2 d = vUv - 0.5;\n float angle = atan(d.y, d.x);\n float t = uTime + vSeed * 37.0;\n\n /*\n * Two bands, turning opposite ways at different rates. Sampled in (angle, radius)\n * space so the noise wraps around the disc instead of sliding across it \u2014 noise in\n * UV space would drift off one edge and read as a texture being scrolled.\n */\n float inner = valueNoise(vec2(angle * 1.9 + t * 0.55, radius * 4.2));\n float outer = valueNoise(vec2(angle * 3.1 - t * 0.34, radius * 2.6));\n float swirl = inner * 0.62 + outer * 0.38;\n if (uNoiseOctaves > 1) {\n swirl = mix(swirl, swirl * valueNoise(vec2(angle * 6.3 + t * 0.9, radius * 8.0)) * 2.0, 0.35);\n }\n\n /*\n * Core and corona as two separate falloffs rather than one curve. A single falloff\n * gives a blob; what makes this read as *contained energy* is a small bright centre\n * with a much wider, much fainter halo, so the object inside stays legible.\n */\n float core = 1.0 - smoothstep(0.0, 0.28, radius);\n float corona = (1.0 - smoothstep(0.18, 1.0, radius)) * (0.35 + swirl * 0.65);\n float body = core * 0.9 + corona;\n if (body <= 0.02) discard;\n\n // Hot centre through the tint to a dark rim, so the core reads as white-hot\n // energy and the edge as the colour it is made of.\n vec3 col = mix(uTint * 0.35, uTint, smoothstep(0.0, 0.55, body));\n col = mix(col, vec3(1.0), smoothstep(0.82, 1.4, body));\n col *= uTint;\n\n float alpha = clamp(body * 1.6, 0.0, 1.0);\n // Additive light must lose alpha as it scatters, or mixing its colour alone\n // still makes a distant aura glow through fog and through water.\n float fog = mediumFog(vDistance, vWorldPos.y);\n col = mix(col, mediumColor(), fog);\n alpha *= 1.0 - fog;\n\n outColor = vec4(col, alpha);\n}\n";
|
|
@@ -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(
|
|
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 // Where the medium begins along the ray: uFogNear, the linear mode's slot, which this mode\n // never read before Atmosphere.fogStart. The ray is integrated from there on, at the density\n // of that point; at a start of 0 every term is the one it always was. See render/fog.ts.\n float start = min(uFogNear, dist);\n float before = dist > 0.0 ? start / dist : 0.0;\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n float rest = t * (1.0 - before);\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(rest) < 1e-4 ? 1e-4 : rest;\n float air =\n 1.0 - exp(-uFogDensity * exp(-t * before) * (dist - start) * (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";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vertex-stage bone animation: a crowd instance's vertex turned and placed by its bone, at its
|
|
3
|
+
* instance's moment of the clip — the GLSL of `boneAnimation.ts`'s `animateBoneVertex`, line for
|
|
4
|
+
* line. Shared by the lit stage and the depth stage, so a crowd's shadow moves with it.
|
|
5
|
+
*
|
|
6
|
+
* **Three textures, read by index and never filtered**: the places and the turns, a texel a bone a
|
|
7
|
+
* frame (bones across, frames down, sizes read with `textureSize` so no uniform says them), and the
|
|
8
|
+
* batch's clocks, two floats an instance wrapped onto rows, read at `gl_InstanceID`. The clip's speed
|
|
9
|
+
* and its bone scale are the one uniform, `uBoneClip`; the scene's time is the pass's `uSceneTime`.
|
|
10
|
+
*
|
|
11
|
+
* **The bone is read from the grain lane**, which is where a mesh's second coordinates ride, stored
|
|
12
|
+
* as `-1 - u` (`lightmap.ts`). An instanced batch reads no grain of its own from that lane: a crowd's
|
|
13
|
+
* mesh carries second coordinates, which is what makes it one.
|
|
14
|
+
*
|
|
15
|
+
* Integer arithmetic for the lattice, by the 2026-09-20 rule, except where a float is clamped onto
|
|
16
|
+
* it: the first frame is `min(floor(wrapped), frames - 1)`, so the wrap that division-by-reciprocal
|
|
17
|
+
* lands on `frames` is the last frame fully blended into the first — the first, which is right.
|
|
18
|
+
*/
|
|
19
|
+
export declare const BONE_ANIMATION_GLSL = "\nuniform highp sampler2D uBonePlaces;\nuniform highp sampler2D uBoneTurns;\nuniform highp sampler2D uInstanceClocks;\n/** x the clip's frames a second, y how many bones one unit of the second coordinates' u counts. */\nuniform vec2 uBoneClip;\n\n/** v turned by the unit quaternion q. */\nvec3 boneTurn(vec4 q, vec3 v) {\n return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);\n}\n\n/**\n * Where the clip puts this vertex, from its place at rest; its normal and tangent turned with it.\n */\nvec3 boneAnimate(vec3 position, inout vec3 normal, inout vec3 tangent) {\n ivec2 size = textureSize(uBonePlaces, 0);\n int clocksWidth = textureSize(uInstanceClocks, 0).x;\n vec2 clock = texelFetch(\n uInstanceClocks,\n ivec2(gl_InstanceID % clocksWidth, gl_InstanceID / clocksWidth),\n 0\n ).xy;\n float moment = (uSceneTime * clock.y + clock.x) * uBoneClip.x;\n float frames = float(size.y);\n float wrapped = moment - frames * floor(moment / frames);\n int first = min(int(floor(wrapped)), size.y - 1);\n int second = first + 1 == size.y ? 0 : first + 1;\n float blend = clamp(wrapped - float(first), 0.0, 1.0);\n int bone = clamp(int(floor((-1.0 - aGrain) * uBoneClip.y + 0.5)), 0, size.x - 1);\n vec4 q0 = texelFetch(uBoneTurns, ivec2(bone, first), 0);\n vec4 q1 = texelFetch(uBoneTurns, ivec2(bone, second), 0);\n /* The shorter arc: a quaternion and its negation are one turn. */\n if (dot(q0, q1) < 0.0) q1 = -q1;\n vec4 q = normalize(mix(q0, q1, blend));\n vec3 place = mix(\n texelFetch(uBonePlaces, ivec2(bone, first), 0).xyz,\n texelFetch(uBonePlaces, ivec2(bone, second), 0).xyz,\n blend\n );\n normal = boneTurn(q, normal);\n tangent = boneTurn(q, tangent);\n return boneTurn(q, position) + place;\n}\n";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vertex-stage bone animation: a crowd instance's vertex turned and placed by its bone, at its
|
|
3
|
+
* instance's moment of the clip — the GLSL of `boneAnimation.ts`'s `animateBoneVertex`, line for
|
|
4
|
+
* line. Shared by the lit stage and the depth stage, so a crowd's shadow moves with it.
|
|
5
|
+
*
|
|
6
|
+
* **Three textures, read by index and never filtered**: the places and the turns, a texel a bone a
|
|
7
|
+
* frame (bones across, frames down, sizes read with `textureSize` so no uniform says them), and the
|
|
8
|
+
* batch's clocks, two floats an instance wrapped onto rows, read at `gl_InstanceID`. The clip's speed
|
|
9
|
+
* and its bone scale are the one uniform, `uBoneClip`; the scene's time is the pass's `uSceneTime`.
|
|
10
|
+
*
|
|
11
|
+
* **The bone is read from the grain lane**, which is where a mesh's second coordinates ride, stored
|
|
12
|
+
* as `-1 - u` (`lightmap.ts`). An instanced batch reads no grain of its own from that lane: a crowd's
|
|
13
|
+
* mesh carries second coordinates, which is what makes it one.
|
|
14
|
+
*
|
|
15
|
+
* Integer arithmetic for the lattice, by the 2026-09-20 rule, except where a float is clamped onto
|
|
16
|
+
* it: the first frame is `min(floor(wrapped), frames - 1)`, so the wrap that division-by-reciprocal
|
|
17
|
+
* lands on `frames` is the last frame fully blended into the first — the first, which is right.
|
|
18
|
+
*/
|
|
19
|
+
export const BONE_ANIMATION_GLSL = `
|
|
20
|
+
uniform highp sampler2D uBonePlaces;
|
|
21
|
+
uniform highp sampler2D uBoneTurns;
|
|
22
|
+
uniform highp sampler2D uInstanceClocks;
|
|
23
|
+
/** x the clip's frames a second, y how many bones one unit of the second coordinates' u counts. */
|
|
24
|
+
uniform vec2 uBoneClip;
|
|
25
|
+
|
|
26
|
+
/** v turned by the unit quaternion q. */
|
|
27
|
+
vec3 boneTurn(vec4 q, vec3 v) {
|
|
28
|
+
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Where the clip puts this vertex, from its place at rest; its normal and tangent turned with it.
|
|
33
|
+
*/
|
|
34
|
+
vec3 boneAnimate(vec3 position, inout vec3 normal, inout vec3 tangent) {
|
|
35
|
+
ivec2 size = textureSize(uBonePlaces, 0);
|
|
36
|
+
int clocksWidth = textureSize(uInstanceClocks, 0).x;
|
|
37
|
+
vec2 clock = texelFetch(
|
|
38
|
+
uInstanceClocks,
|
|
39
|
+
ivec2(gl_InstanceID % clocksWidth, gl_InstanceID / clocksWidth),
|
|
40
|
+
0
|
|
41
|
+
).xy;
|
|
42
|
+
float moment = (uSceneTime * clock.y + clock.x) * uBoneClip.x;
|
|
43
|
+
float frames = float(size.y);
|
|
44
|
+
float wrapped = moment - frames * floor(moment / frames);
|
|
45
|
+
int first = min(int(floor(wrapped)), size.y - 1);
|
|
46
|
+
int second = first + 1 == size.y ? 0 : first + 1;
|
|
47
|
+
float blend = clamp(wrapped - float(first), 0.0, 1.0);
|
|
48
|
+
int bone = clamp(int(floor((-1.0 - aGrain) * uBoneClip.y + 0.5)), 0, size.x - 1);
|
|
49
|
+
vec4 q0 = texelFetch(uBoneTurns, ivec2(bone, first), 0);
|
|
50
|
+
vec4 q1 = texelFetch(uBoneTurns, ivec2(bone, second), 0);
|
|
51
|
+
/* The shorter arc: a quaternion and its negation are one turn. */
|
|
52
|
+
if (dot(q0, q1) < 0.0) q1 = -q1;
|
|
53
|
+
vec4 q = normalize(mix(q0, q1, blend));
|
|
54
|
+
vec3 place = mix(
|
|
55
|
+
texelFetch(uBonePlaces, ivec2(bone, first), 0).xyz,
|
|
56
|
+
texelFetch(uBonePlaces, ivec2(bone, second), 0).xyz,
|
|
57
|
+
blend
|
|
58
|
+
);
|
|
59
|
+
normal = boneTurn(q, normal);
|
|
60
|
+
tangent = boneTurn(q, tangent);
|
|
61
|
+
return boneTurn(q, position) + place;
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
@@ -35,4 +35,4 @@
|
|
|
35
35
|
* ordinary one, and it wants the sheet laid just clear of the tiles.
|
|
36
36
|
*/
|
|
37
37
|
export declare const CAUSTICS_VERT = "#version 300 es\nlayout(location = 0) in vec3 aRest;\nlayout(location = 1) in vec2 aLocal;\n/** x: resting height of the water below. y: spare. */\nlayout(location = 2) in vec2 aParams;\n\nuniform mat4 uViewProj;\n\nout vec3 vWorldPos;\nout vec2 vLocal;\nout vec2 vParams;\n\nvoid main() {\n vWorldPos = aRest;\n vLocal = aLocal;\n vParams = aParams;\n gl_Position = uViewProj * vec4(aRest, 1.0);\n}\n";
|
|
38
|
-
export declare const CAUSTICS_FRAG = "#version 300 es\nprecision highp float;\n\nin vec3 vWorldPos;\nin vec2 vLocal;\nin vec2 vParams;\n\nuniform vec3 uCameraPos;\nuniform vec2 uWindDir;\nuniform float uWaveGain;\nuniform float uTime;\n/** Toward the dominant source, as everywhere else in the engine. */\nuniform vec3 uLightDir;\n/** Colour of the light the water is reflecting. */\nuniform vec3 uTint;\nuniform float uStrength;\n/** How far from the water, on either side, the effect has died out entirely. */\nuniform float uMaxDrop;\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\n\nout vec4 outColor;\n\n\nconst int GERSTNER_COUNT = 4;\n/*\n * direction.xy, steepness, wavelength. Amplitude is steepness/k, so these sum to\n * roughly a 1m swell \u2014 enough to read as a living surface, low enough that crests\n * never wash over an island whose deck sits ~1.8m above sea level.\n */\nconst vec4 GERSTNER_WAVES[4] = vec4[4](\n vec4( 1.0, 0.0, 0.115, 34.0),\n vec4( 0.6, 0.8, 0.100, 18.0),\n vec4(-0.7, 0.7, 0.080, 9.0),\n vec4( 0.2, -0.98, 0.060, 5.0)\n);\n\n/*\n * Steer each wave toward the wind, but only partway. A real sea is a dominant\n * swell plus cross-chop; rotating all four onto one bearing gives corduroy,\n * which reads as a material rather than as water.\n */\nvec2 gerstnerDirection(int i, vec2 windDir) {\n return normalize(mix(normalize(GERSTNER_WAVES[i].xy), windDir, 0.62));\n}\n\nfloat gerstnerWavenumber(int i) {\n return 6.28318530718 / GERSTNER_WAVES[i].w;\n}\n\n/** Deep-water phase in radians at a point on the resting plane. */\nfloat gerstnerPhase(float k, vec2 dir, vec2 p, float time) {\n float c = sqrt(9.81 / k); // deep-water phase speed\n return k * (dot(dir, p) - c * time);\n}\n\nstruct GerstnerSurface {\n /** Displacement from the resting plane \u2014 horizontal as well as vertical. */\n vec3 offset;\n vec3 normal;\n /** Height in units of steepness; positive near a crest. */\n float crest;\n};\n\nGerstnerSurface gerstnerSurface(vec2 p, float time, vec2 windDir, float gain) {\n GerstnerSurface s;\n s.offset = vec3(0.0);\n s.normal = vec3(0.0, 1.0, 0.0);\n s.crest = 0.0;\n for (int i = 0; i < GERSTNER_COUNT; i++) {\n vec2 dir = gerstnerDirection(i, windDir);\n float steepness = GERSTNER_WAVES[i].z * gain;\n float k = gerstnerWavenumber(i);\n float f = gerstnerPhase(k, dir, p, time);\n float a = steepness / k; // amplitude from steepness\n\n s.offset.x += dir.x * a * cos(f);\n s.offset.y += a * sin(f);\n s.offset.z += dir.y * a * cos(f);\n\n // Analytic normal: the partial derivatives of the displacement above.\n s.normal.x -= dir.x * steepness * cos(f);\n s.normal.z -= dir.y * steepness * cos(f);\n s.normal.y -= steepness * sin(f);\n\n s.crest += sin(f) * steepness;\n }\n return s;\n}\n\n/**\n * Second derivatives of the height field: (d2h/dx2, d2h/dz2, d2h/dxdz).\n *\n * How the surface *bends*, which is what decides where reflected light\n * converges. Analytic from the same sum, so it cannot disagree with the shape\n * the vertex stage displaced.\n */\nvec3 gerstnerCurvature(vec2 p, float time, vec2 windDir, float gain) {\n vec3 h = vec3(0.0);\n for (int i = 0; i < GERSTNER_COUNT; i++) {\n vec2 dir = gerstnerDirection(i, windDir);\n float steepness = GERSTNER_WAVES[i].z * gain;\n float k = gerstnerWavenumber(i);\n float f = gerstnerPhase(k, dir, p, time);\n // Second derivative of (steepness/k)*sin(f) along the wave's own bearing.\n float second = -steepness * k * sin(f);\n h.x += second * dir.x * dir.x;\n h.y += second * dir.y * dir.y;\n h.z += second * dir.x * dir.y;\n }\n return h;\n}\n\n\n/**\n * Shallowest the source may be and still be treated as a source.\n *\n * The slant path goes as 1/sin(elevation), so a light on the horizon would send\n * the sampled water position kilometres away and the pattern would smear into\n * noise. Clamping keeps a sunset dramatic instead of broken.\n */\nconst float MIN_ELEVATION = 0.16;\n\nvoid main() {\n float waterY = vParams.x;\n float drop = vWorldPos.y - waterY;\n\n /*\n * Everything is masked rather than branched on, and fwidth() below is why:\n * screen-space derivatives are undefined in non-uniform control flow, so a\n * discard above them would make the antialiasing of the caustic lines a\n * driver-dependent guess. Nothing is saved by branching either \u2014 a masked\n * fragment adds zero under additive blending.\n *\n * Either side of the surface is lit, and the distance from it is what matters\n * rather than the direction. This began as a soffit-only effect \u2014 light thrown\n * *up* onto the underside of a bridge \u2014 on the reasoning that below the water\n * there is nothing to light. That is only true when the water is a ceiling to\n * you. A pool floor, a flooded corridor, a tank with a lamp over it: the water\n * focuses light onto the bottom exactly as it does onto a soffit, and it is by\n * far the more common of the two.\n *\n * Only the surface's own thin band is excluded, where the sheet and the water\n * are coplanar and the pattern would be meaningless.\n *\n * Too far from it and the net has spread into nothing. And the sheet dissolves\n * at its own boundary, so a lit surface does not end in a lit rectangle.\n */\n float distanceFromWater = abs(drop);\n float clear = step(0.02, distanceFromWater);\n float height = 1.0 - smoothstep(uMaxDrop * 0.45, uMaxDrop, distanceFromWater);\n float rim = min(min(vLocal.x, 1.0 - vLocal.x), min(vLocal.y, 1.0 - vLocal.y));\n float edge = smoothstep(0.0, 0.12, rim);\n float reach = clear * height * edge;\n\n /*\n * Which water lights *this* point. For a flat surface the reflected ray leaves\n * along the mirrored light direction, so walking back from the ceiling gives\n * the water position and the path length in one step. This is why the net\n * slides along the ground as the sun moves rather than sitting under itself.\n */\n float elevation = max(uLightDir.y, MIN_ELEVATION);\n float slant = 1.0 / elevation;\n float d = drop * slant;\n vec2 water = vWorldPos.xz + uLightDir.xz * d;\n\n vec3 h = gerstnerCurvature(water, uTime, uWindDir, uWaveGain);\n float a = 1.0 - 2.0 * d * h.x;\n float b = 1.0 - 2.0 * d * h.y;\n float c = 2.0 * d * h.z;\n float det = a * b - c * c;\n\n /*\n * The bright net is where the determinant crosses zero, so its width is a\n * gradient question, not a constant. Widening the core by the screen-space\n * derivative antialiases it for free \u2014 without that, a line thinner than a\n * pixel strobes as the camera moves, which is the usual reason an effect like\n * this ends up looking like noise.\n */\n float soft = max(0.05, fwidth(det) * 1.6);\n float lines = soft / max(abs(det), soft);\n /*\n * Between the lines, the faint brightening of merely convergent water \u2014 and it has to\n * stay faint. At 0.45 it lit the *whole* sheet, and since the sheet hangs a few\n * centimetres under the soffit, the result read as a pale plate floating below the\n * bridge rather than as light on its underside \u2014 reported as being able to see through\n * the bridge ceiling, which looked glassy and transparent. The plate was the sheet.\n *\n * A tighter threshold and a fifth of the weight: the net is bright, everything between\n * it is almost nothing, and a soffit with no caustics on it is simply a soffit.\n */\n float sheen = clamp(1.0 / max(abs(det), 0.18) - 0.85, 0.0, 1.0);\n float caustic = clamp(lines * 0.9 + sheen * 0.09, 0.0, 1.6);\n\n float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);\n\n // Additive: this is light arriving at a surface that is already shaded.\n outColor = vec4(uTint * (caustic * uStrength * reach * (1.0 - fog)), 1.0);\n}\n";
|
|
38
|
+
export declare const CAUSTICS_FRAG = "#version 300 es\nprecision highp float;\n\nin vec3 vWorldPos;\nin vec2 vLocal;\nin vec2 vParams;\n\nuniform vec3 uCameraPos;\nuniform vec2 uWindDir;\nuniform float uWaveGain;\nuniform float uTime;\n/** Toward the dominant source, as everywhere else in the engine. */\nuniform vec3 uLightDir;\n/** Colour of the light the water is reflecting. */\nuniform vec3 uTint;\nuniform float uStrength;\n/** How far from the water, on either side, the effect has died out entirely. */\nuniform float uMaxDrop;\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 // Where the medium begins along the ray: uFogNear, the linear mode's slot, which this mode\n // never read before Atmosphere.fogStart. The ray is integrated from there on, at the density\n // of that point; at a start of 0 every term is the one it always was. See render/fog.ts.\n float start = min(uFogNear, dist);\n float before = dist > 0.0 ? start / dist : 0.0;\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n float rest = t * (1.0 - before);\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(rest) < 1e-4 ? 1e-4 : rest;\n float air =\n 1.0 - exp(-uFogDensity * exp(-t * before) * (dist - start) * (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\n\nout vec4 outColor;\n\n\nconst int GERSTNER_COUNT = 4;\n/*\n * direction.xy, steepness, wavelength. Amplitude is steepness/k, so these sum to\n * roughly a 1m swell \u2014 enough to read as a living surface, low enough that crests\n * never wash over an island whose deck sits ~1.8m above sea level.\n */\nconst vec4 GERSTNER_WAVES[4] = vec4[4](\n vec4( 1.0, 0.0, 0.115, 34.0),\n vec4( 0.6, 0.8, 0.100, 18.0),\n vec4(-0.7, 0.7, 0.080, 9.0),\n vec4( 0.2, -0.98, 0.060, 5.0)\n);\n\n/*\n * Steer each wave toward the wind, but only partway. A real sea is a dominant\n * swell plus cross-chop; rotating all four onto one bearing gives corduroy,\n * which reads as a material rather than as water.\n */\nvec2 gerstnerDirection(int i, vec2 windDir) {\n return normalize(mix(normalize(GERSTNER_WAVES[i].xy), windDir, 0.62));\n}\n\nfloat gerstnerWavenumber(int i) {\n return 6.28318530718 / GERSTNER_WAVES[i].w;\n}\n\n/** Deep-water phase in radians at a point on the resting plane. */\nfloat gerstnerPhase(float k, vec2 dir, vec2 p, float time) {\n float c = sqrt(9.81 / k); // deep-water phase speed\n return k * (dot(dir, p) - c * time);\n}\n\nstruct GerstnerSurface {\n /** Displacement from the resting plane \u2014 horizontal as well as vertical. */\n vec3 offset;\n vec3 normal;\n /** Height in units of steepness; positive near a crest. */\n float crest;\n};\n\nGerstnerSurface gerstnerSurface(vec2 p, float time, vec2 windDir, float gain) {\n GerstnerSurface s;\n s.offset = vec3(0.0);\n s.normal = vec3(0.0, 1.0, 0.0);\n s.crest = 0.0;\n for (int i = 0; i < GERSTNER_COUNT; i++) {\n vec2 dir = gerstnerDirection(i, windDir);\n float steepness = GERSTNER_WAVES[i].z * gain;\n float k = gerstnerWavenumber(i);\n float f = gerstnerPhase(k, dir, p, time);\n float a = steepness / k; // amplitude from steepness\n\n s.offset.x += dir.x * a * cos(f);\n s.offset.y += a * sin(f);\n s.offset.z += dir.y * a * cos(f);\n\n // Analytic normal: the partial derivatives of the displacement above.\n s.normal.x -= dir.x * steepness * cos(f);\n s.normal.z -= dir.y * steepness * cos(f);\n s.normal.y -= steepness * sin(f);\n\n s.crest += sin(f) * steepness;\n }\n return s;\n}\n\n/**\n * Second derivatives of the height field: (d2h/dx2, d2h/dz2, d2h/dxdz).\n *\n * How the surface *bends*, which is what decides where reflected light\n * converges. Analytic from the same sum, so it cannot disagree with the shape\n * the vertex stage displaced.\n */\nvec3 gerstnerCurvature(vec2 p, float time, vec2 windDir, float gain) {\n vec3 h = vec3(0.0);\n for (int i = 0; i < GERSTNER_COUNT; i++) {\n vec2 dir = gerstnerDirection(i, windDir);\n float steepness = GERSTNER_WAVES[i].z * gain;\n float k = gerstnerWavenumber(i);\n float f = gerstnerPhase(k, dir, p, time);\n // Second derivative of (steepness/k)*sin(f) along the wave's own bearing.\n float second = -steepness * k * sin(f);\n h.x += second * dir.x * dir.x;\n h.y += second * dir.y * dir.y;\n h.z += second * dir.x * dir.y;\n }\n return h;\n}\n\n\n/**\n * Shallowest the source may be and still be treated as a source.\n *\n * The slant path goes as 1/sin(elevation), so a light on the horizon would send\n * the sampled water position kilometres away and the pattern would smear into\n * noise. Clamping keeps a sunset dramatic instead of broken.\n */\nconst float MIN_ELEVATION = 0.16;\n\nvoid main() {\n float waterY = vParams.x;\n float drop = vWorldPos.y - waterY;\n\n /*\n * Everything is masked rather than branched on, and fwidth() below is why:\n * screen-space derivatives are undefined in non-uniform control flow, so a\n * discard above them would make the antialiasing of the caustic lines a\n * driver-dependent guess. Nothing is saved by branching either \u2014 a masked\n * fragment adds zero under additive blending.\n *\n * Either side of the surface is lit, and the distance from it is what matters\n * rather than the direction. This began as a soffit-only effect \u2014 light thrown\n * *up* onto the underside of a bridge \u2014 on the reasoning that below the water\n * there is nothing to light. That is only true when the water is a ceiling to\n * you. A pool floor, a flooded corridor, a tank with a lamp over it: the water\n * focuses light onto the bottom exactly as it does onto a soffit, and it is by\n * far the more common of the two.\n *\n * Only the surface's own thin band is excluded, where the sheet and the water\n * are coplanar and the pattern would be meaningless.\n *\n * Too far from it and the net has spread into nothing. And the sheet dissolves\n * at its own boundary, so a lit surface does not end in a lit rectangle.\n */\n float distanceFromWater = abs(drop);\n float clear = step(0.02, distanceFromWater);\n float height = 1.0 - smoothstep(uMaxDrop * 0.45, uMaxDrop, distanceFromWater);\n float rim = min(min(vLocal.x, 1.0 - vLocal.x), min(vLocal.y, 1.0 - vLocal.y));\n float edge = smoothstep(0.0, 0.12, rim);\n float reach = clear * height * edge;\n\n /*\n * Which water lights *this* point. For a flat surface the reflected ray leaves\n * along the mirrored light direction, so walking back from the ceiling gives\n * the water position and the path length in one step. This is why the net\n * slides along the ground as the sun moves rather than sitting under itself.\n */\n float elevation = max(uLightDir.y, MIN_ELEVATION);\n float slant = 1.0 / elevation;\n float d = drop * slant;\n vec2 water = vWorldPos.xz + uLightDir.xz * d;\n\n vec3 h = gerstnerCurvature(water, uTime, uWindDir, uWaveGain);\n float a = 1.0 - 2.0 * d * h.x;\n float b = 1.0 - 2.0 * d * h.y;\n float c = 2.0 * d * h.z;\n float det = a * b - c * c;\n\n /*\n * The bright net is where the determinant crosses zero, so its width is a\n * gradient question, not a constant. Widening the core by the screen-space\n * derivative antialiases it for free \u2014 without that, a line thinner than a\n * pixel strobes as the camera moves, which is the usual reason an effect like\n * this ends up looking like noise.\n */\n float soft = max(0.05, fwidth(det) * 1.6);\n float lines = soft / max(abs(det), soft);\n /*\n * Between the lines, the faint brightening of merely convergent water \u2014 and it has to\n * stay faint. At 0.45 it lit the *whole* sheet, and since the sheet hangs a few\n * centimetres under the soffit, the result read as a pale plate floating below the\n * bridge rather than as light on its underside \u2014 reported as being able to see through\n * the bridge ceiling, which looked glassy and transparent. The plate was the sheet.\n *\n * A tighter threshold and a fifth of the weight: the net is bright, everything between\n * it is almost nothing, and a soffit with no caustics on it is simply a soffit.\n */\n float sheen = clamp(1.0 / max(abs(det), 0.18) - 0.85, 0.0, 1.0);\n float caustic = clamp(lines * 0.9 + sheen * 0.09, 0.0, 1.6);\n\n float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);\n\n // Additive: this is light arriving at a surface that is already shaded.\n outColor = vec4(uTint * (caustic * uStrength * reach * (1.0 - fog)), 1.0);\n}\n";
|
|
@@ -23,6 +23,13 @@ export declare const DEPTH_INSTANCED_VERT: string;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare const DEPTH_CUTOUT_VERT: string;
|
|
25
25
|
export declare const DEPTH_INSTANCED_CUTOUT_VERT: string;
|
|
26
|
+
/**
|
|
27
|
+
* The variants a crowd casts through: an instanced batch playing a bone animation, its vertices
|
|
28
|
+
* where the clip puts them at each instance's moment. See `shaders/boneAnimation.ts`. No glass
|
|
29
|
+
* variant: a crowd is not glass, and a glass batch that animates casts its tint from rest.
|
|
30
|
+
*/
|
|
31
|
+
export declare const DEPTH_INSTANCED_ANIMATED_VERT: string;
|
|
32
|
+
export declare const DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT: string;
|
|
26
33
|
export declare const DEPTH_FRAG = "#version 300 es\nprecision highp float;\nin vec4 vLightPosition;\n\nuniform highp sampler2DArray uPreviousShadowMap;\nuniform int uPeelShadowLayer;\n\nvoid main() {\n if (uPeelShadowLayer != 0) {\n vec3 p = vLightPosition.xyz / vLightPosition.w;\n vec2 uv = p.xy * 0.5 + 0.5;\n float previousDepth = texture(uPreviousShadowMap, vec3(uv, 0.0)).r;\n // Discard the already-recorded surface and everything in front of it. The\n // ordinary depth test then stores the next independently fading occluder.\n if (gl_FragCoord.z <= previousDepth + 0.00001) discard;\n }\n}\n";
|
|
27
34
|
/**
|
|
28
35
|
* The cutout caster's depth: the plain pass, and a texel of alpha under the cutoff writes nothing.
|