From f85f5afc5a4a50c5c87a236bc76f8a08d50e7137 Mon Sep 17 00:00:00 2001 From: HampusM Date: Wed, 16 Sep 2026 12:42:45 +0200 Subject: fix(engine): make textures not flipped --- engine/res/imgui_shader.slang | 2 +- engine/res/main_3d_shader.slang | 2 +- engine/src/file_format/wavefront/obj.rs | 43 +++++++- engine/src/mesh.rs | 179 ++++++++++++++++++++++++-------- engine/src/mesh/vertex_buffer.rs | 4 +- engine/src/model/asset.rs | 42 +++++++- engine/src/rendering/shader.rs | 30 +++--- engine/src/ui/dear_imgui.rs | 4 +- 8 files changed, 235 insertions(+), 71 deletions(-) diff --git a/engine/res/imgui_shader.slang b/engine/res/imgui_shader.slang index 29ad9bf..0277226 100644 --- a/engine/res/imgui_shader.slang +++ b/engine/res/imgui_shader.slang @@ -2,7 +2,7 @@ struct Vertex { float3 pos : STD_POSITION; float4 color : STD_COLOR; - float2 texture_coords : STD_UV; + float2 texture_coords : STD_UV_FROM_TOP_LEFT; }; struct VertexData diff --git a/engine/res/main_3d_shader.slang b/engine/res/main_3d_shader.slang index 7da164c..d1a1ef3 100644 --- a/engine/res/main_3d_shader.slang +++ b/engine/res/main_3d_shader.slang @@ -225,7 +225,7 @@ struct VertexStageOutput struct Vertex { float3 pos : STD_POSITION; - float2 texture_coords : STD_UV; + float2 texture_coords : STD_UV_FROM_TOP_LEFT; float3 normal : STD_NORMAL; }; diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs index bebe291..dbcee33 100644 --- a/engine/src/file_format/wavefront/obj.rs +++ b/engine/src/file_format/wavefront/obj.rs @@ -92,7 +92,7 @@ impl Obj /// - A face's texture position cannot be found /// - A face's vertex normal cannot be found /// - A face index does not fit in a [`u32`] - pub fn to_mesh(&self) -> Result + pub fn to_mesh(&self, options: ToMeshOptions) -> Result { let mut vertex_buf = MeshVertexBuffer::with_capacity( &[ @@ -101,7 +101,7 @@ impl Obj ty: VertexAttrType::Float32Array { length: 3 }, }, VertexAttrInfo { - label: VertexLabel::Uv, + label: VertexLabel::UvFromTopLeft, ty: VertexAttrType::Float32Array { length: 2 }, }, VertexAttrInfo { @@ -154,6 +154,11 @@ impl Obj }, )?; + let texture_pos = options + .y_flip_uvs + .then(|| Vec2 { x: texture_pos.x, y: -texture_pos.y }) + .unwrap_or(texture_pos); + let normal = face_vertex.normal.map_or_else( || { if !self.vertex_normals.is_empty() { @@ -181,7 +186,7 @@ impl Obj value: pos.into_array(), }, NamedVertexAttr { - label: VertexLabel::Uv, + label: VertexLabel::UvFromTopLeft, value: texture_pos.into_array(), }, NamedVertexAttr { @@ -437,6 +442,38 @@ impl Obj } } +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct ToMeshOptions +{ + /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs + /// have their origin at the bottom left corner of the image + /// + /// The default is `true`. + pub y_flip_uvs: bool, +} + +impl ToMeshOptions +{ + /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs have + /// their origin at the bottom left corner of the image + /// + /// The default is `true`. + pub fn y_flip_uvs(mut self, y_flip_uvs: bool) -> Self + { + self.y_flip_uvs = y_flip_uvs; + self + } +} + +impl Default for ToMeshOptions +{ + fn default() -> Self + { + Self { y_flip_uvs: true } + } +} + #[derive(Debug)] #[non_exhaustive] pub struct Face diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index fff5804..c1c0f77 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -28,33 +28,33 @@ impl Mesh size, top_face: CubeFaceOptions { - uv_max: top_uv_max, - uv_min: top_uv_min, + uv_upper_left: top_uv_upper_left, + uv_lower_right: top_uv_lower_right, }, bottom_face: CubeFaceOptions { - uv_max: bot_uv_max, - uv_min: bot_uv_min, + uv_upper_left: bot_uv_upper_left, + uv_lower_right: bot_uv_lower_right, }, left_face: CubeFaceOptions { - uv_max: left_uv_max, - uv_min: left_uv_min, + uv_upper_left: left_uv_upper_left, + uv_lower_right: left_uv_lower_right, }, right_face: CubeFaceOptions { - uv_max: right_uv_max, - uv_min: right_uv_min, + uv_upper_left: right_uv_upper_left, + uv_lower_right: right_uv_lower_right, }, back_face: CubeFaceOptions { - uv_max: back_uv_max, - uv_min: back_uv_min, + uv_upper_left: back_uv_upper_left, + uv_lower_right: back_uv_lower_right, }, front_face: CubeFaceOptions { - uv_max: front_uv_max, - uv_min: front_uv_min, + uv_upper_left: front_uv_upper_left, + uv_lower_right: front_uv_lower_right, }, } = options; @@ -74,50 +74,122 @@ impl Mesh } let top_vertices = [ - ([half_w, half_h, half_d], [top_uv_max.x, top_uv_max.y]), - ([-half_w, half_h, half_d], [top_uv_min.x, top_uv_max.y]), - ([half_w, half_h, -half_d], [top_uv_max.x, top_uv_min.y]), - ([-half_w, half_h, -half_d], [top_uv_min.x, top_uv_min.y]), + ( + [half_w, half_h, half_d], + [top_uv_upper_left.x, top_uv_upper_left.y], + ), + ( + [-half_w, half_h, half_d], + [top_uv_lower_right.x, top_uv_upper_left.y], + ), + ( + [half_w, half_h, -half_d], + [top_uv_upper_left.x, top_uv_lower_right.y], + ), + ( + [-half_w, half_h, -half_d], + [top_uv_lower_right.x, top_uv_lower_right.y], + ), ] .map(|(pos, uv)| vertex(pos, uv, Vec3::UP)); let bottom_vertices = [ - ([half_w, -half_h, half_d], [bot_uv_max.x, bot_uv_max.y]), - ([-half_w, -half_h, half_d], [bot_uv_min.x, bot_uv_max.y]), - ([half_w, -half_h, -half_d], [bot_uv_max.x, bot_uv_min.y]), - ([-half_w, -half_h, -half_d], [bot_uv_min.x, bot_uv_min.y]), + ( + [half_w, -half_h, half_d], + [bot_uv_upper_left.x, bot_uv_upper_left.y], + ), + ( + [-half_w, -half_h, half_d], + [bot_uv_lower_right.x, bot_uv_upper_left.y], + ), + ( + [half_w, -half_h, -half_d], + [bot_uv_upper_left.x, bot_uv_lower_right.y], + ), + ( + [-half_w, -half_h, -half_d], + [bot_uv_lower_right.x, bot_uv_lower_right.y], + ), ] .map(|(pos, uv)| vertex(pos, uv, Vec3::DOWN)); let left_vertices = [ - ([-half_w, half_h, half_d], [left_uv_max.x, left_uv_max.y]), - ([-half_w, half_h, -half_d], [left_uv_min.x, left_uv_max.y]), - ([-half_w, -half_h, half_d], [left_uv_max.x, left_uv_min.y]), - ([-half_w, -half_h, -half_d], [left_uv_min.x, left_uv_min.y]), + ( + [-half_w, half_h, half_d], + [left_uv_upper_left.x, left_uv_upper_left.y], + ), + ( + [-half_w, half_h, -half_d], + [left_uv_lower_right.x, left_uv_upper_left.y], + ), + ( + [-half_w, -half_h, half_d], + [left_uv_upper_left.x, left_uv_lower_right.y], + ), + ( + [-half_w, -half_h, -half_d], + [left_uv_lower_right.x, left_uv_lower_right.y], + ), ] .map(|(pos, uv)| vertex(pos, uv, Vec3::LEFT)); let right_vertices = [ - ([half_w, half_h, half_d], [right_uv_max.x, right_uv_max.y]), - ([half_w, half_h, -half_d], [right_uv_min.x, right_uv_max.y]), - ([half_w, -half_h, half_d], [right_uv_max.x, right_uv_min.y]), - ([half_w, -half_h, -half_d], [right_uv_min.x, right_uv_min.y]), + ( + [half_w, half_h, half_d], + [right_uv_upper_left.x, right_uv_upper_left.y], + ), + ( + [half_w, half_h, -half_d], + [right_uv_lower_right.x, right_uv_upper_left.y], + ), + ( + [half_w, -half_h, half_d], + [right_uv_upper_left.x, right_uv_lower_right.y], + ), + ( + [half_w, -half_h, -half_d], + [right_uv_lower_right.x, right_uv_lower_right.y], + ), ] .map(|(pos, uv)| vertex(pos, uv, Vec3::RIGHT)); let back_vertices = [ - ([half_w, half_h, -half_d], [back_uv_max.x, back_uv_max.y]), - ([-half_w, half_h, -half_d], [back_uv_min.x, back_uv_max.y]), - ([half_w, -half_h, -half_d], [back_uv_max.x, back_uv_min.y]), - ([-half_w, -half_h, -half_d], [back_uv_min.x, back_uv_min.y]), + ( + [half_w, half_h, -half_d], + [back_uv_upper_left.x, back_uv_upper_left.y], + ), + ( + [-half_w, half_h, -half_d], + [back_uv_lower_right.x, back_uv_upper_left.y], + ), + ( + [half_w, -half_h, -half_d], + [back_uv_upper_left.x, back_uv_lower_right.y], + ), + ( + [-half_w, -half_h, -half_d], + [back_uv_lower_right.x, back_uv_lower_right.y], + ), ] .map(|(pos, uv)| vertex(pos, uv, Vec3::BACK)); let front_vertices = [ - ([half_w, half_h, half_d], [front_uv_max.x, front_uv_max.y]), - ([-half_w, half_h, half_d], [front_uv_min.x, front_uv_max.y]), - ([half_w, -half_h, half_d], [front_uv_max.x, front_uv_min.y]), - ([-half_w, -half_h, half_d], [front_uv_min.x, front_uv_min.y]), + ( + [half_w, half_h, half_d], + [front_uv_upper_left.x, front_uv_upper_left.y], + ), + ( + [-half_w, half_h, half_d], + [front_uv_lower_right.x, front_uv_upper_left.y], + ), + ( + [half_w, -half_h, half_d], + [front_uv_upper_left.x, front_uv_lower_right.y], + ), + ( + [-half_w, -half_h, half_d], + [front_uv_lower_right.x, front_uv_lower_right.y], + ), ] .map(|(pos, uv)| vertex(pos, uv, Vec3::FRONT)); @@ -132,7 +204,7 @@ impl Mesh byte_offset: 0, }, VertexAttrProperties { - label: VertexLabel::Uv, + label: VertexLabel::UvFromTopLeft, ty: VertexAttrType::Float32Array { length: 2 }, layout: Layout::new::<[f32; 2]>(), byte_offset: size_of::<[f32; 3]>(), @@ -378,21 +450,38 @@ impl Default for CubeOptions #[non_exhaustive] pub struct CubeFaceOptions { - pub uv_max: Vec2, - pub uv_min: Vec2, + /// Seeing the face head on, this is the texture coordinate for the corner that + /// would be on the upper left of the face. + /// + /// The default is `(0, 0)` + pub uv_upper_left: Vec2, + + /// Seeing the face head on, this is the texture coordinate for the corner that + /// would be on the lower right of the face. + /// + /// The default is `(1, 1)` + pub uv_lower_right: Vec2, } impl CubeFaceOptions { - pub fn uv_max(mut self, uv_max: Vec2) -> Self + /// Seeing the face head on, this is the texture coordinate for the corner that + /// would be on the upper left of the face. + /// + /// The default is `(0, 0)` + pub fn uv_upper_left(mut self, uv_upper_left: Vec2) -> Self { - self.uv_max = uv_max; + self.uv_upper_left = uv_upper_left; self } - pub fn uv_min(mut self, uv_min: Vec2) -> Self + /// Seeing the face head on, this is the texture coordinate for the corner that + /// would be on the lower right of the face. + /// + /// The default is `(1, 1)` + pub fn uv_lower_right(mut self, uv_lower_right: Vec2) -> Self { - self.uv_min = uv_min; + self.uv_lower_right = uv_lower_right; self } } @@ -402,8 +491,8 @@ impl Default for CubeFaceOptions fn default() -> Self { Self { - uv_max: Vec2 { x: 1.0, y: 1.0 }, - uv_min: Vec2 { x: 0.0, y: 0.0 }, + uv_upper_left: Vec2 { x: 0.0, y: 0.0 }, + uv_lower_right: Vec2 { x: 1.0, y: 1.0 }, } } } diff --git a/engine/src/mesh/vertex_buffer.rs b/engine/src/mesh/vertex_buffer.rs index 90e5752..5c03b45 100644 --- a/engine/src/mesh/vertex_buffer.rs +++ b/engine/src/mesh/vertex_buffer.rs @@ -58,7 +58,9 @@ pub enum VertexLabel { Position, Normal, - Uv, + + /// Texture coordinate with (0, 0) being at the top left. + UvFromTopLeft, Color, Other(Cow<'static, str>), } diff --git a/engine/src/model/asset.rs b/engine/src/model/asset.rs index 69a06aa..5487203 100644 --- a/engine/src/model/asset.rs +++ b/engine/src/model/asset.rs @@ -7,7 +7,35 @@ use crate::model::{Materials, Spec}; #[derive(Debug, Clone)] #[non_exhaustive] -pub struct Settings {} +pub struct Settings +{ + /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs + /// have their origin at the bottom left corner of the image + /// + /// The default is `true`. + pub y_flip_uvs: bool, +} + +impl Settings +{ + /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs have + /// their origin at the bottom left corner of the image + /// + /// The default is `true`. + pub fn y_flip_uvs(mut self, y_flip_uvs: bool) -> Self + { + self.y_flip_uvs = y_flip_uvs; + self + } +} + +impl Default for Settings +{ + fn default() -> Self + { + Self { y_flip_uvs: true } + } +} pub fn add_importers(assets: &mut Assets) { @@ -17,9 +45,14 @@ pub fn add_importers(assets: &mut Assets) fn import_wavefront_obj_asset( asset_submitter: &mut AssetSubmitter<'_>, path: &Path, - _settings: Option<&'_ Settings>, + settings: Option<&Settings>, ) -> Result<(), Error> { + let settings = match settings { + Some(settings) => settings, + None => &Settings::default(), + }; + let parent_path = path .parent() .ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?; @@ -29,7 +62,10 @@ fn import_wavefront_obj_asset( .map_err(|err| Error::ReadFailed(err, path.to_path_buf()))?, )?; - let mesh = obj.to_mesh()?; + let mesh = obj.to_mesh( + crate::file_format::wavefront::obj::ToMeshOptions::default() + .y_flip_uvs(settings.y_flip_uvs), + )?; let mesh_asset = asset_submitter.submit_store_named("mesh", mesh); diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs index 12fc9c1..3f07381 100644 --- a/engine/src/rendering/shader.rs +++ b/engine/src/rendering/shader.rs @@ -38,10 +38,10 @@ use crate::mesh::vertex_buffer::VertexLabel; pub mod cursor; -pub const STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION: &str = "STD_POSITION"; -pub const STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL: &str = "STD_NORMAL"; -pub const STD_VERTEX_INPUT_SEMANTIC_NAME_UV: &str = "STD_UV"; -pub const STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR: &str = "STD_COLOR"; +pub const STD_VERT_IN_SEM_NAME_POSITION: &str = "STD_POSITION"; +pub const STD_VERT_IN_SEM_NAME_NORMAL: &str = "STD_NORMAL"; +pub const STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT: &str = "STD_UV_FROM_TOP_LEFT"; +pub const STD_VERT_IN_SEM_NAME_COLOR: &str = "STD_COLOR"; /// Shader module. #[derive(Debug)] @@ -1054,7 +1054,7 @@ pub enum VertexInputSemName { Position, Normal, - Uv, + UvFromTopLeft, Color, Other(Box), } @@ -1064,10 +1064,10 @@ impl VertexInputSemName fn from_semantic_name(semantic_name: &str) -> Self { match semantic_name { - STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION => Self::Position, - STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL => Self::Normal, - STD_VERTEX_INPUT_SEMANTIC_NAME_UV => Self::Uv, - STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR => Self::Color, + STD_VERT_IN_SEM_NAME_POSITION => Self::Position, + STD_VERT_IN_SEM_NAME_NORMAL => Self::Normal, + STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT => Self::UvFromTopLeft, + STD_VERT_IN_SEM_NAME_COLOR => Self::Color, _ => Self::Other(semantic_name.to_lowercase().into_boxed_str()), } } @@ -1077,7 +1077,7 @@ impl VertexInputSemName match (self, vertex_label) { (Self::Position, VertexLabel::Position) | (Self::Normal, VertexLabel::Normal) - | (Self::Uv, VertexLabel::Uv) + | (Self::UvFromTopLeft, VertexLabel::UvFromTopLeft) | (Self::Color, VertexLabel::Color) => true, (Self::Other(other), VertexLabel::Other(other_vertex_label)) => { **other == *other_vertex_label @@ -1092,12 +1092,12 @@ impl Display for VertexInputSemName fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Position => { - formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION) + Self::Position => formatter.write_str(STD_VERT_IN_SEM_NAME_POSITION), + Self::Normal => formatter.write_str(STD_VERT_IN_SEM_NAME_NORMAL), + Self::UvFromTopLeft => { + formatter.write_str(STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT) } - Self::Normal => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL), - Self::Uv => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_UV), - Self::Color => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR), + Self::Color => formatter.write_str(STD_VERT_IN_SEM_NAME_COLOR), Self::Other(other) => { for character in other.chars() { formatter.write_char(character.to_ascii_uppercase())?; diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs index 4da5c5f..0a50ff7 100644 --- a/engine/src/ui/dear_imgui.rs +++ b/engine/src/ui/dear_imgui.rs @@ -395,7 +395,7 @@ fn initialize_context( ty: MeshVertexAttrType::Float32Array { length: 4 }, }, MeshVertexAttrInfo { - label: VertexLabel::Uv, + label: VertexLabel::UvFromTopLeft, ty: MeshVertexAttrType::Float32Array { length: 2 }, }, ], @@ -718,7 +718,7 @@ fn add_drawing_render_pass( value: vertex.rgba().map(|elem| (elem as f32) / 255.0), }, MeshNamedVertexAttr { - label: VertexLabel::Uv, + label: VertexLabel::UvFromTopLeft, value: vertex.uv, }, )); -- cgit v1.2.3-18-g5258