From e1ab3a14be9c35c89d0ebf9675696a77c1282f0c Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Tue, 16 Jun 2026 19:20:29 +0300 Subject: [PATCH] feat: add depth buffer, camera and MVP push constants - Add Camera ECS component with view/projection matrices. - Add Vulkan depth buffer to Swapchain: image, memory, view, render pass, framebuffers. - Update VulkanPipeline with depth stencil testing and push-constant layout. - Update vertex shader to use push-constant MVP matrix. - MeshRenderer finds Camera, computes MVP per entity and pushes it. - Program.cs creates a camera entity and keeps the cube rotating. Build and run verified: window opens and FPS is reported. --- src/CortexEngine.App/Program.cs | 10 ++ src/Engine.Core/Components/Camera.cs | 46 ++++++ src/Engine.Graphics/MeshRenderer.cs | 45 +++++- src/Engine.Graphics/Shaders/vertex.spv | Bin 980 -> 1236 bytes src/Engine.Graphics/Shaders/vertex.vert | 7 +- src/Engine.Graphics/Swapchain.cs | 202 ++++++++++++++++++++---- src/Engine.Graphics/VulkanPipeline.cs | 25 ++- 7 files changed, 300 insertions(+), 35 deletions(-) create mode 100644 src/Engine.Core/Components/Camera.cs diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs index 120a7b4..7229682 100644 --- a/src/CortexEngine.App/Program.cs +++ b/src/CortexEngine.App/Program.cs @@ -35,6 +35,16 @@ class Program .Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, new Vector3(0.5f))) .Set(mesh); + var camera = world.Entity("Camera") + .Set(new Camera( + new Vector3(0.0f, 0.0f, -2.0f), + Vector3.Zero, + Vector3.UnitY, + MathF.PI / 4.0f, + 1280.0f / 720.0f, + 0.1f, + 100.0f)); + var frames = 0; var lastFpsTime = 0.0; var lastWidth = window.Width; diff --git a/src/Engine.Core/Components/Camera.cs b/src/Engine.Core/Components/Camera.cs new file mode 100644 index 0000000..5571a43 --- /dev/null +++ b/src/Engine.Core/Components/Camera.cs @@ -0,0 +1,46 @@ +using System.Numerics; + +namespace Engine.Core.Components; + +/// +/// A perspective camera component for the ECS. +/// Provides view and projection matrices for the renderer. +/// +public record struct Camera +{ + public Vector3 Position; + public Vector3 Target; + public Vector3 Up; + public float FieldOfView; + public float AspectRatio; + public float NearPlane; + public float FarPlane; + + public Camera( + Vector3 position, + Vector3 target, + Vector3 up, + float fieldOfView = MathF.PI / 4.0f, + float aspectRatio = 16.0f / 9.0f, + float nearPlane = 0.1f, + float farPlane = 100.0f) + { + Position = position; + Target = target; + Up = up; + FieldOfView = fieldOfView; + AspectRatio = aspectRatio; + NearPlane = nearPlane; + FarPlane = farPlane; + } + + public Matrix4x4 GetViewMatrix() + { + return Matrix4x4.CreateLookAt(Position, Target, Up); + } + + public Matrix4x4 GetProjectionMatrix() + { + return Matrix4x4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane); + } +} diff --git a/src/Engine.Graphics/MeshRenderer.cs b/src/Engine.Graphics/MeshRenderer.cs index ee59e2b..c034084 100644 --- a/src/Engine.Graphics/MeshRenderer.cs +++ b/src/Engine.Graphics/MeshRenderer.cs @@ -141,17 +141,26 @@ public sealed unsafe class MeshRenderer : IDisposable }; _context.Vk.BeginCommandBuffer(cmd, &beginInfo); - var clearColor = new ClearValue(new ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f)); + var clearValues = new[] + { + new ClearValue(new ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f)), + new ClearValue { DepthStencil = new ClearDepthStencilValue(1.0f, 0) } + }; + var renderPassInfo = new RenderPassBeginInfo { SType = StructureType.RenderPassBeginInfo, RenderPass = _swapchain.RenderPass, Framebuffer = _swapchain.Framebuffers[imageIndex], RenderArea = new Rect2D(new Offset2D(0, 0), _swapchain.Extent), - ClearValueCount = 1, - PClearValues = &clearColor + ClearValueCount = (uint)clearValues.Length }; + fixed (ClearValue* pClearValues = clearValues) + { + renderPassInfo.PClearValues = pClearValues; + } + _context.Vk.CmdBeginRenderPass(cmd, &renderPassInfo, SubpassContents.Inline); _context.Vk.CmdBindPipeline(cmd, PipelineBindPoint.Graphics, _pipeline.Handle); @@ -160,7 +169,11 @@ public sealed unsafe class MeshRenderer : IDisposable _context.Vk.CmdSetViewport(cmd, 0, 1, &viewport); _context.Vk.CmdSetScissor(cmd, 0, 1, &scissor); + var camera = GetCamera(world); + var view = camera.GetViewMatrix(); + var proj = camera.GetProjectionMatrix(); var drawCmd = cmd; + world.Each((Entity e, ref Mesh mesh, ref Transform transform) => { if (!_buffers.TryGetValue(e, out var buffers)) @@ -172,6 +185,11 @@ public sealed unsafe class MeshRenderer : IDisposable var bytes = BuildMeshVertices(mesh, transform); buffers.VertexBuffer.Update(bytes); + var model = transform.GetMatrix(); + var mvp = Matrix4x4.Multiply(Matrix4x4.Multiply(model, view), proj); + var mvpT = Matrix4x4.Transpose(mvp); + _context.Vk.CmdPushConstants(drawCmd, _pipeline.Layout, ShaderStageFlags.VertexBit, 0, 64, &mvpT); + var vertexBuffer = buffers.VertexBuffer.Buffer; var offset = 0ul; _context.Vk.CmdBindVertexBuffers(drawCmd, 0, 1, &vertexBuffer, &offset); @@ -265,6 +283,27 @@ public sealed unsafe class MeshRenderer : IDisposable return bytes; } + private Camera GetCamera(World world) + { + var camera = new Camera( + new Vector3(0.0f, 0.0f, -2.0f), + Vector3.Zero, + Vector3.UnitY, + MathF.PI / 4.0f, + (float)_swapchain.Extent.Width / _swapchain.Extent.Height, + 0.1f, + 100.0f); + + world.Each((Entity e, ref Camera cam) => + { + camera = cam; + }); + + // Always keep the aspect ratio in sync with the swapchain. + camera.AspectRatio = (float)_swapchain.Extent.Width / _swapchain.Extent.Height; + return camera; + } + public void Dispose() { _context.Vk.DeviceWaitIdle(_context.Device); diff --git a/src/Engine.Graphics/Shaders/vertex.spv b/src/Engine.Graphics/Shaders/vertex.spv index 5a0dd5216a87feae9408543a7713081f84e3cbe9..521947565333f26b62843f7641f942e363673e4d 100644 GIT binary patch literal 1236 zcmZ9KUrQT76vf9R8@0At|7)vhY^pvK#0Ql^QIQDggAXZ!uM*m=9awimb`|l-uh6gL zr}9Pc{AP9%>4cj*_uMo0o;$PEN^9JhF*9W*%(n4s)>MQTVRh#_XXj@-dGUK^Z+{oY zw5d8GoEbCe_BH)q_iRe=PPQuhBHNTz^{dD~>Of>_`i;1edQ2(4N-n$c;36Ir@ts1U z9%oN3%WYw^ROqaG224`L0_T3!w+V{l+i}Yh=n+~Nqj63)<=v}q5BpWzh&E1JjJWj-zLqgx74_B>zmbOz*3gdm zLJe|QQ@pqGVgB!B%)5foXWH?6UHTBu$XF|ixdZB=tD#fe4;Z!Q!2KvQ=HVFrhIX7q4{@uE!+B!f0ZypLJ5cXHZ_B+i z2Mf-V5Z?Vn|7cy5p`ZB=#YFd#44jxgA7p6!sWa$XD>1ntwq9cN4LrUD&Z42K37FxN tjPvlxGwVOq_4E1OaL~u>pJluq`i?XKaZ?twZE4TE{=kIGj%y{c&J zWR3w`{I=p`&Hr4d8|#?_sMO#n05&iJuVYsL^||4^kJ<#B-X -/// Simple graphics pipeline for indexed meshes with vec3 position + vec3 color. +/// Graphics pipeline for indexed meshes with push-constant MVP and depth testing. /// Uses Silk.NET.Vulkan. /// public sealed unsafe class VulkanPipeline : IDisposable @@ -55,11 +55,19 @@ public sealed unsafe class VulkanPipeline : IDisposable private PipelineLayout CreatePipelineLayout() { + var pushConstantRange = new PushConstantRange + { + StageFlags = ShaderStageFlags.VertexBit, + Offset = 0, + Size = (uint)(16 * sizeof(float)) + }; + var createInfo = new PipelineLayoutCreateInfo { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 0, - PushConstantRangeCount = 0 + PushConstantRangeCount = 1, + PPushConstantRanges = &pushConstantRange }; PipelineLayout layout; @@ -170,6 +178,18 @@ public sealed unsafe class VulkanPipeline : IDisposable PAttachments = &colorBlendAttachment }; + var depthStencil = new PipelineDepthStencilStateCreateInfo + { + SType = StructureType.PipelineDepthStencilStateCreateInfo, + DepthTestEnable = true, + DepthWriteEnable = true, + DepthCompareOp = CompareOp.Less, + DepthBoundsTestEnable = false, + StencilTestEnable = false, + Back = new StencilOpState(), + Front = new StencilOpState() + }; + var dynamicStates = new[] { DynamicState.Viewport, DynamicState.Scissor }; PipelineDynamicStateCreateInfo dynamicState; fixed (DynamicState* pDynamic = dynamicStates) @@ -195,6 +215,7 @@ public sealed unsafe class VulkanPipeline : IDisposable PViewportState = &viewportState, PRasterizationState = &rasterizer, PMultisampleState = &multisampling, + PDepthStencilState = &depthStencil, PColorBlendState = &colorBlending, PDynamicState = &dynamicState, Layout = Layout,