Major refactor and organization, optimizations to chunk, world and renderer
This commit is contained in:
71
Graphics/Camera.cs
Normal file
71
Graphics/Camera.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Voxel.Graphics
|
||||
{
|
||||
static class Camera
|
||||
{
|
||||
public static Vector3 Position = new Vector3(-8, 16, -8);
|
||||
|
||||
public static float Pitch = -22.5f;
|
||||
public static float Yaw = 0f;
|
||||
public static float FOV = 60f;
|
||||
public static float TargetFOV = FOV;
|
||||
public static float FOVLerpSpeed = 10f;
|
||||
public static float Speed = 5f;
|
||||
public static float ShiftSpeed = 20f;
|
||||
|
||||
private static int _width;
|
||||
private static int _height;
|
||||
|
||||
public static Matrix4 view =>
|
||||
Matrix4.LookAt(Position, Position + Front, Vector3.UnitY);
|
||||
|
||||
public static Matrix4 projection;
|
||||
|
||||
public static Vector3 Front
|
||||
{
|
||||
get
|
||||
{
|
||||
float yawOffset = Yaw - 90f;
|
||||
Vector3 front;
|
||||
front.X = MathF.Cos(MathHelper.DegreesToRadians(yawOffset)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch));
|
||||
front.Y = MathF.Sin(MathHelper.DegreesToRadians(Pitch));
|
||||
front.Z = MathF.Sin(MathHelper.DegreesToRadians(yawOffset)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch));
|
||||
return front.Normalized();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateMouse(Vector2 delta)
|
||||
{
|
||||
float sensitivity = 0.1f;
|
||||
Yaw += delta.X * sensitivity;
|
||||
Pitch -= delta.Y * sensitivity;
|
||||
|
||||
Pitch = MathHelper.Clamp(Pitch, -89f, 89f);
|
||||
}
|
||||
|
||||
public static void UpdateProjection()
|
||||
{
|
||||
float fov = MathHelper.DegreesToRadians(FOV);
|
||||
float aspectRatio = _width / (float)_height;
|
||||
float near = 0.1f;
|
||||
float far = 1000f;
|
||||
|
||||
projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, near, far);
|
||||
}
|
||||
|
||||
public static void UpdateSize(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
UpdateProjection();
|
||||
}
|
||||
|
||||
public static void UpdateFOV(float deltaTime, float alpha)
|
||||
{
|
||||
float currentFOV = MathHelper.Lerp(FOV, TargetFOV, FOVLerpSpeed * deltaTime);
|
||||
Camera.FOV = currentFOV;
|
||||
Camera.UpdateProjection();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Graphics/ChunkMesh.cs
Normal file
47
Graphics/ChunkMesh.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace Voxel.Graphics
|
||||
{
|
||||
public class ChunkMesh
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
private byte[] _packedData;
|
||||
public int Size = 0;
|
||||
|
||||
public ChunkMesh(int x, int y)
|
||||
{
|
||||
_packedData = Array.Empty<byte>();
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public void SetFaces(List<FaceData> faces)
|
||||
{
|
||||
Size = faces.Count;
|
||||
_packedData = PackFaces(faces);
|
||||
}
|
||||
|
||||
private static byte[] PackFaces(List<FaceData> faces)
|
||||
{
|
||||
const int BYTES_PER_FACE = 4;
|
||||
int totalFaces = faces.Count;
|
||||
var result = new byte[faces.Count * BYTES_PER_FACE];
|
||||
|
||||
for (int i = 0; i < totalFaces; i++)
|
||||
{
|
||||
var face = faces[i];
|
||||
uint packed = face._data;
|
||||
int offset = i * 4;
|
||||
|
||||
// Write little-endian (important!)
|
||||
result[offset] = (byte)(packed);
|
||||
result[offset + 1] = (byte)(packed >> 8);
|
||||
result[offset + 2] = (byte)(packed >> 16);
|
||||
result[offset + 3] = (byte)(packed >> 24);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] GetPackedData() => _packedData;
|
||||
}
|
||||
}
|
||||
44
Graphics/FaceData.cs
Normal file
44
Graphics/FaceData.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Voxel.Core;
|
||||
|
||||
namespace Voxel.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct FaceData
|
||||
{
|
||||
public uint _data;
|
||||
|
||||
// Bit layout:
|
||||
// [31-24]: Y (8 bits) 0-255
|
||||
// [23-20]: Z (4 bits) 0-15
|
||||
// [19-16]: X (4 bits) 0-15
|
||||
// [15-10]: Texture (6 bits) 0-63
|
||||
// [9-7]: Facing (3 bits) 0-7
|
||||
// [6-3]: Light (4 bits) 0-15
|
||||
// [2-0]: unused (3 bits)
|
||||
|
||||
public FaceData(byte x, byte y, byte z, Orientation facing, Textures texture, byte lightLevel)
|
||||
{
|
||||
_data = (uint)(
|
||||
((y & 0xFF) << 24) | // 8 bits
|
||||
((z & 0x0F) << 20) | // 4 bits
|
||||
((x & 0x0F) << 16) | // 4 bits
|
||||
(((byte)texture & 0x3F) << 10) | // 6 bits (0-63)
|
||||
(((byte)facing & 0x07) << 7) | // 3 bits
|
||||
((lightLevel & 0x0F) << 3) // 4 bits
|
||||
);
|
||||
}
|
||||
|
||||
public byte X => (byte)((_data >> 16) & 0x0F);
|
||||
public byte Y => (byte)((_data >> 24) & 0xFF);
|
||||
public byte Z => (byte)((_data >> 20) & 0x0F);
|
||||
public Orientation Facing => (Orientation)((_data >> 7) & 0x07);
|
||||
public Textures Texture => (Textures)((_data >> 10) & 0x3F);
|
||||
public byte LightLevel => (byte)((_data >> 3) & 0x0F);
|
||||
|
||||
public byte[] Pack()
|
||||
{
|
||||
return BitConverter.GetBytes(_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
149
Graphics/Renderer.cs
Normal file
149
Graphics/Renderer.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using Voxel.Core;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Voxel.Graphics
|
||||
{
|
||||
static class Renderer
|
||||
{
|
||||
private static int _ssbo;
|
||||
private static int _vao;
|
||||
private static bool _buffersDirty;
|
||||
|
||||
private static Dictionary<(int, int), int> _chunkBufferSizes = new Dictionary<(int, int), int>();
|
||||
private static Shader _shader;
|
||||
private static readonly Texture _texture;
|
||||
private static World? _world;
|
||||
|
||||
static Renderer()
|
||||
{
|
||||
string vertexPath = "Shaders/shader.vert";
|
||||
string fragmentPath = "Shaders/shader.frag";
|
||||
string texturePath = "Assets/atlas.png";
|
||||
|
||||
_shader = new Shader(vertexPath, fragmentPath);
|
||||
_texture = new Texture(texturePath);
|
||||
|
||||
_shader.SetInt("uTexture", 0);
|
||||
|
||||
_ssbo = GL.GenBuffer();
|
||||
_vao = GL.GenVertexArray();
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
|
||||
|
||||
GL.BufferData(BufferTarget.ShaderStorageBuffer, 1024 * 1024 * 128, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, 0, _ssbo);
|
||||
}
|
||||
|
||||
public static void Render()
|
||||
{
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
|
||||
|
||||
_shader.Use();
|
||||
_shader.SetMatrix4("view", Camera.view);
|
||||
_shader.SetVector3("cameraPosition", Camera.Position);
|
||||
_shader.SetMatrix4("projection", Camera.projection);
|
||||
|
||||
if (_buffersDirty)
|
||||
{
|
||||
UpdateAllChunksBuffer();
|
||||
_buffersDirty = false;
|
||||
}
|
||||
|
||||
RenderWorld();
|
||||
RenderUi();
|
||||
}
|
||||
|
||||
public static void UpdateChunkBuffer(Chunk chunk)
|
||||
{
|
||||
if (_world == null || !_chunkBufferSizes.TryGetValue((chunk.X, chunk.Y), out int faceOffset))
|
||||
return;
|
||||
|
||||
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
||||
byte[] data = chunkMesh.GetPackedData();
|
||||
|
||||
int newByteSize = chunkMesh.Size * 4;
|
||||
int currentAllocatedBytes = GetAllocatedSizeForChunk(chunk.X, chunk.Y);
|
||||
|
||||
if (newByteSize <= currentAllocatedBytes)
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
|
||||
GL.BufferSubData(BufferTarget.ShaderStorageBuffer, (IntPtr)(faceOffset * 4), newByteSize, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
MarkBuffersDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetAllocatedSizeForChunk(int x, int y)
|
||||
{
|
||||
// todo, memory manager
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void UpdateAllChunksBuffer()
|
||||
{
|
||||
if (_world == null) return;
|
||||
|
||||
_chunkBufferSizes.Clear();
|
||||
int faceOffset = 0;
|
||||
|
||||
foreach (Chunk chunk in _world.GetAllChunks())
|
||||
{
|
||||
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
||||
|
||||
_chunkBufferSizes[(chunk.X, chunk.Y)] = faceOffset;
|
||||
|
||||
byte[] data = chunkMesh.GetPackedData();
|
||||
|
||||
GL.BufferSubData(BufferTarget.ShaderStorageBuffer,
|
||||
(IntPtr)(faceOffset * 4), // faceOffset * 4 = byte offset
|
||||
chunkMesh.Size * 4,
|
||||
data
|
||||
);
|
||||
|
||||
faceOffset += chunkMesh.Size;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenderUi()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void RenderWorld()
|
||||
{
|
||||
if (_world == null) return;
|
||||
|
||||
foreach (Chunk chunk in _world.GetAllChunks())
|
||||
{
|
||||
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
||||
|
||||
if (chunkMesh.Size == 0) continue;
|
||||
|
||||
if (!_chunkBufferSizes.TryGetValue((chunk.X, chunk.Y), out int offset)) continue;
|
||||
|
||||
_shader.SetInt("chunkX", chunk.X);
|
||||
_shader.SetInt("chunkY", chunk.Y);
|
||||
|
||||
//GL.MemoryBarrier(MemoryBarrierFlags.ShaderStorageBarrierBit);
|
||||
GL.DrawArrays(PrimitiveType.Triangles, offset * 6, chunkMesh.Size * 6);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetWorld(World world)
|
||||
{
|
||||
_world = world;
|
||||
_buffersDirty = true;
|
||||
}
|
||||
|
||||
public static void MarkBuffersDirty()
|
||||
{
|
||||
_buffersDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
Graphics/Shader.cs
Normal file
121
Graphics/Shader.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Voxel.Graphics
|
||||
{
|
||||
public class Shader
|
||||
{
|
||||
private int _handle;
|
||||
private bool disposedValue = false;
|
||||
|
||||
public Shader(string vertexPath, string fragmentPath)
|
||||
{
|
||||
string vertexShaderSource = File.ReadAllText(vertexPath);
|
||||
string fragmentShaderSource = File.ReadAllText(fragmentPath);
|
||||
|
||||
int vertexShader = GL.CreateShader(ShaderType.VertexShader);
|
||||
int fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
|
||||
|
||||
GL.ShaderSource(vertexShader, vertexShaderSource);
|
||||
GL.ShaderSource(fragmentShader, fragmentShaderSource);
|
||||
|
||||
GL.CompileShader(vertexShader);
|
||||
GL.GetShader(vertexShader, ShaderParameter.CompileStatus, out int success);
|
||||
if (success == 0)
|
||||
{
|
||||
string infoLog = GL.GetShaderInfoLog(vertexShader);
|
||||
Console.WriteLine(infoLog);
|
||||
}
|
||||
|
||||
GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out success);
|
||||
if (success == 0)
|
||||
{
|
||||
string infoLog = GL.GetShaderInfoLog(fragmentShader);
|
||||
Console.WriteLine(infoLog);
|
||||
}
|
||||
|
||||
// attach
|
||||
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
GL.AttachShader(_handle, vertexShader);
|
||||
GL.AttachShader(_handle, fragmentShader);
|
||||
|
||||
GL.LinkProgram(_handle);
|
||||
|
||||
GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out success);
|
||||
if (success == 0)
|
||||
{
|
||||
string infoLog = GL.GetProgramInfoLog(_handle);
|
||||
Console.WriteLine(infoLog);
|
||||
}
|
||||
|
||||
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 SetVector3(string name, Vector3 vector3)
|
||||
{
|
||||
int location = GL.GetUniformLocation(_handle, name);
|
||||
if (location == -1)
|
||||
{
|
||||
Console.WriteLine($"Uniform '{name}' not found in shader.");
|
||||
return;
|
||||
}
|
||||
GL.Uniform3(location, ref vector3);
|
||||
}
|
||||
|
||||
public void SetInt(string name, int value)
|
||||
{
|
||||
int location = GL.GetUniformLocation(_handle, name);
|
||||
if (location == -1)
|
||||
{
|
||||
Console.WriteLine($"Uniform '{name}' not found in shader.");
|
||||
return;
|
||||
}
|
||||
GL.Uniform1(location, value);
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
GL.UseProgram(_handle);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
GL.DeleteProgram(_handle);
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Shader()
|
||||
{
|
||||
if (disposedValue == false)
|
||||
{
|
||||
Console.WriteLine("GPU Resource leak! Did you forget to call Dispose()?");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Graphics/Texture.cs
Normal file
47
Graphics/Texture.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using StbImageSharp;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class Texture
|
||||
{
|
||||
private int _handle;
|
||||
private string _path;
|
||||
|
||||
public Texture(string path)
|
||||
{
|
||||
_handle = GL.GenTexture();
|
||||
_path = path;
|
||||
|
||||
LoadFromFile();
|
||||
}
|
||||
|
||||
private void LoadFromFile()
|
||||
{
|
||||
StbImage.stbi_set_flip_vertically_on_load(1);
|
||||
|
||||
using (var stream = File.OpenRead(_path))
|
||||
{
|
||||
ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _handle);
|
||||
GL.TexImage2D(
|
||||
TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
|
||||
image.Width, image.Height, 0,
|
||||
PixelFormat.Rgba, PixelType.UnsignedByte, image.Data
|
||||
);
|
||||
}
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
}
|
||||
|
||||
public void Bind(TextureUnit unit = TextureUnit.Texture0)
|
||||
{
|
||||
GL.ActiveTexture(unit);
|
||||
GL.BindTexture(TextureTarget.Texture2D, _handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Graphics/Textures.cs
Normal file
44
Graphics/Textures.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace Voxel
|
||||
{
|
||||
public enum Textures : byte
|
||||
{
|
||||
GrassTop,
|
||||
Stone,
|
||||
Dirt,
|
||||
GrassSide,
|
||||
OakPlanks,
|
||||
SmoothStoneSlab,
|
||||
SmoothStone,
|
||||
Bricks,
|
||||
TntSide,
|
||||
TntTop,
|
||||
TntBottom,
|
||||
Cobweb,
|
||||
Rose,
|
||||
Dandelion,
|
||||
Water,
|
||||
OakSapling,
|
||||
Cobblestone,
|
||||
Bedrock,
|
||||
Sand,
|
||||
Gravel,
|
||||
OakSide,
|
||||
OakTop,
|
||||
IronBlock,
|
||||
GoldBlock,
|
||||
DiamondBlock,
|
||||
EmeraldBlock,
|
||||
RedstoneBlock,
|
||||
none0,
|
||||
RedMushroom,
|
||||
BrownMushroom,
|
||||
JungleSapling,
|
||||
none1,
|
||||
GoldOre,
|
||||
IronOre,
|
||||
CoalOre,
|
||||
Bookshelf,
|
||||
MossyCobblestone,
|
||||
Obsidian,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user