@aethervtt/shard-render 0.0.1 → 0.2.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/dist/ablation.d.ts +74 -0
- package/dist/ablation.js +117 -0
- package/dist/assets.d.ts +3 -0
- package/dist/assets.js +43 -2
- package/dist/atmosphere-nodes.d.ts +9 -0
- package/dist/atmosphere-nodes.js +157 -30
- package/dist/atmosphere-plugin.js +8 -2
- package/dist/atmosphere-shaders.js +17 -5
- package/dist/baseline/atmosphere.d.ts +16 -0
- package/dist/baseline/atmosphere.js +301 -0
- package/dist/baseline/data-texture.d.ts +36 -0
- package/dist/baseline/data-texture.js +181 -0
- package/dist/baseline/environment.d.ts +14 -0
- package/dist/baseline/environment.js +368 -0
- package/dist/baseline/exposure.d.ts +25 -0
- package/dist/baseline/exposure.js +133 -0
- package/dist/baseline/lights.d.ts +33 -0
- package/dist/baseline/lights.js +133 -0
- package/dist/baseline/textures.d.ts +97 -0
- package/dist/baseline/textures.js +203 -0
- package/dist/camera.d.ts +2 -0
- package/dist/camera.js +4 -0
- package/dist/crowd.d.ts +21 -0
- package/dist/crowd.js +121 -0
- package/dist/culling-shaders.js +21 -2
- package/dist/culling.d.ts +11 -2
- package/dist/culling.js +44 -18
- package/dist/cutaway-plugin.d.ts +48 -0
- package/dist/cutaway-plugin.js +364 -0
- package/dist/cutaway.d.ts +27 -0
- package/dist/cutaway.js +49 -0
- package/dist/data-store.d.ts +69 -0
- package/dist/data-store.js +105 -0
- package/dist/debug-shaders.js +17 -3
- package/dist/debug-views.js +4 -2
- package/dist/decals-plugin.d.ts +5 -0
- package/dist/decals-plugin.js +54 -0
- package/dist/decals.d.ts +69 -0
- package/dist/decals.js +326 -0
- package/dist/deferred-shaders.js +2 -0
- package/dist/deferred.js +28 -4
- package/dist/deform.d.ts +5 -4
- package/dist/deform.js +4 -9
- package/dist/describe-plugin.d.ts +4 -2
- package/dist/describe-plugin.js +75 -3
- package/dist/display-nodes.js +37 -13
- package/dist/dynamic-resolution.d.ts +2 -0
- package/dist/dynamic-resolution.js +6 -1
- package/dist/environment-plugin.js +17 -2
- package/dist/environment-state.d.ts +7 -3
- package/dist/environment-state.js +20 -9
- package/dist/environment.js +7 -1
- package/dist/features.d.ts +49 -0
- package/dist/features.js +106 -0
- package/dist/foliage-budget.d.ts +64 -0
- package/dist/foliage-budget.js +108 -0
- package/dist/foliage-path.d.ts +19 -0
- package/dist/foliage-path.js +7 -0
- package/dist/foliage-shaders.d.ts +11 -0
- package/dist/foliage-shaders.js +504 -0
- package/dist/foliage.d.ts +194 -0
- package/dist/foliage.js +1027 -0
- package/dist/forward.d.ts +38 -2
- package/dist/forward.js +608 -186
- package/dist/fxaa.js +12 -2
- package/dist/gizmos-plugin.js +7 -0
- package/dist/gizmos.d.ts +3 -3
- package/dist/gizmos.js +11 -13
- package/dist/gpu-assets.d.ts +58 -4
- package/dist/gpu-assets.js +191 -8
- package/dist/graph.d.ts +45 -0
- package/dist/graph.js +169 -19
- package/dist/health.d.ts +58 -0
- package/dist/health.js +112 -0
- package/dist/index.d.ts +40 -11
- package/dist/index.js +343 -9
- package/dist/instances.d.ts +119 -8
- package/dist/instances.js +387 -78
- package/dist/interior-plugin.d.ts +121 -0
- package/dist/interior-plugin.js +465 -0
- package/dist/interior.d.ts +24 -0
- package/dist/interior.js +13 -0
- package/dist/layers.d.ts +35 -0
- package/dist/layers.js +49 -0
- package/dist/lens-plugin.d.ts +43 -0
- package/dist/lens-plugin.js +304 -0
- package/dist/lens.d.ts +41 -0
- package/dist/lens.js +99 -0
- package/dist/lights.d.ts +50 -5
- package/dist/lights.js +101 -22
- package/dist/material-pipelines.d.ts +37 -4
- package/dist/material-pipelines.js +102 -5
- package/dist/materials.d.ts +23 -0
- package/dist/materials.js +44 -1
- package/dist/outline-plugin.d.ts +48 -0
- package/dist/outline-plugin.js +529 -0
- package/dist/outline.d.ts +15 -0
- package/dist/outline.js +42 -0
- package/dist/overlay-registry.d.ts +3 -1
- package/dist/overlay-registry.js +3 -1
- package/dist/overlays.js +6 -2
- package/dist/perf-overlay.d.ts +9 -0
- package/dist/perf-overlay.js +125 -0
- package/dist/picking.d.ts +17 -0
- package/dist/picking.js +40 -11
- package/dist/pixel-perfect-plugin.js +10 -2
- package/dist/plugin.d.ts +69 -7
- package/dist/plugin.js +223 -25
- package/dist/post-common.d.ts +22 -2
- package/dist/post-common.js +82 -2
- package/dist/post-nodes.d.ts +7 -1
- package/dist/post-nodes.js +235 -111
- package/dist/post-plugin.js +5 -3
- package/dist/post-scene.d.ts +130 -0
- package/dist/post-scene.js +164 -0
- package/dist/post-shaders.js +85 -62
- package/dist/post.js +11 -1
- package/dist/projection.d.ts +24 -0
- package/dist/projection.js +87 -0
- package/dist/readback.d.ts +1 -0
- package/dist/readback.js +1 -0
- package/dist/render-scale.js +1 -0
- package/dist/ribbons-plugin.d.ts +5 -0
- package/dist/ribbons-plugin.js +59 -0
- package/dist/ribbons.d.ts +44 -0
- package/dist/ribbons.js +288 -0
- package/dist/scenario.d.ts +70 -0
- package/dist/scenario.js +125 -0
- package/dist/screen-effects.d.ts +97 -0
- package/dist/screen-effects.js +192 -0
- package/dist/shaders.d.ts +13 -0
- package/dist/shaders.js +189 -34
- package/dist/shadow-catcher.d.ts +9 -0
- package/dist/shadow-catcher.js +133 -0
- package/dist/shadows.d.ts +27 -2
- package/dist/shadows.js +100 -20
- package/dist/standard-fields.d.ts +1 -0
- package/dist/standard-fields.js +5 -0
- package/dist/standard.js +18 -0
- package/dist/stats.d.ts +45 -1
- package/dist/stats.js +91 -3
- package/dist/surface-model.d.ts +57 -0
- package/dist/surface-model.js +289 -0
- package/dist/surface-shaders.d.ts +3 -0
- package/dist/surface-shaders.js +160 -0
- package/dist/surface.d.ts +40 -0
- package/dist/surface.js +86 -0
- package/dist/target.d.ts +1 -13
- package/dist/target.js +1 -26
- package/dist/testing.d.ts +11 -0
- package/dist/testing.js +39 -1
- package/dist/tier.d.ts +13 -0
- package/dist/tier.js +14 -0
- package/dist/timer.d.ts +18 -1
- package/dist/timer.js +73 -20
- package/dist/variants.d.ts +37 -0
- package/dist/variants.js +104 -0
- package/dist/vfx-draw.d.ts +37 -0
- package/dist/vfx-draw.js +291 -0
- package/dist/vfx-state.d.ts +42 -0
- package/dist/vfx-state.js +81 -0
- package/dist/vfx.d.ts +69 -0
- package/dist/vfx.js +103 -0
- package/dist/view-visibility-plugin.d.ts +61 -0
- package/dist/view-visibility-plugin.js +267 -0
- package/dist/view-visibility.d.ts +28 -0
- package/dist/view-visibility.js +46 -0
- package/dist/view.d.ts +29 -1
- package/dist/view.js +57 -34
- package/package.json +23 -11
package/dist/decals.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import {
|
|
2
|
+
affine,
|
|
3
|
+
defineResource,
|
|
4
|
+
defineSystem
|
|
5
|
+
} from "@aethervtt/shard-core";
|
|
6
|
+
import { FrameDemand, Time } from "@aethervtt/shard-runtime";
|
|
7
|
+
import { GlobalTransform } from "@aethervtt/shard-transform";
|
|
8
|
+
import { Materials, Meshes } from "./assets.js";
|
|
9
|
+
import { Mesh3d, MeshMaterial, MorphWeights, SkinnedMesh } from "./instances.js";
|
|
10
|
+
import { RenderLayers } from "./layers.js";
|
|
11
|
+
import { isTransparent } from "./materials.js";
|
|
12
|
+
import { Decal, DecalReceiver } from "./vfx.js";
|
|
13
|
+
import {
|
|
14
|
+
createRecord,
|
|
15
|
+
matrixChanged,
|
|
16
|
+
number,
|
|
17
|
+
point,
|
|
18
|
+
recordDescription,
|
|
19
|
+
refreshRecord,
|
|
20
|
+
VFX_STRIDE
|
|
21
|
+
} from "./vfx-state.js";
|
|
22
|
+
import { ComputedVisibility } from "./visibility.js";
|
|
23
|
+
const DECAL_TRIANGLE_BUDGET = 32768;
|
|
24
|
+
const Decals = defineResource("render/Decals", {
|
|
25
|
+
init: () => ({
|
|
26
|
+
records: [],
|
|
27
|
+
receivers: [],
|
|
28
|
+
receiverByEntity: /* @__PURE__ */ new Map(),
|
|
29
|
+
frame: 0,
|
|
30
|
+
revision: 0,
|
|
31
|
+
rejected: 0
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
function create(world, entity) {
|
|
35
|
+
const max = world.get(entity, Decal).maxVertices;
|
|
36
|
+
return {
|
|
37
|
+
...createRecord(world, entity, Decal, "decal", max),
|
|
38
|
+
inverse: new Float64Array(12),
|
|
39
|
+
settings: new Float64Array(8).fill(NaN),
|
|
40
|
+
geometryCount: 0,
|
|
41
|
+
receiverRevision: -1,
|
|
42
|
+
rebuilds: 0,
|
|
43
|
+
ranges: [],
|
|
44
|
+
clipA: new Float64Array(48),
|
|
45
|
+
clipB: new Float64Array(48),
|
|
46
|
+
worldTriangle: new Float64Array(9)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function clipPlane(input, count, output, axis, sign, half) {
|
|
50
|
+
let n = 0;
|
|
51
|
+
for (let i = 0; i < count; i++) {
|
|
52
|
+
const a = (i + count - 1) % count * 3, b = i * 3;
|
|
53
|
+
const da = half - sign * input[a + axis], db = half - sign * input[b + axis];
|
|
54
|
+
if (da >= 0 !== db >= 0) {
|
|
55
|
+
const f = da / (da - db);
|
|
56
|
+
for (let k = 0; k < 3; k++)
|
|
57
|
+
output[n * 3 + k] = input[a + k] + (input[b + k] - input[a + k]) * f;
|
|
58
|
+
n++;
|
|
59
|
+
}
|
|
60
|
+
if (db >= 0) {
|
|
61
|
+
for (let k = 0; k < 3; k++) output[n * 3 + k] = input[b + k];
|
|
62
|
+
n++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return n;
|
|
66
|
+
}
|
|
67
|
+
function intersects(r, receiver, sx, sy, sz) {
|
|
68
|
+
const bounds = receiver.mesh.bounds, scratch = r.worldTriangle;
|
|
69
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
70
|
+
for (let i = 0; i < 8; i++) {
|
|
71
|
+
point(
|
|
72
|
+
scratch,
|
|
73
|
+
0,
|
|
74
|
+
receiver.matrix,
|
|
75
|
+
bounds[i & 1 ? 3 : 0],
|
|
76
|
+
bounds[i & 2 ? 4 : 1],
|
|
77
|
+
bounds[i & 4 ? 5 : 2]
|
|
78
|
+
);
|
|
79
|
+
point(scratch, 3, r.inverse, scratch[0], scratch[1], scratch[2]);
|
|
80
|
+
minX = Math.min(minX, scratch[3]);
|
|
81
|
+
minY = Math.min(minY, scratch[4]);
|
|
82
|
+
minZ = Math.min(minZ, scratch[5]);
|
|
83
|
+
maxX = Math.max(maxX, scratch[3]);
|
|
84
|
+
maxY = Math.max(maxY, scratch[4]);
|
|
85
|
+
maxZ = Math.max(maxZ, scratch[5]);
|
|
86
|
+
}
|
|
87
|
+
return minX <= sx / 2 && maxX >= -sx / 2 && minY <= sy / 2 && maxY >= -sy / 2 && minZ <= sz / 2 && maxZ >= -sz / 2;
|
|
88
|
+
}
|
|
89
|
+
function projectDecal(r, receivers) {
|
|
90
|
+
r.count = 0;
|
|
91
|
+
r.geometryCount = 0;
|
|
92
|
+
r.ranges.length = 0;
|
|
93
|
+
r.error = null;
|
|
94
|
+
r.rebuilds++;
|
|
95
|
+
if (!affine.invert(r.inverse, r.matrix)) {
|
|
96
|
+
r.error = "render/decal-singular";
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const size = r.columns.size, so = r.row * 3;
|
|
100
|
+
const sx = size[so], sy = size[so + 1], sz = size[so + 2];
|
|
101
|
+
if (!(sx > 0 && sy > 0 && sz > 0)) {
|
|
102
|
+
r.error = "render/decal-size";
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const mask = number(r, "receiverMask"), offset = number(r, "offset"), cos = Math.cos(number(r, "maxAngle") * Math.PI / 180);
|
|
106
|
+
const zx = r.matrix[2], zy = r.matrix[6], zz = r.matrix[10], zl = Math.sqrt(zx * zx + zy * zy + zz * zz);
|
|
107
|
+
let tested = 0;
|
|
108
|
+
for (let ri = 0; ri < receivers.length; ri++) {
|
|
109
|
+
const receiver = receivers[ri], mesh = receiver.mesh;
|
|
110
|
+
if (!receiver.visible || !(receiver.mask & mask) || !mesh || !intersects(r, receiver, sx, sy, sz))
|
|
111
|
+
continue;
|
|
112
|
+
if (receiver.unsupported) {
|
|
113
|
+
r.error = "render/decal-unsupported-receiver";
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
const start = r.count, positions = mesh.positions, indices = mesh.indices;
|
|
117
|
+
const triangles = Math.floor((indices ? indices.length : positions.length / 3) / 3);
|
|
118
|
+
if (tested + triangles > DECAL_TRIANGLE_BUDGET) {
|
|
119
|
+
r.error = "render/decal-input-budget";
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
tested += triangles;
|
|
123
|
+
for (let t = 0; t < triangles; t++) {
|
|
124
|
+
for (let v = 0; v < 3; v++) {
|
|
125
|
+
const vi = (indices ? indices[t * 3 + v] : t * 3 + v) * 3;
|
|
126
|
+
point(
|
|
127
|
+
r.worldTriangle,
|
|
128
|
+
v * 3,
|
|
129
|
+
receiver.matrix,
|
|
130
|
+
positions[vi],
|
|
131
|
+
positions[vi + 1],
|
|
132
|
+
positions[vi + 2]
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const w = r.worldTriangle;
|
|
136
|
+
const ax = w[3] - w[0], ay = w[4] - w[1], az = w[5] - w[2], bx = w[6] - w[0], by = w[7] - w[1], bz = w[8] - w[2];
|
|
137
|
+
let nx = ay * bz - az * by, ny = az * bx - ax * bz, nz = ax * by - ay * bx;
|
|
138
|
+
const length = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
139
|
+
if (length < 1e-12) continue;
|
|
140
|
+
nx /= length;
|
|
141
|
+
ny /= length;
|
|
142
|
+
nz /= length;
|
|
143
|
+
if ((nx * zx + ny * zy + nz * zz) / zl < cos) continue;
|
|
144
|
+
for (let v = 0; v < 3; v++)
|
|
145
|
+
point(r.clipA, v * 3, r.inverse, w[v * 3], w[v * 3 + 1], w[v * 3 + 2]);
|
|
146
|
+
let a = r.clipA, b = r.clipB, count = 3;
|
|
147
|
+
for (let plane = 0; plane < 6 && count; plane++) {
|
|
148
|
+
const axis = plane >> 1, half = (axis === 0 ? sx : axis === 1 ? sy : sz) * 0.5;
|
|
149
|
+
count = clipPlane(a, count, b, axis, plane % 2 ? -1 : 1, half);
|
|
150
|
+
const swap = a;
|
|
151
|
+
a = b;
|
|
152
|
+
b = swap;
|
|
153
|
+
}
|
|
154
|
+
if (count < 3) continue;
|
|
155
|
+
if (r.count + (count - 2) * 3 > r.vertices.length / VFX_STRIDE) {
|
|
156
|
+
r.error = "render/decal-output-budget";
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
for (let fan = 1; fan < count - 1; fan++)
|
|
160
|
+
for (let v = 0; v < 3; v++) {
|
|
161
|
+
const at = (v === 0 ? 0 : v === 1 ? fan : fan + 1) * 3, o = r.count++ * VFX_STRIDE;
|
|
162
|
+
point(w, 0, r.matrix, a[at], a[at + 1], a[at + 2]);
|
|
163
|
+
r.vertices[o] = w[0] + nx * offset;
|
|
164
|
+
r.vertices[o + 1] = w[1] + ny * offset;
|
|
165
|
+
r.vertices[o + 2] = w[2] + nz * offset;
|
|
166
|
+
r.vertices[o + 3] = 0;
|
|
167
|
+
r.vertices[o + 4] = 0;
|
|
168
|
+
r.vertices[o + 5] = 0;
|
|
169
|
+
r.vertices[o + 6] = a[at] / sx + 0.5;
|
|
170
|
+
r.vertices[o + 7] = 0.5 - a[at + 1] / sy;
|
|
171
|
+
for (let k = 8; k < 12; k++) r.vertices[o + k] = 1;
|
|
172
|
+
r.vertices[o + 12] = 0;
|
|
173
|
+
r.vertices[o + 13] = 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (r.error) break;
|
|
177
|
+
if (r.count > start)
|
|
178
|
+
r.ranges.push({
|
|
179
|
+
start,
|
|
180
|
+
count: r.count - start,
|
|
181
|
+
entity: receiver.entity,
|
|
182
|
+
layers: receiver.layers
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (r.error) {
|
|
186
|
+
r.count = 0;
|
|
187
|
+
r.ranges.length = 0;
|
|
188
|
+
}
|
|
189
|
+
r.geometryCount = r.count;
|
|
190
|
+
r.version++;
|
|
191
|
+
}
|
|
192
|
+
const updateDecals = defineSystem({
|
|
193
|
+
name: "render/decals-update",
|
|
194
|
+
setup: (world) => ({
|
|
195
|
+
q: world.query({ with: [Decal, GlobalTransform] }),
|
|
196
|
+
receivers: world.query({ with: [DecalReceiver, Mesh3d, GlobalTransform] })
|
|
197
|
+
}),
|
|
198
|
+
run({ q, receivers }, world) {
|
|
199
|
+
const state = world.resource(Decals), meshes = world.resource(Meshes), delta = world.resource(Time).delta;
|
|
200
|
+
state.frame++;
|
|
201
|
+
state.rejected = 0;
|
|
202
|
+
for (let ti = 0; ti < receivers.tables.length; ti++) {
|
|
203
|
+
const table = receivers.tables[ti], mask = table.column(DecalReceiver, "mask"), refs = table.column(Mesh3d, "mesh");
|
|
204
|
+
for (let row = 0; row < table.count; row++) {
|
|
205
|
+
const entity = table.entities[row];
|
|
206
|
+
let receiver = state.receiverByEntity.get(entity);
|
|
207
|
+
if (!receiver) {
|
|
208
|
+
receiver = {
|
|
209
|
+
entity,
|
|
210
|
+
matrix: new Float64Array(12),
|
|
211
|
+
mesh: void 0,
|
|
212
|
+
version: -1,
|
|
213
|
+
mask: -1,
|
|
214
|
+
visible: false,
|
|
215
|
+
unsupported: false,
|
|
216
|
+
layers: 1,
|
|
217
|
+
seen: 0
|
|
218
|
+
};
|
|
219
|
+
state.receivers.push(receiver);
|
|
220
|
+
state.receiverByEntity.set(entity, receiver);
|
|
221
|
+
state.revision++;
|
|
222
|
+
}
|
|
223
|
+
const mesh = meshes.get(refs[row]), version = mesh?.version ?? -1;
|
|
224
|
+
const visible = !table.has(ComputedVisibility) || table.column(ComputedVisibility, "visible")[row] !== 0;
|
|
225
|
+
const layers = table.has(RenderLayers) ? table.column(RenderLayers, "mask")[row] : 1;
|
|
226
|
+
let unsupported = !!mesh && (!mesh.positions || !!mesh.joints || !!mesh.targets?.length || table.has(SkinnedMesh) || table.has(MorphWeights));
|
|
227
|
+
if (table.has(MeshMaterial)) {
|
|
228
|
+
const material = world.tryResource(Materials)?.get(table.column(MeshMaterial, "material")[row]);
|
|
229
|
+
if (material && isTransparent(material.type.blendOf(material.value))) unsupported = true;
|
|
230
|
+
}
|
|
231
|
+
if (matrixChanged(world, entity, receiver.matrix) || receiver.mesh !== mesh || receiver.version !== version || receiver.mask !== mask[row] || receiver.visible !== visible || receiver.layers !== layers || receiver.unsupported !== unsupported)
|
|
232
|
+
state.revision++;
|
|
233
|
+
receiver.mesh = mesh;
|
|
234
|
+
receiver.version = version;
|
|
235
|
+
receiver.mask = mask[row];
|
|
236
|
+
receiver.visible = visible;
|
|
237
|
+
receiver.layers = layers;
|
|
238
|
+
receiver.unsupported = unsupported;
|
|
239
|
+
receiver.seen = state.frame;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (let i = state.receivers.length - 1; i >= 0; i--)
|
|
243
|
+
if (state.receivers[i].seen !== state.frame) {
|
|
244
|
+
state.receiverByEntity.delete(state.receivers[i].entity);
|
|
245
|
+
state.receivers.splice(i, 1);
|
|
246
|
+
state.revision++;
|
|
247
|
+
}
|
|
248
|
+
let demand = false;
|
|
249
|
+
for (let ti = 0; ti < q.tables.length; ti++) {
|
|
250
|
+
const table = q.tables[ti];
|
|
251
|
+
for (let row = 0; row < table.count; row++) {
|
|
252
|
+
const entity = table.entities[row];
|
|
253
|
+
const maxVertices = table.column(Decal, "maxVertices")[row];
|
|
254
|
+
if (maxVertices < 3 || maxVertices > 65535) {
|
|
255
|
+
state.rejected++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
let r;
|
|
259
|
+
for (let i = 0; i < state.records.length; i++)
|
|
260
|
+
if (state.records[i].entity === entity) {
|
|
261
|
+
r = state.records[i];
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if (!r) {
|
|
265
|
+
if (state.records.length >= 64) {
|
|
266
|
+
state.rejected++;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
r = create(world, entity);
|
|
270
|
+
state.records.push(r);
|
|
271
|
+
}
|
|
272
|
+
refreshRecord(world, r);
|
|
273
|
+
r.seen = state.frame;
|
|
274
|
+
let changed = matrixChanged(world, entity, r.matrix);
|
|
275
|
+
const size = r.columns.size;
|
|
276
|
+
for (let i = 0; i < 8; i++) {
|
|
277
|
+
const value = i < 3 ? size[row * 3 + i] : i === 3 ? number(r, "receiverMask") : i === 4 ? number(r, "maxAngle") : i === 5 ? number(r, "offset") : i === 6 ? number(r, "maxVertices") : 0;
|
|
278
|
+
if (r.settings[i] !== value) changed = true;
|
|
279
|
+
r.settings[i] = value;
|
|
280
|
+
}
|
|
281
|
+
const capacity = number(r, "maxVertices") * VFX_STRIDE;
|
|
282
|
+
if (r.vertices.length !== capacity) {
|
|
283
|
+
r.vertices = new Float32Array(capacity);
|
|
284
|
+
changed = true;
|
|
285
|
+
}
|
|
286
|
+
const playing = !!number(r, "playing"), paused = !!number(r, "paused"), lifetime = number(r, "lifetime");
|
|
287
|
+
if (playing && !r.wasPlaying) r.age = 0;
|
|
288
|
+
r.wasPlaying = playing;
|
|
289
|
+
if (playing && !paused) r.age += delta;
|
|
290
|
+
if (changed || r.receiverRevision !== state.revision) {
|
|
291
|
+
projectDecal(r, state.receivers);
|
|
292
|
+
r.receiverRevision = state.revision;
|
|
293
|
+
}
|
|
294
|
+
r.count = playing && (!lifetime || r.age < lifetime) ? r.geometryCount : 0;
|
|
295
|
+
if (lifetime && number(r, "fadeOut") > 0)
|
|
296
|
+
r.opacity *= Math.min(1, Math.max(0, (lifetime - r.age) / number(r, "fadeOut")));
|
|
297
|
+
if (playing && !paused && lifetime > 0 && r.age < lifetime) demand = true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (let i = state.records.length - 1; i >= 0; i--)
|
|
301
|
+
if (state.records[i].seen !== state.frame) state.records.splice(i, 1);
|
|
302
|
+
world.tryResource(FrameDemand)?.set("render/decals", demand);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
function describeDecals(world) {
|
|
306
|
+
const s = world.resource(Decals);
|
|
307
|
+
return {
|
|
308
|
+
rejected: s.rejected,
|
|
309
|
+
error: s.rejected ? "render/decal-limit" : null,
|
|
310
|
+
receivers: s.receivers.length,
|
|
311
|
+
records: s.records.map((r) => ({
|
|
312
|
+
...recordDescription(r),
|
|
313
|
+
cpuBytes: r.vertices.byteLength + r.matrix.byteLength + r.inverse.byteLength + r.settings.byteLength + r.clipA.byteLength + r.clipB.byteLength + r.worldTriangle.byteLength,
|
|
314
|
+
rebuilds: r.rebuilds,
|
|
315
|
+
triangles: r.geometryCount / 3
|
|
316
|
+
}))
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
export {
|
|
320
|
+
DECAL_TRIANGLE_BUDGET,
|
|
321
|
+
Decals,
|
|
322
|
+
clipPlane,
|
|
323
|
+
describeDecals,
|
|
324
|
+
projectDecal,
|
|
325
|
+
updateDecals
|
|
326
|
+
};
|
package/dist/deferred-shaders.js
CHANGED
|
@@ -66,6 +66,7 @@ import shard::mesh::mesh_vertex_at;
|
|
|
66
66
|
import shard::pbr::material::pbr_input;
|
|
67
67
|
import shard::pbr::standard::material;
|
|
68
68
|
import shard::pbr::gbuffer::{ GBufferOutput, pack_gbuffer };
|
|
69
|
+
@if(CUTAWAY) import shard::cutaway::cutaway_clip;
|
|
69
70
|
|
|
70
71
|
const FLAG_RECEIVER: u32 = 4u;
|
|
71
72
|
|
|
@@ -85,6 +86,7 @@ const FLAG_RECEIVER: u32 = 4u;
|
|
|
85
86
|
|
|
86
87
|
/** The surface stage only: what the material makes of the surface, packed for lighting. */
|
|
87
88
|
@fragment fn fs(in: VertexOutput) -> GBufferOutput {
|
|
89
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
88
90
|
let p = pbr_input(in);
|
|
89
91
|
@if(MASK) if (p.alpha < material.alphaCutoff) { discard; }
|
|
90
92
|
return pack_gbuffer(p, (in.flags & FLAG_RECEIVER) != 0u, view.exposure);
|
package/dist/deferred.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ShardError } from "@aethervtt/shard-core";
|
|
2
2
|
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
3
3
|
import { Culler } from "./culling.js";
|
|
4
|
+
import { addRenderFeatures } from "./features.js";
|
|
5
|
+
import { FoliagePath } from "./foliage-path.js";
|
|
4
6
|
import {
|
|
5
7
|
drawMaterials,
|
|
6
8
|
ForwardStateResource,
|
|
7
9
|
gbufferEmissiveFormat,
|
|
10
|
+
INTERIOR_DEFINES,
|
|
8
11
|
PASS_GBUFFER,
|
|
9
12
|
PASS_OPAQUE,
|
|
10
13
|
sceneColor,
|
|
@@ -12,6 +15,7 @@ import {
|
|
|
12
15
|
} from "./forward.js";
|
|
13
16
|
import { RenderPhase } from "./graph.js";
|
|
14
17
|
import { Gpu, Graph, RenderDescribers, Shaders, Views } from "./plugin.js";
|
|
18
|
+
import { depthReadEntry } from "./tier.js";
|
|
15
19
|
import { Cameras, cameraOf } from "./view.js";
|
|
16
20
|
const GBUFFER_CHANNELS = ["albedo", "normal", "roughness", "metallic", "emissive"];
|
|
17
21
|
const deferredView = (view) => cameraOf(view)?.deferred === true;
|
|
@@ -28,7 +32,9 @@ function fillGBuffer(ctx) {
|
|
|
28
32
|
const state = ctx.world.resource(ForwardStateResource);
|
|
29
33
|
const cam = cameraOf(ctx.view);
|
|
30
34
|
const pv = state.views.get(ctx.view.name);
|
|
31
|
-
if (pv)
|
|
35
|
+
if (!pv) return;
|
|
36
|
+
drawMaterials(ctx, state, pv, cam, cam.draws, PASS_GBUFFER);
|
|
37
|
+
ctx.world.tryResource(FoliagePath)?.draw(ctx, cam, PASS_GBUFFER, ctx.renderPass);
|
|
32
38
|
}
|
|
33
39
|
const gbufferNode = {
|
|
34
40
|
kind: "render",
|
|
@@ -80,7 +86,11 @@ function lightingNode() {
|
|
|
80
86
|
const cam = cameraOf(ctx.view);
|
|
81
87
|
const pv = state.views.get(ctx.view.name);
|
|
82
88
|
if (!pv) return;
|
|
83
|
-
const
|
|
89
|
+
const mode = state.interiorMode;
|
|
90
|
+
const module = ctx.world.resource(Shaders).module(
|
|
91
|
+
gpu,
|
|
92
|
+
mode !== 0 ? { root: "shard::pbr::deferred_lighting", defines: INTERIOR_DEFINES[mode] } : { root: "shard::pbr::deferred_lighting" }
|
|
93
|
+
);
|
|
84
94
|
if (!module) {
|
|
85
95
|
gpu.pipelines.skipped++;
|
|
86
96
|
return;
|
|
@@ -93,7 +103,7 @@ function lightingNode() {
|
|
|
93
103
|
{ binding: 0, visibility: F, texture: { sampleType: "float" } },
|
|
94
104
|
{ binding: 1, visibility: F, texture: { sampleType: "unfilterable-float" } },
|
|
95
105
|
{ binding: 2, visibility: F, texture: { sampleType: "float" } },
|
|
96
|
-
|
|
106
|
+
depthReadEntry(gpu, 3, F)
|
|
97
107
|
]
|
|
98
108
|
});
|
|
99
109
|
layoutGeneration = gpu.generation;
|
|
@@ -271,7 +281,7 @@ function describeDeferred(world) {
|
|
|
271
281
|
deferredMeshes: cam.draws.visible,
|
|
272
282
|
forwardMeshes: cam.forwardOnly.visible + cam.transparent.visible,
|
|
273
283
|
forwardReasons: forwardReason(cam, world)
|
|
274
|
-
} : { path: "forward", msaa: cam.msaa };
|
|
284
|
+
} : cam.msaaCapped ? { path: "forward", msaa: cam.msaa, msaaLimit: "the device can't multisample rgba16float" } : { path: "forward", msaa: cam.msaa };
|
|
275
285
|
}
|
|
276
286
|
return { views };
|
|
277
287
|
}
|
|
@@ -281,6 +291,20 @@ function addDeferredNodes(app) {
|
|
|
281
291
|
graph.declare({ name: "gbuffer0", format: "rgba8unorm-srgb" });
|
|
282
292
|
graph.declare({ name: "gbuffer1", format: "rgba16float" });
|
|
283
293
|
graph.declare({ name: "gbuffer2", format: gbufferEmissiveFormat(gpu) });
|
|
294
|
+
addRenderFeatures(app.world, {
|
|
295
|
+
name: "render/deferred",
|
|
296
|
+
description: "The deferred path: G-buffer, lighting, and forward-only materials.",
|
|
297
|
+
nodes: [
|
|
298
|
+
"deferred-gbuffer",
|
|
299
|
+
"gbuffer-fill",
|
|
300
|
+
"deferred-lighting",
|
|
301
|
+
"deferred-forward",
|
|
302
|
+
"gbuffer-debug"
|
|
303
|
+
],
|
|
304
|
+
baseline: {
|
|
305
|
+
strategy: "The same passes, reading lights and clusters through the baseline accessors"
|
|
306
|
+
}
|
|
307
|
+
});
|
|
284
308
|
graph.addNode("deferred-gbuffer", gbufferNode);
|
|
285
309
|
graph.addNode("gbuffer-fill", gbufferFillNode);
|
|
286
310
|
graph.addNode("deferred-lighting", lightingNode());
|
package/dist/deform.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
2
2
|
import type { Mesh } from '@aethervtt/shard-mesh';
|
|
3
|
+
import { DataStore } from './data-store.js';
|
|
3
4
|
/**
|
|
4
5
|
* Per-slot deform record, 12 words (mirrors `Deform` in `shard::mesh` and `shard::cull`):
|
|
5
6
|
*
|
|
@@ -47,9 +48,9 @@ export declare class DeformStore {
|
|
|
47
48
|
private vertexWords;
|
|
48
49
|
private wasted;
|
|
49
50
|
readonly meshes: Map<Mesh, MeshDeform>;
|
|
50
|
-
readonly recordBuffer:
|
|
51
|
-
readonly poseBuffer:
|
|
52
|
-
readonly vertexBuffer:
|
|
51
|
+
readonly recordBuffer: DataStore;
|
|
52
|
+
readonly poseBuffer: DataStore;
|
|
53
|
+
readonly vertexBuffer: DataStore;
|
|
53
54
|
private recordLo;
|
|
54
55
|
private recordHi;
|
|
55
56
|
private vertexDirty;
|
package/dist/deform.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataStore } from "./data-store.js";
|
|
2
2
|
const DEFORM_WORDS = 12;
|
|
3
3
|
const DEFORM_BYTES = DEFORM_WORDS * 4;
|
|
4
4
|
const SKIN_WORDS = 6;
|
|
@@ -26,14 +26,9 @@ class DeformStore {
|
|
|
26
26
|
constructor(gpu) {
|
|
27
27
|
this.gpu = gpu;
|
|
28
28
|
this.generation = gpu.generation;
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
usage,
|
|
33
|
-
size: DEFORM_BYTES * 256
|
|
34
|
-
});
|
|
35
|
-
this.poseBuffer = new GpuBuffer(gpu, { label: "deform/poses", usage, size: 16 * 1024 });
|
|
36
|
-
this.vertexBuffer = new GpuBuffer(gpu, { label: "deform/vertices", usage, size: 4096 });
|
|
29
|
+
this.recordBuffer = new DataStore(gpu, { label: "deform/records", size: DEFORM_BYTES * 256 });
|
|
30
|
+
this.poseBuffer = new DataStore(gpu, { label: "deform/poses", size: 16 * 1024 });
|
|
31
|
+
this.vertexBuffer = new DataStore(gpu, { label: "deform/vertices", size: 4096 });
|
|
37
32
|
}
|
|
38
33
|
/** Slots the record array holds. */
|
|
39
34
|
get capacity() {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The lighting, culling, and
|
|
3
|
-
*
|
|
2
|
+
* The lighting, culling, renderScale, surfaces, uploads, and frames sections of `render.describe`,
|
|
3
|
+
* and the
|
|
4
|
+
* `gpu.stats` method, for agents and the editor. Only introspection: an app that ships without it
|
|
5
|
+
* renders the same.
|
|
4
6
|
*/
|
|
5
7
|
export declare const renderDescribePlugin: import("@aethervtt/shard-runtime").Plugin;
|
package/dist/describe-plugin.js
CHANGED
|
@@ -1,17 +1,89 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineSchema, t } from "@aethervtt/shard-core";
|
|
2
|
+
import { definePlugin, FrameDemand } from "@aethervtt/shard-runtime";
|
|
2
3
|
import { describeCulling, describeLighting } from "./debug-views.js";
|
|
3
|
-
import {
|
|
4
|
+
import { GroundLayer } from "./layers.js";
|
|
5
|
+
import { Gpu, RenderDescribers, RenderOptions, Window } from "./plugin.js";
|
|
4
6
|
import { describeRenderScale } from "./render-scale.js";
|
|
7
|
+
import { RenderStats } from "./stats.js";
|
|
8
|
+
import { Cameras } from "./view.js";
|
|
9
|
+
function describeSurfaces(world) {
|
|
10
|
+
const window = world.tryResource(Window);
|
|
11
|
+
return world.resource(Gpu).surfaces.map((s) => ({
|
|
12
|
+
label: s.label,
|
|
13
|
+
size: [s.width, s.height],
|
|
14
|
+
alpha: s.alpha,
|
|
15
|
+
pixelRatio: s.pixelRatio,
|
|
16
|
+
thisApp: s === window
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
function describeUploads(world) {
|
|
20
|
+
const stats = world.resource(RenderStats);
|
|
21
|
+
const bytes = (r) => ({
|
|
22
|
+
bytes: { ...r.bytes },
|
|
23
|
+
sceneBytes: r.sceneBytes,
|
|
24
|
+
created: r.created
|
|
25
|
+
});
|
|
26
|
+
const rebuilds = (r) => ({
|
|
27
|
+
chunksRebuilt: r.chunksRebuilt,
|
|
28
|
+
meshesRebuilt: r.meshesRebuilt,
|
|
29
|
+
shadowMapsRendered: r.shadowMapsRendered
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
frames: stats.frames,
|
|
33
|
+
lastFrame: bytes(stats.lastFrame),
|
|
34
|
+
recent: bytes(stats.recent),
|
|
35
|
+
rebuilds: { lastFrame: rebuilds(stats.lastFrame), recent: rebuilds(stats.recent) }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function describeGround(world) {
|
|
39
|
+
const bands = {};
|
|
40
|
+
world.query({ with: [GroundLayer] }).each((_e, row, table) => {
|
|
41
|
+
const band = String(table.column(GroundLayer, "band")[row]);
|
|
42
|
+
bands[band] = (bands[band] ?? 0) + 1;
|
|
43
|
+
});
|
|
44
|
+
const views = {};
|
|
45
|
+
for (const cam of world.resource(Cameras).values())
|
|
46
|
+
views[`camera:${cam.entity}`] = {
|
|
47
|
+
instances: cam.ground.visible + cam.overlay.visible,
|
|
48
|
+
draws: cam.ground.length + cam.overlay.length
|
|
49
|
+
};
|
|
50
|
+
return { bands, views };
|
|
51
|
+
}
|
|
52
|
+
const gpuStats = {
|
|
53
|
+
name: "gpu.stats",
|
|
54
|
+
description: "Live GPU buffers and textures, by owner: each app's render plugin is one, and 'gpu' holds what apps on the device share.",
|
|
55
|
+
params: defineSchema("render/GpuStatsParams", {
|
|
56
|
+
owner: t.string({ description: "One owner. Empty: this app, every owner, and the total." })
|
|
57
|
+
}),
|
|
58
|
+
handler: ({ world }, p) => {
|
|
59
|
+
const gpu = world.resource(Gpu);
|
|
60
|
+
if (p.owner) return { owner: p.owner, ...gpu.stats(p.owner) };
|
|
61
|
+
return {
|
|
62
|
+
owner: world.resource(RenderOptions).owner,
|
|
63
|
+
total: gpu.stats(),
|
|
64
|
+
owners: Object.fromEntries(gpu.owners().map((o) => [o, gpu.stats(o)]))
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
5
68
|
const renderDescribePlugin = definePlugin({
|
|
6
69
|
name: "render/describe",
|
|
7
70
|
dependencies: ["render/forward"],
|
|
8
|
-
build() {
|
|
71
|
+
build(app) {
|
|
72
|
+
app.addMethod(gpuStats);
|
|
9
73
|
},
|
|
10
74
|
ready(app) {
|
|
11
75
|
const describers = app.world.initResource(RenderDescribers);
|
|
12
76
|
describers.set("lighting", (world) => describeLighting(world));
|
|
13
77
|
describers.set("culling", (world) => describeCulling(world));
|
|
14
78
|
describers.set("renderScale", (world) => describeRenderScale(world));
|
|
79
|
+
describers.set("surfaces", describeSurfaces);
|
|
80
|
+
describers.set("uploads", describeUploads);
|
|
81
|
+
describers.set("ground", describeGround);
|
|
82
|
+
describers.set("frames", (world) => world.resource(FrameDemand).describe());
|
|
83
|
+
describers.set(
|
|
84
|
+
"gpuObjects",
|
|
85
|
+
(world) => world.resource(Gpu).stats(world.resource(RenderOptions).owner)
|
|
86
|
+
);
|
|
15
87
|
}
|
|
16
88
|
});
|
|
17
89
|
export {
|