From 71c5f3a3aab07ec8ed19e7c5fb0655fbc9d8f489 Mon Sep 17 00:00:00 2001 From: Max Westerlund Date: Tue, 2 Sep 2025 13:17:15 +0200 Subject: [PATCH] update --- BlockData.cs | 13 ++++ Blocks.cs | 29 ++++++++ Camera.cs | 73 ++++++++++++++++++++ Chunk.cs | 160 ++++++++++++++++++++++++++++++++++++++++++++ ChunkMesh.cs | 23 +++++++ ChunkMesher.cs | 18 +++++ FaceData.cs | 29 ++++++++ Input.cs | 24 +++++++ Program.cs | 5 ++ Renderer.cs | 61 +++++++++++++++++ Shader.cs | 34 +++++++--- Shaders/shader.frag | 8 +-- Shaders/shader.vert | 106 +++++++++++++++++++++++++++-- Triangle.cs | 4 +- Window.cs | 68 ++++++++++++++----- 15 files changed, 618 insertions(+), 37 deletions(-) create mode 100644 BlockData.cs create mode 100644 Blocks.cs create mode 100644 Camera.cs create mode 100644 Chunk.cs create mode 100644 ChunkMesh.cs create mode 100644 ChunkMesher.cs create mode 100644 FaceData.cs create mode 100644 Input.cs create mode 100644 Renderer.cs diff --git a/BlockData.cs b/BlockData.cs new file mode 100644 index 0000000..13ac6f0 --- /dev/null +++ b/BlockData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + public class BlockData + { + + } +} diff --git a/Blocks.cs b/Blocks.cs new file mode 100644 index 0000000..842ea11 --- /dev/null +++ b/Blocks.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + public enum Blocks : byte + { + Air = 0, + Stone = 1, + } + + public enum Orientation: uint + { + West = 0, // + X + East = 1, // - X + Top = 2, // + Y + Bottom = 3,// - Y + North = 4, // + Z + South = 5, // - Z + } + + public enum Texture: uint + { + Stone = 1 + } +} diff --git a/Camera.cs b/Camera.cs new file mode 100644 index 0000000..91efbb2 --- /dev/null +++ b/Camera.cs @@ -0,0 +1,73 @@ +using OpenTK.Mathematics; +using System.Security.Cryptography; + +namespace Voxel +{ + static class Camera + { + public static Vector3 Position; + + public static float Pitch = 0f; + public static float Yaw = -90f; + public static float FOV = 70f; + public static float Speed = 0.5f; + + public static Matrix4 view => + Matrix4.LookAt(Position, Position + Front, Vector3.UnitY); + + public static Matrix4 projection; + + public static Vector3 Front + { + get + { + 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)); + return front.Normalized(); + } + } + + public static void Update(float time) + { + float moveSpeed = Speed * time; + if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.LeftShift)) + { + moveSpeed *= 10; + } + if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.W)) + { + Position += Front * moveSpeed; + } + if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.S)) + { + Position += Front * -moveSpeed; + } + if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.A)) + { + Position += Vector3.Cross(Front, Vector3.UnitY).Normalized() * -moveSpeed; + } + if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.D)) + { + Position += Vector3.Cross(Front, Vector3.UnitY).Normalized() * moveSpeed; + } + } + + public static void UpdateMouse(Vector2 delta) + { + float fov = MathHelper.DegreesToRadians(60f); + float aspectRatio = 800f / 600f; + float near = 0.1f; + float far = 100f; + + projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, near, far); + + float sensitivity = 0.1f; + Yaw += delta.X * sensitivity; + Pitch -= delta.Y * sensitivity; + + Pitch = MathHelper.Clamp(Pitch, -89f, 89f); + } + } +} diff --git a/Chunk.cs b/Chunk.cs new file mode 100644 index 0000000..230b141 --- /dev/null +++ b/Chunk.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + public class Chunk + { + public readonly int Size = 16; + public readonly int Height = 256; + public readonly int PositionX; + public readonly int PositionY; + + private Dictionary _blockData; + private byte[] _blocks; + + public Chunk(int positionX, int positionY) + { + PositionX = positionX; + PositionY = positionY; + + _blockData = new Dictionary(); + _blocks = new byte[Size * Size * Height]; + + Initialize(); + } + + public void SetBlock(int x, int y, int z, byte blockId) + { + int i = GetBlockIndex(x, y, z); + _blocks[i] = blockId; + } + + public byte GetBlock(int x, int y, int z) + { + int i = GetBlockIndex(x, y, z); + return _blocks[i]; + } + + public BlockData GetBlockData(int x, int y, int z) + { + return new BlockData(); + } + + private void Initialize() + { + for (int x = 0; x < Size; x++) + { + for (int z = 0; z < Size; z++) + { + for (int y = 0; y < Size; y++) + { + byte blockId = 1; + SetBlock(x, y, z, blockId); + Console.WriteLine( + "Set block " + + x.ToString() + ", " + + y.ToString() + ", " + + z.ToString() + " " + ); + } + } + } + } + + // todo + public (int x, int y, int z) IndexToPosition(int i) + { + int x = 0; + int y = 0; + int z = 0; + + return (x, y, z); + } + + private int GetBlockIndex(int x, int y, int z) + { + return x + y * Size + z * Size * Size; + } + + public List GetFaces() + { + List list = new List(); + + for (byte i = 0; i < 6; i++) + { + FaceData faceData = new FaceData(); + + faceData.Facing = (Orientation)i; + + list.Add(faceData); + } + + for (byte i = 0; i < 6; i++) + { + FaceData faceData = new FaceData(); + + faceData.Facing = (Orientation)i; + + faceData.X = 1; + + list.Add(faceData); + } + + for (byte i = 0; i < 6; i++) + { + FaceData faceData = new FaceData(); + + faceData.Facing = (Orientation)i; + + + faceData.X = 1; + faceData.Y = 1; + faceData.Z = 1; + + list.Add(faceData); + } + + for (byte i = 0; i < 6; i++) + { + FaceData faceData = new FaceData(); + + faceData.Facing = (Orientation)i; + + + faceData.X = 1; + faceData.Y = 2; + faceData.Z = 1; + + list.Add(faceData); + } + + for (int x = 0; x < Size; x++) + { + for (int z = 0; z < Size; z++) + { + for (int y = 0; y < Height; y++) + { + for (byte i = 0; i < 6; i++) + { + FaceData faceData = new FaceData(); + + faceData.Facing = (Orientation)i; + + faceData.X = (byte)x; + faceData.Y = (byte)y; + faceData.Z = (byte)z; + + //list.Add(faceData); + } + } + } + } + + return list; + } + } +} diff --git a/ChunkMesh.cs b/ChunkMesh.cs new file mode 100644 index 0000000..a16e930 --- /dev/null +++ b/ChunkMesh.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + public class ChunkMesh + { + private List _faces; + + public ChunkMesh(Chunk chunk) + { + _faces = new List(); + } + + private void MeshBlock(int i) + { + + } + } +} diff --git a/ChunkMesher.cs b/ChunkMesher.cs new file mode 100644 index 0000000..7548ed7 --- /dev/null +++ b/ChunkMesher.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + static class ChunkMesher + { + static ChunkMesh MeshChunk(Chunk chunk) + { + ChunkMesh chunkMesh = new ChunkMesh(chunk); + + return chunkMesh; + } + } +} diff --git a/FaceData.cs b/FaceData.cs new file mode 100644 index 0000000..f3751d0 --- /dev/null +++ b/FaceData.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct FaceData + { + public Orientation Facing; + public uint X, Y, Z; + public Texture Texture; + + public uint[] Pack() + { + return new uint[] + { + X, + Y, + Z, + (uint)Facing, + (uint)Texture + }; + } + } +} diff --git a/Input.cs b/Input.cs new file mode 100644 index 0000000..ff90b6e --- /dev/null +++ b/Input.cs @@ -0,0 +1,24 @@ +using OpenTK.Windowing.GraphicsLibraryFramework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + public static class Input + { + private static Dictionary _keystates = new Dictionary(); + + public static bool GetKey(Keys key) + { + return _keystates.TryGetValue(key, out bool pressed) && pressed; + } + + public static void SetKey(Keys key, bool pressed) + { + _keystates[key] = pressed; + } + } +} diff --git a/Program.cs b/Program.cs index 73b2979..d0d1b82 100644 --- a/Program.cs +++ b/Program.cs @@ -8,6 +8,11 @@ internal class Program int sizeY = 600; string title = "Game"; + Chunk chunk = new Chunk(0, 0); + + var faces = chunk.GetFaces(); + Renderer.AddFaces(faces); + Window window = new Window(sizeX, sizeY, title); window.Run(); } diff --git a/Renderer.cs b/Renderer.cs new file mode 100644 index 0000000..3a21304 --- /dev/null +++ b/Renderer.cs @@ -0,0 +1,61 @@ +using OpenTK.Graphics.OpenGL4; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel +{ + static class Renderer + { + private static int _ssbo; + private static int _vao; + private static List _faces = new List(); + private static Shader _shader; + private static bool _needsUpdate = false; + + public static void OnLoad() + { + string vertexPath = "Shaders/shader.vert"; + string fragmentPath = "Shaders/shader.frag"; + _shader = new Shader(vertexPath, fragmentPath); + + _ssbo = GL.GenBuffer(); + _vao = GL.GenVertexArray(); + + GL.BindVertexArray(_vao); + GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo); + GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, 0, _ssbo); + } + + public static void Render() + { + GL.BindVertexArray(_vao); + GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo); + + if (_needsUpdate) + { + _needsUpdate = false; + Console.WriteLine("Update buffer"); + uint[] data = _faces.SelectMany(f => f.Pack()).ToArray(); + GL.BufferData(BufferTarget.ShaderStorageBuffer, data.Length * sizeof(uint), data, BufferUsageHint.StaticRead); + } + + _shader.Use(); + _shader.SetMatrix4("view", Camera.view); + _shader.SetMatrix4("projection", Camera.projection); + + GL.DrawArrays(PrimitiveType.Triangles, 0, _faces.Count * 6); + + //Console.WriteLine("Rendered " + _faces.Count.ToString() + " faces"); + } + + public static void AddFaces(List faces) + { + _faces.AddRange(faces); + _needsUpdate = true; + } + } +} diff --git a/Shader.cs b/Shader.cs index 058865c..faeab63 100644 --- a/Shader.cs +++ b/Shader.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; using System; using System.Collections.Generic; using System.Linq; @@ -10,7 +11,7 @@ namespace Voxel { public class Shader { - int handle; + private int _handle; private bool disposedValue = false; public Shader(string vertexPath, string fragmentPath) @@ -41,36 +42,47 @@ namespace Voxel // attach - handle = GL.CreateProgram(); + _handle = GL.CreateProgram(); - GL.AttachShader(handle, vertexShader); - GL.AttachShader(handle, fragmentShader); + GL.AttachShader(_handle, vertexShader); + GL.AttachShader(_handle, fragmentShader); - GL.LinkProgram(handle); + GL.LinkProgram(_handle); - GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out success); + GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out success); if (success == 0) { - string infoLog = GL.GetProgramInfoLog(handle); + string infoLog = GL.GetProgramInfoLog(_handle); Console.WriteLine(infoLog); } - GL.DetachShader(handle, vertexShader); - GL.DetachShader(handle, fragmentShader); + GL.DetachShader(_handle, vertexShader); + GL.DetachShader(_handle, fragmentShader); GL.DeleteShader(fragmentShader); GL.DeleteShader(vertexShader); } + public void SetMatrix4(string name, Matrix4 matrix) + { + int location = GL.GetUniformLocation(_handle, name); + if (location == -1) + { + Console.WriteLine($"Uniform '{name}' not found in shader."); + return; + } + GL.UniformMatrix4(location, false, ref matrix); + } + public void Use() { - GL.UseProgram(handle); + GL.UseProgram(_handle); } protected virtual void Dispose(bool disposing) { if (!disposedValue) { - GL.DeleteProgram(handle); + GL.DeleteProgram(_handle); disposedValue = true; } diff --git a/Shaders/shader.frag b/Shaders/shader.frag index ea6443e..0e84ed4 100644 --- a/Shaders/shader.frag +++ b/Shaders/shader.frag @@ -1,9 +1,9 @@ -#version 330 core -out vec4 FragColor; +#version 430 core -in vec4 vertexColor; +out vec4 FragColor; +in vec3 fragColor; void main() { - FragColor = vertexColor; + FragColor = vec4(fragColor, 1.0); } \ No newline at end of file diff --git a/Shaders/shader.vert b/Shaders/shader.vert index 80339d4..0153833 100644 --- a/Shaders/shader.vert +++ b/Shaders/shader.vert @@ -1,10 +1,106 @@ -#version 330 core -layout (location = 0) in vec3 aPosition; +#version 430 core -out vec4 vertexColor; +struct FaceData { + uint x; + uint y; + uint z; + uint facing; // 0=+X,1=-X,2=+Y,3=-Y,4=+Z,5=-Z + uint texture; +}; + +layout(std430, binding = 0) buffer FaceBuffer { + FaceData faces[]; +}; + +out vec3 fragColor; + +vec3 getVertexOffset(uint facing, uint vertIndex) { + if (facing == 0u) { // +X + vec3 offsets[6] = vec3[6]( + vec3(0.5, -0.5, -0.5), + vec3(0.5, 0.5, 0.5), + vec3(0.5, -0.5, 0.5), + vec3(0.5, 0.5, 0.5), + vec3(0.5, -0.5, -0.5), + vec3(0.5, 0.5, -0.5) + ); + return offsets[vertIndex]; + } else if (facing == 1u) { // -X + vec3 offsets[6] = vec3[6]( + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, 0.5) + ); + return offsets[vertIndex]; + } else if (facing == 2u) { // +Y (top) + vec3 offsets[6] = vec3[6]( + vec3(-0.5, 0.5, -0.5), + vec3( 0.5, 0.5, 0.5), + vec3( 0.5, 0.5, -0.5), + vec3( 0.5, 0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, 0.5, 0.5) + ); + return offsets[vertIndex]; + } else if (facing == 3u) { // -Y (bottom) + vec3 offsets[6] = vec3[6]( + vec3(-0.5, -0.5, 0.5), + vec3( 0.5, -0.5, -0.5), + vec3( 0.5, -0.5, 0.5), + vec3( 0.5, -0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, -0.5, -0.5) + ); + return offsets[vertIndex]; + } else if (facing == 4u) { // +Z (front) + vec3 offsets[6] = vec3[6]( + vec3(-0.5, -0.5, 0.5), + vec3( 0.5, -0.5, 0.5), + vec3( 0.5, 0.5, 0.5), + vec3( 0.5, 0.5, 0.5), + vec3(-0.5, 0.5, 0.5), + vec3(-0.5, -0.5, 0.5) + ); + return offsets[vertIndex]; + } else if (facing == 5u) { // -Z (back) + vec3 offsets[6] = vec3[6]( + vec3( 0.5, -0.5, -0.5), + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, 0.5, -0.5), + vec3( 0.5, 0.5, -0.5), + vec3( 0.5, -0.5, -0.5) + ); + return offsets[vertIndex]; + } +} + +uniform mat4 view; +uniform mat4 projection; void main() { - gl_Position = vec4(aPosition, 1.0); - vertexColor = vec4(aPosition + vec3(0.5,0.5,0.5), 1.0); + float[6] lightMult = float[6]( + 0.6, + 0.6, + 1.0, + 0.5, + 0.8, + 0.8 + ); + + uint faceIndex = gl_VertexID / 6; + uint vertIndex = gl_VertexID % 6; + + FaceData f = faces[faceIndex]; + vec3 basePos = vec3(f.x, f.y, f.z); + vec4 worldPos = vec4(basePos + getVertexOffset(f.facing, vertIndex), 1.0); + + + fragColor = vec3(1.0, 1.0, 1.0) * lightMult[f.facing]; + + gl_Position = projection * view * worldPos; } \ No newline at end of file diff --git a/Triangle.cs b/Triangle.cs index 3a23094..8227ed5 100644 --- a/Triangle.cs +++ b/Triangle.cs @@ -21,9 +21,9 @@ namespace Voxel float[] vertices = { - -0.5f, 0.5f, 0.0f, //top + -0.0f, 0.5f, 0.0f, //top 0.5f, -0.5f, 0.0f, // bottom right - 0.0f, 0.5f, 0.0f // bottom left + -0.5f, -0.5f, 0.0f // bottom left }; _vao = GL.GenVertexArray(); diff --git a/Window.cs b/Window.cs index 33e04c7..2ed4a47 100644 --- a/Window.cs +++ b/Window.cs @@ -1,30 +1,43 @@ using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Voxel { - public class Window(int width, int height, string title) : GameWindow(GameWindowSettings.Default, new NativeWindowSettings() { Size = (width, height), Title = title }) + public class Window(int width, int height, string title) : GameWindow(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title }) { - private Triangle _triangle; + public readonly int Width = width; + public readonly int Height = height; + public uint frames = 0; + public double timeElapsed = 0; - protected override void OnUpdateFrame(FrameEventArgs args) + protected override void OnUpdateFrame(FrameEventArgs e) { - base.OnUpdateFrame(args); + base.OnUpdateFrame(e); + + if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.Escape)) + { + Close(); + } } - protected override void OnRenderFrame(FrameEventArgs args) + protected override void OnRenderFrame(FrameEventArgs e) { - base.OnRenderFrame(args); + base.OnRenderFrame(e); - GL.Clear(ClearBufferMask.ColorBufferBit); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - _triangle.Draw(); + frames++; + timeElapsed += e.Time; + if (timeElapsed >= 1) + { + Console.WriteLine("FPS: " + frames.ToString()); + timeElapsed = 0; + frames = 0; + } + + Camera.Update((float)e.Time); + Renderer.Render(); SwapBuffers(); } @@ -40,9 +53,34 @@ namespace Voxel { base.OnLoad(); - _triangle = new Triangle(); + Renderer.OnLoad(); - GL.ClearColor(0.5f, 0.5f, 0.5f, 1f); + GL.ClearColor(0.72f, 0.88f, 0.97f, 1f); + + GL.Enable(EnableCap.CullFace); + GL.Enable(EnableCap.DepthTest); + + CursorState = CursorState.Grabbed; + VSync = VSyncMode.On; + } + + protected override void OnMouseMove(MouseMoveEventArgs e) + { + base.OnMouseMove(e); + + Camera.UpdateMouse(e.Delta); + } + + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + base.OnKeyUp(e); + Input.SetKey(e.Key, false); + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + base.OnKeyDown(e); + Input.SetKey(e.Key, true); } } }