Compare commits

...

5 Commits

Author SHA1 Message Date
maxwes08
9ffda6f5af Reduced render distance, added back fog 2025-12-15 10:36:33 +01:00
maxwes08
01d86cd2aa optimized 2025-12-15 10:25:50 +01:00
maxwes08
f532eddfbb optimized faces 2025-12-15 10:14:16 +01:00
max
0641d22c9b changed some rendering buffer code 2025-12-15 00:18:16 +01:00
max
35ecc36bdb Fixed negative chunks invisible 2025-12-14 23:54:22 +01:00
13 changed files with 230 additions and 182 deletions

View File

@@ -40,7 +40,7 @@ namespace Voxel
Y = y;
_blockData = new Dictionary<ushort, BlockData>();
_blocks = new Blocks[Size * Size * Height];
_blocks = new Blocks[TotalBlocks];
_chunkMesh = new ChunkMesh(X, Y);
Initialize();
@@ -219,14 +219,16 @@ namespace Voxel
private void AddFace(int x, int y, int z, int face, Textures texture, List<FaceData> faces)
{
faces.Add(new FaceData
{
Facing = (Orientation)face,
Texture = texture,
X = (byte)x,
Y = (byte)y,
Z = (byte)z
});
byte lightLevel = 15;
faces.Add(new FaceData(
(byte)x,
(byte)y,
(byte)z,
(Orientation)face,
texture,
lightLevel
));
}
public ChunkMesh GetChunkMesh()

View File

@@ -1,25 +1,15 @@
using OpenTK.Graphics.OpenGL4;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
namespace Voxel
{
public class ChunkMesh
{
public int X;
public int Y;
private byte[] _packedData;
public bool NeedsUpdate = false;
public int Size = 0;
private List<FaceData> _faces;
public ChunkMesh(int x, int y)
{
_packedData = new byte[0];
_packedData = Array.Empty<byte>();
X = x;
Y = y;
}
@@ -27,18 +17,31 @@ namespace Voxel
public void SetFaces(List<FaceData> faces)
{
Size = faces.Count;
_faces = faces;
NeedsUpdate = true;
_packedData = PackFaces(faces);
}
public byte[] GetPackedData()
private static byte[] PackFaces(List<FaceData> faces)
{
if (NeedsUpdate)
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++)
{
_packedData = _faces.SelectMany(f => f.Pack()).ToArray();
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 _packedData;
return result;
}
public byte[] GetPackedData() => _packedData;
}
}

View File

@@ -2,25 +2,42 @@
namespace Voxel
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FaceData
{
public Orientation Facing;
public byte X, Y, Z;
public Textures Texture;
public byte LightLevel;
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 new byte[]
{
X,
Y,
Z,
(byte)Facing,
(byte)Texture,
LightLevel,
0,0 // two bits empty
};
return BitConverter.GetBytes(_data);
}
}
}

View File

@@ -1,12 +1,6 @@
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
{
@@ -48,6 +42,8 @@ namespace Voxel
public new void Tick()
{
_world.UpdateChunkLoading(Position);
previousPosition = Position;
float forwards = 0;
@@ -64,7 +60,6 @@ namespace Voxel
if (Input.GetKey(Keys.Space) && OnGround)
{
Console.WriteLine("Jump");
Velocity = new Vector3(Velocity.X, 0.42f, Velocity.Z);
OnGround = false;
}

View File

@@ -12,34 +12,6 @@ internal class Program
World world = new World();
Window window = new Window(sizeX, sizeY, title);
Console.WriteLine("Generating map...");
int worldSizeX = 8;
int worldSizeY = 8;
float maxI = worldSizeX * worldSizeY;
int i = 0;
int lastPercentage = 0;
for (int x = 0; x < worldSizeX; x++)
{
for (int y = 0; y < worldSizeY; y++)
{
i++;
Chunk chunk = new Chunk(x, y);
world.AddChunk(chunk);
int percentage = (int)((i / maxI) * 100);
if (percentage > lastPercentage)
{
lastPercentage = percentage;
Console.WriteLine((percentage).ToString() + "%");
}
}
}
Console.WriteLine("Generated " + maxI.ToString() + " chunks");
Renderer.SetWorld(world);
Vector3 startPos = new Vector3(15, 64, 15);

View File

@@ -60,46 +60,30 @@ namespace Voxel
{
if (_world == null) return;
int offset = 0;
_chunkBufferSizes.Clear();
int faceOffset = 0;
foreach (Chunk chunk in _world.GetAllChunks())
{
ChunkMesh chunkMesh = chunk.GetChunkMesh();
if (chunkMesh.NeedsUpdate)
{
byte[] data = chunkMesh.GetPackedData();
GL.BufferSubData(BufferTarget.ShaderStorageBuffer, (IntPtr)offset * 8, chunkMesh.Size * 8, data);
}
_chunkBufferSizes[(chunk.X, chunk.Y)] = faceOffset;
_chunkBufferSizes[(chunk.X, chunk.Y)] = offset;
offset += chunkMesh.Size * 8;
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()
{
GL.Disable(EnableCap.DepthTest);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
//_uiShader.Use();
//Matrix4 projection = Matrix4.CreateOrthographicOffCenter(
// 0, screenWidth, screenHeight, 0, -1, 1);
//_uiShader.SetMatrix4("projection", projection);
// Bind UI texture atlas
//_uiTexture.Bind();
// Draw all UI sprites (batch by texture for efficiency)
//foreach (var sprite in _uiSprites)
//{
// sprite.Draw();
//}
// Restore 3D settings
GL.Disable(EnableCap.Blend);
GL.Enable(EnableCap.DepthTest);
}
private static void RenderWorld()

View File

@@ -10,16 +10,23 @@ uniform vec3 cameraPosition;
void main()
{
float fogEnd = 512;
float fogStart = 32;
float dist = length(cameraPosition - fragPos);
float fogFactor = (fogEnd - dist) / (fogEnd - fogStart);
fogFactor = clamp(fogFactor, 0, 1);
vec4 fogColor = vec4(0.8,0.8,0.8,1);
vec4 texColor = texture(uTexture, fragUV) * lighting;
vec4 color = mix(fogColor, texColor, fogFactor);
FragColor = vec4(color.rgb, texColor.a);
// Use squared distance
vec3 delta = cameraPosition - fragPos;
float distSq = dot(delta, delta);
// Precomputed fog parameters
const float fogEndSq = 65536.0; // 128^2
const float fogStartSq = 1024.0; // 32^2
const float fogRangeInv = 1.0 / (fogEndSq - fogStartSq);
float fogFactor = (fogEndSq - distSq) * fogRangeInv;
fogFactor = clamp(fogFactor, 0.0, 1.0);
// Texture and lighting
vec4 texColor = texture(uTexture, fragUV);
texColor.rgb *= lighting;
// Fog blend
const vec3 fogColor = vec3(0.8, 0.8, 0.8);
FragColor = vec4(mix(fogColor, texColor.rgb, fogFactor), texColor.a);
}

View File

@@ -1,17 +1,12 @@
#version 430 core
struct FaceData {
uint x;
uint y;
uint z;
uint facing; // 0=+X,1=-X,2=+Y,3=-Y,4=+Z,5=-Z
uint texture;
uint lightLevel;
};
struct FaceData {
uint pack; // All data in 4 bytes
};
layout(std430, binding = 0) buffer FaceBuffer {
uint faces[];
};
layout(std430, binding = 0) buffer FaceBuffer {
uint faces[];
};
uniform mat4 view;
uniform mat4 projection;
@@ -77,25 +72,38 @@ const vec3 offsets[6][6] = vec3[6][6](
const vec2 uvs[6] = vec2[6](vec2(0,0), vec2(1,1), vec2(1,0), vec2(1,1), vec2(0,0), vec2(0,1));
// Function to unpack the 4-byte face data
// Bit layout from C# (little-endian, but we read as uint):
// Bits 31-24: Y (8 bits) 0-255
// Bits 23-20: Z (4 bits) 0-15
// Bits 19-16: X (4 bits) 0-15
// Bits 15-10: Texture (6 bits) 0-63
// Bits 9-7: Facing (3 bits) 0-7
// Bits 6-3: Light (4 bits) 0-15
// Bits 2-0: unused (3 bits)
void unpackFace(uint pack, out uint x, out uint y, out uint z,
out uint facing, out uint texture, out uint lightLevel)
{
y = (pack >> 24) & 0xFFu; // Y: bits 24-31 (8 bits)
z = (pack >> 20) & 0x0Fu; // Z: bits 20-23 (4 bits)
x = (pack >> 16) & 0x0Fu; // X: bits 16-19 (4 bits)
texture = (pack >> 10) & 0x3Fu; // Texture: bits 10-15 (6 bits)
facing = (pack >> 7) & 0x07u; // Facing: bits 7-9 (3 bits)
lightLevel = (pack >> 3) & 0x0Fu;// Light: bits 3-6 (4 bits)
}
void main()
{
uint faceIndex = gl_VertexID / 6u;
uint vertIndex = gl_VertexID % 6u;
uint start = faceIndex * 2u; // 2 byte per face
uint u0 = faces[start]; // data in uint 0
uint u1 = faces[start + 1]; // data in uint 1
uint pack = faces[faceIndex];
// extract values from bits
uint x = u0 & 0xFFu;
uint y = (u0 >> 8) & 0xFFu;
uint z = (u0 >> 16) & 0xFFu;
uint facing = (u0 >> 24) & 0xFFu;
uint texture = u1 & 0xFFu;
uint lightLevel = (u1 >> 8) & 0xFFu;
uint x, y, z, facing, texture, lightLevel;
unpackFace(pack, x, y, z, facing, texture, lightLevel);
vec3 basePos = vec3(x, y, z) + vec3(chunkX, 0, chunkY) * 16.0;
vec3 basePos = vec3(x + chunkX * 16, y, z + chunkY * 16);
vec4 worldPos = vec4(basePos + offsets[facing][vertIndex], 1.0);
float light = float(lightLevel) / 255.0; // use later for caves and stuff

View File

@@ -1,6 +1,6 @@
namespace Voxel
{
public enum Textures : uint
public enum Textures : byte
{
GrassTop,
Stone,

7
UIElement.cs Normal file
View File

@@ -0,0 +1,7 @@
using OpenTK.Mathematics;
using System.Drawing;
public class UIElement
{
}

View File

@@ -1,17 +0,0 @@
using OpenTK.Mathematics;
using System.Drawing;
public class UISprite
{
public Vector2 Position; // Screen pixels (not normalized)
public Vector2 Size; // Size in pixels
public Rectangle TextureRegion; // Which part of atlas to use
public Color Tint = Color.White;
public float Rotation = 0f;
public Vector2 Origin = Vector2.Zero; // Rotation/scale origin
public void Draw()
{
// Draw textured quad using TextureRegion coordinates
}
}

View File

@@ -90,7 +90,7 @@ namespace Voxel
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
CursorState = CursorState.Grabbed;
//VSync = VSyncMode.On;
VSync = VSyncMode.On;
Camera.UpdateSize(Width, Height);
}

114
World.cs
View File

@@ -8,6 +8,10 @@ namespace Voxel
{
private Dictionary<(int, int), Chunk> _chunks;
private (int x, int z) _lastCenter = (0, 0);
private int _loadDistance = 8;
bool chunkLoadingInitialized = false;
private static readonly Dictionary<Orientation, (int x, int y)> _neighborOffsets = new()
{
{ Orientation.West, (1, 0) },
@@ -33,6 +37,7 @@ namespace Voxel
UpdateNeighboringChunks(chunk);
chunk.UpdateChunkMesh();
Renderer.MarkBuffersDirty();
}
public void RemoveChunk(int chunkX, int chunkZ)
@@ -52,6 +57,65 @@ namespace Voxel
}
}
public new void UpdateChunkLoading(Vector3 playerPosition)
{
int centerX = (int)Math.Floor(playerPosition.X / Chunk.Size);
int centerZ = (int)Math.Floor(playerPosition.Z / Chunk.Size);
// Quick check - skip if still in same chunk
if ((centerX == _lastCenter.x && centerZ == _lastCenter.z) && chunkLoadingInitialized)
return;
_lastCenter = (centerX, centerZ);
// Calculate bounds
int minX = centerX - _loadDistance;
int maxX = centerX + _loadDistance;
int minZ = centerZ - _loadDistance;
int maxZ = centerZ + _loadDistance;
// Unload chunks outside range
UnloadDistantChunks(minX, maxX, minZ, maxZ);
// Load chunks inside range
LoadChunksInRange(minX, maxX, minZ, maxZ);
chunkLoadingInitialized = true;
}
private void UnloadDistantChunks(int minX, int maxX, int minZ, int maxZ)
{
var chunksToRemove = new List<(int, int)>();
foreach (var (chunkX, chunkZ) in _chunks.Keys)
{
if (chunkX < minX || chunkX > maxX || chunkZ < minZ || chunkZ > maxZ)
{
chunksToRemove.Add((chunkX, chunkZ));
}
}
foreach (var chunkPos in chunksToRemove)
{
RemoveChunk(chunkPos.Item1, chunkPos.Item2);
}
}
private void LoadChunksInRange(int minX, int maxX, int minZ, int maxZ)
{
for (int x = minX; x <= maxX; x++)
{
for (int z = minZ; z <= maxZ; z++)
{
if (!_chunks.ContainsKey((x, z)))
{
Chunk chunk = new Chunk(x, z);
AddChunk(chunk);
}
}
}
}
public void UpdateNeighboringChunks(Chunk chunk)
{
chunk.Neighbors[Orientation.West] = null;
@@ -87,39 +151,21 @@ namespace Voxel
public Blocks GetBlock(int worldX, int worldY, int worldZ)
{
int chunkX = worldX / Chunk.Size;
int chunkZ = worldZ / Chunk.Size;
var (chunkX, chunkZ, localX, localZ) = WorldToChunkCoords(worldX, worldZ);
Chunk chunk = GetChunk(chunkX, chunkZ);
if (chunk == null) return 0; // air if chunk not loaded
int localX = worldX % Chunk.Size;
int localZ = worldZ % Chunk.Size;
return chunk.GetBlock(localX, worldY, localZ);
}
List<Orientation> GetEdgeOrientations(int localX, int localZ, int size)
{
var orientations = new List<Orientation>();
if (localX == size - 1) orientations.Add(Orientation.West);
if (localX == 0) orientations.Add(Orientation.East);
if (localZ == size - 1) orientations.Add(Orientation.North);
if (localZ == 0) orientations.Add(Orientation.South);
return orientations;
}
public void SetBlock(int worldX, int worldY, int worldZ, Blocks block)
{
int chunkX = worldX / Chunk.Size;
int chunkZ = worldZ / Chunk.Size;
var (chunkX, chunkZ, localX, localZ) = WorldToChunkCoords(worldX, worldZ);
Chunk chunk = GetChunk(chunkX, chunkZ);
if (chunk == null) return;
int localX = worldX % Chunk.Size;
int localZ = worldZ % Chunk.Size;
chunk.SetBlock(localX, worldY, localZ, block);
if (block == Blocks.Air && ((localX == Chunk.Size - 1 || localX == 0) || (localZ == Chunk.Size - 1 || localZ == 0)))
@@ -134,6 +180,30 @@ namespace Voxel
}
}
public static (int chunkX, int chunkZ, int localX, int localZ) WorldToChunkCoords(int worldX, int worldZ)
{
int chunkX = worldX >= 0 ? worldX / Chunk.Size : (worldX + 1) / Chunk.Size - 1;
int chunkZ = worldZ >= 0 ? worldZ / Chunk.Size : (worldZ + 1) / Chunk.Size - 1;
int localX = ((worldX % Chunk.Size) + Chunk.Size) % Chunk.Size;
int localZ = ((worldZ % Chunk.Size) + Chunk.Size) % Chunk.Size;
return (chunkX, chunkZ, localX, localZ);
}
List<Orientation> GetEdgeOrientations(int localX, int localZ, int size)
{
var orientations = new List<Orientation>();
if (localX == size - 1) orientations.Add(Orientation.West);
if (localX == 0) orientations.Add(Orientation.East);
if (localZ == size - 1) orientations.Add(Orientation.North);
if (localZ == 0) orientations.Add(Orientation.South);
return orientations;
}
public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
{
int chunkX = chunk.X;