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 3e160f5..062cb47 100644 Binary files a/Assets/compiled/Debug.vert.dxil and b/Assets/compiled/Debug.vert.dxil differ diff --git a/Assets/compiled/Skybox.frag.dxil b/Assets/compiled/Skybox.frag.dxil new file mode 100644 index 0000000..32bb087 Binary files /dev/null and b/Assets/compiled/Skybox.frag.dxil differ diff --git a/Assets/compiled/Skybox.vert.dxil b/Assets/compiled/Skybox.vert.dxil new file mode 100644 index 0000000..8702269 Binary files /dev/null and b/Assets/compiled/Skybox.vert.dxil differ diff --git a/Assets/source/Debug.vert.hlsl b/Assets/source/Debug.vert.hlsl index 1513289..2f5e820 100644 --- a/Assets/source/Debug.vert.hlsl +++ b/Assets/source/Debug.vert.hlsl @@ -13,8 +13,8 @@ Output main(uint vertexIndex : SV_VertexID) // Retrieve the vertex buffer using SM6.6 bindless syntax ByteAddressBuffer buffer = ResourceDescriptorHeap[BufferIndex]; - // TextureIndex is used as vertex offset for consolidated buffer (depth-tested at 0, overlay at halfMax) - uint actualVertexIndex = vertexIndex + TextureIndex; + // VertexOffset is used as vertex offset for consolidated buffer (depth-tested at 0, overlay at halfMax) + uint actualVertexIndex = vertexIndex + VertexOffset; // Vertex layout: float3 Position (12 bytes) + float4 Color (16 bytes) = 28 bytes stride uint stride = 28; diff --git a/Assets/source/Skybox.frag.hlsl b/Assets/source/Skybox.frag.hlsl new file mode 100644 index 0000000..e206ee5 --- /dev/null +++ b/Assets/source/Skybox.frag.hlsl @@ -0,0 +1,26 @@ +#include "RootConstants.hlsl" + +float4 main(float3 ViewDir : TEXCOORD0) : SV_Target0 +{ + float3 dir = normalize(ViewDir); + + // Simple Procedural Gradient Skybox Colors + float3 skyZenithColor = float3(0.05f, 0.15f, 0.45f); // Deep blue top + float3 skyHorizonColor = float3(0.4f, 0.6f, 0.9f); // Light blue horizon + float3 groundColor = float3(0.1f, 0.1f, 0.15f); // Dark ground + + float t = dir.z; // -1 to 1 based on vertical alignment + + float3 finalColor = groundColor; + + if (t > 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;