feat(rendering): frustum-cull chunk draws and log render counts
This commit is contained in:
29
Core/Game.cs
29
Core/Game.cs
@@ -29,6 +29,7 @@ public sealed class Game : GameWindow
|
|||||||
private WorldGenerator _generator = null!;
|
private WorldGenerator _generator = null!;
|
||||||
private ChunkStreamer _streamer = null!;
|
private ChunkStreamer _streamer = null!;
|
||||||
private Player _player = null!;
|
private Player _player = null!;
|
||||||
|
private double _logAccumSeconds;
|
||||||
|
|
||||||
public Game(Configuration cfg, GameWindowSettings gameSettings, NativeWindowSettings nativeSettings)
|
public Game(Configuration cfg, GameWindowSettings gameSettings, NativeWindowSettings nativeSettings)
|
||||||
: base(gameSettings, nativeSettings)
|
: base(gameSettings, nativeSettings)
|
||||||
@@ -127,12 +128,30 @@ public sealed class Game : GameWindow
|
|||||||
this._shader.SetVector3("u_skyColor", this._cfg.SkyColor);
|
this._shader.SetVector3("u_skyColor", this._cfg.SkyColor);
|
||||||
this._texture.Use(TextureUnit.Texture0);
|
this._texture.Use(TextureUnit.Texture0);
|
||||||
|
|
||||||
this._streamer.DrawAll((pos, renderer) =>
|
Matrix4 viewProj = this._player.Camera.GetViewMatrix() * this._player.Camera.GetProjectionMatrix();
|
||||||
|
Frustum frustum = Frustum.FromViewProjection(viewProj);
|
||||||
|
|
||||||
|
int drawn = this._streamer.DrawVisible(
|
||||||
|
pos =>
|
||||||
|
{
|
||||||
|
Vector3 min = new(pos.X * Chunk.Size, pos.Y * Chunk.Size, pos.Z * Chunk.Size);
|
||||||
|
Vector3 max = min + new Vector3(Chunk.Size);
|
||||||
|
return frustum.Intersects(min, max);
|
||||||
|
},
|
||||||
|
(pos, renderer) =>
|
||||||
|
{
|
||||||
|
this._shader.SetMatrix4("model", Matrix4.CreateTranslation(
|
||||||
|
pos.X * Chunk.Size, pos.Y * Chunk.Size, pos.Z * Chunk.Size));
|
||||||
|
renderer.Draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._logAccumSeconds += args.Time;
|
||||||
|
if (this._logAccumSeconds >= 1.0)
|
||||||
{
|
{
|
||||||
this._shader.SetMatrix4("model", Matrix4.CreateTranslation(
|
this._logAccumSeconds = 0;
|
||||||
pos.X * Chunk.Size, pos.Y * Chunk.Size, pos.Z * Chunk.Size));
|
Log.Information("Rendered {Drawn} / {Renderers} chunks ({Tracked} loaded)",
|
||||||
renderer.Draw();
|
drawn, this._streamer.RendererCount, this._streamer.TrackedCount);
|
||||||
});
|
}
|
||||||
|
|
||||||
this.SwapBuffers();
|
this.SwapBuffers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using VoxelEngine.Core;
|
using VoxelEngine.Core;
|
||||||
using VoxelEngine.World;
|
using VoxelEngine.World;
|
||||||
@@ -445,4 +446,25 @@ public class ChunkStreamerTests
|
|||||||
Assert.Null(world.GetChunk(new Vector3i(0, 3, 0))); // air above surface — not loaded
|
Assert.Null(world.GetChunk(new Vector3i(0, 3, 0))); // air above surface — not loaded
|
||||||
Assert.Null(world.GetChunk(new Vector3i(0, 5, 0))); // air above surface — not loaded
|
Assert.Null(world.GetChunk(new Vector3i(0, 5, 0))); // air above surface — not loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DrawVisible_DrawsOnlyChunksPassingPredicate_AndReturnsCount()
|
||||||
|
{
|
||||||
|
var cfg = new Configuration
|
||||||
|
{
|
||||||
|
RenderDistanceHorizontal = 1,
|
||||||
|
RenderDistanceVertical = 0,
|
||||||
|
MaxGenerateJobsInFlight = 100,
|
||||||
|
MaxMeshUploadsPerFrame = 100,
|
||||||
|
};
|
||||||
|
var (streamer, _, created) = CreateStreamer(cfg);
|
||||||
|
streamer.Tick(new Vector3i(0, 0, 0)); // 9 columns, all chunk Y 0, non-empty meshes
|
||||||
|
|
||||||
|
int drawn = streamer.DrawVisible(
|
||||||
|
pos => pos == new Vector3i(0, 0, 0), // only the centre chunk "visible"
|
||||||
|
(_, r) => r.Draw());
|
||||||
|
|
||||||
|
Assert.Equal(1, drawn);
|
||||||
|
Assert.Equal(1, created.Sum(r => r.DrawCount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public sealed class ChunkStreamer : IDisposable
|
|||||||
private Vector3i _spawnAnchor;
|
private Vector3i _spawnAnchor;
|
||||||
|
|
||||||
public int RendererCount => this._renderers.Count;
|
public int RendererCount => this._renderers.Count;
|
||||||
|
public int TrackedCount => this._tracked.Count;
|
||||||
public bool IsReady(Vector3i pos) => this._ready.Contains(pos);
|
public bool IsReady(Vector3i pos) => this._ready.Contains(pos);
|
||||||
public bool SpawnReady => this._spawnReady;
|
public bool SpawnReady => this._spawnReady;
|
||||||
|
|
||||||
@@ -88,9 +89,20 @@ public sealed class ChunkStreamer : IDisposable
|
|||||||
this.UpdateSpawnReady();
|
this.UpdateSpawnReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawAll(Action<Vector3i, IChunkRenderer> drawCallback)
|
// Draws every loaded chunk for which `isChunkVisible` returns true. Returns
|
||||||
|
// the number of chunks actually drawn (passed the visibility test). The
|
||||||
|
// visibility predicate lives with the caller so the streamer stays free of
|
||||||
|
// any rendering/Frustum dependency.
|
||||||
|
public int DrawVisible(Func<Vector3i, bool> isChunkVisible, Action<Vector3i, IChunkRenderer> draw)
|
||||||
{
|
{
|
||||||
foreach (var kvp in this._renderers) drawCallback(kvp.Key, kvp.Value);
|
int drawn = 0;
|
||||||
|
foreach (var kvp in this._renderers)
|
||||||
|
{
|
||||||
|
if (!isChunkVisible(kvp.Key)) continue;
|
||||||
|
draw(kvp.Key, kvp.Value);
|
||||||
|
drawn++;
|
||||||
|
}
|
||||||
|
return drawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -258,7 +270,7 @@ public sealed class ChunkStreamer : IDisposable
|
|||||||
}
|
}
|
||||||
// Skip renderer allocation for empty meshes (pure-air chunks above
|
// Skip renderer allocation for empty meshes (pure-air chunks above
|
||||||
// terrain, or chunks whose every face culls). The chunk still counts
|
// terrain, or chunks whose every face culls). The chunk still counts
|
||||||
// as ready — DrawAll's iteration over _renderers naturally no-ops.
|
// as ready — DrawVisible's iteration over _renderers naturally no-ops.
|
||||||
if (result.Mesh.Length > 0)
|
if (result.Mesh.Length > 0)
|
||||||
{
|
{
|
||||||
this._renderers[result.Pos] = this._rendererFactory(result.Mesh);
|
this._renderers[result.Pos] = this._rendererFactory(result.Mesh);
|
||||||
|
|||||||
Reference in New Issue
Block a user