From 932c45d8447305629221e68afd505abc800333f4 Mon Sep 17 00:00:00 2001 From: Patedam Date: Sun, 22 Feb 2026 17:16:21 -0500 Subject: [PATCH] New skyboxrenderer + moved meshrenderer inside engine and out from main to clean that up --- AgentData/lighting_and_skybox_plan.md | 59 +++++++++++++ Assets/compiled/Debug.vert.dxil | Bin 4964 -> 4964 bytes Assets/compiled/Skybox.frag.dxil | Bin 0 -> 3232 bytes Assets/compiled/Skybox.vert.dxil | Bin 0 -> 5304 bytes Assets/source/Debug.vert.hlsl | 4 +- Assets/source/Skybox.frag.hlsl | 26 ++++++ Assets/source/Skybox.vert.hlsl | 74 ++++++++++++++++ Juliet/include/Core/Math/Matrix.h | 42 ++++++++++ Juliet/include/Engine/Engine.h | 5 +- Juliet/include/Graphics/SkyboxRenderer.h | 29 +++++++ Juliet/src/Engine/Engine.cpp | 17 +++- Juliet/src/Graphics/SkyboxRenderer.cpp | 102 +++++++++++++++++++++++ JulietApp/main.cpp | 47 ++++++++++- JulietApp/main.h | 1 + 14 files changed, 399 insertions(+), 7 deletions(-) create mode 100644 Assets/compiled/Skybox.frag.dxil create mode 100644 Assets/compiled/Skybox.vert.dxil create mode 100644 Assets/source/Skybox.frag.hlsl create mode 100644 Assets/source/Skybox.vert.hlsl create mode 100644 Juliet/include/Graphics/SkyboxRenderer.h create mode 100644 Juliet/src/Graphics/SkyboxRenderer.cpp diff --git a/AgentData/lighting_and_skybox_plan.md b/AgentData/lighting_and_skybox_plan.md index 1d99438..26bbb85 100644 --- a/AgentData/lighting_and_skybox_plan.md +++ b/AgentData/lighting_and_skybox_plan.md @@ -231,3 +231,62 @@ graph LR 1. **Cubemap source** — **Procedural gradient skybox** chosen because asset loading infrastructure is not yet established. 2. **Sampler heap** — Evaluate what exists. However, with a procedural skybox, we won't need a sampler for Phase 3! (The sky color can be procedurally generated from the reconstructed view direction). 3. **Specular** — **Blinn-Phong** specular, structured in an agnostic way so PBR (physically based rendering) parameters can be plugged in later. + +--- + +## Phase 4: Forward Rendered Entity Lights + +A simple, fast forward renderer using a Global Structured Buffer for entity-based point lights. + +### Approach: Bindless Global Lights Buffer + +Instead of passing each light via PushConstants, we pass a `StructuredBuffer` index and iterate over the active lights in the fragment shader. + +### New C++ Components + +```cpp +struct PointLight +{ + Vector3 Position; + float Radius; + Vector3 Color; + float Intensity; +}; +``` + +1. **Lights Buffer Allocation**: Allocate a `StructuredBuffer` large enough for all potential lights (e.g., max 1024). +2. **Buffer Upload**: Loop through the active game entity lights every frame and upload them using the TransferBuffer system. +3. **PushData Update**: + - `uint32 LightsBufferIndex` + - `uint32 ActiveLightCount` + +### Shader Design + +**Fragment Shader Expansion** loop over all `ActiveLightCount` to compute the attenuation and diffuse, then accumulate. + +```hlsl +StructuredBuffer lights = ResourceDescriptorHeap[LightsBufferIndex]; + +float3 totalDiffuse = Color.rgb * LightColor * NdotL; // Include Directional Sun + +for (uint i = 0; i < ActiveLightCount; ++i) +{ + PointLight light = lights[i]; + + float3 lightVector = light.Position - WorldPosition; + float distance = length(lightVector); + + if (distance > light.Radius) continue; + + float3 L = lightVector / distance; + float NdotL = saturate(dot(N, L)); + + float attenuation = 1.0 - saturate(distance / light.Radius); + attenuation *= attenuation; // inverse square-ish + + totalDiffuse += Color.rgb * light.Color * light.Intensity * NdotL * attenuation; +} +``` + +> [!NOTE] +> Passing `WorldPosition` to the Fragment shader from the Vertex shader is required for positional lighting. diff --git a/Assets/compiled/Debug.vert.dxil b/Assets/compiled/Debug.vert.dxil index 3e160f5d86badc2e8ba469350bd626319cb306de..062cb479cb2ce770e1e3c3b49c0f29401add96b9 100644 GIT binary patch delta 97 zcmV-n0G|KkCgdg*L|8&YXoK}%T}d{t6DLi!Iu9g+u@utow`L5Zb(yu756OJM3`zUVD_+38i3F$GF0fGIwUF_w3o1%ISN)CzpYK zZ*CwL3`xKLV5XtsaDElkb^86Z6-t_Od?} z94%pkI_2I5`;2AwD6(0D?iB+7w+Be=)7;WAhJP-=y1=Odid#=Q^e>NVW>pmIAAt=w zhuE+*Un;K=IT_Y%h0W(ymqYXu?NPhGwOdOv;UEHt3c#Gu&AtXU4^dW;s#k0x_n|H` z9y=sH4=gsq87Fwu$IOuy0A(LD{&Cz zUZvWrl(QHYFou5dSeTK)YFVsm44oq}<4Pn;zzQpobI{d=!I+Y10@hiruxwGET|0~b zG^L>A+MePyBn1#|wvfaRqnNc0?J=0vvM7HT!<~`XP^U-|q;=SRBcB^W)L{`iJRIcf zh}pf*HZnb%G~j3#?$=G;J^nmCSS|yg2EJz&=4}*c9U6Xc>IwYid-pT&O+&LoI}1?s zqlh6C>CrSrN6+x)V&t15$$TvyB~O|{+U1h>*s`iA>^t7z3EUV4Xcx!%k5tV65G^? z&4T2%fo*WmN9N=kg5>i9_@ErCD-+a4*^DNeWL!-wO`{%)n4d-$N#dcKm~jMtsSEsy zwloa$o|$5}f%*+0oWfy8IR!uvl=0K}sV`IhGp)UNaO#4Oc3?L7#D|G)Dn2mo>&qo) zn7Et|{<-wACl%S1v22m{DL6}}Ak_)uc_eP)eiV?>l=?K$iVCjv9{AP$-?i0sZu(jF z#SZHk4m%&MVre}b{BbPxJcgylrwmKE^?Waurv2Yo>PRxPe%$MMs_=Amzo7WRcxxo?=eDEv??HGG4mDdSB{2PFhYE}Y+f5uzmN+S0$Vwzf6wG$p!* zxY&4PbOlQlN~$$Lm0z6Ii5=}O>gpW6MdoOG|M)#3O0=Sa#(v>kW2*C`rT?!>=68{x?h;O*(uVra3Eb=F}Tj zE-~S#FEF#LyCUPfhmr-1D5nPolMuxQfnYxgIXbZ1Azv zHHDkiO9c|XoR+to2sl<}FZ3vO(^>x*K0N3K42e7A7>k>q{)@=Ghi}PG{x!Of*2ORl zay%2)u|mN*fk3p)ccyXc6YdG> M2{#*%=)IQx507!=?EnA( literal 0 HcmV?d00001 diff --git a/Assets/compiled/Skybox.vert.dxil b/Assets/compiled/Skybox.vert.dxil new file mode 100644 index 0000000000000000000000000000000000000000..8702269b4077d551ead4361fb2a1a4c9b48bcd88 GIT binary patch literal 5304 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

IeD*XU#zN4>4 zLqYN~Njy5`4V&6J%_~DeywugkBpJ`Gqt3ZeUl1vqABymq|adO++E^*^wbchAjg zgp)So;0RDmwAqd4SnN5?PH=uMp

vvC|1+DRJBj^IzzTA76&QP(_{4KufNRHCh0q zORjrgoM299)BtRA*e5hv_J|i-a#BwsC$TwTC=FT;xLT=jgQy7D3$sSNkhzsgH;8bLURG2TB-7W& z)3O9_$AJF^$?yXOr#Jjfn@#@K>Nd4fdjv_JQ1SuztsseFzW~5_LDEN7iKCV3L6g`@ z!TNY?7_7aON;8NY%)6SfG`_-}EdQ_L=}# z*|ikt0dUYqAMyI4!2*Q^K{C8Po3DMEZ7&m6)G&FimL; z6xQwLiHkrSDFM~4K%p_M(Dprcb=nhln#u?l-q!~(61XwYHHX^q2krb{wLOvV4HQ;K Z3Y17JxNIQtn>;2r 0.0f) { + // Blend from horizon to zenith + finalColor = lerp(skyHorizonColor, skyZenithColor, t); + } else { + // Ground - quick blend from horizon to ground + finalColor = lerp(skyHorizonColor, groundColor, saturate(-t * 2.0f)); + } + + // Combine with overall ambient lighting scale + return float4(finalColor, 1.0f); +} diff --git a/Assets/source/Skybox.vert.hlsl b/Assets/source/Skybox.vert.hlsl new file mode 100644 index 0000000..0e63ef1 --- /dev/null +++ b/Assets/source/Skybox.vert.hlsl @@ -0,0 +1,74 @@ +#include "RootConstants.hlsl" + +struct Output +{ + float3 ViewDir : TEXCOORD0; + float4 Position : SV_Position; +}; + +float4x4 Inverse(float4x4 m) +{ + float n11 = m[0][0], n12 = m[1][0], n13 = m[2][0], n14 = m[3][0]; + float n21 = m[0][1], n22 = m[1][1], n23 = m[2][1], n24 = m[3][1]; + float n31 = m[0][2], n32 = m[1][2], n33 = m[2][2], n34 = m[3][2]; + float n41 = m[0][3], n42 = m[1][3], n43 = m[2][3], n44 = m[3][3]; + + float t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44; + float t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44; + float t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44; + float t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; + + float det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; + float idet = 1.0f / det; + + float4x4 ret; + ret[0][0] = t11 * idet; + ret[0][1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * idet; + ret[0][2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * idet; + ret[0][3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * idet; + + ret[1][0] = t12 * idet; + ret[1][1] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * idet; + ret[1][2] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * idet; + ret[1][3] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * idet; + + ret[2][0] = t13 * idet; + ret[2][1] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * idet; + ret[2][2] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * idet; + ret[2][3] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * idet; + + ret[3][0] = t14 * idet; + ret[3][1] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * idet; + ret[3][2] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * idet; + ret[3][3] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * idet; + + return ret; +} + +Output main(uint vertexID : SV_VertexID) +{ + Output output; + + // Fullscreen triangle properties + // vertexID 0 -> uv(0,0) -> pos(-1, 1) + // vertexID 1 -> uv(2,0) -> pos( 3, 1) + // vertexID 2 -> uv(0,2) -> pos(-1, -3) + float2 uv = float2((vertexID << 1) & 2, vertexID & 2); + + // Map uv to clip space position. + // Z is set to 1.0 (far plane in reverse-Z or standard depth) + // Assuming standard Z projection ranges from 0 to 1, we use 1.0 for the far plane. + // Depending on reverse-z, this might need to be 0.0, but let's stick to standard 1.0 + // depth for skyboxes. + float4 clipPos = float4(uv * 2.0 - 1.0, 1.0, 1.0); + clipPos.y = -clipPos.y; // Flip Y for D3D + + output.Position = clipPos; + + // Reconstruct view direction from clip space + float4x4 inverseVP = Inverse(ViewProjection); + float4 worldPos = mul(inverseVP, clipPos); + output.ViewDir = worldPos.xyz / worldPos.w; + + return output; +} diff --git a/Juliet/include/Core/Math/Matrix.h b/Juliet/include/Core/Math/Matrix.h index 2e599f5..38fc07f 100644 --- a/Juliet/include/Core/Math/Matrix.h +++ b/Juliet/include/Core/Math/Matrix.h @@ -133,4 +133,46 @@ namespace Juliet result.m[3][3] = 0.0f; return result; } + + [[nodiscard]] inline Matrix MatrixInverse(const Matrix& m) + { + Matrix out = {}; + + float m00 = m.m[0][0], m01 = m.m[0][1], m02 = m.m[0][2], m03 = m.m[0][3]; + float m10 = m.m[1][0], m11 = m.m[1][1], m12 = m.m[1][2], m13 = m.m[1][3]; + float m20 = m.m[2][0], m21 = m.m[2][1], m22 = m.m[2][2], m23 = m.m[2][3]; + float m30 = m.m[3][0], m31 = m.m[3][1], m32 = m.m[3][2], m33 = m.m[3][3]; + + out.m[0][0] = m11 * m22 * m33 - m11 * m23 * m32 - m21 * m12 * m33 + m21 * m13 * m32 + m31 * m12 * m23 - m31 * m13 * m22; + out.m[1][0] = -m10 * m22 * m33 + m10 * m23 * m32 + m20 * m12 * m33 - m20 * m13 * m32 - m30 * m12 * m23 + m30 * m13 * m22; + out.m[2][0] = m10 * m21 * m33 - m10 * m23 * m31 - m20 * m11 * m33 + m20 * m13 * m31 + m30 * m11 * m23 - m30 * m13 * m21; + out.m[3][0] = -m10 * m21 * m32 + m10 * m22 * m31 + m20 * m11 * m32 - m20 * m12 * m31 - m30 * m11 * m22 + m30 * m12 * m21; + + out.m[0][1] = -m01 * m22 * m33 + m01 * m23 * m32 + m21 * m02 * m33 - m21 * m03 * m32 - m31 * m02 * m23 + m31 * m03 * m22; + out.m[1][1] = m00 * m22 * m33 - m00 * m23 * m32 - m20 * m02 * m33 + m20 * m03 * m32 + m30 * m02 * m23 - m30 * m03 * m22; + out.m[2][1] = -m00 * m21 * m33 + m00 * m23 * m31 + m20 * m01 * m33 - m20 * m03 * m31 - m30 * m01 * m23 + m30 * m03 * m21; + out.m[3][1] = m00 * m21 * m32 - m00 * m22 * m31 - m20 * m01 * m32 + m20 * m02 * m31 + m30 * m01 * m22 - m30 * m02 * m21; + + out.m[0][2] = m01 * m12 * m33 - m01 * m13 * m32 - m11 * m02 * m33 + m11 * m03 * m32 + m31 * m02 * m13 - m31 * m03 * m12; + out.m[1][2] = -m00 * m12 * m33 + m00 * m13 * m32 + m10 * m02 * m33 - m10 * m03 * m32 - m30 * m02 * m13 + m30 * m03 * m12; + out.m[2][2] = m00 * m11 * m33 - m00 * m13 * m31 - m10 * m01 * m33 + m10 * m03 * m31 + m30 * m01 * m13 - m30 * m03 * m11; + out.m[3][2] = -m00 * m11 * m32 + m00 * m12 * m31 + m10 * m01 * m32 - m10 * m02 * m31 - m30 * m01 * m12 + m30 * m02 * m11; + + out.m[0][3] = -m01 * m12 * m23 + m01 * m13 * m22 + m11 * m02 * m23 - m11 * m03 * m22 - m21 * m02 * m13 + m21 * m03 * m12; + out.m[1][3] = m00 * m12 * m23 - m00 * m13 * m22 - m10 * m02 * m23 + m10 * m03 * m22 + m20 * m02 * m13 - m20 * m03 * m12; + out.m[2][3] = -m00 * m11 * m23 + m00 * m13 * m21 + m10 * m01 * m23 - m10 * m03 * m21 - m20 * m01 * m13 + m20 * m03 * m11; + out.m[3][3] = m00 * m11 * m22 - m00 * m12 * m21 - m10 * m01 * m22 + m10 * m02 * m21 + m20 * m01 * m12 - m20 * m02 * m11; + + float det = m00 * out.m[0][0] + m01 * out.m[1][0] + m02 * out.m[2][0] + m03 * out.m[3][0]; + + if (det != 0.0f) + { + float invDet = 1.0f / det; + for (int r = 0; r < 4; ++r) + for (int c = 0; c < 4; ++c) + out.m[r][c] *= invDet; + } + + return out; + } } // namespace Juliet diff --git a/Juliet/include/Engine/Engine.h b/Juliet/include/Engine/Engine.h index d66e6a0..ea7dfb0 100644 --- a/Juliet/include/Engine/Engine.h +++ b/Juliet/include/Engine/Engine.h @@ -8,13 +8,14 @@ namespace Juliet struct Engine { - IApplication* Application = nullptr; + IApplication* Application = nullptr; + Arena* PlatformArena = nullptr; }; void InitializeEngine(JulietInit_Flags flags); void ShutdownEngine(); - void LoadApplication(IApplication& app); + void LoadApplication(IApplication& app, Arena* arena); void UnloadApplication(); void RunEngine(); diff --git a/Juliet/include/Graphics/SkyboxRenderer.h b/Juliet/include/Graphics/SkyboxRenderer.h new file mode 100644 index 0000000..f1a5eb6 --- /dev/null +++ b/Juliet/include/Graphics/SkyboxRenderer.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace Juliet +{ + struct RenderPass; + struct CommandList; + struct Window; + struct GraphicsPipeline; + struct GraphicsDevice; + + struct SkyboxRenderer + { + GraphicsDevice* Device; + GraphicsPipeline* Pipeline; + }; + + [[nodiscard]] JULIET_API bool InitializeSkyboxRenderer(NonNullPtr device, + NonNullPtr window); + JULIET_API void ShutdownSkyboxRenderer(); + JULIET_API void RenderSkybox(NonNullPtr pass, NonNullPtr cmdList, const Matrix& viewProjection); + +#if ALLOW_SHADER_HOT_RELOAD + JULIET_API void ReloadSkyboxShaders(); +#endif + +} // namespace Juliet diff --git a/Juliet/src/Engine/Engine.cpp b/Juliet/src/Engine/Engine.cpp index bc6cb1f..60b0476 100644 --- a/Juliet/src/Engine/Engine.cpp +++ b/Juliet/src/Engine/Engine.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #ifdef JULIET_ENABLE_IMGUI @@ -37,6 +39,11 @@ namespace Juliet if (device) { DebugDisplay_Initialize(device); + if (Window* window = EngineInstance.Application->GetPlatformWindow()) + { + InitializeMeshRenderer(EngineInstance.PlatformArena, device, window); + InitializeSkyboxRenderer(device, window); + } } #ifdef JULIET_ENABLE_IMGUI @@ -73,6 +80,8 @@ namespace Juliet if (device) { DebugDisplay_Shutdown(device); + ShutdownSkyboxRenderer(); + ShutdownMeshRenderer(); } } @@ -121,6 +130,9 @@ namespace Juliet // Debug display flush (inside render pass) Camera debugCamera = EngineInstance.Application->GetDebugCamera(); DebugDisplay_Flush(cmdList, pass, debugCamera); + + // Note: The MeshRenderer and SkyboxRenderer draw calls are still inside Application->OnRender + // They shouldn't be moved here directly without an interface since they require PushData. #ifdef JULIET_ENABLE_IMGUI // ImGui rendering (always last before EndRenderPass) @@ -155,9 +167,10 @@ namespace Juliet ShutdownLogManager(); } - void LoadApplication(IApplication& app) + void LoadApplication(IApplication& app, Arena* platformArena) { - EngineInstance.Application = &app; + EngineInstance.Application = &app; + EngineInstance.PlatformArena = platformArena; EngineInstance.Application->Init(); // Systems depending on Window/GraphicsDevice diff --git a/Juliet/src/Graphics/SkyboxRenderer.cpp b/Juliet/src/Graphics/SkyboxRenderer.cpp new file mode 100644 index 0000000..70b1f5c --- /dev/null +++ b/Juliet/src/Graphics/SkyboxRenderer.cpp @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include +#include + +namespace Juliet +{ + namespace + { + SkyboxRenderer g_SkyboxRenderer; + } // namespace + + bool InitializeSkyboxRenderer(NonNullPtr device, NonNullPtr window) + { + bool result = true; + + GraphicsDevice* graphicsDevice = g_SkyboxRenderer.Device = device.Get(); + + String skyboxVSEntry = WrapString("main"); + ShaderCreateInfo skyboxVSCI = {}; + skyboxVSCI.EntryPoint = skyboxVSEntry; + skyboxVSCI.Stage = ShaderStage::Vertex; + String vsPath = GetAssetPath(WrapString("Skybox.vert.dxil")); + Shader* skyboxVS = CreateShader(graphicsDevice, vsPath, skyboxVSCI); + + String skyboxFSEntry = WrapString("main"); + ShaderCreateInfo skyboxFSCI = {}; + skyboxFSCI.EntryPoint = skyboxFSEntry; + skyboxFSCI.Stage = ShaderStage::Fragment; + String fsPath = GetAssetPath(WrapString("Skybox.frag.dxil")); + Shader* skyboxFS = CreateShader(graphicsDevice, fsPath, skyboxFSCI); + + ColorTargetDescription colorTargetDesc = {}; + colorTargetDesc.Format = GetSwapChainTextureFormat(graphicsDevice, window); + + GraphicsPipelineCreateInfo skyboxPipelineCI = {}; + skyboxPipelineCI.VertexShader = skyboxVS; + skyboxPipelineCI.FragmentShader = skyboxFS; + skyboxPipelineCI.PrimitiveType = PrimitiveType::TriangleList; + skyboxPipelineCI.TargetInfo.ColorTargetDescriptions = &colorTargetDesc; + skyboxPipelineCI.TargetInfo.NumColorTargets = 1; + skyboxPipelineCI.TargetInfo.DepthStencilFormat = TextureFormat::D32_FLOAT; + skyboxPipelineCI.TargetInfo.HasDepthStencilTarget = true; + skyboxPipelineCI.RasterizerState.FillMode = FillMode::Solid; + skyboxPipelineCI.RasterizerState.CullMode = CullMode::None; + skyboxPipelineCI.RasterizerState.FrontFace = FrontFace::Clockwise; + skyboxPipelineCI.DepthStencilState.EnableDepthTest = true; + skyboxPipelineCI.DepthStencilState.EnableDepthWrite = false; + skyboxPipelineCI.DepthStencilState.CompareOperation = CompareOperation::LessOrEqual; + + g_SkyboxRenderer.Pipeline = CreateGraphicsPipeline(graphicsDevice, skyboxPipelineCI); + if (g_SkyboxRenderer.Pipeline == nullptr) + { + LogError(LogCategory::Graphics, "Failed to create skybox pipeline!"); + result = false; + } + + if (skyboxVS) DestroyShader(graphicsDevice, skyboxVS); + if (skyboxFS) DestroyShader(graphicsDevice, skyboxFS); + + return result; + } + + void ShutdownSkyboxRenderer() + { + if (g_SkyboxRenderer.Pipeline) + { + DestroyGraphicsPipeline(g_SkyboxRenderer.Device, g_SkyboxRenderer.Pipeline); + g_SkyboxRenderer.Pipeline = nullptr; + } + + g_SkyboxRenderer = {}; + } + + void RenderSkybox(NonNullPtr pass, NonNullPtr cmdList, const Matrix& viewProjection) + { + if (!g_SkyboxRenderer.Pipeline) + { + return; + } + + PushData pushData = {}; + pushData.ViewProjection = viewProjection; + pushData.Model = MatrixIdentity(); + + BindGraphicsPipeline(pass, g_SkyboxRenderer.Pipeline); + SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData); + SetPushConstants(cmdList, ShaderStage::Fragment, 0, sizeof(pushData) / 4, &pushData); + DrawPrimitives(pass, 3, 1, 0, 0); + } + +#if ALLOW_SHADER_HOT_RELOAD + void ReloadSkyboxShaders() + { + // TODO + } +#endif + +} // namespace Juliet diff --git a/JulietApp/main.cpp b/JulietApp/main.cpp index 325ea2c..dfe7df0 100644 --- a/JulietApp/main.cpp +++ b/JulietApp/main.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #ifdef JULIET_ENABLE_IMGUI #include @@ -135,6 +135,50 @@ void JulietApplication::Init() { return; } + + // Create Skybox Pipeline + String skyboxVSEntry = WrapString("main"); + ShaderCreateInfo skyboxVSCI = {}; + skyboxVSCI.EntryPoint = skyboxVSEntry; + skyboxVSCI.Stage = ShaderStage::Vertex; + String vsPath = GetAssetPath(WrapString("Skybox.vert.dxil")); + Shader* skyboxVS = CreateShader(GraphicsDevice, vsPath, skyboxVSCI); + + String skyboxFSEntry = WrapString("main"); + ShaderCreateInfo skyboxFSCI = {}; + skyboxFSCI.EntryPoint = skyboxFSEntry; + skyboxFSCI.Stage = ShaderStage::Fragment; + String fsPath = GetAssetPath(WrapString("Skybox.frag.dxil")); + Shader* skyboxFS = CreateShader(GraphicsDevice, fsPath, skyboxFSCI); + + ColorTargetDescription colorTargetDesc = {}; + colorTargetDesc.Format = GetSwapChainTextureFormat(GraphicsDevice, MainWindow); + + GraphicsPipelineCreateInfo skyboxPipelineCI = {}; + skyboxPipelineCI.VertexShader = skyboxVS; + skyboxPipelineCI.FragmentShader = skyboxFS; + skyboxPipelineCI.PrimitiveType = PrimitiveType::TriangleList; + skyboxPipelineCI.TargetInfo.ColorTargetDescriptions = &colorTargetDesc; + skyboxPipelineCI.TargetInfo.NumColorTargets = 1; + skyboxPipelineCI.TargetInfo.DepthStencilFormat = TextureFormat::D32_FLOAT; + skyboxPipelineCI.TargetInfo.HasDepthStencilTarget = true; + skyboxPipelineCI.RasterizerState.FillMode = FillMode::Solid; + skyboxPipelineCI.RasterizerState.CullMode = CullMode::None; + skyboxPipelineCI.RasterizerState.FrontFace = FrontFace::Clockwise; + skyboxPipelineCI.DepthStencilState.EnableDepthTest = true; + skyboxPipelineCI.DepthStencilState.EnableDepthWrite = false; + skyboxPipelineCI.DepthStencilState.CompareOperation = CompareOperation::LessOrEqual; + + SkyboxPipeline = CreateGraphicsPipeline(GraphicsDevice, skyboxPipelineCI); + if (SkyboxPipeline == nullptr) + { + LogError(LogCategory::Graphics, "Failed to create skybox pipeline!"); + Running = false; + } + + if (skyboxVS) DestroyShader(GraphicsDevice, skyboxVS); + if (skyboxFS) DestroyShader(GraphicsDevice, skyboxFS); + } GameCode.Functions = reinterpret_cast(&Game); @@ -324,6 +368,7 @@ void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd) pushData.LightColor = { 1.0f, 0.95f, 0.8f }; pushData.AmbientIntensity = 0.2f; + RenderSkybox(pass, cmd, pushData.ViewProjection); RenderMeshes(pass, cmd, pushData); } diff --git a/JulietApp/main.h b/JulietApp/main.h index e78a28a..6ca4c52 100644 --- a/JulietApp/main.h +++ b/JulietApp/main.h @@ -43,6 +43,7 @@ class JulietApplication : public Juliet::IApplication Juliet::HotReloadCode GameCode = {}; Juliet::GraphicsPipeline* GraphicsPipeline = {}; Juliet::Texture* DepthBuffer = {}; + Juliet::GraphicsPipeline* SkyboxPipeline = {}; Juliet::Arena* GameArena = nullptr; Juliet::Arena* GameScratchArena = nullptr;