@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.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- 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
|
+
}
|