@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
|
@@ -13,19 +13,30 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { SurfaceMaterial } from './surfaceTexture.ts';
|
|
15
15
|
|
|
16
|
-
/** A cutout caster's texture, its cutoff and its UV scale. */
|
|
16
|
+
/** A cutout caster's texture, its cutoff and its UV scale and offset. */
|
|
17
17
|
export interface CutoutCaster<Texture> {
|
|
18
18
|
readonly albedo: Texture;
|
|
19
19
|
readonly cutoff: number;
|
|
20
20
|
readonly u: number;
|
|
21
21
|
readonly v: number;
|
|
22
|
+
readonly uOffset: number;
|
|
23
|
+
readonly vOffset: number;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
const answer: {
|
|
26
|
+
const answer: {
|
|
27
|
+
albedo: unknown;
|
|
28
|
+
cutoff: number;
|
|
29
|
+
u: number;
|
|
30
|
+
v: number;
|
|
31
|
+
uOffset: number;
|
|
32
|
+
vOffset: number;
|
|
33
|
+
} = {
|
|
25
34
|
albedo: null,
|
|
26
35
|
cutoff: 0,
|
|
27
36
|
u: 1,
|
|
28
37
|
v: 1,
|
|
38
|
+
uOffset: 0,
|
|
39
|
+
vOffset: 0,
|
|
29
40
|
};
|
|
30
41
|
|
|
31
42
|
/** The cutout `material` casts, or null for an ordinary caster. */
|
|
@@ -40,5 +51,7 @@ export function cutoutOf<Texture>(
|
|
|
40
51
|
answer.cutoff = cutoff;
|
|
41
52
|
answer.u = material.uScale ?? 1;
|
|
42
53
|
answer.v = material.vScale ?? 1;
|
|
54
|
+
answer.uOffset = material.uOffset ?? 0;
|
|
55
|
+
answer.vOffset = material.vOffset ?? 0;
|
|
43
56
|
return answer as CutoutCaster<Texture>;
|
|
44
57
|
}
|
package/src/render/decalPass.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.ts';
|
|
|
7
7
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
8
8
|
import { DECAL_PROJECT_FRAG } from './shaders/decalProject.ts';
|
|
9
9
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
|
|
10
|
+
import { blendKeeping } from './sceneCoverage.ts';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Drawn decals on WebGL2: one scissored triangle per mark, multiplied into the finished scene.
|
|
@@ -66,7 +67,7 @@ export class DecalPass {
|
|
|
66
67
|
gl.depthMask(false);
|
|
67
68
|
gl.disable(gl.CULL_FACE);
|
|
68
69
|
gl.enable(gl.BLEND);
|
|
69
|
-
|
|
70
|
+
blendKeeping(gl, gl.ZERO, gl.SRC_COLOR);
|
|
70
71
|
gl.enable(gl.SCISSOR_TEST);
|
|
71
72
|
|
|
72
73
|
/* Unit 0, chosen rather than inherited: `bindTexture` binds to whichever unit is active, and
|
|
@@ -7,6 +7,7 @@ import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.ts';
|
|
|
7
7
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
8
8
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
|
|
9
9
|
import { MEDIUM_FRAG, MEDIUM_UPSAMPLE_FRAG } from './shaders/globalMedium.ts';
|
|
10
|
+
import { blendCovering, blendTransmitting } from './sceneCoverage.ts';
|
|
10
11
|
|
|
11
12
|
/** What the march needs about the light, which is the sun and the sky and nothing else. */
|
|
12
13
|
export interface MediumLight {
|
|
@@ -270,7 +271,7 @@ export class GlobalMediumPass {
|
|
|
270
271
|
gl.depthMask(false);
|
|
271
272
|
gl.disable(gl.CULL_FACE);
|
|
272
273
|
gl.enable(gl.BLEND);
|
|
273
|
-
|
|
274
|
+
blendTransmitting(gl, gl.ONE, gl.SRC_ALPHA);
|
|
274
275
|
|
|
275
276
|
gl.useProgram(this.upsample);
|
|
276
277
|
gl.bindVertexArray(this.vao);
|
|
@@ -293,7 +294,7 @@ export class GlobalMediumPass {
|
|
|
293
294
|
gl.activeTexture(gl.TEXTURE0);
|
|
294
295
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
295
296
|
/* Back to the blend every other pass in this renderer assumes it inherits. */
|
|
296
|
-
|
|
297
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
297
298
|
gl.disable(gl.BLEND);
|
|
298
299
|
gl.depthMask(true);
|
|
299
300
|
gl.enable(gl.DEPTH_TEST);
|
package/src/render/lineBatch.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
2
2
|
import { LINE_FRAG, LINE_VERT } from './shaders/line.ts';
|
|
3
3
|
import { buildSegmentQuads, expandLineSegments } from './segmentQuads.ts';
|
|
4
4
|
import type { LineSegments } from './linePoints.ts';
|
|
5
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One draw call for a polyline with a real width — an audio waveform, a trail, a plotted
|
|
@@ -102,7 +103,8 @@ export class LineBatch {
|
|
|
102
103
|
drawTo(gl: WebGL2RenderingContext, segmentCount: number, additive = false): void {
|
|
103
104
|
if (segmentCount === 0) return;
|
|
104
105
|
gl.enable(gl.BLEND);
|
|
105
|
-
|
|
106
|
+
if (additive) blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
107
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
106
108
|
gl.depthMask(false);
|
|
107
109
|
// The quad billboards toward the view direction the same way a bolt's does — see
|
|
108
110
|
// `line.ts`'s own `cross(dir, view)` — so its winding can flip with the camera exactly
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
const told = new WeakSet<object>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether a material binds a map that is a picture: albedo, normal or emissive.
|
|
20
|
+
*
|
|
21
|
+
* **Not ORM**, because a one-colour ORM map is how a draw sets a constant roughness and metalness,
|
|
22
|
+
* and read at one texel it is exactly that constant — the materials example draws its spheres so.
|
|
23
|
+
* The three counted here are pictures nobody binds to be read at one point.
|
|
24
|
+
*/
|
|
25
|
+
export function materialHasMaps(material: SurfaceMaterial<unknown> | null): boolean {
|
|
26
|
+
if (material === null) return false;
|
|
27
|
+
return (
|
|
28
|
+
(material.albedo ?? null) !== null ||
|
|
29
|
+
(material.normal ?? null) !== null ||
|
|
30
|
+
(material.emissive ?? null) !== null
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Warn the first time `mesh`, which has no coordinates, is drawn with a picture map bound. */
|
|
35
|
+
export function noteMapsWithoutUvs(mesh: object, hasUvs: boolean, materialHasMaps: boolean): void {
|
|
36
|
+
if (hasUvs || !materialHasMaps || told.has(mesh)) return;
|
|
37
|
+
told.add(mesh);
|
|
38
|
+
console.warn(
|
|
39
|
+
'[driftengine] a mesh with no texture coordinates is drawn with an albedo, normal or emissive ' +
|
|
40
|
+
'map, which it cannot read: every map samples one texel. Give the mesh `uvs` — ' +
|
|
41
|
+
'`MeshBuilder.build({ planarUvs: true })` writes them — or draw it without the maps.',
|
|
42
|
+
);
|
|
43
|
+
}
|
package/src/render/mesh.ts
CHANGED
|
@@ -136,6 +136,8 @@ export class Mesh {
|
|
|
136
136
|
readonly bounds: Bounds = createBounds();
|
|
137
137
|
/** Whether this mesh carries a rig, which decides which flat program draws it. */
|
|
138
138
|
readonly isSkinned: boolean;
|
|
139
|
+
/** Whether it has texture coordinates, without which a material's maps read one texel. */
|
|
140
|
+
readonly hasUvs: boolean;
|
|
139
141
|
|
|
140
142
|
/**
|
|
141
143
|
* This mesh's morph deltas, or null for geometry that does not deform.
|
|
@@ -190,6 +192,7 @@ export class Mesh {
|
|
|
190
192
|
constructor(gl: WebGL2RenderingContext, data: MeshData, dynamic = false, spread = false) {
|
|
191
193
|
validateMeshData(data);
|
|
192
194
|
this.spread = spread;
|
|
195
|
+
this.hasUvs = data.uvs !== undefined;
|
|
193
196
|
boundsOfPositions(data.positions, this.bounds);
|
|
194
197
|
this.positionFloats = data.positions.length;
|
|
195
198
|
const vao = gl.createVertexArray();
|
package/src/render/oitPass.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
2
2
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
|
|
3
3
|
import { OIT_RESOLVE_FRAG } from './shaders/oitResolve.ts';
|
|
4
|
+
import { blendTransmitting } from './sceneCoverage.ts';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Order-independent transparency on WebGL2: two targets, two passes over the same geometry, and a
|
|
@@ -179,7 +180,7 @@ export class OitPass {
|
|
|
179
180
|
gl.depthMask(false);
|
|
180
181
|
gl.disable(gl.CULL_FACE);
|
|
181
182
|
gl.enable(gl.BLEND);
|
|
182
|
-
|
|
183
|
+
blendTransmitting(gl, gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);
|
|
183
184
|
|
|
184
185
|
gl.useProgram(this.program);
|
|
185
186
|
gl.activeTexture(gl.TEXTURE0);
|
|
@@ -2,6 +2,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
2
2
|
import { particleShaders, type ParticleMaterial } from './particleMaterial.ts';
|
|
3
3
|
import { PARTICLE_BLADES, PARTICLE_INDICES, PARTICLE_VERTS } from './shaders/particle.ts';
|
|
4
4
|
import type { ParticleInstances } from './particlePool.ts';
|
|
5
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One draw call for a live particle pool: one camera-facing quad per particle, or a
|
|
@@ -239,8 +240,8 @@ export class ParticleBatch {
|
|
|
239
240
|
drawTo(gl: WebGL2RenderingContext, count: number): void {
|
|
240
241
|
if (count === 0) return;
|
|
241
242
|
gl.enable(gl.BLEND);
|
|
242
|
-
if (this.blend === 'additive')
|
|
243
|
-
else
|
|
243
|
+
if (this.blend === 'additive') blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
244
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
244
245
|
gl.depthMask(false);
|
|
245
246
|
gl.disable(gl.CULL_FACE);
|
|
246
247
|
|
|
@@ -21,7 +21,11 @@ import type { Vec3 } from '../math/color.ts';
|
|
|
21
21
|
export interface ParticleInstances {
|
|
22
22
|
/** World position, three floats each. */
|
|
23
23
|
positions: Float32Array;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Metres from the centre to an edge, a half-width: the quad spans twice this. Then radians of
|
|
26
|
+
* roll about the particle's own axis. This said metres across until 4.8.3, and a caller who took
|
|
27
|
+
* it at its word drew every particle twice as wide as meant.
|
|
28
|
+
*/
|
|
25
29
|
sizes: Float32Array;
|
|
26
30
|
spins: Float32Array;
|
|
27
31
|
/** Linear colour, three floats each. */
|
|
@@ -72,7 +76,7 @@ export interface ParticlePoolOptions {
|
|
|
72
76
|
capacity: number;
|
|
73
77
|
/** Seconds a particle lives. */
|
|
74
78
|
lifeSec: number;
|
|
75
|
-
/** Size at birth and at death,
|
|
79
|
+
/** Size at birth and at death: metres from the centre to an edge, so the quad is twice as wide. */
|
|
76
80
|
sizeStart: number;
|
|
77
81
|
sizeEnd: number;
|
|
78
82
|
/** Colour at birth and at death. */
|
|
@@ -117,6 +121,15 @@ export class ParticlePool {
|
|
|
117
121
|
* together.
|
|
118
122
|
*/
|
|
119
123
|
private readonly seedOf: Float32Array;
|
|
124
|
+
/**
|
|
125
|
+
* Where each slot's particle sits in the drawn streams, or -1 where it sits nowhere.
|
|
126
|
+
*
|
|
127
|
+
* **What lets `emit` draw a particle the frame it is born.** The streams are compacted, so a
|
|
128
|
+
* slot's entry is wherever `update` last put it; an emit into a slot that has one overwrites
|
|
129
|
+
* that entry, and an emit into one that has none appends. Without it a new particle waited for
|
|
130
|
+
* the next `update` to be drawn at all.
|
|
131
|
+
*/
|
|
132
|
+
private readonly drawnAt: Int32Array;
|
|
120
133
|
private next = 0;
|
|
121
134
|
|
|
122
135
|
constructor(private readonly options: ParticlePoolOptions) {
|
|
@@ -138,6 +151,7 @@ export class ParticlePool {
|
|
|
138
151
|
this.spin = new Float32Array(capacity);
|
|
139
152
|
this.scale = new Float32Array(capacity).fill(1);
|
|
140
153
|
this.seedOf = new Float32Array(capacity);
|
|
154
|
+
this.drawnAt = new Int32Array(capacity).fill(-1);
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
/** How many particles are alive. */
|
|
@@ -184,6 +198,18 @@ export class ParticlePool {
|
|
|
184
198
|
this.spin[i] = (hash - Math.floor(hash)) * Math.PI * 2;
|
|
185
199
|
this.scale[i] = sizeScale;
|
|
186
200
|
this.seedOf[i] = seed;
|
|
201
|
+
/*
|
|
202
|
+
* Drawn now, at birth, rather than at the next `update`: into the entry this slot already
|
|
203
|
+
* has when it is taking over a particle that was drawn, and onto the end otherwise.
|
|
204
|
+
*/
|
|
205
|
+
const held = this.drawnAt[i] as number;
|
|
206
|
+
const at = held >= 0 ? held : this.particles.count;
|
|
207
|
+
this.writeDrawn(i, at, 0, x, y, z, vx, vy, vz);
|
|
208
|
+
if (held < 0) {
|
|
209
|
+
this.drawnAt[i] = at;
|
|
210
|
+
this.instances.count = at + 1;
|
|
211
|
+
this.particles.count = at + 1;
|
|
212
|
+
}
|
|
187
213
|
}
|
|
188
214
|
|
|
189
215
|
/**
|
|
@@ -198,11 +224,15 @@ export class ParticlePool {
|
|
|
198
224
|
let count = 0;
|
|
199
225
|
for (let i = 0; i < this.age.length; i++) {
|
|
200
226
|
const age = this.age[i] as number;
|
|
201
|
-
if (age === Infinity)
|
|
227
|
+
if (age === Infinity) {
|
|
228
|
+
this.drawnAt[i] = -1;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
202
231
|
const life = this.life[i] as number;
|
|
203
232
|
const next = age + dt;
|
|
204
233
|
if (next >= life) {
|
|
205
234
|
this.age[i] = Infinity;
|
|
235
|
+
this.drawnAt[i] = -1;
|
|
206
236
|
continue;
|
|
207
237
|
}
|
|
208
238
|
this.age[i] = next;
|
|
@@ -222,68 +252,89 @@ export class ParticlePool {
|
|
|
222
252
|
this.y[i] = py;
|
|
223
253
|
this.z[i] = pz;
|
|
224
254
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const r =
|
|
228
|
-
(o.colorStart[0] as number) + ((o.colorEnd[0] as number) - (o.colorStart[0] as number)) * t;
|
|
229
|
-
const g =
|
|
230
|
-
(o.colorStart[1] as number) + ((o.colorEnd[1] as number) - (o.colorStart[1] as number)) * t;
|
|
231
|
-
const b =
|
|
232
|
-
(o.colorStart[2] as number) + ((o.colorEnd[2] as number) - (o.colorStart[2] as number)) * t;
|
|
233
|
-
/*
|
|
234
|
-
* Colour carries the fade rather than an alpha channel: the instanced
|
|
235
|
-
* path multiplies a tint into the base mesh and has no per-instance
|
|
236
|
-
* opacity, and for an additive puff darkening to nothing *is* fading out.
|
|
237
|
-
* It also means the effect needs no blend-state change.
|
|
238
|
-
*/
|
|
239
|
-
const fade = 1 - t * t;
|
|
240
|
-
writeInstance(
|
|
241
|
-
this.instances,
|
|
242
|
-
count,
|
|
243
|
-
px,
|
|
244
|
-
py,
|
|
245
|
-
pz,
|
|
246
|
-
size,
|
|
247
|
-
this.spin[i] as number,
|
|
248
|
-
r * fade,
|
|
249
|
-
g * fade,
|
|
250
|
-
b * fade,
|
|
251
|
-
0,
|
|
252
|
-
0,
|
|
253
|
-
0,
|
|
254
|
-
);
|
|
255
|
-
/*
|
|
256
|
-
* The same particle for a material that has a real opacity channel. Colour
|
|
257
|
-
* is written *unfaded* here, because dimming it is the scatter path's
|
|
258
|
-
* workaround and doing both would fade twice — a puff that vanished at half
|
|
259
|
-
* its life while still occluding what was behind it.
|
|
260
|
-
*/
|
|
261
|
-
const p = this.particles;
|
|
262
|
-
const alphaStart = o.alphaStart ?? 1;
|
|
263
|
-
const alphaEnd = o.alphaEnd ?? 0;
|
|
264
|
-
p.positions[count * 3] = px;
|
|
265
|
-
p.positions[count * 3 + 1] = py;
|
|
266
|
-
p.positions[count * 3 + 2] = pz;
|
|
267
|
-
p.sizes[count] = size;
|
|
268
|
-
p.spins[count] = this.spin[i] as number;
|
|
269
|
-
p.colors[count * 3] = r;
|
|
270
|
-
p.colors[count * 3 + 1] = g;
|
|
271
|
-
p.colors[count * 3 + 2] = b;
|
|
272
|
-
p.alphas[count] = alphaStart + (alphaEnd - alphaStart) * t;
|
|
273
|
-
p.ages[count] = t;
|
|
274
|
-
p.seeds[count] = this.seedOf[i] as number;
|
|
275
|
-
p.velocities[count * 3] = vx;
|
|
276
|
-
p.velocities[count * 3 + 1] = vy;
|
|
277
|
-
p.velocities[count * 3 + 2] = vz;
|
|
255
|
+
this.drawnAt[i] = count;
|
|
256
|
+
this.writeDrawn(i, count, next / life, px, py, pz, vx, vy, vz);
|
|
278
257
|
count++;
|
|
279
258
|
}
|
|
280
259
|
this.instances.count = count;
|
|
281
260
|
this.particles.count = count;
|
|
282
261
|
}
|
|
283
262
|
|
|
263
|
+
/**
|
|
264
|
+
* One particle's entry in both drawn streams, at `t` of its life: what `update` writes for every
|
|
265
|
+
* live particle and `emit` for a new one, so the two cannot disagree about what a particle looks
|
|
266
|
+
* like.
|
|
267
|
+
*/
|
|
268
|
+
private writeDrawn(
|
|
269
|
+
i: number,
|
|
270
|
+
count: number,
|
|
271
|
+
t: number,
|
|
272
|
+
px: number,
|
|
273
|
+
py: number,
|
|
274
|
+
pz: number,
|
|
275
|
+
vx: number,
|
|
276
|
+
vy: number,
|
|
277
|
+
vz: number,
|
|
278
|
+
): void {
|
|
279
|
+
const o = this.options;
|
|
280
|
+
const size = (o.sizeStart + (o.sizeEnd - o.sizeStart) * t) * (this.scale[i] as number);
|
|
281
|
+
const r =
|
|
282
|
+
(o.colorStart[0] as number) + ((o.colorEnd[0] as number) - (o.colorStart[0] as number)) * t;
|
|
283
|
+
const g =
|
|
284
|
+
(o.colorStart[1] as number) + ((o.colorEnd[1] as number) - (o.colorStart[1] as number)) * t;
|
|
285
|
+
const b =
|
|
286
|
+
(o.colorStart[2] as number) + ((o.colorEnd[2] as number) - (o.colorStart[2] as number)) * t;
|
|
287
|
+
/*
|
|
288
|
+
* Colour carries the fade rather than an alpha channel: the instanced
|
|
289
|
+
* path multiplies a tint into the base mesh and has no per-instance
|
|
290
|
+
* opacity, and for an additive puff darkening to nothing *is* fading out.
|
|
291
|
+
* It also means the effect needs no blend-state change.
|
|
292
|
+
*/
|
|
293
|
+
const fade = 1 - t * t;
|
|
294
|
+
writeInstance(
|
|
295
|
+
this.instances,
|
|
296
|
+
count,
|
|
297
|
+
px,
|
|
298
|
+
py,
|
|
299
|
+
pz,
|
|
300
|
+
size,
|
|
301
|
+
this.spin[i] as number,
|
|
302
|
+
r * fade,
|
|
303
|
+
g * fade,
|
|
304
|
+
b * fade,
|
|
305
|
+
0,
|
|
306
|
+
0,
|
|
307
|
+
0,
|
|
308
|
+
);
|
|
309
|
+
/*
|
|
310
|
+
* The same particle for a material that has a real opacity channel. Colour
|
|
311
|
+
* is written *unfaded* here, because dimming it is the scatter path's
|
|
312
|
+
* workaround and doing both would fade twice — a puff that vanished at half
|
|
313
|
+
* its life while still occluding what was behind it.
|
|
314
|
+
*/
|
|
315
|
+
const p = this.particles;
|
|
316
|
+
const alphaStart = o.alphaStart ?? 1;
|
|
317
|
+
const alphaEnd = o.alphaEnd ?? 0;
|
|
318
|
+
p.positions[count * 3] = px;
|
|
319
|
+
p.positions[count * 3 + 1] = py;
|
|
320
|
+
p.positions[count * 3 + 2] = pz;
|
|
321
|
+
p.sizes[count] = size;
|
|
322
|
+
p.spins[count] = this.spin[i] as number;
|
|
323
|
+
p.colors[count * 3] = r;
|
|
324
|
+
p.colors[count * 3 + 1] = g;
|
|
325
|
+
p.colors[count * 3 + 2] = b;
|
|
326
|
+
p.alphas[count] = alphaStart + (alphaEnd - alphaStart) * t;
|
|
327
|
+
p.ages[count] = t;
|
|
328
|
+
p.seeds[count] = this.seedOf[i] as number;
|
|
329
|
+
p.velocities[count * 3] = vx;
|
|
330
|
+
p.velocities[count * 3 + 1] = vy;
|
|
331
|
+
p.velocities[count * 3 + 2] = vz;
|
|
332
|
+
}
|
|
333
|
+
|
|
284
334
|
/** Drop everything, for a respawn or a scene change. */
|
|
285
335
|
clear(): void {
|
|
286
336
|
this.age.fill(Infinity);
|
|
337
|
+
this.drawnAt.fill(-1);
|
|
287
338
|
this.instances.count = 0;
|
|
288
339
|
this.particles.count = 0;
|
|
289
340
|
this.next = 0;
|
package/src/render/pass.ts
CHANGED
|
@@ -125,6 +125,18 @@ export type PassContext = {
|
|
|
125
125
|
readonly pass: GPURenderPassEncoder;
|
|
126
126
|
/** The frame's jitter, the same array `PrepareContext.jitter` held. */
|
|
127
127
|
readonly jitter: Float32Array;
|
|
128
|
+
/**
|
|
129
|
+
* The colour format, depth format and sample count of the pass this draw lands in, which a
|
|
130
|
+
* pipeline set on it must match exactly.
|
|
131
|
+
*
|
|
132
|
+
* **Not always the device's.** `PassDevice` says what the frame is; a pass drawn after
|
|
133
|
+
* `endFrame` lands on the canvas, at the canvas's format and one sample, where the frame is
|
|
134
|
+
* the scene target at the scene's samples. A pass that is drawn in both places keeps a
|
|
135
|
+
* pipeline for each and chooses here; one built only from the device is refused by the other.
|
|
136
|
+
*/
|
|
137
|
+
readonly format: GPUTextureFormat;
|
|
138
|
+
readonly depthFormat: GPUTextureFormat;
|
|
139
|
+
readonly samples: number;
|
|
128
140
|
}
|
|
129
141
|
);
|
|
130
142
|
|
|
@@ -6,6 +6,7 @@ import { plumeShaders, type PlumeMaterial } from './plumeMaterial.ts';
|
|
|
6
6
|
import { buildPlumeGeometry } from './plumeGeometry.ts';
|
|
7
7
|
import type { Atmosphere } from './atmosphere.ts';
|
|
8
8
|
import type { Vec3 } from '../math/color.ts';
|
|
9
|
+
import { blendCovering, blendKeeping } from './sceneCoverage.ts';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Batched camera-facing quads for volumetric effects — flames, smoke, steam,
|
|
@@ -202,8 +203,8 @@ export class PlumeRenderer {
|
|
|
202
203
|
bindAtmosphere(gl, u, atmosphere, atmosphereCameraY, underwaterEnabled);
|
|
203
204
|
|
|
204
205
|
gl.enable(gl.BLEND);
|
|
205
|
-
if (this.blend === 'additive')
|
|
206
|
-
else
|
|
206
|
+
if (this.blend === 'additive') blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
207
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
207
208
|
gl.depthMask(false);
|
|
208
209
|
gl.disable(gl.CULL_FACE);
|
|
209
210
|
|
|
@@ -20,8 +20,14 @@ import { PREFILTER_SAMPLE_COUNTS } from './prefilterEnvMap.ts';
|
|
|
20
20
|
* `aces` adds filmic tone mapping before that, rolling highlights off instead of
|
|
21
21
|
* clipping them flat. Together they are what three.js does by default, which is why a
|
|
22
22
|
* world ported from it looks wrong until this exists.
|
|
23
|
+
*
|
|
24
|
+
* `shoulder` keeps sRGB's midtones exactly and eases only the highlights: past 0.8 the brightest
|
|
25
|
+
* channel rolls toward 1 and the other two are scaled with it, so an overbright red spark stays
|
|
26
|
+
* red where `srgb` clips each channel and turns its core pink and wide. It is the curve a ported
|
|
27
|
+
* game's own resolve applied — for a port that has to match one — and it has none of ACES's toe or
|
|
28
|
+
* desaturation. See `HIGHLIGHT_SHOULDER_GLSL` for the arithmetic.
|
|
23
29
|
*/
|
|
24
|
-
export type OutputTransform = 'none' | 'srgb' | 'aces';
|
|
30
|
+
export type OutputTransform = 'none' | 'srgb' | 'aces' | 'shoulder';
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
33
|
* The three settings whose accepted values are a closed set, written as one.
|
|
@@ -69,7 +75,8 @@ export interface RenderQuality {
|
|
|
69
75
|
* chooses is a look rather than a standard — three.js uses 1/0.6 and says so. This
|
|
70
76
|
* engine holds no opinion and takes the number from whoever is authoring the world, so
|
|
71
77
|
* that matching another renderer, or simply preferring a different grade, is a setting
|
|
72
|
-
* rather than a patch.
|
|
78
|
+
* rather than a patch. Scales into `aces` and into `shoulder`, the two curves; ignored by
|
|
79
|
+
* `none` and `srgb`, which have none.
|
|
73
80
|
*/
|
|
74
81
|
readonly outputExposure: number;
|
|
75
82
|
/**
|
|
@@ -194,12 +201,13 @@ export interface RenderQuality {
|
|
|
194
201
|
* existed: a pane casts nothing at all.
|
|
195
202
|
*
|
|
196
203
|
* Nothing is allocated until a glass caster first reaches a shadow, so a scene with no glass pays
|
|
197
|
-
* no memory and no pass whatever this says. **
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* at 720p on WebGPU, 0.42 ms on WebGL2 and 1.2 ms at 4K
|
|
201
|
-
*
|
|
202
|
-
*
|
|
204
|
+
* no memory and no pass whatever this says. **Nor any shader, since 4.8.2**: the lit shader is
|
|
205
|
+
* built without the glass lookup and rebuilt with it when the first glass caster reaches a shadow.
|
|
206
|
+
* A lookup that finds no glass still cost the lit pass its registers — measured on the courtyard at
|
|
207
|
+
* 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
|
|
208
|
+
* every scene paid it, glass or none: about 30% of the lit shader's instructions, which on a phone
|
|
209
|
+
* was the lit pass. `'off'` never rebuilds. What would make `'full'` wrong is a phone budget with a
|
|
210
|
+
* lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
|
|
203
211
|
*/
|
|
204
212
|
readonly glassShadows: GlassShadows;
|
|
205
213
|
/** Horizontal world-space reach over which a directional shadow dissolves. */
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
|
|
41
|
+
/** A blended draw that covers the surface by its alpha: `share * (1 - a)`. */
|
|
42
|
+
export const SCENE_ALPHA_COVERS: GPUBlendComponent = {
|
|
43
|
+
srcFactor: 'zero',
|
|
44
|
+
dstFactor: 'one-minus-src-alpha',
|
|
45
|
+
operation: 'add',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** A draw whose alpha is what gets through it, a transmittance or a revealage: `share * a`. */
|
|
49
|
+
export const SCENE_ALPHA_TRANSMITS: GPUBlendComponent = {
|
|
50
|
+
srcFactor: 'zero',
|
|
51
|
+
dstFactor: 'src-alpha',
|
|
52
|
+
operation: 'add',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Light added to the surface, or a layer of it: the share unchanged. */
|
|
56
|
+
export const SCENE_ALPHA_KEEPS: GPUBlendComponent = {
|
|
57
|
+
srcFactor: 'zero',
|
|
58
|
+
dstFactor: 'one',
|
|
59
|
+
operation: 'add',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The same three on WebGL2, as the alpha half of `blendFuncSeparate` beside the colour half a
|
|
64
|
+
* caller already had. `src` and `dst` are the colour factors.
|
|
65
|
+
*/
|
|
66
|
+
export function blendCovering(gl: WebGL2RenderingContext, src: number, dst: number): void {
|
|
67
|
+
gl.blendFuncSeparate(src, dst, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `SCENE_ALPHA_TRANSMITS` on WebGL2, beside the colour factors `src` and `dst`. */
|
|
71
|
+
export function blendTransmitting(gl: WebGL2RenderingContext, src: number, dst: number): void {
|
|
72
|
+
gl.blendFuncSeparate(src, dst, gl.ZERO, gl.SRC_ALPHA);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `SCENE_ALPHA_KEEPS` on WebGL2, beside the colour factors `src` and `dst`. */
|
|
76
|
+
export function blendKeeping(gl: WebGL2RenderingContext, src: number, dst: number): void {
|
|
77
|
+
gl.blendFuncSeparate(src, dst, gl.ZERO, gl.ONE);
|
|
78
|
+
}
|
|
@@ -7,6 +7,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
7
7
|
import { SDF_TEXT_FRAG, SDF_TEXT_VERT } from './shaders/sdfText.ts';
|
|
8
8
|
import type { SurfaceTexture } from './surfaceTexture.ts';
|
|
9
9
|
import { SDF_TEXT_TEXTURE_UNIT } from './lightBudget.ts';
|
|
10
|
+
import { blendCovering } from './sceneCoverage.ts';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Text from a signed distance field, on WebGL2.
|
|
@@ -144,7 +145,7 @@ export class SdfTextRenderer {
|
|
|
144
145
|
*/
|
|
145
146
|
gl.depthMask(false);
|
|
146
147
|
gl.enable(gl.BLEND);
|
|
147
|
-
|
|
148
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
148
149
|
|
|
149
150
|
this.atlas.bind(gl, SDF_TEXT_TEXTURE_UNIT);
|
|
150
151
|
|
|
@@ -60,12 +60,13 @@ ${CHANNEL_BEND}
|
|
|
60
60
|
|
|
61
61
|
#if CUTOUT
|
|
62
62
|
/*
|
|
63
|
-
* **What a cutout caster adds, and where.** The UV at location 5, where every mesh carries it;
|
|
63
|
+
* **What a cutout caster adds, and where.** The UV at location 5, where every mesh carries it; three
|
|
64
64
|
* uniforms declared after every other, so no field of the plain variant moves and a binder written
|
|
65
65
|
* against it stays right for this one; and two outputs after vLightPosition, which keeps location 0.
|
|
66
66
|
*/
|
|
67
67
|
layout(location = 5) in vec3 aUv;
|
|
68
68
|
uniform vec2 uUvScale;
|
|
69
|
+
uniform vec2 uUvOffset;
|
|
69
70
|
uniform float uAlphaCutout;
|
|
70
71
|
#endif
|
|
71
72
|
|
|
@@ -104,7 +105,7 @@ void main() {
|
|
|
104
105
|
vLightPosition = uLightViewProj * vec4(bent, world.w);
|
|
105
106
|
gl_Position = vLightPosition;
|
|
106
107
|
#if CUTOUT
|
|
107
|
-
vUv = vec3(aUv.xy * uUvScale, aUv.z);
|
|
108
|
+
vUv = vec3(aUv.xy * uUvScale + uUvOffset, aUv.z);
|
|
108
109
|
vAlphaCutout = uAlphaCutout;
|
|
109
110
|
#endif
|
|
110
111
|
#if GLASS
|