Phase 2: shader decomposition + Rust-shared constants
shader.wgsl restructured:
- Section headers: Camera → Sky horizon → Atmosphere extras → Terrain
lighting decomposition → Pipelines.
- sky_dome() is now the single source of truth for the horizon→zenith
mix; sky_color() builds on it instead of duplicating the gradient.
- fs_main decomposed into named helpers:
ambient_term(n, sun, day) hemisphere ambient via sky_dome lookup
direct_sun_term(n, sun) Lambert × visibility ramp
leaf_jitter(world_pos) per-pixel canopy variation
fog_factor(dist) 0..1 fog ramp
apply_fog(lit, dist, view) defers sky_color call until needed
- Camera.misc renamed to Camera.frame with named accessors
scene_time() / eye_world() so callsites read intent.
- DAY_PERIOD / SUN_OFFSET REMOVED from the shader — injected at the
top by shader_source::wgsl_constants_header(). One source of truth
in sim::lighting now drives both Rust and WGSL; mob burn and
visible sun direction can never disagree.
New src/shader_source.rs:
- wgsl_constants_header() Rust → WGSL constants prelude
- terrain_shader_source() header + shader.wgsl, used at Renderer::new
- post_shader_source() pass-through
state.rs CameraUniform:
- misc → frame to match the renamed WGSL field
- Renderer uses the assembled shader source instead of include_str! directly
Tests: 53 passing (added 2 shader_source assembly tests). Native build,
wasm release build, server build all green.
This commit is contained in:
parent
989de4f43d
commit
accbf67bf2
4 changed files with 206 additions and 115 deletions
|
|
@ -2,6 +2,7 @@ pub mod camera;
|
|||
pub mod mesh;
|
||||
pub mod net;
|
||||
pub mod proto;
|
||||
pub mod shader_source;
|
||||
pub mod sim;
|
||||
pub mod state;
|
||||
pub mod world;
|
||||
|
|
|
|||
251
src/shader.wgsl
251
src/shader.wgsl
|
|
@ -1,34 +1,46 @@
|
|||
// Voxel-game world/sky/outline pipelines.
|
||||
//
|
||||
// The constants `DAY_PERIOD` and `SUN_OFFSET` are NOT defined here —
|
||||
// they are injected at the top of this file at module-load time by
|
||||
// `crate::shader_source::wgsl_constants_header()`. The same values
|
||||
// drive `crate::sim::lighting`, so the visual sun direction can never
|
||||
// disagree with the mechanical one consumed by mob burn / plant growth /
|
||||
// shade pathfinding.
|
||||
//
|
||||
// Layout (top to bottom):
|
||||
// 1. Camera uniform + accessors
|
||||
// 2. Sky horizon math (one source of truth: sky_dome)
|
||||
// 3. Atmosphere extras (clouds, sun/moon discs, stars) → sky_color
|
||||
// 4. Terrain lighting decomposition (ambient, sun, fog, leaf jitter)
|
||||
// 5. Pipelines: terrain (vs_main/fs_main), sky background, outline.
|
||||
|
||||
// ---------------- 1. Camera ----------------
|
||||
|
||||
struct Camera {
|
||||
view_proj: mat4x4<f32>,
|
||||
inv_view_proj: mat4x4<f32>,
|
||||
eye: vec4<f32>,
|
||||
/// .x = scene time in seconds (drives day/night cycle + leaf sway)
|
||||
misc: vec4<f32>,
|
||||
/// .y/.z/.w reserved
|
||||
frame: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> camera: Camera;
|
||||
|
||||
// ---------------- Time-of-day primitives ----------------
|
||||
//
|
||||
// One in-game day takes DAY_PERIOD seconds. The sun sweeps an east-to-west
|
||||
// arc (cos/sin on the same plane) with a small constant tilt on Z so it
|
||||
// isn't dead-flat. Game starts at noon (offset = 0.25 cycles).
|
||||
fn scene_time() -> f32 { return camera.frame.x; }
|
||||
fn eye_world() -> vec3<f32> { return camera.eye.xyz; }
|
||||
|
||||
const DAY_PERIOD: f32 = 300.0;
|
||||
const SUN_OFFSET: f32 = 0.25;
|
||||
// ---------------- 2. Sky horizon math (shared with ambient) ----------------
|
||||
|
||||
fn sun_direction(t: f32) -> vec3<f32> {
|
||||
let a = (t / DAY_PERIOD + SUN_OFFSET) * 6.28318530718;
|
||||
return normalize(vec3<f32>(cos(a), sin(a), 0.25));
|
||||
}
|
||||
|
||||
// Smooth 0..1 going from -0.05 (sun barely under horizon, blue hour) up
|
||||
// to 0.20 (clearly above the horizon, full daylight).
|
||||
fn day_strength(sun: vec3<f32>) -> f32 {
|
||||
return smoothstep(-0.05, 0.20, sun.y);
|
||||
}
|
||||
|
||||
// Twilight peaks while the sun is near the horizon — sunrise + sunset.
|
||||
fn twilight_amount(sun: vec3<f32>) -> f32 {
|
||||
let above = smoothstep(-0.10, 0.05, sun.y);
|
||||
let high = smoothstep(0.05, 0.30, sun.y);
|
||||
|
|
@ -40,7 +52,29 @@ fn sun_tint(sun: vec3<f32>) -> vec3<f32> {
|
|||
return mix(vec3<f32>(1.00, 0.95, 0.85), vec3<f32>(1.00, 0.55, 0.30), twi);
|
||||
}
|
||||
|
||||
// ---------------- Cheap 2D fbm for clouds ----------------
|
||||
// Horizon → zenith gradient. Used by both:
|
||||
// - the sky background fragment shader (with clouds/sun layered on)
|
||||
// - terrain hemisphere ambient (sample the dome in surface-normal dir
|
||||
// so vertical faces inherit the bright daytime horizon).
|
||||
//
|
||||
// One function = one source of truth for "what color is the sky at this
|
||||
// angle?" — when the palette is tuned, both consumers update together.
|
||||
fn sky_dome(dir: vec3<f32>, sun: vec3<f32>) -> vec3<f32> {
|
||||
let day = day_strength(sun);
|
||||
let twi = twilight_amount(sun);
|
||||
let zenith_day = vec3<f32>(0.30, 0.55, 0.88);
|
||||
let zenith_night = vec3<f32>(0.02, 0.03, 0.10);
|
||||
let horizon_day = vec3<f32>(0.82, 0.92, 0.99);
|
||||
let horizon_twi = vec3<f32>(1.00, 0.55, 0.28);
|
||||
let horizon_night = vec3<f32>(0.03, 0.04, 0.10);
|
||||
let zenith = mix(zenith_night, zenith_day, day);
|
||||
let horizon = mix(mix(horizon_night, horizon_day, day), horizon_twi, twi);
|
||||
let up = clamp(dir.y, -1.0, 1.0);
|
||||
let gradient_t = pow(max(up, 0.0), 0.55);
|
||||
return mix(horizon, zenith, gradient_t);
|
||||
}
|
||||
|
||||
// ---------------- 3. Atmosphere extras ----------------
|
||||
|
||||
fn hash21(p: vec2<f32>) -> f32 {
|
||||
return fract(sin(dot(p, vec2<f32>(127.1, 311.7))) * 43758.5453);
|
||||
|
|
@ -69,27 +103,7 @@ fn fbm2(p_in: vec2<f32>) -> f32 {
|
|||
return v;
|
||||
}
|
||||
|
||||
// Just the horizon→zenith gradient — no clouds, no sun, no stars. Used by
|
||||
// the terrain shader to compute hemisphere ambient: each fragment samples
|
||||
// the dome in its surface-normal direction so vertical faces inherit the
|
||||
// bright daytime horizon instead of a flat dim ambient.
|
||||
fn sky_dome(dir: vec3<f32>, sun: vec3<f32>) -> vec3<f32> {
|
||||
let day = day_strength(sun);
|
||||
let twi = twilight_amount(sun);
|
||||
let zenith_day = vec3<f32>(0.30, 0.55, 0.88);
|
||||
let zenith_night = vec3<f32>(0.02, 0.03, 0.10);
|
||||
let horizon_day = vec3<f32>(0.82, 0.92, 0.99);
|
||||
let horizon_twi = vec3<f32>(1.00, 0.55, 0.28);
|
||||
let horizon_night = vec3<f32>(0.03, 0.04, 0.10);
|
||||
let zenith = mix(zenith_night, zenith_day, day);
|
||||
let horizon = mix(mix(horizon_night, horizon_day, day), horizon_twi, twi);
|
||||
let up = clamp(dir.y, -1.0, 1.0);
|
||||
let gradient_t = pow(max(up, 0.0), 0.55);
|
||||
return mix(horizon, zenith, gradient_t);
|
||||
}
|
||||
|
||||
// Cheap "stars" — high-frequency hash on view direction, threshold to
|
||||
// keep only ~0.2% of cells lit.
|
||||
// High-frequency hash on view direction, threshold to keep ~0.2% lit.
|
||||
fn star_field(dir: vec3<f32>) -> f32 {
|
||||
if (dir.y <= 0.0) { return 0.0; }
|
||||
let cell = floor(dir * 220.0);
|
||||
|
|
@ -97,45 +111,30 @@ fn star_field(dir: vec3<f32>) -> f32 {
|
|||
return step(0.997, h);
|
||||
}
|
||||
|
||||
// ---------------- Sky ----------------
|
||||
//
|
||||
// `dir` is the *view* direction from camera into the scene (unit vector).
|
||||
// Composes a horizon→zenith gradient that re-tones with sun height,
|
||||
// twinklers + cloud streaks + sun + moon discs.
|
||||
|
||||
// Composite sky: dome + below-horizon dim + stars + clouds + sun + moon.
|
||||
// `dir` is the view direction from camera into the scene (unit vector).
|
||||
fn sky_color(dir: vec3<f32>) -> vec3<f32> {
|
||||
let t = camera.misc.x;
|
||||
let t = scene_time();
|
||||
let sun = sun_direction(t);
|
||||
let day = day_strength(sun);
|
||||
let twi = twilight_amount(sun);
|
||||
let night = clamp(1.0 - day, 0.0, 1.0);
|
||||
|
||||
let zenith_day = vec3<f32>(0.30, 0.55, 0.88);
|
||||
let zenith_night = vec3<f32>(0.02, 0.03, 0.10);
|
||||
let horizon_day = vec3<f32>(0.78, 0.88, 0.96);
|
||||
let horizon_twi = vec3<f32>(1.00, 0.55, 0.28);
|
||||
let horizon_night = vec3<f32>(0.03, 0.04, 0.10);
|
||||
|
||||
let zenith = mix(zenith_night, zenith_day, day);
|
||||
let horizon = mix(mix(horizon_night, horizon_day, day), horizon_twi, twi);
|
||||
|
||||
let up = clamp(dir.y, -1.0, 1.0);
|
||||
let gradient_t = pow(max(up, 0.0), 0.55);
|
||||
var sky = mix(horizon, zenith, gradient_t);
|
||||
var sky = sky_dome(dir, sun);
|
||||
|
||||
// Below-horizon slight darken so the world below the player still feels grounded.
|
||||
let up = clamp(dir.y, -1.0, 1.0);
|
||||
let below = step(up, 0.0) * 0.2;
|
||||
sky = sky * (1.0 - below);
|
||||
|
||||
// Stars: fade in as day strength drops. Slight twinkle via time-based jitter.
|
||||
let night_amt = clamp(1.0 - day, 0.0, 1.0);
|
||||
if (night_amt > 0.05) {
|
||||
// Stars: fade in at night with slight twinkle.
|
||||
if (night > 0.05) {
|
||||
let st = star_field(dir);
|
||||
let twinkle = 0.7 + 0.3 * sin(t * 6.0 + dir.x * 100.0 + dir.z * 130.0);
|
||||
sky = sky + vec3<f32>(st * night_amt * twinkle);
|
||||
sky = sky + vec3<f32>(st * night * twinkle);
|
||||
}
|
||||
|
||||
// Cloud layer — fbm scrolled across an imaginary plane high above. Only
|
||||
// visible looking upward (dir.y > 0). Cheap: 4 octaves of value noise.
|
||||
// Cloud layer — fbm scrolled across an imaginary plane high above.
|
||||
if (dir.y > 0.05) {
|
||||
let proj = dir.xz / dir.y;
|
||||
let scroll = vec2<f32>(t * 0.004, t * 0.0015);
|
||||
|
|
@ -147,24 +146,87 @@ fn sky_color(dir: vec3<f32>) -> vec3<f32> {
|
|||
sky = mix(sky, cloud_col, mask * (0.55 + 0.25 * day));
|
||||
}
|
||||
|
||||
// Sun disc + halo. Disc only visible in daytime (no sun glow underground).
|
||||
// Sun disc + halo.
|
||||
let sun_col = sun_tint(sun);
|
||||
let cos_s = max(dot(dir, sun), 0.0);
|
||||
let disc = pow(cos_s, 800.0) * 1.5 * smoothstep(-0.05, 0.05, sun.y);
|
||||
let halo = pow(cos_s, 5.0) * 0.20 * day;
|
||||
sky = sky + sun_col * (disc + halo);
|
||||
|
||||
// Moon disc — opposite the sun, faint white. Only at night.
|
||||
// Moon disc — opposite the sun, faint white, night only.
|
||||
let moon = -sun;
|
||||
let cos_m = max(dot(dir, moon), 0.0);
|
||||
let moon_disc = pow(cos_m, 700.0) * 0.9;
|
||||
let moon_halo = pow(cos_m, 24.0) * 0.06;
|
||||
sky = sky + vec3<f32>(0.86, 0.89, 0.96) * (moon_disc + moon_halo) * night_amt;
|
||||
sky = sky + vec3<f32>(0.86, 0.89, 0.96) * (moon_disc + moon_halo) * night;
|
||||
|
||||
return sky;
|
||||
}
|
||||
|
||||
// ---------------- Terrain ----------------
|
||||
// ---------------- 4. Terrain lighting decomposition ----------------
|
||||
//
|
||||
// Each piece is a named function so `fs_main` reads as a pipeline:
|
||||
//
|
||||
// lit = base_color *
|
||||
// ( ambient_term × ambient_strength + direct_sun_term × sun_color )
|
||||
// × ao × leaf_jitter
|
||||
// final = apply_fog( lit, dist, view_dir )
|
||||
|
||||
/// Hemisphere ambient — samples the sky dome in the surface-normal
|
||||
/// direction so vertical faces inherit the bright horizon during day.
|
||||
/// Bottom faces fade toward an earth-bounce color so they're not dead
|
||||
/// black.
|
||||
fn ambient_term(normal: vec3<f32>, sun: vec3<f32>, day: f32) -> vec3<f32> {
|
||||
let sky_in_normal = sky_dome(normal, sun);
|
||||
let earth_day = vec3<f32>(0.20, 0.18, 0.14);
|
||||
let earth_night = vec3<f32>(0.03, 0.03, 0.04);
|
||||
let earth = mix(earth_night, earth_day, day);
|
||||
let face_up = clamp(normal.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
return mix(earth, sky_in_normal, face_up);
|
||||
}
|
||||
|
||||
/// Direct-sun Lambert term, gated by sun visibility above the horizon.
|
||||
/// Returns a scalar 0..1 — caller multiplies by `sun_tint(sun)` for
|
||||
/// color.
|
||||
///
|
||||
/// NOTE: This is an *unoccluded* Lambert. The mechanical sunlight
|
||||
/// predicate (`crate::sim::lighting::is_in_direct_sun`) is more
|
||||
/// accurate — it ray-traces the voxel grid. When real shadows land,
|
||||
/// this function will multiply by an occlusion factor that approximates
|
||||
/// that ray test (shadow map / voxel raymarch / etc).
|
||||
fn direct_sun_term(normal: vec3<f32>, sun: vec3<f32>) -> f32 {
|
||||
let ndl = max(dot(normal, sun), 0.0);
|
||||
let sun_visible = smoothstep(-0.05, 0.10, sun.y);
|
||||
return ndl * sun_visible;
|
||||
}
|
||||
|
||||
/// Per-pixel value-noise jitter on leaf surfaces so the canopy doesn't
|
||||
/// read as a flat green. 0.88 .. 1.06 range.
|
||||
fn leaf_jitter(world_pos: vec3<f32>) -> f32 {
|
||||
let n = fract(sin(dot(floor(world_pos * 1.3), vec3<f32>(12.9898, 78.233, 37.719))) * 43758.5453);
|
||||
return 0.88 + n * 0.18;
|
||||
}
|
||||
|
||||
/// Distance fog. Returns 0 (no fog) → 1 (fully obscured).
|
||||
fn fog_factor(dist: f32) -> f32 {
|
||||
let fog_start = 90.0;
|
||||
let fog_end = 320.0;
|
||||
return clamp((dist - fog_start) / (fog_end - fog_start), 0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Blend `lit` toward sky color along the view ray when the fragment
|
||||
/// is far enough to be fogged. Defers the (expensive) full `sky_color`
|
||||
/// call until the factor is actually nonzero.
|
||||
fn apply_fog(lit: vec3<f32>, dist: f32, view_dir: vec3<f32>) -> vec3<f32> {
|
||||
let t = fog_factor(dist);
|
||||
if (t <= 0.001) {
|
||||
return lit;
|
||||
}
|
||||
let sky = sky_color(-view_dir);
|
||||
return mix(lit, sky, t);
|
||||
}
|
||||
|
||||
// ---------------- 5a. Terrain pipeline ----------------
|
||||
|
||||
struct VsIn {
|
||||
@location(0) pos: vec3<f32>,
|
||||
|
|
@ -187,9 +249,9 @@ struct VsOut {
|
|||
fn vs_main(in: VsIn) -> VsOut {
|
||||
var pos = in.pos;
|
||||
if (in.leaf > 0.5) {
|
||||
let t = camera.misc.x;
|
||||
let t = scene_time();
|
||||
let phase = pos.x * 0.35 + pos.z * 0.27 + pos.y * 0.11;
|
||||
let sway = sin(t * 1.6 + phase) * 0.045;
|
||||
let sway = sin(t * 1.6 + phase) * 0.045;
|
||||
let sway2 = cos(t * 1.1 + phase * 1.3) * 0.035;
|
||||
pos.x = pos.x + sway;
|
||||
pos.z = pos.z + sway2;
|
||||
|
|
@ -207,65 +269,35 @@ fn vs_main(in: VsIn) -> VsOut {
|
|||
|
||||
@fragment
|
||||
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
|
||||
let t = camera.misc.x;
|
||||
let t = scene_time();
|
||||
let sun = sun_direction(t);
|
||||
let day = day_strength(sun);
|
||||
let sun_col = sun_tint(sun);
|
||||
|
||||
let n = normalize(in.normal);
|
||||
let ndl = max(dot(n, sun), 0.0);
|
||||
let sun_visible = smoothstep(-0.05, 0.10, sun.y);
|
||||
let sun_term = ndl * sun_visible;
|
||||
|
||||
// Hemisphere ambient — *sample* the sky dome in the normal direction
|
||||
// instead of lerping two constants. A vertical face (n.y ≈ 0) picks up
|
||||
// the bright horizon, a top face (n.y ≈ 1) the (darker) zenith, a
|
||||
// bottom face the earth-bounce. This is the cheap analogue of an
|
||||
// integrated environment light and is what makes daytime sides not
|
||||
// look like night.
|
||||
let sky_in_normal = sky_dome(n, sun);
|
||||
let earth_down_day = vec3<f32>(0.20, 0.18, 0.14);
|
||||
let earth_down_night = vec3<f32>(0.03, 0.03, 0.04);
|
||||
let earth_down = mix(earth_down_night, earth_down_day, day);
|
||||
let face_up = clamp(n.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
let ambient_col = mix(earth_down, sky_in_normal, face_up);
|
||||
// Higher strength than before — outdoor diffuse skylight is roughly
|
||||
// 10–20% of direct sun in reality. The old 0.45 cap was making sides
|
||||
// read as if it were dusk during the day.
|
||||
// Lighting pipeline:
|
||||
// ambient + direct_sun → modulated by base color → AO → leaf jitter → fog
|
||||
let ambient = ambient_term(n, sun, day);
|
||||
let ambient_strength = mix(0.25, 0.85, day);
|
||||
let sun_term = direct_sun_term(n, sun);
|
||||
let sun_col = sun_tint(sun);
|
||||
|
||||
let lighting = ambient_col * ambient_strength + sun_col * sun_term;
|
||||
var lit = in.color * lighting;
|
||||
let lighting = ambient * ambient_strength + sun_col * sun_term;
|
||||
var lit = in.color * lighting * in.ao;
|
||||
|
||||
// Bake-time per-vertex ambient occlusion.
|
||||
lit = lit * in.ao;
|
||||
|
||||
// Per-pixel value noise on leaves so the canopy doesn't look uniform.
|
||||
if (in.leaf > 0.5) {
|
||||
let n2 = fract(sin(dot(floor(in.world_pos * 1.3), vec3<f32>(12.9898, 78.233, 37.719))) * 43758.5453);
|
||||
lit = lit * (0.88 + n2 * 0.18);
|
||||
lit = lit * leaf_jitter(in.world_pos);
|
||||
}
|
||||
|
||||
let to_eye = camera.eye.xyz - in.world_pos;
|
||||
let dist = length(to_eye);
|
||||
let to_eye = eye_world() - in.world_pos;
|
||||
let dist = length(to_eye);
|
||||
let view_dir = -to_eye / max(dist, 0.0001);
|
||||
|
||||
let fog_start = 90.0;
|
||||
let fog_end = 320.0;
|
||||
let fog_t = clamp((dist - fog_start) / (fog_end - fog_start), 0.0, 1.0);
|
||||
var color = lit;
|
||||
if (fog_t > 0.001) {
|
||||
// Only pay for the full sky lookup if the fragment is actually
|
||||
// fogged enough to read it. Saves the cloud/fbm cost on near
|
||||
// geometry.
|
||||
let sky = sky_color(-view_dir);
|
||||
color = mix(lit, sky, fog_t);
|
||||
}
|
||||
|
||||
let color = apply_fog(lit, dist, view_dir);
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
|
||||
// ---- Sky background (full-screen triangle) ----
|
||||
// ---------------- 5b. Sky background (full-screen triangle) ----------------
|
||||
|
||||
struct SkyOut {
|
||||
@builtin(position) clip: vec4<f32>,
|
||||
|
|
@ -290,11 +322,11 @@ fn vs_sky(@builtin(vertex_index) idx: u32) -> SkyOut {
|
|||
fn fs_sky(in: SkyOut) -> @location(0) vec4<f32> {
|
||||
let far_h = camera.inv_view_proj * vec4<f32>(in.ndc.x, in.ndc.y, 1.0, 1.0);
|
||||
let world_pos = far_h.xyz / far_h.w;
|
||||
let dir = normalize(world_pos - camera.eye.xyz);
|
||||
let dir = normalize(world_pos - eye_world());
|
||||
return vec4<f32>(sky_color(dir), 1.0);
|
||||
}
|
||||
|
||||
// ---- Outline ----
|
||||
// ---------------- 5c. Outline ----------------
|
||||
|
||||
@vertex
|
||||
fn vs_outline(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> {
|
||||
|
|
@ -305,4 +337,3 @@ fn vs_outline(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> {
|
|||
fn fs_outline() -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(0.05, 0.05, 0.07, 1.0);
|
||||
}
|
||||
|
||||
|
|
|
|||
54
src/shader_source.rs
Normal file
54
src/shader_source.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//! Assembles WGSL shader source by prepending a constants header
|
||||
//! generated from Rust. Single source of truth for `DAY_PERIOD` and
|
||||
//! `SUN_OFFSET`: change them in `sim::lighting` and both Rust *and* the
|
||||
//! shader update together.
|
||||
//!
|
||||
//! No build script needed — the header is built at runtime when the
|
||||
//! shader module is created (once per Renderer init, negligible cost).
|
||||
use crate::sim::lighting::{DAY_PERIOD, SUN_OFFSET};
|
||||
|
||||
/// WGSL header injected at the top of `shader.wgsl`. Mirrors the Rust
|
||||
/// constants in `sim::lighting`.
|
||||
pub fn wgsl_constants_header() -> String {
|
||||
format!(
|
||||
"// ---- AUTO-INJECTED FROM sim::lighting (see shader_source.rs) ----\n\
|
||||
const DAY_PERIOD: f32 = {day:.6};\n\
|
||||
const SUN_OFFSET: f32 = {off:.6};\n\
|
||||
// ---- end injected constants ----\n\n",
|
||||
day = DAY_PERIOD,
|
||||
off = SUN_OFFSET,
|
||||
)
|
||||
}
|
||||
|
||||
/// Full WGSL source for the world/sky/outline pipelines: injected
|
||||
/// constants header + the static `shader.wgsl` body.
|
||||
pub fn terrain_shader_source() -> String {
|
||||
format!("{}{}", wgsl_constants_header(), include_str!("shader.wgsl"))
|
||||
}
|
||||
|
||||
/// Post-process pipeline source. Doesn't depend on the shared
|
||||
/// constants; passed through for symmetry with `terrain_shader_source`.
|
||||
pub fn post_shader_source() -> &'static str {
|
||||
include_str!("post.wgsl")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn header_contains_the_constants() {
|
||||
let h = wgsl_constants_header();
|
||||
assert!(h.contains("DAY_PERIOD"));
|
||||
assert!(h.contains("SUN_OFFSET"));
|
||||
assert!(h.contains(&format!("{:.6}", DAY_PERIOD)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terrain_shader_source_contains_header_and_body() {
|
||||
let src = terrain_shader_source();
|
||||
assert!(src.contains("DAY_PERIOD"));
|
||||
assert!(src.contains("fn sun_direction"));
|
||||
assert!(src.contains("fn fs_main"));
|
||||
}
|
||||
}
|
||||
15
src/state.rs
15
src/state.rs
|
|
@ -40,7 +40,10 @@ struct CameraUniform {
|
|||
view_proj: [[f32; 4]; 4],
|
||||
inv_view_proj: [[f32; 4]; 4],
|
||||
eye: [f32; 4],
|
||||
misc: [f32; 4],
|
||||
/// `.x` = scene time in seconds (drives day/night cycle + leaf sway).
|
||||
/// `.y/.z/.w` reserved. Layout mirrored in `shader.wgsl` as
|
||||
/// `camera.frame` with a `scene_time()` accessor.
|
||||
frame: [f32; 4],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -414,7 +417,9 @@ impl Renderer {
|
|||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
crate::shader_source::terrain_shader_source().into(),
|
||||
),
|
||||
});
|
||||
|
||||
let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
|
|
@ -423,7 +428,7 @@ impl Renderer {
|
|||
view_proj: Mat4::IDENTITY.to_cols_array_2d(),
|
||||
inv_view_proj: Mat4::IDENTITY.to_cols_array_2d(),
|
||||
eye: [0.0; 4],
|
||||
misc: [0.0; 4],
|
||||
frame: [0.0; 4],
|
||||
}),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
|
@ -644,7 +649,7 @@ impl Renderer {
|
|||
|
||||
let post_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("post shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("post.wgsl").into()),
|
||||
source: wgpu::ShaderSource::Wgsl(crate::shader_source::post_shader_source().into()),
|
||||
});
|
||||
|
||||
let post_pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
|
|
@ -839,7 +844,7 @@ impl Renderer {
|
|||
view_proj: vp.to_cols_array_2d(),
|
||||
inv_view_proj: inv.to_cols_array_2d(),
|
||||
eye: [camera.position.x, camera.position.y, camera.position.z, 1.0],
|
||||
misc: [time, 0.0, 0.0, 0.0],
|
||||
frame: [time, 0.0, 0.0, 0.0],
|
||||
};
|
||||
self.queue
|
||||
.write_buffer(&self.camera_buffer, 0, bytemuck::bytes_of(&uni));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue