@aethervtt/shard-render 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/ablation.d.ts +74 -0
  2. package/dist/ablation.js +117 -0
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.js +43 -2
  5. package/dist/atmosphere-nodes.d.ts +9 -0
  6. package/dist/atmosphere-nodes.js +157 -30
  7. package/dist/atmosphere-plugin.js +8 -2
  8. package/dist/atmosphere-shaders.js +17 -5
  9. package/dist/baseline/atmosphere.d.ts +16 -0
  10. package/dist/baseline/atmosphere.js +301 -0
  11. package/dist/baseline/data-texture.d.ts +36 -0
  12. package/dist/baseline/data-texture.js +181 -0
  13. package/dist/baseline/environment.d.ts +14 -0
  14. package/dist/baseline/environment.js +368 -0
  15. package/dist/baseline/exposure.d.ts +25 -0
  16. package/dist/baseline/exposure.js +133 -0
  17. package/dist/baseline/lights.d.ts +33 -0
  18. package/dist/baseline/lights.js +133 -0
  19. package/dist/baseline/textures.d.ts +97 -0
  20. package/dist/baseline/textures.js +203 -0
  21. package/dist/camera.d.ts +2 -0
  22. package/dist/camera.js +4 -0
  23. package/dist/crowd.d.ts +21 -0
  24. package/dist/crowd.js +121 -0
  25. package/dist/culling-shaders.js +21 -2
  26. package/dist/culling.d.ts +11 -2
  27. package/dist/culling.js +44 -18
  28. package/dist/cutaway-plugin.d.ts +48 -0
  29. package/dist/cutaway-plugin.js +364 -0
  30. package/dist/cutaway.d.ts +27 -0
  31. package/dist/cutaway.js +49 -0
  32. package/dist/data-store.d.ts +69 -0
  33. package/dist/data-store.js +105 -0
  34. package/dist/debug-shaders.js +17 -3
  35. package/dist/debug-views.js +4 -2
  36. package/dist/decals-plugin.d.ts +5 -0
  37. package/dist/decals-plugin.js +54 -0
  38. package/dist/decals.d.ts +69 -0
  39. package/dist/decals.js +326 -0
  40. package/dist/deferred-shaders.js +2 -0
  41. package/dist/deferred.js +28 -4
  42. package/dist/deform.d.ts +5 -4
  43. package/dist/deform.js +4 -9
  44. package/dist/describe-plugin.d.ts +4 -2
  45. package/dist/describe-plugin.js +75 -3
  46. package/dist/display-nodes.js +37 -13
  47. package/dist/dynamic-resolution.d.ts +2 -0
  48. package/dist/dynamic-resolution.js +6 -1
  49. package/dist/environment-plugin.js +17 -2
  50. package/dist/environment-state.d.ts +7 -3
  51. package/dist/environment-state.js +20 -9
  52. package/dist/environment.js +7 -1
  53. package/dist/features.d.ts +49 -0
  54. package/dist/features.js +106 -0
  55. package/dist/foliage-budget.d.ts +64 -0
  56. package/dist/foliage-budget.js +108 -0
  57. package/dist/foliage-path.d.ts +19 -0
  58. package/dist/foliage-path.js +7 -0
  59. package/dist/foliage-shaders.d.ts +11 -0
  60. package/dist/foliage-shaders.js +504 -0
  61. package/dist/foliage.d.ts +194 -0
  62. package/dist/foliage.js +1027 -0
  63. package/dist/forward.d.ts +38 -2
  64. package/dist/forward.js +608 -186
  65. package/dist/fxaa.js +12 -2
  66. package/dist/gizmos-plugin.js +7 -0
  67. package/dist/gizmos.d.ts +3 -3
  68. package/dist/gizmos.js +11 -13
  69. package/dist/gpu-assets.d.ts +58 -4
  70. package/dist/gpu-assets.js +191 -8
  71. package/dist/graph.d.ts +45 -0
  72. package/dist/graph.js +169 -19
  73. package/dist/health.d.ts +58 -0
  74. package/dist/health.js +112 -0
  75. package/dist/index.d.ts +40 -11
  76. package/dist/index.js +343 -9
  77. package/dist/instances.d.ts +119 -8
  78. package/dist/instances.js +387 -78
  79. package/dist/interior-plugin.d.ts +121 -0
  80. package/dist/interior-plugin.js +465 -0
  81. package/dist/interior.d.ts +24 -0
  82. package/dist/interior.js +13 -0
  83. package/dist/layers.d.ts +35 -0
  84. package/dist/layers.js +49 -0
  85. package/dist/lens-plugin.d.ts +43 -0
  86. package/dist/lens-plugin.js +304 -0
  87. package/dist/lens.d.ts +41 -0
  88. package/dist/lens.js +99 -0
  89. package/dist/lights.d.ts +50 -5
  90. package/dist/lights.js +101 -22
  91. package/dist/material-pipelines.d.ts +37 -4
  92. package/dist/material-pipelines.js +102 -5
  93. package/dist/materials.d.ts +23 -0
  94. package/dist/materials.js +44 -1
  95. package/dist/outline-plugin.d.ts +48 -0
  96. package/dist/outline-plugin.js +529 -0
  97. package/dist/outline.d.ts +15 -0
  98. package/dist/outline.js +42 -0
  99. package/dist/overlay-registry.d.ts +3 -1
  100. package/dist/overlay-registry.js +3 -1
  101. package/dist/overlays.js +6 -2
  102. package/dist/perf-overlay.d.ts +9 -0
  103. package/dist/perf-overlay.js +125 -0
  104. package/dist/picking.d.ts +17 -0
  105. package/dist/picking.js +40 -11
  106. package/dist/pixel-perfect-plugin.js +10 -2
  107. package/dist/plugin.d.ts +69 -7
  108. package/dist/plugin.js +223 -25
  109. package/dist/post-common.d.ts +22 -2
  110. package/dist/post-common.js +82 -2
  111. package/dist/post-nodes.d.ts +7 -1
  112. package/dist/post-nodes.js +235 -111
  113. package/dist/post-plugin.js +5 -3
  114. package/dist/post-scene.d.ts +130 -0
  115. package/dist/post-scene.js +164 -0
  116. package/dist/post-shaders.js +85 -62
  117. package/dist/post.js +11 -1
  118. package/dist/projection.d.ts +24 -0
  119. package/dist/projection.js +87 -0
  120. package/dist/readback.d.ts +1 -0
  121. package/dist/readback.js +1 -0
  122. package/dist/render-scale.js +1 -0
  123. package/dist/ribbons-plugin.d.ts +5 -0
  124. package/dist/ribbons-plugin.js +59 -0
  125. package/dist/ribbons.d.ts +44 -0
  126. package/dist/ribbons.js +288 -0
  127. package/dist/scenario.d.ts +70 -0
  128. package/dist/scenario.js +125 -0
  129. package/dist/screen-effects.d.ts +97 -0
  130. package/dist/screen-effects.js +192 -0
  131. package/dist/shaders.d.ts +13 -0
  132. package/dist/shaders.js +189 -34
  133. package/dist/shadow-catcher.d.ts +9 -0
  134. package/dist/shadow-catcher.js +133 -0
  135. package/dist/shadows.d.ts +27 -2
  136. package/dist/shadows.js +100 -20
  137. package/dist/standard-fields.d.ts +1 -0
  138. package/dist/standard-fields.js +5 -0
  139. package/dist/standard.js +18 -0
  140. package/dist/stats.d.ts +45 -1
  141. package/dist/stats.js +91 -3
  142. package/dist/surface-model.d.ts +57 -0
  143. package/dist/surface-model.js +289 -0
  144. package/dist/surface-shaders.d.ts +3 -0
  145. package/dist/surface-shaders.js +160 -0
  146. package/dist/surface.d.ts +40 -0
  147. package/dist/surface.js +86 -0
  148. package/dist/target.d.ts +1 -13
  149. package/dist/target.js +1 -26
  150. package/dist/testing.d.ts +11 -0
  151. package/dist/testing.js +39 -1
  152. package/dist/tier.d.ts +13 -0
  153. package/dist/tier.js +14 -0
  154. package/dist/timer.d.ts +18 -1
  155. package/dist/timer.js +73 -20
  156. package/dist/variants.d.ts +37 -0
  157. package/dist/variants.js +104 -0
  158. package/dist/vfx-draw.d.ts +37 -0
  159. package/dist/vfx-draw.js +291 -0
  160. package/dist/vfx-state.d.ts +42 -0
  161. package/dist/vfx-state.js +81 -0
  162. package/dist/vfx.d.ts +69 -0
  163. package/dist/vfx.js +103 -0
  164. package/dist/view-visibility-plugin.d.ts +61 -0
  165. package/dist/view-visibility-plugin.js +267 -0
  166. package/dist/view-visibility.d.ts +28 -0
  167. package/dist/view-visibility.js +46 -0
  168. package/dist/view.d.ts +29 -1
  169. package/dist/view.js +57 -34
  170. package/package.json +23 -11
@@ -0,0 +1,130 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ /** Each post effect's component and settings, by the name `?effects=` uses. */
3
+ export declare const POST_EFFECTS: {
4
+ readonly bloom: readonly [import("@aethervtt/shard-core").ComponentDef<{
5
+ readonly intensity: import("@aethervtt/shard-core").FieldType<number, "f32">;
6
+ readonly threshold: import("@aethervtt/shard-core").FieldType<number, "f32">;
7
+ readonly knee: import("@aethervtt/shard-core").FieldType<number, "f32">;
8
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
9
+ }>, {
10
+ readonly intensity: 0.12;
11
+ }];
12
+ readonly exposure: readonly [import("@aethervtt/shard-core").ComponentDef<{
13
+ readonly minEv: import("@aethervtt/shard-core").FieldType<number, "f32">;
14
+ readonly maxEv: import("@aethervtt/shard-core").FieldType<number, "f32">;
15
+ readonly compensation: import("@aethervtt/shard-core").FieldType<number, "f32">;
16
+ readonly speedUp: import("@aethervtt/shard-core").FieldType<number, "f32">;
17
+ readonly speedDown: import("@aethervtt/shard-core").FieldType<number, "f32">;
18
+ readonly metering: import("@aethervtt/shard-core").FieldType<"average" | "center" | "spot", "u8">;
19
+ }>, {
20
+ readonly compensation: 0.5;
21
+ }];
22
+ readonly dof: readonly [import("@aethervtt/shard-core").ComponentDef<{
23
+ readonly mode: import("@aethervtt/shard-core").FieldType<"bokeh" | "gaussian", "u8">;
24
+ readonly focusDistance: import("@aethervtt/shard-core").FieldType<number, "f32">;
25
+ readonly maxBlur: import("@aethervtt/shard-core").FieldType<number, "f32">;
26
+ }>, {
27
+ readonly focusDistance: 9;
28
+ readonly maxBlur: 0.012;
29
+ }];
30
+ readonly 'motion-blur': readonly [import("@aethervtt/shard-core").ComponentDef<{
31
+ readonly samples: import("@aethervtt/shard-core").FieldType<number, "u32">;
32
+ readonly maxBlur: import("@aethervtt/shard-core").FieldType<number, "f32">;
33
+ }>, {}];
34
+ readonly taa: readonly [import("@aethervtt/shard-core").ComponentDef<{
35
+ readonly mode: import("@aethervtt/shard-core").FieldType<"fxaa" | "msaa" | "none" | "taa", "u8">;
36
+ }>, {
37
+ readonly mode: 'taa';
38
+ }];
39
+ readonly fxaa: readonly [import("@aethervtt/shard-core").ComponentDef<{
40
+ readonly mode: import("@aethervtt/shard-core").FieldType<"fxaa" | "msaa" | "none" | "taa", "u8">;
41
+ }>, {
42
+ readonly mode: 'fxaa';
43
+ }];
44
+ readonly noaa: readonly [import("@aethervtt/shard-core").ComponentDef<{
45
+ readonly mode: import("@aethervtt/shard-core").FieldType<"fxaa" | "msaa" | "none" | "taa", "u8">;
46
+ }>, {
47
+ readonly mode: 'none';
48
+ }];
49
+ readonly ssao: readonly [import("@aethervtt/shard-core").ComponentDef<{
50
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
51
+ readonly intensity: import("@aethervtt/shard-core").FieldType<number, "f32">;
52
+ readonly quality: import("@aethervtt/shard-core").FieldType<"high" | "low" | "medium", "u8">;
53
+ }>, {
54
+ readonly radius: 0.6;
55
+ }];
56
+ readonly fog: readonly [import("@aethervtt/shard-core").ComponentDef<{
57
+ readonly color: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
58
+ readonly density: import("@aethervtt/shard-core").FieldType<number, "f32">;
59
+ readonly heightFalloff: import("@aethervtt/shard-core").FieldType<number, "f32">;
60
+ readonly start: import("@aethervtt/shard-core").FieldType<number, "f32">;
61
+ readonly sunScattering: import("@aethervtt/shard-core").FieldType<number, "f32">;
62
+ readonly mode: import("@aethervtt/shard-core").FieldType<"add" | "default", "u8">;
63
+ }>, {
64
+ readonly density: 0.015;
65
+ readonly heightFalloff: 0.08;
66
+ readonly sunScattering: 0.6;
67
+ }];
68
+ readonly grading: readonly [import("@aethervtt/shard-core").ComponentDef<{
69
+ readonly temperature: import("@aethervtt/shard-core").FieldType<number, "f32">;
70
+ readonly tint: import("@aethervtt/shard-core").FieldType<number, "f32">;
71
+ readonly saturation: import("@aethervtt/shard-core").FieldType<number, "f32">;
72
+ readonly contrast: import("@aethervtt/shard-core").FieldType<number, "f32">;
73
+ readonly lift: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
74
+ readonly gamma: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
75
+ readonly gain: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
76
+ readonly lut: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"Texture"> | null, "object">;
77
+ }>, {
78
+ readonly temperature: 0.1;
79
+ readonly saturation: 1.1;
80
+ readonly contrast: 1.05;
81
+ }];
82
+ readonly vignette: readonly [import("@aethervtt/shard-core").ComponentDef<{
83
+ readonly intensity: import("@aethervtt/shard-core").FieldType<number, "f32">;
84
+ readonly smoothness: import("@aethervtt/shard-core").FieldType<number, "f32">;
85
+ }>, {
86
+ readonly intensity: 0.25;
87
+ }];
88
+ };
89
+ export type PostEffectName = keyof typeof POST_EFFECTS;
90
+ /** The full stack: every effect but the other anti-aliasing modes (TAA is on). */
91
+ export declare const ALL_POST_EFFECTS: readonly PostEffectName[];
92
+ /** The components for effect names; unknown names are skipped. */
93
+ export declare function postEffects(names: readonly string[]): unknown[];
94
+ /** A ball on a circle round the courtyard. */
95
+ export declare const PostSpin: import("@aethervtt/shard-core").ComponentDef<{
96
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<number> | undefined) => import("@aethervtt/shard-core").FieldType<number, "f32">);
97
+ readonly speed: import("@aethervtt/shard-core").FieldType<number, "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<number> | undefined) => import("@aethervtt/shard-core").FieldType<number, "f32">);
98
+ readonly height: import("@aethervtt/shard-core").FieldType<number, "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<number> | undefined) => import("@aethervtt/shard-core").FieldType<number, "f32">);
99
+ }>;
100
+ /** Moves PostSpin balls along their circles by `Time.elapsed`. */
101
+ export declare const postSpin: import("@aethervtt/shard-core").SystemDef<{
102
+ q: import("@aethervtt/shard-core").Query;
103
+ }>;
104
+ /** The courtyard's moving parts: PostSpin and its system. Add it with the render plugins. */
105
+ export declare const postScenePlugin: import("@aethervtt/shard-runtime").Plugin;
106
+ /** Where the courtyard is looked at from: 9 m out, 2.2 m up. */
107
+ export declare const POST_EYE: [number, number, number];
108
+ export declare const POST_TARGET: [number, number, number];
109
+ /**
110
+ * The `post-stack` camera path: from POST_EYE, swinging 30° either side of the courtyard's axis
111
+ * once every 20 s and drifting 1.5 m in and out, always looking at POST_TARGET. `seconds` along it.
112
+ */
113
+ export declare function postCamera(seconds: number): {
114
+ eye: [number, number, number];
115
+ target: [number, number, number];
116
+ };
117
+ /** The camera's lens: a physical exposure, so auto exposure and DoF read real values. */
118
+ export declare const POST_LENS: readonly [import("@aethervtt/shard-core").ComponentDef<{
119
+ readonly aperture: import("@aethervtt/shard-core").FieldType<number, "f32">;
120
+ readonly shutterSpeed: import("@aethervtt/shard-core").FieldType<number, "f32">;
121
+ readonly iso: import("@aethervtt/shard-core").FieldType<number, "f32">;
122
+ readonly sensorHeight: import("@aethervtt/shard-core").FieldType<number, "f32">;
123
+ readonly focalLength: import("@aethervtt/shard-core").FieldType<number, "f32">;
124
+ }>, {
125
+ readonly aperture: 5.6;
126
+ readonly shutterSpeed: number;
127
+ readonly iso: 100;
128
+ }];
129
+ /** The courtyard, its sun and sky; the camera is the caller's. */
130
+ export declare function spawnPostScene(world: World): void;
@@ -0,0 +1,164 @@
1
+ import {
2
+ defineComponent,
3
+ defineSystem,
4
+ quat,
5
+ Rng,
6
+ t,
7
+ Update
8
+ } from "@aethervtt/shard-core";
9
+ import { cube, plane, sphere } from "@aethervtt/shard-mesh";
10
+ import { definePlugin, Time } from "@aethervtt/shard-runtime";
11
+ import { Transform } from "@aethervtt/shard-transform";
12
+ import { MaterialAsset, Materials, Meshes } from "./assets.js";
13
+ import { PhysicalCamera } from "./camera.js";
14
+ import { DefaultEnvironment } from "./environment.js";
15
+ import { Mesh3d, MeshMaterial } from "./instances.js";
16
+ import { DirectionalLight } from "./lights.js";
17
+ import {
18
+ Antialiasing,
19
+ AutoExposure,
20
+ Bloom,
21
+ ColorGrading,
22
+ DepthOfField,
23
+ Fog,
24
+ MotionBlur,
25
+ Ssao,
26
+ Vignette
27
+ } from "./post.js";
28
+ const POST_EFFECTS = {
29
+ bloom: [Bloom, { intensity: 0.12 }],
30
+ exposure: [AutoExposure, { compensation: 0.5 }],
31
+ dof: [DepthOfField, { focusDistance: 9, maxBlur: 0.012 }],
32
+ "motion-blur": [MotionBlur, {}],
33
+ taa: [Antialiasing, { mode: "taa" }],
34
+ fxaa: [Antialiasing, { mode: "fxaa" }],
35
+ noaa: [Antialiasing, { mode: "none" }],
36
+ ssao: [Ssao, { radius: 0.6 }],
37
+ fog: [Fog, { density: 0.015, heightFalloff: 0.08, sunScattering: 0.6 }],
38
+ grading: [ColorGrading, { temperature: 0.1, saturation: 1.1, contrast: 1.05 }],
39
+ vignette: [Vignette, { intensity: 0.25 }]
40
+ };
41
+ const ALL_POST_EFFECTS = Object.keys(POST_EFFECTS).filter((n) => n !== "fxaa" && n !== "noaa");
42
+ function postEffects(names) {
43
+ return names.map((n) => POST_EFFECTS[n]).filter((e) => e !== void 0);
44
+ }
45
+ const PostSpin = defineComponent("render/PostSpin", {
46
+ radius: t.f32,
47
+ speed: t.f32,
48
+ height: t.f32
49
+ });
50
+ const postSpin = defineSystem({
51
+ name: "render/post-spin",
52
+ setup: (world) => ({ q: world.query({ with: [PostSpin, Transform] }) }),
53
+ run: ({ q }, world) => {
54
+ const time = world.resource(Time).elapsed;
55
+ for (const table of q.tables) {
56
+ const r = table.column(PostSpin, "radius");
57
+ const s = table.column(PostSpin, "speed");
58
+ const h = table.column(PostSpin, "height");
59
+ const tr = table.column(Transform, "translation");
60
+ for (let i = 0; i < table.count; i++) {
61
+ const a = time * s[i];
62
+ tr[i * 3] = Math.cos(a) * r[i];
63
+ tr[i * 3 + 1] = h[i];
64
+ tr[i * 3 + 2] = Math.sin(a) * r[i] - 4;
65
+ }
66
+ table.markChanged(Transform);
67
+ }
68
+ }
69
+ });
70
+ const postScenePlugin = definePlugin({
71
+ name: "render/post-scene",
72
+ dependencies: ["render"],
73
+ provides: [PostSpin],
74
+ build(app) {
75
+ app.addSystems(Update, postSpin);
76
+ }
77
+ });
78
+ const POST_EYE = [0, 2.2, 9];
79
+ const POST_TARGET = [0, 1.2, -2];
80
+ function postCamera(seconds) {
81
+ const [tx, ty, tz] = POST_TARGET;
82
+ const dx = POST_EYE[0] - tx;
83
+ const dy = POST_EYE[1] - ty;
84
+ const dz = POST_EYE[2] - tz;
85
+ const flat = Math.sqrt(dx * dx + dz * dz);
86
+ const yaw = Math.atan2(dx, dz) + Math.PI / 6 * Math.sin(seconds * 2 * Math.PI / 20);
87
+ const distance = flat + 1.5 * Math.sin(seconds * 2 * Math.PI / 13);
88
+ return {
89
+ eye: [tx + Math.sin(yaw) * distance, ty + dy, tz + Math.cos(yaw) * distance],
90
+ target: [tx, ty, tz]
91
+ };
92
+ }
93
+ const POST_LENS = [
94
+ PhysicalCamera,
95
+ { aperture: 5.6, shutterSpeed: 1 / 1e3, iso: 100 }
96
+ ];
97
+ function spawnPostScene(world) {
98
+ world.resource(DefaultEnvironment).sky = { turbidity: 2.5 };
99
+ world.spawn(
100
+ [DirectionalLight, { illuminance: 1e5, shadows: true }],
101
+ [Transform, { rotation: quat.fromEuler([0, 0, 0, 1], -0.5, 0.9, 0) }]
102
+ );
103
+ const meshes = world.resource(Meshes);
104
+ const materials = world.resource(Materials);
105
+ const mat = (v) => materials.add(new MaterialAsset(v));
106
+ world.spawn(
107
+ [Mesh3d, { mesh: meshes.add(plane({ size: 400 })) }],
108
+ [MeshMaterial, { material: mat({ baseColor: [0.35, 0.33, 0.3, 1], roughness: 0.9 }) }],
109
+ Transform
110
+ );
111
+ const block = meshes.add(cube({ size: 1 }));
112
+ const ball = meshes.add(sphere({ radius: 0.5, segments: 40 }));
113
+ const rng = new Rng(4);
114
+ const stone = [
115
+ mat({ baseColor: [0.7, 0.68, 0.64, 1], roughness: 0.7 }),
116
+ mat({ baseColor: [0.55, 0.3, 0.22, 1], roughness: 0.8 }),
117
+ mat({ baseColor: [0.9, 0.8, 0.55, 1], metallic: 1, roughness: 0.3 })
118
+ ];
119
+ for (let i = 0; i < 60; i++) {
120
+ const size = rng.range(0.6, 2.2);
121
+ world.spawn(
122
+ [Mesh3d, { mesh: i % 3 === 2 ? ball : block }],
123
+ [MeshMaterial, { material: stone[i % 3] }],
124
+ [
125
+ Transform,
126
+ {
127
+ translation: [rng.range(-14, 14), size / 2, rng.range(-30, 2)],
128
+ rotation: quat.fromEuler([0, 0, 0, 1], 0, rng.float() * 6, 0),
129
+ scale: [size, size * rng.range(0.6, 2.4), size]
130
+ }
131
+ ]
132
+ );
133
+ }
134
+ const neon = (c) => mat({ baseColor: [0, 0, 0, 1], emissive: c, emissiveLuminance: 4e5 });
135
+ const panels = [neon([1, 0.2, 0.6, 1]), neon([0.2, 0.8, 1, 1]), neon([1, 0.7, 0.2, 1])];
136
+ for (let i = 0; i < 3; i++) {
137
+ world.spawn(
138
+ [Mesh3d, { mesh: block }],
139
+ [MeshMaterial, { material: panels[i] }],
140
+ [Transform, { translation: [(i - 1) * 3, 2.5, -5], scale: [2, 0.15, 0.1] }]
141
+ );
142
+ }
143
+ for (let i = 0; i < 4; i++) {
144
+ world.spawn(
145
+ [Mesh3d, { mesh: ball }],
146
+ [MeshMaterial, { material: stone[2] }],
147
+ [PostSpin, { radius: 3 + i, speed: 1.2 - i * 0.2, height: 0.6 + i * 0.3 }],
148
+ Transform
149
+ );
150
+ }
151
+ }
152
+ export {
153
+ ALL_POST_EFFECTS,
154
+ POST_EFFECTS,
155
+ POST_EYE,
156
+ POST_LENS,
157
+ POST_TARGET,
158
+ PostSpin,
159
+ postCamera,
160
+ postEffects,
161
+ postScenePlugin,
162
+ postSpin,
163
+ spawnPostScene
164
+ };
@@ -69,6 +69,7 @@ fn prepass_output(in: PrepassVertex, n: vec3f) -> PrepassOutput {
69
69
  import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, prepass_output, surface_of };
70
70
  import shard::pbr::material::pbr_input;
71
71
  import shard::pbr::standard::material;
72
+ @if(CUTAWAY) import shard::cutaway::cutaway_clip;
72
73
 
73
74
  @vertex fn vs(
74
75
  @builtin(instance_index) instance_index: u32,
@@ -84,12 +85,20 @@ import shard::pbr::standard::material;
84
85
 
85
86
  /** Depth, the shaded normal (normal maps included), and velocity. */
86
87
  @fragment fn fs(in: PrepassVertex) -> PrepassOutput {
88
+ @if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
87
89
  let p = pbr_input(surface_of(in));
88
90
  @if(MASK) if (p.alpha < material.alphaCutoff) { discard; }
89
91
  return prepass_output(in, p.normal);
92
+ }
93
+
94
+ /** Baseline (0064): depth alone, for what reads depth when the scene is multisampled. */
95
+ @if(BASELINE) @fragment fn fs_depth(in: PrepassVertex) {
96
+ @if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
97
+ @if(MASK) if (pbr_input(surface_of(in)).alpha < material.alphaCutoff) { discard; }
90
98
  }`,
91
99
  "shard::prepass::plain": `
92
100
  import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, prepass_output };
101
+ @if(CUTAWAY) import shard::cutaway::cutaway_clip;
93
102
 
94
103
  @vertex fn vs(
95
104
  @builtin(instance_index) instance_index: u32,
@@ -105,13 +114,19 @@ import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, p
105
114
 
106
115
  /** Materials with their own shading: the geometric normal. */
107
116
  @fragment fn fs(in: PrepassVertex) -> PrepassOutput {
117
+ @if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
108
118
  return prepass_output(in, normalize(in.world_normal));
119
+ }
120
+
121
+ /** Baseline (0064): depth alone. */
122
+ @if(BASELINE) @fragment fn fs_depth(in: PrepassVertex) {
123
+ @if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
109
124
  }`,
110
- "shard::post::fog": `
125
+ "shard::post::fog::common": `
111
126
  import shard::view::view;
112
127
  import shard::pbr::lights::directional;
113
128
  import shard::pbr::environment::env_irradiance;
114
- import shard::post::common::{ uv_of, world_at };
129
+ import shard::post::common::world_at;
115
130
 
116
131
  struct FogParams {
117
132
  color: vec4f,
@@ -121,31 +136,27 @@ struct FogParams {
121
136
  sun: f32,
122
137
  }
123
138
 
124
- @group(1) @binding(0) var input: texture_2d<f32>;
125
- @group(1) @binding(1) var depth_texture: texture_depth_2d;
126
- @group(1) @binding(2) var<uniform> fog: FogParams;
127
-
128
139
  const PI: f32 = 3.14159265;
129
140
 
130
141
  /** Optical depth of exponential height fog along the ray from t0 to t1 (meters). */
131
- fn optical_depth(y0: f32, dir_y: f32, t0: f32, t1: f32) -> f32 {
142
+ fn optical_depth(fog: FogParams, y0: f32, dir_y: f32, t0: f32, t1: f32) -> f32 {
132
143
  let base = fog.density * exp(-fog.falloff * y0);
133
144
  let k = fog.falloff * dir_y;
134
145
  if (abs(k) < 1e-5) { return base * (t1 - t0); }
135
146
  return base * (exp(-k * t0) - exp(-k * t1)) / k;
136
147
  }
137
148
 
138
- @fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
139
- let px = vec2i(frag.xy);
140
- let c = textureLoad(input, px, 0);
141
- let depth = textureLoad(depth_texture, px, 0);
142
- let uv = uv_of(frag.xy);
149
+ /**
150
+ * Height fog over color \`c\` at a pixel (\`uv\`, its depth): the fog pass, and the atmosphere's
151
+ * aerial perspective when a view has both (one pass for the two).
152
+ */
153
+ fn fog_apply(c: vec4f, uv: vec2f, depth: f32, fog: FogParams) -> vec4f {
143
154
  // The background is far away, not infinitely: a floor keeps the ray finite.
144
155
  let world = world_at(uv, max(depth, 1e-7));
145
156
  let to = world - view.cameraPosition;
146
157
  let dist = length(to);
147
158
  let dir = to / max(dist, 1e-6);
148
- let tau = optical_depth(view.cameraPosition.y, dir.y, fog.start, max(dist, fog.start));
159
+ let tau = optical_depth(fog, view.cameraPosition.y, dir.y, fog.start, max(dist, fog.start));
149
160
  let transmittance = exp(-tau);
150
161
  // In-scattering: the sky (environment or ambient), plus the sun through a forward-peaked phase.
151
162
  var sky = view.ambient;
@@ -159,7 +170,22 @@ fn optical_depth(y0: f32, dir_y: f32, t0: f32, t1: f32) -> f32 {
159
170
  sun = light.color * view.sunTransmittance[0].rgb * mix(1.0 / (4.0 * PI), hg, fog.sun);
160
171
  }
161
172
  let inscatter = fog.color.rgb * (sky + sun) * view.exposure;
162
- return vec4f(c.rgb * transmittance + inscatter * (1.0 - transmittance), c.a);
173
+ let color = c.rgb * transmittance + inscatter * (1.0 - transmittance);
174
+ @if(!TRANSPARENT) return vec4f(color, c.a);
175
+ // Premultiplied (0052): the fog is a layer of its own opacity over what's behind it.
176
+ @if(TRANSPARENT) return vec4f(color, c.a * transmittance + (1.0 - transmittance));
177
+ }`,
178
+ "shard::post::fog": `
179
+ import shard::post::common::{ uv_of, post_out };
180
+ import shard::post::fog::common::{ FogParams, fog_apply };
181
+
182
+ @group(1) @binding(0) var input: texture_2d<f32>;
183
+ @group(1) @binding(1) var depth_texture: texture_depth_2d;
184
+ @group(1) @binding(2) var<uniform> fog: FogParams;
185
+
186
+ @fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
187
+ let px = vec2i(frag.xy);
188
+ return post_out(fog_apply(textureLoad(input, px, 0), uv_of(frag.xy), textureLoad(depth_texture, px, 0), fog));
163
189
  }`,
164
190
  "shard::post::bloom": `
165
191
  import shard::color::luminance;
@@ -174,7 +200,6 @@ struct BloomParams {
174
200
  @group(0) @binding(0) var source: texture_2d<f32>;
175
201
  @group(0) @binding(1) var source_sampler: sampler;
176
202
  @group(0) @binding(2) var<uniform> params: BloomParams;
177
- @group(0) @binding(3) var scene: texture_2d<f32>;
178
203
 
179
204
  fn tap(uv: vec2f, dx: f32, dy: f32) -> vec3f {
180
205
  return textureSampleLevel(source, source_sampler, uv + vec2f(dx, dy) * params.threshold.zw, 0.0).rgb;
@@ -225,27 +250,24 @@ struct FullscreenInput { @builtin(position) clip: vec4f, @location(0) uv: vec2f
225
250
  + (tap(in.uv, -1.0, 0.0) + tap(in.uv, 1.0, 0.0) + tap(in.uv, 0.0, -1.0) + tap(in.uv, 0.0, 1.0)) * 2.0
226
251
  + tap(in.uv, -1.0, -1.0) + tap(in.uv, 1.0, -1.0) + tap(in.uv, -1.0, 1.0) + tap(in.uv, 1.0, 1.0);
227
252
  return vec4f(c / 16.0, 1.0);
228
- }
229
-
230
- /** The scene plus the glow: mixed (energy-conserving) without a threshold, added with one. */
231
- @fragment fn composite(in: FullscreenInput) -> @location(0) vec4f {
232
- let s = textureLoad(scene, vec2i(in.clip.xy), 0);
233
- let glow = textureSampleLevel(source, source_sampler, in.uv, 0.0).rgb / params.mix.y;
234
- let c = select(s.rgb + glow * params.mix.x, mix(s.rgb, glow, params.mix.x), params.mix.z > 0.5);
235
- return vec4f(c, s.a);
236
253
  }`,
237
254
  "shard::post::exposure": `
238
255
  import shard::view::view;
239
256
  import shard::color::luminance;
257
+ @if(BLOOM) import shard::post::glow::add_glow;
240
258
 
241
259
  struct MeterParams {
242
260
  /** EV of bin 0, bins per EV, metering mode, 0. */
243
261
  range: vec4f,
262
+ /** Bloom, which the tonemap adds to the image: intensity, level count, mix (1) or add (0). */
263
+ glow: vec4f,
244
264
  }
245
265
 
246
266
  @group(0) @binding(1) var input: texture_2d<f32>;
247
267
  @group(0) @binding(2) var<storage, read_write> histogram: array<atomic<u32>, 256>;
248
268
  @group(0) @binding(3) var<uniform> params: MeterParams;
269
+ @if(BLOOM) @group(0) @binding(4) var bloom: texture_2d<f32>;
270
+ @if(BLOOM) @group(0) @binding(5) var bloom_sampler: sampler;
249
271
 
250
272
  var<workgroup> local: array<atomic<u32>, 256>;
251
273
 
@@ -261,7 +283,12 @@ fn meter(@builtin(global_invocation_id) id: vec3u, @builtin(local_invocation_ind
261
283
  let size = textureDimensions(input);
262
284
  let px = id.xy * 4u + 2u;
263
285
  if (px.x < size.x && px.y < size.y) {
264
- let c = textureLoad(input, vec2i(px), 0).rgb;
286
+ var c = textureLoad(input, vec2i(px), 0).rgb;
287
+ // What the tonemap shows: the image with bloom's glow over it.
288
+ @if(BLOOM) {
289
+ let uv = (vec2f(px) + 0.5) / vec2f(size);
290
+ c = add_glow(c, textureSampleLevel(bloom, bloom_sampler, uv, 0.0).rgb, params.glow);
291
+ }
265
292
  let l = luminance(c) / max(view.exposure, 1e-20);
266
293
  let ev = log2(max(l, 1e-10) * 8.0);
267
294
  let bin = u32(clamp((ev - params.range.x) * params.range.y, 0.0, 255.0));
@@ -278,7 +305,7 @@ fn meter(@builtin(global_invocation_id) id: vec3u, @builtin(local_invocation_ind
278
305
  }`,
279
306
  "shard::post::dof": `
280
307
  import shard::view::view;
281
- import shard::post::common::{ uv_of, world_at, view_depth };
308
+ import shard::post::common::{ uv_of, world_at, view_depth, post_out };
282
309
  import shard::color::luminance;
283
310
 
284
311
  struct DofParams {
@@ -359,11 +386,11 @@ const TAPS: i32 = 48;
359
386
  let sharp = textureLoad(input, px, 0);
360
387
  let blurred = textureSampleLevel(half_texture, linear_sampler, uv_of(frag.xy), 0.0);
361
388
  let t = smoothstep(0.5, 1.5, abs(coc_at(px)));
362
- return vec4f(mix(sharp.rgb, blurred.rgb, t), sharp.a);
389
+ return post_out(vec4f(mix(sharp.rgb, blurred.rgb, t), sharp.a));
363
390
  }`,
364
391
  "shard::post::motion_blur": `
365
392
  import shard::view::view;
366
- import shard::post::common::{ uv_of, motion };
393
+ import shard::post::common::{ uv_of, motion, post_out };
367
394
 
368
395
  struct MotionParams {
369
396
  /** Shutter fraction of a frame, max blur in pixels, samples, 0. */
@@ -385,14 +412,14 @@ struct MotionParams {
385
412
  let cap = motion_params.params.y;
386
413
  if (pixels > cap) { v *= cap / pixels; }
387
414
  let center = textureLoad(input, px, 0);
388
- if (pixels < 0.5) { return center; }
415
+ if (pixels < 0.5) { return post_out(center); }
389
416
  let n = i32(motion_params.params.z);
390
417
  var sum = vec3f(0.0);
391
418
  for (var i = 0; i < n; i++) {
392
419
  let t = (f32(i) + 0.5) / f32(n) - 0.5;
393
420
  sum += textureSampleLevel(input, linear_sampler, uv + v * t, 0.0).rgb;
394
421
  }
395
- return vec4f(sum / f32(n), center.a);
422
+ return post_out(vec4f(sum / f32(n), center.a));
396
423
  }`,
397
424
  "shard::post::taa": `
398
425
  import shard::view::view;
@@ -411,11 +438,6 @@ struct TaaParams {
411
438
  @group(0) @binding(5) var linear_sampler: sampler;
412
439
  @group(0) @binding(6) var<uniform> taa: TaaParams;
413
440
 
414
- struct TaaOutput {
415
- @location(0) color: vec4f,
416
- @location(1) history: vec4f,
417
- }
418
-
419
441
  fn rgb_to_ycocg(c: vec3f) -> vec3f {
420
442
  return vec3f(0.25 * c.r + 0.5 * c.g + 0.25 * c.b, 0.5 * c.r - 0.5 * c.b, -0.25 * c.r + 0.5 * c.g - 0.25 * c.b);
421
443
  }
@@ -451,7 +473,7 @@ fn sample_history(uv: vec2f) -> vec3f {
451
473
  return max(c / w, vec3f(0.0));
452
474
  }
453
475
 
454
- @fragment fn fs(@builtin(position) frag: vec4f) -> TaaOutput {
476
+ @fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
455
477
  let px = vec2i(frag.xy);
456
478
  let size = vec2i(textureDimensions(input));
457
479
  let uv = uv_of(frag.xy);
@@ -461,12 +483,19 @@ fn sample_history(uv: vec2f) -> vec3f {
461
483
  var hi = vec3f(-1e20);
462
484
  var nearest = 0.0;
463
485
  var nearest_px = px;
486
+ @if(TRANSPARENT) var alpha_lo = 1.0;
487
+ @if(TRANSPARENT) var alpha_hi = 0.0;
464
488
  for (var y = -1; y <= 1; y++) {
465
489
  for (var x = -1; x <= 1; x++) {
466
490
  let q = clamp(px + vec2i(x, y), vec2i(0), size - 1);
467
- let c = rgb_to_ycocg(compress(textureLoad(input, q, 0).rgb));
491
+ let texel = textureLoad(input, q, 0);
492
+ let c = rgb_to_ycocg(compress(texel.rgb));
468
493
  lo = min(lo, c);
469
494
  hi = max(hi, c);
495
+ @if(TRANSPARENT) {
496
+ alpha_lo = min(alpha_lo, texel.a);
497
+ alpha_hi = max(alpha_hi, texel.a);
498
+ }
470
499
  let d = textureLoad(depth_texture, q, 0);
471
500
  if (d > nearest) { nearest = d; nearest_px = q; }
472
501
  }
@@ -486,9 +515,13 @@ fn sample_history(uv: vec2f) -> vec3f {
486
515
  if (m > 1.0) { h = center + offset / m; }
487
516
  let blended = mix(ycocg_to_rgb(h), current, alpha);
488
517
  let color = expand(max(blended, vec3f(0.0)));
489
- var out: TaaOutput;
490
- out.color = vec4f(color, 1.0);
491
- out.history = vec4f(color, 1.0);
518
+ // The output is also next frame's history.
519
+ var out = vec4f(color, 1.0);
520
+ @if(TRANSPARENT) {
521
+ // Coverage resolves like color: history clamped to the neighborhood, blended at the same rate.
522
+ let past = clamp(textureSampleLevel(history, linear_sampler, prev_uv, 0.0).a, alpha_lo, alpha_hi);
523
+ out.a = mix(past, textureLoad(input, px, 0).a, alpha);
524
+ }
492
525
  return out;
493
526
  }`,
494
527
  "shard::post::ssao": `
@@ -508,7 +541,6 @@ struct SsaoParams {
508
541
  @group(0) @binding(1) var depth_texture: texture_depth_2d;
509
542
  @group(0) @binding(2) var normal_texture: texture_2d<f32>;
510
543
  @group(0) @binding(3) var<uniform> ssao: SsaoParams;
511
- @group(0) @binding(4) var half_ao: texture_2d<f32>;
512
544
 
513
545
  const PI: f32 = 3.14159265;
514
546
 
@@ -575,26 +607,6 @@ fn view_position(px: vec2i) -> vec3f {
575
607
  }
576
608
  let ao = clamp(visibility / f32(dirs), 0.0, 1.0);
577
609
  return vec4f(pow(ao, ssao.params.y), -p.z, 0.0, 1.0);
578
- }
579
-
580
- /** Back to full resolution: the four nearest half-resolution texels, weighted by depth match. */
581
- @fragment fn upsample(@builtin(position) frag: vec4f) -> @location(0) vec4f {
582
- let px = vec2i(frag.xy);
583
- let depth = textureLoad(depth_texture, px, 0);
584
- if (depth <= 0.0) { return vec4f(1.0); }
585
- let z = -view_position(px).z;
586
- let half_size = vec2i(textureDimensions(half_ao));
587
- let base = vec2i(floor((frag.xy - 1.0) * 0.5));
588
- var sum = 0.0;
589
- var weight = 0.0;
590
- for (var k = 0; k < 4; k++) {
591
- let q = clamp(base + vec2i(k & 1, k >> 1), vec2i(0), half_size - 1);
592
- let s = textureLoad(half_ao, q, 0);
593
- let w = 1.0 / (1e-3 + abs(s.y - z) / max(z, 1e-3));
594
- sum += s.x * w;
595
- weight += w;
596
- }
597
- return vec4f(sum / max(weight, 1e-6), 0.0, 0.0, 1.0);
598
610
  }`
599
611
  };
600
612
  const FXAA_SHADERS = {
@@ -614,6 +626,13 @@ fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
614
626
  return textureSampleLevel(input, input_sampler, uv + o * texel, 0.0).rgb;
615
627
  }
616
628
 
629
+ /** The output at uv: opaque views write alpha 1; premultiplied ones carry it, rgb <= alpha (0052). */
630
+ fn result(uv: vec2f) -> vec4f {
631
+ let c = textureSampleLevel(input, input_sampler, uv, 0.0);
632
+ @if(!TRANSPARENT) return vec4f(c.rgb, 1.0);
633
+ @if(TRANSPARENT) return vec4f(c.rgb, max(c.a, max(c.r, max(c.g, c.b))));
634
+ }
635
+
617
636
  /** FXAA 3.11 (Lottes), quality preset 12: local contrast, edge direction, end search, blend. */
618
637
  @fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
619
638
  let size = vec2f(textureDimensions(input));
@@ -628,7 +647,10 @@ fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
628
647
  let lo = min(m, min(min(n, s), min(e, w)));
629
648
  let hi = max(m, max(max(n, s), max(e, w)));
630
649
  let range = hi - lo;
631
- if (range < max(0.0312, hi * 0.125)) { return vec4f(center, 1.0); }
650
+ if (range < max(0.0312, hi * 0.125)) {
651
+ @if(!TRANSPARENT) return vec4f(center, 1.0);
652
+ @if(TRANSPARENT) return result(uv);
653
+ }
632
654
  let nw = luma(at(uv, vec2f(-1.0, -1.0), texel));
633
655
  let ne = luma(at(uv, vec2f(1.0, -1.0), texel));
634
656
  let sw = luma(at(uv, vec2f(-1.0, 1.0), texel));
@@ -689,7 +711,8 @@ fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
689
711
  let blend = max(edge_blend, sub_blend);
690
712
  var final_uv = uv;
691
713
  if (horizontal) { final_uv.y += blend * step_len; } else { final_uv.x += blend * step_len; }
692
- return vec4f(textureSampleLevel(input, input_sampler, final_uv, 0.0).rgb, 1.0);
714
+ @if(!TRANSPARENT) return vec4f(textureSampleLevel(input, input_sampler, final_uv, 0.0).rgb, 1.0);
715
+ @if(TRANSPARENT) return result(final_uv);
693
716
  }`
694
717
  };
695
718
  export {
package/dist/post.js CHANGED
@@ -403,9 +403,19 @@ function postAliases(cam, base) {
403
403
  let flip = false;
404
404
  for (const [name, bit] of HDR_CHAIN) {
405
405
  if ((effects & bit) === 0) continue;
406
+ if (name === "fog" && effects & PostEffect.Atmosphere) continue;
407
+ if (name === "bloom") {
408
+ out["bloom-in"] = current;
409
+ continue;
410
+ }
411
+ out[`${name}-in`] = current;
412
+ if (name === "taa") {
413
+ out["taa-out"] = "taa-history";
414
+ current = "taa-history";
415
+ continue;
416
+ }
406
417
  const next = flip ? "post-b" : "post-a";
407
418
  flip = !flip;
408
- out[`${name}-in`] = current;
409
419
  out[`${name}-out`] = next;
410
420
  current = next;
411
421
  }
@@ -0,0 +1,24 @@
1
+ import { type Entity, type World } from '@aethervtt/shard-core';
2
+ /**
3
+ * Projects a world point to CSS pixels on `camera`'s target (origin top-left). Returns false when
4
+ * the point is behind the camera (`out` is left unchanged), or the camera hasn't rendered yet.
5
+ */
6
+ export declare function worldToScreen(world: World, camera: Entity, point: ArrayLike<number>, out: {
7
+ [index: number]: number;
8
+ }): boolean;
9
+ /**
10
+ * The world ray under CSS pixel (x, y) of `camera`'s target: its origin on the near plane and its
11
+ * unit direction. Orthographic rays are parallel, perspective ones start at the near plane.
12
+ */
13
+ export declare function screenToRay(world: World, camera: Entity, x: number, y: number, outOrigin: {
14
+ [index: number]: number;
15
+ }, outDir: {
16
+ [index: number]: number;
17
+ }): void;
18
+ /**
19
+ * Where the ray under CSS pixel (x, y) meets the horizontal plane `y = planeY` (a floor, a ground
20
+ * band). Returns false when it doesn't (parallel, or behind the camera).
21
+ */
22
+ export declare function screenToPlane(world: World, camera: Entity, x: number, y: number, planeY: number, out: {
23
+ [index: number]: number;
24
+ }): boolean;