Added some lighting

This commit is contained in:
Zyb3rWolfi 2025-11-24 15:14:10 +00:00
parent 1fd8aada02
commit 4de01aa53b
7 changed files with 180 additions and 158 deletions

View File

@ -2,8 +2,7 @@
using LearnOpenTK.Common; using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics; using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop; using System;
using StbImageSharp;
public class Mesh : IDisposable public class Mesh : IDisposable
{ {
@ -13,9 +12,7 @@ public class Mesh : IDisposable
private readonly int _vertexCount; private readonly int _vertexCount;
private readonly bool _useIndices; private readonly bool _useIndices;
// A flexible way to define vertex layout public enum VertexLayout { PosColor, PosTex, PosTexNormal }
// (Could be an enum or a struct)
public enum VertexLayout { PosColor, PosTex }
public Mesh(float[] vertices, uint[] indices, VertexLayout layout) public Mesh(float[] vertices, uint[] indices, VertexLayout layout)
{ {
@ -33,44 +30,22 @@ public class Mesh : IDisposable
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo); GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw); GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw);
// --- Set up Vertex Attributes based on layout --- SetupAttributes(layout); // Refactored into a helper method to avoid duplication
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);
// layout(location = 1) in shader: vec2 texCoord
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
}
GL.BindVertexArray(0); GL.BindVertexArray(0);
} }
// Constructor for non-indexed drawing (like your Circle)
public Mesh(float[] vertices, VertexLayout layout) public Mesh(float[] vertices, VertexLayout layout)
{ {
// Calculate vertex count based on stride // Calculate vertex count based on the layout size
if (layout == VertexLayout.PosColor) int strideCount = 0;
_vertexCount = vertices.Length / 7; if (layout == VertexLayout.PosColor) strideCount = 7;
else // PosTex else if (layout == VertexLayout.PosTex) strideCount = 5;
_vertexCount = vertices.Length / 5; else if (layout == VertexLayout.PosTexNormal) strideCount = 8;
_vertexCount = vertices.Length / strideCount;
_useIndices = false; _useIndices = false;
_ebo = 0; // No EBO _ebo = 0;
_vao = GL.GenVertexArray(); _vao = GL.GenVertexArray();
GL.BindVertexArray(_vao); GL.BindVertexArray(_vao);
@ -79,45 +54,73 @@ public class Mesh : IDisposable
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw); GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
// --- Set up Vertex Attributes based on layout --- SetupAttributes(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);
// layout(location = 1) in shader: vec2 texCoord
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
}
GL.BindVertexArray(0); 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() public void Draw()
{ {
GL.BindVertexArray(_vao); GL.BindVertexArray(_vao);
if (_useIndices) if (_useIndices)
{ {
GL.DrawElements(PrimitiveType.Triangles, _vertexCount, DrawElementsType.UnsignedInt, 0); GL.DrawElements(PrimitiveType.Triangles, _vertexCount, DrawElementsType.UnsignedInt, 0);
} }
else else
{ {
// For your Circle (which used TriangleFan)
GL.DrawArrays(PrimitiveType.TriangleFan, 0, _vertexCount); GL.DrawArrays(PrimitiveType.TriangleFan, 0, _vertexCount);
} }
} }

View File

@ -14,7 +14,7 @@ public static class ModelLoader
var scene = importer.ImportFile(path, PostProcessSteps.Triangulate | PostProcessSteps.JoinIdenticalVertices); var scene = importer.ImportFile(path, PostProcessSteps.Triangulate | PostProcessSteps.JoinIdenticalVertices);
if (scene == null || scene.SceneFlags.HasFlag(SceneFlags.Incomplete) || scene.RootNode == null) if (scene == null || scene.SceneFlags.HasFlag(SceneFlags.Incomplete) || scene.RootNode == null)
{ {
throw new Exception("Error loading model: " + path); throw new Exception("Error loading model: " + path);
} }
@ -37,7 +37,16 @@ public static class ModelLoader
allVertices.Add(pos.X); allVertices.Add(pos.X);
allVertices.Add(pos.Y); allVertices.Add(pos.Y);
allVertices.Add(pos.Z); 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) // Texture Coordinates (u, v)
if (mesh.HasTextureCoords(0)) if (mesh.HasTextureCoords(0))
{ {

View File

@ -65,25 +65,17 @@ namespace TheLabs
GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.DepthTest);
_camera = new Camera(_cameraPosition, _cameraFront, _cameraUp); _camera = new Camera(_cameraPosition, _cameraFront, _cameraUp);
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); _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(); _rootNode = new SceneNode();
_exampleObject = ModelLoader.LoadMesh("Objects/cat.obj"); _buildingObject = ShapeFactory.CreateTexturedCube();
_buildingObject = ModelLoader.LoadMesh("Objects/building1.obj");
_render = new RenderObject(_exampleObject, _shader, _texture);
_buildingRender = new RenderObject(_buildingObject, _shader, _texture); _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 = new SceneNode(_buildingRender);
_buildingNode.Position = new Vector3(2.0f, -1.0f, 0.0f); _buildingNode.Position = new Vector3(2.0f, -1.0f, 0.0f);
_buildingNode.Scale = new Vector3(0.1f, 0.1f, 0.1f);
_rootNode.AddChild(_buildingNode); _rootNode.AddChild(_buildingNode);
CursorState = CursorState.Grabbed; CursorState = CursorState.Grabbed;

View File

@ -21,15 +21,22 @@ public class RenderObject
Shader = shader; Shader = shader;
Texture = texture; Texture = texture;
} }
public RenderObject(Mesh mesh, Shader shader)
{
Mesh = mesh;
Shader = shader;
}
public void Draw(Matrix4 view, Matrix4 projection) public void Draw(Matrix4 view, Matrix4 projection)
{ {
// 1. Activate Shader // 1. Activate Shader
Shader.Use(); Shader.Use();
Shader.SetInt("uTexture", 0);
// 2. Bind Texture // 2. Bind Texture
Texture.Use(); Texture.Use();
Shader.SetInt("uTexture", 0);
// 3. Calculate Model Matrix // 3. Calculate Model Matrix
Matrix4 model = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position); Matrix4 model = Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateTranslation(Position);
int modelLoc = GL.GetUniformLocation(Shader.Handle, "model"); int modelLoc = GL.GetUniformLocation(Shader.Handle, "model");
GL.UniformMatrix4(modelLoc, false, ref model); // Try TRUE here GL.UniformMatrix4(modelLoc, false, ref model); // Try TRUE here

View File

@ -1,11 +1,37 @@
#version 330 #version 330 core
in vec4 outColour;
out vec4 outputColor; out vec4 outputColor;
// --- INPUTS ---
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoord; in vec2 TexCoord;
// --- UNIFORMS ---
uniform sampler2D uTexture; uniform sampler2D uTexture;
void main() 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);
} }

View File

@ -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 #version 330 core
// GLSL's syntax is somewhat like C, but it has a few differences. // --- INPUTS ---
// 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.
layout(location = 0) in vec3 aPosition; layout(location = 0) in vec3 aPosition;
layout(location = 1)in vec2 aTexCoord; layout(location = 1) in vec3 aNormal;
out vec4 outColour; layout(location = 2) in vec2 aTexCoord;
// --- OUTPUTS ---
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoord;
// --- UNIFORMS ---
uniform mat4 model; uniform mat4 model;
uniform mat4 view; uniform mat4 view;
uniform mat4 projection; 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) 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; TexCoord = aTexCoord;
// 4. Calculate Final Position
gl_Position = projection * view * vec4(FragPos, 1.0);
} }

View File

@ -5,40 +5,43 @@ public static class ShapeFactory
public static Mesh CreateTexturedCube() public static Mesh CreateTexturedCube()
{ {
float[] vertices = { float[] vertices = {
// Front face (z = +0.5) // Format: X, Y, Z, NX, NY, NZ, U, V
-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
// Back face (z = -0.5) // --- Front Face (Normal points +Z: 0, 0, 1) ---
-0.5f, -0.5f, -0.5f, 1f, 0f, // Bottom-left -0.5f, -0.5f, 0.5f, 0f, 0f, 1f, 0f, 0f, // Bottom-left
0.5f, -0.5f, -0.5f, 0f, 0f, // Bottom-right 0.5f, -0.5f, 0.5f, 0f, 0f, 1f, 1f, 0f, // Bottom-right
0.5f, 0.5f, -0.5f, 0f, 1f, // Top-right 0.5f, 0.5f, 0.5f, 0f, 0f, 1f, 1f, 1f, // Top-right
-0.5f, 0.5f, -0.5f, 1f, 1f, // Top-left -0.5f, 0.5f, 0.5f, 0f, 0f, 1f, 0f, 1f, // Top-left
// Left face (x = -0.5) // --- Back Face (Normal points -Z: 0, 0, -1) ---
-0.5f, -0.5f, -0.5f, 0f, 0f, // Bottom-left -0.5f, -0.5f, -0.5f, 0f, 0f, -1f, 1f, 0f,
-0.5f, -0.5f, 0.5f, 1f, 0f, // Bottom-right 0.5f, -0.5f, -0.5f, 0f, 0f, -1f, 0f, 0f,
-0.5f, 0.5f, 0.5f, 1f, 1f, // Top-right 0.5f, 0.5f, -0.5f, 0f, 0f, -1f, 0f, 1f,
-0.5f, 0.5f, -0.5f, 0f, 1f, // Top-left -0.5f, 0.5f, -0.5f, 0f, 0f, -1f, 1f, 1f,
// Right face (x = +0.5)
0.5f, -0.5f, -0.5f, 1f, 0f, // Bottom-left // --- Left Face (Normal points -X: -1, 0, 0) ---
0.5f, -0.5f, 0.5f, 0f, 0f, // Bottom-right -0.5f, -0.5f, -0.5f, -1f, 0f, 0f, 0f, 0f,
0.5f, 0.5f, 0.5f, 0f, 1f, // Top-right -0.5f, -0.5f, 0.5f, -1f, 0f, 0f, 1f, 0f,
0.5f, 0.5f, -0.5f, 1f, 1f, // Top-left -0.5f, 0.5f, 0.5f, -1f, 0f, 0f, 1f, 1f,
-0.5f, 0.5f, -0.5f, -1f, 0f, 0f, 0f, 1f,
// Top face (y = +0.5)
-0.5f, 0.5f, -0.5f, 0f, 1f, // Bottom-left // --- Right Face (Normal points +X: 1, 0, 0) ---
0.5f, 0.5f, -0.5f, 1f, 1f, // Bottom-right 0.5f, -0.5f, -0.5f, 1f, 0f, 0f, 1f, 0f,
0.5f, 0.5f, 0.5f, 1f, 0f, // Top-right 0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 0f, 0f,
-0.5f, 0.5f, 0.5f, 0f, 0f, // Top-left 0.5f, 0.5f, 0.5f, 1f, 0f, 0f, 0f, 1f,
0.5f, 0.5f, -0.5f, 1f, 0f, 0f, 1f, 1f,
// Bottom face (y = -0.5)
-0.5f, -0.5f, -0.5f, 1f, 1f, // Bottom-left // --- Top Face (Normal points +Y: 0, 1, 0) ---
0.5f, -0.5f, -0.5f, 0f, 1f, // Bottom-right -0.5f, 0.5f, -0.5f, 0f, 1f, 0f, 0f, 1f,
0.5f, -0.5f, 0.5f, 0f, 0f, // Top-right 0.5f, 0.5f, -0.5f, 0f, 1f, 0f, 1f, 1f,
-0.5f, -0.5f, 0.5f, 1f, 0f, // Top-left 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 20, 22, 23
}; };
return new Mesh(vertices, indices, Mesh.VertexLayout.PosTex); return new Mesh(vertices, indices, Mesh.VertexLayout.PosTexNormal);
} }
public static Mesh CreateColouredCircle() public static Mesh CreateColouredCircle()
@ -123,17 +126,16 @@ public static class ShapeFactory
public static Mesh CreateColorCube() public static Mesh CreateColorCube()
{ {
float[] vertices = { float[] vertices = {
// Front face (z = +0.5) -0.5f, -0.5f, 0.5f, 1f, 0f, 0f, 1f, 0f, 0f, 1f, // Bottom-left
-0.5f, -0.5f, 0.5f, 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, 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, 0f, 0f, 0f, 1f, // Top-right -0.5f, 0.5f, 0.5f, 1f, 1f, 0f, 1f, 0f, 0f, 1f, // Top-left
-0.5f, 0.5f, 0.5f, 1f, 0f, 0f, 1f, // Top-left
// Back face (z = -0.5) // Back face (z = -0.5)
-0.5f, -0.5f, -0.5f, 1f, 0f, 1f, 1f, // Bottom-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, // Bottom-right 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, // Top-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 // Top-left -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 1f, 0f, 0f, -1f, // Top-left
}; };
uint[] indices = { uint[] indices = {