tirbofish/dropbear · diff
feature: improved shaders, way nicer looking.
Signature present but could not be verified.
Unverified
@@ -1,100 +1,80 @@ const PI: f32 = 3.14159265358979; struct Globals { - num_lights: u32, + num_lights: u32, ambient_strength: f32, } struct CameraUniform { - view_pos: vec4<f32>, - view: mat4x4<f32>, + view_pos: vec4<f32>, + view: mat4x4<f32>, view_proj: mat4x4<f32>, - inv_proj: mat4x4<f32>, - inv_view: mat4x4<f32>, + inv_proj: mat4x4<f32>, + inv_view: mat4x4<f32>, } struct Light { - position: vec4<f32>, + position: vec4<f32>, direction: vec4<f32>, // x, y, z, outer_cutoff_angle - color: vec4<f32>, // r, g, b, light_type (0=directional, 1=point, 2=spot) - constant: f32, - lin: f32, + color: vec4<f32>, // r, g, b, light_type (0=directional, 1=point, 2=spot) + constant: f32, + lin: f32, quadratic: f32, - cutoff: f32, + cutoff: f32, } struct MaterialUniform { - base_colour: vec4<f32>, - emissive: vec3<f32>, + base_colour: vec4<f32>, + emissive: vec3<f32>, emissive_strength: f32, - metallic: f32, - roughness: f32, - normal_scale: f32, + metallic: f32, + roughness: f32, + normal_scale: f32, occlusion_strength: f32, - alpha_cutoff: f32, - uv_tiling: vec2<f32>, + alpha_cutoff: f32, + uv_tiling: vec2<f32>, - has_normal_texture: u32, + has_normal_texture: u32, has_emissive_texture: u32, has_metallic_texture: u32, has_occlusion_texture: u32, } struct MorphTargetInfo { - num_vertices: u32, - num_targets: u32, - base_offset: u32, + num_vertices: u32, + num_targets: u32, + base_offset: u32, weight_offset: u32, - uses_morph: u32, + uses_morph: u32, } // per-frame -@group(0) @binding(0) -var<uniform> u_globals: Globals; -@group(0) @binding(1) -var<uniform> u_camera: CameraUniform; -@group(0) @binding(2) -var<storage, read> s_light_array: array<Light>; +@group(0) @binding(0) var<uniform> u_globals: Globals; +@group(0) @binding(1) var<uniform> u_camera: CameraUniform; +@group(0) @binding(2) var<storage, read> s_light_array: array<Light>; // per-material -@group(1) @binding(0) -var<uniform> u_material: MaterialUniform; -@group(1) @binding(1) -var t_diffuse: texture_2d<f32>; -@group(1) @binding(2) -var s_diffuse: sampler; -@group(1) @binding(3) -var t_normal: texture_2d<f32>; -@group(1) @binding(4) -var s_normal: sampler; -@group(1) @binding(5) -var t_emissive: texture_2d<f32>; -@group(1) @binding(6) -var s_emissive: sampler; -@group(1) @binding(7) -var t_metallic: texture_2d<f32>; -@group(1) @binding(8) -var s_metallic: sampler; -@group(1) @binding(9) -var t_occlusion: texture_2d<f32>; -@group(1) @binding(10) -var s_occlusion: sampler; +@group(1) @binding(0) var<uniform> u_material: MaterialUniform; +@group(1) @binding(1) var t_diffuse: texture_2d<f32>; +@group(1) @binding(2) var s_diffuse: sampler; +@group(1) @binding(3) var t_normal: texture_2d<f32>; +@group(1) @binding(4) var s_normal: sampler; +@group(1) @binding(5) var t_emissive: texture_2d<f32>; +@group(1) @binding(6) var s_emissive: sampler; +@group(1) @binding(7) var t_metallic: texture_2d<f32>; +@group(1) @binding(8) var s_metallic: sampler; +@group(1) @binding(9) var t_occlusion: texture_2d<f32>; +@group(1) @binding(10) var s_occlusion: sampler; // animation -@group(2) @binding(0) -var<storage, read> s_skinning: array<mat4x4<f32>>; -@group(2) @binding(1) -var<storage, read> s_morph_deltas: array<f32>; -@group(2) @binding(2) -var<storage, read> s_morph_weights: array<f32>; -@group(2) @binding(3) -var<uniform> u_morph_info: MorphTargetInfo; +@group(2) @binding(0) var<storage, read> s_skinning: array<mat4x4<f32>>; +@group(2) @binding(1) var<storage, read> s_morph_deltas: array<f32>; +@group(2) @binding(2) var<storage, read> s_morph_weights: array<f32>; +@group(2) @binding(3) var<uniform> u_morph_info: MorphTargetInfo; // environment -@group(3) @binding(0) -var env_map: texture_cube<f32>; -@group(3) @binding(1) -var env_sampler: sampler; +@group(3) @binding(0) var env_map: texture_cube<f32>; +@group(3) @binding(1) var env_sampler: sampler; struct InstanceInput { @location(8) model_matrix_0: vec4<f32>, @@ -108,33 +88,33 @@ struct InstanceInput { }; struct VertexInput { - @builtin(vertex_index) vertex_id: u32, - @location(0) position: vec3<f32>, - @location(1) normal: vec3<f32>, - @location(2) tangent: vec4<f32>, + @builtin(vertex_index) vertex_id: u32, + @location(0) position: vec3<f32>, + @location(1) normal: vec3<f32>, + @location(2) tangent: vec4<f32>, @location(3) tex_coords0: vec2<f32>, @location(4) tex_coords1: vec2<f32>, - @location(5) colour0: vec4<f32>, - @location(6) joints: vec4<u32>, - @location(7) weights: vec4<f32>, + @location(5) colour0: vec4<f32>, + @location(6) joints: vec4<u32>, + @location(7) weights: vec4<f32>, }; struct VertexOutput { - @builtin(position) clip_position: vec4<f32>, - @location(0) tex_coords: vec2<f32>, - @location(1) world_normal: vec3<f32>, - @location(2) world_position: vec3<f32>, - @location(3) world_tangent: vec3<f32>, - @location(4) world_bitangent: vec3<f32>, - @location(5) world_view_position: vec3<f32>, + @builtin(position) clip_position: vec4<f32>, + @location(0) tex_coords: vec2<f32>, + @location(1) world_normal: vec3<f32>, + @location(2) world_position: vec3<f32>, + @location(3) world_tangent: vec3<f32>, + @location(4) world_bitangent: vec3<f32>, + @location(5) world_view_position: vec3<f32>, }; fn apply_morph(base_pos: vec3<f32>, vertex_id: u32) -> vec3<f32> { var result = base_pos; for (var t = 0u; t < u_morph_info.num_targets; t++) { let weight = s_morph_weights[u_morph_info.weight_offset + t]; - let idx = u_morph_info.base_offset + (t * u_morph_info.num_vertices + vertex_id) * 3u; - let delta = vec3<f32>( + let idx = u_morph_info.base_offset + (t * u_morph_info.num_vertices + vertex_id) * 3u; + let delta = vec3<f32>( s_morph_deltas[idx], s_morph_deltas[idx + 1u], s_morph_deltas[idx + 2u], @@ -174,27 +154,27 @@ fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput { s_skinning[j.w] * w.w; } - let morphed_pos = select( + let morphed_pos = select( model.position, apply_morph(model.position, model.vertex_id), u_morph_info.uses_morph != 0u, ); let world_position = model_matrix * skin_matrix * vec4<f32>(morphed_pos, 1.0); - let skin_normal = (skin_matrix * vec4<f32>(model.normal, 0.0)).xyz; - let skin_tangent = (skin_matrix * vec4<f32>(model.tangent.xyz, 0.0)).xyz; + let skin_normal = (skin_matrix * vec4<f32>(model.normal, 0.0)).xyz; + let skin_tangent = (skin_matrix * vec4<f32>(model.tangent.xyz, 0.0)).xyz; let world_normal = normalize(normal_matrix * skin_normal); let world_tangent = normalize(normal_matrix * skin_tangent); let world_bitangent = normalize(cross(world_normal, world_tangent) * model.tangent.w); var out: VertexOutput; - out.clip_position = u_camera.view_proj * world_position; - out.tex_coords = model.tex_coords0; - out.world_normal = world_normal; - out.world_position = world_position.xyz; - out.world_tangent = world_tangent; - out.world_bitangent = world_bitangent; + out.clip_position = u_camera.view_proj * world_position; + out.tex_coords = model.tex_coords0; + out.world_normal = world_normal; + out.world_position = world_position.xyz; + out.world_tangent = world_tangent; + out.world_bitangent = world_bitangent; out.world_view_position = u_camera.view_pos.xyz; return out; } @@ -265,7 +245,7 @@ fn directional_light_pbr( roughness: f32, metallic: f32, ) -> vec3<f32> { - let l = normalize(light.direction.xyz); + let l = normalize(-light.direction.xyz); return pbr_direct(n, v, l, albedo, f0, roughness, metallic) * light.color.rgb; } @@ -296,49 +276,48 @@ fn spot_light_pbr( metallic: f32, world_pos: vec3<f32>, ) -> vec3<f32> { - let to_light = light.position.xyz - world_pos; - let dist = length(to_light); - let l = to_light / dist; - let atten = 1.0 / (light.constant + light.lin * dist + light.quadratic * dist * dist); + let to_light = light.position.xyz - world_pos; + let dist = length(to_light); + let l = to_light / dist; + let atten = 1.0 / (light.constant + light.lin * dist + light.quadratic * dist * dist); - let spot_dir = normalize(light.direction.xyz); - let theta = dot(-l, spot_dir); - let cone_factor = smoothstep(light.direction.w, light.cutoff, theta); + let spot_dir = normalize(light.direction.xyz); + let theta = dot(-l, spot_dir); + let outer_cutoff = light.direction.w; + let epsilon = light.cutoff - outer_cutoff; + let cone_factor = clamp((theta - outer_cutoff) / epsilon, 0.0, 1.0); return pbr_direct(n, v, l, albedo, f0, roughness, metallic) * light.color.rgb * atten * cone_factor; } fn ibl( - n: vec3<f32>, - v: vec3<f32>, - albedo: vec3<f32>, - f0: vec3<f32>, - roughness: f32, - metallic: f32, + n: vec3<f32>, + v: vec3<f32>, + albedo: vec3<f32>, + f0: vec3<f32>, + roughness: f32, + metallic: f32, ) -> vec3<f32> { - let n_dot_v = max(dot(n, v), 0.0001); + let n_dot_v = max(dot(n, v), 0.0001); let num_mips = f32(textureNumLevels(env_map)); - // fresnel for smooth roughness fade - let F = fresnel_schlick_roughness(n_dot_v, f0, roughness); - + let F = fresnel_schlick_roughness(n_dot_v, f0, roughness); let k_s = F; let k_d = (1.0 - k_s) * (1.0 - metallic); - // diffuse irradiance: sample the most-blurred mip to approximate - // hemisphere-integrated radiance + // diffuse irradiance: most-blurred mip approximates hemisphere integral let irradiance = textureSampleLevel(env_map, env_sampler, n, num_mips - 1.0).rgb; let diffuse_ibl = k_d * albedo * irradiance; - // specular radiance: rougher materials sample higher (blurrier) mip levels - let r = reflect(-v, n); - let specular_mip = roughness * roughness * (num_mips - 1.0); - let prefiltered = textureSampleLevel(env_map, env_sampler, r, specular_mip).rgb; + // specular: rougher surfaces sample blurrier mips + let r = reflect(-v, n); + let specular_mip = roughness * roughness * (num_mips - 1.0); + let prefiltered = textureSampleLevel(env_map, env_sampler, r, specular_mip).rgb; - // Analytic BRDF integration approximation (no LUT needed) - let env_brdf_x = exp(-6.9 * roughness * roughness * n_dot_v); - let env_brdf = F * (1.0 - env_brdf_x) + f0 * env_brdf_x; + // analytic BRDF integration approximation (no LUT required) + let env_brdf_x = exp(-6.9 * roughness * roughness * n_dot_v); + let env_brdf = F * (1.0 - env_brdf_x) + f0 * env_brdf_x; let specular_ibl = prefiltered * env_brdf; return diffuse_ibl + specular_ibl; @@ -349,20 +328,20 @@ fn get_normal(in: VertexOutput, uv: vec2<f32>) -> vec3<f32> { return normalize(in.world_normal); } - let raw = textureSample(t_normal, s_normal, uv).rgb; - var tangent_normal = raw * 2.0 - 1.0; + let raw = textureSample(t_normal, s_normal, uv).rgb; + let unpacked = normalize((raw * 2.0 - 1.0) * vec3<f32>(u_material.normal_scale, u_material.normal_scale, 1.0)); - tangent_normal = vec3<f32>( - tangent_normal.xy * u_material.normal_scale, - tangent_normal.z, - ); + let n = normalize(in.world_normal); + // Gram-Schmidt re-orthogonalise tangent against normal + var t = normalize(in.world_tangent); + t = normalize(t - n * dot(n, t)); - let t = normalize(in.world_tangent); - let b = normalize(in.world_bitangent); - let nm = normalize(in.world_normal); - let tbn = mat3x3<f32>(t, b, nm); + let b_in = normalize(in.world_bitangent); + let handedness = select(-1.0, 1.0, dot(cross(n, t), b_in) >= 0.0); + let b = cross(n, t) * handedness; - return normalize(tbn * tangent_normal); + let tbn = mat3x3<f32>(t, b, n); + return normalize(tbn * unpacked); } @fragment @@ -371,64 +350,60 @@ fn s_fs_main(in: VertexOutput) -> @location(0) vec4<f32> { // albedo + alpha let albedo_sample = textureSample(t_diffuse, s_diffuse, uv); - if albedo_sample.a < u_material.alpha_cutoff { + let base_colour = albedo_sample * u_material.base_colour; + if base_colour.a < u_material.alpha_cutoff { discard; } + let albedo = base_colour.rgb; - let albedo = albedo_sample.rgb * u_material.base_colour.rgb; - - // metallic / roughness - // glTF convention: B = metallic, G = roughness (R = occlusion when packed). + // metallic / roughness — glTF: B = metallic, G = roughness var metallic = u_material.metallic; var roughness = u_material.roughness; if u_material.has_metallic_texture != 0u { - let mr_sample = textureSample(t_metallic, s_metallic, uv); - metallic *= mr_sample.b; - roughness *= mr_sample.g; + let mr = textureSample(t_metallic, s_metallic, uv); + metallic *= mr.b; + roughness *= mr.g; } + metallic = clamp(metallic, 0.0, 1.0); roughness = clamp(roughness, 0.04, 1.0); // occlusion var occlusion = 1.0; if u_material.has_occlusion_texture != 0u { - let occ_sample = textureSample(t_occlusion, s_occlusion, uv); - occlusion = mix(1.0, occ_sample.r, u_material.occlusion_strength); + let occ = textureSample(t_occlusion, s_occlusion, uv).r; + occlusion = 1.0 + u_material.occlusion_strength * (occ - 1.0); + } + + // emissive + var emissive = u_material.emissive * u_material.emissive_strength; + if u_material.has_emissive_texture != 0u { + emissive *= textureSample(t_emissive, s_emissive, uv).rgb; } let n = get_normal(in, uv); let v = normalize(u_camera.view_pos.xyz - in.world_position); + let f0 = mix(vec3<f32>(0.04), albedo, metallic); - // F0: dielectrics use 0.04, metals use their albedo colour - let f0 = mix(vec3<f32>(0.04), albedo, metallic); - - // cook-torence + // direct lighting (Cook-Torrance GGX) var lo = vec3<f32>(0.0); for (var i = 0u; i < u_globals.num_lights; i++) { let light = s_light_array[i]; - let light_type = u32(light.color.w); + let light_type = i32(light.color.w + 0.1); - if light_type == 0u { + if light_type == 0 { lo += directional_light_pbr(light, n, v, albedo, f0, roughness, metallic); - } else if light_type == 1u { + } else if light_type == 1 { lo += point_light_pbr(light, n, v, albedo, f0, roughness, metallic, in.world_position); - } else if light_type == 2u { + } else if light_type == 2 { lo += spot_light_pbr(light, n, v, albedo, f0, roughness, metallic, in.world_position); } } - // image-based lighting (env map) + // image-based lighting let ambient_ibl = ibl(n, v, albedo, f0, roughness, metallic) * occlusion * u_globals.ambient_strength; - // emissive - var emissive = u_material.emissive * u_material.emissive_strength; - if u_material.has_emissive_texture != 0u { - emissive *= textureSample(t_emissive, s_emissive, uv).rgb; - } - - // combine let colour = lo + ambient_ibl + emissive; - - return vec4<f32>(colour, albedo_sample.a); + return vec4<f32>(colour, base_colour.a); }