From daf280048d82eed42d44423ce50ae9e809ba91a9 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 23 Nov 2024 21:32:03 +0100 Subject: refactor(engine): make shaders a renderer detail --- engine/src/lib.rs | 1 - engine/src/opengl/shader.rs | 17 +- engine/src/renderer/opengl.rs | 99 +++++++----- engine/src/renderer/opengl/glsl/fragment.glsl | 73 +++++++++ engine/src/renderer/opengl/glsl/light.glsl | 133 ++++++++++++++++ engine/src/renderer/opengl/glsl/vertex.glsl | 24 +++ engine/src/renderer/opengl/glsl/vertex_data.glsl | 11 ++ engine/src/shader.rs | 188 ----------------------- 8 files changed, 317 insertions(+), 229 deletions(-) create mode 100644 engine/src/renderer/opengl/glsl/fragment.glsl create mode 100644 engine/src/renderer/opengl/glsl/light.glsl create mode 100644 engine/src/renderer/opengl/glsl/vertex.glsl create mode 100644 engine/src/renderer/opengl/glsl/vertex_data.glsl delete mode 100644 engine/src/shader.rs (limited to 'engine/src') diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 555e1ed..07b2f8d 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -37,7 +37,6 @@ pub mod mesh; pub mod performance; pub mod projection; pub mod renderer; -pub mod shader; pub mod texture; pub mod transform; pub mod vertex; diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs index 070897e..36dc1a4 100644 --- a/engine/src/opengl/shader.rs +++ b/engine/src/opengl/shader.rs @@ -2,7 +2,6 @@ use std::ffi::CStr; use std::ptr::null_mut; use crate::matrix::Matrix; -use crate::shader::Kind; use crate::vector::Vec3; #[derive(Debug)] @@ -20,7 +19,7 @@ impl Shader Self { shader } } - pub fn set_source(&self, source: &str) -> Result<(), Error> + pub fn set_source(&mut self, source: &str) -> Result<(), Error> { if !source.is_ascii() { return Err(Error::SourceNotAscii); @@ -39,7 +38,7 @@ impl Shader Ok(()) } - pub fn compile(&self) -> Result<(), Error> + pub fn compile(&mut self) -> Result<(), Error> { unsafe { gl::CompileShader(self.shader); @@ -90,6 +89,14 @@ impl Drop for Shader } } +/// Shader kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Kind +{ + Vertex, + Fragment, +} + impl Kind { fn into_gl(self) -> gl::types::GLenum @@ -117,14 +124,14 @@ impl Program Self { program } } - pub fn attach(&self, shader: &Shader) + pub fn attach(&mut self, shader: &Shader) { unsafe { gl::AttachShader(self.program, shader.shader); } } - pub fn link(&self) -> Result<(), Error> + pub fn link(&mut self) -> Result<(), Error> { unsafe { gl::LinkProgram(self.program); diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index 6e6347b..deb26a8 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use std::ffi::{c_void, CString}; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::ops::Deref; +use std::path::Path; use std::process::abort; use ecs::actions::Actions; @@ -24,8 +26,13 @@ use crate::mesh::Mesh; use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; #[cfg(feature = "debug")] use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; +use crate::opengl::glsl::{ + preprocess as glsl_preprocess, + PreprocessingError as GlslPreprocessingError, +}; use crate::opengl::shader::{ Error as GlShaderError, + Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, }; @@ -41,7 +48,6 @@ use crate::opengl::vertex_array::{ }; use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; use crate::projection::{new_perspective_matrix, Projection}; -use crate::shader::Program as ShaderProgram; use crate::texture::{Id as TextureId, Texture}; use crate::transform::{Position, Scale}; use crate::util::NeverDrop; @@ -51,7 +57,6 @@ use crate::window::Window; type RenderableEntity = ( Mesh, - ShaderProgram, Material, Option, Option, @@ -137,34 +142,24 @@ fn render( let directional_lights = directional_lights.iter().collect::>(); let GlobalGlObjects { - shader_programs: gl_shader_programs, + shader_program, textures: gl_textures, } = &mut *gl_objects; + let shader_program = + shader_program.get_or_insert_with(|| create_default_shader_program().unwrap()); + clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for ( entity_index, - ( - mesh, - shader_program, - material, - material_flags, - position, - scale, - draw_flags, - gl_objects, - ), + (mesh, material, material_flags, position, scale, draw_flags, gl_objects), ) in query.iter().enumerate() { let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); - let shader_program = gl_shader_programs - .entry(shader_program.u64_hash()) - .or_insert_with(|| create_gl_shader_program(&shader_program).unwrap()); - let new_gl_objects; let gl_objects = if let Some(gl_objects) = gl_objects.as_deref() { @@ -248,7 +243,7 @@ fn render( #[derive(Debug, Default, Component)] struct GlobalGlObjects { - shader_programs: HashMap, + shader_program: Option, textures: HashMap, } @@ -300,34 +295,68 @@ fn create_gl_texture(texture: &Texture) -> GlTexture gl_texture } -fn create_gl_shader_program( - shader_program: &ShaderProgram, -) -> Result +const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); +const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl"); + +const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl"); +const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl"); + +fn create_default_shader_program() -> Result { - let gl_shaders = shader_program - .shaders() - .iter() - .map(|shader| { - let gl_shader = GlShader::new(shader.kind()); + let mut vertex_shader = GlShader::new(ShaderKind::Vertex); - gl_shader.set_source(shader.source())?; - gl_shader.compile()?; + vertex_shader.set_source(&*glsl_preprocess( + VERTEX_GLSL_SHADER_SRC, + &get_glsl_shader_content, + )?)?; - Ok(gl_shader) - }) - .collect::, _>>()?; + vertex_shader.compile()?; - let gl_shader_program = GlShaderProgram::new(); + let mut fragment_shader = GlShader::new(ShaderKind::Fragment); - for gl_shader in &gl_shaders { - gl_shader_program.attach(gl_shader); - } + fragment_shader.set_source(&*glsl_preprocess( + FRAGMENT_GLSL_SHADER_SRC, + &get_glsl_shader_content, + )?)?; + + fragment_shader.compile()?; + + let mut gl_shader_program = GlShaderProgram::new(); + + gl_shader_program.attach(&vertex_shader); + gl_shader_program.attach(&fragment_shader); gl_shader_program.link()?; Ok(gl_shader_program) } +#[derive(Debug, thiserror::Error)] +enum CreateShaderError +{ + #[error(transparent)] + ShaderError(#[from] GlShaderError), + + #[error(transparent)] + PreprocessingError(#[from] GlslPreprocessingError), +} + +fn get_glsl_shader_content(path: &Path) -> Result, std::io::Error> +{ + if path == Path::new("vertex_data.glsl") { + return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec()); + } + + if path == Path::new("light.glsl") { + return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec()); + } + + Err(IoError::new( + IoErrorKind::NotFound, + format!("Content for shader file {} not found", path.display()), + )) +} + #[derive(Debug, Component)] struct GlObjects { diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl new file mode 100644 index 0000000..5bf5ff2 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/fragment.glsl @@ -0,0 +1,73 @@ +#version 330 core + +#preinclude "light.glsl" +#preinclude "vertex_data.glsl" + +#define MAX_LIGHT_CNT 64 + +out vec4 FragColor; + +in VertexData vertex_data; + +uniform vec3 view_pos; +uniform sampler2D input_texture; +uniform Material material; + +uniform PointLight point_lights[MAX_LIGHT_CNT]; +uniform int point_light_cnt; + +uniform DirectionalLight directional_lights[MAX_LIGHT_CNT]; +uniform int directional_light_cnt; + +void main() +{ + vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords); + + vec3 directional_light_sum = vec3(0.0, 0.0, 0.0); + + for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) { + CalculatedLight calculated_dir_light; + + calc_light( + // Negated since we want the light to point from the light direction + normalize(-directional_lights[dl_index].direction), + directional_lights[dl_index].phong, + vertex_data, + view_pos, + material, + calculated_dir_light + ); + + directional_light_sum += + calculated_dir_light.diffuse + calculated_dir_light.specular; + } + + vec3 point_light_sum = vec3(0.0, 0.0, 0.0); + + for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) { + vec3 light_direction = + normalize(point_lights[pl_index].position - vertex_data.world_space_pos); + + CalculatedLight calculated_point_light; + + calc_light( + light_direction, + point_lights[pl_index].phong, + vertex_data, + view_pos, + material, + calculated_point_light + ); + + float attenuation = + calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos); + + calculated_point_light.diffuse *= attenuation; + calculated_point_light.specular *= attenuation; + + point_light_sum += + calculated_point_light.diffuse + calculated_point_light.specular; + } + + FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0); +} diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl new file mode 100644 index 0000000..1bc23a4 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/light.glsl @@ -0,0 +1,133 @@ +#version 330 core + +#ifndef LIGHT_GLSL +#define LIGHT_GLSL + +#preinclude "vertex_data.glsl" + +struct Material +{ + vec3 ambient; + vec3 diffuse; + vec3 specular; + sampler2D ambient_map; + sampler2D diffuse_map; + sampler2D specular_map; + float shininess; +}; + +struct LightPhong +{ + vec3 diffuse; + vec3 specular; +}; + +struct AttenuationProperties +{ + float constant; + float linear; + float quadratic; +}; + +struct PointLight +{ + LightPhong phong; + vec3 position; + AttenuationProperties attenuation_props; +}; + +struct DirectionalLight +{ + LightPhong phong; + vec3 direction; +}; + +struct CalculatedLight +{ + vec3 diffuse; + vec3 specular; +}; + +vec3 calc_ambient_light(in Material material, in vec2 texture_coords) +{ + return vec3(texture(material.ambient_map, texture_coords)) * material.ambient; +} + +vec3 calc_diffuse_light( + in Material material, + in LightPhong light_phong, + in vec3 light_dir, + in vec3 norm, + in vec2 texture_coords +) +{ + float diff = max(dot(norm, light_dir), 0.0); + + return light_phong.diffuse * ( + diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse) + ); +} + +vec3 calc_specular_light( + in Material material, + in LightPhong light_phong, + in vec3 light_dir, + in vec3 norm, + in vec3 view_pos, + in vec3 frag_pos, + in vec2 texture_coords +) +{ + vec3 view_direction = normalize(view_pos - frag_pos); + + vec3 reflect_direction = reflect(-light_dir, norm); + + float spec = + pow(max(dot(view_direction, reflect_direction), 0.0), material.shininess); + + return light_phong.specular * ( + spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular) + ); +} + +float calc_attenuation(in PointLight point_light, in vec3 position) +{ + float light_distance = length(point_light.position - position); + + return 1.0 / (point_light.attenuation_props.constant + + point_light.attenuation_props.linear + * light_distance + point_light.attenuation_props.quadratic + * pow(light_distance, 2)); +} + +void calc_light( + in vec3 light_direction, + in LightPhong light_phong, + in VertexData vertex_data, + in vec3 view_pos, + in Material material, + out CalculatedLight calculated_light +) +{ + vec3 norm = normalize(vertex_data.world_space_normal); + + calculated_light.diffuse = calc_diffuse_light( + material, + light_phong, + light_direction, + norm, + vertex_data.texture_coords + ); + + calculated_light.specular = calc_specular_light( + material, + light_phong, + light_direction, + norm, + view_pos, + vertex_data.world_space_pos, + vertex_data.texture_coords + ); +} + +#endif diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl new file mode 100644 index 0000000..b57caa6 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/vertex.glsl @@ -0,0 +1,24 @@ +#version 330 core + +#preinclude "vertex_data.glsl" + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texture_coords; +layout (location = 2) in vec3 normal; + +out VertexData vertex_data; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(pos, 1.0); + + vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0)); + vertex_data.texture_coords = texture_coords; + + // TODO: Do this using CPU for performance increase + vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal; +} diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl new file mode 100644 index 0000000..486d445 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/vertex_data.glsl @@ -0,0 +1,11 @@ +#ifndef VERTEX_DATA_GLSL +#define VERTEX_DATA_GLSL + +struct VertexData +{ + vec2 texture_coords; + vec3 world_space_pos; + vec3 world_space_normal; +}; + +#endif diff --git a/engine/src/shader.rs b/engine/src/shader.rs deleted file mode 100644 index b43d538..0000000 --- a/engine/src/shader.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::collections::hash_map::DefaultHasher; -use std::fs::read_to_string; -use std::hash::{Hash, Hasher}; -use std::path::{Path, PathBuf}; - -use ecs::Component; - -use crate::opengl::glsl::{ - preprocess as glsl_preprocess, - PreprocessingError as GlslPreprocessingError, -}; - -const VERTEX_SHADER_FILE: &str = "vertex.glsl"; -const FRAGMENT_SHADER_FILE: &str = "fragment.glsl"; - -const SHADER_DIR: &str = "engine"; - -/// Shader program -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Component)] -pub struct Program -{ - shaders: Vec, -} - -impl Program -{ - /// Creates a new shader program with the default shaders. - /// - /// # Errors - /// Returns `Err` if: - /// - Reading a default shader file Fails - /// - Preprocessing a shader fails. - pub fn new() -> Result - { - let mut program = Self { shaders: Vec::new() }; - - program.push_shader( - Shader::read_shader_file( - Kind::Vertex, - &Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE), - )? - .preprocess()?, - ); - - program.push_shader( - Shader::read_shader_file( - Kind::Fragment, - &Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE), - )? - .preprocess()?, - ); - - Ok(program) - } - - pub fn push_shader(&mut self, shader: Shader) - { - self.shaders.push(shader); - } - - pub fn append_shaders(&mut self, shaders: impl IntoIterator) - { - self.shaders.extend(shaders); - } - - #[must_use] - pub fn shaders(&self) -> &[Shader] - { - &self.shaders - } - - pub(crate) fn u64_hash(&self) -> u64 - { - let mut hasher = DefaultHasher::new(); - - self.hash(&mut hasher); - - hasher.finish() - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct Shader -{ - kind: Kind, - source: String, - file: PathBuf, -} - -impl Shader -{ - /// Reads a shader from the specified source file. - /// - /// # Errors - /// Will return `Err` if: - /// - Reading the file fails - /// - The shader source is not ASCII - pub fn read_shader_file(kind: Kind, shader_file: &Path) -> Result - { - let source = read_to_string(shader_file).map_err(|err| Error::ReadFailed { - source: err, - shader_file: shader_file.to_path_buf(), - })?; - - if !source.is_ascii() { - return Err(Error::SourceNotAscii); - } - - Ok(Self { - kind, - source, - file: shader_file.to_path_buf(), - }) - } - - /// Preprocesses the shaders. - /// - /// # Errors - /// Returns `Err` if preprocessing fails. - pub fn preprocess(self) -> Result - { - let parent_dir = self - .file - .parent() - .ok_or(Error::SourcePathHasNoParent)? - .to_path_buf(); - - let source_preprocessed = - glsl_preprocess(self.source, &|path| std::fs::read(parent_dir.join(path))) - .map_err(|err| Error::PreprocessFailed { - source: err, - shader_file: self.file.clone(), - })?; - - Ok(Self { - kind: self.kind, - source: source_preprocessed.into_owned(), - file: self.file.clone(), - }) - } - - #[must_use] - pub fn kind(&self) -> Kind - { - self.kind - } - - #[must_use] - pub fn source(&self) -> &str - { - &self.source - } -} - -/// Shader kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Kind -{ - Vertex, - Fragment, -} - -/// Shader error -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to read shader {}", shader_file.display())] - ReadFailed - { - #[source] - source: std::io::Error, - shader_file: PathBuf, - }, - - #[error("Shader source is not ASCII")] - SourceNotAscii, - - #[error("Failed to preprocess shader {}", shader_file.display())] - PreprocessFailed - { - #[source] - source: GlslPreprocessingError, - shader_file: PathBuf, - }, - - #[error("Shader source path has no parent")] - SourcePathHasNoParent, -} -- cgit v1.2.3-18-g5258