feat(app): render static meshes from Holtburg LandBlockInfo
This commit is contained in:
parent
cefc689ba8
commit
1375780e14
4 changed files with 232 additions and 0 deletions
|
|
@ -21,6 +21,10 @@ public sealed class GameWindow : IDisposable
|
||||||
private DatCollection? _dats;
|
private DatCollection? _dats;
|
||||||
private float _lastMouseX;
|
private float _lastMouseX;
|
||||||
private float _lastMouseY;
|
private float _lastMouseY;
|
||||||
|
private StaticMeshRenderer? _staticMesh;
|
||||||
|
private Shader? _meshShader;
|
||||||
|
private TextureCache? _textureCache;
|
||||||
|
private IReadOnlyList<AcDream.Core.World.WorldEntity> _entities = Array.Empty<AcDream.Core.World.WorldEntity>();
|
||||||
|
|
||||||
public GameWindow(string datDir) => _datDir = datDir;
|
public GameWindow(string datDir) => _datDir = datDir;
|
||||||
|
|
||||||
|
|
@ -83,6 +87,10 @@ public sealed class GameWindow : IDisposable
|
||||||
Path.Combine(shadersDir, "terrain.vert"),
|
Path.Combine(shadersDir, "terrain.vert"),
|
||||||
Path.Combine(shadersDir, "terrain.frag"));
|
Path.Combine(shadersDir, "terrain.frag"));
|
||||||
|
|
||||||
|
_meshShader = new Shader(_gl,
|
||||||
|
Path.Combine(shadersDir, "mesh.vert"),
|
||||||
|
Path.Combine(shadersDir, "mesh.frag"));
|
||||||
|
|
||||||
_camera = new OrbitCamera
|
_camera = new OrbitCamera
|
||||||
{
|
{
|
||||||
Aspect = _window!.Size.X / (float)_window.Size.Y,
|
Aspect = _window!.Size.X / (float)_window.Size.Y,
|
||||||
|
|
@ -117,16 +125,82 @@ public sealed class GameWindow : IDisposable
|
||||||
|
|
||||||
var meshData = LandblockMesh.Build(block);
|
var meshData = LandblockMesh.Build(block);
|
||||||
_terrain = new TerrainRenderer(_gl, meshData, _shader);
|
_terrain = new TerrainRenderer(_gl, meshData, _shader);
|
||||||
|
|
||||||
|
_textureCache = new TextureCache(_gl, _dats);
|
||||||
|
_staticMesh = new StaticMeshRenderer(_gl, _meshShader, _textureCache);
|
||||||
|
|
||||||
|
// Load LandBlockInfo for Holtburg, hydrate entities.
|
||||||
|
var info = _dats.Get<DatReaderWriter.DBObjs.LandBlockInfo>((landblockId & 0xFFFF0000u) | 0xFFFEu);
|
||||||
|
var entities = info is not null
|
||||||
|
? AcDream.Core.World.LandblockLoader.BuildEntitiesFromInfo(info)
|
||||||
|
: Array.Empty<AcDream.Core.World.WorldEntity>();
|
||||||
|
|
||||||
|
// Populate MeshRefs for each entity by resolving its source id to GfxObj or Setup
|
||||||
|
// and extracting sub-meshes. Store back onto the entity. Since WorldEntity is
|
||||||
|
// `required init`, we rebuild the entity here.
|
||||||
|
var hydratedEntities = new List<AcDream.Core.World.WorldEntity>(entities.Count);
|
||||||
|
foreach (var e in entities)
|
||||||
|
{
|
||||||
|
var meshRefs = new List<AcDream.Core.World.MeshRef>();
|
||||||
|
|
||||||
|
if ((e.SourceGfxObjOrSetupId & 0xFF000000u) == 0x01000000u)
|
||||||
|
{
|
||||||
|
// GfxObj: one mesh ref with identity transform.
|
||||||
|
var gfx = _dats.Get<DatReaderWriter.DBObjs.GfxObj>(e.SourceGfxObjOrSetupId);
|
||||||
|
if (gfx is not null)
|
||||||
|
{
|
||||||
|
var subMeshes = AcDream.Core.Meshing.GfxObjMesh.Build(gfx);
|
||||||
|
_staticMesh.EnsureUploaded(e.SourceGfxObjOrSetupId, subMeshes);
|
||||||
|
meshRefs.Add(new AcDream.Core.World.MeshRef(e.SourceGfxObjOrSetupId, System.Numerics.Matrix4x4.Identity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((e.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
|
||||||
|
{
|
||||||
|
// Setup: flatten into parts, upload each part's GfxObj.
|
||||||
|
var setup = _dats.Get<DatReaderWriter.DBObjs.Setup>(e.SourceGfxObjOrSetupId);
|
||||||
|
if (setup is not null)
|
||||||
|
{
|
||||||
|
var flat = AcDream.Core.Meshing.SetupMesh.Flatten(setup);
|
||||||
|
foreach (var mr in flat)
|
||||||
|
{
|
||||||
|
var gfx = _dats.Get<DatReaderWriter.DBObjs.GfxObj>(mr.GfxObjId);
|
||||||
|
if (gfx is null) continue;
|
||||||
|
var subMeshes = AcDream.Core.Meshing.GfxObjMesh.Build(gfx);
|
||||||
|
_staticMesh.EnsureUploaded(mr.GfxObjId, subMeshes);
|
||||||
|
meshRefs.Add(mr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meshRefs.Count > 0)
|
||||||
|
{
|
||||||
|
hydratedEntities.Add(new AcDream.Core.World.WorldEntity
|
||||||
|
{
|
||||||
|
Id = e.Id,
|
||||||
|
SourceGfxObjOrSetupId = e.SourceGfxObjOrSetupId,
|
||||||
|
Position = e.Position,
|
||||||
|
Rotation = e.Rotation,
|
||||||
|
MeshRefs = meshRefs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_entities = hydratedEntities;
|
||||||
|
Console.WriteLine($"hydrated {_entities.Count} entities on landblock 0x{landblockId:X8}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRender(double deltaSeconds)
|
private void OnRender(double deltaSeconds)
|
||||||
{
|
{
|
||||||
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||||
_terrain?.Draw(_camera!);
|
_terrain?.Draw(_camera!);
|
||||||
|
_staticMesh?.Draw(_camera!, _entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClosing()
|
private void OnClosing()
|
||||||
{
|
{
|
||||||
|
_staticMesh?.Dispose();
|
||||||
|
_textureCache?.Dispose();
|
||||||
|
_meshShader?.Dispose();
|
||||||
_terrain?.Dispose();
|
_terrain?.Dispose();
|
||||||
_shader?.Dispose();
|
_shader?.Dispose();
|
||||||
_dats?.Dispose();
|
_dats?.Dispose();
|
||||||
|
|
|
||||||
9
src/AcDream.App/Rendering/Shaders/mesh.frag
Normal file
9
src/AcDream.App/Rendering/Shaders/mesh.frag
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#version 430 core
|
||||||
|
in vec2 vTex;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
uniform sampler2D uDiffuse;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = texture(uDiffuse, vTex);
|
||||||
|
}
|
||||||
15
src/AcDream.App/Rendering/Shaders/mesh.vert
Normal file
15
src/AcDream.App/Rendering/Shaders/mesh.vert
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#version 430 core
|
||||||
|
layout(location = 0) in vec3 aPos;
|
||||||
|
layout(location = 1) in vec3 aNormal;
|
||||||
|
layout(location = 2) in vec2 aTex;
|
||||||
|
|
||||||
|
uniform mat4 uModel;
|
||||||
|
uniform mat4 uView;
|
||||||
|
uniform mat4 uProjection;
|
||||||
|
|
||||||
|
out vec2 vTex;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vTex = aTex;
|
||||||
|
gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0);
|
||||||
|
}
|
||||||
134
src/AcDream.App/Rendering/StaticMeshRenderer.cs
Normal file
134
src/AcDream.App/Rendering/StaticMeshRenderer.cs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
// src/AcDream.App/Rendering/StaticMeshRenderer.cs
|
||||||
|
using System.Numerics;
|
||||||
|
using AcDream.Core.Meshing;
|
||||||
|
using AcDream.Core.Terrain;
|
||||||
|
using AcDream.Core.World;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
|
||||||
|
namespace AcDream.App.Rendering;
|
||||||
|
|
||||||
|
public sealed unsafe class StaticMeshRenderer : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GL _gl;
|
||||||
|
private readonly Shader _shader;
|
||||||
|
private readonly TextureCache _textures;
|
||||||
|
|
||||||
|
// One GPU bundle per unique GfxObj id. Each GfxObj can have multiple sub-meshes.
|
||||||
|
private readonly Dictionary<uint, List<SubMeshGpu>> _gpuByGfxObj = new();
|
||||||
|
|
||||||
|
public StaticMeshRenderer(GL gl, Shader shader, TextureCache textures)
|
||||||
|
{
|
||||||
|
_gl = gl;
|
||||||
|
_shader = shader;
|
||||||
|
_textures = textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureUploaded(uint gfxObjId, IReadOnlyList<GfxObjSubMesh> subMeshes)
|
||||||
|
{
|
||||||
|
if (_gpuByGfxObj.ContainsKey(gfxObjId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var list = new List<SubMeshGpu>(subMeshes.Count);
|
||||||
|
foreach (var sm in subMeshes)
|
||||||
|
list.Add(UploadSubMesh(sm));
|
||||||
|
_gpuByGfxObj[gfxObjId] = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubMeshGpu UploadSubMesh(GfxObjSubMesh sm)
|
||||||
|
{
|
||||||
|
uint vao = _gl.GenVertexArray();
|
||||||
|
_gl.BindVertexArray(vao);
|
||||||
|
|
||||||
|
uint vbo = _gl.GenBuffer();
|
||||||
|
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||||
|
fixed (void* p = sm.Vertices)
|
||||||
|
_gl.BufferData(BufferTargetARB.ArrayBuffer,
|
||||||
|
(nuint)(sm.Vertices.Length * sizeof(Vertex)), p, BufferUsageARB.StaticDraw);
|
||||||
|
|
||||||
|
uint ebo = _gl.GenBuffer();
|
||||||
|
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
||||||
|
fixed (void* p = sm.Indices)
|
||||||
|
_gl.BufferData(BufferTargetARB.ElementArrayBuffer,
|
||||||
|
(nuint)(sm.Indices.Length * sizeof(uint)), p, BufferUsageARB.StaticDraw);
|
||||||
|
|
||||||
|
uint stride = (uint)sizeof(Vertex);
|
||||||
|
_gl.EnableVertexAttribArray(0);
|
||||||
|
_gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||||
|
_gl.EnableVertexAttribArray(1);
|
||||||
|
_gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||||
|
_gl.EnableVertexAttribArray(2);
|
||||||
|
_gl.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float)));
|
||||||
|
|
||||||
|
_gl.BindVertexArray(0);
|
||||||
|
|
||||||
|
return new SubMeshGpu
|
||||||
|
{
|
||||||
|
Vao = vao,
|
||||||
|
Vbo = vbo,
|
||||||
|
Ebo = ebo,
|
||||||
|
IndexCount = sm.Indices.Length,
|
||||||
|
SurfaceId = sm.SurfaceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(OrbitCamera camera, IEnumerable<WorldEntity> entities)
|
||||||
|
{
|
||||||
|
_shader.Use();
|
||||||
|
_shader.SetMatrix4("uView", camera.View);
|
||||||
|
_shader.SetMatrix4("uProjection", camera.Projection);
|
||||||
|
|
||||||
|
foreach (var entity in entities)
|
||||||
|
{
|
||||||
|
if (entity.MeshRefs.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var meshRef in entity.MeshRefs)
|
||||||
|
{
|
||||||
|
if (!_gpuByGfxObj.TryGetValue(meshRef.GfxObjId, out var subMeshes))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// model = entity root transform * per-part transform
|
||||||
|
var entityRoot =
|
||||||
|
Matrix4x4.CreateFromQuaternion(entity.Rotation) *
|
||||||
|
Matrix4x4.CreateTranslation(entity.Position);
|
||||||
|
var model = meshRef.PartTransform * entityRoot;
|
||||||
|
|
||||||
|
_shader.SetMatrix4("uModel", model);
|
||||||
|
|
||||||
|
foreach (var sub in subMeshes)
|
||||||
|
{
|
||||||
|
uint tex = _textures.GetOrUpload(sub.SurfaceId);
|
||||||
|
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||||
|
_gl.BindTexture(TextureTarget.Texture2D, tex);
|
||||||
|
|
||||||
|
_gl.BindVertexArray(sub.Vao);
|
||||||
|
_gl.DrawElements(PrimitiveType.Triangles, (uint)sub.IndexCount, DrawElementsType.UnsignedInt, (void*)0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_gl.BindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var subs in _gpuByGfxObj.Values)
|
||||||
|
{
|
||||||
|
foreach (var sub in subs)
|
||||||
|
{
|
||||||
|
_gl.DeleteBuffer(sub.Vbo);
|
||||||
|
_gl.DeleteBuffer(sub.Ebo);
|
||||||
|
_gl.DeleteVertexArray(sub.Vao);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_gpuByGfxObj.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SubMeshGpu
|
||||||
|
{
|
||||||
|
public uint Vao;
|
||||||
|
public uint Vbo;
|
||||||
|
public uint Ebo;
|
||||||
|
public int IndexCount;
|
||||||
|
public uint SurfaceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue