diff options
Diffstat (limited to 'engine/src/renderer')
-rw-r--r-- | engine/src/renderer/opengl.rs | 525 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/fragment.glsl | 73 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/light.glsl | 133 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/vertex.glsl | 24 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/vertex_data.glsl | 11 | ||||
-rw-r--r-- | engine/src/renderer/opengl/vertex.rs | 64 |
6 files changed, 630 insertions, 200 deletions
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index a353c6a..cfd046f 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -2,75 +2,109 @@ use std::collections::HashMap; use std::ffi::{c_void, CString}; -use std::ops::Deref; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::path::Path; use std::process::abort; use ecs::actions::Actions; use ecs::component::local::Local; -use ecs::query::options::{Not, With}; +use ecs::component::Handle as ComponentHandle; +use ecs::phase::START as START_PHASE; +use ecs::query::term::Without; use ecs::sole::Single; use ecs::system::{Into as _, System}; use ecs::{Component, Query}; +use crate::asset::{Assets, Id as AssetId}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; -use crate::event::{Present as PresentEvent, Start as StartEvent}; +use crate::image::{ColorType as ImageColorType, Image}; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; use crate::mesh::Mesh; +use crate::model::Model; use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; -#[cfg(feature = "debug")] -use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; +use crate::opengl::debug::{ + enable_debug_output, + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + 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, }; use crate::opengl::texture::{ - set_active_texture_unit, + Filtering as GlTextureFiltering, + PixelDataFormat as GlTexturePixelDataFormat, Texture as GlTexture, - TextureUnit, + Wrapping as GlTextureWrapping, }; use crate::opengl::vertex_array::{ DataType as VertexArrayDataType, PrimitiveKind, VertexArray, }; -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; +use crate::opengl::{ + clear_buffers, + enable, + get_context_flags as get_opengl_context_flags, + BufferClearMask, + Capability, + ContextFlags, +}; +use crate::projection::{ClipVolume, Projection}; +use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex}; +use crate::renderer::RENDER_PHASE; +use crate::texture::{ + Filtering as TextureFiltering, + Properties as TextureProperties, + Wrapping as TextureWrapping, +}; +use crate::transform::{Scale, WorldPosition}; +use crate::util::{defer, Defer, RefOrValue}; use crate::vector::{Vec2, Vec3}; -use crate::vertex::{AttributeComponentType, Vertex}; use crate::window::Window; -type RenderableEntity = ( - Mesh, - ShaderProgram, - Material, - Option<MaterialFlags>, - Option<Position>, - Option<Scale>, - Option<DrawFlags>, - Option<GlObjects>, +mod vertex; + +const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0; +const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1; +const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2; + +type RenderableEntity<'a> = ( + &'a Model, + Option<&'a MaterialFlags>, + Option<&'a WorldPosition>, + Option<&'a Scale>, + Option<&'a DrawFlags>, + Option<&'a GlObjects>, ); #[derive(Debug, Default)] +#[non_exhaustive] pub struct Extension {} impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { - collector.add_system(StartEvent, initialize); + collector.add_system(*START_PHASE, initialize); collector.add_system( - PresentEvent, + *RENDER_PHASE, render .into_system() .initialize((GlobalGlObjects::default(),)), @@ -95,8 +129,9 @@ fn initialize(window: Single<Window>) } }); - #[cfg(feature = "debug")] - initialize_debug(); + if get_opengl_context_flags().contains(ContextFlags::DEBUG) { + initialize_debug(); + } let window_size = window.size().expect("Failed to get window size"); @@ -110,78 +145,63 @@ fn initialize(window: Single<Window>) enable(Capability::MultiSample); } +#[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] fn render( - query: Query<RenderableEntity, Not<With<NoDraw>>>, - point_light_query: Query<(PointLight,)>, - directional_lights: Query<(DirectionalLight,)>, - camera_query: Query<(Camera, Position, ActiveCamera)>, + query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, + point_light_query: Query<(&PointLight, &WorldPosition)>, + directional_lights: Query<(&DirectionalLight,)>, + camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, window: Single<Window>, global_light: Single<GlobalLight>, + assets: Single<Assets>, mut gl_objects: Local<GlobalGlObjects>, mut actions: Actions, ) { - let Some((camera, camera_pos, _)) = camera_query.iter().next() else { - #[cfg(feature = "debug")] + let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { tracing::warn!("No current camera. Nothing will be rendered"); return; }; - let point_lights = point_light_query - .iter() - .map(|(point_light,)| point_light) - .collect::<Vec<_>>(); - let directional_lights = directional_lights.iter().collect::<Vec<_>>(); let GlobalGlObjects { - shader_programs: gl_shader_programs, + shader_program, textures: gl_textures, + default_1x1_texture: default_1x1_gl_texture, } = &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, - ), - ) in query.iter().enumerate() + 'subject_loop: for ( + euid, + (model, material_flags, position, scale, draw_flags, gl_objects), + ) in query.iter_with_euids() { + let Some(model_data) = assets.get(&model.asset_handle) else { + tracing::trace!("Missing model asset"); + continue; + }; + 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() { - gl_objects - } else { - // TODO: Account for when meshes are changed - let gl_objects = GlObjects::new(&mesh); - - new_gl_objects = Some(gl_objects.clone()); - - actions.add_components( - query.get_entity_uid(entity_index).unwrap(), - (gl_objects,), - ); - - &*new_gl_objects.unwrap() + let gl_objs = match gl_objects.as_deref() { + Some(gl_objs) => RefOrValue::Ref(gl_objs), + None => RefOrValue::Value(Some(GlObjects::new(&model_data.mesh))), }; + defer!(|gl_objs| { + if let RefOrValue::Value(opt_gl_objs) = gl_objs { + actions.add_components(euid, (opt_gl_objs.take().unwrap(),)); + }; + }); + apply_transformation_matrices( Transformation { position: position.map(|pos| *pos).unwrap_or_default().position, @@ -189,37 +209,82 @@ fn render( }, shader_program, &camera, - &camera_pos, + &camera_world_pos, window.size().expect("Failed to get window size"), ); + if model_data.materials.len() > 1 { + tracing::warn!(concat!( + "Multiple model materials are not supported ", + "so only the first material will be used" + )); + } + + let material = match model_data.materials.values().next() { + Some(material) => material, + None => { + tracing::warn!("Model has no materials. Using default material"); + + &Material::default() + } + }; + apply_light( &material, &material_flags, &global_light, shader_program, - point_lights.as_slice(), + (point_light_query.iter(), point_light_query.iter().count()), directional_lights .iter() .map(|(dir_light,)| &**dir_light) .collect::<Vec<_>>() .as_slice(), - &camera_pos, + &camera_world_pos, ); - for texture in &material.textures { - let gl_texture = gl_textures - .entry(texture.id()) - .or_insert_with(|| create_gl_texture(texture)); - - let texture_unit = - TextureUnit::from_texture_id(texture.id()).unwrap_or_else(|| { - panic!("Texture id {} is a invalid texture unit", texture.id()); + let material_texture_maps = [ + (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT), + (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT), + (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT), + ]; + + for (texture, texture_unit) in material_texture_maps { + let Some(texture) = texture else { + let gl_texture = default_1x1_gl_texture.get_or_insert_with(|| { + create_gl_texture( + &Image::from_color(1, Color::WHITE_U8), + &TextureProperties::default(), + ) }); - set_active_texture_unit(texture_unit); + gl_texture.bind_to_texture_unit(texture_unit); - gl_texture.bind(); + continue; + }; + + let texture_image_asset_id = texture.asset_handle.id(); + + let gl_texture = match gl_textures.get(&texture_image_asset_id) { + Some(gl_texture) => gl_texture, + None => { + let Some(image) = assets.get::<Image>(&texture.asset_handle) else { + tracing::trace!("Missing texture asset"); + continue 'subject_loop; + }; + + gl_textures.insert( + texture_image_asset_id, + create_gl_texture(image, &texture.properties), + ); + + gl_textures + .get(&texture.asset_handle.id()) + .expect("Not possible") + } + }; + + gl_texture.bind_to_texture_unit(texture_unit); } shader_program.activate(); @@ -231,7 +296,7 @@ fn render( ); } - draw_mesh(gl_objects); + draw_mesh(gl_objs.get().unwrap()); if draw_flags.is_some() { let default_polygon_mode_config = PolygonModeConfig::default(); @@ -247,8 +312,9 @@ fn render( #[derive(Debug, Default, Component)] struct GlobalGlObjects { - shader_programs: HashMap<u64, GlShaderProgram>, - textures: HashMap<TextureId, GlTexture>, + shader_program: Option<GlShaderProgram>, + textures: HashMap<AssetId, GlTexture>, + default_1x1_texture: Option<GlTexture>, } fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) @@ -256,20 +322,11 @@ fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) crate::opengl::set_viewport(position, size); } -#[cfg(feature = "debug")] fn initialize_debug() { - use crate::opengl::debug::{ - enable_debug_output, - set_debug_message_callback, - set_debug_message_control, - MessageIdsAction, - }; - enable_debug_output(); set_debug_message_callback(opengl_debug_message_cb); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); } @@ -278,72 +335,119 @@ fn draw_mesh(gl_objects: &GlObjects) gl_objects.vertex_arr.bind(); if gl_objects.index_buffer.is_some() { - VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.index_cnt); + VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); } else { - VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, 3); + VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); } } -fn create_gl_texture(texture: &Texture) -> GlTexture +fn create_gl_texture(image: &Image, texture_properties: &TextureProperties) -> GlTexture { let mut gl_texture = GlTexture::new(); gl_texture.generate( - *texture.dimensions(), - texture.image().as_bytes(), - texture.pixel_data_format(), + image.dimensions(), + image.as_bytes(), + match image.color_type() { + ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, + ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, + _ => { + unimplemented!(); + } + }, ); - gl_texture.apply_properties(texture.properties()); + gl_texture.set_wrap(texture_wrapping_to_gl(texture_properties.wrap)); + + gl_texture.set_magnifying_filter(texture_filtering_to_gl( + texture_properties.magnifying_filter, + )); + + gl_texture.set_minifying_filter(texture_filtering_to_gl( + texture_properties.minifying_filter, + )); gl_texture } -fn create_gl_shader_program( - shader_program: &ShaderProgram, -) -> Result<GlShaderProgram, GlShaderError> +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<GlShaderProgram, CreateShaderError> { - 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::<Result<Vec<_>, _>>()?; + 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<Vec<u8>, 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 { /// Vertex and index buffer has to live as long as the vertex array - vertex_buffer: Buffer<Vertex>, + _vertex_buffer: Buffer<Vertex>, index_buffer: Option<Buffer<u32>>, - index_cnt: u32, + element_cnt: u32, vertex_arr: VertexArray, } impl GlObjects { - #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + #[tracing::instrument(skip_all)] fn new(mesh: &Mesh) -> Self { - #[cfg(feature = "debug")] tracing::trace!( "Creating vertex array, vertex buffer{}", if mesh.indices().is_some() { @@ -356,7 +460,13 @@ impl GlObjects let mut vertex_arr = VertexArray::new(); let mut vertex_buffer = Buffer::new(); - vertex_buffer.store(mesh.vertices(), BufferUsage::Static); + vertex_buffer.store_mapped(mesh.vertices(), BufferUsage::Static, |vertex| { + Vertex { + pos: vertex.pos, + texture_coords: vertex.texture_coords, + normal: vertex.normal, + } + }); vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0); @@ -387,78 +497,77 @@ impl GlObjects vertex_arr.bind_element_buffer(&index_buffer); return Self { - vertex_buffer, + _vertex_buffer: vertex_buffer, index_buffer: Some(index_buffer), - index_cnt: indices.len().try_into().unwrap(), + element_cnt: indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"), vertex_arr, }; } Self { - vertex_buffer, + _vertex_buffer: vertex_buffer, index_buffer: None, - index_cnt: 0, + element_cnt: mesh + .vertices() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), vertex_arr, } } - - pub fn clone(&self) -> NeverDrop<Self> - { - NeverDrop::new(Self { - // SAFETY: The vertex buffer will never become dropped (NeverDrop ensures it) - vertex_buffer: unsafe { self.vertex_buffer.clone_weak() }, - index_buffer: self - .index_buffer - .as_ref() - // SAFETY: The index buffer will never become dropped (NeverDrop ensures - // it) - .map(|index_buffer| unsafe { index_buffer.clone_weak() }), - index_cnt: self.index_cnt, - // SAFETY: The vertex array will never become dropped (NeverDrop ensures it) - vertex_arr: unsafe { self.vertex_arr.clone_unsafe() }, - }) - } } fn apply_transformation_matrices( transformation: Transformation, gl_shader_program: &mut GlShaderProgram, camera: &Camera, - camera_pos: &Position, + camera_world_pos: &WorldPosition, window_size: Dimens<u32>, ) { gl_shader_program - .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation)); + .set_uniform(c"model", &create_transformation_matrix(transformation)); - let view = create_view(camera, camera_pos); + let view_matrix = create_view_matrix(camera, &camera_world_pos.position); - gl_shader_program.set_uniform_matrix_4fv(c"view", &view); + gl_shader_program.set_uniform(c"view", &view_matrix); #[allow(clippy::cast_precision_loss)] - let projection = match &camera.projection { - Projection::Perspective(perspective) => new_perspective_matrix( - perspective, + let proj_matrix = match &camera.projection { + Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( window_size.width as f32 / window_size.height as f32, + ClipVolume::NegOneToOne, ), + Projection::Orthographic(orthographic_proj) => orthographic_proj + .to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne), }; - gl_shader_program.set_uniform_matrix_4fv(c"projection", &projection); + gl_shader_program.set_uniform(c"projection", &proj_matrix); } -fn apply_light<PointLightHolder>( +fn apply_light<'point_light>( material: &Material, material_flags: &MaterialFlags, global_light: &GlobalLight, gl_shader_program: &mut GlShaderProgram, - point_lights: &[PointLightHolder], + (point_light_iter, point_light_cnt): ( + impl Iterator< + Item = ( + ComponentHandle<'point_light, PointLight>, + ComponentHandle<'point_light, WorldPosition>, + ), + >, + usize, + ), directional_lights: &[&DirectionalLight], - camera_pos: &Position, -) where - PointLightHolder: Deref<Target = PointLight>, + camera_world_pos: &WorldPosition, +) { debug_assert!( - point_lights.len() < 64, + point_light_cnt < 64, "Shader cannot handle more than 64 point lights" ); @@ -468,7 +577,7 @@ fn apply_light<PointLightHolder>( ); for (dir_light_index, dir_light) in directional_lights.iter().enumerate() { - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( &create_light_uniform_name( "directional_lights", dir_light_index, @@ -488,71 +597,68 @@ fn apply_light<PointLightHolder>( // There probably won't be more than 2147483648 directional lights #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] gl_shader_program - .set_uniform_1i(c"directional_light_cnt", directional_lights.len() as i32); + .set_uniform(c"directional_light_cnt", &(directional_lights.len() as i32)); - for (point_light_index, point_light) in point_lights.iter().enumerate() { - gl_shader_program.set_uniform_vec_3fv( + for (point_light_index, (point_light, point_light_world_pos)) in + point_light_iter.enumerate() + { + gl_shader_program.set_uniform( &create_light_uniform_name("point_lights", point_light_index, "position"), - &point_light.position, + &(point_light_world_pos.position + point_light.local_position), ); set_light_phong_uniforms( gl_shader_program, "point_lights", point_light_index, - &**point_light, + &*point_light, ); set_light_attenuation_uniforms( gl_shader_program, "point_lights", point_light_index, - point_light, + &*point_light, ); } // There probably won't be more than 2147483648 point lights #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl_shader_program.set_uniform_1i(c"point_light_cnt", point_lights.len() as i32); + gl_shader_program.set_uniform(c"point_light_cnt", &(point_light_cnt as i32)); - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( c"material.ambient", - &if material_flags.use_ambient_color { + &Vec3::from(if material_flags.use_ambient_color { material.ambient.clone() } else { global_light.ambient.clone() - } - .into(), + }), ); gl_shader_program - .set_uniform_vec_3fv(c"material.diffuse", &material.diffuse.clone().into()); + .set_uniform(c"material.diffuse", &Vec3::from(material.diffuse.clone())); #[allow(clippy::cast_possible_wrap)] gl_shader_program - .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into()); + .set_uniform(c"material.specular", &Vec3::from(material.specular.clone())); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( - c"material.ambient_map", - material.ambient_map.into_inner() as i32, - ); + gl_shader_program + .set_uniform(c"material.ambient_map", &(AMBIENT_MAP_TEXTURE_UNIT as i32)); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( - c"material.diffuse_map", - material.diffuse_map.into_inner() as i32, - ); + gl_shader_program + .set_uniform(c"material.diffuse_map", &(DIFFUSE_MAP_TEXTURE_UNIT as i32)); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( + gl_shader_program.set_uniform( c"material.specular_map", - material.specular_map.into_inner() as i32, + &(SPECULAR_MAP_TEXTURE_UNIT as i32), ); - gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess); + gl_shader_program.set_uniform(c"material.shininess", &material.shininess); - gl_shader_program.set_uniform_vec_3fv(c"view_pos", &camera_pos.position); + gl_shader_program.set_uniform(c"view_pos", &camera_world_pos.position); } fn set_light_attenuation_uniforms( @@ -562,27 +668,27 @@ fn set_light_attenuation_uniforms( light: &PointLight, ) { - gl_shader_program.set_uniform_1fv( + gl_shader_program.set_uniform( &create_light_uniform_name( light_array, light_index, "attenuation_props.constant", ), - light.attenuation_params.constant, + &light.attenuation_params.constant, ); - gl_shader_program.set_uniform_1fv( + gl_shader_program.set_uniform( &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"), - light.attenuation_params.linear, + &light.attenuation_params.linear, ); - gl_shader_program.set_uniform_1fv( + gl_shader_program.set_uniform( &create_light_uniform_name( light_array, light_index, "attenuation_props.quadratic", ), - light.attenuation_params.quadratic, + &light.attenuation_params.quadratic, ); } @@ -593,14 +699,14 @@ fn set_light_phong_uniforms( light: &impl Light, ) { - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( &create_light_uniform_name(light_array, light_index, "phong.diffuse"), - &light.diffuse().clone().into(), + &Vec3::from(light.diffuse().clone()), ); - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( &create_light_uniform_name(light_array, light_index, "phong.specular"), - &light.specular().clone().into(), + &Vec3::from(light.specular().clone()), ); } @@ -649,16 +755,15 @@ fn create_light_uniform_name( } } -fn create_view(camera: &Camera, camera_pos: &Position) -> Matrix<f32, 4, 4> +fn create_view_matrix(camera: &Camera, camera_pos: &Vec3<f32>) -> Matrix<f32, 4, 4> { let mut view = Matrix::new(); - view.look_at(&camera_pos.position, &camera.target, &camera.global_up); + view.look_at(&camera_pos, &camera.target, &camera.global_up); view } -#[cfg(feature = "debug")] #[tracing::instrument(skip_all)] fn opengl_debug_message_cb( source: MessageSource, @@ -717,3 +822,23 @@ fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4 matrix } + +#[inline] +fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping +{ + match texture_wrapping { + TextureWrapping::Repeat => GlTextureWrapping::Repeat, + TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, + TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, + TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, + } +} + +#[inline] +fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering +{ + match texture_filtering { + TextureFiltering::Linear => GlTextureFiltering::Linear, + TextureFiltering::Nearest => GlTextureFiltering::Nearest, + } +} 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..f12b5fe --- /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 halfway_direction = normalize(light_dir + view_direction); + + float spec = + pow(max(dot(norm, halfway_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/renderer/opengl/vertex.rs b/engine/src/renderer/opengl/vertex.rs new file mode 100644 index 0000000..499b94b --- /dev/null +++ b/engine/src/renderer/opengl/vertex.rs @@ -0,0 +1,64 @@ +use crate::vector::{Vec2, Vec3}; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct Vertex +{ + pub pos: Vec3<f32>, + pub texture_coords: Vec2<f32>, + pub normal: Vec3<f32>, +} + +impl Vertex +{ + pub fn attrs() -> &'static [Attribute] + { + #[allow(clippy::cast_possible_truncation)] + &[ + Attribute { + index: 0, + component_type: AttributeComponentType::Float, + component_cnt: AttributeComponentCnt::Three, + component_size: size_of::<f32>() as u32, + }, + Attribute { + index: 1, + component_type: AttributeComponentType::Float, + component_cnt: AttributeComponentCnt::Two, + component_size: size_of::<f32>() as u32, + }, + Attribute { + index: 2, + component_type: AttributeComponentType::Float, + component_cnt: AttributeComponentCnt::Three, + component_size: size_of::<f32>() as u32, + }, + ] + } +} + +#[derive(Debug)] +pub struct Attribute +{ + pub index: u32, + pub component_type: AttributeComponentType, + pub component_cnt: AttributeComponentCnt, + pub component_size: u32, +} + +#[derive(Debug)] +pub enum AttributeComponentType +{ + Float, +} + +#[derive(Debug, Clone, Copy)] +#[repr(u32)] +#[allow(dead_code)] +pub enum AttributeComponentCnt +{ + One = 1, + Two = 2, + Three = 3, + Four = 4, +} |