From 4dd5d2516e2f3433b396efbe6968fa67a00e318b Mon Sep 17 00:00:00 2001 From: antopilo Date: Sat, 1 Feb 2025 22:16:50 -0500 Subject: [PATCH] Started porting outline pass --- Data/Shaders/Vulkan/outline.frag | 161 ++++++++++++++++++ Data/Shaders/Vulkan/outline.vert | 112 ++++++++++++ Data/Shaders/Vulkan/triangle.frag | 5 + Data/Shaders/Vulkan/triangle.vert | 1 + .../SceneEditor/Widgets/ViewportWidget.cpp | 9 + .../Vulkan/Pipeline/RenderPipeline.h | 1 + .../Nuake/Rendering/Vulkan/RenderContext.h | 1 + .../Rendering/Vulkan/SceneRenderPipeline.cpp | 57 ++++++- .../Rendering/Vulkan/SceneRenderPipeline.h | 18 +- .../Nuake/Rendering/Vulkan/SceneViewport.h | 4 + .../Vulkan/VulkanImage/VulkanImage.h | 3 + .../Nuake/Rendering/Vulkan/VulkanRenderer.cpp | 2 +- .../Rendering/Vulkan/VulkanSceneRenderer.cpp | 4 +- 13 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 Data/Shaders/Vulkan/outline.frag create mode 100644 Data/Shaders/Vulkan/outline.vert diff --git a/Data/Shaders/Vulkan/outline.frag b/Data/Shaders/Vulkan/outline.frag new file mode 100644 index 00000000..c925a5f7 --- /dev/null +++ b/Data/Shaders/Vulkan/outline.frag @@ -0,0 +1,161 @@ +// Transforms +struct ModelData +{ + float4x4 model; +}; +[[vk::binding(0, 0)]] +StructuredBuffer model : register(t1); + +// Vertex +struct Vertex +{ + float3 position; + float uv_x; + float3 normal; + float uv_y; + float3 tangent; + float3 bitangent; +}; + +[[vk::binding(0, 1)]] +StructuredBuffer vertexBuffer : register(t2); + +// Samplers +[[vk::binding(0, 2)]] +SamplerState mySampler : register(s0); + +// Materials +struct Material +{ + bool hasAlbedo; + float3 albedo; + bool hasNormal; + bool hasMetalness; + bool hasRoughness; + bool hasAO; + float metalnessValue; + float roughnessValue; + float aoValue; + int albedoTextureId; + int normalTextureId; + int metalnessTextureId; + int roughnessTextureId; + int aoTextureId; +}; +[[vk::binding(0, 3)]] +StructuredBuffer material; + +// Textures +[[vk::binding(0, 4)]] +Texture2D textures[]; + +// Lights +struct Light +{ + float3 position; + int type; + float4 color; + float3 direction; + float outerConeAngle; + float innerConeAngle; + bool castShadow; + int shadowMapTextureId[4]; + int transformId[4]; +}; + +[[vk::binding(0, 5)]] +StructuredBuffer lights; + +// Cameras +struct CameraView { + float4x4 View; + float4x4 Projection; + float4x4 ViewProjection; + float4x4 InverseView; + float4x4 InverseProjection; + float3 Position; + float Near; + float Far; +}; +[[vk::binding(0, 6)]] +StructuredBuffer cameras; + +struct PSInput { + float4 Position : SV_Position; + float2 UV : TEXCOORD0; +}; + +struct PSOutput { + float4 oColor0 : SV_TARGET; +}; + +struct OutlinePushConstant +{ + float4 Color; + float Thickness; + int SourceTextureID; + int EntityIDTextureID; + int DepthTextureID; + float SelectedEntity; +}; + +[[vk::push_constant]] +OutlinePushConstant pushConstants; + +PSOutput main(PSInput input) +{ + PSOutput output; + + float4 outlineColor = pushConstants.Color; + float target = pushConstants.SelectedEntity; + float radius = pushConstants.Thickness; + float2 uv = input.UV; + + int entityIDTextureID = pushConstants.EntityIDTextureID; + float hasHit = 0.0f; + + float sampleValue = textures[entityIDTextureID].Sample(mySampler, uv).r; + + if(abs(sampleValue - target) < 0.0001) + { + output.oColor0 = outlineColor; + output.oColor0.a = 1.0; + return output; + } + + float4 fragColor = float4(0, 0, 0, 0); + const float TAU = 6.28318530; + const float steps = 32.0; + for(float i = 0.0f; i < TAU; i += TAU / steps) + { + float2 uvOffset = float2(sin(i), cos(i)) * radius; + + float2 sampleUV = uv + uvOffset; + sampleUV.x = clamp(sampleUV.x, 0.0, 0.999); + sampleUV.y = clamp(sampleUV.y, 0.0, 0.999); + + int sampleValue = (int)textures[entityIDTextureID].Sample(mySampler, sampleUV).r; + if(sampleValue == target) + { + hasHit = 1.0f; + } + + float alpha = smoothstep(0.5, 0.9, int(sampleValue != target) * hasHit * 10.0f); + float4 outputColor = float4( + lerp(fragColor.r, outlineColor.r, alpha), + lerp(fragColor.g, outlineColor.g, alpha), + lerp(fragColor.b, outlineColor.b, alpha), + lerp(fragColor.a, outlineColor.a, alpha) + ); + + fragColor = outputColor; + } + + if(fragColor.a > 0.1) + { + fragColor.a = 1.0f; + } + + output.oColor0 = fragColor; + return output; +} \ No newline at end of file diff --git a/Data/Shaders/Vulkan/outline.vert b/Data/Shaders/Vulkan/outline.vert new file mode 100644 index 00000000..cbd3c87c --- /dev/null +++ b/Data/Shaders/Vulkan/outline.vert @@ -0,0 +1,112 @@ +// Transforms +struct ModelData +{ + float4x4 model; +}; +[[vk::binding(0, 0)]] +StructuredBuffer model : register(t1); + +// Vertex +struct Vertex +{ + float3 position; + float uv_x; + float3 normal; + float uv_y; + float3 tangent; + float3 bitangent; +}; + +[[vk::binding(0, 1)]] +StructuredBuffer vertexBuffer : register(t2); + +// Samplers +[[vk::binding(0, 2)]] +SamplerState mySampler : register(s0); + +// Materials +struct Material +{ + bool hasAlbedo; + float3 albedo; + bool hasNormal; + bool hasMetalness; + bool hasRoughness; + bool hasAO; + float metalnessValue; + float roughnessValue; + float aoValue; + int albedoTextureId; + int normalTextureId; + int metalnessTextureId; + int roughnessTextureId; + int aoTextureId; +}; +[[vk::binding(0, 3)]] +StructuredBuffer material; + +// Textures +[[vk::binding(0, 4)]] +Texture2D textures[]; + +// Lights +struct Light +{ + float3 position; + int type; + float4 color; + float3 direction; + float outerConeAngle; + float innerConeAngle; + bool castShadow; + int shadowMapTextureId[4]; + int transformId[4]; +}; + +[[vk::binding(0, 5)]] +StructuredBuffer lights; + +// Cameras +struct CameraView { + float4x4 View; + float4x4 Projection; + float4x4 ViewProjection; + float4x4 InverseView; + float4x4 InverseProjection; + float3 Position; + float Near; + float Far; +}; +[[vk::binding(0, 6)]] +StructuredBuffer cameras; + +struct OutlinePushConstant +{ + float4 Color; + float Thickness; + int SourceTextureID; + int EntityIDTextureID; + int DepthTextureID; + float SelectedEntity; +}; + +[[vk::push_constant]] +OutlinePushConstant pushConstants; + +// Outputs +struct VSOutput { + float4 Position : SV_Position; + float2 UV : TEXCOORD0; +}; + +// Main vertex shader +VSOutput main(uint vertexIndex : SV_VertexID) +{ + VSOutput output; + + Vertex v = vertexBuffer[vertexIndex]; + output.UV = float2(v.uv_x, v.uv_y); + output.Position = float4(v.position, 1.0f); + + return output; +} \ No newline at end of file diff --git a/Data/Shaders/Vulkan/triangle.frag b/Data/Shaders/Vulkan/triangle.frag index 052cdba0..680beb89 100644 --- a/Data/Shaders/Vulkan/triangle.frag +++ b/Data/Shaders/Vulkan/triangle.frag @@ -92,6 +92,7 @@ struct PSOutput { float4 oColor0 : SV_TARGET; float4 oNormal : SV_TARGET1; float4 oMaterial : SV_TARGET2; + float4 oEntityID : SV_TARGET3; }; struct ModelPushConstant @@ -99,6 +100,7 @@ struct ModelPushConstant int modelIndex; // Push constant data int materialIndex; int cameraID; + float entityID; }; [[vk::push_constant]] @@ -160,5 +162,8 @@ PSOutput main(PSInput input) float3 materialOuput = float3(inMaterial.metalnessValue, inMaterial.aoValue, inMaterial.roughnessValue); output.oMaterial = float4(materialOuput, 1.0f); + + output.oEntityID = float4(pushConstants.entityID, pushConstants.entityID, pushConstants.entityID, pushConstants.entityID); + return output; } \ No newline at end of file diff --git a/Data/Shaders/Vulkan/triangle.vert b/Data/Shaders/Vulkan/triangle.vert index db43be06..432ba036 100644 --- a/Data/Shaders/Vulkan/triangle.vert +++ b/Data/Shaders/Vulkan/triangle.vert @@ -85,6 +85,7 @@ struct ModelPushConstant int modelIndex; // Push constant data int materialIndex; int cameraID; + float entityID; }; [[vk::push_constant]] diff --git a/Editor/Source/Editor/Windows/SceneEditor/Widgets/ViewportWidget.cpp b/Editor/Source/Editor/Windows/SceneEditor/Widgets/ViewportWidget.cpp index a9794b3d..59e00278 100644 --- a/Editor/Source/Editor/Windows/SceneEditor/Widgets/ViewportWidget.cpp +++ b/Editor/Source/Editor/Windows/SceneEditor/Widgets/ViewportWidget.cpp @@ -35,6 +35,15 @@ void ViewportWidget::Update(float ts) const Vector2 viewportSize = sceneViewport->GetViewportSize(); editorCam->OnWindowResize(viewportSize.x, viewportSize.y); + + if (editorContext.GetSelection().Type == EditorSelectionType::Entity) + { + sceneViewport->SetSelectedEntityID(editorContext.GetSelection().Entity.GetHandle()); + } + else + { + sceneViewport->SetSelectedEntityID(-1); + } } void ViewportWidget::Draw() diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/Pipeline/RenderPipeline.h b/Nuake/Source/Nuake/Rendering/Vulkan/Pipeline/RenderPipeline.h index bb7a3b72..600a18cc 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/Pipeline/RenderPipeline.h +++ b/Nuake/Source/Nuake/Rendering/Vulkan/Pipeline/RenderPipeline.h @@ -22,6 +22,7 @@ namespace Nuake Vector2 resolution; RenderPass* renderPass = nullptr; UUID cameraID; + float selectedEntity; }; class TextureAttachment diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/RenderContext.h b/Nuake/Source/Nuake/Rendering/Vulkan/RenderContext.h index 4c4059d5..9a13a6c9 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/RenderContext.h +++ b/Nuake/Source/Nuake/Rendering/Vulkan/RenderContext.h @@ -17,6 +17,7 @@ namespace Nuake Vector2 Size; UUID CameraID; Ref ViewportImage; + int SelectedEntityID = -1; // We might add more to this! }; } \ No newline at end of file diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.cpp b/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.cpp index e3fce0f7..9a045769 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.cpp +++ b/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.cpp @@ -104,6 +104,12 @@ SceneRenderPipeline::SceneRenderPipeline() TonemappedOutput = CreateRef(ImageFormat::RGBA8, defaultSize); TonemappedOutput->SetDebugName("TonemappedOutput"); + GBufferEntityID = CreateRef(ImageFormat::R32F, defaultSize); + GBufferEntityID->SetDebugName("GBufferEntityID"); + + OutlineOutput =CreateRef(ImageFormat::RGBA8, defaultSize); + OutlineOutput->SetDebugName("OutlineOutput"); + // Setup bloom targets BloomOutput = CreateRef(ImageFormat::RGBA16F, defaultSize); BloomThreshold = CreateRef(ImageFormat::RGBA16F, defaultSize); @@ -129,6 +135,7 @@ SceneRenderPipeline::SceneRenderPipeline() gBufferPass.AddAttachment("Albedo", GBufferAlbedo->GetFormat()); gBufferPass.AddAttachment("Normal", GBufferNormal->GetFormat()); gBufferPass.AddAttachment("Material", GBufferMaterial->GetFormat()); + gBufferPass.AddAttachment("EntityID", GBufferEntityID->GetFormat()); gBufferPass.AddAttachment("Depth", GBufferDepth->GetFormat(), ImageUsage::Depth); gBufferPass.SetPushConstant(gbufferConstant); gBufferPass.SetPreRender([&](PassRenderContext& ctx) { @@ -161,6 +168,10 @@ SceneRenderPipeline::SceneRenderPipeline() } gbufferConstant.Index = res.ModelMatrixMapping[Entity((entt::entity)e, scene.get()).GetID()]; + + // Set entity ID + Entity entity = { (entt::entity)e, scene.get() }; + gbufferConstant.EntityID = static_cast(entity.GetHandle()); for (auto& m : mesh.ModelResource.Get()->GetMeshes()) { @@ -262,6 +273,45 @@ SceneRenderPipeline::SceneRenderPipeline() cmd.DrawIndexed(6); }); + auto& outlinePass = GBufferPipeline.AddPass("Outline"); + outlinePass.SetShaders(shaderMgr.GetShader("outline_vert"), shaderMgr.GetShader("outline_frag")); + outlinePass.SetPushConstant(outlineConstant); + outlinePass.AddAttachment("OutlineOutput", ImageFormat::RGBA8); + outlinePass.SetDepthTest(false); + outlinePass.AddInput("ShadingOutput"); + outlinePass.AddInput("EntityID"); + outlinePass.SetPreRender([&](PassRenderContext& ctx) { + Cmd& cmd = ctx.commandBuffer; + auto& layout = ctx.renderPass->PipelineLayout; + auto& res = GPUResources::Get(); + + // Bindless + cmd.BindDescriptorSet(layout, res.ModelDescriptor, 0); + cmd.BindDescriptorSet(layout, res.SamplerDescriptor, 2); + cmd.BindDescriptorSet(layout, res.MaterialDescriptor, 3); + cmd.BindDescriptorSet(layout, res.TexturesDescriptor, 4); + cmd.BindDescriptorSet(layout, res.LightsDescriptor, 5); + cmd.BindDescriptorSet(layout, res.CamerasDescriptor, 6); + }); + outlinePass.SetRender([&](PassRenderContext& ctx) + { + outlineConstant.SourceTextureID = GPUResources::Get().GetBindlessTextureID(ShadingOutput->GetID()); + outlineConstant.EntityIDTextureID = GPUResources::Get().GetBindlessTextureID(GBufferEntityID->GetID()); + outlineConstant.DepthTextureID = GPUResources::Get().GetBindlessTextureID(GBufferDepth->GetID()); + outlineConstant.SelectedEntityID = ctx.selectedEntity; + outlineConstant.Color = Vector4(1, 0, 0, 1); + outlineConstant.Thickness = 4.0f; + + auto& cmd = ctx.commandBuffer; + cmd.PushConstants(ctx.renderPass->PipelineLayout, sizeof(OutlineConstant), &outlineConstant); + + // Draw full screen quad + auto& quadMesh = VkSceneRenderer::QuadMesh; + cmd.BindDescriptorSet(ctx.renderPass->PipelineLayout, quadMesh->GetDescriptorSet(), 1); + cmd.BindIndexBuffer(quadMesh->GetIndexBuffer()->GetBuffer()); + cmd.DrawIndexed(6); + }); + GBufferPipeline.Build(); } @@ -277,14 +327,19 @@ void SceneRenderPipeline::Render(PassRenderContext& ctx) GBufferDepth = ResizeImage(ctx, GBufferDepth, ctx.resolution); GBufferNormal = ResizeImage(ctx, GBufferNormal, ctx.resolution); GBufferMaterial = ResizeImage(ctx, GBufferMaterial, ctx.resolution); + GBufferEntityID = ResizeImage(ctx, GBufferEntityID, ctx.resolution); + ShadingOutput = ResizeImage(ctx, ShadingOutput, ctx.resolution); TonemappedOutput = ResizeImage(ctx, TonemappedOutput, ctx.resolution); + OutlineOutput = ResizeImage(ctx, OutlineOutput, ctx.resolution); + PipelineAttachments pipelineInputs { - { GBufferAlbedo, GBufferDepth, GBufferNormal, GBufferMaterial }, // GBuffer + { GBufferAlbedo, GBufferDepth, GBufferNormal, GBufferMaterial, GBufferEntityID }, // GBuffer { ShadingOutput }, // Shading { TonemappedOutput }, // Tonemap + { OutlineOutput } }; GBufferPipeline.Execute(ctx, pipelineInputs); diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.h b/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.h index b1ee3a82..88aa3083 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.h +++ b/Nuake/Source/Nuake/Rendering/Vulkan/SceneRenderPipeline.h @@ -14,7 +14,7 @@ namespace Nuake int Index; int MaterialIndex; int CameraID; - char padding[120]; // 124 bytes to reach 128 bytes + float EntityID; }; class ShadowRenderPipeline @@ -50,6 +50,16 @@ namespace Nuake int SourceTextureID; }; + struct OutlineConstant + { + Vector4 Color; + float Thickness; + int SourceTextureID; + int EntityIDTextureID; + int DepthTextureID; + float SelectedEntityID; + }; + struct BloomConstant { int Stage; @@ -75,12 +85,15 @@ namespace Nuake Ref GBufferDepth; Ref GBufferNormal; Ref GBufferMaterial; + Ref GBufferEntityID; // Attachments Shading Ref ShadingOutput; Ref TonemappedOutput; + Ref OutlineOutput; + // Bloom const int BloomIteration = 4; const float DownsampleScale = 0.4f; @@ -95,6 +108,7 @@ namespace Nuake GBufferConstant gbufferConstant; ShadingConstant shadingConstant; TonemapConstant tonemapConstant; + OutlineConstant outlineConstant; BloomConstant bloomConstant; static RenderPipeline GBufferPipeline; @@ -108,7 +122,7 @@ namespace Nuake void SetCamera(UUID camera); void Render(PassRenderContext& ctx); - Ref GetOutput() { return TonemappedOutput; } + Ref GetOutput() { return OutlineOutput; } MulticastDelegate& OnDebugDraw() { return DebugDrawDelegate; } diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/SceneViewport.h b/Nuake/Source/Nuake/Rendering/Vulkan/SceneViewport.h index c0c7869e..4a05d2b5 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/SceneViewport.h +++ b/Nuake/Source/Nuake/Rendering/Vulkan/SceneViewport.h @@ -18,6 +18,7 @@ namespace Nuake UUID viewId; Ref renderTarget; + int selectedEntityID; public: Viewport(UUID viewId, const Vector2& size); ~Viewport() = default; @@ -39,6 +40,9 @@ namespace Nuake return queuedResize != viewportSize; } + void SetSelectedEntityID(int id) { selectedEntityID = id; } + int GetSelectedEntityID() const { return selectedEntityID; } + Ref GetRenderTarget() const { return renderTarget; } bool Resize(); }; diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/VulkanImage/VulkanImage.h b/Nuake/Source/Nuake/Rendering/Vulkan/VulkanImage/VulkanImage.h index 5cb31efd..541e8ab7 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/VulkanImage/VulkanImage.h +++ b/Nuake/Source/Nuake/Rendering/Vulkan/VulkanImage/VulkanImage.h @@ -19,6 +19,9 @@ namespace Nuake RGBA16F = 97, RGBA32F = 109, D32F = 126, + R32UINT = 98, + R32INT = 99, + R32F = 100, }; enum class ImageUsage diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/VulkanRenderer.cpp b/Nuake/Source/Nuake/Rendering/Vulkan/VulkanRenderer.cpp index 50acac11..576f29a3 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/VulkanRenderer.cpp +++ b/Nuake/Source/Nuake/Rendering/Vulkan/VulkanRenderer.cpp @@ -371,7 +371,7 @@ void VkRenderer::DrawScenes() ctx.CameraID = viewport->GetViewID(); ctx.Size = viewport->GetRenderTarget()->GetSize(); ctx.ViewportImage = viewport->GetRenderTarget(); - + ctx.SelectedEntityID = viewport->GetSelectedEntityID(); SceneRenderer->DrawSceneView(ctx); } } diff --git a/Nuake/Source/Nuake/Rendering/Vulkan/VulkanSceneRenderer.cpp b/Nuake/Source/Nuake/Rendering/Vulkan/VulkanSceneRenderer.cpp index 371ba3b1..da9e7156 100644 --- a/Nuake/Source/Nuake/Rendering/Vulkan/VulkanSceneRenderer.cpp +++ b/Nuake/Source/Nuake/Rendering/Vulkan/VulkanSceneRenderer.cpp @@ -63,6 +63,8 @@ void VkSceneRenderer::LoadShaders() shaderMgr.AddShader("shadow_vert", shaderCompiler.CompileShader("Resources/Shaders/Vulkan/shadow.vert")); shaderMgr.AddShader("tonemap_frag", shaderCompiler.CompileShader("Resources/Shaders/Vulkan/tonemap.frag")); shaderMgr.AddShader("tonemap_vert", shaderCompiler.CompileShader("Resources/Shaders/Vulkan/tonemap.vert")); + shaderMgr.AddShader("outline_frag", shaderCompiler.CompileShader("Resources/Shaders/Vulkan/outline.frag")); + shaderMgr.AddShader("outline_vert", shaderCompiler.CompileShader("Resources/Shaders/Vulkan/outline.vert")); } void VkSceneRenderer::PrepareScenes(const std::vector>& scenes, RenderContext inContext) @@ -313,7 +315,7 @@ void VkSceneRenderer::DrawSceneView(RenderContext inContext) passCtx.resolution = inContext.Size; passCtx.commandBuffer = inContext.CommandBuffer; passCtx.scene = inContext.CurrentScene; - + passCtx.selectedEntity = static_cast(inContext.SelectedEntityID); sceneRenderPipeline->Render(passCtx); // in case we just resized