@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,260 @@
|
|
|
1
|
+
const ENVIRONMENT_SHADERS = {
|
|
2
|
+
"shard::env::common": `
|
|
3
|
+
const PI: f32 = 3.14159265359;
|
|
4
|
+
|
|
5
|
+
/** The direction through texel uv (0..1, top-left origin) of cube face +X, -X, +Y, -Y, +Z, -Z. */
|
|
6
|
+
fn cube_dir(face: u32, uv: vec2f) -> vec3f {
|
|
7
|
+
let s = uv.x * 2.0 - 1.0;
|
|
8
|
+
let t = uv.y * 2.0 - 1.0;
|
|
9
|
+
switch face {
|
|
10
|
+
case 0u: { return normalize(vec3f(1.0, -t, -s)); }
|
|
11
|
+
case 1u: { return normalize(vec3f(-1.0, -t, s)); }
|
|
12
|
+
case 2u: { return normalize(vec3f(s, 1.0, t)); }
|
|
13
|
+
case 3u: { return normalize(vec3f(s, -1.0, -t)); }
|
|
14
|
+
case 4u: { return normalize(vec3f(s, -t, 1.0)); }
|
|
15
|
+
default: { return normalize(vec3f(-s, -t, -1.0)); }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Equirectangular uv of a direction: u = 0.5 looks along +X, v = 0 is straight up. */
|
|
20
|
+
fn equirect_uv(d: vec3f) -> vec2f {
|
|
21
|
+
return vec2f(atan2(d.z, d.x) / (2.0 * PI) + 0.5, acos(clamp(d.y, -1.0, 1.0)) / PI);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn hammersley(i: u32, n: u32) -> vec2f {
|
|
25
|
+
return vec2f(f32(i) / f32(n), f32(reverseBits(i)) * 2.3283064365386963e-10);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A GGX-distributed half vector around n (alpha = roughness\xB2). */
|
|
29
|
+
fn importance_ggx(xi: vec2f, n: vec3f, a: f32) -> vec3f {
|
|
30
|
+
let phi = 2.0 * PI * xi.x;
|
|
31
|
+
let cos_theta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
|
|
32
|
+
let sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
|
|
33
|
+
let h = vec3f(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);
|
|
34
|
+
let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(n.z) < 0.999);
|
|
35
|
+
let tx = normalize(cross(up, n));
|
|
36
|
+
let ty = cross(n, tx);
|
|
37
|
+
return normalize(tx * h.x + ty * h.y + n * h.z);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The nine real spherical harmonics basis functions (bands 0-2) at a unit direction. */
|
|
41
|
+
fn sh_basis(d: vec3f) -> array<f32, 9> {
|
|
42
|
+
return array<f32, 9>(
|
|
43
|
+
0.282095,
|
|
44
|
+
0.488603 * d.y,
|
|
45
|
+
0.488603 * d.z,
|
|
46
|
+
0.488603 * d.x,
|
|
47
|
+
1.092548 * d.x * d.y,
|
|
48
|
+
1.092548 * d.y * d.z,
|
|
49
|
+
0.315392 * (3.0 * d.z * d.z - 1.0),
|
|
50
|
+
1.092548 * d.x * d.z,
|
|
51
|
+
0.546274 * (d.x * d.x - d.y * d.y),
|
|
52
|
+
);
|
|
53
|
+
}`,
|
|
54
|
+
"shard::env::from_equirect": `
|
|
55
|
+
import shard::env::common::{ cube_dir, equirect_uv };
|
|
56
|
+
|
|
57
|
+
@group(0) @binding(0) var input: texture_2d<f32>;
|
|
58
|
+
@group(0) @binding(1) var input_sampler: sampler;
|
|
59
|
+
@group(0) @binding(2) var output: texture_storage_2d_array<rgba16float, write>;
|
|
60
|
+
|
|
61
|
+
@compute @workgroup_size(8, 8, 1)
|
|
62
|
+
fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
63
|
+
let size = textureDimensions(output).x;
|
|
64
|
+
if (id.x >= size || id.y >= size) { return; }
|
|
65
|
+
var c = vec3f(0.0);
|
|
66
|
+
// 2x2 supersampling: a 512\xB2 face covers several equirect texels near the poles.
|
|
67
|
+
for (var j = 0u; j < 2u; j++) {
|
|
68
|
+
for (var i = 0u; i < 2u; i++) {
|
|
69
|
+
let uv = (vec2f(id.xy) + (vec2f(f32(i), f32(j)) + 0.5) * 0.5) / f32(size);
|
|
70
|
+
c += textureSampleLevel(input, input_sampler, equirect_uv(cube_dir(id.z, uv)), 0.0).rgb;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
textureStore(output, id.xy, id.z, vec4f(c * 0.25, 1.0));
|
|
74
|
+
}`,
|
|
75
|
+
"shard::env::from_cube": `
|
|
76
|
+
import shard::env::common::cube_dir;
|
|
77
|
+
|
|
78
|
+
@group(0) @binding(0) var input: texture_cube<f32>;
|
|
79
|
+
@group(0) @binding(1) var input_sampler: sampler;
|
|
80
|
+
@group(0) @binding(2) var output: texture_storage_2d_array<rgba16float, write>;
|
|
81
|
+
|
|
82
|
+
@compute @workgroup_size(8, 8, 1)
|
|
83
|
+
fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
84
|
+
let size = textureDimensions(output).x;
|
|
85
|
+
if (id.x >= size || id.y >= size) { return; }
|
|
86
|
+
let uv = (vec2f(id.xy) + 0.5) / f32(size);
|
|
87
|
+
let c = textureSampleLevel(input, input_sampler, cube_dir(id.z, uv), 0.0).rgb;
|
|
88
|
+
textureStore(output, id.xy, id.z, vec4f(c, 1.0));
|
|
89
|
+
}`,
|
|
90
|
+
"shard::env::downsample": `
|
|
91
|
+
@group(0) @binding(0) var src: texture_2d_array<f32>;
|
|
92
|
+
@group(0) @binding(1) var dst: texture_storage_2d_array<rgba16float, write>;
|
|
93
|
+
|
|
94
|
+
@compute @workgroup_size(8, 8, 1)
|
|
95
|
+
fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
96
|
+
let size = textureDimensions(dst).x;
|
|
97
|
+
if (id.x >= size || id.y >= size) { return; }
|
|
98
|
+
let p = vec2i(id.xy) * 2;
|
|
99
|
+
let face = i32(id.z);
|
|
100
|
+
let c = textureLoad(src, p, face, 0) + textureLoad(src, p + vec2i(1, 0), face, 0)
|
|
101
|
+
+ textureLoad(src, p + vec2i(0, 1), face, 0) + textureLoad(src, p + vec2i(1, 1), face, 0);
|
|
102
|
+
textureStore(dst, id.xy, id.z, vec4f(c.rgb * 0.25, 1.0));
|
|
103
|
+
}`,
|
|
104
|
+
"shard::env::sh": `
|
|
105
|
+
import shard::env::common::{ PI, cube_dir, sh_basis };
|
|
106
|
+
|
|
107
|
+
@group(0) @binding(0) var src: texture_2d_array<f32>;
|
|
108
|
+
/** Output: SH9 coefficients already convolved with the cosine lobe and divided by \u03C0. */
|
|
109
|
+
@group(0) @binding(1) var<storage, read_write> sh: array<vec4f, 9>;
|
|
110
|
+
|
|
111
|
+
var<workgroup> acc: array<array<vec4f, 9>, 64>;
|
|
112
|
+
|
|
113
|
+
@compute @workgroup_size(64)
|
|
114
|
+
fn main(@builtin(local_invocation_index) t: u32) {
|
|
115
|
+
let n = textureDimensions(src).x;
|
|
116
|
+
var c: array<vec4f, 9>;
|
|
117
|
+
for (var k = 0u; k < 9u; k++) { c[k] = vec4f(0.0); }
|
|
118
|
+
let total = 6u * n * n;
|
|
119
|
+
for (var i = t; i < total; i += 64u) {
|
|
120
|
+
let face = i / (n * n);
|
|
121
|
+
let r = i % (n * n);
|
|
122
|
+
let x = r % n;
|
|
123
|
+
let y = r / n;
|
|
124
|
+
let uv = (vec2f(f32(x), f32(y)) + 0.5) / f32(n);
|
|
125
|
+
let st = uv * 2.0 - 1.0;
|
|
126
|
+
// Solid angle of the texel.
|
|
127
|
+
let w = 4.0 / (f32(n * n) * pow(1.0 + dot(st, st), 1.5));
|
|
128
|
+
let radiance = textureLoad(src, vec2u(x, y), face, 0).rgb;
|
|
129
|
+
let b = sh_basis(cube_dir(face, uv));
|
|
130
|
+
for (var k = 0u; k < 9u; k++) { c[k] += vec4f(radiance * b[k] * w, w); }
|
|
131
|
+
}
|
|
132
|
+
acc[t] = c;
|
|
133
|
+
workgroupBarrier();
|
|
134
|
+
for (var stride = 32u; stride > 0u; stride = stride / 2u) {
|
|
135
|
+
if (t < stride) {
|
|
136
|
+
for (var k = 0u; k < 9u; k++) { acc[t][k] += acc[t + stride][k]; }
|
|
137
|
+
}
|
|
138
|
+
workgroupBarrier();
|
|
139
|
+
}
|
|
140
|
+
if (t == 0u) {
|
|
141
|
+
// Normalize the solid angles to exactly 4\u03C0, then apply the cosine lobe (\xC2_l / \u03C0).
|
|
142
|
+
let norm = 4.0 * PI / acc[0][0].w;
|
|
143
|
+
let band = array<f32, 9>(1.0, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 0.25, 0.25, 0.25, 0.25, 0.25);
|
|
144
|
+
for (var k = 0u; k < 9u; k++) { sh[k] = vec4f(acc[0][k].rgb * norm * band[k], 0.0); }
|
|
145
|
+
}
|
|
146
|
+
}`,
|
|
147
|
+
"shard::env::specular": `
|
|
148
|
+
import shard::env::common::{ PI, cube_dir, hammersley, importance_ggx };
|
|
149
|
+
import shard::pbr::brdf::d_ggx;
|
|
150
|
+
|
|
151
|
+
struct Params { roughness: f32, source_size: f32, size: f32, _pad: f32 }
|
|
152
|
+
|
|
153
|
+
@group(0) @binding(0) var src: texture_cube<f32>;
|
|
154
|
+
@group(0) @binding(1) var src_sampler: sampler;
|
|
155
|
+
@group(0) @binding(2) var dst: texture_storage_2d_array<rgba16float, write>;
|
|
156
|
+
@group(0) @binding(3) var<uniform> params: Params;
|
|
157
|
+
|
|
158
|
+
const SAMPLES: u32 = 64u;
|
|
159
|
+
|
|
160
|
+
/** GGX prefiltering with filtered importance sampling (K\u0159iv\xE1nek & Colbert): n = v = r. */
|
|
161
|
+
@compute @workgroup_size(8, 8, 1)
|
|
162
|
+
fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
163
|
+
let size = u32(params.size);
|
|
164
|
+
if (id.x >= size || id.y >= size) { return; }
|
|
165
|
+
let n = cube_dir(id.z, (vec2f(id.xy) + 0.5) / f32(size));
|
|
166
|
+
if (params.roughness <= 0.0) {
|
|
167
|
+
let c = textureSampleLevel(src, src_sampler, n, log2(params.source_size / f32(size)));
|
|
168
|
+
textureStore(dst, id.xy, id.z, vec4f(c.rgb, 1.0));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let a = params.roughness * params.roughness;
|
|
172
|
+
let omega_p = 4.0 * PI / (6.0 * params.source_size * params.source_size);
|
|
173
|
+
var color = vec3f(0.0);
|
|
174
|
+
var weight = 0.0;
|
|
175
|
+
for (var i = 0u; i < SAMPLES; i++) {
|
|
176
|
+
let h = importance_ggx(hammersley(i, SAMPLES), n, a);
|
|
177
|
+
let l = normalize(2.0 * dot(n, h) * h - n);
|
|
178
|
+
let n_dot_l = dot(n, l);
|
|
179
|
+
if (n_dot_l <= 0.0) { continue; }
|
|
180
|
+
let n_dot_h = max(dot(n, h), 0.0);
|
|
181
|
+
// pdf of l with v = n: D(h) \xB7 (n\xB7h) / (4 (v\xB7h)) = D / 4.
|
|
182
|
+
let pdf = d_ggx(n_dot_h, a) * 0.25 + 1e-6;
|
|
183
|
+
let omega_s = 1.0 / (f32(SAMPLES) * pdf);
|
|
184
|
+
let lod = max(0.5 * log2(omega_s / omega_p) + 1.0, 0.0);
|
|
185
|
+
color += textureSampleLevel(src, src_sampler, l, lod).rgb * n_dot_l;
|
|
186
|
+
weight += n_dot_l;
|
|
187
|
+
}
|
|
188
|
+
textureStore(dst, id.xy, id.z, vec4f(color / max(weight, 1e-6), 1.0));
|
|
189
|
+
}`,
|
|
190
|
+
"shard::env::brdf_lut": `
|
|
191
|
+
import shard::env::common::{ hammersley, importance_ggx };
|
|
192
|
+
import shard::pbr::brdf::v_smith_ggx_correlated;
|
|
193
|
+
|
|
194
|
+
@group(0) @binding(0) var lut: texture_storage_2d<rgba16float, write>;
|
|
195
|
+
|
|
196
|
+
const SAMPLES: u32 = 256u;
|
|
197
|
+
|
|
198
|
+
/** Split-sum BRDF scale and bias: x = n\xB7v, y = roughness. Same visibility term as direct light. */
|
|
199
|
+
@compute @workgroup_size(8, 8, 1)
|
|
200
|
+
fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
201
|
+
let dim = textureDimensions(lut);
|
|
202
|
+
if (id.x >= dim.x || id.y >= dim.y) { return; }
|
|
203
|
+
let n_dot_v = (f32(id.x) + 0.5) / f32(dim.x);
|
|
204
|
+
let roughness = (f32(id.y) + 0.5) / f32(dim.y);
|
|
205
|
+
let a = roughness * roughness;
|
|
206
|
+
let v = vec3f(sqrt(1.0 - n_dot_v * n_dot_v), 0.0, n_dot_v);
|
|
207
|
+
let n = vec3f(0.0, 0.0, 1.0);
|
|
208
|
+
var scale = 0.0;
|
|
209
|
+
var bias = 0.0;
|
|
210
|
+
for (var i = 0u; i < SAMPLES; i++) {
|
|
211
|
+
let h = importance_ggx(hammersley(i, SAMPLES), n, a);
|
|
212
|
+
let l = 2.0 * dot(v, h) * h - v;
|
|
213
|
+
let n_dot_l = max(l.z, 0.0);
|
|
214
|
+
let n_dot_h = max(h.z, 0.0);
|
|
215
|
+
let v_dot_h = max(dot(v, h), 0.0);
|
|
216
|
+
if (n_dot_l <= 0.0) { continue; }
|
|
217
|
+
let g_vis = v_smith_ggx_correlated(n_dot_v, n_dot_l, a) * 4.0 * n_dot_l * v_dot_h / max(n_dot_h, 1e-6);
|
|
218
|
+
let fc = pow(1.0 - v_dot_h, 5.0);
|
|
219
|
+
scale += (1.0 - fc) * g_vis;
|
|
220
|
+
bias += fc * g_vis;
|
|
221
|
+
}
|
|
222
|
+
textureStore(lut, id.xy, vec4f(scale / f32(SAMPLES), bias / f32(SAMPLES), 0.0, 1.0));
|
|
223
|
+
}`,
|
|
224
|
+
"shard::sky::background": `
|
|
225
|
+
import shard::view::view;
|
|
226
|
+
import shard::pbr::environment::environment_background;
|
|
227
|
+
|
|
228
|
+
struct Background {
|
|
229
|
+
/** brightness, unused \xD73. */
|
|
230
|
+
params: vec4f,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@group(1) @binding(0) var<uniform> background: Background;
|
|
234
|
+
|
|
235
|
+
struct BackgroundOutput {
|
|
236
|
+
@builtin(position) clip: vec4f,
|
|
237
|
+
@location(0) ndc: vec2f,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** A fullscreen triangle at depth 0 (reversed-Z infinity): it only draws where nothing else did. */
|
|
241
|
+
@vertex fn vs(@builtin(vertex_index) i: u32) -> BackgroundOutput {
|
|
242
|
+
let xy = vec2f(f32((i << 1u) & 2u), f32(i & 2u)) * 2.0 - 1.0;
|
|
243
|
+
var out: BackgroundOutput;
|
|
244
|
+
out.clip = vec4f(xy, 0.0, 1.0);
|
|
245
|
+
out.ndc = xy;
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@fragment fn fs(in: BackgroundOutput) -> @location(0) vec4f {
|
|
250
|
+
let near = view.invViewProj * vec4f(in.ndc, 1.0, 1.0);
|
|
251
|
+
let far = view.invViewProj * vec4f(in.ndc, 0.5, 1.0);
|
|
252
|
+
let d = normalize(far.xyz / far.w - near.xyz / near.w);
|
|
253
|
+
let color = environment_background(d) * background.params.x;
|
|
254
|
+
// Pre-exposed, and kept inside what rgba16float holds.
|
|
255
|
+
return vec4f(min(color * view.exposure, vec3f(60000.0)), 1.0);
|
|
256
|
+
}`
|
|
257
|
+
};
|
|
258
|
+
export {
|
|
259
|
+
ENVIRONMENT_SHADERS
|
|
260
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
2
|
+
import type { Texture } from '@aethervtt/shard-texture';
|
|
3
|
+
import type { CameraAtmosphere } from './atmosphere-state.js';
|
|
4
|
+
import type { CameraData } from './view.js';
|
|
5
|
+
/** Frames between rebakes of a moving camera's atmosphere environment. */
|
|
6
|
+
export declare const REBAKE_FRAMES = 8;
|
|
7
|
+
export declare const SOURCE_SIZE = 512;
|
|
8
|
+
export declare const SKY_SIZE = 256;
|
|
9
|
+
export declare const SPECULAR_SIZE = 256;
|
|
10
|
+
export declare const SPECULAR_MIPS = 6;
|
|
11
|
+
export declare const LUT_SIZE = 128;
|
|
12
|
+
/** A prefiltered environment: source cube (with mips), specular cube, and SH9 irradiance. */
|
|
13
|
+
export declare class Environment {
|
|
14
|
+
key: string;
|
|
15
|
+
/** map: from a texture; atmosphere: baked from a camera's atmosphere (and ProceduralSky). */
|
|
16
|
+
kind: 'map' | 'atmosphere';
|
|
17
|
+
size: number;
|
|
18
|
+
source: GPUTexture;
|
|
19
|
+
specular: GPUTexture;
|
|
20
|
+
specularView: GPUTextureView;
|
|
21
|
+
sourceView: GPUTextureView;
|
|
22
|
+
readonly sh: GpuBuffer;
|
|
23
|
+
/** 'pending' until the GPU has prefiltered the current key. */
|
|
24
|
+
state: 'pending' | 'ready';
|
|
25
|
+
/** How many times it's been (re)baked or prefiltered. */
|
|
26
|
+
bakes: number;
|
|
27
|
+
lastUsed: number;
|
|
28
|
+
generation: number;
|
|
29
|
+
/** What to prefilter from: a texture (maps) or a camera's atmospheres. */
|
|
30
|
+
input: Texture | undefined;
|
|
31
|
+
atmosphere: CameraAtmosphere | undefined;
|
|
32
|
+
/** The frame a rebake was last queued (atmospheres rebake at most every REBAKE_FRAMES). */
|
|
33
|
+
queuedFrame: number;
|
|
34
|
+
constructor(gpu: GpuContext, kind: 'map' | 'atmosphere');
|
|
35
|
+
destroy(): void;
|
|
36
|
+
}
|
|
37
|
+
/** What a camera is lit and backed by this frame. */
|
|
38
|
+
export interface CameraEnvironment {
|
|
39
|
+
environment: Environment | undefined;
|
|
40
|
+
intensity: number;
|
|
41
|
+
rotation: number;
|
|
42
|
+
/** Background: -1 none, otherwise the skybox brightness. */
|
|
43
|
+
background: number;
|
|
44
|
+
/** Lit by an atmosphere (a planet's, or ProceduralSky's): its sky pass draws the background. */
|
|
45
|
+
sky: boolean;
|
|
46
|
+
/** Atmosphere cameras: the environment map behind the sky (a star field), with its settings. */
|
|
47
|
+
backdrop: {
|
|
48
|
+
environment: Environment;
|
|
49
|
+
intensity: number;
|
|
50
|
+
rotation: number;
|
|
51
|
+
} | undefined;
|
|
52
|
+
}
|
|
53
|
+
/** Environments by key, the BRDF lookup table, and the prefilter work for this frame. */
|
|
54
|
+
export declare class EnvironmentStore {
|
|
55
|
+
readonly environments: Map<string, Environment>;
|
|
56
|
+
readonly cameras: Map<number, CameraEnvironment>;
|
|
57
|
+
readonly pending: Environment[];
|
|
58
|
+
lut: GPUTexture | undefined;
|
|
59
|
+
lutReady: boolean;
|
|
60
|
+
emptyCube: GPUTexture | undefined;
|
|
61
|
+
emptyView: GPUTextureView | undefined;
|
|
62
|
+
emptySh: GpuBuffer | undefined;
|
|
63
|
+
sampler: GPUSampler | undefined;
|
|
64
|
+
generation: number;
|
|
65
|
+
frame: number;
|
|
66
|
+
/** The frame the prefilter work last ran (it runs once per frame, for all views). */
|
|
67
|
+
ranFrame: number;
|
|
68
|
+
ensure(gpu: GpuContext): void;
|
|
69
|
+
}
|
|
70
|
+
export declare const Environments: import("@aethervtt/shard-core").ResourceDef<EnvironmentStore>;
|
|
71
|
+
export declare function environmentParams(store: EnvironmentStore, cam: CameraData, out: Float32Array): Environment | undefined;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { defineResource } from "@aethervtt/shard-core";
|
|
2
|
+
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
3
|
+
const REBAKE_FRAMES = 8;
|
|
4
|
+
const SOURCE_SIZE = 512;
|
|
5
|
+
const SKY_SIZE = 256;
|
|
6
|
+
const SPECULAR_SIZE = 256;
|
|
7
|
+
const SPECULAR_MIPS = 6;
|
|
8
|
+
const LUT_SIZE = 128;
|
|
9
|
+
class Environment {
|
|
10
|
+
key = "";
|
|
11
|
+
/** map: from a texture; atmosphere: baked from a camera's atmosphere (and ProceduralSky). */
|
|
12
|
+
kind;
|
|
13
|
+
size;
|
|
14
|
+
source;
|
|
15
|
+
specular;
|
|
16
|
+
specularView;
|
|
17
|
+
sourceView;
|
|
18
|
+
sh;
|
|
19
|
+
/** 'pending' until the GPU has prefiltered the current key. */
|
|
20
|
+
state = "pending";
|
|
21
|
+
/** How many times it's been (re)baked or prefiltered. */
|
|
22
|
+
bakes = 0;
|
|
23
|
+
lastUsed = 0;
|
|
24
|
+
generation;
|
|
25
|
+
/** What to prefilter from: a texture (maps) or a camera's atmospheres. */
|
|
26
|
+
input;
|
|
27
|
+
atmosphere;
|
|
28
|
+
/** The frame a rebake was last queued (atmospheres rebake at most every REBAKE_FRAMES). */
|
|
29
|
+
queuedFrame = -1e6;
|
|
30
|
+
constructor(gpu, kind) {
|
|
31
|
+
this.kind = kind;
|
|
32
|
+
this.size = kind === "atmosphere" ? SKY_SIZE : SOURCE_SIZE;
|
|
33
|
+
this.generation = gpu.generation;
|
|
34
|
+
const mips = Math.log2(this.size) + 1;
|
|
35
|
+
this.source = gpu.device.createTexture({
|
|
36
|
+
label: `environment/${kind}/source`,
|
|
37
|
+
size: [this.size, this.size, 6],
|
|
38
|
+
format: "rgba16float",
|
|
39
|
+
mipLevelCount: mips,
|
|
40
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC
|
|
41
|
+
});
|
|
42
|
+
this.specular = gpu.device.createTexture({
|
|
43
|
+
label: `environment/${kind}/specular`,
|
|
44
|
+
size: [SPECULAR_SIZE, SPECULAR_SIZE, 6],
|
|
45
|
+
format: "rgba16float",
|
|
46
|
+
mipLevelCount: SPECULAR_MIPS,
|
|
47
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC
|
|
48
|
+
});
|
|
49
|
+
this.sourceView = this.source.createView({ dimension: "cube" });
|
|
50
|
+
this.specularView = this.specular.createView({ dimension: "cube" });
|
|
51
|
+
this.sh = new GpuBuffer(gpu, {
|
|
52
|
+
label: `environment/${kind}/sh`,
|
|
53
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
|
54
|
+
size: 9 * 16
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
destroy() {
|
|
58
|
+
this.source.destroy();
|
|
59
|
+
this.specular.destroy();
|
|
60
|
+
this.sh.destroy();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
class EnvironmentStore {
|
|
64
|
+
environments = /* @__PURE__ */ new Map();
|
|
65
|
+
cameras = /* @__PURE__ */ new Map();
|
|
66
|
+
pending = [];
|
|
67
|
+
lut;
|
|
68
|
+
lutReady = false;
|
|
69
|
+
emptyCube;
|
|
70
|
+
emptyView;
|
|
71
|
+
emptySh;
|
|
72
|
+
sampler;
|
|
73
|
+
generation = -1;
|
|
74
|
+
frame = 0;
|
|
75
|
+
/** The frame the prefilter work last ran (it runs once per frame, for all views). */
|
|
76
|
+
ranFrame = -1;
|
|
77
|
+
ensure(gpu) {
|
|
78
|
+
if (this.generation === gpu.generation && this.lut) return;
|
|
79
|
+
this.generation = gpu.generation;
|
|
80
|
+
for (const env of this.environments.values()) env.destroy();
|
|
81
|
+
this.environments.clear();
|
|
82
|
+
this.pending.length = 0;
|
|
83
|
+
this.lut = gpu.device.createTexture({
|
|
84
|
+
label: "environment/brdf-lut",
|
|
85
|
+
size: [LUT_SIZE, LUT_SIZE],
|
|
86
|
+
format: "rgba16float",
|
|
87
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING
|
|
88
|
+
});
|
|
89
|
+
this.lutReady = false;
|
|
90
|
+
this.emptyCube = gpu.device.createTexture({
|
|
91
|
+
label: "environment/empty",
|
|
92
|
+
size: [1, 1, 6],
|
|
93
|
+
format: "rgba16float",
|
|
94
|
+
usage: GPUTextureUsage.TEXTURE_BINDING
|
|
95
|
+
});
|
|
96
|
+
this.emptyView = this.emptyCube.createView({ dimension: "cube" });
|
|
97
|
+
this.emptySh = new GpuBuffer(gpu, {
|
|
98
|
+
label: "environment/empty-sh",
|
|
99
|
+
usage: GPUBufferUsage.STORAGE,
|
|
100
|
+
size: 9 * 16
|
|
101
|
+
});
|
|
102
|
+
this.sampler = gpu.device.createSampler({
|
|
103
|
+
label: "environment/sampler",
|
|
104
|
+
magFilter: "linear",
|
|
105
|
+
minFilter: "linear",
|
|
106
|
+
mipmapFilter: "linear",
|
|
107
|
+
addressModeU: "clamp-to-edge",
|
|
108
|
+
addressModeV: "clamp-to-edge",
|
|
109
|
+
addressModeW: "clamp-to-edge"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const Environments = defineResource("render/Environments", {
|
|
114
|
+
description: "Prefiltered environment maps and procedural skies, shared by cameras.",
|
|
115
|
+
init: () => new EnvironmentStore()
|
|
116
|
+
});
|
|
117
|
+
function environmentParams(store, cam, out) {
|
|
118
|
+
const entry = store.cameras.get(cam.entity);
|
|
119
|
+
const env = entry?.environment;
|
|
120
|
+
if (!entry || !env || env.bakes === 0) {
|
|
121
|
+
out[0] = 0;
|
|
122
|
+
out[1] = 1;
|
|
123
|
+
out[2] = 0;
|
|
124
|
+
out[3] = 0;
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
out[0] = entry.intensity;
|
|
128
|
+
out[1] = Math.cos(entry.rotation);
|
|
129
|
+
out[2] = Math.sin(entry.rotation);
|
|
130
|
+
out[3] = 1;
|
|
131
|
+
return env;
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
Environment,
|
|
135
|
+
EnvironmentStore,
|
|
136
|
+
Environments,
|
|
137
|
+
LUT_SIZE,
|
|
138
|
+
REBAKE_FRAMES,
|
|
139
|
+
SKY_SIZE,
|
|
140
|
+
SOURCE_SIZE,
|
|
141
|
+
SPECULAR_MIPS,
|
|
142
|
+
SPECULAR_SIZE,
|
|
143
|
+
environmentParams
|
|
144
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { type CameraEnvironment, Environment, EnvironmentStore, Environments, environmentParams, REBAKE_FRAMES, SKY_SIZE, SOURCE_SIZE, SPECULAR_MIPS, SPECULAR_SIZE } from './environment-state.js';
|
|
3
|
+
import type { NodeContext } from './graph.js';
|
|
4
|
+
/** Environment luminance presets (cd/m² a texel of 1.0 represents). */
|
|
5
|
+
export declare const EnvironmentPresets: {
|
|
6
|
+
readonly 'overcast-sky': 2000;
|
|
7
|
+
readonly 'clear-sky': 8000;
|
|
8
|
+
};
|
|
9
|
+
export declare const EnvironmentMap: import("@aethervtt/shard-core").ComponentDef<{
|
|
10
|
+
readonly texture: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"Texture"> | null, "object">;
|
|
11
|
+
readonly intensity: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
12
|
+
readonly rotation: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const Skybox: import("@aethervtt/shard-core").ComponentDef<{
|
|
15
|
+
readonly brightness: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const ProceduralSky: import("@aethervtt/shard-core").ComponentDef<{
|
|
18
|
+
readonly turbidity: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
19
|
+
readonly rayleigh: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
20
|
+
readonly mie: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
21
|
+
readonly groundAlbedo: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
22
|
+
readonly sunDiskSize: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
23
|
+
}>;
|
|
24
|
+
export interface EnvironmentMapValue {
|
|
25
|
+
texture: {
|
|
26
|
+
guid?: string;
|
|
27
|
+
path?: string;
|
|
28
|
+
} | null;
|
|
29
|
+
intensity: number;
|
|
30
|
+
rotation: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ProceduralSkyValue {
|
|
33
|
+
turbidity: number;
|
|
34
|
+
rayleigh: number;
|
|
35
|
+
mie: number;
|
|
36
|
+
groundAlbedo: [number, number, number, number];
|
|
37
|
+
sunDiskSize: number;
|
|
38
|
+
}
|
|
39
|
+
export interface DefaultEnvironmentValue {
|
|
40
|
+
/** An environment map for cameras without one. */
|
|
41
|
+
map: EnvironmentMapValue | null;
|
|
42
|
+
/** A procedural sky for cameras without an environment of their own. */
|
|
43
|
+
sky: Partial<ProceduralSkyValue> | null;
|
|
44
|
+
/** Draw the default environment as the background (like Skybox). Default true. */
|
|
45
|
+
background: boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare const DefaultEnvironment: import("@aethervtt/shard-core").ResourceDef<DefaultEnvironmentValue>;
|
|
48
|
+
/**
|
|
49
|
+
* Resolves each camera's environment (its own map or sky, else the default environment) and
|
|
50
|
+
* queues prefiltering for environments whose source changed.
|
|
51
|
+
*/
|
|
52
|
+
export declare const prepareEnvironments: import("@aethervtt/shard-core").SystemDef<undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Fills level 0 of an environment's source cube (all six faces, `SKY_SIZE`²) for kinds other than
|
|
55
|
+
* maps, in the prefilter pass. Returns false when it can't yet (its inputs aren't ready): the
|
|
56
|
+
* environment stays pending. The atmosphere plugin registers `atmosphere`.
|
|
57
|
+
*/
|
|
58
|
+
export type EnvironmentBaker = (ctx: NodeContext, pass: GPUComputePassEncoder, env: Environment, level0: GPUTextureView) => boolean;
|
|
59
|
+
export declare const environmentBakers: Map<"atmosphere" | "map", EnvironmentBaker>;
|
|
60
|
+
/**
|
|
61
|
+
* Prefilters every pending environment (and the BRDF LUT, once): source cube, mips, SH9, and
|
|
62
|
+
* GGX specular mips. Runs once per frame, before any view draws.
|
|
63
|
+
*/
|
|
64
|
+
export declare function runEnvironmentWork(ctx: NodeContext): void;
|
|
65
|
+
/** The environment section of `render.describe`. */
|
|
66
|
+
export declare function describeEnvironment(world: World): {
|
|
67
|
+
views: Record<string, unknown>;
|
|
68
|
+
prefilteredEnvironments: number;
|
|
69
|
+
} | undefined;
|
|
70
|
+
export { type CameraEnvironment, Environment, EnvironmentStore, Environments, environmentParams, REBAKE_FRAMES, SKY_SIZE, SOURCE_SIZE, SPECULAR_MIPS, SPECULAR_SIZE, };
|