diff --git a/Cargo.lock b/Cargo.lock index 2ac7b4b..fa94293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2464,14 +2464,6 @@ dependencies = [ "roxmltree", ] -[[package]] -name = "shaders" -version = "0.1.0" -dependencies = [ - "shaders-shared", - "spirv-std", -] - [[package]] name = "shaders-shared" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 879ac0c..e416214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,7 @@ [workspace] resolver = "2" -members = [ - "crates/rt-helper", - "crates/shaders", - "crates/shaders-shared", - "crates/vk-rs", -] +members = ["crates/rt-helper", "crates/shaders-shared", "crates/vk-rs"] [workspace.dependencies] ash = { version = "0.37.3" } diff --git a/crates/shaders-shared/src/lib.rs b/crates/shaders-shared/src/lib.rs index 00f2535..685527c 100644 --- a/crates/shaders-shared/src/lib.rs +++ b/crates/shaders-shared/src/lib.rs @@ -17,8 +17,8 @@ pub struct UniformBufferObject { pub model: Mat4, pub view: Mat4, pub proj: Mat4, - // pub camera_pos: Vec3, - // pub material: Material, + pub camera_pos: Vec3, + pub material: Material, } #[repr(C)] diff --git a/crates/shaders/Cargo.toml b/crates/shaders/Cargo.toml deleted file mode 100644 index 69ea8cf..0000000 --- a/crates/shaders/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "shaders" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["dylib"] - -[dependencies] -spirv-std.workspace = true - -shaders-shared = { path = "../shaders-shared" } diff --git a/crates/shaders/src/lib.rs b/crates/shaders/src/lib.rs deleted file mode 100644 index 06b1d2e..0000000 --- a/crates/shaders/src/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![cfg_attr(target_arch = "spirv", no_std)] - -use shaders_shared::UniformBufferObject; -use spirv_std::num_traits::Float; -use spirv_std::{ - glam::{Mat3, UVec2, Vec2, Vec3, Vec4, Vec4Swizzles}, - image::Image2d, - spirv, Sampler, -}; - -pub const PI: f32 = core::f32::consts::PI; - -fn fresnel_schlick(cos_theta: f32, f0: Vec3) -> Vec3 { - f0 + (Vec3::ONE - f0) * (1.0 - cos_theta).powf(5.0) -} - -fn distribution_ggx(n: Vec3, h: Vec3, roughness: f32) -> f32 { - let a = roughness * roughness; - let a2 = a * a; - let n_dot_h = n.dot(h).max(0.0); - let n_dot_h2 = n_dot_h * n_dot_h; - - let nom = a2; - let denom = (n_dot_h2 * (a2 - 1.0) + 1.0); - let denom = PI * denom * denom; - - nom / denom.max(0.001) -} - -fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 { - let r = roughness + 1.0; - let k = (r * r) / 8.0; - - let nom = n_dot_v; - let denom = n_dot_v * (1.0 - k) + k; - - nom / denom -} - -fn geometry_smith(n: Vec3, v: Vec3, l: Vec3, roughness: f32) -> f32 { - let n_dot_v = n.dot(v).max(0.0); - let n_dot_l = n.dot(l).max(0.0); - let ggx2 = geometry_schlick_ggx(n_dot_v, roughness); - let ggx1 = geometry_schlick_ggx(n_dot_l, roughness); - - ggx1 * ggx2 -} - -#[spirv(vertex)] -pub fn main_vs( - #[spirv(position)] in_pos: Vec3, - in_normal: Vec3, - in_tex_coord: Vec2, - #[spirv(uniform, descriptor_set = 0, binding = 0)] ubo: &UniformBufferObject, - out_world_pos: &mut Vec3, - out_world_normal: &mut Vec3, - out_tex_coord: &mut Vec2, - #[spirv(position)] out_pos: &mut Vec4, -) { - let pos = ubo.model * Vec4::from((in_pos, 1.0)); - *out_world_pos = pos.truncate(); - - // Transform normal to world space - let normal_matrix = Mat3::from_mat4(ubo.model).inverse().transpose(); - *out_world_normal = (normal_matrix * in_normal).normalize(); - - *out_pos = ubo.proj * ubo.view * pos; - *out_tex_coord = in_tex_coord; -} diff --git a/crates/vk-rs/src/renderer.rs b/crates/vk-rs/src/renderer.rs index fdaefa6..d0098a4 100644 --- a/crates/vk-rs/src/renderer.rs +++ b/crates/vk-rs/src/renderer.rs @@ -1,12 +1,12 @@ use ash::{ extensions::khr::{Surface, Swapchain}, - vk::{self, Buffer}, + vk::{self, Buffer, DescriptorType}, Device, }; use egui::Vec2; use egui_ash::EguiCommand; use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, Allocator}; -use shaders_shared::{PushConstants, UniformBufferObject}; +use shaders_shared::{Material, PushConstants, UniformBufferObject}; use spirv_std::glam::{Mat4, Vec3, Vec4}; use std::{ ffi::CString, @@ -41,84 +41,6 @@ pub struct DefaultTextures { sampler: vk::Sampler, // Common sampler for all textures } -pub struct Material { - base_color: Vec4, - metallic_factor: f32, - roughness_factor: f32, - base_color_texture: Option>, - // metallic_roughness_texture: Option>, - // normal_texture: Option>, -} - -impl Material { - fn from_gltf( - material: &gltf::Material, - device: &Device, - allocator: Arc>, - command_pool: vk::CommandPool, - queue: vk::Queue, - path: &Path, - texture_cache: &mut TextureCache, - buffers: &[gltf::buffer::Data], - ) -> Self { - let pbr = material.pbr_metallic_roughness(); - - let base_color = pbr.base_color_factor(); - let metallic_factor = pbr.metallic_factor(); - let roughness_factor = pbr.roughness_factor(); - - let base_color_texture = pbr.base_color_texture().and_then(|tex| { - let key = format!("{:?}", tex.texture().source().source()); - texture_cache.get_or_load_texture(key, || { - load_texture_from_gltf( - device, - allocator.clone(), - command_pool, - queue, - &tex.texture(), - buffers, - path, - ) - }) - }); - - // let metallic_roughness_texture = pbr.metallic_roughness_texture().and_then(|tex| { - // load_texture_from_gltf( - // device, - // allocator.clone(), - // command_pool, - // queue, - // &tex.texture(), - // buffers, - // path, - // ) - // .map(Arc::new) - // }); - // - // let normal_texture = material.normal_texture().and_then(|tex| { - // load_texture_from_gltf( - // device, - // allocator.clone(), - // command_pool, - // queue, - // &tex.texture(), - // buffers, - // path, - // ) - // .map(Arc::new) - // }); - - Self { - base_color: Vec4::from(base_color), - metallic_factor, - roughness_factor, - base_color_texture, - // metallic_roughness_texture, - // normal_texture, - } - } -} - #[repr(C)] #[derive(Debug, Clone)] struct Vertex { @@ -434,92 +356,17 @@ impl Texture { } } -impl DefaultTextures { - fn new( - device: &Device, - allocator: Arc>, - command_pool: vk::CommandPool, - queue: vk::Queue, - ) -> Self { - // Create a 1x1 white texture for default albedo - let white_data = vec![255u8, 255, 255, 255]; - let white = Texture::new( - device, - allocator.clone(), - command_pool, - queue, - 1, - 1, - &white_data, - ); - - // Create a 1x1 default metallic-roughness texture - // R: unused, G: roughness (0.5), B: metallic (0.0) - let metallic_roughness_data = vec![0u8, 128, 0, 255]; - let metallic_roughness = Texture::new( - device, - allocator.clone(), - command_pool, - queue, - 1, - 1, - &metallic_roughness_data, - ); - - // Create a 1x1 default normal map (pointing up) - let normal_data = vec![128u8, 128, 255, 255]; - let normal = Texture::new(device, allocator, command_pool, queue, 1, 1, &normal_data); - - // Create a common sampler - let sampler_create_info = vk::SamplerCreateInfo::builder() - .mag_filter(vk::Filter::LINEAR) - .min_filter(vk::Filter::LINEAR) - .address_mode_u(vk::SamplerAddressMode::REPEAT) - .address_mode_v(vk::SamplerAddressMode::REPEAT) - .address_mode_w(vk::SamplerAddressMode::REPEAT) - .anisotropy_enable(true) - .max_anisotropy(16.0) - .border_color(vk::BorderColor::INT_OPAQUE_BLACK) - .unnormalized_coordinates(false) - .compare_enable(false) - .compare_op(vk::CompareOp::ALWAYS) - .mipmap_mode(vk::SamplerMipmapMode::LINEAR) - .mip_lod_bias(0.0) - .min_lod(0.0) - .max_lod(0.0); - - let sampler = unsafe { - device - .create_sampler(&sampler_create_info, None) - .expect("Failed to create sampler") - }; - - Self { - white, - metallic_roughness, - normal, - sampler, - } - } - - fn destroy(&mut self, device: &Device, allocator: &mut Allocator) { - unsafe { - self.white.destroy(device, allocator); - self.metallic_roughness.destroy(device, allocator); - self.normal.destroy(device, allocator); - device.destroy_sampler(self.sampler, None); - } - } -} - pub struct Mesh { vertex_buffer: Buffer, vertex_buffer_allocation: Option, vertex_count: u32, transform: Mat4, - texture: Option>, - material: Material, - default_textures: DefaultTextures, + albedo_texture: Option>, + metallic_roughness_texture: Option>, + normal_texture: Option>, + metallic_factor: f32, + roughness_factor: f32, + base_color: Vec4, descriptor_sets: Vec, } @@ -539,12 +386,33 @@ impl Model { .expect("Failed to free memory"); } self.texture_cache.cleanup(device, allocator); - mesh.default_textures.destroy(device, allocator); } } } } +fn resize_texture(image: &image::DynamicImage, max_dimension: u32) -> image::DynamicImage { + let width = image.width(); + let height = image.height(); + + if width <= max_dimension && height <= max_dimension { + return image.clone(); + } + + let aspect_ratio = width as f32 / height as f32; + let (new_width, new_height) = if width > height { + let w = max_dimension; + let h = (w as f32 / aspect_ratio) as u32; + (w, h) + } else { + let h = max_dimension; + let w = (h as f32 * aspect_ratio) as u32; + (w, h) + }; + + image.resize(new_width, new_height, image::imageops::FilterType::Lanczos3) +} + fn load_texture_from_gltf( device: &Device, allocator: Arc>, @@ -557,33 +425,26 @@ fn load_texture_from_gltf( let img_data = gltf::image::Data::from_source(texture.source().source(), Some(path), buffers).ok()?; - tracing::info!( - "Original texture dimensions: {}x{}", - img_data.width, - img_data.height + tracing::info!("WIDTH: {}", img_data.width); + tracing::info!("HEIGHT: {}", img_data.height); + + // Resize texture if needed + let image = image::DynamicImage::ImageRgb8( + image::RgbImage::from_raw(img_data.width, img_data.height, img_data.pixels).unwrap(), ); - // Convert to RGB/RGBA - let pixels_rgba = if img_data.pixels.len() == (img_data.width * img_data.height * 3) as usize { - // Image is RGB, convert to RGBA - tracing::info!("Converting RGB to RGBA"); - let mut rgba_data = Vec::with_capacity((img_data.width * img_data.height * 4) as usize); - for chunk in img_data.pixels.chunks_exact(3) { - rgba_data.extend_from_slice(&[chunk[0], chunk[1], chunk[2], 255]); - } - rgba_data - } else { - // Assume it's already RGBA - img_data.pixels - }; + const MAX_TEXTURE_DIMENSION: u32 = 2048; // Adjust this value based on your needs + let resized = resize_texture(&image, MAX_TEXTURE_DIMENSION); + + let pixels_rgba = resized.to_rgba8().into_raw(); Some(Texture::new( device, allocator, command_pool, queue, - img_data.width, - img_data.height, + resized.width(), + resized.height(), &pixels_rgba, )) } @@ -650,25 +511,17 @@ fn process_node( if let Some(mesh) = node.mesh() { for primitive in mesh.primitives() { - let material = Material::from_gltf( - &primitive.material(), - device, - allocator.clone(), - command_pool, - queue, - path, - texture_cache, - buffers, - ); + let material = primitive.material(); + let pbr = material.pbr_metallic_roughness(); - let texture = primitive - .material() - .pbr_metallic_roughness() - .base_color_texture() - .and_then(|tex| { - // Create a unique key for the texture - let key = format!("{:?}", tex.texture().source().source()); - texture_cache.get_or_load_texture(key, || { + let base_color = pbr.base_color_factor(); + let metallic_factor = pbr.metallic_factor(); + let roughness_factor = pbr.roughness_factor(); + + let albedo_texture = pbr.base_color_texture().and_then(|tex| { + texture_cache.get_or_load_texture( + format!("albedo_{:?}", tex.texture().source().source()), + || { load_texture_from_gltf( device, allocator.clone(), @@ -678,8 +531,43 @@ fn process_node( buffers, path, ) - }) - }); + }, + ) + }); + + let metallic_roughness_texture = pbr.base_color_texture().and_then(|tex| { + texture_cache.get_or_load_texture( + format!("mr_{:?}", tex.texture().source().source()), + || { + load_texture_from_gltf( + device, + allocator.clone(), + command_pool, + queue, + &tex.texture(), + buffers, + path, + ) + }, + ) + }); + + let normal_texture = pbr.base_color_texture().and_then(|tex| { + texture_cache.get_or_load_texture( + format!("norm_{:?}", tex.texture().source().source()), + || { + load_texture_from_gltf( + device, + allocator.clone(), + command_pool, + queue, + &tex.texture(), + buffers, + path, + ) + }, + ) + }); let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); @@ -724,18 +612,17 @@ fn process_node( let (vertex_buffer, vertex_buffer_allocation) = create_vertex_buffer(device, allocator.clone(), command_pool, queue, &vertices); - let default_textures = - DefaultTextures::new(device, allocator.clone(), command_pool, queue); - meshes.push(Mesh { vertex_buffer, vertex_buffer_allocation: Some(vertex_buffer_allocation), vertex_count: vertices.len() as u32, - // Store identity matrix, it is not used here anymore + albedo_texture, + metallic_roughness_texture, + normal_texture, + metallic_factor, + roughness_factor, + base_color: base_color.into(), transform: Mat4::IDENTITY, - texture, - material, - default_textures, descriptor_sets: Vec::new(), }); } @@ -1109,23 +996,27 @@ impl RendererInner { swapchain_count: usize, ) -> Vec { let bindings = [ - // Binding 0: Uniform buffer vk::DescriptorSetLayoutBinding::builder() .binding(0) .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT) .build(), - // Binding 1: Texture sampler vk::DescriptorSetLayoutBinding::builder() .binding(1) - .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::FRAGMENT) .build(), vk::DescriptorSetLayoutBinding::builder() .binding(2) - .descriptor_type(vk::DescriptorType::SAMPLER) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT) + .build(), + vk::DescriptorSetLayoutBinding::builder() + .binding(3) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::FRAGMENT) .build(), @@ -1147,6 +1038,9 @@ impl RendererInner { descriptor_pool: vk::DescriptorPool, descriptor_set_layouts: &[vk::DescriptorSetLayout], uniform_buffers: &[Buffer], + allocator: Arc>, + command_pool: vk::CommandPool, + queue: vk::Queue, mesh: &Mesh, ) -> Vec { let descriptor_sets = unsafe { @@ -1159,6 +1053,17 @@ impl RendererInner { .expect("failed to allocate descriptor sets") }; + let default_texture = + create_default_texture(device, allocator.clone(), command_pool, queue); + let default_metallic_roughness = create_default_metallic_roughness_texture( + device, + allocator.clone(), + command_pool, + queue, + ); + let default_normal = + create_default_normal_texture(device, allocator.clone(), command_pool, queue); + for (index, &descriptor_set) in descriptor_sets.iter().enumerate() { let buffer_info = vk::DescriptorBufferInfo::builder() .buffer(uniform_buffers[index]) @@ -1166,43 +1071,59 @@ impl RendererInner { .range(vk::WHOLE_SIZE) .build(); - let mut descriptor_writes = vec![vk::WriteDescriptorSet::builder() - .dst_set(descriptor_set) - .dst_binding(0) - .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) - .buffer_info(std::slice::from_ref(&buffer_info)) - .build()]; + let albedo_texture = mesh.albedo_texture.as_ref().unwrap_or(&default_texture); + let albedo_info = vk::DescriptorImageInfo::builder() + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .image_view(albedo_texture.image_view) + .sampler(albedo_texture.sampler) + .build(); - if let Some(ref texture) = mesh.texture { - // Image view descriptor - let image_info = vk::DescriptorImageInfo::builder() - .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) - .image_view(texture.image_view) - .build(); + let metallic_roughness_texture = mesh + .metallic_roughness_texture + .as_ref() + .unwrap_or(&default_metallic_roughness); + let metallic_roughness_info = vk::DescriptorImageInfo::builder() + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .image_view(metallic_roughness_texture.image_view) + .sampler(metallic_roughness_texture.sampler) + .build(); - descriptor_writes.push( - vk::WriteDescriptorSet::builder() - .dst_set(descriptor_set) - .dst_binding(1) - .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE) - .image_info(std::slice::from_ref(&image_info)) - .build(), - ); + let normal_texture = mesh.normal_texture.as_ref().unwrap_or(&default_normal); + let normal_info = vk::DescriptorImageInfo::builder() + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .image_view(normal_texture.image_view) + .sampler(normal_texture.sampler) + .build(); - // Sampler descriptor - let sampler_info = vk::DescriptorImageInfo::builder() - .sampler(texture.sampler) - .build(); - - descriptor_writes.push( - vk::WriteDescriptorSet::builder() - .dst_set(descriptor_set) - .dst_binding(2) - .descriptor_type(vk::DescriptorType::SAMPLER) - .image_info(std::slice::from_ref(&sampler_info)) - .build(), - ); - } + let descriptor_writes = [ + vk::WriteDescriptorSet::builder() + .dst_set(descriptor_set) + .dst_binding(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .buffer_info(std::slice::from_ref(&buffer_info)) + .build(), + // albedo + vk::WriteDescriptorSet::builder() + .dst_set(descriptor_set) + .dst_binding(1) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(std::slice::from_ref(&albedo_info)) + .build(), + // metallic + vk::WriteDescriptorSet::builder() + .dst_set(descriptor_set) + .dst_binding(2) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(std::slice::from_ref(&metallic_roughness_info)) + .build(), + // norm + vk::WriteDescriptorSet::builder() + .dst_set(descriptor_set) + .dst_binding(3) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(std::slice::from_ref(&normal_info)) + .build(), + ]; unsafe { device.update_descriptor_sets(&descriptor_writes, &[]); @@ -1788,7 +1709,10 @@ impl RendererInner { descriptor_pool, &descriptor_set_layouts, &uniform_buffers, - mesh, + allocator.clone(), + command_pool, + queue, + &mesh, ); } @@ -2023,10 +1947,13 @@ impl RendererInner { 0.1, 1000.0, ), - // camera_pos: self.camera_position, - // metallic_factor: mesh.material.metallic_factor, - // roughness_factor: mesh.material.roughness_factor, - // base_color: mesh.material.base_color, + camera_pos: self.camera_position, + material: Material { + base_color: mesh.base_color, + metallic_factor: mesh.metallic_factor, + roughness_factor: mesh.roughness_factor, + _padding: [0.0, 0.0], + }, }; let ptr = self.uniform_buffer_allocations[self.current_frame] @@ -2224,3 +2151,57 @@ fn clamped_color(color: Vec3) -> Vec3 { color.z.clamp(0.0, 1.0), ) } + +fn create_default_texture( + device: &Device, + allocator: Arc>, + command_pool: vk::CommandPool, + queue: vk::Queue, +) -> Arc { + let white_pixel = vec![255u8, 255, 255, 255]; + Arc::new(Texture::new( + device, + allocator, + command_pool, + queue, + 1, + 1, + &white_pixel, + )) +} + +fn create_default_metallic_roughness_texture( + device: &Device, + allocator: Arc>, + command_pool: vk::CommandPool, + queue: vk::Queue, +) -> Arc { + let pixel = vec![0u8, 0, 255, 255]; // Non-metallic (0), rough (1.0) + Arc::new(Texture::new( + device, + allocator, + command_pool, + queue, + 1, + 1, + &pixel, + )) +} + +fn create_default_normal_texture( + device: &Device, + allocator: Arc>, + command_pool: vk::CommandPool, + queue: vk::Queue, +) -> Arc { + let pixel = vec![128u8, 128, 255, 255]; // Default normal pointing up + Arc::new(Texture::new( + device, + allocator, + command_pool, + queue, + 1, + 1, + &pixel, + )) +} diff --git a/shaders/main.frag b/shaders/main.frag index 87539c6..4a3e1c6 100644 --- a/shaders/main.frag +++ b/shaders/main.frag @@ -4,14 +4,131 @@ layout(location = 0) in vec3 frag_world_position; layout(location = 1) in vec3 frag_world_normal; layout(location = 2) in vec2 frag_tex_coord; -layout(set = 0, binding = 1) uniform sampler2D tex_sampler; +struct Material { + vec4 base_color; + float metallic_factor; + float roughness_factor; + vec2 _padding; +}; + +layout(set = 0, binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; + vec3 camera_pos; + float _padding; + Material material; +} ubo; + +layout(set = 0, binding = 1) uniform sampler2D albedo_map; +layout(set = 0, binding = 2) uniform sampler2D metallic_roughness_map; +layout(set = 0, binding = 3) uniform sampler2D normal_map; layout(location = 0) out vec4 out_color; -void main() { - // Sample the texture - vec4 base_color = texture(tex_sampler, frag_tex_coord); - - // Output final color - out_color = vec4(base_color.rgb, base_color.a); +const float PI = 3.14159265359; + +// PBR functions +vec3 fresnelSchlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +float DistributionGGX(vec3 N, vec3 H, float roughness) { + float a = roughness*roughness; + float a2 = a*a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH*NdotH; + + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return nom / max(denom, 0.0000001); +} + +float GeometrySchlickGGX(float NdotV, float roughness) { + float r = (roughness + 1.0); + float k = (r*r) / 8.0; + + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return nom / denom; +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +void main() { + // Sample textures + vec4 albedo = texture(albedo_map, frag_tex_coord); + vec2 metallic_roughness = texture(metallic_roughness_map, frag_tex_coord).bg; + vec3 normal = normalize(2.0 * texture(normal_map, frag_tex_coord).rgb - 1.0); + + float metallic = metallic_roughness.x * ubo.material.metallic_factor; + float roughness = metallic_roughness.y * ubo.material.roughness_factor; + + vec3 N = normalize(normal); + vec3 V = normalize(ubo.camera_pos - frag_world_position); + + // Calculate reflectance at normal incidence + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo.rgb, metallic); + + // Light parameters + vec3 light_positions[4] = vec3[]( + vec3(5.0, 5.0, 5.0), + vec3(-5.0, 5.0, 5.0), + vec3(5.0, -5.0, 5.0), + vec3(-5.0, -5.0, 5.0) + ); + vec3 light_colors[4] = vec3[]( + vec3(23.47, 21.31, 20.79), + vec3(23.47, 21.31, 20.79), + vec3(23.47, 21.31, 20.79), + vec3(23.47, 21.31, 20.79) + ); + + // Reflectance equation + vec3 Lo = vec3(0.0); + for(int i = 0; i < 4; ++i) { + vec3 L = normalize(light_positions[i] - frag_world_position); + vec3 H = normalize(V + L); + float distance = length(light_positions[i] - frag_world_position); + float attenuation = 1.0 / (distance * distance); + vec3 radiance = light_colors[i] * attenuation; + + // Cook-Torrance BRDF + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0); + + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); + vec3 specular = numerator / max(denominator, 0.001); + + vec3 kS = F; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - metallic; + + float NdotL = max(dot(N, L), 0.0); + + Lo += (kD * albedo.rgb / PI + specular) * radiance * NdotL; + } + + vec3 ambient = vec3(0.03) * albedo.rgb; + vec3 color = ambient + Lo; + + // HDR tonemapping + color = color / (color + vec3(1.0)); + // gamma correction + color = pow(color, vec3(1.0/2.2)); + + out_color = vec4(color, albedo.a); }