Parcourir la source

Added `RPI/Subpass` ASV Sample. (#684)

This new Sample demonstrates how to use Vulkan subpasses
with RPI Pass assets.

This example demonstrates how to use Vulkan Subpasses with
the RPI Pass assets.
The are two Render Pipelines that are identical in terms of expected output,
but they work diffrently to achive the same outcome.

The first (default) pipeline is made of Two Subpasses, Forward followed by SkyBox.
`AtomSampleViewer/Passes/SubpassExample/TwoSubpasses/TwoSubpassesPipeline.pass`.

The second pipeline is made of Two Passes, Forward followed by SkyBox.
`AtomSampleViewer/Passes/SubpassExample/TwoPasses/TwoPassesPipeline.pass`.

The user can switch between those two pipelines by using the Keys '1' or '2'.

Signed-off-by: galibzon <[email protected]>
galibzon il y a 1 an
Parent
commit
2adce15dcc

+ 17 - 2
Gem/Code/Source/RHI/SubpassExampleComponent.cpp

@@ -222,20 +222,25 @@ namespace AtomSampleViewer
         uint32_t subpassIndex = 0;
         // Build the render attachment layout with the 2 subpasses.
         RHI::RenderAttachmentLayoutBuilder attachmentsBuilder;
+
         // GBuffer Subpass
         attachmentsBuilder.AddSubpass()
             ->RenderTargetAttachment(RHI::Format::R16G16B16A16_FLOAT, m_positionAttachmentId)
             ->RenderTargetAttachment(RHI::Format::R16G16B16A16_FLOAT, m_normalAttachmentId)
             ->RenderTargetAttachment(RHI::Format::R8G8B8A8_UNORM, m_albedoAttachmentId)
             ->RenderTargetAttachment(m_outputFormat, m_outputAttachmentId)
-            ->DepthStencilAttachment(AZ::RHI::Format::D32_FLOAT, m_depthStencilAttachmentId);
+            ->DepthStencilAttachment(AZ::RHI::Format::D32_FLOAT, m_depthStencilAttachmentId, AZ::RHI::AttachmentLoadStoreAction(),
+                AZ::RHI::ScopeAttachmentAccess::Write,
+                AZ::RHI::ScopeAttachmentStage::EarlyFragmentTest | AZ::RHI::ScopeAttachmentStage::LateFragmentTest);
         // Composition Subpass
         attachmentsBuilder.AddSubpass()
             ->SubpassInputAttachment(m_positionAttachmentId, RHI::ImageAspectFlags::Color)
             ->SubpassInputAttachment(m_normalAttachmentId, RHI::ImageAspectFlags::Color)
             ->SubpassInputAttachment(m_albedoAttachmentId, RHI::ImageAspectFlags::Color)
             ->RenderTargetAttachment(m_outputAttachmentId)
-            ->DepthStencilAttachment(m_depthStencilAttachmentId);
+            ->DepthStencilAttachment(m_depthStencilAttachmentId, AZ::RHI::AttachmentLoadStoreAction(),
+                AZ::RHI::ScopeAttachmentAccess::Read,
+                AZ::RHI::ScopeAttachmentStage::EarlyFragmentTest | AZ::RHI::ScopeAttachmentStage::LateFragmentTest);
 
         RHI::RenderAttachmentLayout renderAttachmentLayout;
         [[maybe_unused]] RHI::ResultCode result = attachmentsBuilder.End(renderAttachmentLayout);
@@ -341,6 +346,7 @@ namespace AtomSampleViewer
                 descriptor.m_attachmentId = m_positionAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
                 descriptor.m_loadStoreAction.m_clearValue = RHI::ClearValue::CreateVector4Float(0.f, 0.f, 0.f, 0.f);
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseColorAttachment(descriptor);
             }
 
@@ -350,6 +356,7 @@ namespace AtomSampleViewer
                 descriptor.m_attachmentId = m_normalAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
                 descriptor.m_loadStoreAction.m_clearValue = RHI::ClearValue::CreateVector4Float(0.f, 0.f, 0.f, 0.f);
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseColorAttachment(descriptor);
             }
 
@@ -359,6 +366,7 @@ namespace AtomSampleViewer
                 descriptor.m_attachmentId = m_albedoAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
                 descriptor.m_loadStoreAction.m_clearValue = RHI::ClearValue::CreateVector4Float(0.f, 0.f, 0.f, 0.f);
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseColorAttachment(descriptor);
             }
 
@@ -367,6 +375,7 @@ namespace AtomSampleViewer
                 RHI::ImageScopeAttachmentDescriptor descriptor;
                 descriptor.m_attachmentId = m_outputAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Load;
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseColorAttachment(descriptor);
             }
 
@@ -376,6 +385,7 @@ namespace AtomSampleViewer
                 dsDesc.m_attachmentId = m_depthStencilAttachmentId;
                 dsDesc.m_loadStoreAction.m_clearValue = RHI::ClearValue::CreateDepthStencil(0, 0);
                 dsDesc.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
+                dsDesc.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Depth;
                 frameGraph.UseDepthStencilAttachment(
                     dsDesc, RHI::ScopeAttachmentAccess::Write,
                     AZ::RHI::ScopeAttachmentStage::EarlyFragmentTest | AZ::RHI::ScopeAttachmentStage::LateFragmentTest);
@@ -456,6 +466,7 @@ namespace AtomSampleViewer
                 RHI::ImageScopeAttachmentDescriptor descriptor;
                 descriptor.m_attachmentId = m_positionAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Load;
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseSubpassInputAttachment(descriptor, RHI::ScopeAttachmentStage::FragmentShader);
             }
 
@@ -464,6 +475,7 @@ namespace AtomSampleViewer
                 RHI::ImageScopeAttachmentDescriptor descriptor;
                 descriptor.m_attachmentId = m_normalAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Load;
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseSubpassInputAttachment(descriptor, RHI::ScopeAttachmentStage::FragmentShader);
             }
 
@@ -472,6 +484,7 @@ namespace AtomSampleViewer
                 RHI::ImageScopeAttachmentDescriptor descriptor;
                 descriptor.m_attachmentId = m_albedoAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Load;
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseSubpassInputAttachment(descriptor, RHI::ScopeAttachmentStage::FragmentShader);
             }
 
@@ -480,6 +493,7 @@ namespace AtomSampleViewer
                 RHI::ImageScopeAttachmentDescriptor descriptor;
                 descriptor.m_attachmentId = m_outputAttachmentId;
                 descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Load;
+                descriptor.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Color;
                 frameGraph.UseColorAttachment(descriptor);
             }
 
@@ -488,6 +502,7 @@ namespace AtomSampleViewer
                 RHI::ImageScopeAttachmentDescriptor dsDesc;
                 dsDesc.m_attachmentId = m_depthStencilAttachmentId;
                 dsDesc.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Load;
+                dsDesc.m_imageViewDescriptor.m_aspectFlags = RHI::ImageAspectFlags::Depth;
                 frameGraph.UseDepthStencilAttachment(
                     dsDesc, RHI::ScopeAttachmentAccess::Read,
                     RHI::ScopeAttachmentStage::EarlyFragmentTest | RHI::ScopeAttachmentStage::LateFragmentTest);

+ 3 - 1
Gem/Code/Source/SampleComponentManager.cpp

@@ -113,6 +113,7 @@
 #include <XRRPIExampleComponent.h>
 #include <ShaderReloadTestComponent.h>
 #include <ReadbackExampleComponent.h>
+#include <Subpass_RPI_ExampleComponent.h>
 
 #include <Atom/Bootstrap/DefaultWindowBus.h>
 
@@ -288,7 +289,7 @@ namespace AtomSampleViewer
             NewRHISample<RayTracingExampleComponent>("RayTracing", []() {return Utils::GetRHIDevice()->GetFeatures().m_rayTracing; }),
             NewRHISample<SphericalHarmonicsExampleComponent>("SphericalHarmonics"),
             NewRHISample<StencilExampleComponent>("Stencil"),
-            NewRHISample<SubpassExampleComponent>("Subpass", []() {return Utils::GetRHIDevice()->GetFeatures().m_renderTargetSubpassInputSupport != AZ::RHI::SubpassInputSupportType::NotSupported; }),
+            NewRHISample<SubpassExampleComponent>("Subpass", []() { return RHI::RHISystemInterface::Get()->CanMergeSubpasses(); }),
             NewRHISample<SwapchainExampleComponent>("Swapchain"),
             NewRHISample<TextureExampleComponent>("Texture"),
             NewRHISample<Texture3dExampleComponent>("Texture3d"),
@@ -319,6 +320,7 @@ namespace AtomSampleViewer
             NewRPISample<SceneReloadSoakTestComponent>("SceneReloadSoakTest"),
             NewRPISample<StreamingImageExampleComponent>("StreamingImage"),
             NewRPISample<ShaderReloadTestComponent>("ShaderReloadTest"),
+            NewRPISample<Subpass_RPI_ExampleComponent>("Subpass", []() { return RHI::RHISystemInterface::Get()->CanMergeSubpasses(); }),
             NewFeaturesSample<AreaLightExampleComponent>("AreaLight"),
             NewFeaturesSample<BloomExampleComponent>("Bloom"),
             NewFeaturesSample<CheckerboardExampleComponent>("Checkerboard", []() {return (Utils::GetRHIDevice()->GetPhysicalDevice().GetDescriptor().m_vendorId != RHI::VendorId::ARM && Utils::GetRHIDevice()->GetPhysicalDevice().GetDescriptor().m_vendorId != RHI::VendorId::Qualcomm); }),

+ 327 - 0
Gem/Code/Source/Subpass_RPI_ExampleComponent.cpp

@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Subpass_RPI_ExampleComponent.h>
+
+#include <Atom/Component/DebugCamera/ArcBallControllerComponent.h>
+#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
+
+#include <Atom/RHI/Device.h>
+#include <Atom/RHI/Factory.h>
+
+#include <Atom/RPI.Public/View.h>
+
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+
+#include <AzCore/Asset/AssetManagerBus.h>
+#include <AzCore/Component/Entity.h>
+#include <AzCore/IO/IOUtils.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+#include <AzCore/std/sort.h>
+
+#include <AzFramework/Components/TransformComponent.h>
+#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
+#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
+
+#include <SampleComponentManager.h>
+#include <SampleComponentConfig.h>
+#include <EntityUtilityFunctions.h>
+
+#include <Automation/ScriptableImGui.h>
+#include <Automation/ScriptRunnerBus.h>
+
+#include <RHI/BasicRHIComponent.h>
+
+namespace AtomSampleViewer
+{
+    const char* Subpass_RPI_ExampleComponent::CameraControllerNameTable[CameraControllerCount] =
+    {
+        "ArcBall",
+        "NoClip"
+    };
+
+    void Subpass_RPI_ExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<Subpass_RPI_ExampleComponent, AZ::Component>()
+                ->Version(0)
+                ;
+        }
+    }
+
+    Subpass_RPI_ExampleComponent::Subpass_RPI_ExampleComponent()
+    {
+    }
+
+    void Subpass_RPI_ExampleComponent::DefaultWindowCreated()
+    {
+        AZ::Render::Bootstrap::DefaultWindowBus::BroadcastResult(m_windowContext, &AZ::Render::Bootstrap::DefaultWindowBus::Events::GetDefaultWindowContext);
+    }
+
+    void Subpass_RPI_ExampleComponent::ChangeActivePipeline(AvailablePipelines pipelineOption)
+    {
+        if ((pipelineOption == m_activePipelineOption) && (m_activePipeline != nullptr))
+        {
+            return;
+        }
+
+        // remove currently running sample pipeline, if any
+        bool removeOriginalPipeline = true;
+        if (m_activePipeline)
+        {
+            m_scene->RemoveRenderPipeline(m_activePipeline->GetId());
+            m_activePipeline = nullptr;
+            removeOriginalPipeline = false;
+        }
+
+        // Create the pipeline.
+        const auto pipelineOptionIdx = static_cast<size_t>(pipelineOption);
+        AZ::RPI::RenderPipelineDescriptor pipelineDesc;
+        pipelineDesc.m_mainViewTagName = "MainCamera";
+        pipelineDesc.m_materialPipelineTag = "MultiViewPipeline";
+        pipelineDesc.m_name = PipelineOptions[pipelineOptionIdx].m_pipelineName;
+        pipelineDesc.m_rootPassTemplate = PipelineOptions[pipelineOptionIdx].m_rootPassTemplate;
+        pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 1;
+
+        m_activePipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext);
+        AZ_Assert(m_activePipeline != nullptr, "Failed to create render pipeline with name='%s' and template='%s'.",
+            PipelineOptions[pipelineOptionIdx].m_pipelineName, PipelineOptions[pipelineOptionIdx].m_rootPassTemplate);
+
+        // Activate the pipeline
+        m_activePipeline->GetRootPass()->SetEnabled(true); // PassSystem::RemoveRenderPipeline was calling SetEnabled(false)
+        m_scene->AddRenderPipeline(m_activePipeline);
+        m_activePipeline->SetDefaultView(m_originalPipeline->GetDefaultView());
+        if (removeOriginalPipeline)
+        {
+            m_scene->RemoveRenderPipeline(m_originalPipeline->GetId());
+        }
+
+        m_activePipelineOption = pipelineOption;
+        AZ_TracePrintf(LogName, "New active pipeline is '%s' from template '%s'",
+            PipelineOptions[pipelineOptionIdx].m_pipelineName,  PipelineOptions[pipelineOptionIdx].m_rootPassTemplate);
+    }
+
+    void Subpass_RPI_ExampleComponent::RestoreOriginalPipeline()
+    {
+        if (m_activePipeline)
+        {
+            m_scene->RemoveRenderPipeline(m_activePipeline->GetId());
+            m_activePipeline = nullptr;
+        }
+
+        m_originalPipeline->GetRootPass()->SetEnabled(true); // PassSystem::RemoveRenderPipeline was calling SetEnabled(false)
+        m_scene->AddRenderPipeline(m_originalPipeline);
+    }
+
+
+    void Subpass_RPI_ExampleComponent::Activate()
+    {
+        UseArcBallCameraController();
+
+        InitLightingPresets(true);
+
+        ActivateGroundPlane();
+        ActivateModel();
+
+        // Need to connect to this EBus before creating the pipeline because
+        // we need the window context.
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusConnect();
+        AzFramework::InputChannelEventListener::BusConnect();
+
+        // Save a pointer to the original default pipeline.
+        m_originalPipeline = m_scene->GetDefaultRenderPipeline();
+
+        // Start with our default pipeline 
+        ChangeActivePipeline(m_activePipelineOption);
+    }
+
+    void Subpass_RPI_ExampleComponent::ActivateGroundPlane()
+    {
+        // Load the material asset first.
+        const auto traceLevel = AZ::RPI::AssetUtils::TraceLevel::Assert;
+        auto groundPlaneMaterialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(DefaultPbrMaterialPath, traceLevel);
+        AZ_Assert(groundPlaneMaterialAsset.GetId().IsValid(), "Invalid material asset from path='%s'", DefaultPbrMaterialPath);
+
+        m_groundPlaneModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(DefaultGroundPlaneModelPath, traceLevel);
+        AZ_Assert(m_groundPlaneModelAsset.GetId().IsValid(), "Invalid ground plane model asset from path='%s'", DefaultGroundPlaneModelPath);
+        auto meshHandleDescriptor = AZ::Render::MeshHandleDescriptor(m_groundPlaneModelAsset, AZ::RPI::Material::FindOrCreate(groundPlaneMaterialAsset));
+        m_groundPlandMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(meshHandleDescriptor);
+
+        GetMeshFeatureProcessor()->SetTransform(m_groundPlandMeshHandle, AZ::Transform::CreateIdentity());
+    }
+
+    void Subpass_RPI_ExampleComponent::ActivateModel()
+    {
+        // Load the material asset first.
+        const auto traceLevel = AZ::RPI::AssetUtils::TraceLevel::Assert;
+        auto materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>(DefaultMaterialPath, traceLevel);
+        AZ_Assert(materialAsset.GetId().IsValid(), "Invalid material asset from path='%s'", DefaultMaterialPath);
+        m_modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(DefaultModelPath, traceLevel);
+        AZ_Assert(m_modelAsset.GetId().IsValid(), "Invalid model asset from path='%s'", DefaultModelPath);
+
+        ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScript);
+
+        GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
+
+        AZ::Render::MeshHandleDescriptor descriptor(m_modelAsset, AZ::RPI::Material::FindOrCreate(materialAsset));
+        descriptor.m_modelChangedEventHandler = AZ::Render::MeshHandleDescriptor::ModelChangedEvent::Handler{
+            [this](const AZ::Data::Instance<AZ::RPI::Model>& /*model*/)
+            {
+                ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript);
+
+                // This handler will be connected to the feature processor so that when the model is updated, the camera
+                // controller will reset. This ensures the camera is a reasonable distance from the model when it resizes.
+                ResetCameraController();
+
+                UpdateGroundPlane();
+            }
+        };
+
+        m_meshHandle = GetMeshFeatureProcessor()->AcquireMesh(descriptor);
+        GetMeshFeatureProcessor()->SetTransform(m_meshHandle, AZ::Transform::CreateIdentity());
+    }
+
+    void Subpass_RPI_ExampleComponent::Deactivate()
+    {
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusDisconnect();
+        AzFramework::InputChannelEventListener::BusDisconnect();
+
+        GetMeshFeatureProcessor()->ReleaseMesh(m_groundPlandMeshHandle);
+        m_groundPlandMeshHandle = {};
+        GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
+        m_modelAsset = {};
+
+        RestoreOriginalPipeline();
+
+        RemoveController();
+
+        ShutdownLightingPresets();
+    }
+
+    void Subpass_RPI_ExampleComponent::UpdateGroundPlane()
+    {
+        if (m_groundPlandMeshHandle.IsValid())
+        {
+            AZ::Transform groundPlaneTransform = AZ::Transform::CreateIdentity();
+
+            if (m_modelAsset)
+            {
+                AZ::Vector3 modelCenter;
+                float modelRadius;
+                m_modelAsset->GetAabb().GetAsSphere(modelCenter, modelRadius);
+
+                static const float GroundPlaneRelativeScale = 4.0f;
+                static const float GroundPlaneOffset = 0.01f;
+
+                groundPlaneTransform.SetUniformScale(GroundPlaneRelativeScale * modelRadius);
+                groundPlaneTransform.SetTranslation(AZ::Vector3(0.0f, 0.0f, m_modelAsset->GetAabb().GetMin().GetZ() - GroundPlaneOffset));
+            }
+
+            GetMeshFeatureProcessor()->SetTransform(m_groundPlandMeshHandle, groundPlaneTransform);
+        }
+    }
+
+    void Subpass_RPI_ExampleComponent::OnEntityDestruction(const AZ::EntityId& entityId)
+    {
+        AZ::EntityBus::MultiHandler::BusDisconnect(entityId);
+    }
+
+    // AzFramework::InputChannelEventListener
+    bool Subpass_RPI_ExampleComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
+    {
+        const auto& inputDevice = inputChannel.GetInputDevice();
+        if (!AzFramework::InputDeviceKeyboard::IsKeyboardDevice(inputDevice.GetInputDeviceId()))
+        {
+            return false;
+        }
+
+        const AzFramework::InputChannelId& inputChannelId = inputChannel.GetInputChannelId();
+        switch (inputChannel.GetState())
+        {
+        case AzFramework::InputChannel::State::Ended:
+            {
+                if (inputChannelId == AzFramework::InputDeviceKeyboard::Key::Alphanumeric1)
+                {
+                    ChangeActivePipeline(AvailablePipelines::TwoSubpassesPipeline);
+                }
+                else if (inputChannelId == AzFramework::InputDeviceKeyboard::Key::Alphanumeric2)
+                {
+                    ChangeActivePipeline(AvailablePipelines::TwoPassesPipeline);
+                }
+                else
+                {
+                    AZ_TracePrintf(LogName, "Invalid Key=%s. Only '1' or '2' are supported\n", inputChannelId.GetName());
+                }
+                break;
+            }
+        default:
+            {
+                break;
+            }
+        }
+
+        return false;
+    }
+
+    void Subpass_RPI_ExampleComponent::UseArcBallCameraController()
+    {
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
+            azrtti_typeid<AZ::Debug::ArcBallControllerComponent>());
+    }
+
+    void Subpass_RPI_ExampleComponent::UseNoClipCameraController()
+    {
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
+            azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
+    }
+
+    void Subpass_RPI_ExampleComponent::RemoveController()
+    {
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
+    }
+
+    void Subpass_RPI_ExampleComponent::SetArcBallControllerParams()
+    {
+        if (!m_modelAsset.IsReady())
+        {
+            return;
+        }
+
+        // Adjust the arc-ball controller so that it has bounds that make sense for the current model
+        
+        AZ::Vector3 center;
+        float radius;
+        m_modelAsset->GetAabb().GetAsSphere(center, radius);
+
+        const float startingDistance = radius * ArcballRadiusDefaultModifier;
+        const float minDistance = radius * ArcballRadiusMinModifier;
+        const float maxDistance = radius * ArcballRadiusMaxModifier;
+
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetCenter, center);
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetDistance, startingDistance);
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetMinDistance, minDistance);
+        AZ::Debug::ArcBallControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::ArcBallControllerRequestBus::Events::SetMaxDistance, maxDistance);
+    }
+    void Subpass_RPI_ExampleComponent::ResetCameraController()
+    {
+        RemoveController();
+        if (m_currentCameraControllerType == CameraControllerType::ArcBall)
+        {
+            UseArcBallCameraController();
+            SetArcBallControllerParams();
+        }
+        else if (m_currentCameraControllerType == CameraControllerType::NoClip)
+        {
+            UseNoClipCameraController();
+        }
+    }
+} // namespace AtomSampleViewer

+ 139 - 0
Gem/Code/Source/Subpass_RPI_ExampleComponent.h

@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/EntityBus.h>
+
+#include <AzFramework/Input/Events/InputChannelEventListener.h>
+
+#include <Atom/Bootstrap/DefaultWindowBus.h>
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+#include <Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h>
+
+#include <Utils/Utils.h>
+#include <Utils/ImGuiSidebar.h>
+
+#include <CommonSampleComponentBase.h>
+
+namespace AtomSampleViewer
+{
+    //! This example demonstrates how to use Subpasses at the RPI level.
+    //! The are two Render Pipelines that are identical in terms of expected output,
+    //! but they work diffrently to achive the same outcome.
+    //! The first (default) pipeline is made of Two Subpasses, Forward followed by SkyBox.
+    //! The second pipeline is made of Two Passes, Forward followed by SkyBox.
+    //! The user can switch between those two pipelines by using the Keys '1' or '2'.
+    class Subpass_RPI_ExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler
+        , public AzFramework::InputChannelEventListener
+    {
+    public:
+        AZ_COMPONENT(Subpass_RPI_ExampleComponent, "{6AD413AE-161D-4A8B-99B3-58E02277F2F0}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static constexpr char LogName[] = "Subpass_RPI_Example";
+
+        Subpass_RPI_ExampleComponent();
+        ~Subpass_RPI_ExampleComponent() override = default;
+
+        //! AZ::Component
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        //! AZ::EntityBus::MultiHandler
+        void OnEntityDestruction(const AZ::EntityId& entityId) override;
+
+        //! AzFramework::InputChannelEventListener
+        //! In this example we use keyboard events for the digit keys to allow
+        //! the user to change the active pipeline.
+        bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
+
+        //! Must be called before ActivateModel();
+        void ActivateGroundPlane();
+        void ActivateModel();
+
+        void UpdateGroundPlane();
+
+        void UseArcBallCameraController();
+        void UseNoClipCameraController();
+        void RemoveController();
+
+        void SetArcBallControllerParams();
+        void ResetCameraController();
+
+        void DefaultWindowCreated() override;
+
+        enum class AvailablePipelines : size_t
+        {
+            TwoSubpassesPipeline,
+            TwoPassesPipeline,
+            Count
+        };
+
+        void ChangeActivePipeline(AvailablePipelines pipelineOption);
+        void RestoreOriginalPipeline();
+
+        static constexpr char DefaultMaterialPath[] = "objects/hermanubis/hermanubis_stone.azmaterial";
+        static constexpr char DefaultModelPath[] = "materialeditor/viewportmodels/hermanubis.fbx.azmodel";
+        static constexpr char DefaultGroundPlaneModelPath[] = "objects/plane.fbx.azmodel";
+
+        struct PipelineOption
+        {
+            char m_pipelineName[256];
+            char m_rootPassTemplate[256];
+        };
+
+        const AZStd::array<PipelineOption, 2> PipelineOptions = {{
+            { "TwoSubpassesPipeline", "TwoSubpassesPipelineTemplate" },
+            { "TwoPassesPipeline", "TwoPassesPipelineTemplate" }
+        }};
+
+        AvailablePipelines m_activePipelineOption = AvailablePipelines::TwoSubpassesPipeline;
+
+        //! Original render pipeline when the sample was started.
+        //! This sample only goes back to this pipeline when it is closed/deactivated.
+        AZ::RPI::RenderPipelinePtr m_originalPipeline = nullptr;
+
+        //! The active pipeline from this sample. It can be one of:
+        //! 1- TwoSubpassesPipeline (default): One Vulkan Render Pass that contains two Subpasses.
+        //!    Manually activated by the user when pressing the '1' keyboard key.
+        //! 2- TwoPassesPipeline: Two Vulkan Render Passes.
+        //!    Manually activated by the user when pressing the '2' keyboard key.
+        AZ::RPI::RenderPipelinePtr m_activePipeline = nullptr;
+
+        AZStd::shared_ptr<AZ::RPI::WindowContext> m_windowContext;
+        AZ::Render::ImGuiActiveContextScope m_imguiScope;
+
+        enum class CameraControllerType : int32_t
+        {
+            ArcBall = 0,
+            NoClip,
+            Count
+        };
+        static const uint32_t CameraControllerCount = static_cast<uint32_t>(CameraControllerType::Count);
+        static const char* CameraControllerNameTable[CameraControllerCount];
+        CameraControllerType m_currentCameraControllerType = CameraControllerType::ArcBall;
+
+        static constexpr float ArcballRadiusMinModifier = 0.01f;
+        static constexpr float ArcballRadiusMaxModifier = 4.0f;
+        static constexpr float ArcballRadiusDefaultModifier = 2.0f;
+
+        bool m_cameraControllerDisabled = false;
+
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
+
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_groundPlandMeshHandle;
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_groundPlaneModelAsset;
+
+    };
+} // namespace AtomSampleViewer

+ 1 - 1
Gem/Code/Source/Utils/Utils.cpp

@@ -44,7 +44,7 @@ namespace AtomSampleViewer
             using namespace AZ;
 
             auto* rhiSystem = RHI::RHISystemInterface::Get();
-            AZ_Assert(rhiSystem, "Failed to retrieve rpi system.");
+            AZ_Assert(rhiSystem, "Failed to retrieve rhi system.");
 
             return rhiSystem->GetDevice();
         }

+ 2 - 0
Gem/Code/atomsampleviewergem_private_files.cmake

@@ -190,6 +190,8 @@ set(FILES
     Source/TransparencyExampleComponent.h
     Source/ShaderReloadTestComponent.cpp
     Source/ShaderReloadTestComponent.h
+    Source/Subpass_RPI_ExampleComponent.cpp
+    Source/Subpass_RPI_ExampleComponent.h
     Source/Utils/ImGuiAssetBrowser.cpp
     Source/Utils/ImGuiAssetBrowser.h
     Source/Utils/ImGuiHistogramQueue.cpp

+ 20 - 0
Passes/ASV/PassTemplates.azasset

@@ -119,6 +119,26 @@
             {
                 "Name": "DeferredLightingPassTemplate",
                 "Path": "Passes/PrototypeDeferredPipeline/DeferredLighting.pass"
+            },
+            {
+                "Name": "ForwardPassExampleTemplate",
+                "Path": "Passes/SubpassExample/TwoPasses/ForwardPass.pass"
+            },
+            {
+                "Name": "TwoPassesPipelineTemplate",
+                "Path": "Passes/SubpassExample/TwoPasses/TwoPassesPipeline.pass"
+            },
+            {
+                "Name": "ForwardSubpassExampleTemplate",
+                "Path": "Passes/SubpassExample/TwoSubpasses/ForwardSubpass.pass"
+            },
+            {
+                "Name": "SkyBoxSubpassExampleTemplate",
+                "Path": "Passes/SubpassExample/TwoSubpasses/SkyBoxSubpass.pass"
+            },
+            {
+                "Name": "TwoSubpassesPipelineTemplate",
+                "Path": "Passes/SubpassExample/TwoSubpasses/TwoSubpassesPipeline.pass"
             }
         ]
     }

+ 105 - 0
Passes/SubpassExample/TwoPasses/ForwardPass.pass

@@ -0,0 +1,105 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "ForwardPassExampleTemplate",
+            "PassClass": "RasterPass",
+            "Slots": [
+                // Inputs...
+                {
+                    "Name": "BRDFTextureInput",
+                    "ShaderInputName": "m_brdfMap",
+                    "SlotType": "Input",
+                    "ScopeAttachmentUsage": "Shader"
+                },
+                // Outputs...
+                {
+                    "Name": "DepthOutput",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "DepthStencil",
+                    // Specifying the AspectFlags is very important
+                    // when using Subpasses because it avoids Validation errors.
+                    "ImageViewDesc": {
+                        "AspectFlags": [
+                            "Depth"
+                        ]
+                    },
+                    "LoadStoreAction": {
+                        "ClearValue": {
+                            "Type": "DepthStencil"
+                        },
+                        "LoadAction": "Clear",
+                        "LoadActionStencil": "DontCare",
+                        // Very important to Store the tile back to memory when using independent passes.
+                        "StoreAction": "Store",
+                        "StoreActionStencil": "DontCare"
+                    }
+                },
+                {
+                    "Name": "LightingOutput",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "RenderTarget",
+                    "LoadStoreAction": {
+                        "ClearValue": {
+                            "Value": [
+                                0.0,
+                                0.0,
+                                0.0,
+                                0.0
+                            ]
+                        },
+                        "LoadAction": "Clear",
+                        "StoreAction": "Store"
+                    }
+                }
+            ],
+            "ImageAttachments": [          
+                {
+                    "Name": "DepthAttachment",
+                    "SizeSource": {
+                      "Source": {
+                        "Pass": "Parent",
+                        "Attachment": "PipelineOutput"
+                      }
+                    },
+                    "ImageDescriptor": {
+                      "Format": "D32_FLOAT_S8X24_UINT",
+                      "SharedQueueMask": "Graphics"
+                    }
+                },
+                {
+                    "Name": "BRDFTexture",
+                    "Lifetime": "Imported",
+                    "AssetRef": {
+                        "FilePath": "Textures/BRDFTexture.attimage"
+                    }
+                }
+            ],
+            "Connections": [
+                {
+                  "LocalSlot": "DepthOutput",
+                  "AttachmentRef": {
+                    "Pass": "This",
+                    "Attachment": "DepthAttachment"
+                  }
+                },
+                {
+                    "LocalSlot": "LightingOutput",
+                    "AttachmentRef": {
+                        "Pass": "Parent",
+                        "Attachment": "PipelineOutput"
+                    }
+                },
+                {
+                    "LocalSlot": "BRDFTextureInput",
+                    "AttachmentRef": {
+                        "Pass": "This",
+                        "Attachment": "BRDFTexture"
+                    }
+                }
+            ]
+        }
+    }
+}

+ 61 - 0
Passes/SubpassExample/TwoPasses/TwoPassesPipeline.pass

@@ -0,0 +1,61 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "TwoPassesPipelineTemplate",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "PipelineOutput",
+                        "Slot": "PipelineOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "ForwardPass",
+                    "TemplateName": "ForwardPassExampleTemplate",
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "multiViewForward",
+                        "BindViewSrg": true,
+                        "PassSrgShaderAsset": {
+                            "FilePath": "Shaders/ForwardPassSrg.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "SkyBoxPass",
+                    "TemplateName": "MultiViewSkyBoxTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "SpecularInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "ForwardPass",
+                                "Attachment": "LightingOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SkyBoxDepth",
+                            "AttachmentRef": {
+                                "Pass": "ForwardPass",
+                                "Attachment": "DepthOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 106 - 0
Passes/SubpassExample/TwoSubpasses/ForwardSubpass.pass

@@ -0,0 +1,106 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "ForwardSubpassExampleTemplate",
+            "PassClass": "RasterPass",
+            "Slots": [
+                // Inputs...
+                {
+                    "Name": "BRDFTextureInput",
+                    "ShaderInputName": "m_brdfMap",
+                    "SlotType": "Input",
+                    "ScopeAttachmentUsage": "Shader"
+                },
+                // Outputs...
+                {
+                    "Name": "DepthOutput",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "DepthStencil",
+                    // Specifying the AspectFlags is very important
+                    // when using Subpasses because it avoids Validation errors.
+                    "ImageViewDesc": {
+                        "AspectFlags": [
+                            "Depth"
+                        ]
+                    },
+                    "LoadStoreAction": {
+                        "ClearValue": {
+                            "Type": "DepthStencil"
+                        },
+                        "LoadAction": "Clear",
+                        "LoadActionStencil": "DontCare",
+                        // The depth buffer won't be used after the two subpasses are completed.
+                        // For efficiency's sake do not Store.
+                        "StoreAction": "DontCare",
+                        "StoreActionStencil": "DontCare"
+                    }
+                },
+                {
+                    "Name": "LightingOutput",
+                    "SlotType": "Output",
+                    "ScopeAttachmentUsage": "RenderTarget",
+                    "LoadStoreAction": {
+                        "ClearValue": {
+                            "Value": [
+                                0.0,
+                                0.0,
+                                0.0,
+                                0.0
+                            ]
+                        },
+                        "LoadAction": "Clear",
+                        "StoreAction": "Store"
+                    }
+                }
+            ],
+            "ImageAttachments": [          
+                {
+                    "Name": "DepthAttachment",
+                    "SizeSource": {
+                      "Source": {
+                        "Pass": "Parent",
+                        "Attachment": "PipelineOutput"
+                      }
+                    },
+                    "ImageDescriptor": {
+                      "Format": "D32_FLOAT_S8X24_UINT",
+                      "SharedQueueMask": "Graphics"
+                    }
+                },
+                {
+                    "Name": "BRDFTexture",
+                    "Lifetime": "Imported",
+                    "AssetRef": {
+                        "FilePath": "Textures/BRDFTexture.attimage"
+                    }
+                }
+            ],
+            "Connections": [
+                {
+                  "LocalSlot": "DepthOutput",
+                  "AttachmentRef": {
+                    "Pass": "This",
+                    "Attachment": "DepthAttachment"
+                  }
+                },
+                {
+                    "LocalSlot": "LightingOutput",
+                    "AttachmentRef": {
+                        "Pass": "Parent",
+                        "Attachment": "PipelineOutput"
+                    }
+                },
+                {
+                    "LocalSlot": "BRDFTextureInput",
+                    "AttachmentRef": {
+                        "Pass": "This",
+                        "Attachment": "BRDFTexture"
+                    }
+                }
+            ]
+        }
+    }
+}

+ 50 - 0
Passes/SubpassExample/TwoSubpasses/SkyBoxSubpass.pass

@@ -0,0 +1,50 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "SkyBoxSubpassExampleTemplate",
+            "PassClass": "FullScreenTriangle",
+            "Slots": [
+                {
+                    "Name": "LightingOutput",
+                    "SlotType": "InputOutput",
+                    "ScopeAttachmentUsage": "RenderTarget",
+                    //"ScopeAttachmentStage": "ColorAttachmentOutput",
+                    "LoadStoreAction": {
+                        "LoadAction": "Load",
+                        "StoreAction": "Store"
+                    }
+                },
+                {
+                    "Name": "SkyBoxDepth",
+                    "SlotType": "Input",
+                    "ScopeAttachmentUsage": "SubpassInput",
+                    "ScopeAttachmentStage": "FragmentShader",
+                    // Specifying the AspectFlags is very important
+                    // because we can only sample Depth or Stencil data but not both of them.
+                    "ImageViewDesc": {
+                        "AspectFlags": [
+                            "Depth"
+                        ]
+                    },
+                    "LoadStoreAction": {
+                        "LoadAction": "Load",
+                        "LoadActionStencil": "DontCare",
+                        // For efficiency, no need to Store the depth pixels after this subpass is done.
+                        "StoreAction": "DontCare",
+                        "StoreActionStencil": "DontCare"
+                    }
+                }
+            ],
+            "PassData": {
+                "$type": "FullscreenTrianglePassData",
+                "ShaderAsset": {
+                    "FilePath": "Shaders/SubpassExample/SkyBoxSubpass.shader"
+                },
+                "BindViewSrg": true
+            }
+        }
+    }
+}

+ 62 - 0
Passes/SubpassExample/TwoSubpasses/TwoSubpassesPipeline.pass

@@ -0,0 +1,62 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "TwoSubpassesPipelineTemplate",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "PipelineOutput",
+                        "Slot": "PipelineOutput"
+                    }
+                ]
+                , "MergeChildrenAsSubpasses": true
+            },
+            "PassRequests": [
+                {
+                    "Name": "ForwardSubpass",
+                    "TemplateName": "ForwardSubpassExampleTemplate",
+                    "PassData": {
+                        "$type": "RasterPassData",
+                        "DrawListTag": "multiViewForward",
+                        "BindViewSrg": true,
+                        "PassSrgShaderAsset": {
+                            "FilePath": "Shaders/ForwardPassSrg.shader"
+                        }
+                    }
+                },
+                {
+                    "Name": "SkyBoxSubpass",
+                    "TemplateName": "SkyBoxSubpassExampleTemplate",
+                    "Enabled": true,
+                    "Connections": [
+                        {
+                            "LocalSlot": "LightingOutput",
+                            "AttachmentRef": {
+                                "Pass": "ForwardSubpass",
+                                "Attachment": "LightingOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "SkyBoxDepth",
+                            "AttachmentRef": {
+                                "Pass": "ForwardSubpass",
+                                "Attachment": "DepthOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 222 - 0
Shaders/SubpassExample/SkyBoxSubpass.azsl

@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+// --- Static Options Available ---
+// SKYBOX_TWO_OUTPUTS - Skybox renders to two rendertargets instead of one (SkyBox_TwoOutputs.pass writes to specular and reflection targets)
+
+//#include <Atom/Features/Pipeline/Forward/ForwardPassSrg.azsli> // GALIB
+#include <Atom/Features/ColorManagement/TransformColor.azsli>
+#include <Atom/Features/PostProcessing/FullscreenVertexUtil.azsli>
+#include <Atom/Features/PostProcessing/Tonemap.azsli>
+#include <Atom/Features/MatrixUtility.azsli>
+#include <Atom/Features/ShaderQualityOptions.azsli>
+
+#include <scenesrg.srgi>
+#include <viewsrg.srgi>
+
+#ifndef ENABLE_PHYSICAL_SKY
+#define ENABLE_PHYSICAL_SKY              1
+#endif
+
+#ifndef ENABLE_MERGE_FILMIC_TONEMAP
+#define ENABLE_MERGE_FILMIC_TONEMAP              0
+#endif
+
+ShaderResourceGroup PassSrg : SRG_PerPass
+{
+    [[input_attachment_index(0)]]
+    SubpassInput m_depthInput;
+
+    // Required for sampling the skybox texture.
+    Sampler LinearSampler
+    {
+        MinFilter = Linear;
+        MagFilter = Linear;
+        MipFilter = Linear;
+        AddressU = Clamp;
+        AddressV = Clamp;
+        AddressW = Clamp;
+    };
+}
+
+struct VSInput
+{
+    uint m_vertexID : SV_VertexID;
+};
+
+struct VSOutput
+{
+    float4 m_position : SV_Position;
+    float3 m_cubemapCoord : TEXCOORD0;
+};
+
+// The Hosek-Wilkie version of Perez formula
+// Parameters defination can be found in SkyBoxSystem.h
+// https://cgg.mff.cuni.cz/projects/SkylightModelling/HosekWilkie_SkylightModel_SIGGRAPH2012_Preprint_lowres.pdf
+
+float3 HosekWilkie(float cosGamma, float gamma, float cosTheta)
+{
+    float3 A = SceneSrg::m_physicalSkyData.m_physicalSkyParameterA.xyz;
+    float3 B = SceneSrg::m_physicalSkyData.m_physicalSkyParameterB.xyz;
+    float3 C = SceneSrg::m_physicalSkyData.m_physicalSkyParameterC.xyz;
+    float3 D = SceneSrg::m_physicalSkyData.m_physicalSkyParameterD.xyz;
+    float3 E = SceneSrg::m_physicalSkyData.m_physicalSkyParameterE.xyz;
+    float3 F = SceneSrg::m_physicalSkyData.m_physicalSkyParameterF.xyz;
+    float3 G = SceneSrg::m_physicalSkyData.m_physicalSkyParameterG.xyz;
+    float3 H = SceneSrg::m_physicalSkyData.m_physicalSkyParameterH.xyz;
+    float3 I = SceneSrg::m_physicalSkyData.m_physicalSkyParameterI.xyz;
+
+    float3 chi = (1 + cosGamma * cosGamma) / pow(1.0 + H * H - 2.0 * cosGamma * H, float3(1.5, 1.5, 1.5));
+    return (1.0 + A * exp(B / (cosTheta + 0.01))) * (C + D * exp(E * gamma) + F * (cosGamma * cosGamma) + G * chi + I * sqrt(cosTheta));
+}
+
+VSOutput MainVS(VSInput input)
+{
+    VSOutput OUT;
+
+    float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID);
+    OUT.m_position = float4(posTex.x, posTex.y, 0.0, 1.0);
+
+    // camera transform matrix without translation
+    float4x4 viewToWorldNoTranslation = ViewSrg::m_viewMatrixInverse;
+    viewToWorldNoTranslation[0][3] = 0.0;
+    viewToWorldNoTranslation[1][3] = 0.0;
+    viewToWorldNoTranslation[2][3] = 0.0;
+    viewToWorldNoTranslation[3][3] = 1.0;
+
+    float4x4 skyboxMatrix = mul(viewToWorldNoTranslation, ViewSrg::m_projectionMatrixInverse);
+    float usePhysicalSky = (float)SceneSrg::m_physicalSky;
+
+    // Workaround for Qualcomm bug. This is the same as:
+    // if(!SceneSrg::m_physicalSky)
+    //    skyboxMatrix = mul(SceneSrg::m_cubemapRotationMatrix, skyboxMatrix);
+    skyboxMatrix = (mul(SceneSrg::m_cubemapRotationMatrix, skyboxMatrix) * (1.0 - usePhysicalSky)) + (skyboxMatrix * usePhysicalSky);
+
+    // calculate cubemap coordinate
+    float4 clipPosition = float4(posTex.x, posTex.y, 1.0, 1.0);
+    float4 worldPosition = mul(skyboxMatrix, clipPosition);
+    OUT.m_cubemapCoord = worldPosition.xyz / worldPosition.w;
+
+    return OUT;
+}
+
+float3 GetCubemapCoords(float3 original)
+{
+    // Environment cubemaps created by IBL Baker have to be rotated to be right-side-up
+    return float3(-original.x, original.z, -original.y);
+}
+
+struct PSOutput
+{
+    float4 m_specular : SV_Target0;
+#ifdef SKYBOX_TWO_OUTPUTS
+    float4 m_reflection : SV_Target1;
+#endif
+};
+
+PSOutput MainPS(VSOutput input)
+{
+    PSOutput OUT;
+
+    if(!SceneSrg::m_enable)
+    {
+        discard;
+    }
+
+    const float zDepth = PassSrg::m_depthInput.SubpassLoad().x;
+    if (zDepth > 0.0)
+    {
+        //TODO: discard or clip(-1)?
+        // In reverse depth, any value bigger than 0.0 means there's something already rendered here.
+        // Because the skybox only renders in empty space, we skip this pixel.
+        discard;
+        return OUT;
+    }
+
+    real3 color = real3(0.5,0.5,0.5);
+#if ENABLE_PHYSICAL_SKY
+    if(SceneSrg::m_physicalSky)
+    {
+        if(input.m_cubemapCoord.z >= 0.0)
+        {
+            // Default sun direction should be in +Y direction, so sun position should be in -Y
+            input.m_cubemapCoord.y = -input.m_cubemapCoord.y;
+            // Atom is Z-up, but the value is calculated in Y-up
+            float3 worldPosition = normalize(input.m_cubemapCoord.yzx);
+
+            // Theta is the angle between viewing direction and the zenith
+            float cosTheta = worldPosition.y;
+            
+            // Gamma is the angle between viewing direction and the sun
+            float cosGamma = dot(worldPosition, SceneSrg::m_physicalSkyData.m_physicalSkySunDirection.xyz);
+
+            float gamma = acos(cosGamma);
+            float3 sunParameters = SceneSrg::m_physicalSkyData.m_physicalSkySunParameters.xyz;
+
+            // if gamma is smaller then anular diameter of the sun
+            if(cosGamma > sunParameters.z)
+            {
+                // Render Sun
+                // m_physicalSkyAndSunIntensity.y is the sun intensity assigned by user
+                color = SceneSrg::m_physicalSkyData.m_physicalSkySunRGB.xyz * SceneSrg::m_physicalSkyData.m_physicalSkyAndSunIntensity.y * PassSrg::m_sunIntensityMultiplier;
+            }
+            else
+            {
+                // Render Sky
+                // m_physicalSkyParameterZ is the sky color value at zenith
+                // m_physicalSkyAndSunIntensity.x is the sky intensity assigned by user
+                float3 Z = SceneSrg::m_physicalSkyData.m_physicalSkyParameterZ.xyz;
+                float3 srgbColor = Z * HosekWilkie(cosGamma, gamma, cosTheta) * SceneSrg::m_physicalSkyData.m_physicalSkyAndSunIntensity.x;
+                color = TransformColor(srgbColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
+            }
+        } 
+
+        if (SceneSrg::m_fogEnable)
+        {
+            if (input.m_cubemapCoord.z >= 0.0 && input.m_cubemapCoord.z <= SceneSrg::m_fogTopHeight)
+            {
+                color = lerp(SceneSrg::m_fogColor.rgb, color, input.m_cubemapCoord.z > 0.0 ? input.m_cubemapCoord.z/SceneSrg::m_fogTopHeight : 0.0);
+            }
+            else if (input.m_cubemapCoord.z < 0.0 && input.m_cubemapCoord.z >= -SceneSrg::m_fogBottomHeight)
+            {
+                color = SceneSrg::m_fogColor.rgb;
+            }
+        }
+    }
+    else
+#endif
+    {
+        color = real3(SceneSrg::m_skyboxCubemap.Sample(PassSrg::LinearSampler, GetCubemapCoords(input.m_cubemapCoord)).rgb);
+
+        //No need to go to acescg if the tonemap will be applied right away.
+#if !ENABLE_MERGE_FILMIC_TONEMAP
+        color = TransformColor(color, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
+#endif
+
+        // apply the exposure for HDRi texture
+        real exposureFactor = pow(2.0, real(SceneSrg::m_cubemapExposure));
+        color *= exposureFactor;
+    }
+    
+    // Clamp lower bounds of sky to prevent zero or negative values, which can leads to NaNs in other shaders like SMAA
+    color = max(0.000001, color);
+
+#if ENABLE_MERGE_FILMIC_TONEMAP
+    // Apply manual exposure compensation
+    color = ApplyManualExposure(color, real(ViewSrg::GetExposureValueCompensation()));
+
+    // We could add Aces support here as well if perf allows.
+    color = TonemapFilmic(color);
+#endif
+
+    OUT.m_specular = float4(color, 1.0);
+#ifdef SKYBOX_TWO_OUTPUTS
+    OUT.m_reflection = float4(color, 1.0);
+#endif
+    return OUT;
+}

+ 26 - 0
Shaders/SubpassExample/SkyBoxSubpass.shader

@@ -0,0 +1,26 @@
+{
+    "Source" : "SkyBoxSubpass.azsl",
+        
+    "Definitions": ["QUALITY_LOW_END_TIER1=1", "QUALITY_LOW_END_TIER2=1", "ENABLE_PHYSICAL_SKY=0"],
+
+    "DepthStencilState" : { 
+        "Depth" : { "Enable" : false, "CompareFunc" : "GreaterEqual" }
+    },
+
+    "DrawList" : "forward",
+
+    "ProgramSettings":
+    {
+      "EntryPoints":
+      [
+        {
+          "name": "MainVS",
+          "type": "Vertex"
+        },
+        {
+          "name": "MainPS",
+          "type": "Fragment"
+        }
+      ]
+    }
+}