@bornengine/engine 0.4.16

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,112 @@
1
+ // Octahedral imposter helpers.
2
+ //
3
+ // The standard "octahedral imposter" technique encodes a hemisphere of
4
+ // view directions into a 2D atlas. A model is pre-rendered from N²
5
+ // directions on an octahedron (V1 expects N = 8 → 64 views in an 8×8
6
+ // grid). At runtime, the fragment shader picks the right grid cell
7
+ // based on the camera's direction relative to the imposter's position.
8
+ //
9
+ // Atlas convention:
10
+ // - 8×8 grid (V1 fixed). 256 to 512 px per cell.
11
+ // - RGBA8 (color + alpha). Future: split into albedo / normal / depth.
12
+ // - Each cell renders the model facing in the encoded direction.
13
+ //
14
+ // Bake tooling: V1 expects games to use Blender's ScreenSpace
15
+ // imposter add-on, Unity's Tree Creator imposter mode, or any
16
+ // equivalent external tool. The engine bake tool ships in a follow-up.
17
+
18
+ const IMPOSTER_GRID: u32 = 8u;
19
+ const IMPOSTER_GRID_F: f32 = 8.0;
20
+
21
+ // Spherical → octahedron: unfold a unit sphere onto the [-1, 1] square.
22
+ fn octahedral_encode(dir: vec3<f32>) -> vec2<f32> {
23
+ let n = dir / (abs(dir.x) + abs(dir.y) + abs(dir.z));
24
+ if (n.y >= 0.0) {
25
+ return vec2<f32>(n.x, n.z);
26
+ }
27
+ // Lower hemisphere: fold around.
28
+ return vec2<f32>(
29
+ (1.0 - abs(n.z)) * select(-1.0, 1.0, n.x >= 0.0),
30
+ (1.0 - abs(n.x)) * select(-1.0, 1.0, n.z >= 0.0),
31
+ );
32
+ }
33
+
34
+ // Octahedron → spherical (inverse of octahedral_encode).
35
+ fn octahedral_decode(uv: vec2<f32>) -> vec3<f32> {
36
+ let abs_uv = abs(uv);
37
+ let y = 1.0 - abs_uv.x - abs_uv.y;
38
+ if (y >= 0.0) {
39
+ return normalize(vec3<f32>(uv.x, y, uv.y));
40
+ }
41
+ let signed_uv = sign(uv);
42
+ return normalize(vec3<f32>(
43
+ signed_uv.x * (1.0 - abs_uv.y),
44
+ y,
45
+ signed_uv.y * (1.0 - abs_uv.x),
46
+ ));
47
+ }
48
+
49
+ // Pick the grid cell + sub-cell UV for sampling an octahedral imposter
50
+ // atlas. `view_dir` is the direction from the imposter to the camera
51
+ // (world space, normalized). `local_uv` is the per-cell UV
52
+ // (0..1, the billboard quad's UV).
53
+ //
54
+ // Returns the atlas UV in [0, 1] mapping to the right cell + sub-cell.
55
+ fn imposter_atlas_uv(view_dir: vec3<f32>, local_uv: vec2<f32>) -> vec2<f32> {
56
+ // Encode direction → octahedral UV in [-1, 1], remap to [0, 1].
57
+ let oct = octahedral_encode(view_dir) * 0.5 + 0.5;
58
+ // Snap to the nearest cell in the 8×8 grid.
59
+ let cell = floor(oct * IMPOSTER_GRID_F);
60
+ let cell_uv = (cell + clamp(local_uv, vec2<f32>(0.0), vec2<f32>(1.0))) / IMPOSTER_GRID_F;
61
+ return cell_uv;
62
+ }
63
+
64
+ // Convenience: pick the UV cell directly from a view direction (ignoring
65
+ // per-cell sub-UV). Useful when sampling a pre-baked atlas where each
66
+ // cell is a flat texture and the billboard quad's per-vertex UV is
67
+ // already in [0, 1].
68
+ fn imposter_uv_for_view(view_dir: vec3<f32>) -> vec2<f32> {
69
+ return imposter_atlas_uv(view_dir, vec2<f32>(0.5, 0.5));
70
+ }
71
+
72
+ // Standard billboard: build a screen-aligned quad facing the camera.
73
+ // `world_center` is the imposter's world-space anchor.
74
+ // `vertex_idx` is the @builtin(vertex_index) (0..3 for two triangles
75
+ // in a TriangleStrip, or 0..6 for a TriangleList).
76
+ // Returns the world-space vertex position + the per-vertex UV.
77
+ struct BillboardVertex {
78
+ world_pos: vec3<f32>,
79
+ uv: vec2<f32>,
80
+ };
81
+
82
+ fn billboard_quad(
83
+ world_center: vec3<f32>, scale: f32,
84
+ camera_pos: vec3<f32>, camera_up: vec3<f32>,
85
+ vertex_idx: u32,
86
+ ) -> BillboardVertex {
87
+ // 4 corners of a unit quad: (-,-), (+,-), (-,+), (+,+).
88
+ // TriangleStrip indexing: 0,1,2,3 → 0=BL, 1=BR, 2=TL, 3=TR.
89
+ let bl = vec2<f32>(-0.5, -0.5);
90
+ let br = vec2<f32>( 0.5, -0.5);
91
+ let tl = vec2<f32>(-0.5, 0.5);
92
+ let tr = vec2<f32>( 0.5, 0.5);
93
+ var corner: vec2<f32>;
94
+ switch (vertex_idx) {
95
+ case 0u: { corner = bl; }
96
+ case 1u: { corner = br; }
97
+ case 2u: { corner = tl; }
98
+ default: { corner = tr; }
99
+ }
100
+
101
+ // View basis: forward = imposter → camera, right = up × forward, up = forward × right.
102
+ let forward = normalize(camera_pos - world_center);
103
+ let right = normalize(cross(camera_up, forward));
104
+ let up = normalize(cross(forward, right));
105
+
106
+ let world_pos = world_center + scale * (right * corner.x + up * corner.y);
107
+ let uv = corner + vec2<f32>(0.5, 0.5); // 0..1
108
+ var out: BillboardVertex;
109
+ out.world_pos = world_pos;
110
+ out.uv = uv;
111
+ return out;
112
+ }
@@ -0,0 +1,186 @@
1
+ // PBR common — GGX specular, Lambert diffuse, split-sum IBL.
2
+ //
3
+ // Depends on material_abi.wgsl (requires group 1 env + brdf_lut, group 2
4
+ // base/normal/mr/em/occ textures + material factors).
5
+
6
+ const PI: f32 = 3.14159265358979;
7
+
8
+ // =====================================================================
9
+ // Microfacet BRDF components
10
+ // =====================================================================
11
+
12
+ // Trowbridge-Reitz / GGX normal distribution.
13
+ fn d_ggx(n_dot_h: f32, roughness: f32) -> f32 {
14
+ let a = roughness * roughness;
15
+ let a2 = a * a;
16
+ let nh2 = n_dot_h * n_dot_h;
17
+ let denom = nh2 * (a2 - 1.0) + 1.0;
18
+ return a2 / (PI * denom * denom);
19
+ }
20
+
21
+ // Schlick-GGX geometry term with combined view + light masking.
22
+ fn g_smith(n_dot_v: f32, n_dot_l: f32, roughness: f32) -> f32 {
23
+ let r = roughness + 1.0;
24
+ let k = (r * r) * 0.125;
25
+ let gv = n_dot_v / (n_dot_v * (1.0 - k) + k);
26
+ let gl = n_dot_l / (n_dot_l * (1.0 - k) + k);
27
+ return gv * gl;
28
+ }
29
+
30
+ // Schlick's Fresnel approximation.
31
+ fn f_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
32
+ return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cos_theta, 5.0);
33
+ }
34
+
35
+ // Fresnel with a roughness term for IBL — fades the specular boost at
36
+ // grazing angles on rough surfaces, avoiding the "everything reflects"
37
+ // artifact.
38
+ fn f_schlick_roughness(cos_theta: f32, f0: vec3<f32>, roughness: f32) -> vec3<f32> {
39
+ let inv_rough = vec3<f32>(1.0 - roughness);
40
+ return f0 + (max(inv_rough, f0) - f0) * pow(1.0 - cos_theta, 5.0);
41
+ }
42
+
43
+ // =====================================================================
44
+ // Direct lighting — one directional or point light contribution
45
+ // =====================================================================
46
+
47
+ fn brdf_direct(
48
+ n: vec3<f32>, v: vec3<f32>, l: vec3<f32>,
49
+ albedo: vec3<f32>, metallic: f32, roughness: f32,
50
+ ) -> vec3<f32> {
51
+ let h = normalize(v + l);
52
+ let n_dot_v = max(dot(n, v), 0.0001);
53
+ let n_dot_l = max(dot(n, l), 0.0);
54
+ let n_dot_h = max(dot(n, h), 0.0);
55
+ let v_dot_h = max(dot(v, h), 0.0);
56
+
57
+ // Dielectrics get F0 = 0.04; metals tint the reflection by the albedo.
58
+ let f0 = mix(vec3<f32>(0.04), albedo, metallic);
59
+
60
+ let d = d_ggx(n_dot_h, roughness);
61
+ let g = g_smith(n_dot_v, n_dot_l, roughness);
62
+ let f = f_schlick(v_dot_h, f0);
63
+
64
+ let specular = (d * g * f) / max(4.0 * n_dot_v * n_dot_l, 0.0001);
65
+ let kd = (vec3<f32>(1.0) - f) * (1.0 - metallic);
66
+ let diffuse = kd * albedo / PI;
67
+
68
+ return (diffuse + specular) * n_dot_l;
69
+ }
70
+
71
+ // =====================================================================
72
+ // IBL — split-sum approximation
73
+ // =====================================================================
74
+ //
75
+ // env_diffuse_tex is a pre-convolved irradiance map sampled with the
76
+ // surface normal. env_tex is the original equirect HDR, sampled with a
77
+ // reflection vector and a roughness-derived mip bias. brdf_lut_tex is
78
+ // the pre-computed 2D LUT keyed on (n·v, roughness).
79
+ //
80
+ // Helper sampling functions assume equirectangular layout, matching the
81
+ // existing engine convention.
82
+
83
+ fn dir_to_equirect_uv(dir: vec3<f32>) -> vec2<f32> {
84
+ let d = normalize(dir);
85
+ let theta = acos(clamp(d.y, -1.0, 1.0));
86
+ let phi = atan2(d.z, d.x);
87
+ let u = phi / (2.0 * PI);
88
+ return vec2<f32>(u - floor(u), theta / PI);
89
+ }
90
+
91
+ fn seamless_equirect_uv(uv: vec2<f32>) -> vec2<f32> {
92
+ let tex_w = f32(textureDimensions(env_tex, 0).x);
93
+ let half_texel = 0.5 / tex_w;
94
+ return vec2<f32>(clamp(uv.x, half_texel, 1.0 - half_texel), uv.y);
95
+ }
96
+
97
+ fn sample_env(dir: vec3<f32>, lod: f32) -> vec3<f32> {
98
+ return textureSampleLevel(env_tex, env_samp,
99
+ seamless_equirect_uv(dir_to_equirect_uv(dir)),
100
+ lod).rgb * view.camera_pos.w;
101
+ }
102
+
103
+ fn sample_env_diffuse(normal: vec3<f32>) -> vec3<f32> {
104
+ return textureSample(env_diffuse_tex, env_samp,
105
+ dir_to_equirect_uv(normal)).rgb * view.camera_pos.w;
106
+ }
107
+
108
+ fn ibl(
109
+ n: vec3<f32>, v: vec3<f32>,
110
+ albedo: vec3<f32>, metallic: f32, roughness: f32, occlusion: f32,
111
+ ) -> vec3<f32> {
112
+ let r = reflect(-v, n);
113
+ let n_dot_v = max(dot(n, v), 0.0001);
114
+ let f0 = mix(vec3<f32>(0.04), albedo, metallic);
115
+ let f = f_schlick_roughness(n_dot_v, f0, roughness);
116
+
117
+ // Diffuse IBL — convolved irradiance.
118
+ let kd = (vec3<f32>(1.0) - f) * (1.0 - metallic);
119
+ let irr = sample_env_diffuse(n);
120
+ let diffuse = kd * albedo * irr;
121
+
122
+ // Specular IBL — roughness-weighted mip (approximation).
123
+ // Real split-sum pre-filters per mip; we use a simple lod bias for
124
+ // now. The engine can swap in a proper pre-filtered chain later.
125
+ let lod = roughness * 6.0;
126
+ let env = sample_env(r, lod);
127
+ let brdf = textureSample(brdf_lut_tex, brdf_lut_samp,
128
+ vec2<f32>(n_dot_v, roughness)).rg;
129
+ let specular = env * (f * brdf.x + brdf.y);
130
+
131
+ return (diffuse + specular) * occlusion;
132
+ }
133
+
134
+ // =====================================================================
135
+ // EN-012 — Foliage shading model
136
+ // =====================================================================
137
+ //
138
+ // Wrap-lambert diffuse + sun-behind-leaf transmission. Use from custom
139
+ // material shaders that declare the foliage shading model
140
+ // (MaterialFactors.shading_model.x == 1.0). The standard pattern is:
141
+ //
142
+ // if (material.shading_model.x > 0.5) {
143
+ // out_color = shade_foliage(N, L, V, lit, sun_color, albedo);
144
+ // } else {
145
+ // out_color = shade_pbr_standard(...);
146
+ // }
147
+ //
148
+ // Inputs:
149
+ // N_world = surface normal in world space
150
+ // L_world = direction TO the sun (normalized)
151
+ // V_world = direction TO the camera (normalized)
152
+ // base_color_lit = the standard lit colour (sun_color * direct_lambert)
153
+ // — V1 doesn't actually consume this; reserved for V2
154
+ // when foliage gets to layer over the standard term
155
+ // sun_color = sun radiance entering this fragment
156
+ // base_albedo = surface albedo (used for both lit + transmitted terms)
157
+ //
158
+ // Returns the final shaded colour. Reads MaterialFactors:
159
+ // shading_model.yzw = transmission_color
160
+ // foliage_params.x = transmission_amount
161
+ // foliage_params.y = wrap_factor
162
+ //
163
+ // V1 limitation: ambient / IBL / specular are NOT included — materials
164
+ // that want IBL ambient on foliage add it themselves outside this
165
+ // helper. shade_foliage is just the directional terms.
166
+ fn shade_foliage(
167
+ N_world: vec3<f32>, L_world: vec3<f32>, V_world: vec3<f32>,
168
+ base_color_lit: vec3<f32>, sun_color: vec3<f32>, base_albedo: vec3<f32>,
169
+ ) -> vec3<f32> {
170
+ // Wrap-lambert: back-faces don't go pure black. wrap=0 reproduces
171
+ // standard lambert; wrap=1 wraps light fully around the back.
172
+ let wrap = material.foliage_params.y;
173
+ let n_dot_l = dot(N_world, L_world);
174
+ let wrap_diffuse = max((n_dot_l + wrap) / (1.0 + wrap), 0.0);
175
+ let lit = base_albedo * sun_color * wrap_diffuse;
176
+
177
+ // Transmission: sun behind the leaf → warm tint into the camera.
178
+ // V . -L is high when the camera is looking toward the sun through
179
+ // the leaf. The pow(., 4) gives a tight halo around the sun.
180
+ let v_dot_neg_l = max(dot(V_world, -L_world), 0.0);
181
+ let trans_strength = pow(v_dot_neg_l, 4.0) * material.foliage_params.x;
182
+ let trans_color = material.shading_model.yzw;
183
+ let transmitted = base_albedo * sun_color * trans_color * trans_strength;
184
+
185
+ return lit + transmitted;
186
+ }
@@ -0,0 +1,186 @@
1
+ // Cascaded shadow maps — 3-cascade PCF sampling for the material path.
2
+ //
3
+ // Depends on material_abi.wgsl for PerView.shadow_cascades, shadow_splits,
4
+ // camera_pos, and the three shadow_tex_N depth samplers.
5
+ //
6
+ // Cascade selection uses world-space DISTANCE from the camera (not view-space
7
+ // depth) so that merely rotating the camera doesn't reshuffle which cascade a
8
+ // surface falls in — that view-dependent reshuffle made terrain/foliage
9
+ // shadows visibly swim as the camera turned. Cross-cascade blending at the
10
+ // split boundaries removes the hard seam that otherwise slides across the
11
+ // ground with the camera. This mirrors the deferred core path
12
+ // (renderer/shaders/core.rs::sample_shadow), so the material-shaded surfaces
13
+ // (terrain, grass, trees, water) match the rest of the scene.
14
+
15
+ // Sample a single cascade with a 4-tap rotated-grid PCF kernel. The kernel
16
+ // softens the edge a touch and hides per-texel crawl without the cost of a
17
+ // full Poisson disk.
18
+ fn sample_shadow_cascade(
19
+ cascade_idx: u32, world_pos: vec3<f32>,
20
+ ) -> f32 {
21
+ let light_clip = view.shadow_cascades[cascade_idx]
22
+ * vec4<f32>(world_pos, 1.0);
23
+ let light_ndc = light_clip.xyz / light_clip.w;
24
+
25
+ // Outside the cascade's frustum → treat as lit.
26
+ if (abs(light_ndc.x) > 1.0 || abs(light_ndc.y) > 1.0
27
+ || light_ndc.z < 0.0 || light_ndc.z > 1.0) {
28
+ return 1.0;
29
+ }
30
+
31
+ let uv = vec2<f32>(
32
+ light_ndc.x * 0.5 + 0.5,
33
+ 1.0 - (light_ndc.y * 0.5 + 0.5),
34
+ );
35
+ // Slight depth bias to avoid surface acne.
36
+ let ref_depth = light_ndc.z - 0.001;
37
+
38
+ var dims: vec2<u32>;
39
+ switch (cascade_idx) {
40
+ case 0u: { dims = textureDimensions(shadow_tex_0); }
41
+ case 1u: { dims = textureDimensions(shadow_tex_1); }
42
+ default: { dims = textureDimensions(shadow_tex_2); }
43
+ }
44
+ let texel = vec2<f32>(1.0 / f32(dims.x), 1.0 / f32(dims.y));
45
+
46
+ var offs = array<vec2<f32>, 4>(
47
+ vec2<f32>(-0.5, -0.5), vec2<f32>( 0.5, -0.5),
48
+ vec2<f32>(-0.5, 0.5), vec2<f32>( 0.5, 0.5),
49
+ );
50
+ var result: f32 = 0.0;
51
+ for (var i: i32 = 0; i < 4; i = i + 1) {
52
+ let suv = uv + offs[i] * texel;
53
+ // wgpu doesn't let us index depth-texture arrays yet — branch.
54
+ switch (cascade_idx) {
55
+ case 0u: { result += textureSampleCompareLevel(shadow_tex_0, shadow_samp, suv, ref_depth); }
56
+ case 1u: { result += textureSampleCompareLevel(shadow_tex_1, shadow_samp, suv, ref_depth); }
57
+ default: { result += textureSampleCompareLevel(shadow_tex_2, shadow_samp, suv, ref_depth); }
58
+ }
59
+ }
60
+ return result * 0.25;
61
+ }
62
+
63
+ // Game-shader entry point. Picks a cascade by rotation-independent world-space
64
+ // distance, blends across the boundary, and returns a shadow factor in [0, 1]
65
+ // (1 = fully lit, 0 = fully shadowed). Use this from custom materials (grass,
66
+ // tree, terrain, water) that want to receive the directional sun shadow with
67
+ // one line. Requires `view` (PerView) to be in scope — any shader that
68
+ // includes material_abi.wgsl already has it.
69
+ fn sample_sun_shadow(world_pos: vec3<f32>) -> f32 {
70
+ // dir_light_count.y carries the shadows-enabled flag (mirrors the core
71
+ // path's sample_shadow gate; shadow_splits.w is the TSR mip-LOD bias, so
72
+ // it can't double as a flag): disabled shadows must read fully lit, not
73
+ // project through stale cascade VPs whose garbage NDC reads as occluded.
74
+ if (view.dir_light_count.y < 0.5) {
75
+ return 1.0;
76
+ }
77
+ let cam = view.camera_pos.xyz;
78
+ let dist = length(world_pos - cam);
79
+
80
+ var cascade = 2u;
81
+ if (dist <= view.shadow_splits.x) {
82
+ cascade = 0u;
83
+ } else if (dist <= view.shadow_splits.y) {
84
+ cascade = 1u;
85
+ }
86
+
87
+ let shadow_val = sample_shadow_cascade(cascade, world_pos);
88
+
89
+ // Blend into the next cascade over the last 10% of this cascade's range so
90
+ // the transition is a soft gradient rather than a hard line that the camera
91
+ // drags across the scene.
92
+ var split_near = 0.0;
93
+ var split_far = view.shadow_splits.x;
94
+ if (cascade == 1u) {
95
+ split_near = view.shadow_splits.x;
96
+ split_far = view.shadow_splits.y;
97
+ } else if (cascade == 2u) {
98
+ split_near = view.shadow_splits.y;
99
+ split_far = view.shadow_splits.z;
100
+ }
101
+ let blend_zone = (split_far - split_near) * 0.1;
102
+ let dist_to_edge = split_far - dist;
103
+ if (cascade < 2u && dist_to_edge < blend_zone) {
104
+ let next_val = sample_shadow_cascade(cascade + 1u, world_pos);
105
+ let t = clamp(dist_to_edge / blend_zone, 0.0, 1.0);
106
+ return mix(next_val, shadow_val, t);
107
+ }
108
+ return shadow_val;
109
+ }
110
+
111
+ // Normal-offset variant — use this when the surface normal is available
112
+ // (it always is in a lit material shader). Offsets the receiver position
113
+ // along the geometric normal by ~1.5 cascade texels before projecting,
114
+ // which kills the full-face acne a constant depth bias can't cover on
115
+ // steep slopes and vertical walls (a wall at 40° sun incidence spans
116
+ // several texels of depth across one shadow texel — the deferred core
117
+ // path learned this the hard way; see core.rs::sample_shadow). Now that
118
+ // material-path geometry (building, terrain, trees) casts into the
119
+ // cascades, every material surface is a potential self-receiver and
120
+ // needs the same treatment.
121
+ fn sample_sun_shadow_n(world_pos: vec3<f32>, geo_n: vec3<f32>) -> f32 {
122
+ if (view.dir_light_count.y < 0.5) {
123
+ return 1.0;
124
+ }
125
+ let cam = view.camera_pos.xyz;
126
+ let dist = length(world_pos - cam);
127
+ var cascade = 2u;
128
+ if (dist <= view.shadow_splits.x) {
129
+ cascade = 0u;
130
+ } else if (dist <= view.shadow_splits.y) {
131
+ cascade = 1u;
132
+ }
133
+ // World-space size of one shadow texel in this cascade: the ortho span
134
+ // is ~2×split distance, mapped across the cascade map's width.
135
+ // textureDimensions keeps this honest if CASCADE_MAP_SIZE changes.
136
+ //
137
+ // Near a split boundary sample_sun_shadow blends in the NEXT cascade,
138
+ // whose texels are larger — an offset sized for this cascade
139
+ // under-biases that sample and paints acne bands exactly in the blend
140
+ // zones (the deferred core path hit the same thing). Size the offset
141
+ // by the next cascade's texel when inside its blend zone.
142
+ var split_far = view.shadow_splits.z;
143
+ if (cascade == 0u) { split_far = view.shadow_splits.x; }
144
+ else if (cascade == 1u) { split_far = view.shadow_splits.y; }
145
+ var split_near = 0.0;
146
+ if (cascade == 1u) { split_near = view.shadow_splits.x; }
147
+ else if (cascade == 2u) { split_near = view.shadow_splits.y; }
148
+ var eff_cascade = cascade;
149
+ if (cascade < 2u && (split_far - dist) < (split_far - split_near) * 0.1) {
150
+ eff_cascade = cascade + 1u;
151
+ if (eff_cascade == 1u) { split_far = view.shadow_splits.y; }
152
+ else { split_far = view.shadow_splits.z; }
153
+ }
154
+ var dims: vec2<u32>;
155
+ switch (eff_cascade) {
156
+ case 0u: { dims = textureDimensions(shadow_tex_0); }
157
+ case 1u: { dims = textureDimensions(shadow_tex_1); }
158
+ default: { dims = textureDimensions(shadow_tex_2); }
159
+ }
160
+ let texel_ws = (2.0 * split_far) / f32(dims.x);
161
+ // Slope-adaptive: at grazing sun incidence one shadow texel spans many
162
+ // times its footprint in receiver depth, so the fixed 1.5-texel offset
163
+ // still stripes walls that run nearly parallel to the light and distant
164
+ // hillsides in the coarse cascade. Grow the offset as n·l falls off
165
+ // (up to ~4.5 texels when fully grazing).
166
+ let ndl = clamp(dot(normalize(geo_n), normalize(view.sun_dir.xyz)), 0.0, 1.0);
167
+ let slope_boost = 1.0 + 2.0 * (1.0 - ndl);
168
+ let offset_pos = world_pos + normalize(geo_n) * texel_ws * 1.5 * slope_boost;
169
+ return sample_sun_shadow(offset_pos);
170
+ }
171
+
172
+ // ---- Back-compat shims -----------------------------------------------------
173
+ // Older callers selected a cascade from view-space depth then sampled it.
174
+ // Cascade selection now lives inside sample_sun_shadow (world-space distance),
175
+ // so these just forward — kept so any shader still including the old names
176
+ // keeps compiling.
177
+ fn select_cascade(view_space_depth: f32) -> u32 {
178
+ let d = abs(view_space_depth);
179
+ if (d < view.shadow_splits.x) { return 0u; }
180
+ if (d < view.shadow_splits.y) { return 1u; }
181
+ return 2u;
182
+ }
183
+
184
+ fn shadow(world_pos: vec3<f32>, view_space_depth: f32) -> f32 {
185
+ return sample_sun_shadow(world_pos);
186
+ }
@@ -0,0 +1,8 @@
1
+ // Sky sampling helpers — thin wrappers over pbr.wgsl's env sampler so
2
+ // the dedicated sky pipeline can stay short.
3
+
4
+ // Sample the HDR equirect env map along a world-space direction with
5
+ // no mip bias (sky pass doesn't want the roughness-based blur).
6
+ fn sample_sky(dir: vec3<f32>) -> vec3<f32> {
7
+ return sample_env(dir, 0.0);
8
+ }
@@ -0,0 +1,25 @@
1
+ // ACES filmic tonemapping — the Narkowicz fit, same one the composite
2
+ // pass uses today. Kept here as a shared helper so any future custom
3
+ // shader that wants its output already tonemapped (rare, usually
4
+ // post-FX territory) can reuse the same curve.
5
+
6
+ fn aces_tonemap(x: vec3<f32>) -> vec3<f32> {
7
+ let a = 2.51;
8
+ let b = 0.03;
9
+ let c = 2.43;
10
+ let d = 0.59;
11
+ let e = 0.14;
12
+ return clamp((x * (a * x + b)) / (x * (c * x + d) + e),
13
+ vec3<f32>(0.0), vec3<f32>(1.0));
14
+ }
15
+
16
+ // Linear-to-sRGB for the rare case a shader needs to sample a texture
17
+ // stored as linear and output sRGB directly. The engine's composite
18
+ // pass already does this at display time, so most materials never call
19
+ // it.
20
+ fn linear_to_srgb(x: vec3<f32>) -> vec3<f32> {
21
+ let cutoff = step(vec3<f32>(0.0031308), x);
22
+ let low = x * 12.92;
23
+ let high = 1.055 * pow(x, vec3<f32>(1.0 / 2.4)) - 0.055;
24
+ return mix(low, high, cutoff);
25
+ }
@@ -0,0 +1,57 @@
1
+ // Phase 7 — impulse field update compute shader.
2
+ //
3
+ // Input: `src` — last frame's R32Float field (world-space top-down).
4
+ // `info` — world bounds, decay factor, queued splats.
5
+ // Output: `dst` — this frame's field.
6
+ //
7
+ // Per texel: value = min(previous * decay + sum(splat strength × (1-d/r)²), 1).
8
+ // The clamp keeps repeated splats at the same spot (a player wading
9
+ // through water submits one every few frames) from accumulating far
10
+ // past 1.0 — an unclamped field takes seconds of decay to drop back
11
+ // under 1.0, which reads as a stuck full-strength splat.
12
+
13
+ struct Splat {
14
+ pos: vec2<f32>, // world xz
15
+ radius: f32,
16
+ strength: f32,
17
+ };
18
+
19
+ struct Info {
20
+ world_min: vec2<f32>,
21
+ world_size: vec2<f32>,
22
+ decay: f32,
23
+ _pad0: f32,
24
+ splat_count: u32,
25
+ _pad1: u32,
26
+ splats: array<Splat, 16>,
27
+ };
28
+
29
+ @group(0) @binding(0) var src: texture_2d<f32>;
30
+ @group(0) @binding(1) var dst: texture_storage_2d<r32float, write>;
31
+ @group(0) @binding(2) var<uniform> info: Info;
32
+
33
+ @compute @workgroup_size(8, 8)
34
+ fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
35
+ let dims = textureDimensions(src);
36
+ if (gid.x >= dims.x || gid.y >= dims.y) { return; }
37
+
38
+ // Decay the previous field.
39
+ let prev = textureLoad(src, vec2<i32>(gid.xy), 0).r;
40
+ var value = prev * info.decay;
41
+
42
+ // Convert the texel's world position.
43
+ let uv = (vec2<f32>(gid.xy) + vec2<f32>(0.5)) / vec2<f32>(dims);
44
+ let world = info.world_min + uv * info.world_size;
45
+
46
+ // Additively accumulate every queued splat.
47
+ for (var i: u32 = 0u; i < info.splat_count; i = i + 1u) {
48
+ let s = info.splats[i];
49
+ let d = distance(world, s.pos);
50
+ if (d < s.radius) {
51
+ let falloff = 1.0 - d / s.radius;
52
+ value = value + s.strength * falloff * falloff;
53
+ }
54
+ }
55
+
56
+ textureStore(dst, vec2<i32>(gid.xy), vec4<f32>(min(value, 1.0), 0.0, 0.0, 0.0));
57
+ }