rein 0.1.1

rein 3D Rendering Library
Documentation
// Common shader definitions for rein
// This file provides shared structures and functions used across multiple shaders.

// =============================================================================
// Constants
// =============================================================================

const PI: f32 = 3.14159265359;
const TWO_PI: f32 = 6.28318530718;
const HALF_PI: f32 = 1.57079632679;
const INV_PI: f32 = 0.31830988618;

// Maximum number of lights per type
const MAX_DIRECTIONAL_LIGHTS: u32 = 4u;
const MAX_POINT_LIGHTS: u32 = 8u;
const MAX_SPOT_LIGHTS: u32 = 8u;

// =============================================================================
// Light Structures
// =============================================================================

struct AmbientLight {
    color: vec3<f32>,
    intensity: f32,
}

struct DirectionalLight {
    direction: vec3<f32>,
    _padding0: f32,
    color: vec3<f32>,
    intensity: f32,
}

struct PointLight {
    position: vec3<f32>,
    _padding0: f32,
    color: vec3<f32>,
    intensity: f32,
    attenuation: vec3<f32>,  // constant, linear, quadratic
    _padding1: f32,
}

struct SpotLight {
    position: vec3<f32>,
    _padding0: f32,
    direction: vec3<f32>,
    _padding1: f32,
    color: vec3<f32>,
    intensity: f32,
    inner_angle: f32,
    outer_angle: f32,
    attenuation: vec2<f32>,  // linear, quadratic (constant = 1.0)
}

struct LightUniform {
    ambient: AmbientLight,
    directional_count: u32,
    point_count: u32,
    spot_count: u32,
    _padding: u32,
    // Note: Arrays would be defined in the actual uniform, not here
}

// =============================================================================
// PBR Helper Functions
// =============================================================================

// Normal Distribution Function (GGX/Trowbridge-Reitz)
fn distribution_ggx(n_dot_h: f32, roughness: f32) -> f32 {
    let a = roughness * roughness;
    let a2 = a * a;
    let denom = n_dot_h * n_dot_h * (a2 - 1.0) + 1.0;
    return a2 / (PI * denom * denom);
}

// Geometry function (Schlick-GGX)
fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 {
    let r = roughness + 1.0;
    let k = (r * r) / 8.0;
    return n_dot_v / (n_dot_v * (1.0 - k) + k);
}

// Smith's method for geometry term
fn geometry_smith(n_dot_v: f32, n_dot_l: f32, roughness: f32) -> f32 {
    let ggx1 = geometry_schlick_ggx(n_dot_v, roughness);
    let ggx2 = geometry_schlick_ggx(n_dot_l, roughness);
    return ggx1 * ggx2;
}

// Fresnel-Schlick approximation
fn fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
    return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0);
}

// Fresnel-Schlick with roughness for IBL
fn fresnel_schlick_roughness(cos_theta: f32, f0: vec3<f32>, roughness: f32) -> vec3<f32> {
    return f0 + (max(vec3<f32>(1.0 - roughness), f0) - f0) * pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0);
}

// Calculate F0 (reflectance at normal incidence) for metals
fn calculate_f0(base_color: vec3<f32>, metallic: f32) -> vec3<f32> {
    return mix(vec3<f32>(0.04), base_color, metallic);
}

// =============================================================================
// Lighting Calculations
// =============================================================================

// Point light attenuation
fn point_light_attenuation(distance: f32, attenuation: vec3<f32>) -> f32 {
    return 1.0 / (attenuation.x + attenuation.y * distance + attenuation.z * distance * distance);
}

// Spotlight intensity falloff
fn spotlight_intensity(light_dir: vec3<f32>, spot_dir: vec3<f32>, inner_angle: f32, outer_angle: f32) -> f32 {
    let cos_theta = dot(light_dir, spot_dir);
    let cos_inner = cos(inner_angle);
    let cos_outer = cos(outer_angle);
    return clamp((cos_theta - cos_outer) / (cos_inner - cos_outer), 0.0, 1.0);
}

// =============================================================================
// Shadow Mapping Functions
// =============================================================================

// Basic shadow calculation
fn calculate_shadow(
    shadow_pos: vec4<f32>,
    shadow_map: texture_depth_2d,
    shadow_sampler: sampler_comparison,
) -> f32 {
    // Perspective divide
    let proj_coords = shadow_pos.xyz / shadow_pos.w;

    // Transform to [0, 1] range for texture lookup
    let shadow_uv = vec2<f32>(
        proj_coords.x * 0.5 + 0.5,
        proj_coords.y * -0.5 + 0.5  // Flip Y for texture coordinates
    );

    // Check if outside shadow map
    if shadow_uv.x < 0.0 || shadow_uv.x > 1.0 || shadow_uv.y < 0.0 || shadow_uv.y > 1.0 {
        return 1.0;
    }

    // Current depth
    let current_depth = proj_coords.z;

    // Sample shadow map with comparison
    return textureSampleCompare(shadow_map, shadow_sampler, shadow_uv, current_depth);
}

// PCF shadow (Percentage Closer Filtering)
fn calculate_shadow_pcf(
    shadow_pos: vec4<f32>,
    shadow_map: texture_depth_2d,
    shadow_sampler: sampler_comparison,
    texel_size: vec2<f32>,
) -> f32 {
    // Perspective divide
    let proj_coords = shadow_pos.xyz / shadow_pos.w;

    // Transform to [0, 1] range
    let shadow_uv = vec2<f32>(
        proj_coords.x * 0.5 + 0.5,
        proj_coords.y * -0.5 + 0.5
    );

    // Check bounds
    if shadow_uv.x < 0.0 || shadow_uv.x > 1.0 || shadow_uv.y < 0.0 || shadow_uv.y > 1.0 {
        return 1.0;
    }

    let current_depth = proj_coords.z;

    // 3x3 PCF kernel
    var shadow: f32 = 0.0;
    for (var x: i32 = -1; x <= 1; x++) {
        for (var y: i32 = -1; y <= 1; y++) {
            let offset = vec2<f32>(f32(x), f32(y)) * texel_size;
            shadow += textureSampleCompare(shadow_map, shadow_sampler, shadow_uv + offset, current_depth);
        }
    }

    return shadow / 9.0;
}

// =============================================================================
// Utility Functions
// =============================================================================

// Linear to sRGB conversion
fn linear_to_srgb(linear: vec3<f32>) -> vec3<f32> {
    let cutoff = step(linear, vec3<f32>(0.0031308));
    let low = linear * 12.92;
    let high = 1.055 * pow(linear, vec3<f32>(1.0 / 2.4)) - 0.055;
    return mix(high, low, cutoff);
}

// sRGB to linear conversion
fn srgb_to_linear(srgb: vec3<f32>) -> vec3<f32> {
    let cutoff = step(srgb, vec3<f32>(0.04045));
    let low = srgb / 12.92;
    let high = pow((srgb + 0.055) / 1.055, vec3<f32>(2.4));
    return mix(high, low, cutoff);
}

// Tonemap (ACES approximation)
fn tonemap_aces(color: vec3<f32>) -> vec3<f32> {
    let a = 2.51;
    let b = 0.03;
    let c = 2.43;
    let d = 0.59;
    let e = 0.14;
    return saturate((color * (a * color + b)) / (color * (c * color + d) + e));
}

// Reinhard tonemap
fn tonemap_reinhard(color: vec3<f32>) -> vec3<f32> {
    return color / (color + vec3<f32>(1.0));
}

// Calculate fog factor (linear)
fn fog_factor_linear(distance: f32, fog_start: f32, fog_end: f32) -> f32 {
    return clamp((fog_end - distance) / (fog_end - fog_start), 0.0, 1.0);
}

// Calculate fog factor (exponential)
fn fog_factor_exp(distance: f32, density: f32) -> f32 {
    return exp(-density * distance);
}

// Calculate fog factor (exponential squared)
fn fog_factor_exp2(distance: f32, density: f32) -> f32 {
    let f = density * distance;
    return exp(-f * f);
}

// Pack normal to octahedron encoding (for G-buffer)
fn encode_normal_octahedron(n: vec3<f32>) -> vec2<f32> {
    let l1_norm = abs(n.x) + abs(n.y) + abs(n.z);
    var result = n.xy / l1_norm;
    if n.z < 0.0 {
        result = (1.0 - abs(result.yx)) * sign(result);
    }
    return result * 0.5 + 0.5;
}

// Unpack normal from octahedron encoding
fn decode_normal_octahedron(encoded: vec2<f32>) -> vec3<f32> {
    var n = encoded * 2.0 - 1.0;
    let z = 1.0 - abs(n.x) - abs(n.y);
    if z < 0.0 {
        n = (1.0 - abs(n.yx)) * sign(n);
    }
    return normalize(vec3<f32>(n, z));
}