@driftengine/core 4.8.6 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +132 -0
- package/README.md +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -1
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +35 -0
- package/dist/render/areaLights.js +35 -0
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +41 -3
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/webgl2/renderer.d.ts +140 -16
- package/dist/render/backend/webgl2/renderer.js +436 -44
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
- package/dist/render/backend/webgpu/flatPass.js +58 -26
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +166 -3
- package/dist/render/backend/webgpu/renderer.js +738 -83
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/clusteredLights.d.ts +23 -5
- package/dist/render/clusteredLights.js +35 -8
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +5 -0
- package/dist/render/instances.d.ts +19 -5
- package/dist/render/instances.js +9 -3
- package/dist/render/lightBudget.d.ts +12 -1
- package/dist/render/lightBudget.js +23 -3
- package/dist/render/lightmap.d.ts +23 -9
- package/dist/render/lightmap.js +40 -18
- package/dist/render/particlePool.d.ts +21 -0
- package/dist/render/particlePool.js +7 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +47 -15
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +32 -1
- package/dist/render/screenSpaceReflection.js +14 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +1 -1
- package/dist/render/shaders/flat/areaLight.js +46 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +5 -1
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +22 -2
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +85 -22
- package/dist/render/shaders/flat/models.d.ts +5 -2
- package/dist/render/shaders/flat/models.js +100 -16
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +1 -1
- package/dist/render/shaders/flat/preamble.js +10 -3
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
- package/dist/render/shaders/generated/flat.wgsl.js +230 -54
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +46 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +107 -1
- package/dist/render/surfaceModel.d.ts +29 -8
- package/dist/render/surfaceModel.js +33 -9
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +44 -1
- package/dist/render/surfaceTexture.js +96 -5
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +22 -2
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +58 -0
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +58 -2
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/webgl2/renderer.ts +548 -50
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/flatPass.ts +63 -26
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +927 -100
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
- package/src/render/clusteredLights.ts +41 -8
- package/src/render/displayRange.ts +61 -0
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +4 -0
- package/src/render/instances.ts +26 -6
- package/src/render/lightBudget.ts +40 -2
- package/src/render/lightmap.ts +64 -23
- package/src/render/particlePool.ts +23 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +52 -15
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +38 -1
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +46 -0
- package/src/render/shaders/flat/glassTint.ts +13 -2
- package/src/render/shaders/flat/index.ts +37 -2
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +85 -22
- package/src/render/shaders/flat/models.ts +100 -16
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +1 -1
- package/src/render/shaders/flat/preamble.ts +10 -3
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +230 -54
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +46 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +139 -1
- package/src/render/surfaceModel.ts +47 -10
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +136 -9
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
|
|
18
|
+
export class ReflectionSurfaceTargets {
|
|
19
|
+
private probe: WebGLTexture | null = null;
|
|
20
|
+
private tint: WebGLTexture | null = null;
|
|
21
|
+
private width = 0;
|
|
22
|
+
private height = 0;
|
|
23
|
+
private readonly all: number[];
|
|
24
|
+
private readonly colourAlone: number[];
|
|
25
|
+
|
|
26
|
+
constructor(private readonly gl: WebGL2RenderingContext) {
|
|
27
|
+
this.all = [gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2];
|
|
28
|
+
this.colourAlone = [gl.COLOR_ATTACHMENT0];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The environment as the frame shows it, and the roughness in alpha. */
|
|
32
|
+
get probeTexture(): WebGLTexture | null {
|
|
33
|
+
return this.probe;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** What a found reflection is multiplied by. */
|
|
37
|
+
get tintTexture(): WebGLTexture | null {
|
|
38
|
+
return this.tint;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* At the frame's start, with the scene's framebuffer bound: both at its size, attached, cleared to
|
|
43
|
+
* nothing reflected, and left disabled.
|
|
44
|
+
*/
|
|
45
|
+
begin(width: number, height: number): void {
|
|
46
|
+
const { gl } = this;
|
|
47
|
+
if (this.probe === null || this.width !== width || this.height !== height) {
|
|
48
|
+
this.release();
|
|
49
|
+
this.probe = this.make(width, height);
|
|
50
|
+
this.tint = this.make(width, height);
|
|
51
|
+
this.width = width;
|
|
52
|
+
this.height = height;
|
|
53
|
+
}
|
|
54
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.probe, 0);
|
|
55
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, this.tint, 0);
|
|
56
|
+
gl.drawBuffers(this.all);
|
|
57
|
+
gl.clearBufferfv(gl.COLOR, 1, ZERO);
|
|
58
|
+
gl.clearBufferfv(gl.COLOR, 2, ZERO);
|
|
59
|
+
gl.drawBuffers(this.colourAlone);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Around an opaque lit draw into the frame: its two extra outputs land. */
|
|
63
|
+
on(): void {
|
|
64
|
+
this.gl.drawBuffers(this.all);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** And after it: only the colour does. */
|
|
68
|
+
off(): void {
|
|
69
|
+
this.gl.drawBuffers(this.colourAlone);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** With the scene's framebuffer bound, after the trace has read them: see the header. */
|
|
73
|
+
detach(): void {
|
|
74
|
+
const { gl } = this;
|
|
75
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, null, 0);
|
|
76
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, null, 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
dispose(): void {
|
|
80
|
+
this.release();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private make(width: number, height: number): WebGLTexture {
|
|
84
|
+
const { gl } = this;
|
|
85
|
+
const texture = gl.createTexture();
|
|
86
|
+
if (texture === null) throw new Error('ReflectionSurfaceTargets: createTexture failed');
|
|
87
|
+
/* Unit 0, chosen rather than inherited, for the reason `SsrPass` gives. */
|
|
88
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
89
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
90
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA16F, width, height);
|
|
91
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
92
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
93
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
94
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
95
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
96
|
+
return texture;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private release(): void {
|
|
100
|
+
if (this.probe !== null) this.gl.deleteTexture(this.probe);
|
|
101
|
+
if (this.tint !== null) this.gl.deleteTexture(this.tint);
|
|
102
|
+
this.probe = null;
|
|
103
|
+
this.tint = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Construction-time GPU quality controls shared by every renderer resource. */
|
|
2
2
|
|
|
3
|
+
import type { FrameReflections } from './frameReflections.ts';
|
|
3
4
|
import { MAX_AREA_LIGHTS } from './areaLights.ts';
|
|
4
5
|
import { MAX_GLOBAL_MEDIUM_STEPS } from './globalMedium.ts';
|
|
5
6
|
import { MAX_POINT_LIGHTS } from './lightBudget.ts';
|
|
@@ -26,8 +27,14 @@ import { PREFILTER_SAMPLE_COUNTS } from './prefilterEnvMap.ts';
|
|
|
26
27
|
* red where `srgb` clips each channel and turns its core pink and wide. It is the curve a ported
|
|
27
28
|
* game's own resolve applied — for a port that has to match one — and it has none of ACES's toe or
|
|
28
29
|
* desaturation. See `HIGHLIGHT_SHOULDER_GLSL` for the arithmetic.
|
|
30
|
+
*
|
|
31
|
+
* `filmic` is a film curve shaped by five numbers — a slope, a toe, a shoulder and how far black
|
|
32
|
+
* and white clip — that holds a mid grey of 0.18 at 0.18 where `aces` gives about 0.11, so a look
|
|
33
|
+
* graded against such a curve is matched by its numbers (`setFilmicCurve`) rather than by a lookup
|
|
34
|
+
* table. Applied by the composite alone, so it needs `screenEffects` and `hdrScene`; without them
|
|
35
|
+
* every pass grades with `aces` and the renderer says so once. See `filmicCurve.ts`.
|
|
29
36
|
*/
|
|
30
|
-
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder';
|
|
37
|
+
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder' | 'filmic';
|
|
31
38
|
|
|
32
39
|
/**
|
|
33
40
|
* The three settings whose accepted values are a closed set, written as one.
|
|
@@ -219,13 +226,14 @@ export interface RenderQuality {
|
|
|
219
226
|
* **`'pre-integrated'`, the default**: the lit stage's fit alone — a terminator softened and
|
|
220
227
|
* reddened by the surface's curvature, light through thin parts, a penumbra each channel crosses
|
|
221
228
|
* as far as it travels — at no cost beyond the skin pipeline itself. **`'screen-space'`** adds
|
|
222
|
-
* Burley's blur in the picture as well: a skin draw writes its diffuse
|
|
223
|
-
* is drawn
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
+
* Burley's blur in the picture as well: a skin draw writes the light its diffuse is made of to a
|
|
230
|
+
* target of its own and its colour to another (it is drawn three times to do so), and the light is
|
|
231
|
+
* spread across the pixels its scatter distance covers, multiplied by the colour and added back
|
|
232
|
+
* before anything blended is drawn, so light crosses a shadow's edge and a nostril's rim in the
|
|
233
|
+
* picture rather than in a fit. See `skinBlur.ts`.
|
|
234
|
+
*
|
|
235
|
+
* Construction-time: three half-float targets the size of the frame, and two multisampled ones
|
|
236
|
+
* under multisampling, made at the first skin draw. It needs the composite (`screenEffects`), which is
|
|
229
237
|
* where the frame is still linear light to add to; without one, skin is pre-integrated and the
|
|
230
238
|
* renderer says so once. What would make the default wrong is a close-up face that reads as wax
|
|
231
239
|
* at a shadow's edge — that is what the blur is for.
|
|
@@ -354,6 +362,18 @@ export interface RenderQuality {
|
|
|
354
362
|
* eight-bit and this buys only the consistent grading.
|
|
355
363
|
*/
|
|
356
364
|
readonly hdrScene: boolean;
|
|
365
|
+
/**
|
|
366
|
+
* Ask for a high dynamic range display: a canvas whose highlights run past paper white to as
|
|
367
|
+
* far as the display allows, where it reports one. Off by default.
|
|
368
|
+
*
|
|
369
|
+
* **Asked for, not assumed.** It needs WebGPU, a display reporting a high dynamic range, and a
|
|
370
|
+
* composite that holds the light to give it (`screenEffects` and `hdrScene`); where any of those
|
|
371
|
+
* is missing the frame goes out as it always did and the renderer says which in
|
|
372
|
+
* `displayRangeReason`. `displayRange` is the answer a game reads to choose a look graded for the
|
|
373
|
+
* output it got. `setDisplayLuminance` sets how far above paper white the highlights may reach.
|
|
374
|
+
* WebGL2 has no extended-range canvas to draw into, so there it is always the standard range.
|
|
375
|
+
*/
|
|
376
|
+
readonly highDynamicRange: boolean;
|
|
357
377
|
/** Camera-depth water tint and scattering across every visible pass. */
|
|
358
378
|
readonly underwaterAtmosphere: boolean;
|
|
359
379
|
/** Procedural noise octaves evaluated by plume shaders. */
|
|
@@ -521,14 +541,14 @@ export interface RenderQuality {
|
|
|
521
541
|
*/
|
|
522
542
|
readonly sceneSamples: number;
|
|
523
543
|
/**
|
|
524
|
-
*
|
|
544
|
+
* Motion blur, 0 to 1. Off by default.
|
|
525
545
|
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
* needs only the previous view-projection,
|
|
531
|
-
*
|
|
546
|
+
* By the camera's motion everywhere, and **by each drawn object's own motion where a
|
|
547
|
+
* reconstruction draws one** (DriftTR, WebGPU): a mover's pixels smear along its path while a
|
|
548
|
+
* still stage stays sharp. Elsewhere a velocity buffer would be every mesh writing its motion into
|
|
549
|
+
* a second target — a structural change to a forward renderer — so those frames blur by the
|
|
550
|
+
* camera alone, which needs only the previous view-projection, and say so once when a draw names
|
|
551
|
+
* where it was. `setCameraMotionBlur` is the per-frame dial and the longest smear.
|
|
532
552
|
*
|
|
533
553
|
* Runs inside the composite pass, so it costs a depth sample and eight taps on the pixels
|
|
534
554
|
* that are moving and nothing on a still frame. Needs `screenEffects`, since without the
|
|
@@ -579,6 +599,18 @@ export interface RenderQuality {
|
|
|
579
599
|
* Needs `screenEffects`, since the resolve composites over the finished scene.
|
|
580
600
|
*/
|
|
581
601
|
readonly orderIndependent: boolean;
|
|
602
|
+
/**
|
|
603
|
+
* Every opaque lit surface reflects the frame by its own material: a ray marched against the
|
|
604
|
+
* frame's depth swaps the probes' share of each pixel for what it finds, at the pixel's own
|
|
605
|
+
* roughness and reflectance, and a surface past `maxRoughness` keeps the probes alone. Off by
|
|
606
|
+
* default; `true` for the defaults, or the numbers. See `frameReflections.ts`.
|
|
607
|
+
*
|
|
608
|
+
* Needs `screenEffects` and `hdrScene`, since the swap is made in scene light, and a frame of one
|
|
609
|
+
* sample; it says once where it cannot be drawn. What it costs on WebGPU is every opaque lit draw
|
|
610
|
+
* a second time without its lamps, into two half-float targets, and a march per reflective pixel;
|
|
611
|
+
* on WebGL2 the same two targets, written beside the colour by the opaque lit draws themselves.
|
|
612
|
+
*/
|
|
613
|
+
readonly screenSpaceReflections: boolean | FrameReflections;
|
|
582
614
|
/**
|
|
583
615
|
* Depth of field: how wide the blur may reach, as a fraction of the frame's height. Off by
|
|
584
616
|
* default.
|
|
@@ -963,9 +995,11 @@ export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
|
|
|
963
995
|
sceneSamples: 1,
|
|
964
996
|
planarReflections: false,
|
|
965
997
|
hdrScene: false,
|
|
998
|
+
highDynamicRange: false,
|
|
966
999
|
cameraMotionBlur: 0,
|
|
967
1000
|
temporalAa: false,
|
|
968
1001
|
orderIndependent: false,
|
|
1002
|
+
screenSpaceReflections: false,
|
|
969
1003
|
depthOfField: 0,
|
|
970
1004
|
reconstruction: 0,
|
|
971
1005
|
indirectLight: false,
|
|
@@ -1125,12 +1159,15 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
|
|
|
1125
1159
|
),
|
|
1126
1160
|
planarReflections: options.planarReflections ?? DEFAULT_RENDER_QUALITY.planarReflections,
|
|
1127
1161
|
hdrScene: options.hdrScene ?? DEFAULT_RENDER_QUALITY.hdrScene,
|
|
1162
|
+
highDynamicRange: options.highDynamicRange ?? DEFAULT_RENDER_QUALITY.highDynamicRange,
|
|
1128
1163
|
cameraMotionBlur: Math.min(
|
|
1129
1164
|
1,
|
|
1130
1165
|
Math.max(0, options.cameraMotionBlur ?? DEFAULT_RENDER_QUALITY.cameraMotionBlur),
|
|
1131
1166
|
),
|
|
1132
1167
|
temporalAa: options.temporalAa ?? DEFAULT_RENDER_QUALITY.temporalAa,
|
|
1133
1168
|
orderIndependent: options.orderIndependent ?? DEFAULT_RENDER_QUALITY.orderIndependent,
|
|
1169
|
+
screenSpaceReflections:
|
|
1170
|
+
options.screenSpaceReflections ?? DEFAULT_RENDER_QUALITY.screenSpaceReflections,
|
|
1134
1171
|
/* Off, or a ratio at least 1.3 and at most 2 — never a number between zero and the range. */
|
|
1135
1172
|
reconstruction: resolveReconstruction(options.reconstruction),
|
|
1136
1173
|
indirectLight: options.indirectLight ?? DEFAULT_RENDER_QUALITY.indirectLight,
|
|
@@ -119,6 +119,9 @@ export function recordingGl(
|
|
|
119
119
|
ONE: 1,
|
|
120
120
|
ONE_MINUS_SRC_ALPHA: 0x0303,
|
|
121
121
|
TRIANGLES: 0x0004,
|
|
122
|
+
/* The winding a pass names, so a test can tell which one it chose. */
|
|
123
|
+
CW: 0x0900,
|
|
124
|
+
CCW: 0x0901,
|
|
122
125
|
TEXTURE0: 0x84c0,
|
|
123
126
|
DEPTH_BUFFER_BIT: 0x00000100,
|
|
124
127
|
DEPTH_TEST: 0x0b71,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rgb9e5: three 9-bit mantissas over one shared 5-bit exponent, an unsigned float colour in four
|
|
3
|
+
* bytes. `EXT_texture_shared_exponent`'s encoding, the one a GPU's `rgb9e5ufloat` reads, written
|
|
4
|
+
* out so a lightmap page can hold its irradiance in four bytes a texel against a half float's eight.
|
|
5
|
+
*
|
|
6
|
+
* **What it gives up**: precision in the smaller channels, which share the largest one's exponent —
|
|
7
|
+
* a channel a thousandth of its neighbour keeps about one bit — and anything negative or past
|
|
8
|
+
* 65,408, which it clamps. Light is neither, and a bake's colours rarely span more than a few
|
|
9
|
+
* octaves within one texel, so a lightmap loses nothing visible; a value that needs each channel
|
|
10
|
+
* exact wants a format of its own.
|
|
11
|
+
*
|
|
12
|
+
* The bits, low to high: red in 0–8, green in 9–17, blue in 18–26, the exponent in 27–31, biased by
|
|
13
|
+
* 15, and a value is `mantissa * 2^(exponent - 15 - 9)`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const BIAS = 15;
|
|
17
|
+
const MANTISSA_BITS = 9;
|
|
18
|
+
const MANTISSA_VALUES = 1 << MANTISSA_BITS;
|
|
19
|
+
const MAX_EXPONENT = 31;
|
|
20
|
+
|
|
21
|
+
/** The largest value the format holds: 511/512 of 2^16. */
|
|
22
|
+
export const RGB9E5_MAX = ((MANTISSA_VALUES - 1) / MANTISSA_VALUES) * 2 ** (MAX_EXPONENT - BIAS);
|
|
23
|
+
|
|
24
|
+
const clampChannel = (v: number): number => (v > 0 ? Math.min(v, RGB9E5_MAX) : 0);
|
|
25
|
+
|
|
26
|
+
/** One colour packed into its 32 bits, rounding to nearest; NaN and negatives are zero. */
|
|
27
|
+
export function packRgb9e5(red: number, green: number, blue: number): number {
|
|
28
|
+
const r = clampChannel(red);
|
|
29
|
+
const g = clampChannel(green);
|
|
30
|
+
const b = clampChannel(blue);
|
|
31
|
+
const largest = Math.max(r, g, b);
|
|
32
|
+
let exponent = Math.max(-BIAS - 1, Math.floor(Math.log2(largest))) + 1 + BIAS;
|
|
33
|
+
let scale = 2 ** (exponent - BIAS - MANTISSA_BITS);
|
|
34
|
+
/* Rounding the largest channel up to 512 needs one more exponent, which the spec allows for. */
|
|
35
|
+
if (Math.floor(largest / scale + 0.5) === MANTISSA_VALUES) {
|
|
36
|
+
exponent += 1;
|
|
37
|
+
scale *= 2;
|
|
38
|
+
}
|
|
39
|
+
/* Inline rather than a helper: a page packs millions of texels, and a closure a call is one each. */
|
|
40
|
+
const mr = Math.min(MANTISSA_VALUES - 1, Math.floor(r / scale + 0.5));
|
|
41
|
+
const mg = Math.min(MANTISSA_VALUES - 1, Math.floor(g / scale + 0.5));
|
|
42
|
+
const mb = Math.min(MANTISSA_VALUES - 1, Math.floor(b / scale + 0.5));
|
|
43
|
+
return (mr | (mg << 9) | (mb << 18) | (exponent << 27)) >>> 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A packed colour's three channels, into `out[at…at+2]`. */
|
|
47
|
+
export function unpackRgb9e5(bits: number, out: { [index: number]: number }, at = 0): void {
|
|
48
|
+
const scale = 2 ** ((bits >>> 27) - BIAS - MANTISSA_BITS);
|
|
49
|
+
out[at] = (bits & 511) * scale;
|
|
50
|
+
out[at + 1] = ((bits >>> 9) & 511) * scale;
|
|
51
|
+
out[at + 2] = ((bits >>> 18) & 511) * scale;
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scene capture: a texture the world is drawn into from a camera of the caller's, which a material
|
|
3
|
+
* then shows like any image — a screen in the world showing what a camera elsewhere sees, a monitor,
|
|
4
|
+
* a security feed. The planar reflection's pass, for a caller's camera rather than a mirrored one.
|
|
5
|
+
*
|
|
6
|
+
* **It holds radiance, not display pixels**, as a probe does: drawn with the output transform held
|
|
7
|
+
* off, at the colour format a probe takes — half floats under `hdrScene`, bytes otherwise — and
|
|
8
|
+
* sampled linear, so a material showing it as its `emissive` emits the light the capture saw and the
|
|
9
|
+
* frame's grade then applies once, to the screen as to everything around it.
|
|
10
|
+
*
|
|
11
|
+
* **What it gives up**: it is the mesh pass alone, as a probe bake is — the sky, water, particles and
|
|
12
|
+
* anything else a scene draws after its world are in the capture only if the callback draws them;
|
|
13
|
+
* no temporal resolve, bloom or other effect after the world runs on it; and it has no mip chain, so
|
|
14
|
+
* a screen seen small and far shimmers where a mipmapped image would not.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** What a capture's texture is made from, in place of an image: its size and colour format. */
|
|
18
|
+
export interface SceneCaptureTexels {
|
|
19
|
+
readonly kind: 'scene-capture';
|
|
20
|
+
readonly width: number;
|
|
21
|
+
readonly height: number;
|
|
22
|
+
/** Half floats, which hold light past one; bytes otherwise. */
|
|
23
|
+
readonly float: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The largest side a capture may have, the size every WebGL2 device must take. */
|
|
27
|
+
export const MAX_SCENE_CAPTURE_SIZE = 4096;
|
|
28
|
+
|
|
29
|
+
/** A capture's description, refused by name where a side is not a whole number of pixels it can be. */
|
|
30
|
+
export function sceneCaptureTexels(
|
|
31
|
+
width: number,
|
|
32
|
+
height: number,
|
|
33
|
+
float: boolean,
|
|
34
|
+
): SceneCaptureTexels {
|
|
35
|
+
for (const [name, side] of [
|
|
36
|
+
['width', width],
|
|
37
|
+
['height', height],
|
|
38
|
+
] as const) {
|
|
39
|
+
if (!Number.isInteger(side) || side < 1 || side > MAX_SCENE_CAPTURE_SIZE) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`createSceneCapture: ${name} must be a whole number from 1 to ${MAX_SCENE_CAPTURE_SIZE}, ` +
|
|
42
|
+
`not ${String(side)}`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { kind: 'scene-capture', width, height, float };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Whether a texture source is a capture's description rather than an image. */
|
|
50
|
+
export function isSceneCaptureTexels(source: unknown): source is SceneCaptureTexels {
|
|
51
|
+
return (
|
|
52
|
+
typeof source === 'object' &&
|
|
53
|
+
source !== null &&
|
|
54
|
+
(source as { kind?: unknown }).kind === 'scene-capture'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { glSceneDepthFormat } from './depthConvention.ts';
|
|
2
2
|
import { clampGrain, clampVignette, grainSeed } from './shaders/filmLook.ts';
|
|
3
|
+
import { FRINGE_FLOATS, resolveFringe } from './shaders/fringe.ts';
|
|
3
4
|
import { AmbientOcclusionPass } from './ambientOcclusionPass.ts';
|
|
4
5
|
import { ExposurePass } from './exposurePass.ts';
|
|
5
6
|
import { clampAutoExposure } from './shaders/exposure.ts';
|
|
@@ -99,6 +100,8 @@ export class SceneTarget {
|
|
|
99
100
|
private gradeStrength = 0;
|
|
100
101
|
/** The lens and the print, held like the grade. See `filmLook.ts`. */
|
|
101
102
|
private vignette = 0;
|
|
103
|
+
/** The lens's colour fringe: red's and green's pull and where it begins. See `fringe.ts`. */
|
|
104
|
+
private readonly fringe = new Float32Array(FRINGE_FLOATS);
|
|
102
105
|
private grain = 0;
|
|
103
106
|
private grainSeed = 0;
|
|
104
107
|
/** Eye adaptation: how far, and this frame's time step. See `Renderer.setAutoExposure`. */
|
|
@@ -702,6 +705,11 @@ export class SceneTarget {
|
|
|
702
705
|
this.vignette = clampVignette(strength);
|
|
703
706
|
}
|
|
704
707
|
|
|
708
|
+
/** The lens's colour fringe, held until changed. See `Renderer.setChromaticAberration`. */
|
|
709
|
+
setChromaticAberration(intensity: number, start: number): void {
|
|
710
|
+
resolveFringe(intensity, start, this.fringe);
|
|
711
|
+
}
|
|
712
|
+
|
|
705
713
|
/** The print's grain and this frame's seed, held until changed. See `Renderer.setFilmGrain`. */
|
|
706
714
|
setFilmGrain(strength: number, seed: number): void {
|
|
707
715
|
this.grain = clampGrain(strength);
|
|
@@ -769,8 +777,15 @@ export class SceneTarget {
|
|
|
769
777
|
resolve(
|
|
770
778
|
strength: number,
|
|
771
779
|
reachUv: number,
|
|
772
|
-
/*
|
|
773
|
-
|
|
780
|
+
/*
|
|
781
|
+
* The grade, applied here because this is the last pass. See RUSH_FRAG's own note. `film` is
|
|
782
|
+
* the `filmic` curve's eight constants (`resolveFilmicCurve`), read only under transform 4.
|
|
783
|
+
*/
|
|
784
|
+
grade: {
|
|
785
|
+
readonly transform: number;
|
|
786
|
+
readonly exposure: number;
|
|
787
|
+
readonly film?: Float32Array;
|
|
788
|
+
} = {
|
|
774
789
|
transform: 0,
|
|
775
790
|
exposure: 1,
|
|
776
791
|
},
|
|
@@ -1012,6 +1027,12 @@ export class SceneTarget {
|
|
|
1012
1027
|
gl.uniform1f(this.uniforms['uReach'] ?? null, reachUv);
|
|
1013
1028
|
gl.uniform1i(this.uniforms['uOutputTransform'] ?? null, grade.transform);
|
|
1014
1029
|
gl.uniform1f(this.uniforms['uOutputExposure'] ?? null, grade.exposure);
|
|
1030
|
+
if (grade.film !== undefined) {
|
|
1031
|
+
gl.uniform4fv(this.uniforms['uFilmA'] ?? null, grade.film, 0, 4);
|
|
1032
|
+
gl.uniform4fv(this.uniforms['uFilmB'] ?? null, grade.film, 4, 4);
|
|
1033
|
+
}
|
|
1034
|
+
/* A standard range always here: see `Renderer.displayRange`. */
|
|
1035
|
+
gl.uniform1f(this.uniforms['uDisplayHeadroom'] ?? null, 1);
|
|
1015
1036
|
|
|
1016
1037
|
/*
|
|
1017
1038
|
* Depth on its own unit, and the reprojection with it. Bound every frame rather than
|
|
@@ -1113,6 +1134,12 @@ export class SceneTarget {
|
|
|
1113
1134
|
this.uniforms['uLocalExposure'] ?? null,
|
|
1114
1135
|
localGrid === null ? 0 : this.localExposure,
|
|
1115
1136
|
);
|
|
1137
|
+
/* Each object's own motion is WebGPU's under a reconstruction; here the scene stands in on
|
|
1138
|
+
unit 7 and the flag says not to read it, so the camera's motion alone blurs. */
|
|
1139
|
+
gl.activeTexture(gl.TEXTURE7);
|
|
1140
|
+
gl.bindTexture(gl.TEXTURE_2D, scene);
|
|
1141
|
+
gl.uniform1i(this.uniforms['uMotion'] ?? null, 7);
|
|
1142
|
+
gl.uniform1f(this.uniforms['uObjectMotion'] ?? null, 0);
|
|
1116
1143
|
gl.activeTexture(gl.TEXTURE0);
|
|
1117
1144
|
|
|
1118
1145
|
/* No unit and no texture: a flat colour, not a sample. Uploaded unconditionally — two
|
|
@@ -1122,6 +1149,7 @@ export class SceneTarget {
|
|
|
1122
1149
|
gl.uniform3fv(this.uniforms['uVeilColor'] ?? null, veilColor);
|
|
1123
1150
|
gl.uniform1f(this.uniforms['uVeilAlpha'] ?? null, veilAlpha);
|
|
1124
1151
|
gl.uniform1f(this.uniforms['uVignette'] ?? null, this.vignette);
|
|
1152
|
+
gl.uniform3fv(this.uniforms['uFringe'] ?? null, this.fringe);
|
|
1125
1153
|
gl.uniform2f(this.uniforms['uGrain'] ?? null, this.grain, this.grainSeed);
|
|
1126
1154
|
|
|
1127
1155
|
gl.bindVertexArray(this.vao);
|
|
@@ -27,6 +27,11 @@ import type { DecalBox } from './decalProjector.ts';
|
|
|
27
27
|
* which is the same price the temporal row declined for a velocity buffer. A caller declares a
|
|
28
28
|
* `ReflectiveSurface` instead: a box, and a facing rule for which surfaces inside it reflect. It is
|
|
29
29
|
* the shape `DecalProjector` already has, for the same reason.
|
|
30
|
+
*
|
|
31
|
+
* **Or every surface by its own material**, where the renderer was built with
|
|
32
|
+
* `screenSpaceReflections`: the lit stage leaves two maps of what it reflected — not a G-buffer, the
|
|
33
|
+
* environment's share and its tint, which is all a swap needs — and the same trace runs in a
|
|
34
|
+
* material mode over every pixel. See `frameReflections.ts`.
|
|
30
35
|
*/
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -350,9 +355,30 @@ export function towardCameraFade(direction: ReadonlyVec3, viewDirection: Readonl
|
|
|
350
355
|
return Math.min(1, Math.max(0, (along + 1) / 2));
|
|
351
356
|
}
|
|
352
357
|
|
|
358
|
+
/**
|
|
359
|
+
* What a `ReflectiveSurface` is made with: its box, which way the surfaces inside it face, and how
|
|
360
|
+
* much and how far they reflect. Every number but the box has a default; see each field.
|
|
361
|
+
*/
|
|
353
362
|
export interface ReflectiveSurfaceOptions extends DecalBox {
|
|
354
|
-
/**
|
|
363
|
+
/**
|
|
364
|
+
* How much of what the ray finds lands on the surface, 0 to 1: one share over the box, or with
|
|
365
|
+
* `fresnel` the surface's reflectance head-on.
|
|
366
|
+
*/
|
|
355
367
|
readonly strength?: number;
|
|
368
|
+
/**
|
|
369
|
+
* Whether `strength` is the surface's reflectance head-on, F0, rather than one share over the
|
|
370
|
+
* box. Each pixel then returns `F0 · A + B` of what its ray finds, the split-sum environment BRDF
|
|
371
|
+
* at its own view and `roughness` — Karis's fit, the one the lit stage reflects its environment
|
|
372
|
+
* by — so a floor returns more toward the horizon than underfoot, and the share follows the
|
|
373
|
+
* camera as Unreal weighs its reflections. **False by default**, the one share as before.
|
|
374
|
+
*
|
|
375
|
+
* **What it gives up**: the pass has no material buffer, so the roughness and F0 are the box's,
|
|
376
|
+
* not each pixel's, and nothing occludes the reflection where the lit stage's ambient occlusion
|
|
377
|
+
* would. A floor of one material is what it is for.
|
|
378
|
+
*/
|
|
379
|
+
readonly fresnel?: boolean;
|
|
380
|
+
/** The surface's roughness, 0 to 1, that `fresnel` weighs by. 0, a polished surface, if absent. */
|
|
381
|
+
readonly roughness?: number;
|
|
356
382
|
/** How far a ray may travel, world metres. */
|
|
357
383
|
readonly reachM?: number;
|
|
358
384
|
/** How deep a depth sample is treated as being. See `ScreenSpaceMarch.thicknessM`. */
|
|
@@ -382,6 +408,9 @@ export class ReflectiveSurface {
|
|
|
382
408
|
readonly tint = new Float32Array(3);
|
|
383
409
|
|
|
384
410
|
strength: number;
|
|
411
|
+
/** See `ReflectiveSurfaceOptions.fresnel`. */
|
|
412
|
+
fresnel: boolean;
|
|
413
|
+
roughness: number;
|
|
385
414
|
reachM: number;
|
|
386
415
|
thicknessM: number;
|
|
387
416
|
steps: number;
|
|
@@ -405,6 +434,8 @@ export class ReflectiveSurface {
|
|
|
405
434
|
/* Half, because a mirror is the rare case: a wet floor, polished stone and a car bonnet all
|
|
406
435
|
return a fraction of what lands on them, and a consumer who wants a mirror asks for one. */
|
|
407
436
|
this.strength = options.strength ?? 0.5;
|
|
437
|
+
this.fresnel = options.fresnel ?? false;
|
|
438
|
+
this.roughness = options.roughness ?? 0;
|
|
408
439
|
/* Eight metres covers a room and a street corner. Further costs steps rather than accuracy,
|
|
409
440
|
since the step is the reach divided by them. */
|
|
410
441
|
this.reachM = options.reachM ?? 8;
|
|
@@ -469,6 +500,8 @@ export interface RecordedReflection {
|
|
|
469
500
|
readonly axis: Float32Array;
|
|
470
501
|
readonly tint: Float32Array;
|
|
471
502
|
strength: number;
|
|
503
|
+
fresnel: boolean;
|
|
504
|
+
roughness: number;
|
|
472
505
|
reachM: number;
|
|
473
506
|
thicknessM: number;
|
|
474
507
|
steps: number;
|
|
@@ -482,6 +515,8 @@ function newRecord(): RecordedReflection {
|
|
|
482
515
|
axis: new Float32Array(3),
|
|
483
516
|
tint: new Float32Array(3),
|
|
484
517
|
strength: 0.5,
|
|
518
|
+
fresnel: false,
|
|
519
|
+
roughness: 0,
|
|
485
520
|
reachM: 8,
|
|
486
521
|
thicknessM: 0.25,
|
|
487
522
|
steps: 24,
|
|
@@ -537,6 +572,8 @@ export class ReflectionQueue {
|
|
|
537
572
|
record.axis.set(surface.axis);
|
|
538
573
|
record.tint.set(surface.tint);
|
|
539
574
|
record.strength = Math.min(1, Math.max(0, surface.strength));
|
|
575
|
+
record.fresnel = surface.fresnel;
|
|
576
|
+
record.roughness = Math.min(1, Math.max(0, surface.roughness));
|
|
540
577
|
record.reachM = Math.max(0, surface.reachM);
|
|
541
578
|
record.thicknessM = Math.max(0, surface.thicknessM);
|
|
542
579
|
record.steps = Math.min(32, Math.max(1, Math.round(surface.steps)));
|
|
@@ -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
|
|
@@ -88,7 +88,8 @@ export function grainWeight(luma: number): number {
|
|
|
88
88
|
|
|
89
89
|
/** Both backends' words when there is no composite to apply the look in. */
|
|
90
90
|
export const FILM_LOOK_WITHOUT_COMPOSITE =
|
|
91
|
-
'[driftengine] setVignette and
|
|
91
|
+
'[driftengine] setVignette, setFilmGrain and setChromaticAberration need `screenEffects`: ' +
|
|
92
|
+
'without a composite every ' +
|
|
92
93
|
'pass is the last thing to touch the frame, so there is no one place to apply a lens or a ' +
|
|
93
94
|
'print. The look was not applied.';
|
|
94
95
|
|
|
@@ -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. */
|