@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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Atmosphere scattering (spec 0044): planets' Atmosphere components and ProceduralSky, with their
3
+ * LUTs, sky, aerial perspective, and the environment bake that lights the scene from them.
4
+ */
5
+ export declare const atmospherePlugin: import("@aethervtt/shard-runtime").Plugin;
@@ -0,0 +1,42 @@
1
+ import { Last, ProfilerResource } from "@aethervtt/shard-core";
2
+ import { definePlugin } from "@aethervtt/shard-runtime";
3
+ import {
4
+ Atmosphere,
5
+ AtmosphereSettings,
6
+ atmosphereMethods,
7
+ describeAtmospheres,
8
+ selectAtmospheres
9
+ } from "./atmosphere.js";
10
+ import { AtmosphereGpuResource, addAtmosphereNodes, uploadAtmospheres } from "./atmosphere-nodes.js";
11
+ import { ATMOSPHERE_SHADERS } from "./atmosphere-shaders.js";
12
+ import { Atmospheres } from "./atmosphere-state.js";
13
+ import { extractLights } from "./lights.js";
14
+ import { RenderDescribers, RenderSet, Shaders } from "./plugin.js";
15
+ import { registerShaders } from "./shaders.js";
16
+ import { extractCameras } from "./view.js";
17
+ const atmospherePlugin = definePlugin({
18
+ name: "render/atmosphere",
19
+ // Skies bake into the environment, and ProceduralSky and DefaultEnvironment are its components.
20
+ dependencies: ["render/forward", "render/environment"],
21
+ provides: [Atmosphere, AtmosphereSettings, Atmospheres, AtmosphereGpuResource],
22
+ build(app) {
23
+ app.world.initResource(Atmospheres);
24
+ app.addSystems(
25
+ Last,
26
+ selectAtmospheres.inSet(RenderSet.Extract).after(extractCameras).after(extractLights),
27
+ uploadAtmospheres.inSet(RenderSet.Upload)
28
+ );
29
+ app.addMethod(...atmosphereMethods);
30
+ },
31
+ ready(app) {
32
+ registerShaders(app.world.resource(Shaders), ATMOSPHERE_SHADERS);
33
+ app.world.initResource(RenderDescribers).set(
34
+ "atmosphere",
35
+ (world) => describeAtmospheres(world, world.tryResource(ProfilerResource)?.all())
36
+ );
37
+ addAtmosphereNodes(app.world);
38
+ }
39
+ });
40
+ export {
41
+ atmospherePlugin
42
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * WGSL for atmospheres (spec 0044, Hillaire 2020): the shared model (`shard::atmosphere`), the
3
+ * transmittance and multiple-scattering LUTs, the per-camera sky-view LUT and aerial-perspective
4
+ * froxels, the sky pass, the aerial-perspective composite, and the environment bake. Mirrors
5
+ * `atmosphere-model.ts`; everything runs in km relative to the atmosphere's center.
6
+ */
7
+ export declare const ATMOSPHERE_SHADERS: Record<string, string>;
@@ -0,0 +1,595 @@
1
+ const ATMOSPHERE_SHADERS = {
2
+ "shard::atmosphere": `
3
+ const ATMO_PI: f32 = 3.14159265359;
4
+ /** LUTs store luminance in kcd/m\xB2, so rgba16float holds a noon sky. */
5
+ const KCD: f32 = 1000.0;
6
+ const TRANSMITTANCE_SIZE: vec2f = vec2f(256.0, 64.0);
7
+ const MULTISCATTER_SIZE: vec2f = vec2f(32.0, 32.0);
8
+
9
+ struct AtmosphereParams {
10
+ /** bottom, top, LUT floor (bottom \u2212 deck) in km; intensity. */
11
+ radii: vec4f,
12
+ /** Rayleigh scattering (1/km) rgb, 1 / scale height (1/km). */
13
+ rayleigh: vec4f,
14
+ /** Mie scattering, Mie absorption (1/km), 1 / scale height, LUT layer. */
15
+ mie: vec4f,
16
+ /** Mie asymmetry per channel. */
17
+ mie_g: vec4f,
18
+ /** Ozone-like absorption (1/km) rgb, tent center (km). */
19
+ absorption: vec4f,
20
+ /** Tent width (km). */
21
+ absorption_width: vec4f,
22
+ albedo: vec4f,
23
+ /** The camera relative to the center (km); w = 1 when the camera is inside. */
24
+ origin: vec4f,
25
+ }
26
+
27
+ struct Sun {
28
+ /** Toward the sun; w: angular radius (rad), 0 without a disk. */
29
+ direction: vec4f,
30
+ /** Illuminance (lux) per channel at the top of the atmosphere. */
31
+ illuminance: vec4f,
32
+ }
33
+
34
+ struct AtmosphereView {
35
+ /** Composite order, far to near. */
36
+ atmospheres: array<AtmosphereParams, 4>,
37
+ suns: array<Sun, 2>,
38
+ /** count, primary index (-1: none), sun count, aerial perspective on (1). */
39
+ info: vec4f,
40
+ /** Sky-view frame of the primary: up (radial), azimuth 0, azimuth 90\xB0. */
41
+ frame_up: vec4f,
42
+ frame_x: vec4f,
43
+ frame_y: vec4f,
44
+ /** Froxels: max distance (km), first-slice scale D0 (km), slices, 1 / ln(1 + max / D0). */
45
+ froxels: vec4f,
46
+ /** Clip space to camera-relative positions (m): the inverse view-projection without translation. */
47
+ inv_view_proj: mat4x4f,
48
+ /** Camera position in world space (m); w: exposure. */
49
+ camera: vec4f,
50
+ }
51
+
52
+ /** Near and far distances along unit d to the sphere of radius r at the origin; (-1, -1): miss. */
53
+ fn ray_sphere(o: vec3f, d: vec3f, r: f32) -> vec2f {
54
+ let b = dot(o, d);
55
+ let c = dot(o, o) - r * r;
56
+ let disc = b * b - c;
57
+ if (disc < 0.0) { return vec2f(-1.0, -1.0); }
58
+ let s = sqrt(disc);
59
+ return vec2f(-b - s, -b + s);
60
+ }
61
+
62
+ struct Medium {
63
+ rayleigh: vec3f,
64
+ mie: f32,
65
+ scattering: vec3f,
66
+ extinction: vec3f,
67
+ }
68
+
69
+ /** The medium at altitude h km above the bottom (negative in a gas giant's deck). */
70
+ fn medium_at(a: AtmosphereParams, h: f32) -> Medium {
71
+ let hc = max(h, a.radii.z - a.radii.x);
72
+ let dr = exp(-hc * a.rayleigh.w);
73
+ let dm = exp(-hc * a.mie.z);
74
+ let da = max(0.0, 1.0 - abs(hc - a.absorption.w) / (0.5 * a.absorption_width.x));
75
+ var m: Medium;
76
+ m.rayleigh = a.rayleigh.xyz * dr;
77
+ m.mie = a.mie.x * dm;
78
+ m.scattering = m.rayleigh + vec3f(m.mie);
79
+ m.extinction = m.scattering + vec3f(a.mie.y * dm) + a.absorption.xyz * da;
80
+ return m;
81
+ }
82
+
83
+ fn rayleigh_phase(mu: f32) -> f32 {
84
+ return 3.0 / (16.0 * ATMO_PI) * (1.0 + mu * mu);
85
+ }
86
+
87
+ /** Cornette-Shanks, per channel. */
88
+ fn mie_phase(mu: f32, g: vec3f) -> vec3f {
89
+ let g2 = g * g;
90
+ let denom = (2.0 + g2) * pow(1.0 + g2 - 2.0 * g * mu, vec3f(1.5));
91
+ return 3.0 / (8.0 * ATMO_PI) * ((1.0 - g2) * (1.0 + mu * mu)) / denom;
92
+ }
93
+
94
+ /** Texel-center remapping: unit 0 and 1 land on the first and last texel centers. */
95
+ fn unit_to_uv(x: vec2f, size: vec2f) -> vec2f { return (0.5 + x * (size - 1.0)) / size; }
96
+ fn uv_to_unit(uv: vec2f, size: vec2f) -> vec2f { return (uv * size - 0.5) / (size - 1.0); }
97
+
98
+ /** Transmittance LUT unit coordinates of radius r and zenith cosine mu (Bruneton). */
99
+ fn transmittance_unit(a: AtmosphereParams, r: f32, mu: f32) -> vec2f {
100
+ let rb = a.radii.z;
101
+ let rt = a.radii.y;
102
+ let H = sqrt(max(0.0, rt * rt - rb * rb));
103
+ let rho = sqrt(max(0.0, r * r - rb * rb));
104
+ let disc = r * r * (mu * mu - 1.0) + rt * rt;
105
+ let d = max(0.0, -r * mu + sqrt(max(disc, 0.0)));
106
+ let d_min = rt - r;
107
+ let d_max = rho + H;
108
+ return vec2f(select(0.0, (d - d_min) / (d_max - d_min), d_max > d_min), select(0.0, rho / H, H > 0.0));
109
+ }
110
+
111
+ fn transmittance_r_mu(a: AtmosphereParams, x: vec2f) -> vec2f {
112
+ let rb = a.radii.z;
113
+ let rt = a.radii.y;
114
+ let H = sqrt(max(0.0, rt * rt - rb * rb));
115
+ let rho = H * x.y;
116
+ let r = sqrt(rho * rho + rb * rb);
117
+ let d_min = rt - r;
118
+ let d_max = rho + H;
119
+ let d = d_min + x.x * (d_max - d_min);
120
+ var mu = 1.0;
121
+ if (d > 0.0) { mu = (H * H - rho * rho - d * d) / (2.0 * r * d); }
122
+ return vec2f(r, clamp(mu, -1.0, 1.0));
123
+ }
124
+
125
+ fn sample_transmittance(lut: texture_2d_array<f32>, s: sampler, a: AtmosphereParams, r: f32, mu: f32) -> vec3f {
126
+ let uv = unit_to_uv(clamp(transmittance_unit(a, r, mu), vec2f(0.0), vec2f(1.0)), TRANSMITTANCE_SIZE);
127
+ return textureSampleLevel(lut, s, uv, i32(a.mie.w), 0.0).rgb;
128
+ }
129
+
130
+ /** Transmittance from p toward a sun; zero in the planet's shadow. */
131
+ fn sun_transmittance(lut: texture_2d_array<f32>, s: sampler, a: AtmosphereParams, p: vec3f, l: vec3f) -> vec3f {
132
+ if (ray_sphere(p, l, a.radii.z).x > 0.0) { return vec3f(0.0); }
133
+ let r = length(p);
134
+ return sample_transmittance(lut, s, a, r, dot(p, l) / r);
135
+ }
136
+
137
+ fn sample_multiscatter(lut: texture_2d_array<f32>, s: sampler, a: AtmosphereParams, r: f32, cos_sun: f32) -> vec3f {
138
+ let x = vec2f(cos_sun * 0.5 + 0.5, (r - a.radii.z) / (a.radii.y - a.radii.z));
139
+ let uv = unit_to_uv(clamp(x, vec2f(0.0), vec2f(1.0)), MULTISCATTER_SIZE);
140
+ return textureSampleLevel(lut, s, uv, i32(a.mie.w), 0.0).rgb;
141
+ }
142
+
143
+ /** Sample placement along a segment: quadratic toward the start, or toward the end. */
144
+ fn bunch(s: f32, toward_end: bool) -> f32 {
145
+ if (toward_end) { return 1.0 - (1.0 - s) * (1.0 - s); }
146
+ return s * s;
147
+ }
148
+
149
+ struct Segment {
150
+ /** In-scattered radiance (cd/m\xB2), before exposure. */
151
+ luminance: vec3f,
152
+ transmittance: vec3f,
153
+ /** The ray ended on the ground. */
154
+ ground: bool,
155
+ }
156
+
157
+ /**
158
+ * Radiance along a ray from o (km, relative to the center) in unit direction d, clipped to the
159
+ * atmosphere and to max_t km: single scattering of up to two suns, multiple scattering from the
160
+ * LUT, and (with ground) the lit surface where it ends on it. Samples bunch up near the start.
161
+ */
162
+ fn integrate(tlut: texture_2d_array<f32>, mlut: texture_2d_array<f32>, s: sampler, a: AtmosphereParams,
163
+ sun0: Sun, sun1: Sun, suns: u32, o: vec3f, d: vec3f, max_t: f32, steps: u32, with_ground: bool) -> Segment {
164
+ var out: Segment;
165
+ out.luminance = vec3f(0.0);
166
+ out.transmittance = vec3f(1.0);
167
+ out.ground = false;
168
+ let top = ray_sphere(o, d, a.radii.y);
169
+ if (top.y <= 0.0) { return out; }
170
+ let t0 = max(0.0, top.x);
171
+ var t1 = top.y;
172
+ var hits = false;
173
+ let g = ray_sphere(o, d, a.radii.z);
174
+ if (g.x > 0.0 && g.x < t1) {
175
+ t1 = g.x;
176
+ hits = true;
177
+ }
178
+ if (max_t < t1) {
179
+ t1 = max_t;
180
+ hits = false;
181
+ }
182
+ if (t1 <= t0) { return out; }
183
+ let span = t1 - t0;
184
+ let n = f32(steps);
185
+ // Samples bunch up where the air is densest: near the start from inside, near the end (the
186
+ // ground) for a ray that enters from space.
187
+ let from_space = t0 > 0.0;
188
+ var T = vec3f(1.0);
189
+ var L = vec3f(0.0);
190
+ for (var k = 0u; k < steps; k++) {
191
+ let s0 = bunch(f32(k) / n, from_space);
192
+ let s1 = bunch((f32(k) + 1.0) / n, from_space);
193
+ let sm = bunch((f32(k) + 0.5) / n, from_space);
194
+ let dt = span * (s1 - s0);
195
+ let p = o + d * (t0 + span * sm);
196
+ let r = length(p);
197
+ let m = medium_at(a, r - a.radii.x);
198
+ var S = vec3f(0.0);
199
+ for (var i = 0u; i < suns; i++) {
200
+ var sun = sun0;
201
+ if (i == 1u) { sun = sun1; }
202
+ let l = sun.direction.xyz;
203
+ let mu = dot(d, l);
204
+ let st = sun_transmittance(tlut, s, a, p, l);
205
+ let ms = sample_multiscatter(mlut, s, a, r, dot(p, l) / r);
206
+ S += ((m.rayleigh * rayleigh_phase(mu) + m.mie * mie_phase(mu, a.mie_g.xyz)) * st + ms * m.scattering) * sun.illuminance.rgb;
207
+ }
208
+ let ext = max(m.extinction, vec3f(1e-9));
209
+ let step_t = exp(-ext * dt);
210
+ L += T * S * (1.0 - step_t) / ext;
211
+ T *= step_t;
212
+ }
213
+ if (hits && with_ground) {
214
+ let p = o + d * t1;
215
+ let n_up = normalize(p);
216
+ for (var i = 0u; i < suns; i++) {
217
+ var sun = sun0;
218
+ if (i == 1u) { sun = sun1; }
219
+ let l = sun.direction.xyz;
220
+ let ndl = max(dot(n_up, l), 0.0);
221
+ if (ndl <= 0.0) { continue; }
222
+ let st = sun_transmittance(tlut, s, a, p * 1.00001, l);
223
+ L += T * sun.illuminance.rgb * st * ndl * a.albedo.rgb / ATMO_PI;
224
+ }
225
+ out.ground = true;
226
+ }
227
+ out.luminance = L * a.radii.w;
228
+ // Behind a background ray that ends on the ground there's nothing; geometry sits on it.
229
+ out.transmittance = select(T, vec3f(0.0), hits && with_ground);
230
+ return out;
231
+ }
232
+
233
+ /** Sky-view LUT uv of a direction in the primary's frame (full azimuth, horizon-dense latitude). */
234
+ fn sky_view_uv(a: AtmosphereParams, up: vec3f, fx: vec3f, fy: vec3f, d: vec3f, size: vec2f) -> vec2f {
235
+ let r = max(length(a.origin.xyz), a.radii.z + 1e-4);
236
+ let v_horizon = sqrt(max(r * r - a.radii.z * a.radii.z, 0.0));
237
+ let beta = acos(clamp(v_horizon / r, -1.0, 1.0));
238
+ let zenith_horizon = ATMO_PI - beta;
239
+ let zenith = acos(clamp(dot(d, up), -1.0, 1.0));
240
+ var v = 0.0;
241
+ if (zenith < zenith_horizon) {
242
+ v = (1.0 - sqrt(max(1.0 - zenith / zenith_horizon, 0.0))) * 0.5;
243
+ } else {
244
+ v = sqrt(max((zenith - zenith_horizon) / max(beta, 1e-6), 0.0)) * 0.5 + 0.5;
245
+ }
246
+ var az = atan2(dot(d, fy), dot(d, fx));
247
+ if (az < 0.0) { az += 2.0 * ATMO_PI; }
248
+ let u = az / (2.0 * ATMO_PI);
249
+ // v on texel centers; u wraps (the sampler repeats).
250
+ return vec2f(u, (0.5 + clamp(v, 0.0, 1.0) * (size.y - 1.0)) / size.y);
251
+ }
252
+
253
+ /** The direction of a sky-view texel (uv in 0..1). */
254
+ fn sky_view_dir(a: AtmosphereParams, up: vec3f, fx: vec3f, fy: vec3f, uv: vec2f, size: vec2f) -> vec3f {
255
+ let r = max(length(a.origin.xyz), a.radii.z + 1e-4);
256
+ let v_horizon = sqrt(max(r * r - a.radii.z * a.radii.z, 0.0));
257
+ let beta = acos(clamp(v_horizon / r, -1.0, 1.0));
258
+ let zenith_horizon = ATMO_PI - beta;
259
+ let v = clamp((uv.y * size.y - 0.5) / (size.y - 1.0), 0.0, 1.0);
260
+ var zenith = 0.0;
261
+ if (v < 0.5) {
262
+ let c = 1.0 - 2.0 * v;
263
+ zenith = zenith_horizon * (1.0 - c * c);
264
+ } else {
265
+ let c = v * 2.0 - 1.0;
266
+ zenith = zenith_horizon + beta * c * c;
267
+ }
268
+ let az = uv.x * 2.0 * ATMO_PI;
269
+ return normalize(up * cos(zenith) + (fx * cos(az) + fy * sin(az)) * sin(zenith));
270
+ }
271
+
272
+ /** Distance (km) of froxel slice coordinate s in 0..1 (exponential slices). */
273
+ fn froxel_distance(f: vec4f, s: f32) -> f32 {
274
+ return f.y * (exp(s / f.w) - 1.0);
275
+ }
276
+
277
+ fn froxel_slice(f: vec4f, t: f32) -> f32 {
278
+ return log(1.0 + t / f.y) * f.w;
279
+ }
280
+
281
+ /** Camera-relative position (m) at a uv and (reversed-Z) depth. */
282
+ fn relative_at(atmo: AtmosphereView, uv: vec2f, depth: f32) -> vec3f {
283
+ let ndc = vec2f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
284
+ let p = atmo.inv_view_proj * vec4f(ndc, depth, 1.0);
285
+ return p.xyz / p.w;
286
+ }
287
+
288
+ /** The view ray through a uv. */
289
+ fn ray_at(atmo: AtmosphereView, uv: vec2f) -> vec3f {
290
+ let ndc = vec2f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
291
+ let near = atmo.inv_view_proj * vec4f(ndc, 1.0, 1.0);
292
+ let far = atmo.inv_view_proj * vec4f(ndc, 0.5, 1.0);
293
+ return normalize(far.xyz / far.w - near.xyz / near.w);
294
+ }`,
295
+ "shard::atmosphere::transmittance_lut": `
296
+ import shard::atmosphere::{ AtmosphereParams, transmittance_r_mu, medium_at, ray_sphere };
297
+
298
+ @group(0) @binding(0) var<uniform> params: AtmosphereParams;
299
+ @group(0) @binding(1) var lut: texture_storage_2d<rgba16float, write>;
300
+
301
+ const STEPS: u32 = 40u;
302
+
303
+ @compute @workgroup_size(8, 8, 1)
304
+ fn main(@builtin(global_invocation_id) id: vec3u) {
305
+ let size = textureDimensions(lut);
306
+ if (id.x >= size.x || id.y >= size.y) { return; }
307
+ let x = vec2f(f32(id.x), f32(id.y)) / (vec2f(size) - 1.0);
308
+ let rm = transmittance_r_mu(params, x);
309
+ let o = vec3f(0.0, rm.x, 0.0);
310
+ let d = vec3f(sqrt(max(0.0, 1.0 - rm.y * rm.y)), rm.y, 0.0);
311
+ let top = ray_sphere(o, d, params.radii.y).y;
312
+ let dt = max(top, 0.0) / f32(STEPS);
313
+ var depth = vec3f(0.0);
314
+ for (var i = 0u; i < STEPS; i++) {
315
+ let p = o + d * ((f32(i) + 0.5) * dt);
316
+ depth += medium_at(params, length(p) - params.radii.x).extinction * dt;
317
+ }
318
+ textureStore(lut, id.xy, vec4f(exp(-depth), 1.0));
319
+ }`,
320
+ "shard::atmosphere::multiscatter_lut": `
321
+ import shard::atmosphere::{ ATMO_PI, AtmosphereParams, medium_at, ray_sphere, sun_transmittance };
322
+
323
+ @group(0) @binding(0) var<uniform> params: AtmosphereParams;
324
+ @group(0) @binding(1) var tlut: texture_2d_array<f32>;
325
+ @group(0) @binding(2) var lut_sampler: sampler;
326
+ @group(0) @binding(3) var lut: texture_storage_2d<rgba16float, write>;
327
+
328
+ const STEPS: u32 = 20u;
329
+ const DIRS: u32 = 8u;
330
+
331
+ /** L2 / (1 \u2212 f_ms) per unit illuminance: Hillaire's isotropic second-order approximation. */
332
+ @compute @workgroup_size(8, 8, 1)
333
+ fn main(@builtin(global_invocation_id) id: vec3u) {
334
+ let size = textureDimensions(lut);
335
+ if (id.x >= size.x || id.y >= size.y) { return; }
336
+ let a = params;
337
+ let x = vec2f(f32(id.x), f32(id.y)) / (vec2f(size) - 1.0);
338
+ let r = max(a.radii.z + x.y * (a.radii.y - a.radii.z), a.radii.z + 1e-3);
339
+ let cs = x.x * 2.0 - 1.0;
340
+ let l = vec3f(0.0, cs, sqrt(max(0.0, 1.0 - cs * cs)));
341
+ let o = vec3f(0.0, r, 0.0);
342
+ let isotropic = 1.0 / (4.0 * ATMO_PI);
343
+ var lum = vec3f(0.0);
344
+ var fms = vec3f(0.0);
345
+ for (var i = 0u; i < DIRS; i++) {
346
+ for (var j = 0u; j < DIRS; j++) {
347
+ let ct = 1.0 - 2.0 * (f32(i) + 0.5) / f32(DIRS);
348
+ let st = sqrt(max(0.0, 1.0 - ct * ct));
349
+ let phi = 2.0 * ATMO_PI * (f32(j) + 0.5) / f32(DIRS);
350
+ let d = vec3f(st * cos(phi), ct, st * sin(phi));
351
+ let g = ray_sphere(o, d, a.radii.z);
352
+ let hits = g.x > 0.0;
353
+ let t_max = select(ray_sphere(o, d, a.radii.y).y, g.x, hits);
354
+ let dt = t_max / f32(STEPS);
355
+ var th = vec3f(1.0);
356
+ for (var k = 0u; k < STEPS; k++) {
357
+ let p = o + d * ((f32(k) + 0.5) * dt);
358
+ let m = medium_at(a, length(p) - a.radii.x);
359
+ let sun_t = sun_transmittance(tlut, lut_sampler, a, p, l);
360
+ let ext = max(m.extinction, vec3f(1e-9));
361
+ let step_t = exp(-ext * dt);
362
+ lum += th * sun_t * m.scattering * isotropic * (1.0 - step_t) / ext;
363
+ fms += th * m.scattering * (1.0 - step_t) / ext;
364
+ th *= step_t;
365
+ }
366
+ if (hits) {
367
+ let p = o + d * t_max;
368
+ let ndl = max(dot(normalize(p), l), 0.0);
369
+ let sun_t = sun_transmittance(tlut, lut_sampler, a, p * 1.00001, l);
370
+ lum += th * sun_t * ndl * a.albedo.rgb / ATMO_PI;
371
+ }
372
+ }
373
+ }
374
+ let count = f32(DIRS * DIRS);
375
+ let psi = (lum / count) / max(vec3f(1.0) - fms / count, vec3f(1e-4));
376
+ textureStore(lut, id.xy, vec4f(psi, 1.0));
377
+ }`,
378
+ "shard::atmosphere::sky_view": `
379
+ import shard::atmosphere::{ AtmosphereView, KCD, integrate, sky_view_dir };
380
+
381
+ @group(0) @binding(0) var<uniform> atmo: AtmosphereView;
382
+ @group(0) @binding(1) var tlut: texture_2d_array<f32>;
383
+ @group(0) @binding(2) var mlut: texture_2d_array<f32>;
384
+ @group(0) @binding(3) var lut_sampler: sampler;
385
+ @group(0) @binding(4) var out_lut: texture_storage_2d<rgba16float, write>;
386
+
387
+ /** The primary's sky around the camera, every azimuth, with the lit ground below the horizon. */
388
+ @compute @workgroup_size(8, 8, 1)
389
+ fn main(@builtin(global_invocation_id) id: vec3u) {
390
+ let size = textureDimensions(out_lut);
391
+ if (id.x >= size.x || id.y >= size.y) { return; }
392
+ let a = atmo.atmospheres[u32(atmo.info.y)];
393
+ let uv = (vec2f(id.xy) + 0.5) / vec2f(size);
394
+ let d = sky_view_dir(a, atmo.frame_up.xyz, atmo.frame_x.xyz, atmo.frame_y.xyz, uv, vec2f(size));
395
+ let o = a.origin.xyz * (max(length(a.origin.xyz), a.radii.z + 1e-4) / max(length(a.origin.xyz), 1e-6));
396
+ let seg = integrate(tlut, mlut, lut_sampler, a, atmo.suns[0], atmo.suns[1], u32(atmo.info.z), o, d, 1e9, 32u, true);
397
+ textureStore(out_lut, id.xy, vec4f(seg.luminance / KCD, 1.0));
398
+ }`,
399
+ "shard::atmosphere::aerial": `
400
+ import shard::atmosphere::{ AtmosphereView, KCD, integrate, froxel_distance, ray_at };
401
+
402
+ @group(0) @binding(0) var<uniform> atmo: AtmosphereView;
403
+ @group(0) @binding(1) var tlut: texture_2d_array<f32>;
404
+ @group(0) @binding(2) var mlut: texture_2d_array<f32>;
405
+ @group(0) @binding(3) var lut_sampler: sampler;
406
+ @group(0) @binding(4) var out_scatter: texture_storage_3d<rgba16float, write>;
407
+ @group(0) @binding(5) var out_transmittance: texture_storage_3d<rgba16float, write>;
408
+
409
+ /**
410
+ * Aerial perspective froxels: along each froxel column's ray, in-scattering (kcd/m\xB2) and
411
+ * transmittance from the camera to each exponential slice's far end.
412
+ */
413
+ @compute @workgroup_size(8, 8, 1)
414
+ fn main(@builtin(global_invocation_id) id: vec3u) {
415
+ let size = textureDimensions(out_scatter);
416
+ if (id.x >= size.x || id.y >= size.y) { return; }
417
+ let a = atmo.atmospheres[u32(atmo.info.y)];
418
+ let uv = (vec2f(id.xy) + 0.5) / vec2f(size.xy);
419
+ let d = ray_at(atmo, uv);
420
+ let o = a.origin.xyz;
421
+ var L = vec3f(0.0);
422
+ var T = vec3f(1.0);
423
+ var t_prev = 0.0;
424
+ for (var k = 0u; k < size.z; k++) {
425
+ let t = froxel_distance(atmo.froxels, (f32(k) + 1.0) / f32(size.z));
426
+ // Two steps per slice, from where the last one ended.
427
+ let seg = integrate(tlut, mlut, lut_sampler, a, atmo.suns[0], atmo.suns[1], u32(atmo.info.z), o + d * t_prev, d, t - t_prev, 2u, false);
428
+ L += T * seg.luminance;
429
+ T *= seg.transmittance;
430
+ t_prev = t;
431
+ textureStore(out_scatter, vec3u(id.xy, k), vec4f(L / KCD, 1.0));
432
+ textureStore(out_transmittance, vec3u(id.xy, k), vec4f(T, 1.0));
433
+ }
434
+ }`,
435
+ "shard::atmosphere::sky": `
436
+ import shard::view::view;
437
+ import shard::atmosphere::{ AtmosphereView, ATMO_PI, KCD, integrate, sample_transmittance, sky_view_uv, ray_sphere, ray_at };
438
+
439
+ @group(1) @binding(0) var<uniform> atmo: AtmosphereView;
440
+ @group(1) @binding(1) var tlut: texture_2d_array<f32>;
441
+ @group(1) @binding(2) var mlut: texture_2d_array<f32>;
442
+ @group(1) @binding(3) var lut_sampler: sampler;
443
+ @group(1) @binding(4) var sky_view: texture_2d<f32>;
444
+ @group(1) @binding(5) var sky_view_sampler: sampler;
445
+ @group(1) @binding(6) var background: texture_cube<f32>;
446
+
447
+ struct BackgroundParams {
448
+ /** intensity (cd/m\xB2 per unit) \xD7 brightness, cos and sin of the rotation, enabled. */
449
+ env: vec4f,
450
+ /** Sky-view LUT size. */
451
+ size: vec4f,
452
+ }
453
+ @group(1) @binding(7) var<uniform> bg: BackgroundParams;
454
+
455
+ struct SkyOutput {
456
+ @builtin(position) clip: vec4f,
457
+ @location(0) ndc: vec2f,
458
+ }
459
+
460
+ /** A fullscreen triangle at depth 0 (reversed-Z infinity): it only draws where nothing else did. */
461
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> SkyOutput {
462
+ let xy = vec2f(f32((i << 1u) & 2u), f32(i & 2u)) * 2.0 - 1.0;
463
+ var out: SkyOutput;
464
+ out.clip = vec4f(xy, 0.0, 1.0);
465
+ out.ndc = xy;
466
+ return out;
467
+ }
468
+
469
+ @fragment fn fs(in: SkyOutput) -> @location(0) vec4f {
470
+ let d = ray_at(atmo, vec2f(in.ndc.x * 0.5 + 0.5, 0.5 - in.ndc.y * 0.5));
471
+ // What lies beyond every atmosphere: the environment (a star field), and the sun disks.
472
+ var color = vec3f(0.0);
473
+ if (bg.env.w > 0.5) {
474
+ let c = bg.env.y;
475
+ let s = bg.env.z;
476
+ let e = vec3f(c * d.x - s * d.z, d.y, s * d.x + c * d.z);
477
+ color = textureSampleLevel(background, lut_sampler, e, 0.0).rgb * bg.env.x;
478
+ }
479
+ let suns = u32(atmo.info.z);
480
+ for (var i = 0u; i < suns; i++) {
481
+ let sun = atmo.suns[i];
482
+ let radius = sun.direction.w;
483
+ if (radius <= 0.0) { continue; }
484
+ let cd = dot(d, sun.direction.xyz);
485
+ if (cd < cos(radius * 1.2)) { continue; }
486
+ let angle = acos(clamp(cd, -1.0, 1.0));
487
+ {
488
+ // E / solid angle, a soft edge, and a little limb darkening.
489
+ let x = clamp(angle / radius, 0.0, 1.0);
490
+ let edge = 1.0 - smoothstep(0.9, 1.2, angle / radius);
491
+ let limb = 1.0 - 0.6 * (1.0 - sqrt(max(0.0, 1.0 - x * x)));
492
+ color += sun.illuminance.rgb / (ATMO_PI * radius * radius) * edge * limb;
493
+ }
494
+ }
495
+ // Then through each atmosphere, far to near.
496
+ let count = u32(atmo.info.x);
497
+ let primary = i32(atmo.info.y);
498
+ for (var i = 0u; i < count; i++) {
499
+ let a = atmo.atmospheres[i];
500
+ if (i32(i) == primary && a.origin.w > 0.5) {
501
+ // Inside: the sky-view LUT, and transmittance to space unless the ground is in the way.
502
+ let o = a.origin.xyz;
503
+ let r = length(o);
504
+ let uv = sky_view_uv(a, atmo.frame_up.xyz, atmo.frame_x.xyz, atmo.frame_y.xyz, d, bg.size.xy);
505
+ let lum = textureSampleLevel(sky_view, sky_view_sampler, uv, 0.0).rgb * KCD;
506
+ // Transmittance matters only for what's behind (a star field, a sun disk, a farther limb).
507
+ var t = vec3f(0.0);
508
+ if (any(color > vec3f(0.0)) && ray_sphere(o, d, a.radii.z).x <= 0.0) { t = sample_transmittance(tlut, lut_sampler, a, r, dot(o, d) / r); }
509
+ color = color * t + lum;
510
+ } else {
511
+ let steps = select(8u, 16u, i32(i) == primary);
512
+ let seg = integrate(tlut, mlut, lut_sampler, a, atmo.suns[0], atmo.suns[1], suns, a.origin.xyz, d, 1e9, steps, true);
513
+ color = color * seg.transmittance + seg.luminance;
514
+ }
515
+ }
516
+ // Pre-exposed, and kept inside what rgba16float holds.
517
+ return vec4f(min(color * view.exposure, vec3f(60000.0)), 1.0);
518
+ }`,
519
+ "shard::atmosphere::composite": `
520
+ import shard::view::view;
521
+ import shard::post::common::uv_of;
522
+ import shard::atmosphere::{ AtmosphereView, KCD, integrate, froxel_slice, relative_at };
523
+
524
+ @group(1) @binding(0) var input: texture_2d<f32>;
525
+ @group(1) @binding(1) var depth_texture: texture_depth_2d;
526
+ @group(1) @binding(2) var<uniform> atmo: AtmosphereView;
527
+ @group(1) @binding(3) var tlut: texture_2d_array<f32>;
528
+ @group(1) @binding(4) var mlut: texture_2d_array<f32>;
529
+ @group(1) @binding(5) var lut_sampler: sampler;
530
+ @group(1) @binding(6) var ap_scatter: texture_3d<f32>;
531
+ @group(1) @binding(7) var ap_transmittance: texture_3d<f32>;
532
+
533
+ /**
534
+ * Aerial perspective on geometry: color \xD7 transmittance + in-scattering for every atmosphere the
535
+ * view ray crosses before the surface, far to near. The primary reads its froxels within their
536
+ * range; beyond it (terrain seen from orbit), and for the others, the ray is marched.
537
+ */
538
+ @fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
539
+ let px = vec2i(frag.xy);
540
+ let c = textureLoad(input, px, 0);
541
+ let depth = textureLoad(depth_texture, px, 0);
542
+ let count = u32(atmo.info.x);
543
+ if (depth <= 0.0 || count == 0u) { return c; }
544
+ let uv = uv_of(frag.xy);
545
+ let to = relative_at(atmo, uv, depth);
546
+ let dist = length(to) / 1000.0;
547
+ let d = to / max(length(to), 1e-6);
548
+ var color = c.rgb;
549
+ let primary = i32(atmo.info.y);
550
+ let suns = u32(atmo.info.z);
551
+ let froxels = atmo.froxels.x > 0.0 && atmo.info.w > 0.5;
552
+ for (var i = 0u; i < count; i++) {
553
+ let a = atmo.atmospheres[i];
554
+ if (i32(i) == primary && a.origin.w > 0.5 && froxels && dist < atmo.froxels.x) {
555
+ let s = froxel_slice(atmo.froxels, dist);
556
+ let n = atmo.froxels.z;
557
+ let coord = vec3f(uv, max(s - 0.5 / n, 0.0));
558
+ // The first slice fades in from the camera.
559
+ let near = clamp(s * n, 0.0, 1.0);
560
+ let lum = textureSampleLevel(ap_scatter, lut_sampler, coord, 0.0).rgb * KCD * near;
561
+ let t = mix(vec3f(1.0), textureSampleLevel(ap_transmittance, lut_sampler, coord, 0.0).rgb, near);
562
+ color = color * t + lum * view.exposure;
563
+ } else {
564
+ // Marched: the primary finely from inside, coarser from space; the others coarsest.
565
+ let steps = select(8u, select(12u, 24u, a.origin.w > 0.5), i32(i) == primary);
566
+ let seg = integrate(tlut, mlut, lut_sampler, a, atmo.suns[0], atmo.suns[1], suns, a.origin.xyz, d, dist, steps, false);
567
+ color = color * seg.transmittance + seg.luminance * view.exposure;
568
+ }
569
+ }
570
+ return vec4f(min(color, vec3f(60000.0)), c.a);
571
+ }`,
572
+ "shard::atmosphere::bake": `
573
+ import shard::env::common::cube_dir;
574
+ import shard::atmosphere::{ AtmosphereView, integrate };
575
+
576
+ @group(0) @binding(0) var<uniform> atmo: AtmosphereView;
577
+ @group(0) @binding(1) var tlut: texture_2d_array<f32>;
578
+ @group(0) @binding(2) var mlut: texture_2d_array<f32>;
579
+ @group(0) @binding(3) var lut_sampler: sampler;
580
+ @group(0) @binding(4) var dst: texture_storage_2d_array<rgba16float, write>;
581
+
582
+ /** The primary's sky from the camera into a cube face (no sun disk: the DirectionalLight is the sun). */
583
+ @compute @workgroup_size(8, 8, 1)
584
+ fn main(@builtin(global_invocation_id) id: vec3u) {
585
+ let size = textureDimensions(dst).x;
586
+ if (id.x >= size || id.y >= size) { return; }
587
+ let d = cube_dir(id.z, (vec2f(id.xy) + 0.5) / f32(size));
588
+ let a = atmo.atmospheres[u32(atmo.info.y)];
589
+ let seg = integrate(tlut, mlut, lut_sampler, a, atmo.suns[0], atmo.suns[1], u32(atmo.info.z), a.origin.xyz, d, 1e9, 32u, true);
590
+ textureStore(dst, id.xy, id.z, vec4f(min(seg.luminance, vec3f(60000.0)), 1.0));
591
+ }`
592
+ };
593
+ export {
594
+ ATMOSPHERE_SHADERS
595
+ };