diff --git a/TheRepo/TheLabs/Mesh.cs b/TheRepo/TheLabs/Mesh.cs index 27ad6f5..c6f8ece 100644 --- a/TheRepo/TheLabs/Mesh.cs +++ b/TheRepo/TheLabs/Mesh.cs @@ -2,8 +2,7 @@ using LearnOpenTK.Common; using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; -using OpenTK.Windowing.Desktop; -using StbImageSharp; +using System; public class Mesh : IDisposable { @@ -13,9 +12,7 @@ public class Mesh : IDisposable private readonly int _vertexCount; private readonly bool _useIndices; - // A flexible way to define vertex layout - // (Could be an enum or a struct) - public enum VertexLayout { PosColor, PosTex } + public enum VertexLayout { PosColor, PosTex, PosTexNormal } public Mesh(float[] vertices, uint[] indices, VertexLayout layout) { @@ -33,44 +30,22 @@ public class Mesh : IDisposable GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo); GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw); - // --- Set up Vertex Attributes based on layout --- - if (layout == VertexLayout.PosColor) - { - // layout(location = 0) in shader: vec3 position - var stride = 7 * sizeof(float); - GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); - GL.EnableVertexAttribArray(0); - - // layout(location = 1) in shader: vec4 color - GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); - GL.EnableVertexAttribArray(1); - } - else if (layout == VertexLayout.PosTex) - { - // layout(location = 0) in shader: vec3 position - var stride = 5 * sizeof(float); - GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0 ); - GL.EnableVertexAttribArray(0); + SetupAttributes(layout); // Refactored into a helper method to avoid duplication - // layout(location = 1) in shader: vec2 texCoord - GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); - GL.EnableVertexAttribArray(1); - } - GL.BindVertexArray(0); } - // Constructor for non-indexed drawing (like your Circle) public Mesh(float[] vertices, VertexLayout layout) { - // Calculate vertex count based on stride - if (layout == VertexLayout.PosColor) - _vertexCount = vertices.Length / 7; - else // PosTex - _vertexCount = vertices.Length / 5; + // Calculate vertex count based on the layout size + int strideCount = 0; + if (layout == VertexLayout.PosColor) strideCount = 7; + else if (layout == VertexLayout.PosTex) strideCount = 5; + else if (layout == VertexLayout.PosTexNormal) strideCount = 8; + _vertexCount = vertices.Length / strideCount; _useIndices = false; - _ebo = 0; // No EBO + _ebo = 0; _vao = GL.GenVertexArray(); GL.BindVertexArray(_vao); @@ -79,45 +54,73 @@ public class Mesh : IDisposable GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw); - // --- Set up Vertex Attributes based on layout --- - if (layout == VertexLayout.PosColor) - { - // layout(location = 0) in shader: vec3 position - var stride = 7 * sizeof(float); - GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); - GL.EnableVertexAttribArray(0); - - // layout(location = 1) in shader: vec4 color - GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); - GL.EnableVertexAttribArray(1); - } - else if (layout == VertexLayout.PosTex) - { - // layout(location = 0) in shader: vec3 position - var stride = 5 * sizeof(float); - GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); - GL.EnableVertexAttribArray(0); + SetupAttributes(layout); - // layout(location = 1) in shader: vec2 texCoord - GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); - GL.EnableVertexAttribArray(1); - } - GL.BindVertexArray(0); } - // Draw the mesh. Notice it doesn't know about shaders or matrices! + // Helper method to set up pointers (shared by both constructors) + private void SetupAttributes(VertexLayout layout) + { + if (layout == VertexLayout.PosColor) + { + // Stride = 7 floats (3 Pos + 4 Color) + var stride = 7 * sizeof(float); + + // Location 0: Position + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // Location 1: Color + GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(1); + + GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(2); + } + else if (layout == VertexLayout.PosTex) + { + // Stride = 5 floats (3 Pos + 2 Tex) + var stride = 5 * sizeof(float); + + // Location 0: Position + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // Location 2: TexCoord (I used 2 to match the Normal layout below, but 1 works too if you change shader) + // Let's keep it at 1 for now to break less code, but for lighting we usually move Tex to 2. + // For now, let's assume your shader uses Location 2 for Texture if using Normals. + GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(2); + } + else if (layout == VertexLayout.PosTexNormal) + { + // Stride = 8 floats (3 Pos + 3 Normal + 2 Tex) + var stride = 8 * sizeof(float); + + // Location 0: Position + GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0); + GL.EnableVertexAttribArray(0); + + // Location 1: Normal (Offset 3 floats) + GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float)); + GL.EnableVertexAttribArray(1); + + // Location 2: TexCoord (Offset 6 floats: 3 pos + 3 normal) + GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, 6 * sizeof(float)); + GL.EnableVertexAttribArray(2); + } + } + public void Draw() { GL.BindVertexArray(_vao); - if (_useIndices) { GL.DrawElements(PrimitiveType.Triangles, _vertexCount, DrawElementsType.UnsignedInt, 0); } else { - // For your Circle (which used TriangleFan) GL.DrawArrays(PrimitiveType.TriangleFan, 0, _vertexCount); } } diff --git a/TheRepo/TheLabs/ModelLoader.cs b/TheRepo/TheLabs/ModelLoader.cs index 9e1faab..80f6432 100644 --- a/TheRepo/TheLabs/ModelLoader.cs +++ b/TheRepo/TheLabs/ModelLoader.cs @@ -14,7 +14,7 @@ public static class ModelLoader var scene = importer.ImportFile(path, PostProcessSteps.Triangulate | PostProcessSteps.JoinIdenticalVertices); if (scene == null || scene.SceneFlags.HasFlag(SceneFlags.Incomplete) || scene.RootNode == null) - { + { throw new Exception("Error loading model: " + path); } @@ -37,7 +37,16 @@ public static class ModelLoader allVertices.Add(pos.X); allVertices.Add(pos.Y); allVertices.Add(pos.Z); - + // 2. Normals (NEW) + if (mesh.HasNormals) + { + var n = mesh.Normals[i]; + allVertices.Add(n.X); allVertices.Add(n.Y); allVertices.Add(n.Z); + } + else + { + allVertices.Add(0); allVertices.Add(1); allVertices.Add(0); // Default up + } // Texture Coordinates (u, v) if (mesh.HasTextureCoords(0)) { diff --git a/TheRepo/TheLabs/MyExampleWindow.cs b/TheRepo/TheLabs/MyExampleWindow.cs index a81e44a..798cac6 100644 --- a/TheRepo/TheLabs/MyExampleWindow.cs +++ b/TheRepo/TheLabs/MyExampleWindow.cs @@ -65,25 +65,17 @@ namespace TheLabs GL.Enable(EnableCap.DepthTest); _camera = new Camera(_cameraPosition, _cameraFront, _cameraUp); _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _texture = new Texture("Textures/texture-a.png"); + _texture = new Texture("Textures/placeholder.png"); _rootNode = new SceneNode(); - _exampleObject = ModelLoader.LoadMesh("Objects/cat.obj"); - _buildingObject = ModelLoader.LoadMesh("Objects/building1.obj"); - _render = new RenderObject(_exampleObject, _shader, _texture); + _buildingObject = ShapeFactory.CreateTexturedCube(); _buildingRender = new RenderObject(_buildingObject, _shader, _texture); - _characterNode = new SceneNode(_render); - _characterNode.Position = new Vector3(0.0f, -1.0f, 0.0f); - _rootNode.AddChild(_characterNode); - _buildingNode = new SceneNode(_buildingRender); _buildingNode.Position = new Vector3(2.0f, -1.0f, 0.0f); + _buildingNode.Scale = new Vector3(0.1f, 0.1f, 0.1f); _rootNode.AddChild(_buildingNode); - - - CursorState = CursorState.Grabbed; diff --git a/TheRepo/TheLabs/RenderObject.cs b/TheRepo/TheLabs/RenderObject.cs index 05fa7fb..41da021 100644 --- a/TheRepo/TheLabs/RenderObject.cs +++ b/TheRepo/TheLabs/RenderObject.cs @@ -21,15 +21,22 @@ public class RenderObject Shader = shader; Texture = texture; } + + public RenderObject(Mesh mesh, Shader shader) + { + Mesh = mesh; + Shader = shader; + } public void Draw(Matrix4 view, Matrix4 projection) { // 1. Activate Shader Shader.Use(); - Shader.SetInt("uTexture", 0); // 2. Bind Texture Texture.Use(); + Shader.SetInt("uTexture", 0); // 3. Calculate Model Matrix + Matrix4 model = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position); int modelLoc = GL.GetUniformLocation(Shader.Handle, "model"); GL.UniformMatrix4(modelLoc, false, ref model); // Try TRUE here diff --git a/TheRepo/TheLabs/Shaders/shader.frag b/TheRepo/TheLabs/Shaders/shader.frag index f93b27a..9bdd061 100644 --- a/TheRepo/TheLabs/Shaders/shader.frag +++ b/TheRepo/TheLabs/Shaders/shader.frag @@ -1,11 +1,37 @@ -#version 330 +#version 330 core -in vec4 outColour; out vec4 outputColor; + +// --- INPUTS --- +in vec3 FragPos; +in vec3 Normal; in vec2 TexCoord; + +// --- UNIFORMS --- uniform sampler2D uTexture; void main() { - outputColor = texture(uTexture, TexCoord); + // Lighting Setup + vec3 lightPos = vec3(1.2f, 1.0f, 2.0f); + vec3 lightColor = vec3(1.0f, 1.0f, 1.0f); + + // 1. Get Texture Color (Keep it as vec4!) + vec4 objColor = texture(uTexture, TexCoord); + + // 2. Ambient + float ambientStrength = 0.1f; + vec3 ambient = ambientStrength * lightColor; + + // 3. Diffuse + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(lightPos - FragPos); + float diff = max(dot(norm, lightDir), 0.0f); + vec3 diffuse = diff * lightColor; + + // 4. Combine + // Note: We use objColor.rgb (vec3) to match the lighting calculation + vec3 result = (ambient + diffuse) * objColor.rgb; + + outputColor = vec4(result, objColor.a); } \ No newline at end of file diff --git a/TheRepo/TheLabs/Shaders/shader.vert b/TheRepo/TheLabs/Shaders/shader.vert index 21f0c8f..6ac83f9 100644 --- a/TheRepo/TheLabs/Shaders/shader.vert +++ b/TheRepo/TheLabs/Shaders/shader.vert @@ -1,48 +1,31 @@ -// For more information on how shaders work, check out the web version of this tutorial. -// I'll include a simpler summary here. - -// First non-comment line should always be a #version statement; this just tells the GLSL compiler what version it should use. #version 330 core -// GLSL's syntax is somewhat like C, but it has a few differences. - -// There are four different types of variables in GLSL: input, output, uniform, and internal. -// - Input variables are sent from the buffer, in a way defined by GL.VertexAttribPointer. -// - Output variables are sent from this shader to the next one in the chain (which will be the fragment shader most of the time). -// - Uniforms will be touched on in the next tutorial. -// - Internal variables are defined in the shader file and only used there. - - -// The vertex shader is run once for every vertex. In C# pseudocode, it might look something like: -// foreach(var vertex in vertices) -// shader(vertex) - - -// This defines our input variable, aPosition. -// It starts with the line "layout(location = 0)". This defines where this input variable will be located, which is needed for GL.VertexAttribPointer. -// However, you can omit it, and replace this with just "in vec3 aPosition". If you do that, you'll have to replace the 0 in GL.VertexAttribPointer with -// a call to GL.GetAttribLocation(shaderHandle, attributeName) -// Next, the keyword "in" defines this as an input variable. We'll have an example of the "out" keyword in the next tutorial. -// Then, the keyword "vec3" means this is a vector with 3 floats inside. - +// --- INPUTS --- layout(location = 0) in vec3 aPosition; -layout(location = 1)in vec2 aTexCoord; -out vec4 outColour; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +// --- OUTPUTS --- +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoord; + +// --- UNIFORMS --- uniform mat4 model; uniform mat4 view; uniform mat4 projection; -out vec2 TexCoord; - -// Like C, we have an entrypoint function. In this case, it takes void and returns void, and must be named main. -// You can do all sorts of calculations here to modify your vertices, but right now, we don't need to do any of that. -// gl_Position is the final vertex position; pass a vec4 to it and you're done. -// Keep in mind that we only pass a vec3 to this shader; the fourth component of a vertex is known as "w". -// It's only used in some more advanced OpenGL functions; it's not needed here. -// So with a call to the vec4 function, we just give it a constant value of 1.0. - void main(void) { - gl_Position = projection * view * model * vec4(aPosition, 1.0); + // 1. Calculate World Position + FragPos = vec3(model * vec4(aPosition, 1.0)); + + // 2. Calculate Normal + Normal = mat3(transpose(inverse(model))) * aNormal; + + // 3. Pass Texture Coordinates TexCoord = aTexCoord; + + // 4. Calculate Final Position + gl_Position = projection * view * vec4(FragPos, 1.0); } \ No newline at end of file diff --git a/TheRepo/TheLabs/ShapeFactory.cs b/TheRepo/TheLabs/ShapeFactory.cs index 300b176..a0a3d23 100644 --- a/TheRepo/TheLabs/ShapeFactory.cs +++ b/TheRepo/TheLabs/ShapeFactory.cs @@ -5,40 +5,43 @@ public static class ShapeFactory public static Mesh CreateTexturedCube() { float[] vertices = { - // Front face (z = +0.5) - -0.5f, -0.5f, 0.5f, 0f, 0f, // Bottom-left - 0.5f, -0.5f, 0.5f, 1f, 0f, // Bottom-right - 0.5f, 0.5f, 0.5f, 1f, 1f, // Top-right - -0.5f, 0.5f, 0.5f, 0f, 1f, // Top-left + // Format: X, Y, Z, NX, NY, NZ, U, V - // Back face (z = -0.5) - -0.5f, -0.5f, -0.5f, 1f, 0f, // Bottom-left - 0.5f, -0.5f, -0.5f, 0f, 0f, // Bottom-right - 0.5f, 0.5f, -0.5f, 0f, 1f, // Top-right - -0.5f, 0.5f, -0.5f, 1f, 1f, // Top-left - - // Left face (x = -0.5) - -0.5f, -0.5f, -0.5f, 0f, 0f, // Bottom-left - -0.5f, -0.5f, 0.5f, 1f, 0f, // Bottom-right - -0.5f, 0.5f, 0.5f, 1f, 1f, // Top-right - -0.5f, 0.5f, -0.5f, 0f, 1f, // Top-left - // Right face (x = +0.5) - 0.5f, -0.5f, -0.5f, 1f, 0f, // Bottom-left - 0.5f, -0.5f, 0.5f, 0f, 0f, // Bottom-right - 0.5f, 0.5f, 0.5f, 0f, 1f, // Top-right - 0.5f, 0.5f, -0.5f, 1f, 1f, // Top-left - - // Top face (y = +0.5) - -0.5f, 0.5f, -0.5f, 0f, 1f, // Bottom-left - 0.5f, 0.5f, -0.5f, 1f, 1f, // Bottom-right - 0.5f, 0.5f, 0.5f, 1f, 0f, // Top-right - -0.5f, 0.5f, 0.5f, 0f, 0f, // Top-left - - // Bottom face (y = -0.5) - -0.5f, -0.5f, -0.5f, 1f, 1f, // Bottom-left - 0.5f, -0.5f, -0.5f, 0f, 1f, // Bottom-right - 0.5f, -0.5f, 0.5f, 0f, 0f, // Top-right - -0.5f, -0.5f, 0.5f, 1f, 0f, // Top-left + // --- Front Face (Normal points +Z: 0, 0, 1) --- + -0.5f, -0.5f, 0.5f, 0f, 0f, 1f, 0f, 0f, // Bottom-left + 0.5f, -0.5f, 0.5f, 0f, 0f, 1f, 1f, 0f, // Bottom-right + 0.5f, 0.5f, 0.5f, 0f, 0f, 1f, 1f, 1f, // Top-right + -0.5f, 0.5f, 0.5f, 0f, 0f, 1f, 0f, 1f, // Top-left + + // --- Back Face (Normal points -Z: 0, 0, -1) --- + -0.5f, -0.5f, -0.5f, 0f, 0f, -1f, 1f, 0f, + 0.5f, -0.5f, -0.5f, 0f, 0f, -1f, 0f, 0f, + 0.5f, 0.5f, -0.5f, 0f, 0f, -1f, 0f, 1f, + -0.5f, 0.5f, -0.5f, 0f, 0f, -1f, 1f, 1f, + + // --- Left Face (Normal points -X: -1, 0, 0) --- + -0.5f, -0.5f, -0.5f, -1f, 0f, 0f, 0f, 0f, + -0.5f, -0.5f, 0.5f, -1f, 0f, 0f, 1f, 0f, + -0.5f, 0.5f, 0.5f, -1f, 0f, 0f, 1f, 1f, + -0.5f, 0.5f, -0.5f, -1f, 0f, 0f, 0f, 1f, + + // --- Right Face (Normal points +X: 1, 0, 0) --- + 0.5f, -0.5f, -0.5f, 1f, 0f, 0f, 1f, 0f, + 0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 0f, 0f, + 0.5f, 0.5f, 0.5f, 1f, 0f, 0f, 0f, 1f, + 0.5f, 0.5f, -0.5f, 1f, 0f, 0f, 1f, 1f, + + // --- Top Face (Normal points +Y: 0, 1, 0) --- + -0.5f, 0.5f, -0.5f, 0f, 1f, 0f, 0f, 1f, + 0.5f, 0.5f, -0.5f, 0f, 1f, 0f, 1f, 1f, + 0.5f, 0.5f, 0.5f, 0f, 1f, 0f, 1f, 0f, + -0.5f, 0.5f, 0.5f, 0f, 1f, 0f, 0f, 0f, + + // --- Bottom Face (Normal points -Y: 0, -1, 0) --- + -0.5f, -0.5f, -0.5f, 0f, -1f, 0f, 1f, 1f, + 0.5f, -0.5f, -0.5f, 0f, -1f, 0f, 0f, 1f, + 0.5f, -0.5f, 0.5f, 0f, -1f, 0f, 0f, 0f, + -0.5f, -0.5f, 0.5f, 0f, -1f, 0f, 1f, 0f }; @@ -63,7 +66,7 @@ public static class ShapeFactory 20, 22, 23 }; - return new Mesh(vertices, indices, Mesh.VertexLayout.PosTex); + return new Mesh(vertices, indices, Mesh.VertexLayout.PosTexNormal); } public static Mesh CreateColouredCircle() @@ -123,17 +126,16 @@ public static class ShapeFactory public static Mesh CreateColorCube() { float[] vertices = { - // Front face (z = +0.5) - -0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, // Bottom-left - 0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, // Bottom-right - 0.5f, 0.5f, 0.5f, 0f, 0f, 0f, 1f, // Top-right - -0.5f, 0.5f, 0.5f, 1f, 0f, 0f, 1f, // Top-left + -0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, // Bottom-left + 0.5f, -0.5f, 0.5f, 0f, 1f, 0f, 1f, 0f, 0f, 1f, // Bottom-right + 0.5f, 0.5f, 0.5f, 0f, 0f, 1f, 1f, 0f, 0f, 1f, // Top-right + -0.5f, 0.5f, 0.5f, 1f, 1f, 0f, 1f, 0f, 0f, 1f, // Top-left // Back face (z = -0.5) - -0.5f, -0.5f, -0.5f, 1f, 0f, 1f, 1f, // Bottom-left - 0.5f, -0.5f, -0.5f, 0f, 1f, 1f, 1f, // Bottom-right - 0.5f, 0.5f, -0.5f, 1f, 1f, 1f, 1f, // Top-right - -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 1f // Top-left + -0.5f, -0.5f, -0.5f, 1f, 0f, 1f, 1f, 0f, 0f, -1f, // Bottom-left + 0.5f, -0.5f, -0.5f, 0f, 1f, 1f, 1f, 0f, 0f, -1f, // Bottom-right + 0.5f, 0.5f, -0.5f, 1f, 1f, 1f, 1f, 0f, 0f, -1f, // Top-right + -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 1f, 0f, 0f, -1f, // Top-left }; uint[] indices = {