@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,810 @@
|
|
|
1
|
+
//! Surface-cache GI: mesh cards, SDF bakes, WSRC.
|
|
2
|
+
//! Split from renderer/shaders.rs.
|
|
3
|
+
|
|
4
|
+
/// Ticket 013 V3 — Mesh-Cards capture shader with dual render targets.
|
|
5
|
+
///
|
|
6
|
+
/// Location 0 (albedo) + Location 1 (emissive). Rasterises a mesh
|
|
7
|
+
/// orthographically along its assigned signed axis into the card slot,
|
|
8
|
+
/// writing baked albedo and emissive into their respective atlases in
|
|
9
|
+
/// one draw. Emissive reads the material's emissive texture (if any)
|
|
10
|
+
/// and multiplies by the `emissive_factor`. Flat `base_color_factor` +
|
|
11
|
+
/// a 1×1 white fallback texture cover the case where the mesh has
|
|
12
|
+
/// only a scalar material.
|
|
13
|
+
pub(in crate::renderer) const CARD_CAPTURE_WGSL: &str = "
|
|
14
|
+
struct CaptureParams {
|
|
15
|
+
ortho_vp: mat4x4<f32>,
|
|
16
|
+
// Mesh's base_color_factor (xyz) + `has_base_texture` flag (w).
|
|
17
|
+
base_color: vec4<f32>,
|
|
18
|
+
// Mesh's emissive_factor (xyz) + `has_emissive_texture` flag (w).
|
|
19
|
+
emissive: vec4<f32>,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
struct VertexIn {
|
|
23
|
+
@location(0) position: vec3<f32>,
|
|
24
|
+
@location(1) normal: vec3<f32>,
|
|
25
|
+
@location(2) color: vec4<f32>,
|
|
26
|
+
@location(3) uv: vec2<f32>,
|
|
27
|
+
@location(4) joints: vec4<f32>,
|
|
28
|
+
@location(5) weights: vec4<f32>,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
struct VsOut {
|
|
32
|
+
@builtin(position) clip_pos: vec4<f32>,
|
|
33
|
+
@location(0) uv: vec2<f32>,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
struct FsOut {
|
|
37
|
+
@location(0) albedo: vec4<f32>,
|
|
38
|
+
@location(1) emissive: vec4<f32>,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
@group(0) @binding(0) var<uniform> u: CaptureParams;
|
|
42
|
+
@group(1) @binding(0) var albedo_tex: texture_2d<f32>;
|
|
43
|
+
@group(1) @binding(1) var albedo_samp: sampler;
|
|
44
|
+
@group(1) @binding(2) var emissive_tex: texture_2d<f32>;
|
|
45
|
+
|
|
46
|
+
@vertex
|
|
47
|
+
fn vs_main(v: VertexIn) -> VsOut {
|
|
48
|
+
var out: VsOut;
|
|
49
|
+
out.clip_pos = u.ortho_vp * vec4<f32>(v.position, 1.0);
|
|
50
|
+
out.uv = v.uv;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@fragment
|
|
55
|
+
fn fs_main(in: VsOut) -> FsOut {
|
|
56
|
+
var albedo = u.base_color.rgb;
|
|
57
|
+
if (u.base_color.w > 0.5) {
|
|
58
|
+
albedo = albedo * textureSample(albedo_tex, albedo_samp, in.uv).rgb;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var emissive = u.emissive.rgb;
|
|
62
|
+
if (u.emissive.w > 0.5) {
|
|
63
|
+
emissive = emissive * textureSample(emissive_tex, albedo_samp, in.uv).rgb;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
var out: FsOut;
|
|
67
|
+
out.albedo = vec4<f32>(albedo, 1.0);
|
|
68
|
+
// Rgba8UnormSrgb clamps emissive at 1.0 per channel; that's fine
|
|
69
|
+
// for Sponza-scale lanterns. Pre-divide by EMISSIVE_SCALE at write
|
|
70
|
+
// and multiply back at sample if HDR emissive becomes necessary.
|
|
71
|
+
out.emissive = vec4<f32>(clamp(emissive, vec3<f32>(0.0), vec3<f32>(1.0)), 1.0);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
";
|
|
75
|
+
|
|
76
|
+
/// Ticket 014 V1 — per-mesh unsigned distance field bake.
|
|
77
|
+
///
|
|
78
|
+
/// One compute invocation per voxel (32×32×32 = 32768 per mesh). Each
|
|
79
|
+
/// lane reads the mesh's vertex + index buffers as storage, iterates
|
|
80
|
+
/// all triangles, and computes `min(point-triangle distance)` from the
|
|
81
|
+
/// voxel centre. Output is R16Float distance (unsigned — we don't
|
|
82
|
+
/// attempt inside/outside classification, which is brittle on open
|
|
83
|
+
/// Sponza meshes; sphere-trace works with UDF either way because each
|
|
84
|
+
/// march step advances by `d` regardless of sign).
|
|
85
|
+
///
|
|
86
|
+
/// Vertex stride matches `Vertex3D` (12 f32 = 48 bytes). Only the
|
|
87
|
+
/// first 3 floats (position) are read.
|
|
88
|
+
pub(in crate::renderer) const SDF_BAKE_WGSL: &str = "
|
|
89
|
+
struct SdfBakeParams {
|
|
90
|
+
aabb_min: vec4<f32>,
|
|
91
|
+
aabb_max: vec4<f32>,
|
|
92
|
+
// x = triangle_count, y = sdf_resolution, zw unused
|
|
93
|
+
counts: vec4<u32>,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
@group(0) @binding(0) var<uniform> u: SdfBakeParams;
|
|
97
|
+
@group(0) @binding(1) var<storage, read> vertex_buf: array<f32>;
|
|
98
|
+
@group(0) @binding(2) var<storage, read> index_buf: array<u32>;
|
|
99
|
+
@group(0) @binding(3) var sdf_out: texture_storage_3d<r32float, write>;
|
|
100
|
+
|
|
101
|
+
const VERTEX_STRIDE_F32: u32 = 12u; // Vertex3D: pos(3) + normal(3) + color(4) + uv(2) = 12 f32
|
|
102
|
+
|
|
103
|
+
fn vtx_pos(idx: u32) -> vec3<f32> {
|
|
104
|
+
let base = idx * VERTEX_STRIDE_F32;
|
|
105
|
+
return vec3<f32>(vertex_buf[base], vertex_buf[base + 1u], vertex_buf[base + 2u]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Point-triangle distance, clamped-edge form. From Ericson, Real-Time
|
|
109
|
+
// Collision Detection. Returns unsigned distance to the closest point
|
|
110
|
+
// on triangle abc from point p.
|
|
111
|
+
fn point_triangle_distance(p: vec3<f32>, a: vec3<f32>, b: vec3<f32>, c: vec3<f32>) -> f32 {
|
|
112
|
+
let ab = b - a;
|
|
113
|
+
let ac = c - a;
|
|
114
|
+
let ap = p - a;
|
|
115
|
+
let d1 = dot(ab, ap);
|
|
116
|
+
let d2 = dot(ac, ap);
|
|
117
|
+
if (d1 <= 0.0 && d2 <= 0.0) { return length(ap); }
|
|
118
|
+
let bp = p - b;
|
|
119
|
+
let d3 = dot(ab, bp);
|
|
120
|
+
let d4 = dot(ac, bp);
|
|
121
|
+
if (d3 >= 0.0 && d4 <= d3) { return length(bp); }
|
|
122
|
+
let vc = d1 * d4 - d3 * d2;
|
|
123
|
+
if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
|
|
124
|
+
let v = d1 / (d1 - d3);
|
|
125
|
+
return length(p - (a + v * ab));
|
|
126
|
+
}
|
|
127
|
+
let cp = p - c;
|
|
128
|
+
let d5 = dot(ab, cp);
|
|
129
|
+
let d6 = dot(ac, cp);
|
|
130
|
+
if (d6 >= 0.0 && d5 <= d6) { return length(cp); }
|
|
131
|
+
let vb = d5 * d2 - d1 * d6;
|
|
132
|
+
if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
|
|
133
|
+
let w = d2 / (d2 - d6);
|
|
134
|
+
return length(p - (a + w * ac));
|
|
135
|
+
}
|
|
136
|
+
let va = d3 * d6 - d5 * d4;
|
|
137
|
+
if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
|
|
138
|
+
let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
|
139
|
+
return length(p - (b + w * (c - b)));
|
|
140
|
+
}
|
|
141
|
+
// Closest point in face interior.
|
|
142
|
+
let denom = 1.0 / (va + vb + vc);
|
|
143
|
+
let v = vb * denom;
|
|
144
|
+
let w = vc * denom;
|
|
145
|
+
return length(p - (a + ab * v + ac * w));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@compute @workgroup_size(4, 4, 4)
|
|
149
|
+
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
150
|
+
let res = u.counts.y;
|
|
151
|
+
if (gid.x >= res || gid.y >= res || gid.z >= res) { return; }
|
|
152
|
+
|
|
153
|
+
// Voxel centre in object space.
|
|
154
|
+
let uvw = (vec3<f32>(gid) + vec3<f32>(0.5)) / f32(res);
|
|
155
|
+
let voxel_os = mix(u.aabb_min.xyz, u.aabb_max.xyz, uvw);
|
|
156
|
+
|
|
157
|
+
var min_dist = 1e9;
|
|
158
|
+
let tri_count = u.counts.x;
|
|
159
|
+
for (var t: u32 = 0u; t < tri_count; t = t + 1u) {
|
|
160
|
+
let i0 = index_buf[t * 3u + 0u];
|
|
161
|
+
let i1 = index_buf[t * 3u + 1u];
|
|
162
|
+
let i2 = index_buf[t * 3u + 2u];
|
|
163
|
+
let a = vtx_pos(i0);
|
|
164
|
+
let b = vtx_pos(i1);
|
|
165
|
+
let c = vtx_pos(i2);
|
|
166
|
+
let d = point_triangle_distance(voxel_os, a, b, c);
|
|
167
|
+
min_dist = min(min_dist, d);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
textureStore(sdf_out, vec3<i32>(gid), vec4<f32>(min_dist, 0.0, 0.0, 0.0));
|
|
171
|
+
}
|
|
172
|
+
";
|
|
173
|
+
|
|
174
|
+
/// Fullscreen-lag fix — scene-clipmap variant of the SDF bake.
|
|
175
|
+
///
|
|
176
|
+
/// The original clipmap bake reused `SDF_BAKE_WGSL`: every one of the
|
|
177
|
+
/// 64³ voxels looped over EVERY scene triangle, and the whole volume
|
|
178
|
+
/// baked in a single dispatch. On integrated GPUs that one dispatch
|
|
179
|
+
/// stalled the end-of-frame submit for seconds whenever the camera
|
|
180
|
+
/// drifted 10 m. This variant fixes both axes of that cost:
|
|
181
|
+
///
|
|
182
|
+
/// - Triangles are binned on the CPU into `counts.x` cells per axis,
|
|
183
|
+
/// each cell's list pre-expanded by one cell in every direction. A
|
|
184
|
+
/// voxel only tests its own cell's list. Distances are clamped at
|
|
185
|
+
/// one cell width (`band`); because of the one-cell expansion the
|
|
186
|
+
/// clamp never OVERestimates the distance to the nearest surface,
|
|
187
|
+
/// which keeps sphere-trace steps conservative. Empty-air cells skip
|
|
188
|
+
/// the triangle loop entirely.
|
|
189
|
+
/// - `counts.z` carries a voxel Z offset so the caller can bake a few
|
|
190
|
+
/// Z-layers per frame into a staging texture instead of the whole
|
|
191
|
+
/// volume at once.
|
|
192
|
+
pub(in crate::renderer) const SDF_CLIPMAP_BAKE_WGSL: &str = "
|
|
193
|
+
struct SdfClipmapBakeParams {
|
|
194
|
+
aabb_min: vec4<f32>,
|
|
195
|
+
aabb_max: vec4<f32>,
|
|
196
|
+
// x = bin cells per axis, y = sdf resolution,
|
|
197
|
+
// z = voxel Z offset of this slice batch, w unused
|
|
198
|
+
counts: vec4<u32>,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
@group(0) @binding(0) var<uniform> u: SdfClipmapBakeParams;
|
|
202
|
+
@group(0) @binding(1) var<storage, read> vertex_buf: array<f32>;
|
|
203
|
+
@group(0) @binding(2) var<storage, read> index_buf: array<u32>;
|
|
204
|
+
@group(0) @binding(3) var sdf_out: texture_storage_3d<r32float, write>;
|
|
205
|
+
@group(0) @binding(4) var<storage, read> cell_offsets: array<u32>;
|
|
206
|
+
@group(0) @binding(5) var<storage, read> cell_tris: array<u32>;
|
|
207
|
+
|
|
208
|
+
const VERTEX_STRIDE_F32: u32 = 12u; // Vertex3D: pos(3) + normal(3) + color(4) + uv(2)
|
|
209
|
+
|
|
210
|
+
fn vtx_pos(idx: u32) -> vec3<f32> {
|
|
211
|
+
let base = idx * VERTEX_STRIDE_F32;
|
|
212
|
+
return vec3<f32>(vertex_buf[base], vertex_buf[base + 1u], vertex_buf[base + 2u]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Point-triangle distance, clamped-edge form (Ericson).
|
|
216
|
+
fn point_triangle_distance(p: vec3<f32>, a: vec3<f32>, b: vec3<f32>, c: vec3<f32>) -> f32 {
|
|
217
|
+
let ab = b - a;
|
|
218
|
+
let ac = c - a;
|
|
219
|
+
let ap = p - a;
|
|
220
|
+
let d1 = dot(ab, ap);
|
|
221
|
+
let d2 = dot(ac, ap);
|
|
222
|
+
if (d1 <= 0.0 && d2 <= 0.0) { return length(ap); }
|
|
223
|
+
let bp = p - b;
|
|
224
|
+
let d3 = dot(ab, bp);
|
|
225
|
+
let d4 = dot(ac, bp);
|
|
226
|
+
if (d3 >= 0.0 && d4 <= d3) { return length(bp); }
|
|
227
|
+
let vc = d1 * d4 - d3 * d2;
|
|
228
|
+
if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
|
|
229
|
+
let v = d1 / (d1 - d3);
|
|
230
|
+
return length(p - (a + v * ab));
|
|
231
|
+
}
|
|
232
|
+
let cp = p - c;
|
|
233
|
+
let d5 = dot(ab, cp);
|
|
234
|
+
let d6 = dot(ac, cp);
|
|
235
|
+
if (d6 >= 0.0 && d5 <= d6) { return length(cp); }
|
|
236
|
+
let vb = d5 * d2 - d1 * d6;
|
|
237
|
+
if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
|
|
238
|
+
let w = d2 / (d2 - d6);
|
|
239
|
+
return length(p - (a + w * ac));
|
|
240
|
+
}
|
|
241
|
+
let va = d3 * d6 - d5 * d4;
|
|
242
|
+
if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
|
|
243
|
+
let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
|
244
|
+
return length(p - (b + w * (c - b)));
|
|
245
|
+
}
|
|
246
|
+
let denom = 1.0 / (va + vb + vc);
|
|
247
|
+
let v = vb * denom;
|
|
248
|
+
let w = vc * denom;
|
|
249
|
+
return length(p - (a + ab * v + ac * w));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@compute @workgroup_size(4, 4, 4)
|
|
253
|
+
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
254
|
+
let res = u.counts.y;
|
|
255
|
+
let vox = vec3<u32>(gid.x, gid.y, gid.z + u.counts.z);
|
|
256
|
+
if (vox.x >= res || vox.y >= res || vox.z >= res) { return; }
|
|
257
|
+
|
|
258
|
+
let uvw = (vec3<f32>(vox) + vec3<f32>(0.5)) / f32(res);
|
|
259
|
+
let voxel_ws = mix(u.aabb_min.xyz, u.aabb_max.xyz, uvw);
|
|
260
|
+
|
|
261
|
+
let cells = u.counts.x;
|
|
262
|
+
let vpc = res / cells;
|
|
263
|
+
let cell = vox / vpc;
|
|
264
|
+
let ci = (cell.z * cells + cell.y) * cells + cell.x;
|
|
265
|
+
let start = cell_offsets[ci];
|
|
266
|
+
let end = cell_offsets[ci + 1u];
|
|
267
|
+
|
|
268
|
+
let band = (u.aabb_max.x - u.aabb_min.x) / f32(cells);
|
|
269
|
+
var min_dist = band;
|
|
270
|
+
for (var k: u32 = start; k < end; k = k + 1u) {
|
|
271
|
+
let t = cell_tris[k];
|
|
272
|
+
let a = vtx_pos(index_buf[t * 3u + 0u]);
|
|
273
|
+
let b = vtx_pos(index_buf[t * 3u + 1u]);
|
|
274
|
+
let c = vtx_pos(index_buf[t * 3u + 2u]);
|
|
275
|
+
min_dist = min(min_dist, point_triangle_distance(voxel_ws, a, b, c));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
textureStore(sdf_out, vec3<i32>(vox), vec4<f32>(min_dist, 0.0, 0.0, 0.0));
|
|
279
|
+
}
|
|
280
|
+
";
|
|
281
|
+
|
|
282
|
+
/// Ticket 013 V3 — per-frame card-lighting compute pass with
|
|
283
|
+
/// shadow-cascade sampling + emissive contribution.
|
|
284
|
+
///
|
|
285
|
+
/// Per texel: reconstruct the world-space position of the card point
|
|
286
|
+
/// (slot metadata's aabb + axis + mesh transform), sample the shadow
|
|
287
|
+
/// cascade at that point, compute direct + sky + emissive. Writes to
|
|
288
|
+
/// the radiance atlas. Shadow lookup makes indirect bounce meaningfully
|
|
289
|
+
/// darker on sun-occluded card faces (arches, column undersides).
|
|
290
|
+
pub(in crate::renderer) const CARD_LIGHT_WGSL: &str = "
|
|
291
|
+
struct SlotMeta {
|
|
292
|
+
// xyz = world-space card-face normal, w = signed axis (0..6 as f32)
|
|
293
|
+
normal_ws: vec4<f32>,
|
|
294
|
+
aabb_min: vec4<f32>,
|
|
295
|
+
aabb_max: vec4<f32>,
|
|
296
|
+
transform: mat4x4<f32>,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
struct CardLightParams {
|
|
300
|
+
sun_dir: vec4<f32>,
|
|
301
|
+
sun_color: vec4<f32>,
|
|
302
|
+
sky_color: vec4<f32>,
|
|
303
|
+
atlas_info: vec4<u32>,
|
|
304
|
+
shadow_vps: array<mat4x4<f32>, 3>,
|
|
305
|
+
shadow_splits: vec4<f32>,
|
|
306
|
+
view_matrix: mat4x4<f32>,
|
|
307
|
+
flags: vec4<f32>,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
@group(0) @binding(0) var<uniform> u: CardLightParams;
|
|
311
|
+
@group(0) @binding(1) var albedo_atlas: texture_2d<f32>;
|
|
312
|
+
@group(0) @binding(2) var atlas_samp: sampler;
|
|
313
|
+
@group(0) @binding(3) var<storage, read> slot_meta: array<SlotMeta>;
|
|
314
|
+
@group(0) @binding(4) var radiance_out: texture_storage_2d<rgba16float, write>;
|
|
315
|
+
@group(0) @binding(5) var emissive_atlas: texture_2d<f32>;
|
|
316
|
+
@group(0) @binding(6) var shadow_atlas_0: texture_depth_2d;
|
|
317
|
+
@group(0) @binding(7) var shadow_atlas_1: texture_depth_2d;
|
|
318
|
+
@group(0) @binding(8) var shadow_atlas_2: texture_depth_2d;
|
|
319
|
+
@group(0) @binding(9) var shadow_samp: sampler_comparison;
|
|
320
|
+
|
|
321
|
+
fn sample_cascade(cascade: i32, pos_ws: vec3<f32>, bias: f32) -> f32 {
|
|
322
|
+
var clip: vec4<f32>;
|
|
323
|
+
if (cascade == 0) {
|
|
324
|
+
clip = u.shadow_vps[0] * vec4<f32>(pos_ws, 1.0);
|
|
325
|
+
} else if (cascade == 1) {
|
|
326
|
+
clip = u.shadow_vps[1] * vec4<f32>(pos_ws, 1.0);
|
|
327
|
+
} else {
|
|
328
|
+
clip = u.shadow_vps[2] * vec4<f32>(pos_ws, 1.0);
|
|
329
|
+
}
|
|
330
|
+
let ndc = clip.xyz / clip.w;
|
|
331
|
+
// Outside the cascade frustum → treat as lit (no shadow).
|
|
332
|
+
if (ndc.x < -1.0 || ndc.x > 1.0 || ndc.y < -1.0 || ndc.y > 1.0 || ndc.z < 0.0 || ndc.z > 1.0) {
|
|
333
|
+
return 1.0;
|
|
334
|
+
}
|
|
335
|
+
let shadow_uv = vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
336
|
+
let ref_depth = ndc.z - bias;
|
|
337
|
+
if (cascade == 0) {
|
|
338
|
+
return textureSampleCompareLevel(shadow_atlas_0, shadow_samp, shadow_uv, ref_depth);
|
|
339
|
+
} else if (cascade == 1) {
|
|
340
|
+
return textureSampleCompareLevel(shadow_atlas_1, shadow_samp, shadow_uv, ref_depth);
|
|
341
|
+
} else {
|
|
342
|
+
return textureSampleCompareLevel(shadow_atlas_2, shadow_samp, shadow_uv, ref_depth);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@compute @workgroup_size(8, 8, 1)
|
|
347
|
+
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
348
|
+
let px = gid.xy;
|
|
349
|
+
let atlas_sz = u.atlas_info.x;
|
|
350
|
+
if (px.x >= atlas_sz || px.y >= atlas_sz) { return; }
|
|
351
|
+
|
|
352
|
+
let slot_sz = u.atlas_info.y;
|
|
353
|
+
let slots_per_row = u.atlas_info.z;
|
|
354
|
+
let active_count = u.atlas_info.w;
|
|
355
|
+
|
|
356
|
+
let slot_x = px.x / slot_sz;
|
|
357
|
+
let slot_y = px.y / slot_sz;
|
|
358
|
+
let slot_idx = slot_y * slots_per_row + slot_x;
|
|
359
|
+
|
|
360
|
+
if (slot_idx >= active_count) {
|
|
361
|
+
textureStore(radiance_out, vec2<i32>(px), vec4<f32>(0.0));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let uv = (vec2<f32>(px) + vec2<f32>(0.5)) / f32(atlas_sz);
|
|
366
|
+
let albedo = textureSampleLevel(albedo_atlas, atlas_samp, uv, 0.0).rgb;
|
|
367
|
+
let emissive = textureSampleLevel(emissive_atlas, atlas_samp, uv, 0.0).rgb;
|
|
368
|
+
|
|
369
|
+
let slot_m = slot_meta[slot_idx];
|
|
370
|
+
let n_ws = slot_m.normal_ws.xyz;
|
|
371
|
+
let axis = u32(slot_m.normal_ws.w);
|
|
372
|
+
|
|
373
|
+
// Local slot UV (0..1 inside this slot) → object-space card-plane
|
|
374
|
+
// position. Signed-axis-aware u-flip matches the capture pass's
|
|
375
|
+
// projection so the texel we light is the one the capture baked.
|
|
376
|
+
let sx = f32((px.x) % slot_sz);
|
|
377
|
+
let sy = f32((px.y) % slot_sz);
|
|
378
|
+
let sd = f32(slot_sz);
|
|
379
|
+
var u_norm = (sx + 0.5) / sd;
|
|
380
|
+
let v_norm = (sy + 0.5) / sd;
|
|
381
|
+
var pos_os = vec3<f32>(0.0);
|
|
382
|
+
if (axis == 0u || axis == 1u) {
|
|
383
|
+
// Card plane at x = bmax.x (+X) or bmin.x (-X); u=y, v=z.
|
|
384
|
+
if (axis == 1u) { u_norm = 1.0 - u_norm; }
|
|
385
|
+
pos_os.y = mix(slot_m.aabb_min.y, slot_m.aabb_max.y, u_norm);
|
|
386
|
+
pos_os.z = mix(slot_m.aabb_min.z, slot_m.aabb_max.z, v_norm);
|
|
387
|
+
if (axis == 0u) { pos_os.x = slot_m.aabb_max.x; } else { pos_os.x = slot_m.aabb_min.x; }
|
|
388
|
+
} else if (axis == 2u || axis == 3u) {
|
|
389
|
+
if (axis == 3u) { u_norm = 1.0 - u_norm; }
|
|
390
|
+
pos_os.x = mix(slot_m.aabb_min.x, slot_m.aabb_max.x, u_norm);
|
|
391
|
+
pos_os.z = mix(slot_m.aabb_min.z, slot_m.aabb_max.z, v_norm);
|
|
392
|
+
if (axis == 2u) { pos_os.y = slot_m.aabb_max.y; } else { pos_os.y = slot_m.aabb_min.y; }
|
|
393
|
+
} else {
|
|
394
|
+
if (axis == 5u) { u_norm = 1.0 - u_norm; }
|
|
395
|
+
pos_os.x = mix(slot_m.aabb_min.x, slot_m.aabb_max.x, u_norm);
|
|
396
|
+
pos_os.y = mix(slot_m.aabb_min.y, slot_m.aabb_max.y, v_norm);
|
|
397
|
+
if (axis == 4u) { pos_os.z = slot_m.aabb_max.z; } else { pos_os.z = slot_m.aabb_min.z; }
|
|
398
|
+
}
|
|
399
|
+
let pos_ws = (slot_m.transform * vec4<f32>(pos_os, 1.0)).xyz;
|
|
400
|
+
|
|
401
|
+
// Shadow. Cascade selection uses view-space Z against splits.
|
|
402
|
+
var shadow: f32 = 1.0;
|
|
403
|
+
if (u.flags.y > 0.5) {
|
|
404
|
+
let view_z = -(u.view_matrix * vec4<f32>(pos_ws, 1.0)).z;
|
|
405
|
+
var cascade: i32 = 2;
|
|
406
|
+
if (view_z <= u.shadow_splits.x) {
|
|
407
|
+
cascade = 0;
|
|
408
|
+
} else if (view_z <= u.shadow_splits.y) {
|
|
409
|
+
cascade = 1;
|
|
410
|
+
}
|
|
411
|
+
shadow = sample_cascade(cascade, pos_ws, u.flags.x);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let ndotl = max(dot(n_ws, u.sun_dir.xyz), 0.0);
|
|
415
|
+
let direct = u.sun_color.xyz * ndotl * shadow;
|
|
416
|
+
let ndotup = max(dot(n_ws, vec3<f32>(0.0, 1.0, 0.0)), 0.0);
|
|
417
|
+
let sky = u.sky_color.xyz * ndotup;
|
|
418
|
+
|
|
419
|
+
let lit = albedo * (direct + sky) + emissive;
|
|
420
|
+
textureStore(radiance_out, vec2<i32>(px), vec4<f32>(lit, 1.0));
|
|
421
|
+
}
|
|
422
|
+
";
|
|
423
|
+
|
|
424
|
+
/// Ticket 014 V6 — World-Space Radiance Cache bake.
|
|
425
|
+
///
|
|
426
|
+
/// One workgroup per probe (16×16×16 probes on a 120 m camera-following
|
|
427
|
+
/// cube), 8×8 threads per workgroup = one octel texel each. Output is
|
|
428
|
+
/// the flat rgba16float atlas read by the SDF miss path.
|
|
429
|
+
///
|
|
430
|
+
/// Per texel: analytic sun term for the octel direction, gated by a
|
|
431
|
+
/// shadow-cascade lookup at the probe's world position (position-
|
|
432
|
+
/// varying occlusion — the crucial signal that makes the cache worth
|
|
433
|
+
/// having; flat analytic would mean every probe holds the same
|
|
434
|
+
/// content). Analytic sky hemisphere for up-biased directions. No
|
|
435
|
+
/// per-direction geometry trace — this is the "distant envelope",
|
|
436
|
+
/// not another SDF march.
|
|
437
|
+
pub(in crate::renderer) const WSRC_BAKE_WGSL: &str = "
|
|
438
|
+
struct WsrcBakeParams {
|
|
439
|
+
sun_dir: vec4<f32>,
|
|
440
|
+
sun_color: vec4<f32>,
|
|
441
|
+
sky_color: vec4<f32>,
|
|
442
|
+
// xyz = cascade origin (world-space cube centre), w = full extent
|
|
443
|
+
grid: vec4<f32>,
|
|
444
|
+
shadow_vps: array<mat4x4<f32>, 3>,
|
|
445
|
+
shadow_splits: vec4<f32>,
|
|
446
|
+
// x = shadow bias, y = shadows-enabled flag, z = cascade index
|
|
447
|
+
// (0..WSRC_CASCADE_COUNT), w unused. Cascade index offsets the
|
|
448
|
+
// output z-slice so this pipeline can be dispatched once per
|
|
449
|
+
// cascade with the same layout.
|
|
450
|
+
flags: vec4<f32>,
|
|
451
|
+
// EN-023 — xyz = scene-average albedo for the ground-bounce term.
|
|
452
|
+
ground_albedo: vec4<f32>,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
@group(0) @binding(0) var<uniform> u: WsrcBakeParams;
|
|
456
|
+
@group(0) @binding(1) var shadow_atlas_0: texture_depth_2d;
|
|
457
|
+
@group(0) @binding(2) var shadow_atlas_1: texture_depth_2d;
|
|
458
|
+
@group(0) @binding(3) var shadow_atlas_2: texture_depth_2d;
|
|
459
|
+
@group(0) @binding(4) var shadow_samp: sampler_comparison;
|
|
460
|
+
@group(0) @binding(5) var wsrc_out: texture_storage_3d<rgba16float, write>;
|
|
461
|
+
|
|
462
|
+
fn wsrc_sample_cascade(cascade: i32, pos_ws: vec3<f32>, bias: f32) -> f32 {
|
|
463
|
+
var clip: vec4<f32>;
|
|
464
|
+
if (cascade == 0) {
|
|
465
|
+
clip = u.shadow_vps[0] * vec4<f32>(pos_ws, 1.0);
|
|
466
|
+
} else if (cascade == 1) {
|
|
467
|
+
clip = u.shadow_vps[1] * vec4<f32>(pos_ws, 1.0);
|
|
468
|
+
} else {
|
|
469
|
+
clip = u.shadow_vps[2] * vec4<f32>(pos_ws, 1.0);
|
|
470
|
+
}
|
|
471
|
+
let ndc = clip.xyz / clip.w;
|
|
472
|
+
if (ndc.x < -1.0 || ndc.x > 1.0 || ndc.y < -1.0 || ndc.y > 1.0 || ndc.z < 0.0 || ndc.z > 1.0) {
|
|
473
|
+
return 1.0;
|
|
474
|
+
}
|
|
475
|
+
let shadow_uv = vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
476
|
+
let ref_depth = ndc.z - bias;
|
|
477
|
+
if (cascade == 0) {
|
|
478
|
+
return textureSampleCompareLevel(shadow_atlas_0, shadow_samp, shadow_uv, ref_depth);
|
|
479
|
+
} else if (cascade == 1) {
|
|
480
|
+
return textureSampleCompareLevel(shadow_atlas_1, shadow_samp, shadow_uv, ref_depth);
|
|
481
|
+
} else {
|
|
482
|
+
return textureSampleCompareLevel(shadow_atlas_2, shadow_samp, shadow_uv, ref_depth);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// V10 — workgroup writes the 10×10 padded slab per probe. Thread
|
|
487
|
+
// (lid.x, lid.y) writes texel (wg.*10 + lid) in the atlas; border
|
|
488
|
+
// threads (lid on 0 or 9) shade for the nearest INSIDE octel
|
|
489
|
+
// direction so the sampler's edge-extend behaviour is baked into
|
|
490
|
+
// the data. The 1-texel border is what lets the hardware bilinear
|
|
491
|
+
// sampler do octel smoothing without leaking into adjacent probes.
|
|
492
|
+
const WSRC_OCT_PADDED_SIZE: u32 = 10u;
|
|
493
|
+
|
|
494
|
+
@compute @workgroup_size(10, 10, 1)
|
|
495
|
+
fn cs_main(
|
|
496
|
+
@builtin(workgroup_id) wg: vec3<u32>,
|
|
497
|
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
|
498
|
+
) {
|
|
499
|
+
let grid_res: u32 = 16u;
|
|
500
|
+
if (wg.x >= grid_res || wg.y >= grid_res || wg.z >= grid_res) { return; }
|
|
501
|
+
if (lid.x >= WSRC_OCT_PADDED_SIZE || lid.y >= WSRC_OCT_PADDED_SIZE) { return; }
|
|
502
|
+
|
|
503
|
+
// Probe world-space centre — cell-centre within the grid cube.
|
|
504
|
+
let extent = u.grid.w;
|
|
505
|
+
let cell = extent / f32(grid_res);
|
|
506
|
+
let probe_pos = u.grid.xyz
|
|
507
|
+
- vec3<f32>(extent * 0.5)
|
|
508
|
+
+ (vec3<f32>(f32(wg.x), f32(wg.y), f32(wg.z)) + vec3<f32>(0.5)) * cell;
|
|
509
|
+
|
|
510
|
+
// V11 — map padded octel → real octel with true octahedral
|
|
511
|
+
// silhouette wrap on the 4 edges. Beyond v<0 or v>1 in octel uv
|
|
512
|
+
// space the octahedron folds onto itself with u ↔ 1-u; likewise
|
|
513
|
+
// u<0 or u>1 folds with v ↔ 1-v. Corners (both axes out) keep
|
|
514
|
+
// the V10 edge-extend fill since the double-fold has two valid
|
|
515
|
+
// representations and the exact corner only matters when the
|
|
516
|
+
// sampler bilinear-weights it near zero anyway.
|
|
517
|
+
let px = i32(lid.x);
|
|
518
|
+
let py = i32(lid.y);
|
|
519
|
+
let is_edge_x = px == 0 || px == 9;
|
|
520
|
+
let is_edge_y = py == 0 || py == 9;
|
|
521
|
+
var real_ox: i32;
|
|
522
|
+
var real_oy: i32;
|
|
523
|
+
if (is_edge_x && is_edge_y) {
|
|
524
|
+
// Corner — nearest-inside (edge-extend).
|
|
525
|
+
real_ox = clamp(px - 1, 0, 7);
|
|
526
|
+
real_oy = clamp(py - 1, 0, 7);
|
|
527
|
+
} else if (is_edge_y) {
|
|
528
|
+
// Top/bottom border: mirror x across the edge, same row.
|
|
529
|
+
real_ox = 8 - px;
|
|
530
|
+
real_oy = clamp(py - 1, 0, 7);
|
|
531
|
+
} else if (is_edge_x) {
|
|
532
|
+
// Left/right border: same column, mirror y across the edge.
|
|
533
|
+
real_ox = clamp(px - 1, 0, 7);
|
|
534
|
+
real_oy = 8 - py;
|
|
535
|
+
} else {
|
|
536
|
+
// Interior — direct mapping.
|
|
537
|
+
real_ox = px - 1;
|
|
538
|
+
real_oy = py - 1;
|
|
539
|
+
}
|
|
540
|
+
let dir = octel_direction(vec2<u32>(u32(real_ox), u32(real_oy)));
|
|
541
|
+
|
|
542
|
+
// Shadow at the probe position (cascade 2 — widest, covers the
|
|
543
|
+
// full 120 m cube without per-probe cascade selection).
|
|
544
|
+
var shadow: f32 = 1.0;
|
|
545
|
+
if (u.flags.y > 0.5) {
|
|
546
|
+
shadow = wsrc_sample_cascade(2, probe_pos, u.flags.x);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
let ndotl = max(dot(dir, u.sun_dir.xyz), 0.0);
|
|
550
|
+
let sun = u.sun_color.xyz * ndotl * shadow;
|
|
551
|
+
let up = clamp(dir.y * 0.5 + 0.5, 0.0, 1.0);
|
|
552
|
+
let sky = u.sky_color.xyz * up * up;
|
|
553
|
+
|
|
554
|
+
// EN-023 — ground bounce for below-horizon octels. This envelope
|
|
555
|
+
// was light-source-only: any miss ray pointing downward returned
|
|
556
|
+
// ~black, so shaded receivers lost the strongest real bounce
|
|
557
|
+
// source (sunlit ground). Approximate it as the scene-average
|
|
558
|
+
// albedo lit by the sun (shadowed at the probe) + half the sky.
|
|
559
|
+
let down = clamp(-dir.y, 0.0, 1.0);
|
|
560
|
+
let ground_irr = u.sun_color.xyz * max(u.sun_dir.y, 0.0) * shadow
|
|
561
|
+
+ u.sky_color.xyz * 0.5;
|
|
562
|
+
let ground = u.ground_albedo.xyz * ground_irr * down * down;
|
|
563
|
+
|
|
564
|
+
let radiance = sun + sky + ground;
|
|
565
|
+
|
|
566
|
+
// V13 — cascade index in flags.z offsets the output z-slice.
|
|
567
|
+
let cascade_idx = u32(u.flags.z);
|
|
568
|
+
let tex_coord = vec3<i32>(
|
|
569
|
+
i32(wg.x * WSRC_OCT_PADDED_SIZE + lid.x),
|
|
570
|
+
i32(wg.y * WSRC_OCT_PADDED_SIZE + lid.y),
|
|
571
|
+
i32(cascade_idx * grid_res + wg.z),
|
|
572
|
+
);
|
|
573
|
+
textureStore(wsrc_out, tex_coord, vec4<f32>(radiance, 1.0));
|
|
574
|
+
}
|
|
575
|
+
";
|
|
576
|
+
|
|
577
|
+
/// Ticket 014 V14 — HW-ray-traced WSRC bake for RT-capable adapters.
|
|
578
|
+
///
|
|
579
|
+
/// Same probe grid + padded octel layout as the SW bake; the
|
|
580
|
+
/// difference is the radiance computation. For each probe-octel
|
|
581
|
+
/// texel:
|
|
582
|
+
/// 1. Fire a ray from `probe_pos` in `dir` (octel direction) with
|
|
583
|
+
/// `t_max = extent * 0.5` — long enough for a probe to reach
|
|
584
|
+
/// the cascade boundary, short enough that each cascade stays
|
|
585
|
+
/// in its spatial regime.
|
|
586
|
+
/// 2. On hit: sample the Mesh Cards radiance atlas at the hit
|
|
587
|
+
/// point (already pre-lit each frame with sun × shadow +
|
|
588
|
+
/// emissive by `card_light_pass`, so the bake propagates
|
|
589
|
+
/// one-bounce shaded radiance into WSRC — effectively 2-bounce
|
|
590
|
+
/// when the SSGI probe then samples WSRC on miss).
|
|
591
|
+
/// 3. On miss: analytic sun (shadow-sampled at the probe, not
|
|
592
|
+
/// per-direction) + hemisphere sky — same fallback as the V13
|
|
593
|
+
/// SW path, so escaped rays still carry a useful envelope.
|
|
594
|
+
///
|
|
595
|
+
/// Border texels still use the V11 octahedral wrap — same rule as
|
|
596
|
+
/// the SW bake. The cascade index comes from `flags.z` and offsets
|
|
597
|
+
/// the output z slice so one pipeline covers all 3 cascades via
|
|
598
|
+
/// per-dispatch uniform.
|
|
599
|
+
pub(in crate::renderer) const WSRC_BAKE_HW_WGSL: &str = "
|
|
600
|
+
struct WsrcBakeParams {
|
|
601
|
+
sun_dir: vec4<f32>,
|
|
602
|
+
sun_color: vec4<f32>,
|
|
603
|
+
sky_color: vec4<f32>,
|
|
604
|
+
grid: vec4<f32>,
|
|
605
|
+
shadow_vps: array<mat4x4<f32>, 3>,
|
|
606
|
+
shadow_splits: vec4<f32>,
|
|
607
|
+
flags: vec4<f32>,
|
|
608
|
+
// EN-023 — layout mirror; the HW bake traces real geometry and
|
|
609
|
+
// ignores the scene-average ground albedo.
|
|
610
|
+
ground_albedo: vec4<f32>,
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
struct HwBakeInstanceGiData {
|
|
614
|
+
albedo: vec3<f32>,
|
|
615
|
+
emissive_luma: f32,
|
|
616
|
+
normal_ws: vec3<f32>,
|
|
617
|
+
_pad0: f32,
|
|
618
|
+
card_slot: vec4<f32>,
|
|
619
|
+
card_aabb_min: vec4<f32>,
|
|
620
|
+
card_aabb_max: vec4<f32>,
|
|
621
|
+
// EN-023 — world-space AABB (SDF path only; layout mirror).
|
|
622
|
+
world_aabb_min: vec4<f32>,
|
|
623
|
+
world_aabb_max: vec4<f32>,
|
|
624
|
+
// PT-2 — layout mirror only; the WSRC bake ignores both fields.
|
|
625
|
+
geo: vec4<u32>,
|
|
626
|
+
mat_params: vec4<f32>,
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const HW_BAKE_CARD_SLOTS_PER_ROW: f32 = 64.0;
|
|
630
|
+
const HW_BAKE_OCT_PADDED: u32 = 10u;
|
|
631
|
+
|
|
632
|
+
@group(0) @binding(0) var<uniform> u: WsrcBakeParams;
|
|
633
|
+
@group(0) @binding(1) var shadow_atlas_0: texture_depth_2d;
|
|
634
|
+
@group(0) @binding(2) var shadow_atlas_1: texture_depth_2d;
|
|
635
|
+
@group(0) @binding(3) var shadow_atlas_2: texture_depth_2d;
|
|
636
|
+
@group(0) @binding(4) var shadow_samp: sampler_comparison;
|
|
637
|
+
@group(0) @binding(5) var wsrc_out: texture_storage_3d<rgba16float, write>;
|
|
638
|
+
@group(0) @binding(6) var accel: acceleration_structure;
|
|
639
|
+
@group(0) @binding(7) var<storage, read> instance_data: array<HwBakeInstanceGiData>;
|
|
640
|
+
@group(0) @binding(8) var card_atlas: texture_2d<f32>;
|
|
641
|
+
@group(0) @binding(9) var card_samp: sampler;
|
|
642
|
+
|
|
643
|
+
fn hw_bake_sample_cascade(cascade: i32, pos_ws: vec3<f32>, bias: f32) -> f32 {
|
|
644
|
+
var clip: vec4<f32>;
|
|
645
|
+
if (cascade == 0) { clip = u.shadow_vps[0] * vec4<f32>(pos_ws, 1.0); }
|
|
646
|
+
else if (cascade == 1) { clip = u.shadow_vps[1] * vec4<f32>(pos_ws, 1.0); }
|
|
647
|
+
else { clip = u.shadow_vps[2] * vec4<f32>(pos_ws, 1.0); }
|
|
648
|
+
let ndc = clip.xyz / clip.w;
|
|
649
|
+
if (ndc.x < -1.0 || ndc.x > 1.0 || ndc.y < -1.0 || ndc.y > 1.0 || ndc.z < 0.0 || ndc.z > 1.0) {
|
|
650
|
+
return 1.0;
|
|
651
|
+
}
|
|
652
|
+
let shadow_uv = vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
653
|
+
let ref_depth = ndc.z - bias;
|
|
654
|
+
if (cascade == 0) { return textureSampleCompareLevel(shadow_atlas_0, shadow_samp, shadow_uv, ref_depth); }
|
|
655
|
+
else if (cascade == 1) { return textureSampleCompareLevel(shadow_atlas_1, shadow_samp, shadow_uv, ref_depth); }
|
|
656
|
+
else { return textureSampleCompareLevel(shadow_atlas_2, shadow_samp, shadow_uv, ref_depth); }
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
@compute @workgroup_size(10, 10, 1)
|
|
660
|
+
fn cs_main(
|
|
661
|
+
@builtin(workgroup_id) wg: vec3<u32>,
|
|
662
|
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
|
663
|
+
) {
|
|
664
|
+
let grid_res: u32 = 16u;
|
|
665
|
+
if (wg.x >= grid_res || wg.y >= grid_res || wg.z >= grid_res) { return; }
|
|
666
|
+
if (lid.x >= HW_BAKE_OCT_PADDED || lid.y >= HW_BAKE_OCT_PADDED) { return; }
|
|
667
|
+
|
|
668
|
+
let extent = u.grid.w;
|
|
669
|
+
let cell = extent / f32(grid_res);
|
|
670
|
+
let probe_pos = u.grid.xyz
|
|
671
|
+
- vec3<f32>(extent * 0.5)
|
|
672
|
+
+ (vec3<f32>(f32(wg.x), f32(wg.y), f32(wg.z)) + vec3<f32>(0.5)) * cell;
|
|
673
|
+
|
|
674
|
+
// V11 octahedral wrap for the padded borders.
|
|
675
|
+
let px = i32(lid.x);
|
|
676
|
+
let py = i32(lid.y);
|
|
677
|
+
let is_edge_x = px == 0 || px == 9;
|
|
678
|
+
let is_edge_y = py == 0 || py == 9;
|
|
679
|
+
var real_ox: i32;
|
|
680
|
+
var real_oy: i32;
|
|
681
|
+
if (is_edge_x && is_edge_y) {
|
|
682
|
+
real_ox = clamp(px - 1, 0, 7);
|
|
683
|
+
real_oy = clamp(py - 1, 0, 7);
|
|
684
|
+
} else if (is_edge_y) {
|
|
685
|
+
real_ox = 8 - px;
|
|
686
|
+
real_oy = clamp(py - 1, 0, 7);
|
|
687
|
+
} else if (is_edge_x) {
|
|
688
|
+
real_ox = clamp(px - 1, 0, 7);
|
|
689
|
+
real_oy = 8 - py;
|
|
690
|
+
} else {
|
|
691
|
+
real_ox = px - 1;
|
|
692
|
+
real_oy = py - 1;
|
|
693
|
+
}
|
|
694
|
+
let dir = octel_direction(vec2<u32>(u32(real_ox), u32(real_oy)));
|
|
695
|
+
|
|
696
|
+
// V14 — fire a short ray from the probe centre. Ray length
|
|
697
|
+
// scales with the cascade extent so each cascade's rays stay
|
|
698
|
+
// in its resolution regime (near: ~15 m, mid: ~60 m, far:
|
|
699
|
+
// ~250 m).
|
|
700
|
+
let ray_length = extent * 0.5;
|
|
701
|
+
var rq: ray_query;
|
|
702
|
+
rayQueryInitialize(&rq, accel, RayDesc(
|
|
703
|
+
0u,
|
|
704
|
+
0xFFu,
|
|
705
|
+
0.01,
|
|
706
|
+
ray_length,
|
|
707
|
+
probe_pos,
|
|
708
|
+
dir,
|
|
709
|
+
));
|
|
710
|
+
loop {
|
|
711
|
+
if (!rayQueryProceed(&rq)) { break; }
|
|
712
|
+
}
|
|
713
|
+
let hit = rayQueryGetCommittedIntersection(&rq);
|
|
714
|
+
|
|
715
|
+
var radiance = vec3<f32>(0.0);
|
|
716
|
+
if (hit.kind != RAY_QUERY_INTERSECTION_NONE) {
|
|
717
|
+
let inst = instance_data[hit.instance_custom_data];
|
|
718
|
+
if (inst.card_slot.w > 0.5) {
|
|
719
|
+
// Sample Mesh Cards pre-lit radiance at hit. Same
|
|
720
|
+
// projection math as SSGI_PROBE_TRACE_HW_WGSL's hit
|
|
721
|
+
// branch.
|
|
722
|
+
let hit_world = probe_pos + dir * hit.t;
|
|
723
|
+
let hit_os = (hit.world_to_object * vec4<f32>(hit_world, 1.0)).xyz;
|
|
724
|
+
let abs_d = abs(dir);
|
|
725
|
+
var axis_idx: u32 = 0u;
|
|
726
|
+
if (abs_d.y >= abs_d.x && abs_d.y >= abs_d.z) {
|
|
727
|
+
axis_idx = 2u;
|
|
728
|
+
} else if (abs_d.z >= abs_d.x) {
|
|
729
|
+
axis_idx = 4u;
|
|
730
|
+
}
|
|
731
|
+
var signed_axis: u32 = axis_idx;
|
|
732
|
+
if (axis_idx == 0u && dir.x > 0.0) { signed_axis = 1u; }
|
|
733
|
+
else if (axis_idx == 2u && dir.y > 0.0) { signed_axis = 3u; }
|
|
734
|
+
else if (axis_idx == 4u && dir.z > 0.0) { signed_axis = 5u; }
|
|
735
|
+
|
|
736
|
+
let first_slot = u32(inst.card_slot.x);
|
|
737
|
+
let slot = first_slot + signed_axis;
|
|
738
|
+
let slot_x = slot % 64u;
|
|
739
|
+
let slot_y = slot / 64u;
|
|
740
|
+
|
|
741
|
+
let bmin = inst.card_aabb_min.xyz;
|
|
742
|
+
let bmax = inst.card_aabb_max.xyz;
|
|
743
|
+
var u_os: f32;
|
|
744
|
+
var v_os: f32;
|
|
745
|
+
var u_lo: f32; var u_hi: f32;
|
|
746
|
+
var v_lo: f32; var v_hi: f32;
|
|
747
|
+
var u_flip: f32 = 1.0;
|
|
748
|
+
if (signed_axis == 0u || signed_axis == 1u) {
|
|
749
|
+
u_os = hit_os.y; v_os = hit_os.z;
|
|
750
|
+
u_lo = bmin.y; u_hi = bmax.y; v_lo = bmin.z; v_hi = bmax.z;
|
|
751
|
+
if (signed_axis == 1u) { u_flip = -1.0; }
|
|
752
|
+
} else if (signed_axis == 2u || signed_axis == 3u) {
|
|
753
|
+
u_os = hit_os.x; v_os = hit_os.z;
|
|
754
|
+
u_lo = bmin.x; u_hi = bmax.x; v_lo = bmin.z; v_hi = bmax.z;
|
|
755
|
+
if (signed_axis == 3u) { u_flip = -1.0; }
|
|
756
|
+
} else {
|
|
757
|
+
u_os = hit_os.x; v_os = hit_os.y;
|
|
758
|
+
u_lo = bmin.x; u_hi = bmax.x; v_lo = bmin.y; v_hi = bmax.y;
|
|
759
|
+
if (signed_axis == 5u) { u_flip = -1.0; }
|
|
760
|
+
}
|
|
761
|
+
var u_norm = clamp((u_os - u_lo) / max(u_hi - u_lo, 1e-4), 0.0, 1.0);
|
|
762
|
+
let v_norm = clamp((v_os - v_lo) / max(v_hi - v_lo, 1e-4), 0.0, 1.0);
|
|
763
|
+
if (u_flip < 0.0) { u_norm = 1.0 - u_norm; }
|
|
764
|
+
|
|
765
|
+
let slot_size_uv = 1.0 / HW_BAKE_CARD_SLOTS_PER_ROW;
|
|
766
|
+
let texel_in_slot = slot_size_uv / f32(64);
|
|
767
|
+
let slot_u0 = f32(slot_x) * slot_size_uv + texel_in_slot;
|
|
768
|
+
let slot_v0 = f32(slot_y) * slot_size_uv + texel_in_slot;
|
|
769
|
+
let slot_span = slot_size_uv - 2.0 * texel_in_slot;
|
|
770
|
+
let atlas_uv = vec2<f32>(
|
|
771
|
+
slot_u0 + u_norm * slot_span,
|
|
772
|
+
slot_v0 + v_norm * slot_span,
|
|
773
|
+
);
|
|
774
|
+
radiance = textureSampleLevel(card_atlas, card_samp, atlas_uv, 0.0).rgb;
|
|
775
|
+
} else {
|
|
776
|
+
// Instance without a card — shade analytically using
|
|
777
|
+
// its flat normal + albedo.
|
|
778
|
+
let hit_n = inst.normal_ws;
|
|
779
|
+
let ndotl = max(dot(hit_n, u.sun_dir.xyz), 0.0);
|
|
780
|
+
let direct = u.sun_color.xyz * ndotl;
|
|
781
|
+
let ndotup = max(dot(hit_n, vec3<f32>(0.0, 1.0, 0.0)), 0.0);
|
|
782
|
+
let sky = u.sky_color.xyz * ndotup;
|
|
783
|
+
radiance = inst.albedo * (direct + sky)
|
|
784
|
+
+ inst.albedo * inst.emissive_luma;
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
// Miss — V13 analytic fallback (shadow-sampled sun +
|
|
788
|
+
// hemisphere sky). The ray went past `extent * 0.5` without
|
|
789
|
+
// hitting anything, so this is the open-sky direction at
|
|
790
|
+
// probe scale.
|
|
791
|
+
var shadow: f32 = 1.0;
|
|
792
|
+
if (u.flags.y > 0.5) {
|
|
793
|
+
shadow = hw_bake_sample_cascade(2, probe_pos, u.flags.x);
|
|
794
|
+
}
|
|
795
|
+
let ndotl = max(dot(dir, u.sun_dir.xyz), 0.0);
|
|
796
|
+
let sun = u.sun_color.xyz * ndotl * shadow;
|
|
797
|
+
let up = clamp(dir.y * 0.5 + 0.5, 0.0, 1.0);
|
|
798
|
+
let sky = u.sky_color.xyz * up * up;
|
|
799
|
+
radiance = sun + sky;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
let cascade_idx = u32(u.flags.z);
|
|
803
|
+
let tex_coord = vec3<i32>(
|
|
804
|
+
i32(wg.x * HW_BAKE_OCT_PADDED + lid.x),
|
|
805
|
+
i32(wg.y * HW_BAKE_OCT_PADDED + lid.y),
|
|
806
|
+
i32(cascade_idx * grid_res + wg.z),
|
|
807
|
+
);
|
|
808
|
+
textureStore(wsrc_out, tex_coord, vec4<f32>(radiance, 1.0));
|
|
809
|
+
}
|
|
810
|
+
";
|