@driftengine/core 4.13.6 → 4.14.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 +51 -0
- package/README.md +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/render/backend/api.d.ts +19 -3
- package/dist/render/backend/webgl2/motionTarget.d.ts +39 -0
- package/dist/render/backend/webgl2/motionTarget.js +254 -0
- package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +13 -3
- package/dist/render/backend/webgl2/pointSkinFeedback.js +45 -12
- package/dist/render/backend/webgl2/renderer.d.ts +101 -7
- package/dist/render/backend/webgl2/renderer.js +337 -9
- package/dist/render/backend/webgpu/pointSkinPass.d.ts +16 -3
- package/dist/render/backend/webgpu/pointSkinPass.js +79 -21
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/renderer.d.ts +41 -0
- package/dist/render/backend/webgpu/renderer.js +143 -5
- package/dist/render/baseDecals.d.ts +48 -0
- package/dist/render/baseDecals.js +71 -0
- package/dist/render/emissiveFlow.d.ts +40 -0
- package/dist/render/emissiveFlow.js +51 -0
- package/dist/render/fixtureAtlas.d.ts +8 -3
- package/dist/render/fixtureAtlas.js +17 -6
- package/dist/render/globalMedium.d.ts +17 -0
- package/dist/render/globalMedium.js +14 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/iridescentOverlay.d.ts +78 -0
- package/dist/render/iridescentOverlay.js +102 -0
- package/dist/render/mesh.d.ts +24 -0
- package/dist/render/mesh.js +79 -0
- package/dist/render/pointSkin.d.ts +18 -0
- package/dist/render/pointSkin.js +50 -5
- package/dist/render/recon/mover.d.ts +3 -2
- package/dist/render/recon/mover.js +3 -2
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shaders/flat/baseDecals.d.ts +17 -0
- package/dist/render/shaders/flat/baseDecals.js +71 -0
- package/dist/render/shaders/flat/emissiveFlow.d.ts +15 -0
- package/dist/render/shaders/flat/emissiveFlow.js +45 -0
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +16 -0
- package/dist/render/shaders/flat/index.js +9 -0
- package/dist/render/shaders/flat/iridescentOverlay.d.ts +15 -0
- package/dist/render/shaders/flat/iridescentOverlay.js +65 -0
- package/dist/render/shaders/flat/layered.js +4 -4
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +12 -1
- package/dist/render/shaders/flat/mapReads.d.ts +1 -1
- package/dist/render/shaders/flat/mapReads.js +7 -3
- package/dist/render/shaders/flat/worldUv.js +6 -5
- package/dist/render/shaders/generated/flat.wgsl.d.ts +480 -16
- package/dist/render/shaders/generated/flat.wgsl.js +519 -55
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +15 -5
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +15 -5
- package/dist/render/shaders/temporalResolve.js +25 -9
- package/dist/render/surfaceTexture.d.ts +32 -0
- package/dist/render/temporalPass.d.ts +3 -1
- package/dist/render/temporalPass.js +9 -1
- package/package.json +6 -6
- package/src/index.ts +3 -0
- package/src/render/backend/api.ts +19 -2
- package/src/render/backend/webgl2/motionTarget.ts +299 -0
- package/src/render/backend/webgl2/pointSkinFeedback.ts +49 -14
- package/src/render/backend/webgl2/renderer.ts +411 -9
- package/src/render/backend/webgpu/pointSkinPass.ts +90 -23
- package/src/render/backend/webgpu/renderer.ts +163 -3
- package/src/render/baseDecals.ts +93 -0
- package/src/render/emissiveFlow.ts +66 -0
- package/src/render/fixtureAtlas.ts +26 -7
- package/src/render/globalMedium.ts +31 -0
- package/src/render/globalMediumPass.ts +3 -7
- package/src/render/iridescentOverlay.ts +177 -0
- package/src/render/mesh.ts +82 -0
- package/src/render/pointSkin.ts +64 -5
- package/src/render/recon/mover.ts +3 -2
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shaders/flat/baseDecals.ts +71 -0
- package/src/render/shaders/flat/emissiveFlow.ts +45 -0
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +25 -0
- package/src/render/shaders/flat/iridescentOverlay.ts +65 -0
- package/src/render/shaders/flat/layered.ts +4 -4
- package/src/render/shaders/flat/main.ts +12 -1
- package/src/render/shaders/flat/mapReads.ts +7 -3
- package/src/render/shaders/flat/worldUv.ts +6 -5
- package/src/render/shaders/generated/flat.wgsl.ts +519 -55
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +15 -5
- package/src/render/shaders/temporalResolve.ts +25 -9
- package/src/render/surfaceTexture.ts +32 -0
- package/src/render/temporalPass.ts +8 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* for every resource a later write in the frame overwrites. A skin posed twice in a frame draws its
|
|
9
9
|
* last pose everywhere, as a mesh rewritten twice with `updateMesh` does.
|
|
10
10
|
*/
|
|
11
|
-
import { reachOf, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
|
|
11
|
+
import { reachOf, widenReach, type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
|
|
12
12
|
import type { GpuMesh } from './buffers.ts';
|
|
13
13
|
import { POINT_SKIN_WGSL, POINT_SKIN_WORKGROUP } from './shaders/pointSkin.wgsl.ts';
|
|
14
14
|
|
|
@@ -30,12 +30,20 @@ export function createPointSkinPipeline(device: GPUDevice): GPUComputePipeline {
|
|
|
30
30
|
|
|
31
31
|
export class GpuPointSkin {
|
|
32
32
|
readonly mesh: GpuMesh;
|
|
33
|
-
|
|
33
|
+
/** Widened by each rewrite, never narrowed: see `widenReach`. */
|
|
34
|
+
reach: PointSkinReach;
|
|
34
35
|
private readonly count: number;
|
|
35
36
|
private readonly pipeline: GPUComputePipeline;
|
|
36
|
-
private
|
|
37
|
+
private joints: GPUBuffer;
|
|
38
|
+
/** Params, points with their weights, joints named, normals: in that order. */
|
|
37
39
|
private readonly buffers: readonly GPUBuffer[];
|
|
38
|
-
private
|
|
40
|
+
private group: GPUBindGroup;
|
|
41
|
+
/** A rewrite's vertices laid out as the buffers hold them, grown to the largest rewrite. */
|
|
42
|
+
private scratch = {
|
|
43
|
+
points: new Float32Array(0),
|
|
44
|
+
named: new Uint32Array(0),
|
|
45
|
+
normals: new Float32Array(0),
|
|
46
|
+
};
|
|
39
47
|
|
|
40
48
|
constructor(device: GPUDevice, pipeline: GPUComputePipeline, mesh: GpuMesh, data: PointSkinData) {
|
|
41
49
|
this.mesh = mesh;
|
|
@@ -44,15 +52,11 @@ export class GpuPointSkin {
|
|
|
44
52
|
const count = data.weights.length / 4;
|
|
45
53
|
this.count = count;
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
const named = Uint32Array.from(data.joints);
|
|
54
|
-
const normals = new Float32Array(count * 4);
|
|
55
|
-
for (let v = 0; v < count; v++) normals.set(data.normals.subarray(v * 3, v * 3 + 3), v * 4);
|
|
55
|
+
const { points, named, normals } = laidOut(data, count, {
|
|
56
|
+
points: new Float32Array(count * 16),
|
|
57
|
+
named: new Uint32Array(count * 4),
|
|
58
|
+
normals: new Float32Array(count * 4),
|
|
59
|
+
});
|
|
56
60
|
const params = new Uint32Array([count, 0, 0, 0]);
|
|
57
61
|
|
|
58
62
|
const made = (
|
|
@@ -68,22 +72,22 @@ export class GpuPointSkin {
|
|
|
68
72
|
device.queue.writeBuffer(buffer, 0, contents);
|
|
69
73
|
return buffer;
|
|
70
74
|
};
|
|
71
|
-
this.joints = device.
|
|
72
|
-
|
|
73
|
-
size: Math.max(1, this.reach.joints) * 64,
|
|
74
|
-
usage: STORAGE | COPY_DST,
|
|
75
|
-
});
|
|
76
|
-
const buffers = [
|
|
75
|
+
this.joints = jointBuffer(device, this.reach.joints);
|
|
76
|
+
this.buffers = [
|
|
77
77
|
made('pointSkin.params', params, UNIFORM),
|
|
78
78
|
made('pointSkin.points', points, STORAGE),
|
|
79
79
|
made('pointSkin.named', named, STORAGE),
|
|
80
80
|
made('pointSkin.normals', normals, STORAGE),
|
|
81
81
|
];
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
this.group = this.bind(device);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private bind(device: GPUDevice): GPUBindGroup {
|
|
86
|
+
const buffers = this.buffers;
|
|
87
|
+
const [positionsOut, normalsOut] = this.mesh.vertexBuffers as [GPUBuffer, GPUBuffer];
|
|
88
|
+
return device.createBindGroup({
|
|
85
89
|
label: 'pointSkin.pose',
|
|
86
|
-
layout: pipeline.getBindGroupLayout(0),
|
|
90
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
87
91
|
entries: [
|
|
88
92
|
{ binding: 0, resource: { buffer: buffers[0] as GPUBuffer } },
|
|
89
93
|
{ binding: 1, resource: { buffer: this.joints } },
|
|
@@ -96,6 +100,37 @@ export class GpuPointSkin {
|
|
|
96
100
|
});
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Rewrite vertices `first` onward, as many as `data` holds, in place: three queue writes at their
|
|
105
|
+
* offsets. **A queue write reaches every dispatch submitted after it**, so the renderer submits a
|
|
106
|
+
* pose already recorded before calling this, and the rewrite reaches the next pose alone, as it
|
|
107
|
+
* does on WebGL2. A joint none of the skin named before grows the joints buffer, which is made
|
|
108
|
+
* again with its bind group; the old one goes once the work already submitted is done with it.
|
|
109
|
+
*/
|
|
110
|
+
rewrite(device: GPUDevice, first: number, data: PointSkinData): void {
|
|
111
|
+
const count = data.weights.length / 4;
|
|
112
|
+
const scratch = this.scratch;
|
|
113
|
+
if (scratch.named.length < count * 4) {
|
|
114
|
+
this.scratch = {
|
|
115
|
+
points: new Float32Array(count * 16),
|
|
116
|
+
named: new Uint32Array(count * 4),
|
|
117
|
+
normals: new Float32Array(count * 4),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const { points, named, normals } = laidOut(data, count, this.scratch);
|
|
121
|
+
const buffers = this.buffers;
|
|
122
|
+
device.queue.writeBuffer(buffers[1] as GPUBuffer, first * 64, points, 0, count * 16);
|
|
123
|
+
device.queue.writeBuffer(buffers[2] as GPUBuffer, first * 16, named, 0, count * 4);
|
|
124
|
+
device.queue.writeBuffer(buffers[3] as GPUBuffer, first * 16, normals, 0, count * 4);
|
|
125
|
+
const joints = this.reach.joints;
|
|
126
|
+
this.reach = widenReach(this.reach, data);
|
|
127
|
+
if (Math.max(1, this.reach.joints) > Math.max(1, joints)) {
|
|
128
|
+
this.joints.destroy();
|
|
129
|
+
this.joints = jointBuffer(device, this.reach.joints);
|
|
130
|
+
this.group = this.bind(device);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
99
134
|
/** Write the pose's joints and record the dispatch that poses every vertex into `encoder`. */
|
|
100
135
|
pose(device: GPUDevice, encoder: GPUCommandEncoder, matrices: Float32Array): void {
|
|
101
136
|
if (this.reach.joints > 0) {
|
|
@@ -113,3 +148,35 @@ export class GpuPointSkin {
|
|
|
113
148
|
for (const buffer of this.buffers) buffer.destroy();
|
|
114
149
|
}
|
|
115
150
|
}
|
|
151
|
+
|
|
152
|
+
function jointBuffer(device: GPUDevice, joints: number): GPUBuffer {
|
|
153
|
+
return device.createBuffer({
|
|
154
|
+
label: 'pointSkin.joints',
|
|
155
|
+
size: Math.max(1, joints) * 64,
|
|
156
|
+
usage: STORAGE | COPY_DST,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* `count` vertices of `data` as the buffers hold them, into `out`'s leading entries: each influence
|
|
162
|
+
* a vec4 of its point and its weight in w, the joints as integers, each normal a vec4.
|
|
163
|
+
*/
|
|
164
|
+
function laidOut(
|
|
165
|
+
data: PointSkinData,
|
|
166
|
+
count: number,
|
|
167
|
+
out: { points: Float32Array; named: Uint32Array; normals: Float32Array },
|
|
168
|
+
): { points: Float32Array; named: Uint32Array; normals: Float32Array } {
|
|
169
|
+
for (let i = 0; i < count * 4; i++) {
|
|
170
|
+
out.points[i * 4] = data.points[i * 3] as number;
|
|
171
|
+
out.points[i * 4 + 1] = data.points[i * 3 + 1] as number;
|
|
172
|
+
out.points[i * 4 + 2] = data.points[i * 3 + 2] as number;
|
|
173
|
+
out.points[i * 4 + 3] = data.weights[i] as number;
|
|
174
|
+
out.named[i] = data.joints[i] as number;
|
|
175
|
+
}
|
|
176
|
+
for (let v = 0; v < count; v++) {
|
|
177
|
+
out.normals[v * 4] = data.normals[v * 3] as number;
|
|
178
|
+
out.normals[v * 4 + 1] = data.normals[v * 3 + 1] as number;
|
|
179
|
+
out.normals[v * 4 + 2] = data.normals[v * 3 + 2] as number;
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
@@ -125,7 +125,7 @@ import { ProbeSweeps } from '../../probeSweeps.ts';
|
|
|
125
125
|
import { ggxMaxLevelFor, irradianceLevelFor, octahedralEdgeFor } from '../../prefilterEnvMap.ts';
|
|
126
126
|
import { environmentTexels } from './environmentTexels.ts';
|
|
127
127
|
import { packIesAtlas } from '../../iesProfile.ts';
|
|
128
|
-
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
128
|
+
import { decalBandTop, fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
129
129
|
import { resolveSurfaceScene } from '../../surfaceScene.ts';
|
|
130
130
|
import { COOKIE_TILE } from '../../lightBudget.ts';
|
|
131
131
|
import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.ts';
|
|
@@ -192,6 +192,7 @@ import { GpuPointSkin, createPointSkinPipeline } from './pointSkinPass.ts';
|
|
|
192
192
|
import {
|
|
193
193
|
poseBounds,
|
|
194
194
|
validatePointSkin,
|
|
195
|
+
validatePointSkinRange,
|
|
195
196
|
validatePose,
|
|
196
197
|
type PointSkinData,
|
|
197
198
|
} from '../../pointSkin.ts';
|
|
@@ -232,6 +233,20 @@ import {
|
|
|
232
233
|
type SurfaceDetail,
|
|
233
234
|
} from '../../surfaceDetail.ts';
|
|
234
235
|
import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.ts';
|
|
236
|
+
import { EMISSIVE_FLOW_FLOATS, packEmissiveFlow } from '../../emissiveFlow.ts';
|
|
237
|
+
import {
|
|
238
|
+
BASE_DECAL_FLOATS,
|
|
239
|
+
BASE_DECAL_TILE,
|
|
240
|
+
packBaseDecals,
|
|
241
|
+
type BaseDecal,
|
|
242
|
+
} from '../../baseDecals.ts';
|
|
243
|
+
import { sourceSize } from '../../textureSource.ts';
|
|
244
|
+
import {
|
|
245
|
+
IRIDESCENT_FLOATS,
|
|
246
|
+
iridescentRefusal,
|
|
247
|
+
packIridescentOverlay,
|
|
248
|
+
type IridescentOverlay,
|
|
249
|
+
} from '../../iridescentOverlay.ts';
|
|
235
250
|
import {
|
|
236
251
|
frameReflectionsRefused,
|
|
237
252
|
resolveFrameReflections,
|
|
@@ -439,6 +454,7 @@ import {
|
|
|
439
454
|
} from '../../lightBudget.ts';
|
|
440
455
|
import { resolveRenderQuality, type RenderQuality } from '../../renderQuality.ts';
|
|
441
456
|
import {
|
|
457
|
+
mediumSkyFill,
|
|
442
458
|
DEFAULT_GLOBAL_MEDIUM,
|
|
443
459
|
WEAK_GPU_MEDIUM_STEPS,
|
|
444
460
|
createResolvedMediumLamps,
|
|
@@ -651,6 +667,8 @@ import { fitProbeGrid } from '../../gi/probeGridFit.ts';
|
|
|
651
667
|
* `fitProbeGrid` widens it uniformly when the budget bites, so a large scene gets a coarser grid
|
|
652
668
|
* rather than a truncated one.
|
|
653
669
|
*/
|
|
670
|
+
/** The ambient a medium is lit by in a frame bound with no environment: none. */
|
|
671
|
+
const MEDIUM_NO_AMBIENT: readonly number[] = [0, 0, 0];
|
|
654
672
|
const INDIRECT_PROBE_SPACING = 2;
|
|
655
673
|
|
|
656
674
|
/** What the trace is given when a frame never said what was lighting it. */
|
|
@@ -3057,6 +3075,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3057
3075
|
private warnedLayersModelMap = false;
|
|
3058
3076
|
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
3059
3077
|
private readonly detailFloats = new Float32Array(DETAIL_FLOATS);
|
|
3078
|
+
/** `uEmissiveFlow`, a material's glow moved and masked, packed by `packEmissiveFlow`. */
|
|
3079
|
+
private readonly flowFloats = new Float32Array(EMISSIVE_FLOW_FLOATS);
|
|
3080
|
+
/** `uIridescence`, a material's iridescent overlay packed by `packIridescentOverlay`. */
|
|
3081
|
+
private readonly iridescentFloats = new Float32Array(IRIDESCENT_FLOATS);
|
|
3082
|
+
/** The refusals of an overlay already said, each once. */
|
|
3083
|
+
private readonly warnedIridescent = new Set<string>();
|
|
3060
3084
|
/** The detail refusals already said, each once. See `detailOfMaterial`. */
|
|
3061
3085
|
private readonly warnedDetail = new Set<DetailRefusal>();
|
|
3062
3086
|
private warnedLayersTriplanar = false;
|
|
@@ -3607,6 +3631,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3607
3631
|
f[emissive] = asked?.[0] ?? 1;
|
|
3608
3632
|
f[emissive + 1] = asked?.[1] ?? 1;
|
|
3609
3633
|
f[emissive + 2] = asked?.[2] ?? 1;
|
|
3634
|
+
/* And its glow's scroll and mask, the first time a material asks: emissiveFlow.ts. */
|
|
3635
|
+
if (packEmissiveFlow(material, this.flowFloats)) this.enableLit('EMISSIVE_FLOW');
|
|
3636
|
+
f.set(this.flowFloats, this.materialField('uEmissiveFlow'));
|
|
3637
|
+
/* And its iridescent overlay, the first time a material lays one: iridescentOverlay.ts. */
|
|
3638
|
+
const iridescent = this.iridescentOf(material);
|
|
3639
|
+
packIridescentOverlay(iridescent, material ?? {}, this.iridescentFloats);
|
|
3640
|
+
f.set(this.iridescentFloats, this.materialField('uIridescence'));
|
|
3641
|
+
/* And whether the frame's decals mark it. See `SurfaceMaterial.receivesDecals`. */
|
|
3642
|
+
f[this.materialField('uDecalReceiver')] = material?.receivesDecals === false ? 1 : 0;
|
|
3643
|
+
if (iridescent !== null) this.enableLit('IRIDESCENT_OVERLAY');
|
|
3610
3644
|
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
3611
3645
|
packModel(
|
|
3612
3646
|
material?.model ?? null,
|
|
@@ -6426,6 +6460,21 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6426
6460
|
}
|
|
6427
6461
|
|
|
6428
6462
|
/** One of the lit stage's switches on, in every cache a lit pipeline can come from. */
|
|
6463
|
+
/** A material's iridescent overlay, or null where it is malformed, said once. */
|
|
6464
|
+
private iridescentOf(
|
|
6465
|
+
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
6466
|
+
): IridescentOverlay | null {
|
|
6467
|
+
const overlay = material?.iridescentOverlay ?? null;
|
|
6468
|
+
if (overlay === null) return null;
|
|
6469
|
+
const refusal = iridescentRefusal(overlay);
|
|
6470
|
+
if (refusal === null) return overlay;
|
|
6471
|
+
if (!this.warnedIridescent.has(refusal)) {
|
|
6472
|
+
this.warnedIridescent.add(refusal);
|
|
6473
|
+
console.warn(`Renderer: an iridescent overlay is refused: ${refusal}. Drawn without it.`);
|
|
6474
|
+
}
|
|
6475
|
+
return null;
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6429
6478
|
private enableLit(feature: LitSwitch): void {
|
|
6430
6479
|
if (this.pipelines.litSwitches[feature]) return;
|
|
6431
6480
|
void this.pipelines.enable(feature);
|
|
@@ -7759,6 +7808,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7759
7808
|
{ binding: TAA_TEXTURES.uFlicker },
|
|
7760
7809
|
{ binding: TAA_TEXTURES.uFlickerMotion },
|
|
7761
7810
|
{ binding: TAA_TEXTURES.uDepth, filterable: false },
|
|
7811
|
+
/* WebGL2's movers' motion (`motionTarget.ts`); here the depth stands in and is never read,
|
|
7812
|
+
`uMotionOn` being 0, since this backend follows movers through its reconstruction. */
|
|
7813
|
+
{ binding: TAA_TEXTURES.uMotion, filterable: false },
|
|
7762
7814
|
]);
|
|
7763
7815
|
this.taaUniforms = device.createBuffer({
|
|
7764
7816
|
label: 'post.taaUniforms',
|
|
@@ -8932,6 +8984,19 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8932
8984
|
* a destroyed buffer in a submit loses the whole command buffer. Nothing else reads them, so once
|
|
8933
8985
|
* the rewrites are submitted, which only moves them earlier, the buffers can go at once.
|
|
8934
8986
|
*/
|
|
8987
|
+
/**
|
|
8988
|
+
* Rewrite part of a skin in place. **A pose already recorded is submitted first**, since a queue
|
|
8989
|
+
* write reaches every dispatch submitted after it and would otherwise reach a pose the caller made
|
|
8990
|
+
* before the rewrite; then the pose after it alone reads the rewrite, as on WebGL2. A rewrite with
|
|
8991
|
+
* nothing pending submits nothing.
|
|
8992
|
+
*/
|
|
8993
|
+
updatePointSkin(skin: GpuPointSkin, first: number, data: PointSkinData): void {
|
|
8994
|
+
validatePointSkinRange(data, first, skin.mesh.vertexCount);
|
|
8995
|
+
if (this.surface.lost) return;
|
|
8996
|
+
if (this.pendingRewrites !== null) this.submit([]);
|
|
8997
|
+
skin.rewrite(this.surface.device, first, data);
|
|
8998
|
+
}
|
|
8999
|
+
|
|
8935
9000
|
disposePointSkin(skin: GpuPointSkin): void {
|
|
8936
9001
|
if (this.pendingRewrites !== null && !this.surface.lost) this.submit([]);
|
|
8937
9002
|
skin.dispose();
|
|
@@ -9094,6 +9159,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9094
9159
|
f.fill(0, at('uLayerLooks'), at('uLayerLooks') + LOOK_FLOATS);
|
|
9095
9160
|
/* Nor detail. */
|
|
9096
9161
|
f.fill(0, at('uDetail'), at('uDetail') + DETAIL_FLOATS);
|
|
9162
|
+
/* Nor a glow that moves or is masked. */
|
|
9163
|
+
f.fill(0, at('uEmissiveFlow'), at('uEmissiveFlow') + EMISSIVE_FLOW_FLOATS);
|
|
9164
|
+
/* Nor an iridescent overlay. */
|
|
9165
|
+
f.fill(0, at('uIridescence'), at('uIridescence') + IRIDESCENT_FLOATS);
|
|
9166
|
+
/* The frame's decals as last laid, held across passes, and every surface receiving them. */
|
|
9167
|
+
f.set(this.baseDecalFloats, at('uBaseDecals'));
|
|
9168
|
+
f[at('uDecalReceiver')] = 0;
|
|
9097
9169
|
f[at('uCutout')] = 0;
|
|
9098
9170
|
f[at('uCutout') + 1] = 0;
|
|
9099
9171
|
this.cutoutResolveStaged = 'hard';
|
|
@@ -13840,6 +13912,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13840
13912
|
f[depthRow + 3] = 1;
|
|
13841
13913
|
}
|
|
13842
13914
|
f[at('uAntiFlicker')] = 1;
|
|
13915
|
+
/* The camera's motion alone: this backend follows a mover through its reconstruction. */
|
|
13916
|
+
f[at('uMotionOn')] = 0;
|
|
13843
13917
|
device.queue.writeBuffer(this.taaUniforms, 0, this.taaStaging);
|
|
13844
13918
|
|
|
13845
13919
|
const pass = encoder.beginRenderPass({
|
|
@@ -14330,7 +14404,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14330
14404
|
f.set(this.frameEye, at('uCameraPos'));
|
|
14331
14405
|
f.set(env?.directionalDir ?? [0, 1, 0], at('uSunDir'));
|
|
14332
14406
|
f.set(env?.directionalColor ?? [0, 0, 0], at('uSunColor'));
|
|
14333
|
-
f.set(env?.ambient ??
|
|
14407
|
+
f.set(mediumSkyFill(options, env?.ambient ?? MEDIUM_NO_AMBIENT), at('uAmbient'));
|
|
14334
14408
|
f[at('uDensity')] = options.density;
|
|
14335
14409
|
const height = at('uHeight');
|
|
14336
14410
|
f[height] = options.heightFalloff;
|
|
@@ -17647,6 +17721,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
17647
17721
|
/** The profiles as packed and the cookie images, kept so either can rebuild the atlas. */
|
|
17648
17722
|
private iesAtlas = packIesAtlas([]);
|
|
17649
17723
|
private cookieImages: readonly TexImageSource[] = [];
|
|
17724
|
+
/** The images the frame's base decals lay, in the atlas's band of them. See `setBaseDecalImages`. */
|
|
17725
|
+
private decalImages: readonly TexImageSource[] = [];
|
|
17726
|
+
/** The decals as last laid, packed again whenever the cookies move their band. */
|
|
17727
|
+
private baseDecalList: readonly BaseDecal[] = [];
|
|
17728
|
+
/** `uBaseDecals`, held until the next `setBaseDecals`, across passes. */
|
|
17729
|
+
private readonly baseDecalFloats = new Float32Array(BASE_DECAL_FLOATS);
|
|
17730
|
+
/** The refusals of a decal or its image already said, each once. */
|
|
17731
|
+
private readonly warnedBaseDecals = new Set<string>();
|
|
17650
17732
|
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
17651
17733
|
private lightField: DriftLightVolumes | null = null;
|
|
17652
17734
|
private driftIndex: GPUTexture | null = null;
|
|
@@ -18198,6 +18280,78 @@ export class WebGPURenderer implements RendererApi {
|
|
|
18198
18280
|
this.cookieImages = images.slice();
|
|
18199
18281
|
this.forgetFixtureAtlas();
|
|
18200
18282
|
this.rebuildFlatBindGroup();
|
|
18283
|
+
/* The decals' band starts below the cookies, so their rows move with them. */
|
|
18284
|
+
if (this.baseDecalList.length > 0) this.setBaseDecals(this.baseDecalList);
|
|
18285
|
+
}
|
|
18286
|
+
|
|
18287
|
+
/**
|
|
18288
|
+
* The images the frame's base decals lay, each a `BASE_DECAL_TILE` square. The same count again
|
|
18289
|
+
* copies the squares into the atlas and nothing else; a different count builds it again. See
|
|
18290
|
+
* `renderer.ts` and `baseDecals.ts`. **Once a frame, before the draws they mark**: a copy reaches
|
|
18291
|
+
* every draw of the frame it is made in, as any queue write does.
|
|
18292
|
+
*/
|
|
18293
|
+
setBaseDecalImages(images: readonly TexImageSource[]): void {
|
|
18294
|
+
if (this.surface.lost) return;
|
|
18295
|
+
if (images.length === this.decalImages.length && this.fixtureTexture !== null) {
|
|
18296
|
+
this.decalImages = images;
|
|
18297
|
+
this.copyDecalImages(this.fixtureTexture);
|
|
18298
|
+
return;
|
|
18299
|
+
}
|
|
18300
|
+
this.decalImages = images.slice();
|
|
18301
|
+
this.forgetFixtureAtlas();
|
|
18302
|
+
this.rebuildFlatBindGroup();
|
|
18303
|
+
this.setBaseDecals(this.baseDecalList);
|
|
18304
|
+
}
|
|
18305
|
+
|
|
18306
|
+
/** The decal images into their squares of the atlas. */
|
|
18307
|
+
private copyDecalImages(texture: GPUTexture): void {
|
|
18308
|
+
const top = decalBandTop(this.cookieImages.length);
|
|
18309
|
+
for (let tile = 0; tile < this.decalImages.length; tile++) {
|
|
18310
|
+
const source = this.decalImages[tile];
|
|
18311
|
+
if (source === undefined || !this.decalImageFits(source)) continue;
|
|
18312
|
+
this.surface.device.queue.copyExternalImageToTexture(
|
|
18313
|
+
{ source: source as GPUCopyExternalImageSource },
|
|
18314
|
+
{ texture, origin: { x: tile * BASE_DECAL_TILE, y: top } },
|
|
18315
|
+
{ width: BASE_DECAL_TILE, height: BASE_DECAL_TILE },
|
|
18316
|
+
);
|
|
18317
|
+
}
|
|
18318
|
+
}
|
|
18319
|
+
|
|
18320
|
+
/** Whether an image is a decal's square, said once where it is not. */
|
|
18321
|
+
private decalImageFits(image: TexImageSource): boolean {
|
|
18322
|
+
const { width, height } = sourceSize(image);
|
|
18323
|
+
if (width === BASE_DECAL_TILE && height === BASE_DECAL_TILE) return true;
|
|
18324
|
+
const refusal =
|
|
18325
|
+
`a base decal image of ${width} by ${height}; it is a square of ${BASE_DECAL_TILE} ` +
|
|
18326
|
+
'texels, and is left clear';
|
|
18327
|
+
if (!this.warnedBaseDecals.has(refusal)) {
|
|
18328
|
+
this.warnedBaseDecals.add(refusal);
|
|
18329
|
+
console.warn(`WebGPU: ${refusal}.`);
|
|
18330
|
+
}
|
|
18331
|
+
return false;
|
|
18332
|
+
}
|
|
18333
|
+
|
|
18334
|
+
/**
|
|
18335
|
+
* The decals laid into the base colours of the draws after it, until the next call, across
|
|
18336
|
+
* passes. In the material block, as the overlay is, and written again where each pass starts it.
|
|
18337
|
+
* See `renderer.ts` and `baseDecals.ts`.
|
|
18338
|
+
*/
|
|
18339
|
+
setBaseDecals(decals: readonly BaseDecal[]): void {
|
|
18340
|
+
if (this.surface.lost) return;
|
|
18341
|
+
this.baseDecalList = decals;
|
|
18342
|
+
const refusal = packBaseDecals(
|
|
18343
|
+
decals,
|
|
18344
|
+
this.decalImages.length,
|
|
18345
|
+
decalBandTop(this.cookieImages.length),
|
|
18346
|
+
this.baseDecalFloats,
|
|
18347
|
+
);
|
|
18348
|
+
if (refusal !== null && !this.warnedBaseDecals.has(refusal)) {
|
|
18349
|
+
this.warnedBaseDecals.add(refusal);
|
|
18350
|
+
console.warn(`WebGPU: ${refusal}.`);
|
|
18351
|
+
}
|
|
18352
|
+
if ((this.baseDecalFloats[0] as number) > 0) this.enableLit('BASE_DECALS');
|
|
18353
|
+
this.perFrameFloats.set(this.baseDecalFloats, this.materialField('uBaseDecals'));
|
|
18354
|
+
this.materials.dirty();
|
|
18201
18355
|
}
|
|
18202
18356
|
|
|
18203
18357
|
/** Let the atlas go, to be built again from what is in hand the next time it is bound. */
|
|
@@ -18218,7 +18372,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
18218
18372
|
private fixtureAtlasView(): GPUTextureView {
|
|
18219
18373
|
if (this.fixtureView !== null) return this.fixtureView;
|
|
18220
18374
|
const { device } = this.surface;
|
|
18221
|
-
const layout = fixtureAtlasLayout(
|
|
18375
|
+
const layout = fixtureAtlasLayout(
|
|
18376
|
+
this.iesAtlas.height,
|
|
18377
|
+
this.cookieImages.length,
|
|
18378
|
+
this.decalImages.length,
|
|
18379
|
+
);
|
|
18222
18380
|
const texture = device.createTexture({
|
|
18223
18381
|
label: 'fixture.atlas',
|
|
18224
18382
|
size: [layout.width, layout.height],
|
|
@@ -18241,6 +18399,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
18241
18399
|
{ width: COOKIE_TILE, height: COOKIE_TILE },
|
|
18242
18400
|
);
|
|
18243
18401
|
}
|
|
18402
|
+
this.copyDecalImages(texture);
|
|
18244
18403
|
this.fixtureTexture = texture;
|
|
18245
18404
|
this.fixtureView = texture.createView();
|
|
18246
18405
|
this.iesRows = this.iesAtlas.height;
|
|
@@ -19039,6 +19198,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
19039
19198
|
...entry(TAA_TEXTURES.uFlicker, record),
|
|
19040
19199
|
...entry(TAA_TEXTURES.uFlickerMotion, motion),
|
|
19041
19200
|
...entry(TAA_TEXTURES.uDepth, this.resolvedDepthView, true),
|
|
19201
|
+
...entry(TAA_TEXTURES.uMotion, this.resolvedDepthView, true),
|
|
19042
19202
|
],
|
|
19043
19203
|
});
|
|
19044
19204
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decals laid into the base colour of what they land on, before it is lit: `setBaseDecals` and
|
|
3
|
+
* `setBaseDecalImages`, packed into `uBaseDecals` for the lit stage's `BASE_DECALS` switch.
|
|
4
|
+
*
|
|
5
|
+
* **What they are for, and why `DecalProjector` is not it.** A projected decal multiplies the
|
|
6
|
+
* finished pixel, so where the surface under it glows, a lettered floor or a lit panel, the mark
|
|
7
|
+
* dims the glow too. A shadow a body throws on a lit floor, a footprint, a stain is a change in what
|
|
8
|
+
* the surface *is*: it darkens the colour light falls on and leaves what the surface emits, how
|
|
9
|
+
* rough it is and its normal as they are. So each of these is a box, as a `DecalBox` is, an image
|
|
10
|
+
* read across its face, and the base colour moved toward the image's colour by its alpha.
|
|
11
|
+
*
|
|
12
|
+
* **The images ride in the fixture atlas**, a band of `BASE_DECAL_TILE` squares between the cookies
|
|
13
|
+
* and the photometric profiles (`fixtureAtlas.ts`), because the lit stage has no texture binding
|
|
14
|
+
* left (`textureUnitBudget.test.ts`). An image is that square, read as a picture: its colour is
|
|
15
|
+
* decoded from sRGB and its alpha is as it is. Rewriting them once a frame is the use they are for,
|
|
16
|
+
* and costs an upload of the squares; changing how many there are builds the atlas again.
|
|
17
|
+
*
|
|
18
|
+
* **What it gives up**: eight a frame, the uniforms they take being the limit (thirty-four vectors,
|
|
19
|
+
* so WebGL2 refuses them, in words, on a part whose lit stage has no room); the GPU-driven pipeline
|
|
20
|
+
* does not draw them; and a surface opts out by its material (`receivesDecals: false`), not by a
|
|
21
|
+
* mask of what each decal may mark.
|
|
22
|
+
*/
|
|
23
|
+
import { worldToDecalMatrix, type DecalBox } from './decalProjector.ts';
|
|
24
|
+
|
|
25
|
+
/** Decals a frame. */
|
|
26
|
+
export const MAX_BASE_DECALS = 8;
|
|
27
|
+
/** Texels across an image's square in the atlas. */
|
|
28
|
+
export const BASE_DECAL_TILE = 128;
|
|
29
|
+
/** Floats in `uBaseDecals`: a header, then four vectors a decal. */
|
|
30
|
+
export const BASE_DECAL_FLOATS = (1 + MAX_BASE_DECALS * 4) * 4;
|
|
31
|
+
|
|
32
|
+
/** One decal: a box, which image it lays, and how much. */
|
|
33
|
+
export interface BaseDecal extends DecalBox {
|
|
34
|
+
/** Which of `setBaseDecalImages`' images it lays, from 0. */
|
|
35
|
+
readonly image: number;
|
|
36
|
+
/** How much of the image's alpha lands, 0 to 1. Default 1. */
|
|
37
|
+
readonly opacity?: number;
|
|
38
|
+
/**
|
|
39
|
+
* How far from facing back along `forward` a surface may be turned and still be marked, as a
|
|
40
|
+
* cosine of the geometric normal against it. Default 0.2, so a floor is marked and the side of a
|
|
41
|
+
* step it stands on is not smeared. -1 marks every surface in the box.
|
|
42
|
+
*/
|
|
43
|
+
readonly facingCos?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const MATRIX = new Float32Array(16);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Pack `decals` against `images` laid in the atlas band starting at row `bandTop` into `out`, and
|
|
50
|
+
* return why one was left out, or null where none was: past the eighth, an image there is none of,
|
|
51
|
+
* or a box with no size or no orientation. The rest are packed in order.
|
|
52
|
+
*/
|
|
53
|
+
export function packBaseDecals(
|
|
54
|
+
decals: readonly BaseDecal[],
|
|
55
|
+
images: number,
|
|
56
|
+
bandTop: number,
|
|
57
|
+
out: Float32Array,
|
|
58
|
+
): string | null {
|
|
59
|
+
out.fill(0);
|
|
60
|
+
let refusal: string | null =
|
|
61
|
+
decals.length > MAX_BASE_DECALS
|
|
62
|
+
? `${decals.length} base decals; a frame lays the first ${MAX_BASE_DECALS}`
|
|
63
|
+
: null;
|
|
64
|
+
let kept = 0;
|
|
65
|
+
for (let i = 0; i < decals.length && kept < MAX_BASE_DECALS; i++) {
|
|
66
|
+
const decal = decals[i] as BaseDecal;
|
|
67
|
+
if (!(Number.isInteger(decal.image) && decal.image >= 0 && decal.image < images)) {
|
|
68
|
+
refusal ??= `a base decal lays image ${decal.image} and there are ${images}`;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
worldToDecalMatrix(MATRIX, decal);
|
|
73
|
+
} catch (cause) {
|
|
74
|
+
refusal ??= cause instanceof Error ? cause.message : String(cause);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const at = 4 + kept * 16;
|
|
78
|
+
for (let row = 0; row < 3; row++) {
|
|
79
|
+
out[at + row * 4] = MATRIX[row] as number;
|
|
80
|
+
out[at + row * 4 + 1] = MATRIX[4 + row] as number;
|
|
81
|
+
out[at + row * 4 + 2] = MATRIX[8 + row] as number;
|
|
82
|
+
out[at + row * 4 + 3] = MATRIX[12 + row] as number;
|
|
83
|
+
}
|
|
84
|
+
out[at + 12] = decal.image;
|
|
85
|
+
out[at + 13] = Math.min(1, Math.max(0, decal.opacity ?? 1));
|
|
86
|
+
out[at + 14] = decal.facingCos ?? 0.2;
|
|
87
|
+
kept += 1;
|
|
88
|
+
}
|
|
89
|
+
out[0] = kept;
|
|
90
|
+
out[1] = bandTop;
|
|
91
|
+
out[2] = BASE_DECAL_TILE;
|
|
92
|
+
return refusal;
|
|
93
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's glow moved and masked: `SurfaceMaterial.emissiveScroll` and `emissiveMaskLayer`,
|
|
3
|
+
* packed into `uEmissiveFlow` for the lit stage's `EMISSIVE_FLOW` switch. Both backends pack it
|
|
4
|
+
* here and only upload it, so they cannot disagree about which layer a mask is.
|
|
5
|
+
*
|
|
6
|
+
* **What it is for**: lava in a rock's cracks, a sign's lettering, a glow repeated with a surface's
|
|
7
|
+
* first layer and drifting across it, held to where the surface glows by a map that spans the mesh
|
|
8
|
+
* once. Folded into each vertex's emission instead, the glow spreads across whole faces of a coarse
|
|
9
|
+
* mesh where it should follow the cracks.
|
|
10
|
+
*
|
|
11
|
+
* **The mask is the alpha of a layer of the emissive map's own array**, read at the mesh's first
|
|
12
|
+
* coordinates as they are, before the material's repeat, offset, layers or projection: alpha is
|
|
13
|
+
* never decoded as sRGB, so it is the number it was written as in either colour space, and no
|
|
14
|
+
* texture binding is spent, since the lit stage has none left (`textureUnitBudget.test.ts`). What
|
|
15
|
+
* that gives up is a mask of its own size: a layer of an array is the array's size.
|
|
16
|
+
*
|
|
17
|
+
* **The scroll moves the emissive map alone**, by `Environment.surfaceTime`, in its own repeats a
|
|
18
|
+
* second, wrapped to one so a long-running clock keeps its precision; the albedo and every other map
|
|
19
|
+
* stay where they are.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Floats in `uEmissiveFlow`: the scroll on u and v, the mask's layer plus one (0, none) and one
|
|
24
|
+
* unused; then the scale and offset that take the material's coordinates back to the mesh's own.
|
|
25
|
+
*/
|
|
26
|
+
export const EMISSIVE_FLOW_FLOATS = 8;
|
|
27
|
+
|
|
28
|
+
/** What a material says about its glow's flow, and the repeat and offset its coordinates carry. */
|
|
29
|
+
export interface EmissiveFlowMaterial {
|
|
30
|
+
readonly emissiveScroll?: readonly [number, number] | null;
|
|
31
|
+
readonly emissiveMaskLayer?: number | null;
|
|
32
|
+
readonly uScale?: number;
|
|
33
|
+
readonly vScale?: number;
|
|
34
|
+
readonly uOffset?: number;
|
|
35
|
+
readonly vOffset?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pack a material's flow into `out` and say whether it asks for one: a scroll that moves, or a mask
|
|
40
|
+
* that is a whole layer. A scroll that is not a number is still; a layer that is not a whole number
|
|
41
|
+
* at or above zero is no mask.
|
|
42
|
+
*/
|
|
43
|
+
export function packEmissiveFlow(
|
|
44
|
+
material: EmissiveFlowMaterial | null,
|
|
45
|
+
out: Float32Array,
|
|
46
|
+
): boolean {
|
|
47
|
+
const scroll = material?.emissiveScroll ?? null;
|
|
48
|
+
const u = scroll !== null && Number.isFinite(scroll[0]) ? scroll[0] : 0;
|
|
49
|
+
const v = scroll !== null && Number.isFinite(scroll[1]) ? scroll[1] : 0;
|
|
50
|
+
const layer = material?.emissiveMaskLayer ?? null;
|
|
51
|
+
const masked = layer !== null && Number.isInteger(layer) && layer >= 0;
|
|
52
|
+
out[0] = u;
|
|
53
|
+
out[1] = v;
|
|
54
|
+
/* Plus one, so an unwritten zero is no mask rather than a mask of layer 0. */
|
|
55
|
+
out[2] = masked ? layer + 1 : 0;
|
|
56
|
+
out[3] = 0;
|
|
57
|
+
/* The vertex stage laid `uv · scale + offset`; this undoes it. A scale of zero has no inverse,
|
|
58
|
+
and every texel of such a surface is the one coordinate, so the mesh's own is taken as 0. */
|
|
59
|
+
const uScale = material?.uScale ?? 1;
|
|
60
|
+
const vScale = material?.vScale ?? 1;
|
|
61
|
+
out[4] = uScale === 0 ? 0 : 1 / uScale;
|
|
62
|
+
out[5] = vScale === 0 ? 0 : 1 / vScale;
|
|
63
|
+
out[6] = uScale === 0 ? 0 : -(material?.uOffset ?? 0) / uScale;
|
|
64
|
+
out[7] = vScale === 0 ? 0 : -(material?.vOffset ?? 0) / vScale;
|
|
65
|
+
return u !== 0 || v !== 0 || masked;
|
|
66
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a light fixture throws, in one texture: its spot cookies in the top band, its photometric
|
|
3
|
-
* profiles in the rows below.
|
|
3
|
+
* profiles in the rows below. And between the two, since the lit stage had no binding left for them
|
|
4
|
+
* either, the images of the decals laid into base colours (`baseDecals.ts`), a band of squares a
|
|
5
|
+
* frame may rewrite; each band is found from its own end or its own row, so the others' lookups are
|
|
6
|
+
* as they were.
|
|
4
7
|
*
|
|
5
8
|
* **One binding for two tables, because the lit stage had one sampler left.** The cookie atlas and
|
|
6
9
|
* the photometric atlas were two small float textures read with `textureLod`, and the widest lit
|
|
@@ -19,24 +22,40 @@
|
|
|
19
22
|
*/
|
|
20
23
|
import { IES_ATLAS_WIDTH } from './iesProfile.ts';
|
|
21
24
|
import { COOKIE_TILE } from './lightBudget.ts';
|
|
25
|
+
import { BASE_DECAL_TILE } from './baseDecals.ts';
|
|
22
26
|
|
|
23
27
|
export interface FixtureAtlasLayout {
|
|
24
28
|
readonly width: number;
|
|
25
29
|
readonly height: number;
|
|
26
30
|
/** Rows of cookie tiles at the top: `COOKIE_TILE`, or 0 with none loaded. */
|
|
27
31
|
readonly cookieRows: number;
|
|
28
|
-
/** The first row of the
|
|
32
|
+
/** The first row of the decals' images, below the cookies, and how many rows they take. */
|
|
33
|
+
readonly decalTop: number;
|
|
34
|
+
readonly decalRows: number;
|
|
35
|
+
/** The first row of the profiles, below the cookies and the decals. */
|
|
29
36
|
readonly iesTop: number;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
/** The
|
|
33
|
-
export function
|
|
39
|
+
/** The row the decals' band starts at: below the cookies, which are there or are not. */
|
|
40
|
+
export function decalBandTop(cookieTiles: number): number {
|
|
41
|
+
return cookieTiles > 0 ? COOKIE_TILE : 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The atlas for `iesRows` rows of profile, `cookieTiles` cookies and `decalTiles` decal images. */
|
|
45
|
+
export function fixtureAtlasLayout(
|
|
46
|
+
iesRows: number,
|
|
47
|
+
cookieTiles: number,
|
|
48
|
+
decalTiles = 0,
|
|
49
|
+
): FixtureAtlasLayout {
|
|
34
50
|
const cookieRows = cookieTiles > 0 ? COOKIE_TILE : 0;
|
|
51
|
+
const decalRows = decalTiles > 0 ? BASE_DECAL_TILE : 0;
|
|
35
52
|
return {
|
|
36
|
-
width: Math.max(IES_ATLAS_WIDTH, COOKIE_TILE * cookieTiles),
|
|
37
|
-
height: cookieRows + iesRows,
|
|
53
|
+
width: Math.max(IES_ATLAS_WIDTH, COOKIE_TILE * cookieTiles, BASE_DECAL_TILE * decalTiles),
|
|
54
|
+
height: cookieRows + decalRows + iesRows,
|
|
38
55
|
cookieRows,
|
|
39
|
-
|
|
56
|
+
decalTop: cookieRows,
|
|
57
|
+
decalRows,
|
|
58
|
+
iesTop: cookieRows + decalRows,
|
|
40
59
|
};
|
|
41
60
|
}
|
|
42
61
|
|