feat(app): render landblock with height-ramp shader + orbit camera

Adds a 2-stage GLSL shader (vertex + fragment), a Shader helper that
compiles/links and exposes SetMatrix4 for uniforms, and an OrbitCamera
with yaw/pitch/distance and a 192-unit-centered target for a single
landblock. TerrainRenderer now takes a Shader and issues an actual
DrawElements call with uView + uProjection uniforms. GameWindow owns
the Shader and Camera, routes mouse drag to camera yaw/pitch, and
scroll wheel to camera distance.

The fragment shader maps world Z to a green-brown-white ramp so
lowlands read green, midlands brown, and peaks white — no textures
yet, but enough to visually confirm the terrain shape.

Shaders are copied to the output dir via a <None Update> item group.

Smoke verified against real dats: process stays alive with no GL
errors, no shader compile/link failures, and no exception trail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-10 16:44:08 +02:00
parent 8356fe65a0
commit 87c45c70ac
7 changed files with 154 additions and 7 deletions

View file

@ -16,7 +16,11 @@ public sealed class GameWindow : IDisposable
private GL? _gl;
private IInputContext? _input;
private TerrainRenderer? _terrain;
private Shader? _shader;
private OrbitCamera? _camera;
private DatCollection? _dats;
private float _lastMouseX;
private float _lastMouseY;
public GameWindow(string datDir) => _datDir = datDir;
@ -53,9 +57,37 @@ public sealed class GameWindow : IDisposable
_window!.Close();
};
foreach (var mouse in _input.Mice)
{
mouse.MouseMove += (m, pos) =>
{
if (m.IsButtonPressed(MouseButton.Left))
{
_camera!.Yaw -= (pos.X - _lastMouseX) * 0.005f;
_camera!.Pitch = Math.Clamp(
_camera.Pitch + (pos.Y - _lastMouseY) * 0.005f,
0.1f, 1.5f);
}
_lastMouseX = pos.X;
_lastMouseY = pos.Y;
};
mouse.Scroll += (_, scroll) =>
_camera!.Distance = Math.Clamp(_camera.Distance - scroll.Y * 20f, 50f, 2000f);
}
_gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
_gl.Enable(EnableCap.DepthTest);
string shadersDir = Path.Combine(AppContext.BaseDirectory, "Rendering", "Shaders");
_shader = new Shader(_gl,
Path.Combine(shadersDir, "terrain.vert"),
Path.Combine(shadersDir, "terrain.frag"));
_camera = new OrbitCamera
{
Aspect = _window!.Size.X / (float)_window.Size.Y,
};
_dats = new DatCollection(_datDir, DatAccessType.Read);
// Find ANY landblock ending in 0xFFFF. Holtburg 0xA9B4FFFF is a
@ -81,18 +113,19 @@ public sealed class GameWindow : IDisposable
Console.WriteLine($"loaded landblock 0x{landblockId:X8}");
var meshData = LandblockMesh.Build(block);
_terrain = new TerrainRenderer(_gl, meshData);
_terrain = new TerrainRenderer(_gl, meshData, _shader);
}
private void OnRender(double deltaSeconds)
{
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_terrain?.Draw();
_terrain?.Draw(_camera!);
}
private void OnClosing()
{
_terrain?.Dispose();
_shader?.Dispose();
_dats?.Dispose();
_input?.Dispose();
_gl?.Dispose();