@aethervtt/shard-render 0.0.1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/assets.d.ts +104 -0
- package/dist/assets.js +213 -0
- package/dist/atmosphere-model.d.ts +96 -0
- package/dist/atmosphere-model.js +370 -0
- package/dist/atmosphere-nodes.d.ts +55 -0
- package/dist/atmosphere-nodes.js +780 -0
- package/dist/atmosphere-plugin.d.ts +5 -0
- package/dist/atmosphere-plugin.js +42 -0
- package/dist/atmosphere-shaders.d.ts +7 -0
- package/dist/atmosphere-shaders.js +595 -0
- package/dist/atmosphere-state.d.ts +72 -0
- package/dist/atmosphere-state.js +18 -0
- package/dist/atmosphere.d.ts +191 -0
- package/dist/atmosphere.js +950 -0
- package/dist/camera.d.ts +83 -0
- package/dist/camera.js +163 -0
- package/dist/clusters.d.ts +68 -0
- package/dist/clusters.js +288 -0
- package/dist/culling-shaders.d.ts +2 -0
- package/dist/culling-shaders.js +203 -0
- package/dist/culling.d.ts +84 -0
- package/dist/culling.js +498 -0
- package/dist/debug-shaders.d.ts +3 -0
- package/dist/debug-shaders.js +207 -0
- package/dist/debug-views.d.ts +60 -0
- package/dist/debug-views.js +197 -0
- package/dist/deferred-plugin.d.ts +5 -0
- package/dist/deferred-plugin.js +20 -0
- package/dist/deferred-shaders.d.ts +2 -0
- package/dist/deferred-shaders.js +151 -0
- package/dist/deferred.d.ts +22 -0
- package/dist/deferred.js +296 -0
- package/dist/deform.d.ts +80 -0
- package/dist/deform.js +213 -0
- package/dist/describe-plugin.d.ts +5 -0
- package/dist/describe-plugin.js +19 -0
- package/dist/display-nodes.d.ts +5 -0
- package/dist/display-nodes.js +189 -0
- package/dist/dynamic-resolution.d.ts +47 -0
- package/dist/dynamic-resolution.js +191 -0
- package/dist/environment-plugin.d.ts +6 -0
- package/dist/environment-plugin.js +44 -0
- package/dist/environment-shaders.d.ts +2 -0
- package/dist/environment-shaders.js +260 -0
- package/dist/environment-state.d.ts +71 -0
- package/dist/environment-state.js +144 -0
- package/dist/environment.d.ts +70 -0
- package/dist/environment.js +529 -0
- package/dist/forward.d.ts +123 -0
- package/dist/forward.js +1216 -0
- package/dist/fxaa.d.ts +2 -0
- package/dist/fxaa.js +65 -0
- package/dist/gizmo-font.d.ts +21 -0
- package/dist/gizmo-font.js +132 -0
- package/dist/gizmos-plugin.d.ts +5 -0
- package/dist/gizmos-plugin.js +34 -0
- package/dist/gizmos.d.ts +156 -0
- package/dist/gizmos.js +772 -0
- package/dist/gpu-assets.d.ts +114 -0
- package/dist/gpu-assets.js +494 -0
- package/dist/graph.d.ts +204 -0
- package/dist/graph.js +417 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +540 -0
- package/dist/instances.d.ts +349 -0
- package/dist/instances.js +1335 -0
- package/dist/large-world.d.ts +13 -0
- package/dist/large-world.js +20 -0
- package/dist/lights.d.ts +160 -0
- package/dist/lights.js +468 -0
- package/dist/material-noise.d.ts +19 -0
- package/dist/material-noise.js +7 -0
- package/dist/material-pipelines.d.ts +46 -0
- package/dist/material-pipelines.js +166 -0
- package/dist/materials.d.ts +108 -0
- package/dist/materials.js +219 -0
- package/dist/noise.d.ts +76 -0
- package/dist/noise.js +286 -0
- package/dist/overlay-registry.d.ts +18 -0
- package/dist/overlay-registry.js +30 -0
- package/dist/overlays.d.ts +35 -0
- package/dist/overlays.js +344 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.js +46 -0
- package/dist/picking-plugin.d.ts +2 -0
- package/dist/picking-plugin.js +20 -0
- package/dist/picking.d.ts +94 -0
- package/dist/picking.js +456 -0
- package/dist/pixel-perfect-plugin.d.ts +9 -0
- package/dist/pixel-perfect-plugin.js +135 -0
- package/dist/pixel-perfect.d.ts +27 -0
- package/dist/pixel-perfect.js +70 -0
- package/dist/plugin.d.ts +111 -0
- package/dist/plugin.js +182 -0
- package/dist/pool.d.ts +19 -0
- package/dist/pool.js +43 -0
- package/dist/post-common.d.ts +51 -0
- package/dist/post-common.js +148 -0
- package/dist/post-nodes.d.ts +39 -0
- package/dist/post-nodes.js +849 -0
- package/dist/post-plugin.d.ts +5 -0
- package/dist/post-plugin.js +26 -0
- package/dist/post-shaders.d.ts +8 -0
- package/dist/post-shaders.js +698 -0
- package/dist/post.d.ts +175 -0
- package/dist/post.js +453 -0
- package/dist/readback.d.ts +4 -0
- package/dist/readback.js +95 -0
- package/dist/render-scale.d.ts +53 -0
- package/dist/render-scale.js +63 -0
- package/dist/shaders.d.ts +122 -0
- package/dist/shaders.js +1168 -0
- package/dist/shadows.d.ts +119 -0
- package/dist/shadows.js +562 -0
- package/dist/skin-asset.d.ts +26 -0
- package/dist/skin-asset.js +25 -0
- package/dist/skinning-plugin.d.ts +5 -0
- package/dist/skinning-plugin.js +16 -0
- package/dist/skinning.d.ts +19 -0
- package/dist/skinning.js +343 -0
- package/dist/standard-fields.d.ts +67 -0
- package/dist/standard-fields.js +73 -0
- package/dist/standard.d.ts +7 -0
- package/dist/standard.js +37 -0
- package/dist/stats.d.ts +25 -0
- package/dist/stats.js +18 -0
- package/dist/target.d.ts +55 -0
- package/dist/target.js +70 -0
- package/dist/testing.d.ts +34 -0
- package/dist/testing.js +126 -0
- package/dist/timer.d.ts +28 -0
- package/dist/timer.js +136 -0
- package/dist/view.d.ts +117 -0
- package/dist/view.js +389 -0
- package/dist/visibility.d.ts +10 -0
- package/dist/visibility.js +84 -0
- package/package.json +49 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
defineResource,
|
|
4
|
+
defineSystem,
|
|
5
|
+
t
|
|
6
|
+
} from "@aethervtt/shard-core";
|
|
7
|
+
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
8
|
+
import { Textures } from "@aethervtt/shard-texture";
|
|
9
|
+
import { Atmospheres } from "./atmosphere-state.js";
|
|
10
|
+
import {
|
|
11
|
+
Environment,
|
|
12
|
+
EnvironmentStore,
|
|
13
|
+
Environments,
|
|
14
|
+
environmentParams,
|
|
15
|
+
LUT_SIZE,
|
|
16
|
+
REBAKE_FRAMES,
|
|
17
|
+
SKY_SIZE,
|
|
18
|
+
SOURCE_SIZE,
|
|
19
|
+
SPECULAR_MIPS,
|
|
20
|
+
SPECULAR_SIZE
|
|
21
|
+
} from "./environment-state.js";
|
|
22
|
+
import { GpuAssetsResource } from "./gpu-assets.js";
|
|
23
|
+
import { Gpu, Shaders, Views } from "./plugin.js";
|
|
24
|
+
import { cameraOf } from "./view.js";
|
|
25
|
+
const EnvironmentPresets = {
|
|
26
|
+
"overcast-sky": 2e3,
|
|
27
|
+
"clear-sky": 8e3
|
|
28
|
+
};
|
|
29
|
+
const EnvironmentMap = defineComponent(
|
|
30
|
+
"render/EnvironmentMap",
|
|
31
|
+
{
|
|
32
|
+
texture: t.handle("Texture", {
|
|
33
|
+
description: 'An HDR texture: an equirectangular .hdr (usage "hdr") or a cube-map KTX2.'
|
|
34
|
+
}),
|
|
35
|
+
intensity: t.f32({
|
|
36
|
+
default: 5e3,
|
|
37
|
+
min: 0,
|
|
38
|
+
unit: "cd/m\xB2",
|
|
39
|
+
presets: EnvironmentPresets,
|
|
40
|
+
description: "Luminance a texel value of 1.0 represents. HDR files are relative; this ties them to physical units. Presets: overcast-sky 2000, clear-sky 8000."
|
|
41
|
+
}),
|
|
42
|
+
rotation: t.f32({ unit: "deg", description: "Rotation of the environment about +Y." })
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
description: "Image-based lighting for a camera: ambient light and reflections from an HDR environment, prefiltered on the GPU."
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
const Skybox = defineComponent(
|
|
49
|
+
"render/Skybox",
|
|
50
|
+
{
|
|
51
|
+
brightness: t.f32({
|
|
52
|
+
default: 1,
|
|
53
|
+
min: 0,
|
|
54
|
+
description: "Multiplies the environment's luminance in the background (not its lighting)."
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
description: "Draws the camera's environment map behind everything. Replaces the clear color."
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
const ProceduralSky = defineComponent(
|
|
62
|
+
"render/ProceduralSky",
|
|
63
|
+
{
|
|
64
|
+
turbidity: t.f32({
|
|
65
|
+
default: 2,
|
|
66
|
+
min: 1,
|
|
67
|
+
max: 10,
|
|
68
|
+
description: "Haze: aerosol density. 1 is very clear, 10 is hazy."
|
|
69
|
+
}),
|
|
70
|
+
rayleigh: t.f32({
|
|
71
|
+
default: 1,
|
|
72
|
+
min: 0,
|
|
73
|
+
description: "Multiplies molecular (blue) scattering."
|
|
74
|
+
}),
|
|
75
|
+
mie: t.f32({ default: 1, min: 0, description: "Multiplies aerosol (white haze) scattering." }),
|
|
76
|
+
groundAlbedo: t.color({
|
|
77
|
+
default: [0.3, 0.3, 0.3, 1],
|
|
78
|
+
description: "Ground below the horizon."
|
|
79
|
+
}),
|
|
80
|
+
sunDiskSize: t.f32({
|
|
81
|
+
default: 1,
|
|
82
|
+
min: 0,
|
|
83
|
+
description: "Sun disk size, as a multiple of the DirectionalLight's angularDiameter (the Sun's 0.53\xB0). 0 hides the disk."
|
|
84
|
+
})
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
description: "An Earth sky seen from 10 m above the ground wherever the camera is: an Atmosphere (spec 0044) lit by the two brightest DirectionalLights, in cd/m\xB2 consistent with their illuminance. Drawn as the background, and baked into the environment so image-based lighting follows the time of day. For skies you can fly out of, put an Atmosphere on the planet instead."
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
const DefaultEnvironment = defineResource(
|
|
91
|
+
"render/DefaultEnvironment",
|
|
92
|
+
{
|
|
93
|
+
description: "The environment (map or procedural sky) for cameras that have none. AmbientLight is the fallback when neither is set.",
|
|
94
|
+
init: () => ({ map: null, sky: null, background: true })
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
const prepareEnvironments = defineSystem({
|
|
98
|
+
name: "render/prepare-environments",
|
|
99
|
+
description: "Picks each camera environment and queues GPU prefiltering when sources change.",
|
|
100
|
+
run: (_, world) => {
|
|
101
|
+
const store = world.resource(Environments);
|
|
102
|
+
const gpu = world.resource(Gpu);
|
|
103
|
+
store.ensure(gpu);
|
|
104
|
+
store.frame++;
|
|
105
|
+
store.cameras.clear();
|
|
106
|
+
const defaults = world.resource(DefaultEnvironment);
|
|
107
|
+
const textures = world.tryResource(Textures);
|
|
108
|
+
const assets = world.resource(GpuAssetsResource);
|
|
109
|
+
const atmospheres = world.tryResource(Atmospheres);
|
|
110
|
+
for (const view of world.resource(Views).list) {
|
|
111
|
+
const cam = cameraOf(view);
|
|
112
|
+
if (!cam) continue;
|
|
113
|
+
const e = cam.entity;
|
|
114
|
+
const own = world.isAlive(e);
|
|
115
|
+
const map = own && world.has(e, EnvironmentMap) ? world.get(e, EnvironmentMap) : void 0;
|
|
116
|
+
const skybox = own && world.has(e, Skybox) ? world.get(e, Skybox).brightness : -1;
|
|
117
|
+
let entry = {
|
|
118
|
+
environment: void 0,
|
|
119
|
+
intensity: 0,
|
|
120
|
+
rotation: 0,
|
|
121
|
+
background: -1,
|
|
122
|
+
sky: false,
|
|
123
|
+
backdrop: void 0
|
|
124
|
+
};
|
|
125
|
+
const mapEnvironment = (value) => {
|
|
126
|
+
const texture = textures?.get(value.texture);
|
|
127
|
+
if (!texture) return void 0;
|
|
128
|
+
const key = `map:${idOf(texture)}:${texture.version}`;
|
|
129
|
+
let env = store.environments.get(key);
|
|
130
|
+
if (!env) {
|
|
131
|
+
for (const [k, old] of store.environments) {
|
|
132
|
+
if (old.input === texture) {
|
|
133
|
+
old.destroy();
|
|
134
|
+
store.environments.delete(k);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
env = new Environment(gpu, "map");
|
|
138
|
+
env.key = key;
|
|
139
|
+
env.input = texture;
|
|
140
|
+
store.environments.set(key, env);
|
|
141
|
+
store.pending.push(env);
|
|
142
|
+
}
|
|
143
|
+
env.lastUsed = store.frame;
|
|
144
|
+
assets.texture(texture);
|
|
145
|
+
return env;
|
|
146
|
+
};
|
|
147
|
+
const useMap = (value, background) => {
|
|
148
|
+
const env = mapEnvironment(value);
|
|
149
|
+
if (!env) return;
|
|
150
|
+
entry = {
|
|
151
|
+
environment: env,
|
|
152
|
+
intensity: value.intensity,
|
|
153
|
+
rotation: value.rotation * Math.PI / 180,
|
|
154
|
+
background,
|
|
155
|
+
sky: false,
|
|
156
|
+
backdrop: void 0
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
const ca = atmospheres?.cameras.get(e);
|
|
160
|
+
if (ca?.primary) {
|
|
161
|
+
const id = `atmosphere@${e}`;
|
|
162
|
+
let env = store.environments.get(id);
|
|
163
|
+
if (!env) {
|
|
164
|
+
env = new Environment(gpu, "atmosphere");
|
|
165
|
+
store.environments.set(id, env);
|
|
166
|
+
}
|
|
167
|
+
env.atmosphere = ca;
|
|
168
|
+
const same = env.key.slice(0, env.key.indexOf("|")) === ca.bakeKey.slice(0, ca.bakeKey.indexOf("|"));
|
|
169
|
+
if (env.key !== ca.bakeKey && (!same || store.frame - env.queuedFrame >= REBAKE_FRAMES)) {
|
|
170
|
+
env.key = ca.bakeKey;
|
|
171
|
+
env.queuedFrame = store.frame;
|
|
172
|
+
env.state = "pending";
|
|
173
|
+
if (!store.pending.includes(env)) store.pending.push(env);
|
|
174
|
+
}
|
|
175
|
+
env.lastUsed = store.frame;
|
|
176
|
+
let backdrop;
|
|
177
|
+
const behind = map?.texture && skybox >= 0 ? map : !map?.texture && defaults.map?.texture && defaults.background ? defaults.map : void 0;
|
|
178
|
+
const backdropEnv = behind ? mapEnvironment(behind) : void 0;
|
|
179
|
+
if (behind && backdropEnv) {
|
|
180
|
+
backdrop = {
|
|
181
|
+
environment: backdropEnv,
|
|
182
|
+
intensity: behind.intensity * (skybox >= 0 ? skybox : 1),
|
|
183
|
+
rotation: behind.rotation * Math.PI / 180
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
entry = {
|
|
187
|
+
environment: env,
|
|
188
|
+
intensity: 1,
|
|
189
|
+
rotation: 0,
|
|
190
|
+
background: ca.background,
|
|
191
|
+
sky: true,
|
|
192
|
+
backdrop
|
|
193
|
+
};
|
|
194
|
+
} else if (map?.texture) useMap(map, skybox);
|
|
195
|
+
else if (defaults.map?.texture)
|
|
196
|
+
useMap(defaults.map, defaults.background ? Math.max(skybox, 1) : skybox);
|
|
197
|
+
store.cameras.set(e, entry);
|
|
198
|
+
view.data.environment = entry;
|
|
199
|
+
}
|
|
200
|
+
for (const [key, env] of store.environments) {
|
|
201
|
+
if (store.frame - env.lastUsed > 120) {
|
|
202
|
+
env.destroy();
|
|
203
|
+
store.environments.delete(key);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
209
|
+
let nextId = 1;
|
|
210
|
+
function idOf(o) {
|
|
211
|
+
let id = ids.get(o);
|
|
212
|
+
if (id === void 0) {
|
|
213
|
+
id = nextId++;
|
|
214
|
+
ids.set(o, id);
|
|
215
|
+
}
|
|
216
|
+
return id;
|
|
217
|
+
}
|
|
218
|
+
function storage2dArray(binding) {
|
|
219
|
+
return {
|
|
220
|
+
binding,
|
|
221
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
222
|
+
storageTexture: { access: "write-only", format: "rgba16float", viewDimension: "2d-array" }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function environmentPipelines(gpu, world) {
|
|
226
|
+
const shaders = world.resource(Shaders);
|
|
227
|
+
const C = GPUShaderStage.COMPUTE;
|
|
228
|
+
const layouts = {
|
|
229
|
+
equirect: gpu.layouts.bindGroupLayout({
|
|
230
|
+
label: "env/equirect",
|
|
231
|
+
entries: [
|
|
232
|
+
{ binding: 0, visibility: C, texture: { sampleType: "float" } },
|
|
233
|
+
{ binding: 1, visibility: C, sampler: { type: "filtering" } },
|
|
234
|
+
storage2dArray(2)
|
|
235
|
+
]
|
|
236
|
+
}),
|
|
237
|
+
cube: gpu.layouts.bindGroupLayout({
|
|
238
|
+
label: "env/cube",
|
|
239
|
+
entries: [
|
|
240
|
+
{ binding: 0, visibility: C, texture: { sampleType: "float", viewDimension: "cube" } },
|
|
241
|
+
{ binding: 1, visibility: C, sampler: { type: "filtering" } },
|
|
242
|
+
storage2dArray(2)
|
|
243
|
+
]
|
|
244
|
+
}),
|
|
245
|
+
downsample: gpu.layouts.bindGroupLayout({
|
|
246
|
+
label: "env/downsample",
|
|
247
|
+
entries: [
|
|
248
|
+
{ binding: 0, visibility: C, texture: { sampleType: "float", viewDimension: "2d-array" } },
|
|
249
|
+
storage2dArray(1)
|
|
250
|
+
]
|
|
251
|
+
}),
|
|
252
|
+
sh: gpu.layouts.bindGroupLayout({
|
|
253
|
+
label: "env/sh",
|
|
254
|
+
entries: [
|
|
255
|
+
{ binding: 0, visibility: C, texture: { sampleType: "float", viewDimension: "2d-array" } },
|
|
256
|
+
{ binding: 1, visibility: C, buffer: { type: "storage" } }
|
|
257
|
+
]
|
|
258
|
+
}),
|
|
259
|
+
specular: gpu.layouts.bindGroupLayout({
|
|
260
|
+
label: "env/specular",
|
|
261
|
+
entries: [
|
|
262
|
+
{ binding: 0, visibility: C, texture: { sampleType: "float", viewDimension: "cube" } },
|
|
263
|
+
{ binding: 1, visibility: C, sampler: { type: "filtering" } },
|
|
264
|
+
storage2dArray(2),
|
|
265
|
+
{ binding: 3, visibility: C, buffer: { type: "uniform" } }
|
|
266
|
+
]
|
|
267
|
+
}),
|
|
268
|
+
lut: gpu.layouts.bindGroupLayout({
|
|
269
|
+
label: "env/lut",
|
|
270
|
+
entries: [
|
|
271
|
+
{
|
|
272
|
+
binding: 0,
|
|
273
|
+
visibility: C,
|
|
274
|
+
storageTexture: { access: "write-only", format: "rgba16float", viewDimension: "2d" }
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
})
|
|
278
|
+
};
|
|
279
|
+
const pipelines = {};
|
|
280
|
+
const modules = {
|
|
281
|
+
equirect: "shard::env::from_equirect",
|
|
282
|
+
cube: "shard::env::from_cube",
|
|
283
|
+
downsample: "shard::env::downsample",
|
|
284
|
+
sh: "shard::env::sh",
|
|
285
|
+
specular: "shard::env::specular",
|
|
286
|
+
lut: "shard::env::brdf_lut"
|
|
287
|
+
};
|
|
288
|
+
let missing = false;
|
|
289
|
+
for (const [name, root] of Object.entries(modules)) {
|
|
290
|
+
const module = shaders.module(gpu, { root });
|
|
291
|
+
if (!module) {
|
|
292
|
+
missing = true;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const p = gpu.pipelines.compute({
|
|
296
|
+
label: `env/${name}`,
|
|
297
|
+
layout: gpu.layouts.pipelineLayout({
|
|
298
|
+
label: `env/${name}`,
|
|
299
|
+
bindGroupLayouts: [layouts[name]]
|
|
300
|
+
}),
|
|
301
|
+
compute: { module, entryPoint: "main" }
|
|
302
|
+
});
|
|
303
|
+
if (!p) missing = true;
|
|
304
|
+
else pipelines[name] = p;
|
|
305
|
+
}
|
|
306
|
+
return missing ? void 0 : { layouts, pipelines };
|
|
307
|
+
}
|
|
308
|
+
const specData = new Float32Array(4);
|
|
309
|
+
const environmentBakers = /* @__PURE__ */ new Map();
|
|
310
|
+
function runEnvironmentWork(ctx) {
|
|
311
|
+
const world = ctx.world;
|
|
312
|
+
const gpu = ctx.gpu;
|
|
313
|
+
const store = world.resource(Environments);
|
|
314
|
+
if (store.ranFrame === store.frame) return;
|
|
315
|
+
if (store.pending.length === 0 && store.lutReady) {
|
|
316
|
+
store.ranFrame = store.frame;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const p = environmentPipelines(gpu, world);
|
|
320
|
+
if (!p) {
|
|
321
|
+
gpu.pipelines.skipped++;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
store.ranFrame = store.frame;
|
|
325
|
+
const device = gpu.device;
|
|
326
|
+
const pass = ctx.encoder.beginComputePass({
|
|
327
|
+
label: "environment/prefilter",
|
|
328
|
+
timestampWrites: ctx.timestamps("environment/prefilter")
|
|
329
|
+
});
|
|
330
|
+
if (!store.lutReady) {
|
|
331
|
+
pass.setPipeline(p.pipelines.lut);
|
|
332
|
+
pass.setBindGroup(
|
|
333
|
+
0,
|
|
334
|
+
device.createBindGroup({
|
|
335
|
+
layout: p.layouts.lut,
|
|
336
|
+
entries: [{ binding: 0, resource: store.lut.createView() }]
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
pass.dispatchWorkgroups(LUT_SIZE / 8, LUT_SIZE / 8);
|
|
340
|
+
store.lutReady = true;
|
|
341
|
+
}
|
|
342
|
+
const assets = world.resource(GpuAssetsResource);
|
|
343
|
+
const sampler = store.sampler;
|
|
344
|
+
const wrapU = gpu.layouts.sampler({
|
|
345
|
+
label: "environment/equirect",
|
|
346
|
+
magFilter: "linear",
|
|
347
|
+
minFilter: "linear",
|
|
348
|
+
addressModeU: "repeat",
|
|
349
|
+
addressModeV: "clamp-to-edge"
|
|
350
|
+
});
|
|
351
|
+
const done = [];
|
|
352
|
+
for (const env of store.pending) {
|
|
353
|
+
const size = env.size;
|
|
354
|
+
const level0 = env.source.createView({
|
|
355
|
+
dimension: "2d-array",
|
|
356
|
+
baseMipLevel: 0,
|
|
357
|
+
mipLevelCount: 1
|
|
358
|
+
});
|
|
359
|
+
if (env.kind === "map") {
|
|
360
|
+
const input = env.input && assets.texture(env.input);
|
|
361
|
+
if (!input) continue;
|
|
362
|
+
const cube = env.input.faces === 6;
|
|
363
|
+
pass.setPipeline(cube ? p.pipelines.cube : p.pipelines.equirect);
|
|
364
|
+
pass.setBindGroup(
|
|
365
|
+
0,
|
|
366
|
+
device.createBindGroup({
|
|
367
|
+
layout: cube ? p.layouts.cube : p.layouts.equirect,
|
|
368
|
+
entries: [
|
|
369
|
+
{ binding: 0, resource: input.linear },
|
|
370
|
+
{ binding: 1, resource: cube ? sampler : wrapU },
|
|
371
|
+
{ binding: 2, resource: level0 }
|
|
372
|
+
]
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
pass.dispatchWorkgroups(size / 8, size / 8, 6);
|
|
376
|
+
} else {
|
|
377
|
+
const bake = environmentBakers.get(env.kind);
|
|
378
|
+
if (!bake?.(ctx, pass, env, level0)) continue;
|
|
379
|
+
}
|
|
380
|
+
const mips = Math.log2(size) + 1;
|
|
381
|
+
pass.setPipeline(p.pipelines.downsample);
|
|
382
|
+
for (let m = 1; m < mips; m++) {
|
|
383
|
+
const dim = Math.max(1, size >> m);
|
|
384
|
+
pass.setBindGroup(
|
|
385
|
+
0,
|
|
386
|
+
device.createBindGroup({
|
|
387
|
+
layout: p.layouts.downsample,
|
|
388
|
+
entries: [
|
|
389
|
+
{
|
|
390
|
+
binding: 0,
|
|
391
|
+
resource: env.source.createView({
|
|
392
|
+
dimension: "2d-array",
|
|
393
|
+
baseMipLevel: m - 1,
|
|
394
|
+
mipLevelCount: 1
|
|
395
|
+
})
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
binding: 1,
|
|
399
|
+
resource: env.source.createView({
|
|
400
|
+
dimension: "2d-array",
|
|
401
|
+
baseMipLevel: m,
|
|
402
|
+
mipLevelCount: 1
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
pass.dispatchWorkgroups(Math.ceil(dim / 8), Math.ceil(dim / 8), 6);
|
|
409
|
+
}
|
|
410
|
+
const shLevel = Math.max(0, Math.log2(size) - 5);
|
|
411
|
+
pass.setPipeline(p.pipelines.sh);
|
|
412
|
+
pass.setBindGroup(
|
|
413
|
+
0,
|
|
414
|
+
device.createBindGroup({
|
|
415
|
+
layout: p.layouts.sh,
|
|
416
|
+
entries: [
|
|
417
|
+
{
|
|
418
|
+
binding: 0,
|
|
419
|
+
resource: env.source.createView({
|
|
420
|
+
dimension: "2d-array",
|
|
421
|
+
baseMipLevel: shLevel,
|
|
422
|
+
mipLevelCount: 1
|
|
423
|
+
})
|
|
424
|
+
},
|
|
425
|
+
{ binding: 1, resource: { buffer: env.sh.buffer } }
|
|
426
|
+
]
|
|
427
|
+
})
|
|
428
|
+
);
|
|
429
|
+
pass.dispatchWorkgroups(1);
|
|
430
|
+
pass.setPipeline(p.pipelines.specular);
|
|
431
|
+
for (let m = 0; m < SPECULAR_MIPS; m++) {
|
|
432
|
+
const dim = SPECULAR_SIZE >> m;
|
|
433
|
+
const params = new GpuBuffer(gpu, {
|
|
434
|
+
label: "env/specular-params",
|
|
435
|
+
usage: GPUBufferUsage.UNIFORM,
|
|
436
|
+
size: 16
|
|
437
|
+
});
|
|
438
|
+
specData[0] = m / (SPECULAR_MIPS - 1);
|
|
439
|
+
specData[1] = size;
|
|
440
|
+
specData[2] = dim;
|
|
441
|
+
specData[3] = 0;
|
|
442
|
+
params.write(specData);
|
|
443
|
+
ctx.afterSubmit(() => params.destroy());
|
|
444
|
+
pass.setBindGroup(
|
|
445
|
+
0,
|
|
446
|
+
device.createBindGroup({
|
|
447
|
+
layout: p.layouts.specular,
|
|
448
|
+
entries: [
|
|
449
|
+
{ binding: 0, resource: env.sourceView },
|
|
450
|
+
{ binding: 1, resource: sampler },
|
|
451
|
+
{
|
|
452
|
+
binding: 2,
|
|
453
|
+
resource: env.specular.createView({
|
|
454
|
+
dimension: "2d-array",
|
|
455
|
+
baseMipLevel: m,
|
|
456
|
+
mipLevelCount: 1
|
|
457
|
+
})
|
|
458
|
+
},
|
|
459
|
+
{ binding: 3, resource: { buffer: params.buffer } }
|
|
460
|
+
]
|
|
461
|
+
})
|
|
462
|
+
);
|
|
463
|
+
pass.dispatchWorkgroups(Math.ceil(dim / 8), Math.ceil(dim / 8), 6);
|
|
464
|
+
}
|
|
465
|
+
env.state = "ready";
|
|
466
|
+
env.bakes++;
|
|
467
|
+
done.push(env);
|
|
468
|
+
}
|
|
469
|
+
pass.end();
|
|
470
|
+
for (const env of done) store.pending.splice(store.pending.indexOf(env), 1);
|
|
471
|
+
}
|
|
472
|
+
function atmosphereDescription(world, camera, ca) {
|
|
473
|
+
const s = ca.suns;
|
|
474
|
+
if (s.count === 0) return { sunIlluminance: 0 };
|
|
475
|
+
const lux = 0.2126 * s.data[4] + 0.7152 * s.data[5] + 0.0722 * s.data[6];
|
|
476
|
+
const radius = s.data[3];
|
|
477
|
+
const sky = world.isAlive(camera) ? world.tryGet(camera, ProceduralSky) : void 0;
|
|
478
|
+
return {
|
|
479
|
+
...sky ? { sky } : {},
|
|
480
|
+
sunDirection: [s.data[0], s.data[1], s.data[2]],
|
|
481
|
+
sunIlluminance: lux,
|
|
482
|
+
// E / solid angle, before the atmosphere dims it.
|
|
483
|
+
sunDiskLuminance: radius > 0 ? lux / (Math.PI * radius * radius) : 0
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function describeEnvironment(world) {
|
|
487
|
+
const store = world.tryResource(Environments);
|
|
488
|
+
if (!store) return void 0;
|
|
489
|
+
const views = {};
|
|
490
|
+
for (const view of world.resource(Views).list) {
|
|
491
|
+
const cam = cameraOf(view);
|
|
492
|
+
if (!cam) continue;
|
|
493
|
+
const entry = store.cameras.get(cam.entity);
|
|
494
|
+
const env = entry?.environment;
|
|
495
|
+
views[view.name] = {
|
|
496
|
+
tonemapping: ["aces", "agx", "pbr-neutral", "reinhard", "none"][cam.curve],
|
|
497
|
+
environment: env ? {
|
|
498
|
+
source: env.kind === "map" ? "environment-map" : env.atmosphere?.primary?.wrapper ? "procedural-sky" : "atmosphere",
|
|
499
|
+
intensity: entry.intensity,
|
|
500
|
+
rotation: entry.rotation * 180 / Math.PI,
|
|
501
|
+
prefilter: env.state,
|
|
502
|
+
bakes: env.bakes,
|
|
503
|
+
background: entry.background >= 0,
|
|
504
|
+
...env.kind === "atmosphere" && env.atmosphere ? atmosphereDescription(world, cam.entity, env.atmosphere) : {}
|
|
505
|
+
} : { source: "ambient-light" }
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
return { views, prefilteredEnvironments: store.environments.size };
|
|
509
|
+
}
|
|
510
|
+
export {
|
|
511
|
+
DefaultEnvironment,
|
|
512
|
+
Environment,
|
|
513
|
+
EnvironmentMap,
|
|
514
|
+
EnvironmentPresets,
|
|
515
|
+
EnvironmentStore,
|
|
516
|
+
Environments,
|
|
517
|
+
ProceduralSky,
|
|
518
|
+
REBAKE_FRAMES,
|
|
519
|
+
SKY_SIZE,
|
|
520
|
+
SOURCE_SIZE,
|
|
521
|
+
SPECULAR_MIPS,
|
|
522
|
+
SPECULAR_SIZE,
|
|
523
|
+
Skybox,
|
|
524
|
+
describeEnvironment,
|
|
525
|
+
environmentBakers,
|
|
526
|
+
environmentParams,
|
|
527
|
+
prepareEnvironments,
|
|
528
|
+
runEnvironmentWork
|
|
529
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
3
|
+
import { type Plugin } from '@aethervtt/shard-runtime';
|
|
4
|
+
import { ClusterBuffers, ViewLightList } from './clusters.js';
|
|
5
|
+
import { type ColorAttachment, type NodeContext, type RenderView } from './graph.js';
|
|
6
|
+
import { type DrawList, Mesh3d, MeshMaterial } from './instances.js';
|
|
7
|
+
import { MaterialPipelines } from './material-pipelines.js';
|
|
8
|
+
import { type RenderScaleValue } from './render-scale.js';
|
|
9
|
+
import { Cascades } from './shadows.js';
|
|
10
|
+
import { type CameraData } from './view.js';
|
|
11
|
+
export { Mesh3d, MeshMaterial };
|
|
12
|
+
/** Per camera view: uniforms, clusters, lights, and shadows, and the bind groups over them. */
|
|
13
|
+
export interface ViewGpu {
|
|
14
|
+
uniform: GpuBuffer;
|
|
15
|
+
/** This frame's SSAO texture, when the camera has Ssao. */
|
|
16
|
+
ao: GPUTexture | undefined;
|
|
17
|
+
lightList: ViewLightList;
|
|
18
|
+
clusters: ClusterBuffers;
|
|
19
|
+
shadowData: GpuBuffer;
|
|
20
|
+
cascades: Cascades;
|
|
21
|
+
bindGroup: GPUBindGroup | undefined;
|
|
22
|
+
bound: string;
|
|
23
|
+
clusterBindGroup: GPUBindGroup | undefined;
|
|
24
|
+
clusterBound: string;
|
|
25
|
+
}
|
|
26
|
+
interface Layouts {
|
|
27
|
+
view: GPUBindGroupLayout;
|
|
28
|
+
pipeline: GPUPipelineLayout;
|
|
29
|
+
cluster: GPUBindGroupLayout;
|
|
30
|
+
clusterPipeline: GPUPipelineLayout;
|
|
31
|
+
shadowView: GPUBindGroupLayout;
|
|
32
|
+
shadowPipeline: GPUPipelineLayout;
|
|
33
|
+
}
|
|
34
|
+
export interface ForwardState {
|
|
35
|
+
views: Map<string, ViewGpu>;
|
|
36
|
+
viewBytes: DataView;
|
|
37
|
+
shadowFloats: Float32Array;
|
|
38
|
+
layouts: Layouts;
|
|
39
|
+
layoutGeneration: number;
|
|
40
|
+
/** A 1x1x1 depth array bound where no shadow map exists (and its generation). */
|
|
41
|
+
emptyShadow: {
|
|
42
|
+
texture: GPUTexture;
|
|
43
|
+
generation: number;
|
|
44
|
+
} | undefined;
|
|
45
|
+
white: {
|
|
46
|
+
texture: GPUTexture;
|
|
47
|
+
generation: number;
|
|
48
|
+
} | undefined;
|
|
49
|
+
shadowSampler: GPUSampler | undefined;
|
|
50
|
+
/** Frame counter, so shared passes (local shadows) run once per frame. */
|
|
51
|
+
frame: number;
|
|
52
|
+
/** Last warnings, so each is logged once per change. */
|
|
53
|
+
warned: {
|
|
54
|
+
budget: string;
|
|
55
|
+
overflow: string;
|
|
56
|
+
};
|
|
57
|
+
/** Time, delta time, and frame for shaders (`shard::globals`). */
|
|
58
|
+
globals: GpuBuffer;
|
|
59
|
+
globalsData: Float32Array;
|
|
60
|
+
pipelines: MaterialPipelines;
|
|
61
|
+
/** G-buffer color targets (the emissive format depends on the device). */
|
|
62
|
+
gbufferTargets: GPUColorTargetState[];
|
|
63
|
+
/** Whether GPU culling ran this frame (the cull node reads it). */
|
|
64
|
+
cullerActive: boolean;
|
|
65
|
+
}
|
|
66
|
+
export declare const ForwardStateResource: import("@aethervtt/shard-core").ResourceDef<ForwardState>;
|
|
67
|
+
export declare const upload: import("@aethervtt/shard-core").SystemDef<undefined>;
|
|
68
|
+
export declare const PASS_OPAQUE = 0;
|
|
69
|
+
export declare const PASS_TRANSPARENT = 1;
|
|
70
|
+
export declare const PASS_GBUFFER = 3;
|
|
71
|
+
/** Depth, normals, and velocity for post-processing (TAA, motion blur, forward SSAO). */
|
|
72
|
+
export declare const PASS_PREPASS = 5;
|
|
73
|
+
/** Entity ids, normals, and depth for GPU picking (picking.ts). */
|
|
74
|
+
export declare const PASS_PICK = 7;
|
|
75
|
+
/**
|
|
76
|
+
* The picking pass's color targets: entity id, and the world normal with the depth in w (0 where
|
|
77
|
+
* nothing drew). Depth rides in a color target because depth formats only copy out whole.
|
|
78
|
+
*/
|
|
79
|
+
export declare const PICK_TARGETS: GPUColorTargetState[];
|
|
80
|
+
/**
|
|
81
|
+
* Draws a list of batches with their material types' pipelines. Returns the pipeline switches, so
|
|
82
|
+
* `render.describe` can show that sorting keeps them rare.
|
|
83
|
+
*/
|
|
84
|
+
export declare function drawMaterials(ctx: NodeContext, state: ForwardState, pv: ViewGpu, cam: CameraData, draws: DrawList, pass: number): number;
|
|
85
|
+
export declare const isCamera: (view: RenderView) => boolean;
|
|
86
|
+
/** The view bind group (group 0): uniforms, lights, clusters, shadows. Rebuilt when inputs change. */
|
|
87
|
+
export declare function viewBindGroup(gpu: GpuContext, world: World, pv: ViewGpu, cam: CameraData): GPUBindGroup;
|
|
88
|
+
/** Prefilters environments whose source changed (once per frame, before any view draws). */
|
|
89
|
+
/** Draws the environment map (Skybox) wherever no geometry was drawn. Atmospheres draw their own. */
|
|
90
|
+
export declare function skyNode(state: ForwardState): {
|
|
91
|
+
kind: 'render';
|
|
92
|
+
phase: 400;
|
|
93
|
+
enabled: (view: RenderView) => boolean;
|
|
94
|
+
reads: string[];
|
|
95
|
+
writes: string[];
|
|
96
|
+
color: (view: RenderView) => readonly ColorAttachment[];
|
|
97
|
+
depth: {
|
|
98
|
+
resource: string;
|
|
99
|
+
readOnly: boolean;
|
|
100
|
+
};
|
|
101
|
+
run: (ctx: NodeContext) => void;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The scene color attachment for a pass that draws into the 3D scene. With MSAA it resolves into
|
|
105
|
+
* `hdr` at the end of every such pass; the graph stores the multisampled buffer only when a later
|
|
106
|
+
* pass loads it, so the last scene pass resolves for free.
|
|
107
|
+
*/
|
|
108
|
+
export declare function sceneColor(view: RenderView, clear?: GPUColor): readonly ColorAttachment[];
|
|
109
|
+
export interface ForwardPluginOptions {
|
|
110
|
+
/** MSAA sample count: 1 or 4. Default 4. */
|
|
111
|
+
msaa?: 1 | 4;
|
|
112
|
+
/** Render scale of window cameras (0051). Default: auto, 0.5–1. */
|
|
113
|
+
renderScale?: Partial<Pick<RenderScaleValue, 'mode' | 'scale' | 'min' | 'max' | 'targetMs' | 'maxHz' | 'sharpen'>>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The core of the 3D renderer: cameras, meshes, the standard material, lights, shadows, and ambient
|
|
117
|
+
* light, drawn into HDR with instancing, frustum culling, and clustered light culling, then
|
|
118
|
+
* tonemapped. Needs the render and transform plugins. Features (atmosphere, post, ...) are plugins
|
|
119
|
+
* of their own; `forwardPlugin` adds all of them.
|
|
120
|
+
*/
|
|
121
|
+
export declare function forwardCorePlugin(options?: ForwardPluginOptions): Plugin;
|
|
122
|
+
/** rg11b10ufloat when the device can render to it, else rgba16float. */
|
|
123
|
+
export declare function gbufferEmissiveFormat(gpu: GpuContext): GPUTextureFormat;
|