From 9daa9f8267cb40e64116ab5db18f6f246b43d2ad Mon Sep 17 00:00:00 2001 From: MDietermann Date: Mon, 1 Jun 2026 18:46:02 +0200 Subject: [PATCH] feat(rendering): distance fog and config-derived far plane --- Assets/shaders/chunk.frag | 17 ++++++++++++++++- Assets/shaders/chunk.vert | 2 ++ Core/Game.cs | 7 ++++++- Game/Player.cs | 6 +++++- Rendering/Camera.cs | 3 ++- Rendering/Shader.cs | 3 +++ VoxelEngine.Tests/CameraTests.cs | 8 ++++++++ 7 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Assets/shaders/chunk.frag b/Assets/shaders/chunk.frag index e3e392f..0bc186b 100644 --- a/Assets/shaders/chunk.frag +++ b/Assets/shaders/chunk.frag @@ -2,6 +2,7 @@ in vec2 vUv; in float vShade; in float vWorldY; +in vec3 vWorldPos; out vec4 FragColor; @@ -10,11 +11,25 @@ uniform float u_caveDarkStartY; uniform float u_caveDarkEndY; uniform float u_caveMinBrightness; +uniform vec3 u_cameraPos; +uniform float u_fogStart; +uniform float u_fogEnd; +uniform vec3 u_skyColor; + void main() { vec4 texel = texture(atlas, vUv); + + // Y-based cave darkness. float t = clamp((u_caveDarkStartY - vWorldY) / (u_caveDarkStartY - u_caveDarkEndY), 0.0, 1.0); float brightness = mix(1.0, u_caveMinBrightness, t); - FragColor = vec4(texel.rgb * vShade * brightness, 1.0); + vec3 color = texel.rgb * vShade * brightness; + + // Distance fog to the sky color, applied after cave darkness. + float dist = length(vWorldPos - u_cameraPos); + float fog = clamp((dist - u_fogStart) / (u_fogEnd - u_fogStart), 0.0, 1.0); + color = mix(color, u_skyColor, fog); + + FragColor = vec4(color, 1.0); } diff --git a/Assets/shaders/chunk.vert b/Assets/shaders/chunk.vert index 09d7209..0dfad7b 100644 --- a/Assets/shaders/chunk.vert +++ b/Assets/shaders/chunk.vert @@ -6,6 +6,7 @@ layout(location = 2) in float aShade; out vec2 vUv; out float vShade; out float vWorldY; +out vec3 vWorldPos; uniform mat4 model; uniform mat4 view; @@ -17,6 +18,7 @@ void main() vShade = aShade; // World-space position uses row-vector convention (matches gl_Position below). vec4 worldPos = vec4(aPos, 1.0) * model; + vWorldPos = worldPos.xyz; vWorldY = worldPos.y; gl_Position = worldPos * view * projection; } diff --git a/Core/Game.cs b/Core/Game.cs index f0a0a5e..7abc614 100644 --- a/Core/Game.cs +++ b/Core/Game.cs @@ -43,7 +43,7 @@ public sealed class Game : GameWindow Log.Information("Game starting with seed={Seed} renderDistanceH={H} renderDistanceV={V}", this._cfg.Seed, this._cfg.RenderDistanceHorizontal, this._cfg.RenderDistanceVertical); - GL.ClearColor(0.45f, 0.65f, 0.95f, 1.0f); + GL.ClearColor(this._cfg.SkyColor.X, this._cfg.SkyColor.Y, this._cfg.SkyColor.Z, 1.0f); GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.CullFace); @@ -119,6 +119,11 @@ public sealed class Game : GameWindow this._shader.SetFloat("u_caveDarkStartY", this._cfg.CaveDarkStartY); this._shader.SetFloat("u_caveDarkEndY", this._cfg.CaveDarkEndY); this._shader.SetFloat("u_caveMinBrightness", this._cfg.CaveMinBrightness); + float fogEnd = this._cfg.RenderDistanceHorizontal * Chunk.Size; + this._shader.SetFloat("u_fogEnd", fogEnd); + this._shader.SetFloat("u_fogStart", fogEnd * this._cfg.FogStartFraction); + this._shader.SetVector3("u_cameraPos", this._player.Camera.Position); + this._shader.SetVector3("u_skyColor", this._cfg.SkyColor); this._texture.Use(TextureUnit.Texture0); this._streamer.DrawAll((pos, renderer) => diff --git a/Game/Player.cs b/Game/Player.cs index 270a37f..7b66e9e 100644 --- a/Game/Player.cs +++ b/Game/Player.cs @@ -29,7 +29,11 @@ public sealed class Player public Player(Configuration cfg, Vector3 spawnPosition, float aspectRatio) { this._cfg = cfg; - this.Camera = new Camera(spawnPosition, aspectRatio) { Fov = cfg.Fov }; + this.Camera = new Camera(spawnPosition, aspectRatio) + { + Fov = cfg.Fov, + FarPlane = (cfg.RenderDistanceHorizontal + 1) * VoxelChunk.Size, + }; } public void Move(Vector3 delta) => this.Camera.Move(delta); diff --git a/Rendering/Camera.cs b/Rendering/Camera.cs index 9105a06..db8c21a 100644 --- a/Rendering/Camera.cs +++ b/Rendering/Camera.cs @@ -10,6 +10,7 @@ public sealed class Camera public float Yaw { get; private set; } = -90f; // degrees; -90 looks down -Z public float Pitch { get; private set; } // degrees public float Fov { get; set; } = 60f; // degrees + public float FarPlane { get; set; } = 100f; // world units public float AspectRatio { get; set; } public Vector3 Front { get; private set; } = -Vector3.UnitZ; @@ -36,7 +37,7 @@ public sealed class Camera public Matrix4 GetProjectionMatrix() => Matrix4.CreatePerspectiveFieldOfView( - MathHelper.DegreesToRadians(this.Fov), this.AspectRatio, 0.1f, 100f); + MathHelper.DegreesToRadians(this.Fov), this.AspectRatio, 0.1f, this.FarPlane); private void UpdateVectors() { diff --git a/Rendering/Shader.cs b/Rendering/Shader.cs index 522e0ae..c4ba827 100644 --- a/Rendering/Shader.cs +++ b/Rendering/Shader.cs @@ -39,6 +39,9 @@ public sealed class Shader : IDisposable public void SetFloat(string name, float value) => GL.Uniform1(this.Location(name), value); + public void SetVector3(string name, Vector3 value) => + GL.Uniform3(this.Location(name), value.X, value.Y, value.Z); + private int Location(string name) { if (this._uniforms.TryGetValue(name, out int location)) return location; diff --git a/VoxelEngine.Tests/CameraTests.cs b/VoxelEngine.Tests/CameraTests.cs index 0ac5a96..36b6986 100644 --- a/VoxelEngine.Tests/CameraTests.cs +++ b/VoxelEngine.Tests/CameraTests.cs @@ -40,4 +40,12 @@ public class CameraTests var camera = new Camera(Vector3.Zero, 1.5f); Assert.NotEqual(Matrix4.Identity, camera.GetProjectionMatrix()); } + + [Fact] + public void FarPlane_ChangesProjection() + { + var near = new Camera(Vector3.Zero, 1f) { FarPlane = 100f }; + var far = new Camera(Vector3.Zero, 1f) { FarPlane = 200f }; + Assert.NotEqual(near.GetProjectionMatrix(), far.GetProjectionMatrix()); + } }