From c230f5aaea3df46ae9a4d7c1c9761e55ef827b82 Mon Sep 17 00:00:00 2001 From: HampusM Date: Mon, 27 Nov 2023 20:02:08 +0100 Subject: feat(engine): add lighting maps --- Cargo.lock | 7 ++++ engine/Cargo.toml | 1 + engine/fragment-color.glsl | 28 -------------- engine/fragment-texture.glsl | 31 --------------- engine/fragment.glsl | 34 ++++++++++++++++ engine/light.glsl | 19 ++++----- engine/src/lib.rs | 15 +++++--- engine/src/material.rs | 53 +++++++++++++------------ engine/src/object.rs | 92 +++++++++++++++++++------------------------- engine/src/opengl/shader.rs | 14 ++++++- engine/src/opengl/texture.rs | 42 ++++++++++++++++++++ engine/src/renderer/mod.rs | 42 +++++++++++++------- engine/src/texture.rs | 58 +++++++++++++++++++++++++++- 13 files changed, 268 insertions(+), 168 deletions(-) delete mode 100644 engine/fragment-color.glsl delete mode 100644 engine/fragment-texture.glsl create mode 100644 engine/fragment.glsl diff --git a/Cargo.lock b/Cargo.lock index 1d25105..93ec3d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,7 @@ dependencies = [ "gl", "glfw", "image", + "seq-macro", "thiserror", "tracing", ] @@ -410,6 +411,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "seq-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" + [[package]] name = "sharded-slab" version = "0.1.7" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index db30fff..b33b2d6 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -13,6 +13,7 @@ gl = "0.14.0" bitflags = "2.4.0" cstr = "0.2.11" tracing = { version = "0.1.39", optional = true } +seq-macro = "0.3.5" [dependencies.image] version = "0.24.7" diff --git a/engine/fragment-color.glsl b/engine/fragment-color.glsl deleted file mode 100644 index 4213860..0000000 --- a/engine/fragment-color.glsl +++ /dev/null @@ -1,28 +0,0 @@ -#version 330 core - -#preinclude "light.glsl" - -out vec4 FragColor; - -in vec3 in_frag_color; -in vec3 in_frag_pos; -in vec3 in_normal; - -uniform vec3 view_pos; - -void main() -{ - vec3 ambient_light = calc_ambient_light(); - - vec3 light_direction = normalize(light.position - in_frag_pos); - vec3 norm = normalize(in_normal); - - vec3 diffuse_light = calc_diffuse_light(light_direction, norm); - - vec3 specular_light = - calc_specular_light(light_direction, norm, view_pos, in_frag_pos); - - FragColor = - vec4((ambient_light + diffuse_light + specular_light) * in_frag_color, 1.0); -} - diff --git a/engine/fragment-texture.glsl b/engine/fragment-texture.glsl deleted file mode 100644 index 69718a6..0000000 --- a/engine/fragment-texture.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#version 330 core - -#preinclude "light.glsl" - -out vec4 FragColor; - -in vec2 in_texture_coords; - -in vec3 in_frag_pos; -in vec3 in_normal; - -uniform vec3 view_pos; - -uniform sampler2D input_texture; - -void main() -{ - vec3 ambient_light = calc_ambient_light(); - - vec3 light_direction = normalize(light.position - in_frag_pos); - vec3 norm = normalize(in_normal); - - vec3 diffuse_light = calc_diffuse_light(light_direction, norm); - - vec3 specular_light = - calc_specular_light(light_direction, norm, view_pos, in_frag_pos); - - vec4 light = vec4((ambient_light + diffuse_light + specular_light), 1.0); - - FragColor = light * texture(input_texture, in_texture_coords); -} diff --git a/engine/fragment.glsl b/engine/fragment.glsl new file mode 100644 index 0000000..428e87f --- /dev/null +++ b/engine/fragment.glsl @@ -0,0 +1,34 @@ +#version 330 core + +#preinclude "light.glsl" + +out vec4 FragColor; + +in vec2 in_texture_coords; + +in vec3 in_frag_pos; +in vec3 in_normal; + +uniform vec3 view_pos; + +uniform sampler2D input_texture; + +void main() +{ + vec3 ambient_light = calc_ambient_light(in_texture_coords); + + vec3 light_direction = normalize(light.position - in_frag_pos); + vec3 norm = normalize(in_normal); + + vec3 diffuse_light = calc_diffuse_light(light_direction, norm, in_texture_coords); + + vec3 specular_light = calc_specular_light( + light_direction, + norm, + view_pos, + in_frag_pos, + in_texture_coords + ); + + FragColor = vec4((ambient_light + diffuse_light + specular_light), 1.0); +} diff --git a/engine/light.glsl b/engine/light.glsl index 90539a1..3ba5ba0 100644 --- a/engine/light.glsl +++ b/engine/light.glsl @@ -4,9 +4,9 @@ #define LIGHT_GLSL struct Material { - vec3 ambient; - vec3 diffuse; - vec3 specular; + sampler2D ambient; + sampler2D diffuse; + sampler2D specular; float shininess; }; @@ -21,23 +21,24 @@ struct Light { uniform Material material; uniform Light light; -vec3 calc_ambient_light() +vec3 calc_ambient_light(in vec2 texture_coords) { - return light.ambient * material.ambient; + return light.ambient * vec3(texture(material.ambient, texture_coords)); } -vec3 calc_diffuse_light(in vec3 light_dir, in vec3 norm) +vec3 calc_diffuse_light(in vec3 light_dir, in vec3 norm, in vec2 texture_coords) { float diff = max(dot(norm, light_dir), 0.0); - return light.diffuse * (diff * material.diffuse); + return light.diffuse * (diff * vec3(texture(material.diffuse, texture_coords))); } vec3 calc_specular_light( in vec3 light_dir, in vec3 norm, in vec3 view_pos, - in vec3 frag_pos + in vec3 frag_pos, + in vec2 texture_coords ) { vec3 view_direction = normalize(view_pos - frag_pos); @@ -47,7 +48,7 @@ vec3 calc_specular_light( float spec = pow(max(dot(view_direction, reflect_direction), 0.0), material.shininess); - return light.specular * (spec * material.specular); + return light.specular * (spec * vec3(texture(material.specular, texture_coords))); } #endif diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 30346ed..c4711f2 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -135,11 +135,13 @@ impl Engine let window_size = self.window.size().map_err(Error::GetWindowSizeFailed)?; - self.renderer.render( - self.objects.values(), - self.light_source.as_ref(), - &window_size, - ); + self.renderer + .render( + self.objects.values(), + self.light_source.as_ref(), + &window_size, + ) + .map_err(Error::RenderingFailed)?; self.window .swap_buffers() @@ -234,4 +236,7 @@ pub enum Error #[error("Failed to get cursor position")] GetCursorPosFailed(#[source] glfw::Error), + + #[error("Rendering failed")] + RenderingFailed(#[source] renderer::Error), } diff --git a/engine/src/material.rs b/engine/src/material.rs index 240d7e2..5af3282 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,32 +1,32 @@ -use crate::color::Color; +use crate::texture::Id as TextureId; #[derive(Debug, Clone)] pub struct Material { - ambient: Color, - diffuse: Color, - specular: Color, + ambient_map: TextureId, + diffuse_map: TextureId, + specular_map: TextureId, shininess: f32, } impl Material { #[must_use] - pub fn ambient(&self) -> &Color + pub fn ambient_map(&self) -> &TextureId { - &self.ambient + &self.ambient_map } #[must_use] - pub fn diffuse(&self) -> &Color + pub fn diffuse_map(&self) -> &TextureId { - &self.diffuse + &self.diffuse_map } #[must_use] - pub fn specular(&self) -> &Color + pub fn specular_map(&self) -> &TextureId { - &self.specular + &self.specular_map } #[must_use] @@ -40,9 +40,9 @@ impl Material #[derive(Debug, Clone)] pub struct Builder { - ambient: Color, - diffuse: Color, - specular: Color, + ambient_map: Option, + diffuse_map: Option, + specular_map: Option, shininess: f32, } @@ -52,33 +52,33 @@ impl Builder pub fn new() -> Self { Self { - ambient: 0.2.into(), - diffuse: 0.5.into(), - specular: 1.0.into(), + ambient_map: None, + diffuse_map: None, + specular_map: None, shininess: 32.0, } } #[must_use] - pub fn ambient(mut self, ambient: Color) -> Self + pub fn ambient_map(mut self, ambient_map: TextureId) -> Self { - self.ambient = ambient; + self.ambient_map = Some(ambient_map); self } #[must_use] - pub fn diffuse(mut self, diffuse: Color) -> Self + pub fn diffuse_map(mut self, diffuse_map: TextureId) -> Self { - self.diffuse = diffuse; + self.diffuse_map = Some(diffuse_map); self } #[must_use] - pub fn specular(mut self, specular: Color) -> Self + pub fn specular_map(mut self, specular_map: TextureId) -> Self { - self.specular = specular; + self.specular_map = Some(specular_map); self } @@ -92,13 +92,16 @@ impl Builder } /// Builds a new [`Material`]. + /// + /// # Panics + /// Will panic if no ambient map, diffuse map or specular map is set. #[must_use] pub fn build(&self) -> Material { Material { - ambient: self.ambient.clone(), - diffuse: self.diffuse.clone(), - specular: self.specular.clone(), + ambient_map: self.ambient_map.unwrap(), + diffuse_map: self.diffuse_map.unwrap(), + specular_map: self.specular_map.unwrap(), shininess: self.shininess, } } diff --git a/engine/src/object.rs b/engine/src/object.rs index 6749124..4e143a0 100644 --- a/engine/src/object.rs +++ b/engine/src/object.rs @@ -1,8 +1,9 @@ +use std::collections::HashMap; use std::fs::read_to_string; use std::io::Error as IoError; use std::path::{Path, PathBuf}; -use crate::material::{Builder as MaterialBuilder, Material}; +use crate::material::Material; use crate::opengl::shader::{ Error as ShaderError, Kind as ShaderKind, @@ -11,7 +12,7 @@ use crate::opengl::shader::{ }; use crate::renderer::Renderable; use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; -use crate::texture::Texture; +use crate::texture::{Id as TextureId, Texture}; use crate::transform::Transform; use crate::vector::Vec3; use crate::vertex::Vertex; @@ -19,8 +20,7 @@ use crate::vertex::Vertex; const SHADER_DIR: &str = "engine"; const VERTEX_SHADER_FILE: &str = "vertex.glsl"; -const FRAG_SHADER_COLOR_FILE: &str = "fragment-color.glsl"; -const FRAG_SHADER_TEXTURE_FILE: &str = "fragment-texture.glsl"; +const FRAGMENT_SHADER_FILE: &str = "fragment.glsl"; #[derive(Debug)] pub struct Object @@ -28,7 +28,7 @@ pub struct Object id: Id, renderable: Renderable, transform: Transform, - texture: Option, + textures: HashMap, material: Material, } @@ -58,10 +58,9 @@ impl Object self.transform.set_scale(scaling); } - #[must_use] - pub fn texture(&self) -> Option<&Texture> + pub fn textures(&self) -> impl Iterator { - self.texture.as_ref() + self.textures.iter() } #[must_use] @@ -87,8 +86,8 @@ pub struct Builder { vertices: Vec, indices: Option>, - texture: Option, - material: Material, + textures: HashMap, + material: Option, } impl Builder @@ -100,8 +99,8 @@ impl Builder Self { vertices: Vec::new(), indices: None, - texture: None, - material: MaterialBuilder::new().build(), + textures: HashMap::new(), + material: None, } } @@ -122,9 +121,19 @@ impl Builder } #[must_use] - pub fn texture(mut self, texture: Texture) -> Self + pub fn texture(mut self, id: TextureId, texture: Texture) -> Self + { + self.textures.insert(id, texture); + + self + } + + #[must_use] + pub fn textures(mut self, textures: Textures) -> Self + where + Textures: IntoIterator, { - self.texture = Some(texture); + self.textures.extend(textures); self } @@ -132,7 +141,7 @@ impl Builder #[must_use] pub fn material(mut self, material: Material) -> Self { - self.material = material; + self.material = Some(material); self } @@ -147,11 +156,7 @@ impl Builder { let vertex_shader_file = Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE); - let fragment_shader_color_file = - Path::new(SHADER_DIR).join(FRAG_SHADER_COLOR_FILE); - - let fragment_shader_texture_file = - Path::new(SHADER_DIR).join(FRAG_SHADER_TEXTURE_FILE); + let fragment_shader_file = Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE); let vertex_shader_content = read_to_string(&vertex_shader_file).map_err(|err| { @@ -161,17 +166,11 @@ impl Builder } })?; - let fragment_shader_color_content = read_to_string(&fragment_shader_color_file) - .map_err(|err| Error::ReadShaderFailed { - source: err, - shader_file: fragment_shader_color_file.clone(), - })?; - - let fragment_shader_texture_content = - read_to_string(&fragment_shader_texture_file).map_err(|err| { + let fragment_shader_content = + read_to_string(&fragment_shader_file).map_err(|err| { Error::ReadShaderFailed { source: err, - shader_file: fragment_shader_texture_file.clone(), + shader_file: fragment_shader_file.clone(), } })?; @@ -184,36 +183,20 @@ impl Builder shader_file: vertex_shader_file, })?; - let frag_shader_color_content_preprocessed = shader_preprocessor - .preprocess(fragment_shader_color_content, &fragment_shader_color_file) + let frag_shader_content_preprocessed = shader_preprocessor + .preprocess(fragment_shader_content, &fragment_shader_file) .map_err(|err| Error::PreprocessShaderFailed { source: err, - shader_file: fragment_shader_color_file, - })?; - - let frag_shader_texture_content_preprocessed = shader_preprocessor - .preprocess( - fragment_shader_texture_content, - &fragment_shader_texture_file, - ) - .map_err(|err| Error::PreprocessShaderFailed { - source: err, - shader_file: fragment_shader_texture_file, + shader_file: fragment_shader_file, })?; let vertex_shader = Self::create_shader(ShaderKind::Vertex, &vertex_shader_content_preprocessed) .map_err(Error::CreateVertexShaderFailed)?; - let fragment_shader = Self::create_shader( - ShaderKind::Fragment, - if self.texture.is_some() { - &frag_shader_texture_content_preprocessed - } else { - &frag_shader_color_content_preprocessed - }, - ) - .map_err(Error::CreateFragmentShaderFailed)?; + let fragment_shader = + Self::create_shader(ShaderKind::Fragment, &frag_shader_content_preprocessed) + .map_err(Error::CreateFragmentShaderFailed)?; let shader_program = ShaderProgram::new(); @@ -233,8 +216,8 @@ impl Builder id, renderable, transform, - texture: self.texture, - material: self.material, + textures: self.textures, + material: self.material.ok_or(Error::NoMaterial)?, }) } @@ -285,6 +268,9 @@ pub enum Error source: ShaderPreprocessorError, shader_file: PathBuf, }, + + #[error("Object builder has no material")] + NoMaterial, } /// Object ID. diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs index 9bed09b..224a9bc 100644 --- a/engine/src/opengl/shader.rs +++ b/engine/src/opengl/shader.rs @@ -153,7 +153,7 @@ impl Program Ok(()) } - pub fn activate(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>)) + pub fn activate(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>) -> Ret) -> Ret { unsafe { gl::UseProgram(self.program); @@ -162,7 +162,7 @@ impl Program // SAFETY: The shader program object is bound above let currently_bound = unsafe { CurrentlyBound::new() }; - cb(currently_bound); + cb(currently_bound) } pub fn set_uniform_matrix_4fv( @@ -205,6 +205,16 @@ impl Program } } + pub fn set_uniform_1i(&self, _: &CurrentlyBound, name: &CStr, num: i32) + { + let uniform_location = + unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) }; + + unsafe { + gl::Uniform1i(uniform_location, num); + } + } + fn get_info_log(&self) -> String { let mut buf = vec![gl::types::GLchar::default(); 512]; diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs index 4186479..56eb118 100644 --- a/engine/src/opengl/texture.rs +++ b/engine/src/opengl/texture.rs @@ -1,6 +1,7 @@ use std::ptr::null; use crate::opengl::currently_bound::CurrentlyBound; +use crate::texture::Id; use crate::vector::Vec2; #[derive(Debug)] @@ -234,3 +235,44 @@ impl PixelDataFormat } } } + +pub fn set_active_texture_unit(texture_unit: TextureUnit) +{ + unsafe { + gl::ActiveTexture(texture_unit.into_gl()); + } +} + +macro_rules! texture_unit_enum { + (cnt=$cnt: literal) => { + seq_macro::seq!(N in 0..$cnt { + #[derive(Debug, Clone, Copy)] + pub enum TextureUnit { + #( + No~N, + )* + } + + impl TextureUnit { + fn into_gl(self) -> gl::types::GLenum { + match self { + #( + Self::No~N => gl::TEXTURE~N, + )* + } + } + + pub fn from_texture_id(texture_id: Id) -> Option { + match texture_id.into_inner() { + #( + N => Some(Self::No~N), + )* + _ => None + } + } + } + }); + }; +} + +texture_unit_enum!(cnt = 31); diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs index 6fa692c..593b55f 100644 --- a/engine/src/renderer/mod.rs +++ b/engine/src/renderer/mod.rs @@ -18,6 +18,7 @@ use crate::opengl::currently_bound::CurrentlyBound; #[cfg(feature = "debug")] use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; use crate::opengl::shader::Program as ShaderProgram; +use crate::opengl::texture::{set_active_texture_unit, TextureUnit}; use crate::opengl::vertex_array::{PrimitiveKind, VertexArray}; use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; use crate::projection::new_perspective; @@ -69,7 +70,7 @@ impl Renderer objects: impl IntoIterator, light_source: Option<&LightSource>, window_size: &WindowSize, - ) + ) -> Result<(), Error> { clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); @@ -91,15 +92,22 @@ impl Renderer &shader_program_curr_bound, ); - if let Some(texture) = obj.texture() { - texture.inner().bind(|_| { - Self::draw_object(obj); - }); - } else { - Self::draw_object(obj); + for (texture_id, texture) in obj.textures() { + let texture_unit = TextureUnit::from_texture_id(*texture_id) + .ok_or(Error::TextureIdIsInvalidTextureUnit)?; + + set_active_texture_unit(texture_unit); + + texture.inner().bind(|_| {}); } - }); + + Self::draw_object(obj); + + Ok(()) + })?; } + + Ok(()) } pub fn set_viewport(position: &Vec2, size: &WindowSize) @@ -217,6 +225,9 @@ pub enum Error { #[error("Failed to get window size")] GetWindowSizeFailed(#[source] glfw::Error), + + #[error("Texture ID is a invalid texture unit")] + TextureIdIsInvalidTextureUnit, } fn apply_transformation_matrices( @@ -300,22 +311,25 @@ fn apply_light( .into(), ); - obj.renderable().shader_program.set_uniform_vec_3fv( + #[allow(clippy::cast_possible_wrap)] + obj.renderable().shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.ambient"), - &obj.material().ambient().clone().into(), + obj.material().ambient_map().into_inner() as i32, ); - obj.renderable().shader_program.set_uniform_vec_3fv( + #[allow(clippy::cast_possible_wrap)] + obj.renderable().shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.diffuse"), - &obj.material().diffuse().clone().into(), + obj.material().diffuse_map().into_inner() as i32, ); - obj.renderable().shader_program.set_uniform_vec_3fv( + #[allow(clippy::cast_possible_wrap)] + obj.renderable().shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.specular"), - &obj.material().specular().clone().into(), + obj.material().specular_map().into_inner() as i32, ); obj.renderable().shader_program.set_uniform_1fv( diff --git a/engine/src/texture.rs b/engine/src/texture.rs index f32bc0a..f644b58 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -1,8 +1,9 @@ use std::path::Path; use image::io::Reader as ImageReader; -use image::{DynamicImage, ImageError}; +use image::{DynamicImage, EncodableLayout, ImageError, Rgb, RgbImage}; +use crate::color::Color; use crate::opengl::texture::{PixelDataFormat, Texture as InnerTexture}; use crate::vector::Vec2; @@ -67,6 +68,39 @@ impl Texture Ok(me) } + #[must_use] + pub fn new_from_color(dimensions: &Vec2, color: &Color) -> Self + { + let image = RgbImage::from_pixel( + dimensions.x, + dimensions.y, + Rgb([color.red, color.green, color.blue]), + ); + + let inner = InnerTexture::new(); + + inner.bind(|texture_curr_bound| { + InnerTexture::generate( + &texture_curr_bound, + dimensions, + image.as_bytes(), + PixelDataFormat::Rgb, + ); + }); + + let me = Self { + inner, + pixel_data_format: PixelDataFormat::Rgb, + dimensions: dimensions.clone(), + }; + + me.set_wrap(Wrapping::Repeat); + me.set_magnifying_filter(Filtering::Linear); + me.set_minifying_filter(Filtering::Nearest); + + me + } + pub fn set_wrap(&self, wrapping: Wrapping) { self.inner.bind(|texture_curr_bound| { @@ -126,3 +160,25 @@ pub enum Error #[error("Unsupported image data kind")] UnsupportedImageDataKind, } + +/// Texture ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id +{ + id: u32, +} + +impl Id +{ + /// Returns a new texture ID. + #[must_use] + pub fn new(id: u32) -> Self + { + Self { id } + } + + pub(crate) fn into_inner(self) -> u32 + { + self.id + } +} -- cgit v1.2.3-18-g5258