diff --git a/engine/BUILD b/engine/BUILD index 7f8b4fc..551d907 100644 --- a/engine/BUILD +++ b/engine/BUILD @@ -13,6 +13,7 @@ rust_binary( "src/main.rs", "src/physics/mod.rs", "src/physics/color.rs", + "src/render/light.rs", "src/render/material.rs", "src/render/mesh.rs", "src/render/mod.rs", diff --git a/engine/shaders/forward.frag b/engine/shaders/forward.frag index 6e95c76..887dd0c 100644 --- a/engine/shaders/forward.frag +++ b/engine/shaders/forward.frag @@ -2,11 +2,197 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable +struct OmniLight { + vec4 pos; + vec4 color; +}; + +layout(push_constant) uniform PushConstantObject { + mat4 view; + vec4 cameraPos; + OmniLight omniLights[3]; +} pco; + + layout(binding = 0) uniform sampler2D texSamplerDiffuse; layout(binding = 1) uniform sampler2D texSamplerRoughness; + layout(location = 0) in vec2 fragTexCoord; +layout(location = 1) in vec3 fragWorldPos; +layout(location = 2) in vec3 fragNormal; + layout(location = 0) out vec4 outColor; -void main() { - outColor = texture(texSamplerDiffuse, fragTexCoord) * texture(texSamplerRoughness, fragTexCoord).x; +const float PI = 3.14159; + +// We implement a Lambertiand & Cook-Torrance BRDF-based lighting system. +// All of this is based on a number of scientific papers, meta-studies and modern sources. We do +// our best to cite as much as possible for future reference. +// Most of the maths is used straight from [Kar13]. +// +// A good summary of different research is available this blog post by Brian Karis, that attempts +// to catalogue all existing BRDF-related functions: +// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html + +/// Bibliography: +// +// [Bec63] +// P. Beckmann & A. Spizzichino. 1963. "The Scattering of Electromagnetic Waves from Rough Surfaces" +// MacMillan, New York +// +// [Smi67] +// Bruce Smith. 1967. "Geometrical shadowing of a random rough surface." +// IEEE transactions on antennas and propagation 15.5 (1967): 668-671. +// +// [CT82] +// Robert L. Cook, Kenneth E. Torrance. 1982. "A Reflectance Model for Computer Graphics" +// ACM Transactions on Graphics, 1(1), 7–24. +// doi: 10.1145/357290.357293 +// +// [Sch94] +// Christophe Schlick. 1994. "An Inexpensive BRDF Model for Physically-based Rendering" +// Computer Graphics Forum, 13(3), 233–246. +// doi: 10.1111/1467-8659.1330233 +// +// [Wa07] +// Bruce Walter et al. 2007. "Microfacet Models for Refraction through Rough Surfaces." +// Proceedings of the Eurographics Symposium on Rendering. +// +// [Bur12] +// Brent Burley. 2012. "Physically-Based Shading at Disney" +// URL: https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf +// +// [Kar13] +// Brian Karis. 2013. "Real Shading in Unreal Engine 4" +// URL: https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf +// +// [Hei14] +// Eric Heitz. 2014. "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" +// Journal of Computer Graphics Techniques, 3 (2). +// +// [GA19] +// Romain Guy, Mathias Agopian, "Physically Based Rendering in Filament" +// URL: https://google.github.io/filament/Filament.html + +// [Sch94] Fresnel approximation, used for F in Cook-Torrance BRDF. +vec3 FresnelSchlick(float HdotV, vec3 F0) { + return F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0); +} + +// Microfacet Normal Distribution Function, used for D in Cook-Torrance BRDF. +float DistributionGGX(float NdotH, float roughness) { + // 'Roughness remapping' as per [Bur12] + float a = roughness * roughness; + + // NDF from [Kar13], that cites [Bur12], which in turn cites [Wa07]. + // However, I could not find the same equation form in [Bur12] or deduce it myself from [Wa07], + // and ended up taking the direct, untraceable form from [Kar13], so take this with a grain of salt. + float a2 = a * a; + float NdotH2 = NdotH * NdotH; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + return (a * a) / (PI * denom * denom); +} + + +float GeometrySchlickGGX(float NdotV, float roughness) { + // Remapping of K for analytical (non-IBL) lighting per [Kar13]. + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + + // [Sch94] approximation of [Smi67] equation for [Bec63]. + return (NdotV) / (NdotV * (1.0 - k) + k); +} + +// Geometric shadowing function, used for G in Cook-Torrance BRDF. +float GeometrySmith(float NdotV, float NdotL, float roughness) { + // Smith geometric shadowing function. + // [GA19] cites [Hei14] as demonstrating [Smi97] to be correct. + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + return ggx1 * ggx2; +} + +// Cook-Torrance [CT82] specular model. +vec3 SpecularCookTorrance(float NdotH, float NdotV, float NdotL, vec3 F, float roughness) { + float NDF = DistributionGGX(NdotH, roughness); + float G = GeometrySmith(NdotV, NdotL, roughness); + // F is taken in as a pre-computed argument for optimization purposes (it's reused for the + // lambertian component of the lighting model). + + // Form from [Kar13], decuced from [CT82]. + vec3 specular = (NDF * G * F) / max((4.0 * NdotV * NdotL), 0.0001); + return specular; +} + + +void main() { + vec3 cameraPos = pco.cameraPos.xyz / pco.cameraPos.w; + + // Normal of this fragment. + vec3 N = normalize(fragNormal); + // Unit vector pointing at camera from this fragment. + vec3 V = normalize(cameraPos - fragWorldPos); + + // Texture parameters for this fragment. + vec3 albedo = texture(texSamplerDiffuse, fragTexCoord).xyz; + float roughness = texture(texSamplerRoughness, fragTexCoord).x; + float metallic = 0.0; + float dielectric = 1.0 - metallic; + + // Absolute Specular Reflectance at normal incidence. Ie., the base reflectivity of a + // material when looking straight at it. + // Trick from https://learnopengl.com/PBR/Theory : encode the reflectivity in the albedo for + // metallic materials (as they have no albedo). Otherwise, default to a typical reflectivity + // for non-metallic (dielectric) materials (0.04). + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo, metallic); + + // Luminance of this fragment. + // Luminance is defined as the sum (integral) of all ilncoming illuminance over the half-sphere + // 'above' that point. As we currently only support analytic lighting (ie. omni lights), we + // integrate by iterating over all luminance sources, that currently are point lights. + vec3 Lo = vec3(0.0); + for (int i = 0; i < 3; ++i) { + vec3 lightPos = pco.omniLights[i].pos.xyz; + vec3 lightColor = pco.omniLights[i].color.xyz; + + // Unit vector pointing at light from fragment. + vec3 L = normalize(lightPos - fragWorldPos); + // Half-vector between to-light and to-camera unit vectors. + vec3 H = normalize(V + L); + + // Dot products re-used across further computation for this (fragment, light) pair. + float HdotV = max(dot(H, V), 0.0); + float NdotH = max(dot(N, H), 0.0); + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + + // Translate luminous flux (lumen) into luminous intensity at this solid angle (candela). + // This follows the derivation in [GA19] (58). + float distance = length(lightPos - fragWorldPos); + vec3 intensity = (lightColor / (4 * PI * (distance * distance))); + + // The Fresnel component from the Cook-Torrance specular BRDF is also used to calculate the + // lambertian diffuse weight kD. We calculate it outside of the function. + vec3 F = FresnelSchlick(HdotV, F0); + // Cook-Torrance specular value. + vec3 specular = SpecularCookTorrance(NdotH, NdotV, NdotL, F, roughness); + + // Lambertian diffuse component, influenced by fresnel and dielectric/metalness. + vec3 kD = (vec3(1.0) - F) * dielectric; + // Lambertian diffuse value. + vec3 diffuse = albedo / PI; + + // Illuminance for this point from this light is a result of scaling the luminous + // intensity of this light by the BRDL and by (N o L). This follows the definitions + // of illuminance and luminous intensity. + vec3 Li = (kD * diffuse + specular) * intensity * NdotL; + + // Integration of luminance from illuminance. + Lo += Li; + } + + vec3 ambient = vec3(0.03) * albedo; + vec3 color = ambient + Lo; + outColor = vec4(color, 1.0); } diff --git a/engine/shaders/forward.vert b/engine/shaders/forward.vert index e95d14c..fec4f98 100644 --- a/engine/shaders/forward.vert +++ b/engine/shaders/forward.vert @@ -1,25 +1,38 @@ // vim: set ft=glsl: #version 450 +struct OmniLight { + vec3 pos; + vec3 color; +}; + layout(push_constant) uniform UniformBufferObject { mat4 view; + vec3 cameraPos; + OmniLight omniLights[2]; } ubo; layout(location = 0) in vec3 pos; -layout(location = 1) in mat4 model; -layout(location = 5) in vec2 tex; +layout(location = 1) in vec3 normal; +layout(location = 2) in mat4 model; +layout(location = 6) in vec2 tex; layout(location = 0) out vec2 fragTexCoord; +layout(location = 1) out vec3 fragWorldPos; +layout(location = 2) out vec3 fragNormal; out gl_PerVertex { vec4 gl_Position; }; void main() { - gl_Position = ubo.view * model * vec4(pos, 1.0); + vec4 world = model * vec4(pos, 1.0); fragTexCoord = tex; + fragNormal = normal; + fragWorldPos = world.xyz / world.w; + gl_Position = ubo.view * world; // Vulkanize gl_Position.y = -gl_Position.y; } diff --git a/engine/src/main.rs b/engine/src/main.rs index 8f9486a..c0b82df 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -10,9 +10,10 @@ mod util; mod physics; use render::vulkan::data; +use render::light::Omni; use render::material::{Texture, PBRMaterialBuilder}; use render::mesh::Mesh; -use render::renderable::{Object, Renderable, ResourceManager}; +use render::renderable::{Light, Object, Renderable, ResourceManager}; use physics::color; fn main() { @@ -23,35 +24,35 @@ fn main() { let mesh = { let vertices = Arc::new(vec![ - data::Vertex::new([-0.5, -0.5, 0.5], [1.0, 1.0, 1.0], [1.0, 0.0]), - data::Vertex::new([0.5, -0.5, 0.5], [1.0, 1.0, 0.0], [0.0, 0.0]), - data::Vertex::new([0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [0.0, 1.0]), - data::Vertex::new([-0.5, 0.5, 0.5], [1.0, 0.0, 1.0], [1.0, 1.0]), + data::Vertex::new([-0.5, -0.5, 0.5], [ 0.0, 0.0, 1.0], [1.0, 0.0]), + data::Vertex::new([ 0.5, -0.5, 0.5], [ 0.0, 0.0, 1.0], [0.0, 0.0]), + data::Vertex::new([ 0.5, 0.5, 0.5], [ 0.0, 0.0, 1.0], [0.0, 1.0]), + data::Vertex::new([-0.5, 0.5, 0.5], [ 0.0, 0.0, 1.0], [1.0, 1.0]), - data::Vertex::new([0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [0.0, 1.0]), - data::Vertex::new([0.5, 0.5, -0.5], [1.0, 1.0, 0.0], [1.0, 1.0]), - data::Vertex::new([0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [1.0, 0.0]), - data::Vertex::new([0.5, -0.5, 0.5], [1.0, 0.0, 1.0], [0.0, 0.0]), + data::Vertex::new([ 0.5, -0.5, -0.5], [ 1.0, 0.0, 0.0], [0.0, 1.0]), + data::Vertex::new([ 0.5, 0.5, -0.5], [ 1.0, 0.0, 0.0], [1.0, 1.0]), + data::Vertex::new([ 0.5, 0.5, 0.5], [ 1.0, 0.0, 0.0], [1.0, 0.0]), + data::Vertex::new([ 0.5, -0.5, 0.5], [ 1.0, 0.0, 0.0], [0.0, 0.0]), - data::Vertex::new([-0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [1.0, 1.0]), - data::Vertex::new([-0.5, 0.5, -0.5], [1.0, 1.0, 0.0], [0.0, 1.0]), - data::Vertex::new([-0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [0.0, 0.0]), - data::Vertex::new([-0.5, -0.5, 0.5], [1.0, 0.0, 1.0], [1.0, 0.0]), + data::Vertex::new([-0.5, -0.5, -0.5], [-1.0, 0.0, 0.0], [1.0, 1.0]), + data::Vertex::new([-0.5, 0.5, -0.5], [-1.0, 0.0, 0.0], [0.0, 1.0]), + data::Vertex::new([-0.5, 0.5, 0.5], [-1.0, 0.0, 0.0], [0.0, 0.0]), + data::Vertex::new([-0.5, -0.5, 0.5], [-1.0, 0.0, 0.0], [1.0, 0.0]), - data::Vertex::new([-0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [0.0, 1.0]), - data::Vertex::new([0.5, -0.5, -0.5], [1.0, 1.0, 0.0], [1.0, 1.0]), - data::Vertex::new([0.5, -0.5, 0.5], [0.0, 1.0, 1.0], [1.0, 0.0]), - data::Vertex::new([-0.5, -0.5, 0.5], [1.0, 0.0, 1.0], [0.0, 0.0]), + data::Vertex::new([-0.5, -0.5, -0.5], [ 0.0, -1.0, 0.0], [0.0, 1.0]), + data::Vertex::new([ 0.5, -0.5, -0.5], [ 0.0, -1.0, 0.0], [1.0, 1.0]), + data::Vertex::new([ 0.5, -0.5, 0.5], [ 0.0, -1.0, 0.0], [1.0, 0.0]), + data::Vertex::new([-0.5, -0.5, 0.5], [ 0.0, -1.0, 0.0], [0.0, 0.0]), - data::Vertex::new([-0.5, 0.5, -0.5], [1.0, 1.0, 1.0], [1.0, 1.0]), - data::Vertex::new([0.5, 0.5, -0.5], [1.0, 1.0, 0.0], [0.0, 1.0]), - data::Vertex::new([0.5, 0.5, 0.5], [0.0, 1.0, 1.0], [0.0, 0.0]), - data::Vertex::new([-0.5, 0.5, 0.5], [1.0, 0.0, 1.0], [1.0, 0.0]), + data::Vertex::new([-0.5, 0.5, -0.5], [ 0.0, 1.0, 0.0], [1.0, 1.0]), + data::Vertex::new([ 0.5, 0.5, -0.5], [ 0.0, 1.0, 0.0], [0.0, 1.0]), + data::Vertex::new([ 0.5, 0.5, 0.5], [ 0.0, 1.0, 0.0], [0.0, 0.0]), + data::Vertex::new([-0.5, 0.5, 0.5], [ 0.0, 1.0, 0.0], [1.0, 0.0]), - data::Vertex::new([-0.5, -0.5, -0.5], [1.0, 1.0, 1.0], [0.0, 0.0]), - data::Vertex::new([0.5, -0.5, -0.5], [1.0, 1.0, 0.0], [1.0, 0.0]), - data::Vertex::new([0.5, 0.5, -0.5], [0.0, 1.0, 1.0], [1.0, 1.0]), - data::Vertex::new([-0.5, 0.5, -0.5], [1.0, 0.0, 1.0], [0.0, 1.0]), + data::Vertex::new([-0.5, -0.5, -0.5], [ 0.0, 0.0, -1.0], [0.0, 0.0]), + data::Vertex::new([ 0.5, -0.5, -0.5], [ 0.0, 0.0, -1.0], [1.0, 0.0]), + data::Vertex::new([ 0.5, 0.5, -0.5], [ 0.0, 0.0, -1.0], [1.0, 1.0]), + data::Vertex::new([-0.5, 0.5, -0.5], [ 0.0, 0.0, -1.0], [0.0, 1.0]), ]); let indices = Arc::new(vec![ 0, 1, 2, 2, 3, 0, @@ -87,7 +88,13 @@ fn main() { } } - let renderables: Vec> = cubes.into_iter().map(|e| e as Box).collect(); + let light1 = rm.add_light(Omni::test(cgm::Vector3::new(-10.0, -10.0, -5.0))); + let light2 = rm.add_light(Omni::test(cgm::Vector3::new(10.0, 10.0, -5.0))); + + + let mut renderables: Vec> = cubes.into_iter().map(|e| e as Box).collect(); + renderables.push(Box::new(Light{ light: light1 })); + renderables.push(Box::new(Light{ light: light2 })); let start = time::Instant::now(); let mut renderer = render::Renderer::initialize(); @@ -97,17 +104,29 @@ fn main() { let position = (instant / 10.0) * 3.14 * 2.0; + let camera = cgm::Point3::new( + //position.cos() * 10.0 * (((position*2.0).cos()/2.0)+1.0), + //position.sin() * 10.0 * (((position*2.0).cos()/2.0)+1.0), + 7.0 + (position / 4.0).sin(), + 12.0 + (position / 4.0).cos(), + 3.0 + ); + + if let Some(light) = rm.light_mut(&light1) { + light.position = cgm::Vector3::new( + -0.0 + (position*3.0).sin() * 4.0, + -0.0 + (position*4.0).cos() * 4.0, + -0.0 + (position*2.0).sin() * 1.0, + ); + } + let view = cgm::Matrix4::look_at( - cgm::Point3::new( - position.cos() * 10.0 * (((position*2.0).cos()/2.0)+1.0), - position.sin() * 10.0 * (((position*2.0).cos()/2.0)+1.0), - 3.0 - ), + camera.clone(), cgm::Point3::new(0.0, 0.0, 0.0), cgm::Vector3::new(0.0, 0.0, 1.0) ); - renderer.draw_frame(&view, &rm, &renderables); + renderer.draw_frame(&camera, &view, &rm, &renderables); if renderer.poll_close() { return; } diff --git a/engine/src/render/light.rs b/engine/src/render/light.rs new file mode 100644 index 0000000..5503d7b --- /dev/null +++ b/engine/src/render/light.rs @@ -0,0 +1,43 @@ +use std::time; + +use cgmath as cgm; + +use crate::physics::color; +use crate::render::vulkan::data; + +/// An Omni point light, with position in 3d space, and 'color' defined in lumens per CIE XYZ +/// color channel. +pub struct Omni { + pub position: cgm::Vector3, + /// Luminour power/flux defined as lumens per XYZ color channel. + pub color: color::XYZ, + + pub id: u64, +} + +impl Omni { + /// Make a test light. This has... a color. It's kinda yellow. And something close to 650 + /// lumens of luminous power. + // TODO(q3k): implement [Kry85] (eq. 68) somewhere in //engine/src/physics for generation + // of nice lights colours from color temperature. + // + // [Kry85] + // M. Krystek. 1985. "An algorithm to calculate correlated color temperature" + // Color Research & Application, 10 (1), 38–40. + pub fn test(position: cgm::Vector3) -> Self { + Self { + position, + color: color::XYZ::new(234.7, 214.1, 207.9), + // TODO: use a better method + id: time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos() as u64, + } + } + + pub fn vulkan_uniform(&self) -> data::OmniLight { + // TODO: cache this? + data::OmniLight { + pos: [self.position.x, self.position.y, self.position.z, 1.0], + color: [self.color.x, self.color.y, self.color.z, 0.0], + } + } +} diff --git a/engine/src/render/mod.rs b/engine/src/render/mod.rs index 031229a..171c483 100644 --- a/engine/src/render/mod.rs +++ b/engine/src/render/mod.rs @@ -15,10 +15,11 @@ use vulkano_win::VkSurfaceBuild; use vulkano::instance as vi; use vulkano::swapchain as vs; -pub mod vulkan; +pub mod light; pub mod material; pub mod mesh; pub mod renderable; +pub mod vulkan; const WIDTH: u32 = 800; const HEIGHT: u32 = 600; @@ -52,11 +53,12 @@ impl Renderer { pub fn draw_frame( &mut self, + camera: &cgm::Point3, view: &cgm::Matrix4, rm: &renderable::ResourceManager, renderables: &Vec> ) { - self.instance.flip(view, rm, renderables); + self.instance.flip(camera, view, rm, renderables); } pub fn poll_close(&mut self) -> bool { diff --git a/engine/src/render/renderable.rs b/engine/src/render/renderable.rs index 1609d2b..ce442e3 100644 --- a/engine/src/render/renderable.rs +++ b/engine/src/render/renderable.rs @@ -3,18 +3,31 @@ use std::hash; use cgmath as cgm; +use crate::render::light::Omni; use crate::render::material::Material; use crate::render::mesh::Mesh; pub struct ResourceManager { meshes: HashMap, materials: HashMap, + lights: HashMap, } #[derive(Copy, Clone)] pub enum ResourceID { Material(u64), Mesh(u64), + Light(u64), +} + +impl ResourceID { + pub fn id(&self) -> u64 { + match self { + ResourceID::Material(i) => *i, + ResourceID::Mesh(i) => *i, + ResourceID::Light(i) => *i, + } + } } impl hash::Hash for ResourceID { @@ -22,21 +35,14 @@ impl hash::Hash for ResourceID { match self { ResourceID::Material(i) => i.hash(state), ResourceID::Mesh(i) => i.hash(state), + ResourceID::Light(i) => i.hash(state), } } } impl PartialEq for ResourceID { fn eq(&self, other: &Self) -> bool { - let this = match self { - ResourceID::Material(i) => i, - ResourceID::Mesh(i) => i, - }; - let that = match other { - ResourceID::Material(i) => i, - ResourceID::Mesh(i) => i, - }; - this == that + self.id() == other.id() } } @@ -47,6 +53,7 @@ impl<'a> ResourceManager { Self { meshes: HashMap::new(), materials: HashMap::new(), + lights: HashMap::new(), } } @@ -62,6 +69,12 @@ impl<'a> ResourceManager { ResourceID::Mesh(id) } + pub fn add_light(&mut self, t: Omni) -> ResourceID { + let id = t.id; + self.lights.insert(id, t); + ResourceID::Light(id) + } + pub fn material(&'a self, id: &ResourceID) -> Option<&'a Material> { if let ResourceID::Material(i) = id { return Some(self.materials.get(&i).unwrap()); @@ -75,12 +88,29 @@ impl<'a> ResourceManager { } return None } + + pub fn light(&'a self, id: &ResourceID) -> Option<&'a Omni> { + if let ResourceID::Light(i) = id { + return Some(self.lights.get(&i).unwrap()); + } + return None + } + + pub fn light_mut(&'a mut self, id: &ResourceID) -> Option<&'a mut Omni> { + if let ResourceID::Light(i) = id { + return Some(self.lights.get_mut(&i).unwrap()); + } + return None + } } pub trait Renderable { fn render_data(&self) -> Option<(ResourceID, ResourceID, &cgm::Matrix4)> { None } + fn light_data(&self) -> Option { + None + } } pub struct Object { @@ -95,3 +125,12 @@ impl Renderable for Object { } } +pub struct Light { + pub light: ResourceID, +} + +impl Renderable for Light { + fn light_data(&self) -> Option { + Some(self.light) + } +} diff --git a/engine/src/render/vulkan/data.rs b/engine/src/render/vulkan/data.rs index 61aae9c..6c7795c 100644 --- a/engine/src/render/vulkan/data.rs +++ b/engine/src/render/vulkan/data.rs @@ -9,18 +9,18 @@ use cgmath as cgm; #[derive(Default, Copy, Clone)] pub struct Vertex { pos: [f32; 3], - color: [f32; 3], + normal: [f32; 3], tex: [f32; 2], } impl Vertex { - pub fn new(pos: [f32; 3], color: [f32; 3], tex: [f32; 2]) -> Self { + pub fn new(pos: [f32; 3], normal: [f32; 3], tex: [f32; 2]) -> Self { Self { - pos, color, tex, + pos, normal, tex, } } } -vulkano::impl_vertex!(Vertex, pos, color, tex); +vulkano::impl_vertex!(Vertex, pos, normal, tex); #[derive(Default, Copy, Clone)] pub struct Instance { @@ -37,9 +37,17 @@ impl Instance { } vulkano::impl_vertex!(Instance, model); -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] +pub struct OmniLight { + pub pos: [f32; 4], + pub color: [f32; 4], +} + +#[derive(Copy, Clone, Debug)] pub struct UniformBufferObject { pub view: cgm::Matrix4, + pub camera_pos: cgm::Vector4, + pub omni_lights: [OmniLight; 3], } #[derive(Clone)] diff --git a/engine/src/render/vulkan/mod.rs b/engine/src/render/vulkan/mod.rs index 410473c..a2aa8a5 100644 --- a/engine/src/render/vulkan/mod.rs +++ b/engine/src/render/vulkan/mod.rs @@ -148,6 +148,7 @@ impl Instance { fn make_graphics_commands( &mut self, profiler: &mut Profiler, + camera: &cgm::Point3, view: &cgm::Matrix4, rm: &renderable::ResourceManager, renderables: &Vec>, @@ -160,31 +161,43 @@ impl Instance { 0.1, 1000.0 ); - let mut buffers: Vec> = vec![]; - - - // Sort renderables by mesh and materialid. - let mut meshes: ResourceMap<(renderable::ResourceID, renderable::ResourceID), &cgm::Matrix4> = ResourceMap::new(); - - let ubo = data::UniformBufferObject { - view: proj * view, - }; - profiler.end("mgc.prep"); + + + // Sort renderables by mesh and materialid, and find lights. + let mut meshes: ResourceMap<(renderable::ResourceID, renderable::ResourceID), &cgm::Matrix4> = ResourceMap::new(); + let mut omni_lights = [data::OmniLight{ pos: [0.0, 0.0, 0.0, 0.0], color: [0.0, 0.0, 0.0, 0.0]}; 3]; + let mut omni_light_count = 0; + for r in renderables { if let Some((mesh_id, material_id, transform)) = r.render_data() { meshes.add((mesh_id, material_id), transform); } + if let Some(light_id) = r.light_data() { + if omni_light_count < 3 { + let light = rm.light(&light_id).unwrap(); + omni_lights[omni_light_count] = light.vulkan_uniform(); + omni_light_count += 1; + } + } } profiler.end("mgc.sort"); + let device = self.surface_binding().device.clone(); let queue = self.surface_binding().graphics_queue.clone(); let rp = self.swapchain_binding().render_pass.clone(); let pipeline = self.pipeline.as_ref().unwrap().get_pipeline().clone(); + let camera_pos = camera.to_homogeneous(); + let ubo = data::UniformBufferObject { + camera_pos: camera_pos, + view: proj * view, + omni_lights, + }; + for ((mesh_id, material_id), transforms) in meshes.resources { let mesh = rm.mesh(&mesh_id).unwrap(); let material = rm.material(&material_id).unwrap(); @@ -240,6 +253,7 @@ impl Instance { // (╯°□°)╯︵ ┻━┻ pub fn flip( &mut self, + camera: &cgm::Point3, view: &cgm::Matrix4, rm: &renderable::ResourceManager, renderables: &Vec>, @@ -247,7 +261,7 @@ impl Instance { let mut profiler = Profiler::new(); // Build batch command buffer as early as possible. - let mut batches = self.make_graphics_commands(&mut profiler, view, rm, renderables); + let mut batches = self.make_graphics_commands(&mut profiler, camera, view, rm, renderables); profiler.end("mgc"); match &self.previous_frame_end { @@ -258,7 +272,7 @@ impl Instance { if !self.armed { self.arm(); // Rearming means the batch is invalid - rebuild it. - batches = self.make_graphics_commands(&mut profiler, view, rm, renderables); + batches = self.make_graphics_commands(&mut profiler, camera, view, rm, renderables); } profiler.end("arm"); diff --git a/engine/src/render/vulkan/pipeline_forward.rs b/engine/src/render/vulkan/pipeline_forward.rs index de40f79..8fa30bb 100644 --- a/engine/src/render/vulkan/pipeline_forward.rs +++ b/engine/src/render/vulkan/pipeline_forward.rs @@ -37,11 +37,15 @@ impl Forward { name: Some(Cow::Borrowed("pos")), }, vps::ShaderInterfaceDefEntry { - location: 1..5, format: Format::R32G32B32A32Sfloat, + location: 1..2, format: Format::R32G32B32Sfloat, + name: Some(Cow::Borrowed("normal")), + }, + vps::ShaderInterfaceDefEntry { + location: 2..6, format: Format::R32G32B32A32Sfloat, name: Some(Cow::Borrowed("model")), }, vps::ShaderInterfaceDefEntry { - location: 5..6, format: Format::R32G32Sfloat, + location: 6..7, format: Format::R32G32Sfloat, name: Some(Cow::Borrowed("tex")), }, ], @@ -50,14 +54,23 @@ impl Forward { location: 0..1, format: Format::R32G32Sfloat, name: Some(Cow::Borrowed("fragTexCoord")), }, + vps::ShaderInterfaceDefEntry { + location: 1..2, format: Format::R32G32B32Sfloat, + name: Some(Cow::Borrowed("fragWorldPos")), + }, + vps::ShaderInterfaceDefEntry { + location: 2..3, format: Format::R32G32B32Sfloat, + name: Some(Cow::Borrowed("fragNormal")), + }, ], uniforms: vec![], push_constants: vec![ vdp::PipelineLayoutDescPcRange { offset: 0, - size: 64usize, + size: 128usize, stages: vdD::ShaderStages { vertex: true, + fragment: true, ..vdD::ShaderStages::none() }, }, @@ -72,6 +85,14 @@ impl Forward { location: 0..1, format: Format::R32G32Sfloat, name: Some(Cow::Borrowed("fragTexCoord")), }, + vps::ShaderInterfaceDefEntry { + location: 1..2, format: Format::R32G32B32Sfloat, + name: Some(Cow::Borrowed("fragWorldPos")), + }, + vps::ShaderInterfaceDefEntry { + location: 2..3, format: Format::R32G32B32Sfloat, + name: Some(Cow::Borrowed("fragNormal")), + }, ], outputs: vec![ vps::ShaderInterfaceDefEntry { @@ -111,7 +132,17 @@ impl Forward { }, }, ], - push_constants: vec![], + push_constants: vec![ + vdp::PipelineLayoutDescPcRange { + offset: 0, + size: 128usize, + stages: vdD::ShaderStages { + vertex: true, + fragment: true, + ..vdD::ShaderStages::none() + }, + }, + ], }.load_into(device.clone()).expect("could not load fragment shader"); let dimensions = [viewport_dimensions[0] as f32, viewport_dimensions[1] as f32];