From fe63a6d9380fed35006642dd44d3fe33e78e2595 Mon Sep 17 00:00:00 2001 From: zyb3rwolfi Date: Tue, 25 Nov 2025 14:46:16 +0000 Subject: [PATCH] Finished lighting shaders --- .../.idea/.idea.TheRepo/.idea/indexLayout.xml | 4 +- TheRepo/TheLabs/Lighting.cs | 29 ++++++++ TheRepo/TheLabs/Material.cs | 30 ++++++++ TheRepo/TheLabs/MyExampleWindow.cs | 60 ++++++++++------ TheRepo/TheLabs/RenderObject.cs | 22 ++++-- TheRepo/TheLabs/SceneNode.cs | 6 +- TheRepo/TheLabs/Shaders/shader.frag | 66 +++++++++++++++--- TheRepo/TheLabs/Shaders/shader.vert | 13 ++-- TheRepo/TheLabs/ShapeFactory.cs | 68 +++++++++++++++++++ TheRepo/TheLabs/Shapes/Bus.cs | 4 +- TheRepo/TheLabs/TheLabs.csproj | 4 ++ 11 files changed, 258 insertions(+), 48 deletions(-) create mode 100644 TheRepo/TheLabs/Lighting.cs create mode 100644 TheRepo/TheLabs/Material.cs diff --git a/TheRepo/.idea/.idea.TheRepo/.idea/indexLayout.xml b/TheRepo/.idea/.idea.TheRepo/.idea/indexLayout.xml index 7da28d5..48aedf3 100644 --- a/TheRepo/.idea/.idea.TheRepo/.idea/indexLayout.xml +++ b/TheRepo/.idea/.idea.TheRepo/.idea/indexLayout.xml @@ -6,6 +6,8 @@ TheLabs/Textures - + + TheLabs/Shapes/Bus.cs + \ No newline at end of file diff --git a/TheRepo/TheLabs/Lighting.cs b/TheRepo/TheLabs/Lighting.cs new file mode 100644 index 0000000..211200d --- /dev/null +++ b/TheRepo/TheLabs/Lighting.cs @@ -0,0 +1,29 @@ +using LearnOpenTK.Common; +using OpenTK.Graphics.OpenGL; +using OpenTK.Mathematics; + +namespace TheLabs; + +public struct Lighting +{ + public Vector3 position; + public Vector3 ambientColour; + public Vector3 diffuseColour; + public Vector3 specularColour; + + public Lighting(Vector3 pos, Vector3 ambient, Vector3 diffuse, Vector3 specular) + { + position = pos; + ambientColour = ambient; + diffuseColour = diffuse; + specularColour = specular; + } + + public void Apply(Shader shader) + { + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uPointLight.position"), ref position); + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uPointLight.ambientColour"), ref ambientColour); + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uPointLight.diffuseColour"), ref diffuseColour); + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uPointLight.specularColour"), ref specularColour); + } +} \ No newline at end of file diff --git a/TheRepo/TheLabs/Material.cs b/TheRepo/TheLabs/Material.cs new file mode 100644 index 0000000..2512c5b --- /dev/null +++ b/TheRepo/TheLabs/Material.cs @@ -0,0 +1,30 @@ +using LearnOpenTK.Common; +using OpenTK.Graphics.OpenGL; +using OpenTK.Mathematics; + +namespace TheLabs; + +public struct Material +{ + public Vector3 ambientColour; + public Vector3 diffuseColour; + public Vector3 specularColour; + public float shininess; + + public Material(Vector3 ambient, Vector3 diffuse, Vector3 specular, float shininessFactor) + { + ambientColour = ambient; + diffuseColour = diffuse; + specularColour = specular; + shininess = shininessFactor; + } + + public void Apply(Shader shader) + { + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uMaterial.ambientColour"), ref ambientColour); + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uMaterial.diffuseColour"), ref diffuseColour); + GL.Uniform3(GL.GetUniformLocation(shader.Handle, "uMaterial.specularColour"), ref specularColour); + GL.Uniform1(GL.GetUniformLocation(shader.Handle, "uMaterial.shininess"), shininess); + } + +} \ No newline at end of file diff --git a/TheRepo/TheLabs/MyExampleWindow.cs b/TheRepo/TheLabs/MyExampleWindow.cs index 798cac6..b246d8e 100644 --- a/TheRepo/TheLabs/MyExampleWindow.cs +++ b/TheRepo/TheLabs/MyExampleWindow.cs @@ -11,39 +11,37 @@ namespace TheLabs { public class MyExampleWindow : GameWindow { - // Create the vertices for our triangle. These are listed in normalized device coordinates (NDC) - // In NDC, (0, 0) is the center of the screen. - // Negative X coordinates move to the left, positive X move to the right. - // Negative Y coordinates move to the bottom, positive Y move to the top. - // OpenGL only supports rendering in 3D, so to create a flat triangle, the Z coordinate will be kept as 0. + // -- Scene Graph Nodes private SceneNode _rootNode; private SceneNode _characterNode; private SceneNode _buildingNode; + + // -- OpenGL Resources private Shader _shader; private Camera _camera; + // - Resources that only need to be created once private Mesh _exampleObject; private Mesh _buildingObject; private Texture _texture; private RenderObject _render; private RenderObject _buildingRender; - float _rotation = 0.0f; - - // -- Scene Objects + // Camera parameters private Vector3 _cameraPosition = new Vector3(0.0f, 0.0f, -3.0f); private Vector3 _cameraFront = new Vector3(0.0f, 0.0f, -1.0f); - private Vector3 _cameraUp = Vector3.UnitY; - private float _yaw = -90.0f; - private float _pitch = 0.0f; - private float _cameraSpeed = 2.5f; private float _rotationSpeed = 50.0f; - - private float _mouseSensitivity = 0.1f; + private float _mouseSensitivity = 0.1f; + private float _pitch = 0.0f; + private float _rotation = 0.0f; + + // Materials and Lighting + private Lighting _mainLight; + private Material _defaultMaterial; // This prevents a large camera jump when the window first gets focus private bool _firstMove = true; @@ -58,24 +56,42 @@ namespace TheLabs { // This is called when the window is created and is where we can set up OpenGL resources. base.OnLoad(); - //GL.Disable(EnableCap.CullFace); - //GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); + // --- Set up Lighting and Material --- + _mainLight = new Lighting(new Vector3(1.2f, 1.0f, 2.0f), + new Vector3(0.3f), + new Vector3(1.5f), + new Vector3(1.0f)); + + _defaultMaterial = new Material( + new Vector3(0.5f, 0.5f, 0.5f) * 0.1f, + new Vector3(0.5f, 0.5f, 0.5f) * 0.6f, + new Vector3(1.0f, 1.0f, 1.0f), + 32.0f); + + // --- Set up OpenGL State --- GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); GL.Enable(EnableCap.DepthTest); + + // --- Set up Camera --- _camera = new Camera(_cameraPosition, _cameraFront, _cameraUp); + + // -- Load Shaders, Textures, and Create Scene Objects --- _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); _texture = new Texture("Textures/placeholder.png"); + // --- Create Scene Graph --- _rootNode = new SceneNode(); - _buildingObject = ShapeFactory.CreateTexturedCube(); - _buildingRender = new RenderObject(_buildingObject, _shader, _texture); + // Create Example Object + _buildingObject = ShapeFactory.CreateTexturedSphere(); + _buildingRender = new RenderObject(_buildingObject, _shader, _texture, _defaultMaterial, _mainLight); _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); + // Create Character Object CursorState = CursorState.Grabbed; @@ -89,8 +105,6 @@ namespace TheLabs base.OnRenderFrame(e); // Clear the color buffer and the depth buffer GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit); - //_cubeObject.Rotation = Quaternion.FromEulerAngles(_rotation * 0.5f, _rotation, 0); // - //_cubeObject2.Rotation = Quaternion.FromEulerAngles(-_rotation * 0.5f, -_rotation, 0); // --- Set up Camera --- Matrix4 view = _camera.LookAt(); @@ -101,15 +115,17 @@ namespace TheLabs { aspectRatio = 1.0f; // Default to 1:1 if window is minimized } + + // 3. Create the projection matrix Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView( MathHelper.DegreesToRadians(45f), aspectRatio, 0.1f, 100.0f ); - // --- Draw Scene Objects --- - _rootNode.Draw(view, projection, Matrix4.Identity); + // --- Draw Scene Objects --- + _rootNode.Draw(view, projection, Matrix4.Identity, _mainLight); SwapBuffers(); } diff --git a/TheRepo/TheLabs/RenderObject.cs b/TheRepo/TheLabs/RenderObject.cs index 41da021..d61d833 100644 --- a/TheRepo/TheLabs/RenderObject.cs +++ b/TheRepo/TheLabs/RenderObject.cs @@ -9,17 +9,21 @@ public class RenderObject public Mesh Mesh; public Shader Shader; public Texture Texture; + public Material Material; + public Lighting Lighting; public Vector3 Position = Vector3.Zero; public Matrix4 Transform = Matrix4.Identity; public Quaternion Rotation = Quaternion.Identity; public Vector3 Scale = Vector3.One; - public RenderObject(Mesh mesh, Shader shader, Texture texture) + public RenderObject(Mesh mesh, Shader shader, Texture texture, Material material, Lighting lighting) { Mesh = mesh; Shader = shader; Texture = texture; + Material = material; + Lighting = lighting; } public RenderObject(Mesh mesh, Shader shader) @@ -28,14 +32,20 @@ public class RenderObject Shader = shader; } - public void Draw(Matrix4 view, Matrix4 projection) + public void Draw(Matrix4 view, Matrix4 projection, Lighting light) { - // 1. Activate Shader + Vector3 pink = new Vector3(0.5f, 0.5f, 0.5f); + Material mat = new Material(pink * 0.1f, pink * 0.6f, Vector3.One, 32.0f); + Shader.Use(); - // 2. Bind Texture Texture.Use(); Shader.SetInt("uTexture", 0); - // 3. Calculate Model Matrix + + Material.Apply(Shader); + light.Apply(Shader); + + Vector3 cameraPos = view.Inverted().ExtractTranslation(); + Shader.SetVector3("uViewPos", cameraPos); Matrix4 model = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position); int modelLoc = GL.GetUniformLocation(Shader.Handle, "model"); @@ -47,7 +57,9 @@ public class RenderObject int projLoc = GL.GetUniformLocation(Shader.Handle, "projection"); GL.UniformMatrix4(projLoc, false, ref projection); // Try TRUE here + // 5. Tell the Mesh to draw itself Mesh.Draw(); } + } \ No newline at end of file diff --git a/TheRepo/TheLabs/SceneNode.cs b/TheRepo/TheLabs/SceneNode.cs index 3de370f..21e259d 100644 --- a/TheRepo/TheLabs/SceneNode.cs +++ b/TheRepo/TheLabs/SceneNode.cs @@ -23,7 +23,7 @@ public class SceneNode Children.Add(child); } - public void Draw(Matrix4 view, Matrix4 projection, Matrix4 parentTransform) + public void Draw(Matrix4 view, Matrix4 projection, Matrix4 parentTransform, Lighting light) { Matrix4 localTransform = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position); Matrix4 globalTransform = localTransform * parentTransform; @@ -34,12 +34,12 @@ public class SceneNode RenderObject.Rotation = Quaternion.Identity; RenderObject.Scale = Vector3.One; - RenderObject.Draw(view, projection); + RenderObject.Draw(view, projection, light); } foreach (var child in Children) { - child.Draw(view, projection, globalTransform); + child.Draw(view, projection, globalTransform, light); } } } \ No newline at end of file diff --git a/TheRepo/TheLabs/Shaders/shader.frag b/TheRepo/TheLabs/Shaders/shader.frag index 9bdd061..41adafe 100644 --- a/TheRepo/TheLabs/Shaders/shader.frag +++ b/TheRepo/TheLabs/Shaders/shader.frag @@ -1,37 +1,81 @@ #version 330 core -out vec4 outputColor; +// Fragment shader for basic lighting with texture mapping + +out vec4 outputColor; // The final output color of the fragment // --- INPUTS --- +// These come from the Vertex Shader in vec3 FragPos; in vec3 Normal; in vec2 TexCoord; +struct MaterialProperty { + vec3 ambientColour; + vec3 diffuseColour; + vec3 specularColour; + float shininess; +}; + +struct LightProperty { + vec3 position; + vec3 ambientColour; + vec3 diffuseColour; + vec3 specularColour; +}; + // --- UNIFORMS --- -uniform sampler2D uTexture; +uniform sampler2D uTexture; // Image Texture +uniform vec3 uViewPos; // Camera Position +uniform MaterialProperty uMaterial; // Material Properties +uniform LightProperty uPointLight; // Light Properties + void main() { - // Lighting Setup + // --- LIGHTING CALCULATIONS --- + // Lighting Setup (hardcoded for simplicity) 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!) + // 1. Get Texture Color + // Sample the texture at the given texture coordinates vec4 objColor = texture(uTexture, TexCoord); - // 2. Ambient + // 2. Ambient strength + // A constant, dim light that ensure s object are not completely dark + // It simply multiplies the light color by a small factor float ambientStrength = 0.1f; - vec3 ambient = ambientStrength * lightColor; + vec3 ambient = uMaterial.ambientColour * uPointLight.ambientColour; // 3. Diffuse + // Creates the "matte" effect by calculating the angle between the light direction and the surface normal + + // Normalize the normal vector and calculate the light direction vec3 norm = normalize(Normal); - vec3 lightDir = normalize(lightPos - FragPos); + vec3 lightDir = normalize(uPointLight.position - FragPos); + + // The dot product gives us the cosine of the angle between the two vectors + // The max function ensures we don't get negative values (light coming from behind the surface) float diff = max(dot(norm, lightDir), 0.0f); - vec3 diffuse = diff * lightColor; + vec3 diffuse = (uPointLight.diffuseColour * diff) * uMaterial.diffuseColour; - // 4. Combine - // Note: We use objColor.rgb (vec3) to match the lighting calculation - vec3 result = (ambient + diffuse) * objColor.rgb; + // 4. Specular + // Creates the shiny highlights on the surface + float shininess = 32.0f; + // Calculate the view direction and the reflection direction + vec3 viewDirection = normalize(uViewPos - FragPos); + // If the light is coming from the light direction, we need to negate it for reflection + vec3 reflectDirection = reflect(-lightDir, normalize(Normal)); + + // Calculate the specular component using the dot product and shininess factor + float spec = pow(max(dot(viewDirection, reflectDirection), 0.0f), shininess); + vec3 specular = uMaterial.specularColour * spec * uPointLight.specularColour; + + // 5. Combine + // We multiply (Ambient + Diffuse) by the Texture Color. + // We ADD Specular afterwards so the highlights look bright white (like plastic/metal). + vec3 result = (ambient + diffuse) * objColor.rgb + specular; 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 6ac83f9..08ea0c7 100644 --- a/TheRepo/TheLabs/Shaders/shader.vert +++ b/TheRepo/TheLabs/Shaders/shader.vert @@ -1,16 +1,21 @@ #version 330 core +// Vertex shader decides where each vertex is in 3D space + // --- INPUTS --- -layout(location = 0) in vec3 aPosition; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTexCoord; +// Comes directly from the vertex buffer +layout(location = 0) in vec3 aPosition; // Raw 3D position +layout(location = 1) in vec3 aNormal; // The direction perpendicular to the surface +layout(location = 2) in vec2 aTexCoord; // The U,V texture coordinates // --- OUTPUTS --- +// We can pass data to the fragment shader through these out vec3 FragPos; out vec3 Normal; out vec2 TexCoord; // --- UNIFORMS --- +// These are matrices that help transform our vertices uniform mat4 model; uniform mat4 view; uniform mat4 projection; @@ -22,7 +27,7 @@ void main(void) // 2. Calculate Normal Normal = mat3(transpose(inverse(model))) * aNormal; - + // 3. Pass Texture Coordinates TexCoord = aTexCoord; diff --git a/TheRepo/TheLabs/ShapeFactory.cs b/TheRepo/TheLabs/ShapeFactory.cs index a0a3d23..b5b0de6 100644 --- a/TheRepo/TheLabs/ShapeFactory.cs +++ b/TheRepo/TheLabs/ShapeFactory.cs @@ -253,6 +253,74 @@ public static class ShapeFactory // --- 4. Create the Mesh --- return new Mesh(vertices.ToArray(), indices.ToArray(), Mesh.VertexLayout.PosTex); } + public static Mesh CreateTexturedSphere(float radius = 0.5f, int stacks = 20, int slices = 20) +{ + var vertices = new List(); + var indices = new List(); + + // 1. Generate Vertices + for (int i = 0; i <= stacks; i++) + { + // V ranges from 0.0 to 1.0 (bottom to top) + float v = (float)i / stacks; + + // Phi ranges from -PI/2 (bottom) to +PI/2 (top) + float phi = v * MathF.PI - MathF.PI / 2; + + for (int j = 0; j <= slices; j++) + { + // U ranges from 0.0 to 1.0 (around the sphere) + float u = (float)j / slices; + + // Theta ranges from 0 to 2*PI + float theta = u * 2 * MathF.PI; + + // Calculate Position (Spherical Coordinates) + float x = radius * MathF.Cos(phi) * MathF.Cos(theta); + float y = radius * MathF.Sin(phi); + float z = radius * MathF.Cos(phi) * MathF.Sin(theta); + + // Calculate Normal (Normalized position) + // Since center is (0,0,0), normal is just (x,y,z) normalized. + // Or simpler: (x/r, y/r, z/r) + float nx = x / radius; + float ny = y / radius; + float nz = z / radius; + + // Add Vertex Data: Pos (3) + Normal (3) + Tex (2) + vertices.Add(x); vertices.Add(y); vertices.Add(z); // Position + vertices.Add(nx); vertices.Add(ny); vertices.Add(nz); // Normal + vertices.Add(u); vertices.Add(v); // Texture + } + } + + // 2. Generate Indices + for (int i = 0; i < stacks; i++) + { + for (int j = 0; j < slices; j++) + { + // We are building a quad from 4 vertices: + // TopLeft (TL), TopRight (TR), BottomLeft (BL), BottomRight (BR) + + uint bl = (uint)(i * (slices + 1) + j); + uint br = (uint)(i * (slices + 1) + (j + 1)); + uint tl = (uint)((i + 1) * (slices + 1) + j); + uint tr = (uint)((i + 1) * (slices + 1) + (j + 1)); + + // Triangle 1 + indices.Add(bl); + indices.Add(br); + indices.Add(tl); + + // Triangle 2 + indices.Add(br); + indices.Add(tr); + indices.Add(tl); + } + } + + return new Mesh(vertices.ToArray(), indices.ToArray(), Mesh.VertexLayout.PosTexNormal); +} } \ No newline at end of file diff --git a/TheRepo/TheLabs/Shapes/Bus.cs b/TheRepo/TheLabs/Shapes/Bus.cs index 1e35e71..53dfd1a 100644 --- a/TheRepo/TheLabs/Shapes/Bus.cs +++ b/TheRepo/TheLabs/Shapes/Bus.cs @@ -52,7 +52,7 @@ public class Bus foreach (var pos in wheelPositions) { - var wheelObj = new RenderObject(_wheelMesh, shader, wheelTexture); + //var wheelObj = new RenderObject(_wheelMesh, shader, wheelTexture); var wheelNode = new SceneNode(wheelObj); wheelNode.Position = pos; // Relative to the MainNode! @@ -68,7 +68,7 @@ public class Bus public void Draw(Matrix4 view, Matrix4 projection) { - MainNode.Draw(view, projection, Matrix4.Identity); + //MainNode.Draw(view, projection, Matrix4.Identity); } diff --git a/TheRepo/TheLabs/TheLabs.csproj b/TheRepo/TheLabs/TheLabs.csproj index 6b70cc0..00c10e4 100644 --- a/TheRepo/TheLabs/TheLabs.csproj +++ b/TheRepo/TheLabs/TheLabs.csproj @@ -30,4 +30,8 @@ + + + +