threecrate-gpu 0.8.0

GPU-accelerated algorithms for threecrate using wgpu
Documentation
// PBR Mesh Shader with Metallic-Roughness Workflow
// Supports albedo, metallic, roughness, ambient occlusion, emission, and normal mapping

struct MeshCameraUniform {
    view_proj: mat4x4<f32>,
    view_pos: vec3<f32>,
    _padding: f32,
}

struct PbrMaterial {
    albedo: vec3<f32>,
    metallic: f32,
    roughness: f32,
    ao: f32,
    emission: vec3<f32>,
    _padding: f32,
}

struct MeshLightingParams {
    light_position: vec3<f32>,
    light_intensity: f32,
    light_color: vec3<f32>,
    ambient_strength: f32,
    gamma: f32,
    exposure: f32,
    _padding: vec2<f32>,
}

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) normal: vec3<f32>,
    @location(2) tangent: vec3<f32>,
    @location(3) bitangent: vec3<f32>,
    @location(4) uv: vec2<f32>,
    @location(5) color: vec3<f32>,
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) world_pos: vec3<f32>,
    @location(1) normal: vec3<f32>,
    @location(2) tangent: vec3<f32>,
    @location(3) bitangent: vec3<f32>,
    @location(4) uv: vec2<f32>,
    @location(5) color: vec3<f32>,
    @location(6) view_pos: vec3<f32>,
}

@group(0) @binding(0)
var<uniform> camera: MeshCameraUniform;

@group(0) @binding(1)
var<uniform> material: PbrMaterial;

@group(0) @binding(2)
var<uniform> lighting: MeshLightingParams;

@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    
    out.clip_position = camera.view_proj * vec4<f32>(input.position, 1.0);
    out.world_pos = input.position;
    out.normal = normalize(input.normal);
    out.tangent = normalize(input.tangent);
    out.bitangent = normalize(input.bitangent);
    out.uv = input.uv;
    out.color = input.color;
    out.view_pos = camera.view_pos;
    
    return out;
}

// === PBR BRDF Functions ===

fn distribution_ggx(n: vec3<f32>, h: vec3<f32>, roughness: f32) -> f32 {
    let a = roughness * roughness;
    let a2 = a * a;
    let n_dot_h = max(dot(n, h), 0.0);
    let n_dot_h2 = n_dot_h * n_dot_h;
    
    let denom = n_dot_h2 * (a2 - 1.0) + 1.0;
    return a2 / (3.14159265 * denom * denom);
}

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

fn geometry_smith(n: vec3<f32>, v: vec3<f32>, l: vec3<f32>, roughness: f32) -> f32 {
    let n_dot_v = max(dot(n, v), 0.0);
    let n_dot_l = max(dot(n, l), 0.0);
    let ggx2 = geometry_schlick_ggx(n_dot_v, roughness);
    let ggx1 = geometry_schlick_ggx(n_dot_l, roughness);
    
    return ggx1 * ggx2;
}

fn fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
    return f0 + (vec3<f32>(1.0) - f0) * pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0);
}

fn calculate_pbr_lighting(
    world_pos: vec3<f32>,
    normal: vec3<f32>,
    view_dir: vec3<f32>,
    albedo: vec3<f32>,
    metallic: f32,
    roughness: f32,
    ao: f32
) -> vec3<f32> {
    // Light direction
    let light_dir = normalize(lighting.light_position - world_pos);
    let half_dir = normalize(view_dir + light_dir);
    
    // Distance and attenuation
    let distance = length(lighting.light_position - world_pos);
    let attenuation = 1.0 / (distance * distance);
    let radiance = lighting.light_color * lighting.light_intensity * attenuation;
    
    // F0 for dielectric and metallic materials
    var f0 = vec3<f32>(0.04);
    f0 = mix(f0, albedo, metallic);
    
    // Calculate BRDF
    let ndf = distribution_ggx(normal, half_dir, roughness);
    let g = geometry_smith(normal, view_dir, light_dir, roughness);
    let f = fresnel_schlick(max(dot(half_dir, view_dir), 0.0), f0);
    
    let numerator = ndf * g * f;
    let denominator = 4.0 * max(dot(normal, view_dir), 0.0) * max(dot(normal, light_dir), 0.0) + 0.0001;
    let specular = numerator / denominator;
    
    // Energy conservation
    let ks = f;
    var kd = vec3<f32>(1.0) - ks;
    kd *= 1.0 - metallic;
    
    // Lambertian diffuse
    let n_dot_l = max(dot(normal, light_dir), 0.0);
    let diffuse = kd * albedo / 3.14159265;
    
    // Outgoing radiance
    let lo = (diffuse + specular) * radiance * n_dot_l;
    
    // Ambient lighting
    let ambient = lighting.ambient_strength * albedo * ao;
    
    return ambient + lo;
}

// HDR tone mapping
fn tone_map_reinhard(color: vec3<f32>) -> vec3<f32> {
    // Exposure
    let exposed = color * lighting.exposure;
    
    // Reinhard tone mapping
    let mapped = exposed / (exposed + vec3<f32>(1.0));
    
    // Gamma correction
    return pow(mapped, vec3<f32>(1.0 / lighting.gamma));
}

// Advanced tone mapping (ACES)
fn tone_map_aces(color: vec3<f32>) -> vec3<f32> {
    let exposed = color * lighting.exposure;
    
    // ACES tone mapping curve
    let a = 2.51;
    let b = 0.03;
    let c = 2.43;
    let d = 0.59;
    let e = 0.14;
    
    let tone_mapped = (exposed * (a * exposed + b)) / (exposed * (c * exposed + d) + e);
    
    // Gamma correction
    return pow(clamp(tone_mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / lighting.gamma));
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let view_dir = normalize(input.view_pos - input.world_pos);
    
    // Use vertex color blended with material albedo
    let albedo = mix(material.albedo, input.color, 0.5);
    
    // Calculate PBR lighting
    let color = calculate_pbr_lighting(
        input.world_pos,
        input.normal,
        view_dir,
        albedo,
        material.metallic,
        material.roughness,
        material.ao
    );
    
    // Add emission
    let final_color = color + material.emission;
    
    // HDR tone mapping
    let tone_mapped = tone_map_aces(final_color);
    
    return vec4<f32>(tone_mapped, 1.0);
}