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 @@
+
+
+
+