kitgit

tirbofish/dropbear · diff

59ef018 · Thribhu K

wip: working on shader remake, issue with normals

Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 81c5a3e..5e4273c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -75,9 +75,6 @@ rapier3d = { version = "0.32", features = [ "simd-stable", "serde-serialize" ] }
 cbindgen = { version = "0.29.2" }
 postcard = { version = "1.1", features = ["use-std"]}
 pollster = "0.4"
-#yakui = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f", features = ["default-fonts"] }
-#yakui-wgpu = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f" }
-#yakui-winit = { git = "https://github.com/SecondHalfGames/yakui", rev = "af8fc1f" }
 thiserror = "2.0"
 tempfile = "3.24"
 combine = "4.6"
@@ -90,6 +87,7 @@ dyn-clone = "1.0"
 downcast-rs = "2.0"
 float-derive = "0.1"
 rkyv = "0.8"
+bevy_mikktspace = "1.0.0"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/crates/dropbear-engine/Cargo.toml b/crates/dropbear-engine/Cargo.toml
index 3f42a2c..4421245 100644
--- a/crates/dropbear-engine/Cargo.toml
+++ b/crates/dropbear-engine/Cargo.toml
@@ -50,12 +50,10 @@ bitflags.workspace = true
 puffin_http.workspace = true
 float-derive.workspace = true
 rkyv.workspace = true
-
-#yakui-wgpu.workspace = true
-#yakui.workspace = true
+bevy_mikktspace.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
 
 [build-dependencies]
-slank = { path = "../slank", features = ["download-slang"] }
\ No newline at end of file
+slank = { path = "../slank", features = ["download-slang"] }
diff --git a/crates/dropbear-engine/src/asset.rs b/crates/dropbear-engine/src/asset.rs
index 850d1f9..e524609 100644
--- a/crates/dropbear-engine/src/asset.rs
+++ b/crates/dropbear-engine/src/asset.rs
@@ -134,16 +134,27 @@ impl AssetRegistry {
     pub fn flush_unused(&mut self) -> usize {
         let mut counter = 0;
 
-        // textures
+        // models
         self.models.retain(|id, arc| {
             let is_null = *id == 0;
             let is_protected = arc.label.eq_ignore_ascii_case("light cube");
-            if is_null || is_protected || Arc::get_mut(arc).is_none() { counter+=1; false } else { true }
+            
+            if is_null || is_protected {
+                return true;
+            }
+            
+            let in_use = Arc::get_mut(arc).is_none();
+            if !in_use { counter += 1; }
+            in_use
         });
         self.model_labels.retain(|_, handle| self.models.contains_key(&handle.id));
 
-        // models
-        self.textures.retain(|_, arc| if Arc::get_mut(arc).is_none() { counter+=1; false } else { true });
+        // textures
+        self.textures.retain(|_, arc| {
+            let in_use = Arc::get_mut(arc).is_none();
+            if !in_use { counter += 1; }
+            in_use
+        });
         self.texture_labels.retain(|_, handle| self.textures.contains_key(&handle.id));
 
         counter
diff --git a/crates/dropbear-engine/src/model.rs b/crates/dropbear-engine/src/model.rs
index 8534ba4..fd4db41 100644
--- a/crates/dropbear-engine/src/model.rs
+++ b/crates/dropbear-engine/src/model.rs
@@ -593,6 +593,51 @@ struct ProcessedTexture {
     mime_type: Option<String>,
 }
 
+/// Adapter that feeds indexed triangle mesh data into bevy_mikktspace and
+/// collects the resulting per-vertex tangents (xyzw, where w is handedness).
+struct MikktMesh<'a> {
+    positions: &'a [[f32; 3]],
+    normals: &'a [[f32; 3]],
+    tex_coords: &'a [[f32; 2]],
+    indices: &'a [u32],
+    /// Output tangents, indexed by vertex (same order as `positions`).
+    tangents: Vec<[f32; 4]>,
+}
+
+impl<'a> bevy_mikktspace::Geometry for MikktMesh<'a> {
+    fn num_faces(&self) -> usize {
+        self.indices.len() / 3
+    }
+
+    fn num_vertices_of_face(&self, _face: usize) -> usize {
+        3
+    }
+
+    fn position(&self, face: usize, vert: usize) -> [f32; 3] {
+        self.positions[self.indices[face * 3 + vert] as usize]
+    }
+
+    fn normal(&self, face: usize, vert: usize) -> [f32; 3] {
+        self.normals[self.indices[face * 3 + vert] as usize]
+    }
+
+    fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] {
+        self.tex_coords[self.indices[face * 3 + vert] as usize]
+    }
+
+    fn set_tangent(
+        &mut self,
+        tangent_space: Option<bevy_mikktspace::TangentSpace>,
+        face: usize,
+        vert: usize,
+    ) {
+        let vertex_idx = self.indices[face * 3 + vert] as usize;
+        if let Some(ts) = tangent_space {
+            self.tangents[vertex_idx] = ts.tangent_encoded();
+        }
+    }
+}
+
 impl Model {
     fn load_materials(
         gltf: &gltf::Document,
@@ -732,11 +777,6 @@ impl Model {
                 .map(|iter| iter.collect())
                 .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
 
-            let tangents: Vec<[f32; 4]> = reader
-                .read_tangents()
-                .map(|iter| iter.collect())
-                .unwrap_or_else(|| vec![[0.0, 0.0, 0.0, 1.0]; positions.len()]);
-
             let colors: Vec<[f32; 4]> = reader
                 .read_colors(0)
                 .map(|iter| iter.into_rgba_f32().collect())
@@ -772,6 +812,21 @@ impl Model {
                 .map(|iter| iter.into_f32().collect())
                 .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
 
+            let tangents: Vec<[f32; 4]> = reader
+                .read_tangents()
+                .map(|iter| iter.collect())
+                .unwrap_or_else(|| {
+                    let mut mikkt = MikktMesh {
+                        positions: &positions,
+                        normals: &normals,
+                        tex_coords: &tex_coords,
+                        indices: &indices,
+                        tangents: vec![[1.0, 0.0, 0.0, 1.0]; positions.len()],
+                    };
+                    let _ = bevy_mikktspace::generate_tangents(&mut mikkt);
+                    mikkt.tangents
+                });
+
             let mut morph_deltas: Vec<f32> = Vec::new();
             let mut morph_target_count: usize = 0;
             for (target_positions, _target_normals, _target_tangents) in reader.read_morph_targets() {
@@ -1723,4 +1778,4 @@ pub struct MaterialUniform {
     pub has_metallic_texture: u32,
     pub has_occlusion_texture: u32,
     pub pad: u32,
-}
+}
\ No newline at end of file
diff --git a/crates/dropbear-engine/src/shaders/shader.wgsl b/crates/dropbear-engine/src/shaders/shader.wgsl
index 8162f0a..c5ee895 100644
--- a/crates/dropbear-engine/src/shaders/shader.wgsl
+++ b/crates/dropbear-engine/src/shaders/shader.wgsl
@@ -197,194 +197,137 @@ fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput {
     return out;
 }
 
-
-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 apply_normal_map(
-    world_normal_in:    vec3<f32>,
-    world_tangent_in:   vec3<f32>,
-    world_bitangent_in: vec3<f32>,
-    normal_sample_raw:  vec3<f32>,
-    normal_scale:       f32,
+fn blinn_phong(
+    n: vec3<f32>,
+    l: vec3<f32>,
+    v: vec3<f32>,
+    albedo: vec3<f32>,
+    light_colour: vec3<f32>,
 ) -> vec3<f32> {
-    let unpacked = normalize((normal_sample_raw * 2.0 - 1.0) * vec3<f32>(normal_scale, normal_scale, 1.0));
-
-    let n = normalize(world_normal_in);
-    var t = normalize(world_tangent_in);
-    // Gram-Schmidt re-orthogonalise
-    t = normalize(t - n * dot(n, t));
-
-    let b_in     = normalize(world_bitangent_in);
-    let handedness = select(-1.0, 1.0, dot(cross(n, t), b_in) >= 0.0);
-    let b        = cross(n, t) * handedness;
-
-    let tbn = mat3x3<f32>(t, b, n);
-    return normalize(tbn * unpacked);
-}
-
-fn eval_brdf(
-    light_dir:   vec3<f32>,
-    world_normal: vec3<f32>,
-    view_dir:    vec3<f32>,
-    light_color: vec3<f32>,
-    tex_color:   vec3<f32>,
-    metallic:    f32,
-    roughness:   f32,
-) -> vec3<f32> {
-    let r = clamp(roughness, 0.05, 1.0);
-
-    let n_dot_l  = max(dot(world_normal, light_dir), 0.0);
-    let diffuse  = light_color * n_dot_l * tex_color * (1.0 - metallic);
-
-    let half_dir = normalize(light_dir + view_dir);
-    let n_dot_h  = max(dot(world_normal, half_dir), 0.0);
-    let shininess = pow(2.0, (1.0 - r) * 10.0); // maps [0,1] roughness → [2, 1024]
-    let spec_val  = pow(n_dot_h, shininess);
-
-    let f0       = mix(vec3<f32>(0.04), tex_color, metallic);
-    let n_dot_v  = max(dot(world_normal, view_dir), 0.0);
-    let fresnel  = fresnel_schlick(max(dot(half_dir, view_dir), 0.0), f0);
-    let specular = light_color * spec_val * fresnel;
+    // diffuse
+    // 1.0 = light hits head-on
+    // 0.0 = grazing
+    // negative = behind
+    let ndotl = max(dot(n, l), 0.0);
+    let diffuse = albedo * light_colour * ndotl;
+
+    // specular
+    let h = normalize(l + v);
+    let ndoth = max(dot(n, h), 0.0);
+    let specular = light_colour * pow(ndoth, 32.0); // 32.0 = shininess
 
     return diffuse + specular;
 }
 
+// l will always be the same
 fn directional_light(
-    light:        Light,
-    world_normal: vec3<f32>,
-    view_dir:     vec3<f32>,
-    tex_color:    vec3<f32>,
-    metallic:     f32,
-    roughness:    f32,
+    light: Light,
+    n: vec3<f32>,
+    v: vec3<f32>,
+    albedo: vec3<f32>,
 ) -> vec3<f32> {
-    let light_dir = normalize(-light.direction.xyz);
-    return eval_brdf(light_dir, world_normal, view_dir, light.color.xyz, tex_color, metallic, roughness);
+    // direction stored in light points towards light source
+    let l = normalize(light.direction.xyz);
+    let light_colour = light.color.rgb;
+    return blinn_phong(n, l, v, albedo, light_colour);
 }
 
+// light comes from a position and falls off with distance
 fn point_light(
-    light:        Light,
-    world_pos:    vec3<f32>,
-    world_normal: vec3<f32>,
-    view_dir:     vec3<f32>,
-    tex_color:    vec3<f32>,
-    metallic:     f32,
-    roughness:    f32,
+    light: Light,
+    n: vec3<f32>,
+    v: vec3<f32>,
+    albedo: vec3<f32>,
+    world_pos: vec3<f32>,
 ) -> vec3<f32> {
-    let to_light  = light.position.xyz - world_pos;
-    let distance  = length(to_light);
-    let light_dir = normalize(to_light);
-
-    let attenuation = 1.0 / (light.constant + light.lin * distance + light.quadratic * (distance * distance));
-
-    return eval_brdf(light_dir, world_normal, view_dir, light.color.xyz, tex_color, metallic, roughness)
-           * attenuation;
+    let to_light = light.position.xyz - world_pos;
+    let l = normalize(to_light);
+    let dist = length(to_light);
+
+    let attenuation = 1.0 / (light.constant + light.lin * dist + light.quadratic * pow(dist, 2));
+    
+    let light_colour = light.color.rgb * attenuation;
+    return blinn_phong(n, l, v, albedo, light_colour);
 }
 
+// same as point light, except in the shape of a cone and falls off with further distance
 fn spot_light(
-    light:        Light,
-    world_pos:    vec3<f32>,
-    world_normal: vec3<f32>,
-    view_dir:     vec3<f32>,
-    tex_color:    vec3<f32>,
-    metallic:     f32,
-    roughness:    f32,
+    light: Light,
+    n: vec3<f32>,
+    v: vec3<f32>,
+    albedo: vec3<f32>,
+    world_pos: vec3<f32>,
 ) -> vec3<f32> {
-    let to_light     = light.position.xyz - world_pos;
-    let distance     = length(to_light);
-    let light_dir    = normalize(to_light);
+    let to_light = light.position.xyz - world_pos;
+    let l = normalize(to_light);
+    let dist = length(to_light);
+
+    let attenuation = 1.0 / (light.constant + light.lin * dist + light.quadratic * pow(dist, 2));
+
+    // how far off-center is the fragment from the spotlight's direction?
+    let spot_dir = normalize(light.direction.xyz);
+    let theta = dot(-l, spot_dir); // cosine from the angle of the center
 
-    let theta        = dot(light_dir, normalize(-light.direction.xyz));
-    let outer_cutoff = light.direction.w;
-    let epsilon      = light.cutoff - outer_cutoff;
-    let intensity    = clamp((theta - outer_cutoff) / epsilon, 0.0, 1.0);
+    let inner = light.cutoff;
+    let outer = light.direction.w;
+    let cone_factor = smoothstep(outer, inner, theta);
 
-    let attenuation  = 1.0 / (light.constant + light.lin * distance + light.quadratic * (distance * distance));
+    let light_colour = light.color.rgb * attenuation * cone_factor;
+    return blinn_phong(n, l, v, albedo, light_colour);
+}
+
+fn get_normal(in: VertexOutput, uv: vec2<f32>) -> vec3<f32> {
+    if u_material.has_normal_texture == 0u {
+        return normalize(in.world_normal);
+    }
 
-    return eval_brdf(light_dir, world_normal, view_dir, light.color.xyz, tex_color, metallic, roughness)
-           * attenuation * intensity;
+    let raw = textureSample(t_normal, s_normal, uv).rgb;
+    var tangent_normal = raw * 2.0 - 1.0;
+
+    tangent_normal = vec3<f32>(
+        tangent_normal.xy * u_material.normal_scale,
+        tangent_normal.z,
+    );
+
+    let t = normalize(in.world_tangent);
+    let b = normalize(in.world_bitangent);
+    let n = normalize(in.world_normal);
+    let tbn = mat3x3<f32>(t, b, n);
+
+    return normalize(tbn * tangent_normal);
 }
 
 @fragment
 fn s_fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
     let uv = in.tex_coords * u_material.uv_tiling;
+    let albedo_sample = textureSample(t_diffuse, s_diffuse, uv);
 
-    // base colour
-    let tex_color  = textureSample(t_diffuse, s_diffuse, uv);
-    let base_colour = tex_color * u_material.base_colour;
-    if (base_colour.a < u_material.alpha_cutoff) {
+    // transparency
+    if albedo_sample.a < u_material.alpha_cutoff {
         discard;
     }
 
-    // normal
-    var world_normal: vec3<f32>;
-    if (u_material.has_normal_texture != 0u) {
-        let normal_sample_raw = textureSample(t_normal, s_normal, uv).xyz;
-        world_normal = apply_normal_map(
-            in.world_normal,
-            in.world_tangent,
-            in.world_bitangent,
-            normal_sample_raw,
-            u_material.normal_scale,
-        );
-    } else {
-        world_normal = normalize(in.world_normal);
-    }
-
-    // metallic and roughness
-    var metallic  = u_material.metallic;
-    var roughness = u_material.roughness;
-    if (u_material.has_metallic_texture != 0u) {
-        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.05, 1.0);
-
-    // occlusion
-    var occlusion = 1.0;
-    if (u_material.has_occlusion_texture != 0u) {
-        let occ   = textureSample(t_occlusion, s_occlusion, uv).r;
-        occlusion = 1.0 + u_material.occlusion_strength * (occ - 1.0);
-    }
+    let albedo = albedo_sample.rgb * u_material.base_colour.rgb;
+    let v = normalize(u_camera.view_pos.xyz - in.world_position);
+    let n = get_normal(in, uv);
 
-    // emissive
-    var emissive = u_material.emissive * u_material.emissive_strength;
-    if (u_material.has_emissive_texture != 0u) {
-        let emissive_tex = textureSample(t_emissive, s_emissive, uv).rgb;
-        emissive *= emissive_tex;
-    }
-
-    let view_dir = normalize(u_camera.view_pos.xyz - in.world_position);
+    var total_light = vec3<f32>(0.0);
 
-    // ambient
-    let ambient = u_globals.ambient_strength * base_colour.rgb * occlusion;
-    var final_color = ambient;
+    let ambient = albedo * u_globals.ambient_strength;
+    total_light += ambient;
 
-    for (var i = 0u; i < u_globals.num_lights; i += 1u) {
-        let light      = s_light_array[i];
-        let light_type = i32(light.color.w + 0.1);
+    for (var i = 0u; i < u_globals.num_lights; i++) {
+        let light = s_light_array[i];
+        let light_type = u32(light.color.w);
 
-        if (light_type == 0) {
-            final_color += directional_light(light, world_normal, view_dir, base_colour.rgb, metallic, roughness);
-        } else if (light_type == 1) {
-            final_color += point_light(light, in.world_position, world_normal, view_dir, base_colour.rgb, metallic, roughness);
-        } else if (light_type == 2) {
-            final_color += spot_light(light, in.world_position, world_normal, view_dir, base_colour.rgb, metallic, roughness);
+        if light_type == 0u {
+            total_light += directional_light(light, n, v, albedo);
+        } else if light_type == 1u {
+            total_light += point_light(light, n, v, albedo, in.world_position);
+        } else if light_type == 2u {
+            total_light += spot_light(light, n, v, albedo, in.world_position);
         }
     }
 
-    let world_reflect      = reflect(-view_dir, world_normal);
-    let reflection         = textureSample(env_map, env_sampler, world_reflect).rgb;
-    let n_dot_v            = max(dot(world_normal, view_dir), 0.0);
-    let f0                 = mix(vec3<f32>(0.04), base_colour.rgb, metallic);
-    let fresnel            = fresnel_schlick(n_dot_v, f0);
-    let reflection_weight  = fresnel * pow(1.0 - roughness, 2.0) * mix(vec3<f32>(1.0), base_colour.rgb, metallic);
-    final_color           += reflection * reflection_weight;
-
-    final_color += emissive;
-
-    return vec4<f32>(final_color, base_colour.a);
+    return vec4<f32>(total_light, albedo_sample.a);
 }
\ No newline at end of file