@driftengine/core 4.8.1 → 4.8.3
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 +142 -0
- package/README.md +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -0
- package/dist/input/actionMap.d.ts +6 -1
- package/dist/input/actionMap.js +20 -7
- package/dist/input/input.d.ts +28 -0
- package/dist/input/input.js +71 -9
- package/dist/input/inputHarness.d.ts +9 -0
- package/dist/input/inputHarness.js +34 -2
- package/dist/render/backend/materialChanges.d.ts +7 -2
- package/dist/render/backend/materialChanges.js +8 -3
- package/dist/render/backend/webgl2/renderer.d.ts +75 -3
- package/dist/render/backend/webgl2/renderer.js +209 -22
- package/dist/render/backend/webgpu/buffers.d.ts +2 -0
- package/dist/render/backend/webgpu/buffers.js +2 -0
- package/dist/render/backend/webgpu/causticsPass.js +2 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +13 -3
- package/dist/render/backend/webgpu/depthPass.js +4 -3
- package/dist/render/backend/webgpu/filmPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +5 -0
- package/dist/render/backend/webgpu/flatPass.js +18 -8
- package/dist/render/backend/webgpu/linePass.js +2 -1
- package/dist/render/backend/webgpu/panelPass.js +2 -1
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/pipelineCache.d.ts +40 -8
- package/dist/render/backend/webgpu/pipelineCache.js +76 -9
- package/dist/render/backend/webgpu/plumePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +12 -2
- package/dist/render/backend/webgpu/postPass.js +19 -5
- package/dist/render/backend/webgpu/renderer.d.ts +66 -2
- package/dist/render/backend/webgpu/renderer.js +189 -38
- package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
- package/dist/render/backend/webgpu/textPass.js +2 -1
- package/dist/render/backend/webgpu/waterPass.js +2 -1
- package/dist/render/backend/webgpu/windStreakPass.js +2 -1
- package/dist/render/boltBatch.js +2 -1
- package/dist/render/causticsRenderer.js +2 -1
- package/dist/render/cutoutCaster.d.ts +3 -1
- package/dist/render/cutoutCaster.js +4 -0
- package/dist/render/decalPass.js +2 -1
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/lineBatch.js +5 -1
- package/dist/render/mapsWithoutUvs.d.ts +24 -0
- package/dist/render/mapsWithoutUvs.js +24 -0
- package/dist/render/mesh.d.ts +2 -0
- package/dist/render/mesh.js +3 -0
- package/dist/render/oitPass.js +2 -1
- package/dist/render/particleBatch.js +3 -2
- package/dist/render/particlePool.d.ts +21 -2
- package/dist/render/particlePool.js +72 -37
- package/dist/render/pass.d.ts +12 -0
- package/dist/render/plumeRenderer.js +3 -2
- package/dist/render/renderQuality.d.ts +16 -8
- package/dist/render/sceneCoverage.d.ts +54 -0
- package/dist/render/sceneCoverage.js +72 -0
- package/dist/render/sdfTextRenderer.js +2 -1
- package/dist/render/shaders/depth.js +3 -2
- package/dist/render/shaders/flat/glassTint.d.ts +28 -0
- package/dist/render/shaders/flat/glassTint.js +31 -0
- package/dist/render/shaders/flat/index.d.ts +12 -0
- package/dist/render/shaders/flat/index.js +13 -2
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +60 -25
- package/dist/render/shaders/flat/surfaceEffects.d.ts +1 -1
- package/dist/render/shaders/flat/surfaceEffects.js +6 -2
- package/dist/render/shaders/generated/depth.wgsl.d.ts +32 -12
- package/dist/render/shaders/generated/depth.wgsl.js +32 -12
- package/dist/render/shaders/generated/flat.wgsl.d.ts +89 -0
- package/dist/render/shaders/generated/flat.wgsl.js +127 -38
- 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 +3 -3
- package/dist/render/shaders/generated/particle.wgsl.js +3 -3
- package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/rush.wgsl.js +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +14 -1
- package/dist/render/shaders/outputTransform.js +23 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
- package/dist/render/shaders/rush.js +13 -2
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/temporalResolve.js +12 -5
- package/dist/render/ssrPass.js +2 -1
- package/dist/render/surfaceTexture.d.ts +13 -0
- package/dist/render/surfaceTexture.js +4 -0
- package/dist/render/textRenderer.js +2 -1
- package/dist/render/vertexDefaults.js +1 -0
- package/dist/render/viewModel.d.ts +29 -0
- package/dist/render/viewModel.js +35 -0
- package/dist/render/waterRenderer.js +2 -1
- package/dist/render/windStreakRenderer.js +2 -1
- package/package.json +6 -6
- package/src/index.ts +22 -1
- package/src/input/actionMap.ts +23 -8
- package/src/input/input.ts +79 -9
- package/src/input/inputHarness.ts +27 -2
- package/src/render/backend/materialChanges.ts +8 -3
- package/src/render/backend/webgl2/renderer.ts +223 -29
- package/src/render/backend/webgpu/buffers.ts +5 -0
- package/src/render/backend/webgpu/causticsPass.ts +2 -1
- package/src/render/backend/webgpu/depthPass.ts +4 -3
- package/src/render/backend/webgpu/filmPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +43 -26
- package/src/render/backend/webgpu/linePass.ts +2 -1
- package/src/render/backend/webgpu/panelPass.ts +2 -1
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/pipelineCache.ts +82 -8
- package/src/render/backend/webgpu/plumePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +20 -5
- package/src/render/backend/webgpu/renderer.ts +191 -37
- package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
- package/src/render/backend/webgpu/textPass.ts +2 -1
- package/src/render/backend/webgpu/waterPass.ts +2 -1
- package/src/render/backend/webgpu/windStreakPass.ts +2 -1
- package/src/render/boltBatch.ts +2 -1
- package/src/render/causticsRenderer.ts +2 -1
- package/src/render/cutoutCaster.ts +15 -2
- package/src/render/decalPass.ts +2 -1
- package/src/render/globalMediumPass.ts +3 -2
- package/src/render/lineBatch.ts +3 -1
- package/src/render/mapsWithoutUvs.ts +43 -0
- package/src/render/mesh.ts +3 -0
- package/src/render/oitPass.ts +2 -1
- package/src/render/particleBatch.ts +3 -2
- package/src/render/particlePool.ts +107 -56
- package/src/render/pass.ts +12 -0
- package/src/render/plumeRenderer.ts +3 -2
- package/src/render/renderQuality.ts +16 -8
- package/src/render/sceneCoverage.ts +78 -0
- package/src/render/sdfTextRenderer.ts +2 -1
- package/src/render/shaders/depth.ts +3 -2
- package/src/render/shaders/flat/glassTint.ts +48 -0
- package/src/render/shaders/flat/index.ts +25 -2
- package/src/render/shaders/flat/main.ts +60 -25
- package/src/render/shaders/flat/surfaceEffects.ts +6 -2
- package/src/render/shaders/generated/depth.wgsl.ts +32 -12
- package/src/render/shaders/generated/flat.wgsl.ts +127 -38
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +3 -3
- package/src/render/shaders/generated/rush.wgsl.ts +1 -1
- package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/outputTransform.ts +24 -1
- package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
- package/src/render/shaders/rush.ts +13 -2
- package/src/render/shaders/temporalResolve.ts +12 -5
- package/src/render/ssrPass.ts +2 -1
- package/src/render/surfaceTexture.ts +16 -0
- package/src/render/textRenderer.ts +2 -1
- package/src/render/vertexDefaults.ts +1 -0
- package/src/render/viewModel.ts +41 -0
- package/src/render/waterRenderer.ts +2 -1
- package/src/render/windStreakRenderer.ts +2 -1
|
@@ -3,6 +3,7 @@ import { WATER_BINDINGS, WATER_FRAG_WGSL, WATER_VERT_WGSL, } from '../../shaders
|
|
|
3
3
|
import { PATCH_RESOLUTION, buildUnitSheet, buildWaterGrid } from '../../waterGrid.js';
|
|
4
4
|
import { DEPTH_FORMAT } from './flatPass.js';
|
|
5
5
|
import { shaderModule } from './shaderModules.js';
|
|
6
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* The water pass: two sheets and a patch, displaced by a wave field in the vertex stage.
|
|
8
9
|
*
|
|
@@ -148,7 +149,7 @@ export function waterPipeline(cache, device, layout) {
|
|
|
148
149
|
format: cache.format,
|
|
149
150
|
blend: {
|
|
150
151
|
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
151
|
-
alpha:
|
|
152
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
152
153
|
},
|
|
153
154
|
},
|
|
154
155
|
],
|
|
@@ -3,6 +3,7 @@ import { WINDSTREAKS_BINDINGS, WIND_STREAK_FRAG_WGSL, WIND_STREAK_VERT_WGSL, } f
|
|
|
3
3
|
import { buildWindStreakLattice } from '../../windStreakDraw.js';
|
|
4
4
|
import { DEPTH_FORMAT } from './flatPass.js';
|
|
5
5
|
import { shaderModule } from './shaderModules.js';
|
|
6
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* The wind-streak pass: a lattice of debris that follows the camera and wraps.
|
|
8
9
|
*
|
|
@@ -141,7 +142,7 @@ export function windStreakPipeline(cache, device, layout) {
|
|
|
141
142
|
dstFactor: 'one-minus-src-alpha',
|
|
142
143
|
operation: 'add',
|
|
143
144
|
},
|
|
144
|
-
alpha:
|
|
145
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
145
146
|
},
|
|
146
147
|
},
|
|
147
148
|
],
|
package/dist/render/boltBatch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
2
2
|
import { BOLT_FRAG, BOLT_VERT } from './shaders/bolt.js';
|
|
3
3
|
import { buildSegmentQuads, expandBoltSegments } from './segmentQuads.js';
|
|
4
|
+
import { blendKeeping } from './sceneCoverage.js';
|
|
4
5
|
/**
|
|
5
6
|
* One draw call for a pool of electrical arcs.
|
|
6
7
|
*
|
|
@@ -80,7 +81,7 @@ export class BoltBatch {
|
|
|
80
81
|
if (segmentCount === 0)
|
|
81
82
|
return;
|
|
82
83
|
gl.enable(gl.BLEND);
|
|
83
|
-
|
|
84
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
84
85
|
gl.depthMask(false);
|
|
85
86
|
gl.disable(gl.CULL_FACE);
|
|
86
87
|
gl.bindVertexArray(this.vao);
|
|
@@ -3,6 +3,7 @@ import { CAUSTICS_FRAG, CAUSTICS_VERT } from './shaders/caustics.js';
|
|
|
3
3
|
import { bindAtmosphere } from './atmosphere.js';
|
|
4
4
|
import { buildSheets } from './surfaceSheet.js';
|
|
5
5
|
import { seaStateForWind } from './seaState.js';
|
|
6
|
+
import { blendKeeping } from './sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* How far from the water the effect has died out.
|
|
8
9
|
*
|
|
@@ -97,7 +98,7 @@ export class CausticsRenderer {
|
|
|
97
98
|
gl.uniform1f(u['uWaveGain'] ?? null, sea.steepness);
|
|
98
99
|
bindAtmosphere(gl, u, atmosphere, camera.position[1] ?? 0, underwaterEnabled);
|
|
99
100
|
gl.enable(gl.BLEND);
|
|
100
|
-
|
|
101
|
+
blendKeeping(gl, gl.ONE, gl.ONE);
|
|
101
102
|
/*
|
|
102
103
|
* No depth write, but depth *testing* stays on: the sheet sits a couple of
|
|
103
104
|
* centimetres under the surface it lights, so geometry in front of it — the
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
* reads it and binds before it asks again.
|
|
13
13
|
*/
|
|
14
14
|
import type { SurfaceMaterial } from './surfaceTexture.ts';
|
|
15
|
-
/** A cutout caster's texture, its cutoff and its UV scale. */
|
|
15
|
+
/** A cutout caster's texture, its cutoff and its UV scale and offset. */
|
|
16
16
|
export interface CutoutCaster<Texture> {
|
|
17
17
|
readonly albedo: Texture;
|
|
18
18
|
readonly cutoff: number;
|
|
19
19
|
readonly u: number;
|
|
20
20
|
readonly v: number;
|
|
21
|
+
readonly uOffset: number;
|
|
22
|
+
readonly vOffset: number;
|
|
21
23
|
}
|
|
22
24
|
/** The cutout `material` casts, or null for an ordinary caster. */
|
|
23
25
|
export declare function cutoutOf<Texture>(material: SurfaceMaterial<Texture> | null | undefined): CutoutCaster<Texture> | null;
|
|
@@ -3,6 +3,8 @@ const answer = {
|
|
|
3
3
|
cutoff: 0,
|
|
4
4
|
u: 1,
|
|
5
5
|
v: 1,
|
|
6
|
+
uOffset: 0,
|
|
7
|
+
vOffset: 0,
|
|
6
8
|
};
|
|
7
9
|
/** The cutout `material` casts, or null for an ordinary caster. */
|
|
8
10
|
export function cutoutOf(material) {
|
|
@@ -16,5 +18,7 @@ export function cutoutOf(material) {
|
|
|
16
18
|
answer.cutoff = cutoff;
|
|
17
19
|
answer.u = material.uScale ?? 1;
|
|
18
20
|
answer.v = material.vScale ?? 1;
|
|
21
|
+
answer.uOffset = material.uOffset ?? 0;
|
|
22
|
+
answer.vOffset = material.vOffset ?? 0;
|
|
19
23
|
return answer;
|
|
20
24
|
}
|
package/dist/render/decalPass.js
CHANGED
|
@@ -4,6 +4,7 @@ import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.js';
|
|
|
4
4
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
5
5
|
import { DECAL_PROJECT_FRAG } from './shaders/decalProject.js';
|
|
6
6
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
|
|
7
|
+
import { blendKeeping } from './sceneCoverage.js';
|
|
7
8
|
/**
|
|
8
9
|
* Drawn decals on WebGL2: one scissored triangle per mark, multiplied into the finished scene.
|
|
9
10
|
*
|
|
@@ -54,7 +55,7 @@ export class DecalPass {
|
|
|
54
55
|
gl.depthMask(false);
|
|
55
56
|
gl.disable(gl.CULL_FACE);
|
|
56
57
|
gl.enable(gl.BLEND);
|
|
57
|
-
|
|
58
|
+
blendKeeping(gl, gl.ZERO, gl.SRC_COLOR);
|
|
58
59
|
gl.enable(gl.SCISSOR_TEST);
|
|
59
60
|
/* Unit 0, chosen rather than inherited: `bindTexture` binds to whichever unit is active, and
|
|
60
61
|
the renderer leaves that wherever its last pass finished. `OitPass` records what leaving it
|
|
@@ -4,6 +4,7 @@ import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.js';
|
|
|
4
4
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
5
5
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
|
|
6
6
|
import { MEDIUM_FRAG, MEDIUM_UPSAMPLE_FRAG } from './shaders/globalMedium.js';
|
|
7
|
+
import { blendCovering, blendTransmitting } from './sceneCoverage.js';
|
|
7
8
|
/**
|
|
8
9
|
* The global participating medium on WebGL2: a half-res march, then a depth-aware composite.
|
|
9
10
|
*
|
|
@@ -200,7 +201,7 @@ export class GlobalMediumPass {
|
|
|
200
201
|
gl.depthMask(false);
|
|
201
202
|
gl.disable(gl.CULL_FACE);
|
|
202
203
|
gl.enable(gl.BLEND);
|
|
203
|
-
|
|
204
|
+
blendTransmitting(gl, gl.ONE, gl.SRC_ALPHA);
|
|
204
205
|
gl.useProgram(this.upsample);
|
|
205
206
|
gl.bindVertexArray(this.vao);
|
|
206
207
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -220,7 +221,7 @@ export class GlobalMediumPass {
|
|
|
220
221
|
gl.activeTexture(gl.TEXTURE0);
|
|
221
222
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
222
223
|
/* Back to the blend every other pass in this renderer assumes it inherits. */
|
|
223
|
-
|
|
224
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
224
225
|
gl.disable(gl.BLEND);
|
|
225
226
|
gl.depthMask(true);
|
|
226
227
|
gl.enable(gl.DEPTH_TEST);
|
package/dist/render/lineBatch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
2
2
|
import { LINE_FRAG, LINE_VERT } from './shaders/line.js';
|
|
3
3
|
import { buildSegmentQuads, expandLineSegments } from './segmentQuads.js';
|
|
4
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.js';
|
|
4
5
|
/**
|
|
5
6
|
* One draw call for a polyline with a real width — an audio waveform, a trail, a plotted
|
|
6
7
|
* curve, anything that is a stroke rather than a surface.
|
|
@@ -96,7 +97,10 @@ export class LineBatch {
|
|
|
96
97
|
if (segmentCount === 0)
|
|
97
98
|
return;
|
|
98
99
|
gl.enable(gl.BLEND);
|
|
99
|
-
|
|
100
|
+
if (additive)
|
|
101
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
102
|
+
else
|
|
103
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
100
104
|
gl.depthMask(false);
|
|
101
105
|
// The quad billboards toward the view direction the same way a bolt's does — see
|
|
102
106
|
// `line.ts`'s own `cross(dir, view)` — so its winding can flip with the camera exactly
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saying, once a mesh, that a material's maps cannot be read on it.
|
|
3
|
+
*
|
|
4
|
+
* **A mesh with no texture coordinates reads one constant coordinate**, so every map bound to it
|
|
5
|
+
* samples a single texel: an emissive map makes nothing glow, a normal map tilts nothing, and the
|
|
6
|
+
* frame is identical pixel for pixel with and without the material's images. Found when an example's
|
|
7
|
+
* burning logs never glowed — `MeshBuilder` writes no coordinates unless `build({ planarUvs: true })`
|
|
8
|
+
* asks — and nothing said why. One decision for both backends, each calling it from its draw.
|
|
9
|
+
*
|
|
10
|
+
* Once per mesh, held weakly: a mesh drawn every frame is said once, and a disposed one is
|
|
11
|
+
* forgotten. **What it gives up** is a material shared between a textured mesh and an untextured one
|
|
12
|
+
* on purpose, which is said once for the untextured one and is then quiet.
|
|
13
|
+
*/
|
|
14
|
+
import type { SurfaceMaterial } from './surfaceTexture.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Whether a material binds a map that is a picture: albedo, normal or emissive.
|
|
17
|
+
*
|
|
18
|
+
* **Not ORM**, because a one-colour ORM map is how a draw sets a constant roughness and metalness,
|
|
19
|
+
* and read at one texel it is exactly that constant — the materials example draws its spheres so.
|
|
20
|
+
* The three counted here are pictures nobody binds to be read at one point.
|
|
21
|
+
*/
|
|
22
|
+
export declare function materialHasMaps(material: SurfaceMaterial<unknown> | null): boolean;
|
|
23
|
+
/** Warn the first time `mesh`, which has no coordinates, is drawn with a picture map bound. */
|
|
24
|
+
export declare function noteMapsWithoutUvs(mesh: object, hasUvs: boolean, materialHasMaps: boolean): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const told = new WeakSet();
|
|
2
|
+
/**
|
|
3
|
+
* Whether a material binds a map that is a picture: albedo, normal or emissive.
|
|
4
|
+
*
|
|
5
|
+
* **Not ORM**, because a one-colour ORM map is how a draw sets a constant roughness and metalness,
|
|
6
|
+
* and read at one texel it is exactly that constant — the materials example draws its spheres so.
|
|
7
|
+
* The three counted here are pictures nobody binds to be read at one point.
|
|
8
|
+
*/
|
|
9
|
+
export function materialHasMaps(material) {
|
|
10
|
+
if (material === null)
|
|
11
|
+
return false;
|
|
12
|
+
return ((material.albedo ?? null) !== null ||
|
|
13
|
+
(material.normal ?? null) !== null ||
|
|
14
|
+
(material.emissive ?? null) !== null);
|
|
15
|
+
}
|
|
16
|
+
/** Warn the first time `mesh`, which has no coordinates, is drawn with a picture map bound. */
|
|
17
|
+
export function noteMapsWithoutUvs(mesh, hasUvs, materialHasMaps) {
|
|
18
|
+
if (hasUvs || !materialHasMaps || told.has(mesh))
|
|
19
|
+
return;
|
|
20
|
+
told.add(mesh);
|
|
21
|
+
console.warn('[driftengine] a mesh with no texture coordinates is drawn with an albedo, normal or emissive ' +
|
|
22
|
+
'map, which it cannot read: every map samples one texel. Give the mesh `uvs` — ' +
|
|
23
|
+
'`MeshBuilder.build({ planarUvs: true })` writes them — or draw it without the maps.');
|
|
24
|
+
}
|
package/dist/render/mesh.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ export declare class Mesh {
|
|
|
56
56
|
readonly bounds: Bounds;
|
|
57
57
|
/** Whether this mesh carries a rig, which decides which flat program draws it. */
|
|
58
58
|
readonly isSkinned: boolean;
|
|
59
|
+
/** Whether it has texture coordinates, without which a material's maps read one texel. */
|
|
60
|
+
readonly hasUvs: boolean;
|
|
59
61
|
/**
|
|
60
62
|
* This mesh's morph deltas, or null for geometry that does not deform.
|
|
61
63
|
*
|
package/dist/render/mesh.js
CHANGED
|
@@ -123,6 +123,8 @@ export class Mesh {
|
|
|
123
123
|
bounds = createBounds();
|
|
124
124
|
/** Whether this mesh carries a rig, which decides which flat program draws it. */
|
|
125
125
|
isSkinned;
|
|
126
|
+
/** Whether it has texture coordinates, without which a material's maps read one texel. */
|
|
127
|
+
hasUvs;
|
|
126
128
|
/**
|
|
127
129
|
* This mesh's morph deltas, or null for geometry that does not deform.
|
|
128
130
|
*
|
|
@@ -171,6 +173,7 @@ export class Mesh {
|
|
|
171
173
|
constructor(gl, data, dynamic = false, spread = false) {
|
|
172
174
|
validateMeshData(data);
|
|
173
175
|
this.spread = spread;
|
|
176
|
+
this.hasUvs = data.uvs !== undefined;
|
|
174
177
|
boundsOfPositions(data.positions, this.bounds);
|
|
175
178
|
this.positionFloats = data.positions.length;
|
|
176
179
|
const vao = gl.createVertexArray();
|
package/dist/render/oitPass.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
2
2
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
|
|
3
3
|
import { OIT_RESOLVE_FRAG } from './shaders/oitResolve.js';
|
|
4
|
+
import { blendTransmitting } from './sceneCoverage.js';
|
|
4
5
|
/**
|
|
5
6
|
* Order-independent transparency on WebGL2: two targets, two passes over the same geometry, and a
|
|
6
7
|
* resolve.
|
|
@@ -163,7 +164,7 @@ export class OitPass {
|
|
|
163
164
|
gl.depthMask(false);
|
|
164
165
|
gl.disable(gl.CULL_FACE);
|
|
165
166
|
gl.enable(gl.BLEND);
|
|
166
|
-
|
|
167
|
+
blendTransmitting(gl, gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);
|
|
167
168
|
gl.useProgram(this.program);
|
|
168
169
|
gl.activeTexture(gl.TEXTURE0);
|
|
169
170
|
gl.bindTexture(gl.TEXTURE_2D, this.accum);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
2
2
|
import { particleShaders } from './particleMaterial.js';
|
|
3
3
|
import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from './shaders/particle.js';
|
|
4
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.js';
|
|
4
5
|
/** Corners of one blade, as a triangle-strip-friendly quad. */
|
|
5
6
|
const CORNERS = [
|
|
6
7
|
[-1, -1],
|
|
@@ -138,9 +139,9 @@ export class ParticleBatch {
|
|
|
138
139
|
return;
|
|
139
140
|
gl.enable(gl.BLEND);
|
|
140
141
|
if (this.blend === 'additive')
|
|
141
|
-
|
|
142
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
142
143
|
else
|
|
143
|
-
|
|
144
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
144
145
|
gl.depthMask(false);
|
|
145
146
|
gl.disable(gl.CULL_FACE);
|
|
146
147
|
gl.bindVertexArray(this.vao);
|
|
@@ -19,7 +19,11 @@ import type { Vec3 } from '../math/color.ts';
|
|
|
19
19
|
export interface ParticleInstances {
|
|
20
20
|
/** World position, three floats each. */
|
|
21
21
|
positions: Float32Array;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Metres from the centre to an edge, a half-width: the quad spans twice this. Then radians of
|
|
24
|
+
* roll about the particle's own axis. This said metres across until 4.8.3, and a caller who took
|
|
25
|
+
* it at its word drew every particle twice as wide as meant.
|
|
26
|
+
*/
|
|
23
27
|
sizes: Float32Array;
|
|
24
28
|
spins: Float32Array;
|
|
25
29
|
/** Linear colour, three floats each. */
|
|
@@ -54,7 +58,7 @@ export interface ParticlePoolOptions {
|
|
|
54
58
|
capacity: number;
|
|
55
59
|
/** Seconds a particle lives. */
|
|
56
60
|
lifeSec: number;
|
|
57
|
-
/** Size at birth and at death,
|
|
61
|
+
/** Size at birth and at death: metres from the centre to an edge, so the quad is twice as wide. */
|
|
58
62
|
sizeStart: number;
|
|
59
63
|
sizeEnd: number;
|
|
60
64
|
/** Colour at birth and at death. */
|
|
@@ -98,6 +102,15 @@ export declare class ParticlePool {
|
|
|
98
102
|
* together.
|
|
99
103
|
*/
|
|
100
104
|
private readonly seedOf;
|
|
105
|
+
/**
|
|
106
|
+
* Where each slot's particle sits in the drawn streams, or -1 where it sits nowhere.
|
|
107
|
+
*
|
|
108
|
+
* **What lets `emit` draw a particle the frame it is born.** The streams are compacted, so a
|
|
109
|
+
* slot's entry is wherever `update` last put it; an emit into a slot that has one overwrites
|
|
110
|
+
* that entry, and an emit into one that has none appends. Without it a new particle waited for
|
|
111
|
+
* the next `update` to be drawn at all.
|
|
112
|
+
*/
|
|
113
|
+
private readonly drawnAt;
|
|
101
114
|
private next;
|
|
102
115
|
constructor(options: ParticlePoolOptions);
|
|
103
116
|
/** How many particles are alive. */
|
|
@@ -119,6 +132,12 @@ export declare class ParticlePool {
|
|
|
119
132
|
* particles cost nothing but the loop iteration.
|
|
120
133
|
*/
|
|
121
134
|
update(dt: number): void;
|
|
135
|
+
/**
|
|
136
|
+
* One particle's entry in both drawn streams, at `t` of its life: what `update` writes for every
|
|
137
|
+
* live particle and `emit` for a new one, so the two cannot disagree about what a particle looks
|
|
138
|
+
* like.
|
|
139
|
+
*/
|
|
140
|
+
private writeDrawn;
|
|
122
141
|
/** Drop everything, for a respawn or a scene change. */
|
|
123
142
|
clear(): void;
|
|
124
143
|
}
|
|
@@ -38,6 +38,15 @@ export class ParticlePool {
|
|
|
38
38
|
* together.
|
|
39
39
|
*/
|
|
40
40
|
seedOf;
|
|
41
|
+
/**
|
|
42
|
+
* Where each slot's particle sits in the drawn streams, or -1 where it sits nowhere.
|
|
43
|
+
*
|
|
44
|
+
* **What lets `emit` draw a particle the frame it is born.** The streams are compacted, so a
|
|
45
|
+
* slot's entry is wherever `update` last put it; an emit into a slot that has one overwrites
|
|
46
|
+
* that entry, and an emit into one that has none appends. Without it a new particle waited for
|
|
47
|
+
* the next `update` to be drawn at all.
|
|
48
|
+
*/
|
|
49
|
+
drawnAt;
|
|
41
50
|
next = 0;
|
|
42
51
|
constructor(options) {
|
|
43
52
|
this.options = options;
|
|
@@ -60,6 +69,7 @@ export class ParticlePool {
|
|
|
60
69
|
this.spin = new Float32Array(capacity);
|
|
61
70
|
this.scale = new Float32Array(capacity).fill(1);
|
|
62
71
|
this.seedOf = new Float32Array(capacity);
|
|
72
|
+
this.drawnAt = new Int32Array(capacity).fill(-1);
|
|
63
73
|
}
|
|
64
74
|
/** How many particles are alive. */
|
|
65
75
|
get live() {
|
|
@@ -95,6 +105,18 @@ export class ParticlePool {
|
|
|
95
105
|
this.spin[i] = (hash - Math.floor(hash)) * Math.PI * 2;
|
|
96
106
|
this.scale[i] = sizeScale;
|
|
97
107
|
this.seedOf[i] = seed;
|
|
108
|
+
/*
|
|
109
|
+
* Drawn now, at birth, rather than at the next `update`: into the entry this slot already
|
|
110
|
+
* has when it is taking over a particle that was drawn, and onto the end otherwise.
|
|
111
|
+
*/
|
|
112
|
+
const held = this.drawnAt[i];
|
|
113
|
+
const at = held >= 0 ? held : this.particles.count;
|
|
114
|
+
this.writeDrawn(i, at, 0, x, y, z, vx, vy, vz);
|
|
115
|
+
if (held < 0) {
|
|
116
|
+
this.drawnAt[i] = at;
|
|
117
|
+
this.instances.count = at + 1;
|
|
118
|
+
this.particles.count = at + 1;
|
|
119
|
+
}
|
|
98
120
|
}
|
|
99
121
|
/**
|
|
100
122
|
* Advance every particle and rebuild the instance buffer.
|
|
@@ -108,12 +130,15 @@ export class ParticlePool {
|
|
|
108
130
|
let count = 0;
|
|
109
131
|
for (let i = 0; i < this.age.length; i++) {
|
|
110
132
|
const age = this.age[i];
|
|
111
|
-
if (age === Infinity)
|
|
133
|
+
if (age === Infinity) {
|
|
134
|
+
this.drawnAt[i] = -1;
|
|
112
135
|
continue;
|
|
136
|
+
}
|
|
113
137
|
const life = this.life[i];
|
|
114
138
|
const next = age + dt;
|
|
115
139
|
if (next >= life) {
|
|
116
140
|
this.age[i] = Infinity;
|
|
141
|
+
this.drawnAt[i] = -1;
|
|
117
142
|
continue;
|
|
118
143
|
}
|
|
119
144
|
this.age[i] = next;
|
|
@@ -131,50 +156,60 @@ export class ParticlePool {
|
|
|
131
156
|
this.x[i] = px;
|
|
132
157
|
this.y[i] = py;
|
|
133
158
|
this.z[i] = pz;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const r = o.colorStart[0] + (o.colorEnd[0] - o.colorStart[0]) * t;
|
|
137
|
-
const g = o.colorStart[1] + (o.colorEnd[1] - o.colorStart[1]) * t;
|
|
138
|
-
const b = o.colorStart[2] + (o.colorEnd[2] - o.colorStart[2]) * t;
|
|
139
|
-
/*
|
|
140
|
-
* Colour carries the fade rather than an alpha channel: the instanced
|
|
141
|
-
* path multiplies a tint into the base mesh and has no per-instance
|
|
142
|
-
* opacity, and for an additive puff darkening to nothing *is* fading out.
|
|
143
|
-
* It also means the effect needs no blend-state change.
|
|
144
|
-
*/
|
|
145
|
-
const fade = 1 - t * t;
|
|
146
|
-
writeInstance(this.instances, count, px, py, pz, size, this.spin[i], r * fade, g * fade, b * fade, 0, 0, 0);
|
|
147
|
-
/*
|
|
148
|
-
* The same particle for a material that has a real opacity channel. Colour
|
|
149
|
-
* is written *unfaded* here, because dimming it is the scatter path's
|
|
150
|
-
* workaround and doing both would fade twice — a puff that vanished at half
|
|
151
|
-
* its life while still occluding what was behind it.
|
|
152
|
-
*/
|
|
153
|
-
const p = this.particles;
|
|
154
|
-
const alphaStart = o.alphaStart ?? 1;
|
|
155
|
-
const alphaEnd = o.alphaEnd ?? 0;
|
|
156
|
-
p.positions[count * 3] = px;
|
|
157
|
-
p.positions[count * 3 + 1] = py;
|
|
158
|
-
p.positions[count * 3 + 2] = pz;
|
|
159
|
-
p.sizes[count] = size;
|
|
160
|
-
p.spins[count] = this.spin[i];
|
|
161
|
-
p.colors[count * 3] = r;
|
|
162
|
-
p.colors[count * 3 + 1] = g;
|
|
163
|
-
p.colors[count * 3 + 2] = b;
|
|
164
|
-
p.alphas[count] = alphaStart + (alphaEnd - alphaStart) * t;
|
|
165
|
-
p.ages[count] = t;
|
|
166
|
-
p.seeds[count] = this.seedOf[i];
|
|
167
|
-
p.velocities[count * 3] = vx;
|
|
168
|
-
p.velocities[count * 3 + 1] = vy;
|
|
169
|
-
p.velocities[count * 3 + 2] = vz;
|
|
159
|
+
this.drawnAt[i] = count;
|
|
160
|
+
this.writeDrawn(i, count, next / life, px, py, pz, vx, vy, vz);
|
|
170
161
|
count++;
|
|
171
162
|
}
|
|
172
163
|
this.instances.count = count;
|
|
173
164
|
this.particles.count = count;
|
|
174
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* One particle's entry in both drawn streams, at `t` of its life: what `update` writes for every
|
|
168
|
+
* live particle and `emit` for a new one, so the two cannot disagree about what a particle looks
|
|
169
|
+
* like.
|
|
170
|
+
*/
|
|
171
|
+
writeDrawn(i, count, t, px, py, pz, vx, vy, vz) {
|
|
172
|
+
const o = this.options;
|
|
173
|
+
const size = (o.sizeStart + (o.sizeEnd - o.sizeStart) * t) * this.scale[i];
|
|
174
|
+
const r = o.colorStart[0] + (o.colorEnd[0] - o.colorStart[0]) * t;
|
|
175
|
+
const g = o.colorStart[1] + (o.colorEnd[1] - o.colorStart[1]) * t;
|
|
176
|
+
const b = o.colorStart[2] + (o.colorEnd[2] - o.colorStart[2]) * t;
|
|
177
|
+
/*
|
|
178
|
+
* Colour carries the fade rather than an alpha channel: the instanced
|
|
179
|
+
* path multiplies a tint into the base mesh and has no per-instance
|
|
180
|
+
* opacity, and for an additive puff darkening to nothing *is* fading out.
|
|
181
|
+
* It also means the effect needs no blend-state change.
|
|
182
|
+
*/
|
|
183
|
+
const fade = 1 - t * t;
|
|
184
|
+
writeInstance(this.instances, count, px, py, pz, size, this.spin[i], r * fade, g * fade, b * fade, 0, 0, 0);
|
|
185
|
+
/*
|
|
186
|
+
* The same particle for a material that has a real opacity channel. Colour
|
|
187
|
+
* is written *unfaded* here, because dimming it is the scatter path's
|
|
188
|
+
* workaround and doing both would fade twice — a puff that vanished at half
|
|
189
|
+
* its life while still occluding what was behind it.
|
|
190
|
+
*/
|
|
191
|
+
const p = this.particles;
|
|
192
|
+
const alphaStart = o.alphaStart ?? 1;
|
|
193
|
+
const alphaEnd = o.alphaEnd ?? 0;
|
|
194
|
+
p.positions[count * 3] = px;
|
|
195
|
+
p.positions[count * 3 + 1] = py;
|
|
196
|
+
p.positions[count * 3 + 2] = pz;
|
|
197
|
+
p.sizes[count] = size;
|
|
198
|
+
p.spins[count] = this.spin[i];
|
|
199
|
+
p.colors[count * 3] = r;
|
|
200
|
+
p.colors[count * 3 + 1] = g;
|
|
201
|
+
p.colors[count * 3 + 2] = b;
|
|
202
|
+
p.alphas[count] = alphaStart + (alphaEnd - alphaStart) * t;
|
|
203
|
+
p.ages[count] = t;
|
|
204
|
+
p.seeds[count] = this.seedOf[i];
|
|
205
|
+
p.velocities[count * 3] = vx;
|
|
206
|
+
p.velocities[count * 3 + 1] = vy;
|
|
207
|
+
p.velocities[count * 3 + 2] = vz;
|
|
208
|
+
}
|
|
175
209
|
/** Drop everything, for a respawn or a scene change. */
|
|
176
210
|
clear() {
|
|
177
211
|
this.age.fill(Infinity);
|
|
212
|
+
this.drawnAt.fill(-1);
|
|
178
213
|
this.instances.count = 0;
|
|
179
214
|
this.particles.count = 0;
|
|
180
215
|
this.next = 0;
|
package/dist/render/pass.d.ts
CHANGED
|
@@ -122,6 +122,18 @@ export type PassContext = {
|
|
|
122
122
|
readonly pass: GPURenderPassEncoder;
|
|
123
123
|
/** The frame's jitter, the same array `PrepareContext.jitter` held. */
|
|
124
124
|
readonly jitter: Float32Array;
|
|
125
|
+
/**
|
|
126
|
+
* The colour format, depth format and sample count of the pass this draw lands in, which a
|
|
127
|
+
* pipeline set on it must match exactly.
|
|
128
|
+
*
|
|
129
|
+
* **Not always the device's.** `PassDevice` says what the frame is; a pass drawn after
|
|
130
|
+
* `endFrame` lands on the canvas, at the canvas's format and one sample, where the frame is
|
|
131
|
+
* the scene target at the scene's samples. A pass that is drawn in both places keeps a
|
|
132
|
+
* pipeline for each and chooses here; one built only from the device is refused by the other.
|
|
133
|
+
*/
|
|
134
|
+
readonly format: GPUTextureFormat;
|
|
135
|
+
readonly depthFormat: GPUTextureFormat;
|
|
136
|
+
readonly samples: number;
|
|
125
137
|
});
|
|
126
138
|
/**
|
|
127
139
|
* What a pass is handed once a frame, before the frame's own target exists.
|
|
@@ -3,6 +3,7 @@ import { compileProgram, uniformLocations } from './shader.js';
|
|
|
3
3
|
import { bindAtmosphere } from './atmosphere.js';
|
|
4
4
|
import { plumeShaders } from './plumeMaterial.js';
|
|
5
5
|
import { buildPlumeGeometry } from './plumeGeometry.js';
|
|
6
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.js';
|
|
6
7
|
/* The shared count, so both backends step a plume's vertices the same way. */
|
|
7
8
|
const VERTS_PER_PLUME = PLUME_VERTS;
|
|
8
9
|
export class PlumeRenderer {
|
|
@@ -123,9 +124,9 @@ export class PlumeRenderer {
|
|
|
123
124
|
bindAtmosphere(gl, u, atmosphere, atmosphereCameraY, underwaterEnabled);
|
|
124
125
|
gl.enable(gl.BLEND);
|
|
125
126
|
if (this.blend === 'additive')
|
|
126
|
-
|
|
127
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
127
128
|
else
|
|
128
|
-
|
|
129
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
129
130
|
gl.depthMask(false);
|
|
130
131
|
gl.disable(gl.CULL_FACE);
|
|
131
132
|
gl.bindVertexArray(this.vao);
|
|
@@ -14,8 +14,14 @@
|
|
|
14
14
|
* `aces` adds filmic tone mapping before that, rolling highlights off instead of
|
|
15
15
|
* clipping them flat. Together they are what three.js does by default, which is why a
|
|
16
16
|
* world ported from it looks wrong until this exists.
|
|
17
|
+
*
|
|
18
|
+
* `shoulder` keeps sRGB's midtones exactly and eases only the highlights: past 0.8 the brightest
|
|
19
|
+
* channel rolls toward 1 and the other two are scaled with it, so an overbright red spark stays
|
|
20
|
+
* red where `srgb` clips each channel and turns its core pink and wide. It is the curve a ported
|
|
21
|
+
* game's own resolve applied — for a port that has to match one — and it has none of ACES's toe or
|
|
22
|
+
* desaturation. See `HIGHLIGHT_SHOULDER_GLSL` for the arithmetic.
|
|
17
23
|
*/
|
|
18
|
-
export type OutputTransform = 'none' | 'srgb' | 'aces';
|
|
24
|
+
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder';
|
|
19
25
|
/**
|
|
20
26
|
* The three settings whose accepted values are a closed set, written as one.
|
|
21
27
|
*
|
|
@@ -60,7 +66,8 @@ export interface RenderQuality {
|
|
|
60
66
|
* chooses is a look rather than a standard — three.js uses 1/0.6 and says so. This
|
|
61
67
|
* engine holds no opinion and takes the number from whoever is authoring the world, so
|
|
62
68
|
* that matching another renderer, or simply preferring a different grade, is a setting
|
|
63
|
-
* rather than a patch.
|
|
69
|
+
* rather than a patch. Scales into `aces` and into `shoulder`, the two curves; ignored by
|
|
70
|
+
* `none` and `srgb`, which have none.
|
|
64
71
|
*/
|
|
65
72
|
readonly outputExposure: number;
|
|
66
73
|
/**
|
|
@@ -185,12 +192,13 @@ export interface RenderQuality {
|
|
|
185
192
|
* existed: a pane casts nothing at all.
|
|
186
193
|
*
|
|
187
194
|
* Nothing is allocated until a glass caster first reaches a shadow, so a scene with no glass pays
|
|
188
|
-
* no memory and no pass whatever this says. **
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* at 720p on WebGPU, 0.42 ms on WebGL2 and 1.2 ms at 4K
|
|
192
|
-
*
|
|
193
|
-
*
|
|
195
|
+
* no memory and no pass whatever this says. **Nor any shader, since 4.8.2**: the lit shader is
|
|
196
|
+
* built without the glass lookup and rebuilt with it when the first glass caster reaches a shadow.
|
|
197
|
+
* A lookup that finds no glass still cost the lit pass its registers — measured on the courtyard at
|
|
198
|
+
* night, 0.27 ms at 720p on WebGPU, 0.42 ms on WebGL2 and 1.2 ms at 4K — and from 4.5.0 to 4.8.1
|
|
199
|
+
* every scene paid it, glass or none: about 30% of the lit shader's instructions, which on a phone
|
|
200
|
+
* was the lit pass. `'off'` never rebuilds. What would make `'full'` wrong is a phone budget with a
|
|
201
|
+
* lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
194
202
|
*/
|
|
195
203
|
readonly glassShadows: GlassShadows;
|
|
196
204
|
/** Horizontal world-space reach over which a directional shadow dissolves. */
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a blended draw into the scene does to its alpha, which holds how much of each pixel is still
|
|
3
|
+
* the opaque surface — the share ambient occlusion is allowed to darken.
|
|
4
|
+
*
|
|
5
|
+
* **Occlusion is applied in the composite, from the depth the opaque world left**, after every
|
|
6
|
+
* translucent draw has landed, and a particle or a pane writes no depth: so a puff of smoke in front
|
|
7
|
+
* of a wall was darkened by the corner behind it. Reported from a game as walls and two figures
|
|
8
|
+
* standing behind a cloud showing through it as outlines, and nothing at all with occlusion off.
|
|
9
|
+
*
|
|
10
|
+
* So the scene's alpha is a running share. The frame clears it to 1 and an opaque draw writes 1,
|
|
11
|
+
* which is the whole of the surface; then each draw blended in front of it says which of three
|
|
12
|
+
* things it is, and the composite darkens `share` of the pixel and leaves the rest:
|
|
13
|
+
*
|
|
14
|
+
* - **It covers**: glass, smoke, water, a line, text in the world, a panel. The share falls by what
|
|
15
|
+
* the draw lets through, `share * (1 - a)`.
|
|
16
|
+
* - **It transmits**: the medium, the order-independent composite. Its alpha already *is* what gets
|
|
17
|
+
* through, `share * a`.
|
|
18
|
+
* - **It keeps**: light added to the surface or a layer of the surface itself — caustics, a light
|
|
19
|
+
* volume, a bolt, a wet film, a decal, a reflection. The share is unchanged.
|
|
20
|
+
*
|
|
21
|
+
* **What it gives up** is added light in front of an occluded corner: a glow or a spark adds to the
|
|
22
|
+
* pixel and keeps the share, so the composite darkens what it added along with the surface — which
|
|
23
|
+
* is how every blended draw behaved before this, and one multiply cannot tell the two apart. **What
|
|
24
|
+
* would make it wrong** is an opaque pass writing an alpha below 1: its surface would then take less
|
|
25
|
+
* occlusion than it should, which is the safe direction, and is the lit shader's own case at a
|
|
26
|
+
* cutout's edge or under a per-vertex alpha lane.
|
|
27
|
+
*
|
|
28
|
+
* **It survives the temporal passes.** A temporal resolve passes the frame's alpha through, since
|
|
29
|
+
* it is a fact about this frame and takes no history; a reconstruction reads it from the scene it
|
|
30
|
+
* rebuilt into the picture it shows, and the blended draws that land after it cover that picture.
|
|
31
|
+
*
|
|
32
|
+
* **One pipeline does not take part**: the GPU-driven pass blends its own surfaces into a target
|
|
33
|
+
* whose alpha is the mask its blit reads to tell a drawn pixel from an undrawn one, and that blit
|
|
34
|
+
* writes the share back to 1. Its panes still take the occlusion of what they cover. What would
|
|
35
|
+
* close it is a channel for that mask other than alpha.
|
|
36
|
+
*
|
|
37
|
+
* A contributed pass drawing something blended into the scene states the same thing for its own
|
|
38
|
+
* pipeline with `SCENE_ALPHA_COVERS`, `SCENE_ALPHA_TRANSMITS` or `SCENE_ALPHA_KEEPS`.
|
|
39
|
+
*/
|
|
40
|
+
/** A blended draw that covers the surface by its alpha: `share * (1 - a)`. */
|
|
41
|
+
export declare const SCENE_ALPHA_COVERS: GPUBlendComponent;
|
|
42
|
+
/** A draw whose alpha is what gets through it, a transmittance or a revealage: `share * a`. */
|
|
43
|
+
export declare const SCENE_ALPHA_TRANSMITS: GPUBlendComponent;
|
|
44
|
+
/** Light added to the surface, or a layer of it: the share unchanged. */
|
|
45
|
+
export declare const SCENE_ALPHA_KEEPS: GPUBlendComponent;
|
|
46
|
+
/**
|
|
47
|
+
* The same three on WebGL2, as the alpha half of `blendFuncSeparate` beside the colour half a
|
|
48
|
+
* caller already had. `src` and `dst` are the colour factors.
|
|
49
|
+
*/
|
|
50
|
+
export declare function blendCovering(gl: WebGL2RenderingContext, src: number, dst: number): void;
|
|
51
|
+
/** `SCENE_ALPHA_TRANSMITS` on WebGL2, beside the colour factors `src` and `dst`. */
|
|
52
|
+
export declare function blendTransmitting(gl: WebGL2RenderingContext, src: number, dst: number): void;
|
|
53
|
+
/** `SCENE_ALPHA_KEEPS` on WebGL2, beside the colour factors `src` and `dst`. */
|
|
54
|
+
export declare function blendKeeping(gl: WebGL2RenderingContext, src: number, dst: number): void;
|