rustial-renderer-wgpu 0.0.1

Pure WGPU renderer for the rustial 2.5D map engine
Documentation
struct Uniforms {
    view_proj:           mat4x4<f32>,
    fog_color:           vec4<f32>,
    eye_pos:             vec4<f32>,
    fog_params:          vec4<f32>,   // (start, end, density, 0)
    hillshade_highlight: vec4<f32>,
    hillshade_shadow:    vec4<f32>,
    hillshade_accent:    vec4<f32>,
    hillshade_light:     vec4<f32>,
    ambient_color:       vec4<f32>,   // (r, g, b, lighting_enabled)
    directional_dir:     vec4<f32>,   // (x, y, z, 0) — toward light
    directional_color:   vec4<f32>,   // (r, g, b, 0)
};

struct ShadowParams {
    light_matrix_0: mat4x4<f32>,
    light_matrix_1: mat4x4<f32>,
    shadow_config:  vec4<f32>,   // [intensity, texel_size, normal_offset, cascade_split]
    shadow_dir:     vec4<f32>,   // [dir.x, dir.y, dir.z, shadow_enabled]
};

struct ModelVertexInput {
    @location(0) position: vec3<f32>,
    @location(1) normal: vec3<f32>,
    @location(2) uv: vec2<f32>,
};

struct ModelVertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) world_normal: vec3<f32>,
    @location(1) tex_coord: vec2<f32>,
    @location(2) world_pos: vec3<f32>,
};

@group(0) @binding(0)
var<uniform> u: Uniforms;

@group(1) @binding(0)
var<uniform> model_transform: mat4x4<f32>;

@group(2) @binding(0)
var<uniform> shadow_params: ShadowParams;

@group(2) @binding(1)
var shadow_map_0: texture_depth_2d;

@group(2) @binding(2)
var shadow_map_1: texture_depth_2d;

@group(2) @binding(3)
var shadow_sampler: sampler_comparison;

// ---------------------------------------------------------------------------
// Shadow sampling
// ---------------------------------------------------------------------------

fn shadow_coords(light_matrix: mat4x4<f32>, world_pos: vec3<f32>) -> vec3<f32> {
    let light_clip = light_matrix * vec4<f32>(world_pos, 1.0);
    let ndc = light_clip.xyz / light_clip.w;
    return vec3<f32>(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5, ndc.z);
}

fn compute_shadow(world_pos: vec3<f32>, normal: vec3<f32>) -> f32 {
    let shadow_enabled = shadow_params.shadow_dir.w;
    if shadow_enabled < 0.5 {
        return 0.0;
    }

    let intensity = shadow_params.shadow_config.x;
    if intensity < 0.001 {
        return 0.0;
    }

    let normal_offset_scale = shadow_params.shadow_config.z;
    let sun_dir = normalize(shadow_params.shadow_dir.xyz);
    let ndotl = max(dot(normal, sun_dir), 0.0);
    let offset_scale = normal_offset_scale * (1.0 - ndotl);
    let offset_pos = world_pos + normal * offset_scale;

    let coords0 = shadow_coords(shadow_params.light_matrix_0, offset_pos);
    if coords0.x >= 0.0 && coords0.x <= 1.0 && coords0.y >= 0.0 && coords0.y <= 1.0
        && coords0.z >= 0.0 && coords0.z <= 1.0 {
        let shadow = textureSampleCompare(shadow_map_0, shadow_sampler, coords0.xy, coords0.z);
        return (1.0 - shadow) * intensity;
    }

    let coords1 = shadow_coords(shadow_params.light_matrix_1, offset_pos);
    if coords1.x >= 0.0 && coords1.x <= 1.0 && coords1.y >= 0.0 && coords1.y <= 1.0
        && coords1.z >= 0.0 && coords1.z <= 1.0 {
        let edge = max(
            max(abs(coords1.x * 2.0 - 1.0), abs(coords1.y * 2.0 - 1.0)),
            0.0
        );
        let fade = smoothstep(0.75, 1.0, edge);
        let shadow = textureSampleCompare(shadow_map_1, shadow_sampler, coords1.xy, coords1.z);
        return (1.0 - shadow) * intensity * (1.0 - fade);
    }

    return 0.0;
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

@vertex
fn vs_main(in: ModelVertexInput) -> ModelVertexOutput {
    var out: ModelVertexOutput;
    let world_pos = model_transform * vec4<f32>(in.position, 1.0);
    out.clip_position = u.view_proj * world_pos;
    out.world_normal = (model_transform * vec4<f32>(in.normal, 0.0)).xyz;
    out.tex_coord = in.uv;
    out.world_pos = world_pos.xyz;
    return out;
}

@fragment
fn fs_main(in: ModelVertexOutput) -> @location(0) vec4<f32> {
    let n = normalize(in.world_normal);
    let lighting_on = u.ambient_color.w;

    // Configurable directional light.
    let light_dir = normalize(u.directional_dir.xyz);
    let ndotl = max(dot(n, light_dir), 0.0);

    // Flat gray base color (texturing deferred to full PBR).
    let base_color = vec3<f32>(0.7, 0.7, 0.7);

    // Ambient + directional.
    let ambient = u.ambient_color.rgb;
    let diffuse = u.directional_color.rgb * ndotl;

    // Shadow attenuation.
    let shadow = compute_shadow(in.world_pos, n);
    let shadow_factor = 1.0 - shadow;

    let lit = clamp(base_color * (ambient + diffuse * shadow_factor), vec3<f32>(0.0), vec3<f32>(1.0));

    // Flat mode: use base colour without shading.
    let final_rgb = mix(base_color, lit, lighting_on);

    // Horizon fade: ground-plane distance only.
    let dx = in.world_pos.x - u.eye_pos.x;
    let dy = in.world_pos.y - u.eye_pos.y;
    let ground_dist = sqrt(dx * dx + dy * dy);
    let fog_start = u.fog_params.x;
    let fog_end   = u.fog_params.y;
    let density   = u.fog_params.z;
    let fog_t = clamp((ground_dist - fog_start) / max(fog_end - fog_start, 0.001), 0.0, 1.0) * density;

    let blended_rgb = mix(final_rgb, u.fog_color.rgb, fog_t);
    let blended_a   = 1.0 - fog_t;
    return vec4<f32>(blended_rgb, blended_a);
}