Compare commits

..

9 Commits

Author SHA1 Message Date
87804fc932 Added some basic camera stuff 2025-11-13 02:51:38 +00:00
9bdb641096 Finished refractor 2025-11-13 02:28:09 +00:00
8d46d85df0 refractor 2025-11-11 15:35:10 +00:00
3cada23149 Texture stuff 2025-11-11 14:28:12 +00:00
82bc46fbb3 Fixed some bugs 2025-10-07 16:12:24 +01:00
72512fb632 doing some stuff 2025-10-07 13:08:23 +01:00
ced63735a5 Added cylinder class 2025-10-07 12:53:30 +01:00
825bdb0b59 Added circle and cube 2025-10-07 11:58:15 +01:00
83d2311ff5 fixed issue 2025-10-07 10:28:44 +01:00
16 changed files with 1014 additions and 188 deletions

View File

@ -3,6 +3,7 @@
<component name="UserContentModel">
<attachedFolders>
<Path>../../551455-graphics-programming-2526-the-repo-Zyb3rWolfi</Path>
<Path>TheLabs/Textures</Path>
</attachedFolders>
<explicitIncludes />
<explicitExcludes />

View File

@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="StbImageSharp" Version="2.30.15" />
</ItemGroup>
<ItemGroup>

131
TheRepo/TheLabs/Mesh.cs Normal file
View File

@ -0,0 +1,131 @@
namespace TheLabs;
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using StbImageSharp;
public class Mesh : IDisposable
{
private readonly int _vao;
private readonly int _vbo;
private readonly int _ebo;
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 Mesh(float[] vertices, uint[] indices, VertexLayout layout)
{
_vertexCount = indices.Length;
_useIndices = true;
_vao = GL.GenVertexArray();
GL.BindVertexArray(_vao);
_vbo = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
_ebo = GL.GenBuffer();
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);
// 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;
_useIndices = false;
_ebo = 0; // No EBO
_vao = GL.GenVertexArray();
GL.BindVertexArray(_vao);
_vbo = GL.GenBuffer();
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);
// 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!
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);
}
}
public void Dispose()
{
GL.DeleteBuffer(_vbo);
GL.DeleteBuffer(_ebo);
GL.DeleteVertexArray(_vao);
}
}

View File

@ -3,6 +3,8 @@ using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
using LearnOpenTK.Common;
using OpenTK.Mathematics;
using TheLabs.Shapes;
namespace TheLabs
{
@ -13,70 +15,35 @@ namespace TheLabs
// 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.
private readonly float[] _vertices =
{
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-left vertex
0.0f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top vertex
};
private readonly float[] _quadvertices =
{
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-left vertex
0.0f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Top vertex
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-left vertex
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top vertex
};
private readonly float[] _cubevertices =
{
// Positions
// Front face
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, // Bottom-left
0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, // Bottom-right
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, // Top-right
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, // Top-left
// Back face
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 1.0f, // Bottom-left
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, // Bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, // Top-right
-0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 1.0f, // Top-left
};
private readonly uint[] _cubeIndices =
{
// Front face
0, 1, 2, 2, 3, 0,
// Back face
4, 5, 6, 6, 7, 4,
// Left face
4, 0, 3, 3, 7, 4,
// Right face
1, 5, 6, 6, 2, 1,
// Top face
3, 2, 6, 6, 7, 3,
// Bottom face
4, 5, 1, 1, 0, 4
};
// These are the handles to OpenGL objects. A handle is an integer representing where the object lives on the
// graphics card. Consider them sort of like a pointer; we can't do anything with them directly, but we can
// send them to OpenGL functions that need them.
// What these objects are will be explained in OnLoad.
private int _vertexBufferObject;
private int _vao, _vbo, _ebo;
private int _vertexArrayObject;
// This class is a wrapper around a shader, which helps us manage it.
// The shader class's code is in the Common project.
// What shaders are and what they're used for will be explained later in this tutorial.
private Shader _shader;
// - Resources that only need to be created once
private Mesh _cubeMesh;
private Texture _cubeTexture;
private Texture _cubeTexture2;
private float _rotation;
// -- Scene Objects
private RenderObject _cubeObject;
private RenderObject _cubeObject2;
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;
// This prevents a large camera jump when the window first gets focus
private bool _firstMove = true;
public MyExampleWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
@ -85,141 +52,128 @@ namespace TheLabs
// Now, we start initializing OpenGL.
protected override void OnLoad()
{
// This is called when the window is created and is where we can set up OpenGL resources.
base.OnLoad();
//GL.Disable(EnableCap.CullFace);
// This will be the color of the background after we clear it, in normalized colors.
// Normalized colors are mapped on a range of 0.0 to 1.0, with 0.0 representing black, and 1.0 representing
// the largest possible value for that channel.
// This is a deep green.
//GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
// Set The background color to a nice blue.
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
// We need to send our vertices over to the graphics card so OpenGL can use them.
// To do this, we need to create what's called a Vertex Buffer Object (VBO).
// These allow you to upload a bunch of data to a buffer, and send the buffer to the graphics card.
// This effectively sends all the vertices at the same time.
// First, we need to create a buffer. This function returns a handle to it, but as of right now, it's empty.
_vertexBufferObject = GL.GenBuffer();
// Now, bind the buffer. OpenGL uses one global state, so after calling this,
// all future calls that modify the VBO will be applied to this buffer until another buffer is bound instead.
// The first argument is an enum, specifying what type of buffer we're binding. A VBO is an ArrayBuffer.
// There are multiple types of buffers, but for now, only the VBO is necessary.
// The second argument is the handle to our buffer.
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
// Finally, upload the vertices to the buffer.
// Arguments:
// Which buffer the data should be sent to.
// How much data is being sent, in bytes. You can generally set this to the length of your array, multiplied by sizeof(array type).
// The vertices themselves.
// How the buffer will be used, so that OpenGL can write the data to the proper memory space on the GPU.
// There are three different BufferUsageHints for drawing:
// StaticDraw: This buffer will rarely, if ever, update after being initially uploaded.
// DynamicDraw: This buffer will change frequently after being initially uploaded.
// StreamDraw: This buffer will change on every frame.
// Writing to the proper memory space is important! Generally, you'll only want StaticDraw,
// but be sure to use the right one for your use case.
GL.BufferData(BufferTarget.ArrayBuffer, _cubevertices.Length * sizeof(float), _cubevertices, BufferUsageHint.StaticDraw);
// One notable thing about the buffer we just loaded data into is that it doesn't have any structure to it. It's just a bunch of floats (which are actaully just bytes).
// The opengl driver doesn't know how this data should be interpreted or how it should be divided up into vertices. To do this opengl introduces the idea of a
// Vertex Array Obejct (VAO) which has the job of keeping track of what parts or what buffers correspond to what data. In this example we want to set our VAO up so that
// it tells opengl that we want to interpret 12 bytes as 3 floats and divide the buffer into vertices using that.
// To do this we generate and bind a VAO (which looks deceptivly similar to creating and binding a VBO, but they are different!).
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
// Now, we need to setup how the vertex shader will interpret the VBO data; you can send almost any C datatype (and a few non-C ones too) to it.
// While this makes them incredibly flexible, it means we have to specify how that data will be mapped to the shader's input variables.
// --- ELEMENT BUFFER (for indices) ---
_ebo = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
GL.BufferData(BufferTarget.ElementArrayBuffer, _cubeIndices.Length * sizeof(uint), _cubeIndices, BufferUsageHint.StaticDraw);
// To do this, we use the GL.VertexAttribPointer function
// This function has two jobs, to tell opengl about the format of the data, but also to associate the current array buffer with the VAO.
// This means that after this call, we have setup this attribute to source data from the current array buffer and interpret it in the way we specified.
// Arguments:
// Location of the input variable in the shader. the layout(location = 0) line in the vertex shader explicitly sets it to 0.
// How many elements will be sent to the variable. In this case, 3 floats for every vertex.
// The data type of the elements set, in this case float.
// Whether or not the data should be converted to normalized device coordinates. In this case, false, because that's already done.
// The stride; this is how many bytes are between the last element of one vertex and the first element of the next. 3 * sizeof(float) in this case.
// The offset; this is how many bytes it should skip to find the first element of the first vertex. 0 as of right now.
// Stride and Offset are just sort of glossed over for now, but when we get into texture coordinates they'll be shown in better detail.
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 7 * sizeof(float), 0);
GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, 7 * sizeof(float), 3 * sizeof(float));
// Enable variable 0 in the shader.
GL.EnableVertexAttribArray(0);
GL.EnableVertexAttribArray(1);
// We've got the vertices done, but how exactly should this be converted to pixels for the final image?
// Modern OpenGL makes this pipeline very free, giving us a lot of freedom on how vertices are turned to pixels.
// The drawback is that we actually need two more programs for this! These are called "shaders".
// Shaders are tiny programs that live on the GPU. OpenGL uses them to handle the vertex-to-pixel pipeline.
// Check out the Shader class in Common to see how we create our shaders, as well as a more in-depth explanation of how shaders work.
// shader.vert and shader.frag contain the actual shader code.
CursorState = CursorState.Grabbed;
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_cubeMesh = ShapeFactory.CreateTexturedCube();
_cubeTexture = new Texture("Textures/stone.jpg");
_cubeTexture2 = new Texture("Textures/placeholder.png");
_cubeObject = new RenderObject(_cubeMesh, _shader, _cubeTexture);
_cubeObject2 = new RenderObject(_cubeMesh, _shader, _cubeTexture2);
_cubeObject.Position = new Vector3(-1.5f, 0.0f, 0.0f);
_cubeObject2.Position = new Vector3(1.5f, 0.0f, 0.0f);
_cubeObject.Scale = new Vector3(0.5f, 0.5f, 0.5f);
_cubeObject2.Scale = new Vector3(0.5f, 0.5f, 0.5f);
// Now, enable the shader.
// Just like the VBO, this is global, so every function that uses a shader will modify this one until a new one is bound instead.
_shader.Use();
// Setup is now complete! Now we move to the OnRenderFrame function to finally draw the triangle.
}
// Now that initialization is done, let's create our render loop.
protected override void OnRenderFrame(FrameEventArgs e)
{
// This is called once per frame and is where all the rendering code goes.
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);
// This clears the image, using what you set as GL.ClearColor earlier.
// OpenGL provides several different types of data that can be rendered.
// You can clear multiple buffers by using multiple bit flags.
// However, we only modify the color, so ColorBufferBit is all we need to clear.
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// --- Set up Camera ---
Matrix4 view = Matrix4.LookAt(_cameraPosition, _cameraPosition + _cameraFront, _cameraUp);
float aspectRatio = (float)Size.X / Size.Y;
// To draw an object in OpenGL, it's typically as simple as binding your shader,
// setting shader uniforms (not done here, will be shown in a future tutorial)
// binding the VAO,
// and then calling an OpenGL function to render.
// 2. Add a safety check for divide-by-zero
if (Size.Y == 0 || Size.X == 0)
{
aspectRatio = 1.0f; // Default to 1:1 if window is minimized
}
Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(
MathHelper.DegreesToRadians(45f),
aspectRatio,
0.1f,
100.0f
);
// Bind the shader
_shader.Use();
_cubeObject.Draw(view, projection);
_cubeObject2.Draw(view, projection);
// Bind the VAO
GL.BindVertexArray(_vertexArrayObject);
// And then call our drawing function.
// For this tutorial, we'll use GL.DrawArrays, which is a very simple rendering function.
// Arguments:
// Primitive type; What sort of geometric primitive the vertices represent.
// OpenGL used to support many different primitive types, but almost all of the ones still supported
// is some variant of a triangle. Since we just want a single triangle, we use Triangles.
// Starting index; this is just the start of the data you want to draw. 0 here.
// How many vertices you want to draw. 3 for a triangle.
GL.DrawElements(PrimitiveType.Triangles, _cubeIndices.Length, DrawElementsType.UnsignedInt, 0); // OpenTK windows are what's known as "double-buffered". In essence, the window manages two buffers.
// One is rendered to while the other is currently displayed by the window.
// This avoids screen tearing, a visual artifact that can happen if the buffer is modified while being displayed.
// After drawing, call this function to swap the buffers. If you don't, it won't display what you've rendered.
SwapBuffers();
// And that's all you have to do for rendering! You should now see a yellow triangle on a black screen.
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
_rotation += (float)e.Time;
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
if (!IsFocused)
{
_firstMove = true;
return;
}
var mouuse = MouseState;
if (_firstMove)
{
mouuse = MouseState;
_firstMove = false;
}
else
{
// Get the mouse's movement delta
float deltaX = mouuse.Delta.X;
float deltaY = mouuse.Delta.Y;
// Apply sensitivity
_yaw += deltaX * _mouseSensitivity;
_pitch -= deltaY * _mouseSensitivity; // Y-axis is inverted
}
// --- Rotation (Arrow Keys) ---
if (input.IsKeyDown(Keys.Left))
{
_yaw -= _rotationSpeed * (float)e.Time;
}
if (input.IsKeyDown(Keys.Right))
{
_yaw += _rotationSpeed * (float)e.Time;
}
if (input.IsKeyDown(Keys.Up))
{
_pitch += _rotationSpeed * (float)e.Time;
}
if (input.IsKeyDown(Keys.Down))
{
_pitch -= _rotationSpeed * (float)e.Time;
}
_pitch = MathHelper.Clamp(_pitch, -89.0f, 89.0f);
Vector3 front;
front.X = MathF.Cos(MathHelper.DegreesToRadians(_yaw)) * MathF.Cos(MathHelper.DegreesToRadians(_pitch));
front.Y = MathF.Sin(MathHelper.DegreesToRadians(_pitch));
front.Z = MathF.Sin(MathHelper.DegreesToRadians(_yaw)) * MathF.Cos(MathHelper.DegreesToRadians(_pitch));
_cameraFront = Vector3.Normalize(front);
var _cameraRight = Vector3.Normalize(Vector3.Cross(_cameraFront, _cameraUp));
if (input.IsKeyDown(Keys.W)) _cameraPosition += _cameraFront * _cameraSpeed * (float)e.Time;
if (input.IsKeyDown(Keys.S)) _cameraPosition -= _cameraFront * _cameraSpeed * (float)e.Time;
if (input.IsKeyDown(Keys.A)) _cameraPosition -= _cameraRight * _cameraSpeed * (float)e.Time;
if (input.IsKeyDown(Keys.D)) _cameraPosition += _cameraRight * _cameraSpeed * (float)e.Time;
}
protected override void OnResize(ResizeEventArgs e)
{
@ -250,10 +204,6 @@ namespace TheLabs
GL.BindVertexArray(0);
GL.UseProgram(0);
// Delete all the resources.
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shader.Handle);
base.OnUnload();

View File

@ -0,0 +1,45 @@
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace TheLabs;
public class RenderObject
{
public Mesh Mesh;
public Shader Shader;
public Texture Texture;
public Vector3 Position = Vector3.Zero;
public Quaternion Rotation = Quaternion.Identity;
public Vector3 Scale = Vector3.One;
public RenderObject(Mesh mesh, Shader shader, Texture texture)
{
Mesh = mesh;
Shader = shader;
Texture = texture;
}
public void Draw(Matrix4 view, Matrix4 projection)
{
// 1. Activate Shader
Shader.Use();
Shader.SetInt("uTexture", 0);
// 2. Bind Texture
Texture.Use(); // Assumes you have a Texture class
// 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
int viewLoc = GL.GetUniformLocation(Shader.Handle, "view");
GL.UniformMatrix4(viewLoc, false, ref view); // Try TRUE here
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();
}
}

View File

@ -1,8 +1,11 @@
#version 330
in vec4 vertexColor;
out vec4 FragColor;
in vec4 outColour;
out vec4 outputColor;
in vec2 TexCoord;
uniform sampler2D uTexture;
void main()
{
FragColor = vertexColor;
outputColor = texture(uTexture, TexCoord);
}

View File

@ -25,16 +25,14 @@
// 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 = 1) in vec4 aColor;
out vec4 vertexColor;
layout(location = 0) in vec3 aPosition;
layout(location = 1)in vec2 aTexCoord;
out vec4 outColour;
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.
@ -43,8 +41,8 @@ uniform mat4 projection;
// 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 main(void)
{
gl_Position = projection * view * model * vec4(aPosition, 1.0);
vertexColor = aColor;
TexCoord = aTexCoord;
}

View File

@ -0,0 +1,118 @@
namespace TheLabs;
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
// 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
};
uint[] indices = {
// Front face
0, 2, 1,
0, 2, 3,
// Back face
4, 5, 6,
4, 6, 7,
// Left face
8, 9, 10,
8, 10, 11,
// Right face
12, 14, 13,
12, 14, 15,
// Top face
16, 17, 18,
16, 18, 19,
// Bottom face
20, 22, 21,
20, 22, 23
};
return new Mesh(vertices, indices, Mesh.VertexLayout.PosTex);
}
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
// 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
};
uint[] indices = {
// Front face
0, 1, 2,
2, 3, 0,
// Back face
4, 5, 6,
6, 7, 4,
// Left face
4, 0, 3,
3, 7, 4,
// Right face
1, 5, 6,
6, 2, 1,
// Top face
3, 2, 6,
6, 7, 3,
// Bottom face
4, 5, 1,
1, 0, 4
};
return new Mesh(vertices, indices, Mesh.VertexLayout.PosColor);
}
// You'd also add CreateCircle, CreateCylinder, etc.
// Your Cylinder should be ONE mesh, not 3 draw calls.
// Generate the top, bottom, and side vertices into one
// big array and use one EBO for all of it.
}

View File

@ -0,0 +1,94 @@
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace TheLabs.Shapes;
public class Circle
{
// Vertex Array Object, Vertex Buffer Object, Element Buffer Object
private int _vao;
private int _vbo;
private readonly float[] _vertices =
{
};
public Circle(float z = 0f, int segments = 100, Vector4? color = null)
{
Vector4 col = color ?? new Vector4(0f, 0f, 1f, 1f);
for (int i = 0; i <= segments; i++)
{
float angle = i * 2.0f * MathF.PI / segments;
float x = 0.5f * MathF.Cos(angle);
float y = 0.5f * MathF.Sin(angle);
Array.Resize(ref _vertices, _vertices.Length + 7);
_vertices[^7] = x;
_vertices[^6] = y;
_vertices[^5] = z;
_vertices[^4] = col.X;
_vertices[^3] = col.Y;
_vertices[^2] = col.Z;
_vertices[^1] = col.W;
}
// The Vertex Array Object
// This stores the confifuration of vertex atributes
_vao = GL.GenVertexArray();
// The Vertex Buffer Object
// This stores the actual vertex data e.g. positions, colors, normals, texture coordinates
_vbo = GL.GenBuffer();
// We bind the VAO
GL.BindVertexArray(_vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); // Specifying the type of buffer
// Uploading the vertex data to the GPU
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
// We tell opengl how to interpret the vertex data.
// The Location 0 corresponds to the layout(location = 0) in the vertex shader
// How many components (x, y, z) -> 3
// Data type -> float
// Not normalized
// The Stride -> The total size of a vertex (in bytes)
// The offset -> The position data starts at the beginning of the vertex data so 0
var stride = 7 * sizeof(float);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
GL.BindVertexArray(0);
}
public float[] GetVertices()
{
return _vertices;
}
public void Draw(Shader shader, Matrix4 view, Matrix4 projection, Matrix4 model)
{
//shader.SetMatrix4("model", matrix4);
int modelLoc = GL.GetUniformLocation(shader.Handle, "model");
int viewLoc = GL.GetUniformLocation(shader.Handle, "view");
int projLoc = GL.GetUniformLocation(shader.Handle, "projection");
// Send the matrices to the shader
GL.UniformMatrix4(modelLoc, false, ref model);
GL.UniformMatrix4(viewLoc, false, ref view);
GL.UniformMatrix4(projLoc, false, ref projection);
shader.Use();
GL.BindVertexArray(_vao);
GL.DrawArrays(PrimitiveType.TriangleFan, 0, _vertices.Length / 7);
}
public void Dispose()
{
GL.DeleteBuffer(_vbo);
GL.DeleteVertexArray(_vao);
}
}

View File

@ -0,0 +1,126 @@
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
namespace TheLabs.Shapes;
public class Cube
{
// Vertex Array Object, Vertex Buffer Object, Element Buffer Object
private int _vao;
private int _vbo;
private int _ebo;
private int _vertexCount;
public Vector3 Position = Vector3.Zero;
private readonly float[] _cubevertices =
{
// Position // Color (RGBA)
// 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
// 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
};
private readonly uint[] _cubeIndices =
{
// Front face
0, 1, 2,
2, 3, 0,
// Back face
4, 5, 6,
6, 7, 4,
// Left face
4, 0, 3,
3, 7, 4,
// Right face
1, 5, 6,
6, 2, 1,
// Top face
3, 2, 6,
6, 7, 3,
// Bottom face
4, 5, 1,
1, 0, 4
};
public Cube()
{
// The number of vertices to draw
_vertexCount = _cubeIndices.Length;
// The Vertex Array Object
// This stores the confifuration of vertex atributes
_vao = GL.GenVertexArray();
// The Vertex Buffer Object
// This stores the actual vertex data e.g. positions, colors, normals, texture coordinates
_vbo = GL.GenBuffer();
// The Element Buffer Object, stores the indices for indexed drawing
_ebo = GL.GenBuffer();
// We bind the VAO
GL.BindVertexArray(_vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); // Specifying the type of buffer
// Uploading the vertex data to the GPU
GL.BufferData(BufferTarget.ArrayBuffer, _cubevertices.Length * sizeof(float), _cubevertices, BufferUsageHint.StaticDraw);
// Now we set up the EBO
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
// Uploading the index data to the GPU
GL.BufferData(BufferTarget.ElementArrayBuffer, _cubeIndices.Length * sizeof(uint), _cubeIndices, BufferUsageHint.StaticDraw);
// We tell opengl how to interpret the vertex data.
// The Location 0 corresponds to the layout(location = 0) in the vertex shader
// How many components (x, y, z) -> 3
// Data type -> float
// Not normalized
// The Stride -> The total size of a vertex (in bytes)
// The offset -> The position data starts at the beginning of the vertex data so 0
var stride = 7 * sizeof(float);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
GL.BindVertexArray(0);
}
public void Draw(Shader shader, Matrix4 view, Matrix4 projection, float rotation)
{
// Create the model matrix with rotation and translation
Matrix4 model = Matrix4.CreateRotationY(rotation) * Matrix4.CreateRotationX(rotation * 0.5f) * Matrix4.CreateTranslation(Position);
// Set the matrices in the shader
int modelLoc = GL.GetUniformLocation(shader.Handle, "model");
int viewLoc = GL.GetUniformLocation(shader.Handle, "view");
int projLoc = GL.GetUniformLocation(shader.Handle, "projection");
// Send the matrices to the shader
GL.UniformMatrix4(modelLoc, false, ref model);
GL.UniformMatrix4(viewLoc, false, ref view);
GL.UniformMatrix4(projLoc, false, ref projection);
GL.BindVertexArray(_vao);
GL.DrawElements(PrimitiveType.Triangles, _cubeIndices.Length, DrawElementsType.UnsignedInt, 0);
}
public void Dispose()
{
GL.DeleteBuffer(_vbo);
GL.DeleteBuffer(_ebo);
GL.DeleteVertexArray(_vao);
}
}

View File

@ -0,0 +1,133 @@
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
namespace TheLabs.Shapes;
public class Cylinder
{
// Vertex Array Object, Vertex Buffer Object, Element Buffer Object
private int _vao;
private int _vbo;
private int _ebo;
private int _vertexCount;
private Circle _topCircle;
private Circle _bottomCircle;
private float _radius = 0.5f;
private float _height = 1.0f;
private float[] _vertices =
{
};
private uint[] _indices =
{
};
public Cylinder()
{
// The number of vertices to draw
_vertexCount = _indices.Length;
_topCircle = new Circle(0.5f, 10, new Vector4(1f, 1f, 0f, 1f));
_bottomCircle = new Circle(-0.5f, 10, new Vector4(1f, 0f, 0f, 1f));
GenerateCylinder(10);
}
private void GenerateCylinder(int segments)
{
var vertices = new List<float>();
var indices = new List<uint>();
for (int i = 0; i < segments; i++)
{
float angle1 = 2.0f * MathF.PI * i / segments;
float angle2 = 2.0f * MathF.PI * ((i + 1) % segments) / segments;
float x1 = _radius * MathF.Cos(angle1);
float y1 = _radius * MathF.Sin(angle1);
float x2 = _radius * MathF.Cos(angle2);
float y2 = _radius * MathF.Sin(angle2);
float zTop = _height / 2f;
float zBottom = -_height / 2f;
Vector4 sideColor = new Vector4(0f, 1f, 1f, 1f); // side color
// Add vertices: top1, bottom1, top2, bottom2
int top1Index = vertices.Count / 7;
vertices.AddRange(new float[] { x1, y1, zTop, sideColor.X, sideColor.Y, sideColor.Z, sideColor.W });
int bottom1Index = vertices.Count / 7;
vertices.AddRange(new float[] { x1, y1, zBottom, sideColor.X, sideColor.Y, sideColor.Z, sideColor.W });
int top2Index = vertices.Count / 7;
vertices.AddRange(new float[] { x2, y2, zTop, sideColor.X, sideColor.Y, sideColor.Z, sideColor.W });
int bottom2Index = vertices.Count / 7;
vertices.AddRange(new float[] { x2, y2, zBottom, sideColor.X, sideColor.Y, sideColor.Z, sideColor.W });
// First triangle
indices.Add((uint)top1Index);
indices.Add((uint)bottom1Index);
indices.Add((uint)top2Index);
// Second triangle
indices.Add((uint)top2Index);
indices.Add((uint)bottom1Index);
indices.Add((uint)bottom2Index);
}
_vertices = vertices.ToArray();
_indices = indices.ToArray();
_vertexCount = indices.Count;
// OpenGL setup
_vao = GL.GenVertexArray();
_vbo = GL.GenBuffer();
_ebo = GL.GenBuffer();
GL.BindVertexArray(_vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Count * sizeof(float), vertices.ToArray(), BufferUsageHint.StaticDraw);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Count * sizeof(uint), indices.ToArray(), BufferUsageHint.StaticDraw);
var stride = 7 * sizeof(float);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
GL.BindVertexArray(0);
}
public void Draw(Shader shader, Matrix4 model, Matrix4 view, Matrix4 projection)
{
shader.SetMatrix4("model", model);
// Set the matrices in the shader
int modelLoc = GL.GetUniformLocation(shader.Handle, "model");
int viewLoc = GL.GetUniformLocation(shader.Handle, "view");
int projLoc = GL.GetUniformLocation(shader.Handle, "projection");
// Send the matrices to the shader
GL.UniformMatrix4(modelLoc, false, ref model);
GL.UniformMatrix4(viewLoc, false, ref view);
GL.UniformMatrix4(projLoc, false, ref projection);
GL.BindVertexArray(_vao);
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
_topCircle.Draw(shader, view, projection, model);
_bottomCircle.Draw(shader, view, projection, model);
}
public void Dispose()
{
GL.DeleteBuffer(_vbo);
GL.DeleteBuffer(_ebo);
GL.DeleteVertexArray(_vao);
}
}

View File

@ -0,0 +1,163 @@
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using StbImageSharp;
namespace TheLabs.Shapes;
public class TexturedCube
{
// Vertex Array Object, Vertex Buffer Object, Element Buffer Object
private int _vao;
private int _vbo;
private int _ebo;
private int _tb;
private int _vertexCount;
public Vector3 Position = Vector3.Zero;
private readonly float[] _cubevertices =
{
// Position // Color (RGBA)
// 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
// 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
};
private readonly uint[] _cubeIndices =
{
// Front face
0, 2, 1,
0, 2, 3,
// Back face
4, 5, 6,
4, 6, 7,
// Left face
8, 9, 10,
8, 10, 11,
// Right face
12, 14, 13,
12, 14, 15,
// Top face
16, 17, 18,
16, 18, 19,
// Bottom face
20, 22, 21,
20, 22, 23
};
public TexturedCube()
{
_tb = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, _tb);
StbImage.stbi_set_flip_vertically_on_load(1);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
// Load the image
ImageResult image = ImageResult.FromStream(File.OpenRead("Textures/placeholder.png"), ColorComponents.RedGreenBlueAlpha);
GL.TexImage2D(TextureTarget.Texture2D,0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data);
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
// The number of vertices to draw
_vertexCount = _cubeIndices.Length;
// The Vertex Array Object
// This stores the confifuration of vertex atributes
_vao = GL.GenVertexArray();
// The Vertex Buffer Object
// This stores the actual vertex data e.g. positions, colors, normals, texture coordinates
_vbo = GL.GenBuffer();
// The Element Buffer Object, stores the indices for indexed drawing
_ebo = GL.GenBuffer();
// We bind the VAO
GL.BindVertexArray(_vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo); // Specifying the type of buffer
// Uploading the vertex data to the GPU
GL.BufferData(BufferTarget.ArrayBuffer, _cubevertices.Length * sizeof(float), _cubevertices, BufferUsageHint.StaticDraw);
// Now we set up the EBO
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
// Uploading the index data to the GPU
GL.BufferData(BufferTarget.ElementArrayBuffer, _cubeIndices.Length * sizeof(uint), _cubeIndices, BufferUsageHint.StaticDraw);
// We tell opengl how to interpret the vertex data.
// The Location 0 corresponds to the layout(location = 0) in the vertex shader
// How many components (x, y, z) -> 3
// Data type -> float
// Not normalized
// The Stride -> The total size of a vertex (in bytes)
// The offset -> The position data starts at the beginning of the vertex data so 0
var stride = 5 * sizeof(float);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
GL.BindVertexArray(0);
}
public void Draw(Shader shader, Matrix4 view, Matrix4 projection, float rotation)
{
// Create the model matrix with rotation and translation
Matrix4 model = Matrix4.CreateRotationY(rotation) * Matrix4.CreateRotationX(rotation * 0.5f) * Matrix4.CreateTranslation(Position);
shader.Use();
GL.Uniform1(GL.GetUniformLocation(shader.Handle, "uTexture"), 0);
GL.UniformMatrix4(GL.GetUniformLocation(shader.Handle, "model"), false, ref model);
GL.UniformMatrix4(GL.GetUniformLocation(shader.Handle, "view"), false, ref view);
GL.UniformMatrix4(GL.GetUniformLocation(shader.Handle, "projection"), false, ref projection);
int texLoc = GL.GetUniformLocation(shader.Handle, "uTexture");
GL.Uniform1(texLoc, 0);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, _tb);
GL.BindVertexArray(_vao);
GL.DrawElements(PrimitiveType.Triangles, _vertexCount, DrawElementsType.UnsignedInt, 0);
}
public void Dispose()
{
GL.DeleteBuffer(_vbo);
GL.DeleteBuffer(_ebo);
GL.DeleteVertexArray(_vao);
}
}

View File

@ -0,0 +1,61 @@
namespace TheLabs;
using OpenTK.Graphics.OpenGL4;
using StbImageSharp;
using System.IO;
public class Texture : IDisposable
{
public readonly int Handle;
public Texture(string path)
{
// Generate the handle
Handle = GL.GenTexture();
// Bind the texture so we can configure it
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, Handle);
// --- Set texture parameters ---
// Repeat the texture if UVs go outside [0, 1]
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
// Set filter for shrinking (mipmap) and stretching (linear)
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
// --- Load and upload the image data ---
StbImage.stbi_set_flip_vertically_on_load(1);
// Load the image from disk
using (var stream = File.OpenRead(path))
{
ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
// Upload data to the GPU
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
image.Width, image.Height, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, image.Data);
}
// Generate mipmaps for better quality shrinking
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
// Unbind the texture
GL.BindTexture(TextureTarget.Texture2D, 0);
}
// A simple method to bind the texture to a specific unit
public void Use(TextureUnit unit = TextureUnit.Texture0)
{
GL.ActiveTexture(unit);
GL.BindTexture(TextureTarget.Texture2D, Handle);
}
public void Dispose()
{
GL.DeleteTexture(Handle);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="StbImageSharp" Version="2.30.15" />
</ItemGroup>
<ItemGroup>

View File

@ -15,6 +15,7 @@
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="StbImageSharp" Version="2.30.15" />
</ItemGroup>
<ItemGroup>