소스 검색

Added a new XR level that tests a proper VR multi-view pipeline. (#7)

* Added a new XR level that tests a proper VR multi-view pipeline. It creates a render pipeline for each eye and runs than consecutively with a pipeline for PC. The two XR pipelines themselves are a variant of low end render pipeline as a start but in future they will be modified for performance and memory improvements. For example currently we are doing the ShadowMap passes for both the pipelines. This sample adds a ground plane and a few meshes on top of the plane. It also added support for rendering and updating Quest 2 controller related meshes. There is support to switch through different lighting presets for testing purposes as well as support for iterating through differnt ground materials in order to test shadows, reflections, etc.

The sample  supports Quest 2 controllers to fly around the world. It has support to use button presses for specific functionality in the scene. The schema for each controller is below
    - Left controller
             Joystick - Camera movement, Button X - Camera Up (View space y-axis), Button Y - Camera Down (View space Y axis), Squeeze - Scales Controller model
    - Right controller
             Joystick - View Orientation if Trigger button is pressed, otherwise it will use device for view tracking,
             Button A - Iterate through lighting preset, Button B - Iterate through ground plane material, Squeeze - Scales Controller model

Signed-off-by: moudgils <[email protected]>

* Address fedback

Signed-off-by: moudgils <[email protected]>

* Minor feedback

Signed-off-by: moudgils <[email protected]>

* Changed view index to an enum

Signed-off-by: moudgils <[email protected]>

Signed-off-by: moudgils <[email protected]>
Signed-off-by: amzn-phist <[email protected]>
moudgils 2 년 전
부모
커밋
696b86f4c2

+ 6 - 0
Gem/Code/Source/CommonSampleComponentBase.cpp

@@ -235,6 +235,12 @@ namespace AtomSampleViewer
         }
     }
 
+    void CommonSampleComponentBase::IterateToNextLightingPreset()
+    {
+        m_currentLightingPresetIndex = (m_currentLightingPresetIndex + 1) % m_lightingPresets.size();
+        OnLightingPresetSelected(m_lightingPresets[m_currentLightingPresetIndex].m_preset, m_useAlternateSkybox);
+    }
+
     void CommonSampleComponentBase::OnLightingPresetSelected(const AZ::Render::LightingPreset& preset, bool useAlternateSkybox)
     {
         AZ::Render::SkyBoxFeatureProcessorInterface* skyboxFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntityContextId<AZ::Render::SkyBoxFeatureProcessorInterface>(m_entityContextId);

+ 3 - 0
Gem/Code/Source/CommonSampleComponentBase.h

@@ -78,6 +78,9 @@ namespace AtomSampleViewer
         // Preload assets 
         void PreloadAssets(const AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList);
 
+        //! Iterate to the next lighting preset in a loop
+        void IterateToNextLightingPreset();
+
         //! Async asset load
         AZ::AssetCollectionAsyncLoader m_assetLoadManager;
 

+ 12 - 0
Gem/Code/Source/Platform/Windows/SampleComponentManager_Windows.cpp

@@ -21,11 +21,23 @@ namespace AtomSampleViewer
 
     const char* SampleComponentManager::GetRootPassTemplateName()
     {
+        // Use Low end pipeline template for VR
+        AZ::RPI::XRRenderingInterface* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+        if(xrSystem)
+        {
+            return "LowEndPipelineTemplate";
+        }
         return "MainPipeline";
     }
 
     int SampleComponentManager::GetDefaultNumMSAASamples()
     {
+        // Use sample count of 1 for VR pipelines
+        AZ::RPI::XRRenderingInterface* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+        if (xrSystem)
+        {
+            return 1;
+        }
         return 4;
     }
 } // namespace AtomSampleViewer

+ 15 - 11
Gem/Code/Source/RHI/XRExampleComponent.cpp

@@ -55,35 +55,39 @@ namespace AtomSampleViewer
         AZ::RPI::XRRenderingInterface* xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
         if (xrSystem && xrSystem->ShouldRender())
         {
-            const AZ::RPI::FovData fovData = xrSystem->GetViewFov(m_viewIndex);
-            const AZ::RPI::PoseData poseData = xrSystem->GetViewPose(m_viewIndex);
+            AZ::RPI::FovData fovData;
+            AZ::RPI::PoseData poseData, frontViewPoseData;
+            [[maybe_unused]] AZ::RHI::ResultCode resultCode = xrSystem->GetViewFov(m_viewIndex, fovData);
+            resultCode = xrSystem->GetViewPose(m_viewIndex, poseData);
                 
             static const float clip_near = 0.05f;
             static const float clip_far = 100.0f;
+            bool reverseDepth = false;
             projection = xrSystem->CreateProjectionOffset(fovData.m_angleLeft, fovData.m_angleRight, 
                                                           fovData.m_angleDown, fovData.m_angleUp, 
-                                                          clip_near, clip_far);
+                                                          clip_near, clip_far, reverseDepth);
 
-            AZ::Quaternion poseOrientation = poseData.orientation; 
+            AZ::Quaternion poseOrientation = poseData.m_orientation; 
             poseOrientation.InvertFast(); 
-            AZ::Matrix4x4 viewMat = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(poseOrientation, -poseData.position);
+            AZ::Matrix4x4 viewMat = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(poseOrientation, -poseData.m_position);
             m_viewProjMatrix = projection * viewMat;
  
             const AZ::Matrix4x4 initialScaleMat = AZ::Matrix4x4::CreateScale(AZ::Vector3(0.1f, 0.1f, 0.1f));
 
             //Model matrix for the cube related to the front view
-            AZ::RPI::PoseData frontViewPoseData = xrSystem->GetViewFrontPose();
-            m_modelMatrices[0] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(frontViewPoseData.orientation, frontViewPoseData.position) * initialScaleMat;
+            resultCode = xrSystem->GetViewFrontPose(frontViewPoseData);
+            m_modelMatrices[0] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(frontViewPoseData.m_orientation, frontViewPoseData.m_position) * initialScaleMat;
                       
             //Model matrix for the cube related to the left controller
-            AZ::RPI::PoseData controllerLeftPose = xrSystem->GetControllerPose(0);
+            AZ::RPI::PoseData controllerLeftPose, controllerRightPose;
+            resultCode = xrSystem->GetControllerPose(0, controllerLeftPose);
             AZ::Matrix4x4 leftScaleMat = initialScaleMat * AZ::Matrix4x4::CreateScale(AZ::Vector3(xrSystem->GetControllerScale(0)));
-            m_modelMatrices[1] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(controllerLeftPose.orientation, controllerLeftPose.position) * leftScaleMat;
+            m_modelMatrices[1] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(controllerLeftPose.m_orientation, controllerLeftPose.m_position) * leftScaleMat;
 
             //Model matrix for the cube related to the right controller
             AZ::Matrix4x4 rightScaleMat = initialScaleMat * AZ::Matrix4x4::CreateScale(AZ::Vector3(xrSystem->GetControllerScale(1)));
-            AZ::RPI::PoseData controllerRightPose = xrSystem->GetControllerPose(1);
-            m_modelMatrices[2] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(controllerRightPose.orientation, controllerRightPose.position) * rightScaleMat;
+            resultCode = xrSystem->GetControllerPose(1, controllerRightPose);
+            m_modelMatrices[2] = AZ::Matrix4x4::CreateFromQuaternionAndTranslation(controllerRightPose.m_orientation, controllerRightPose.m_position) * rightScaleMat;
         }      
         
         for (int i = 0; i < NumberOfCubes; ++i)

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

@@ -103,6 +103,7 @@
 #include <TransparencyExampleComponent.h>
 #include <DiffuseGIExampleComponent.h>
 #include <SSRExampleComponent.h>
+#include <XRRPIExampleComponent.h>
 #include <ShaderReloadTestComponent.h>
 #include <ReadbackExampleComponent.h>
 
@@ -341,6 +342,7 @@ namespace AtomSampleViewer
             NewFeaturesSample<SkinnedMeshExampleComponent>("SkinnedMesh"),
             NewFeaturesSample<SsaoExampleComponent>("SSAO"),
             NewFeaturesSample<SSRExampleComponent>("SSR"),
+            NewFeaturesSample<XRRPIExampleComponent>("OpenXR"),
             NewFeaturesSample<TonemappingExampleComponent>("Tonemapping"),
             NewFeaturesSample<TransparencyExampleComponent>("Transparency"),
             NewPerfSample<_100KDrawableExampleComponent>("100KDrawable_SingleView"),
@@ -1691,6 +1693,34 @@ namespace AtomSampleViewer
 
         renderPipeline->SetDefaultViewFromEntity(m_cameraEntity->GetId());
 
+        AZ::RPI::RPISystemInterface* rpiSystem = AZ::RPI::RPISystemInterface::Get();
+        if (rpiSystem->GetXRSystem())
+        {
+            // Build the pipeline for left eye
+            pipelineDesc.m_name = "RPISamplePipelineXRLeft";
+            pipelineDesc.m_rootPassTemplate = "LowEndPipelineXRLeftTemplate";
+            RPI::RenderPipelinePtr renderPipelineLeft = RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext.get(), AZ::RPI::WindowContext::SwapChainMode::XrLeft);
+            
+            // Build the pipeline for right eye
+            pipelineDesc.m_name = "RHISamplePipelineXRRight";
+            pipelineDesc.m_rootPassTemplate = "LowEndPipelineXRRightTemplate";
+            RPI::RenderPipelinePtr renderPipelineRight = RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext.get(), AZ::RPI::WindowContext::SwapChainMode::XrRight);
+            
+            //Add both the pipelines to the scene
+            m_rpiScene->AddRenderPipeline(renderPipelineLeft);
+            m_rpiScene->AddRenderPipeline(renderPipelineRight);
+
+            renderPipelineLeft->SetDefaultStereoscopicViewFromEntity(m_cameraEntity->GetId(), RPI::ViewType::XrLeft); //Left eye
+            renderPipelineRight->SetDefaultStereoscopicViewFromEntity(m_cameraEntity->GetId(), RPI::ViewType::XrRight); //Right eye
+
+            //Cache the pipelines in case we want to enable/disable them
+            m_xrPipelines.push_back(renderPipelineLeft);
+            m_xrPipelines.push_back(renderPipelineRight);
+
+            // Disable XR pipelines by default
+            DisableXrPipelines();
+        }
+
         // As part of our initialization we need to create the BRDF texture generation pipeline
         AZ::RPI::RenderPipelineDescriptor brdfPipelineDesc;
         brdfPipelineDesc.m_mainViewTagName = "MainCamera";
@@ -1701,7 +1731,7 @@ namespace AtomSampleViewer
         RPI::RenderPipelinePtr brdfTexturePipeline = AZ::RPI::RenderPipeline::CreateRenderPipeline(brdfPipelineDesc);
         m_rpiScene->AddRenderPipeline(brdfTexturePipeline);
         
-        // Save a reference to the generated BRDF texture so it doesn't get deleted if all the passes refering to it get deleted and it's ref count goes to zero
+        // Save a reference to the generated BRDF texture so it doesn't get deleted if all the passes referring to it get deleted and it's ref count goes to zero
         if (!m_brdfTexture)
         {
             const AZStd::shared_ptr<const RPI::PassTemplate> brdfTextureTemplate = RPI::PassSystemInterface::Get()->GetPassTemplate(Name("BRDFTextureTemplate"));
@@ -1730,6 +1760,7 @@ namespace AtomSampleViewer
             [[maybe_unused]] bool result = scene->UnsetSubsystem(m_rpiScene);
             AZ_Assert(result, "SampleComponentManager failed to unregister its RPI scene from the general scene.");
             
+            m_xrPipelines.clear();
             m_rpiScene = nullptr;
         }
     }

+ 284 - 0
Gem/Code/Source/XRRPIExampleComponent.cpp

@@ -0,0 +1,284 @@
+/*
+ * 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 <XRRPIExampleComponent.h>
+#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+#include <Atom/RPI.Public/Pass/PassFilter.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Automation/ScriptableImGui.h>
+#include <Automation/ScriptRunnerBus.h>
+#include <Utils/Utils.h>
+
+#include <SSRExampleComponent_Traits_Platform.h>
+
+namespace AtomSampleViewer
+{
+    static const float ControllerOffsetScale = 2.0f;
+    static const float ViewOrientationScale = 10.0f;
+    static const float PixelToDegree = 1.0 / 360.0f;
+
+    void XRRPIExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<XRRPIExampleComponent, AZ::Component>()
+                ->Version(0);
+        }
+    }
+
+    void XRRPIExampleComponent::Activate()
+    {
+        AZ::TickBus::Handler::BusConnect();
+
+        // setup the camera
+        Camera::CameraRequestBus::EventResult(m_originalFarClipDistance, GetCameraEntityId(), &Camera::CameraRequestBus::Events::GetFarClipDistance);
+        Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, 180.f);
+
+        m_xrSystem = AZ::RPI::RPISystemInterface::Get()->GetXRSystem();
+        m_numXrViews = m_xrSystem->GetNumViews();
+
+        // create scene
+        CreateModels();
+        CreateGroundPlane();
+
+        InitLightingPresets(true);
+    }
+
+    void XRRPIExampleComponent::Deactivate()
+    {
+        ShutdownLightingPresets();
+
+        GetMeshFeatureProcessor()->ReleaseMesh(m_statueMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_boxMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_shaderBallMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_groundMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_leftControllerMeshHandle);
+        GetMeshFeatureProcessor()->ReleaseMesh(m_rightControllerMeshHandle);
+
+        Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, m_originalFarClipDistance);
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
+
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void XRRPIExampleComponent::CreateModels()
+    {
+        GetMeshFeatureProcessor();
+
+        // statue
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("objects/hermanubis/hermanubis_stone.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME, AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            transform.SetTranslation(0.0f, 0.0f, -0.05f);
+
+            m_statueMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_statueMeshHandle, transform);
+        }
+
+        // cube
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("materials/ssrexample/cube.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/cube.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            transform.SetTranslation(-4.5f, 0.0f, 0.49f);
+
+            m_boxMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_boxMeshHandle, transform);
+        }
+
+        // shader ball
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("Materials/Presets/PBR/default_grid.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/ShaderBall_simple.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            transform *= AZ::Transform::CreateRotationZ(AZ::Constants::Pi);
+            transform.SetTranslation(4.5f, 0.0f, 0.89f);
+
+            m_shaderBallMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_shaderBallMeshHandle, transform);
+        }
+
+        // controller meshes
+        {
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("Materials/XR/XR_Hand_Controller_ControlerMAT.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/left_hand_controller.azmodel", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Transform transform = AZ::Transform::CreateIdentity();
+            
+            m_leftControllerMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            m_rightControllerMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
+            GetMeshFeatureProcessor()->SetTransform(m_leftControllerMeshHandle, transform);
+            GetMeshFeatureProcessor()->SetTransform(m_rightControllerMeshHandle, transform);
+        }
+    }
+
+    void XRRPIExampleComponent::CreateGroundPlane()
+    {
+        AZ::Render::MeshFeatureProcessorInterface* meshFeatureProcessor = GetMeshFeatureProcessor();
+        if (m_groundMeshHandle.IsValid())
+        {
+            meshFeatureProcessor->ReleaseMesh(m_groundMeshHandle);
+        }
+
+        // load material
+        AZStd::string materialName;
+        switch (m_groundPlaneMaterial)
+        {
+        case 0:
+            materialName = AZStd::string::format("materials/ssrexample/groundplanechrome.azmaterial");
+            break;
+        case 1:
+            materialName = AZStd::string::format("materials/ssrexample/groundplanealuminum.azmaterial");
+            break;
+        case 2:
+            materialName = AZStd::string::format("materials/presets/pbr/default_grid.azmaterial");
+            break;
+        default:
+            materialName = AZStd::string::format("materials/ssrexample/groundplanemirror.azmaterial");
+            break;
+        }
+
+        AZ::Data::AssetId groundMaterialAssetId = AZ::RPI::AssetUtils::GetAssetIdForProductPath(materialName.c_str(), AZ::RPI::AssetUtils::TraceLevel::Error);
+        m_groundMaterialAsset.Create(groundMaterialAssetId);
+
+        // load mesh
+        AZ::Data::Asset<AZ::RPI::ModelAsset> planeModel = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/plane.azmodel", AZ::RPI::AssetUtils::TraceLevel::Error);
+        m_groundMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ planeModel }, AZ::RPI::Material::FindOrCreate(m_groundMaterialAsset));
+
+        AZ::Transform transform = AZ::Transform::CreateIdentity();
+        const AZ::Vector3 nonUniformScale(15.0f, 15.0f, 1.0f);
+        GetMeshFeatureProcessor()->SetTransform(m_groundMeshHandle, transform, nonUniformScale);
+    }
+
+    void XRRPIExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
+    {
+        if (m_resetCamera)
+        {
+            AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Reset);
+            AZ::TransformBus::Event(GetCameraEntityId(), &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(7.5f, -10.5f, 3.0f));
+            AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable, azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetHeading, AZ::DegToRad(22.5f));
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetPitch, AZ::DegToRad(-10.0f));
+            m_resetCamera = false;
+        }
+
+        if (m_xrSystem && m_xrSystem->ShouldRender())
+        {
+            AZ::RPI::PoseData frontPoseData;
+            AZ::RHI::ResultCode resultCode = m_xrSystem->GetViewLocalPose(frontPoseData);
+            for (AZ::u32 i = 0; i < m_numXrViews; i++)
+            {
+                //Pose data for the controller
+                AZ::RPI::PoseData controllerPose;
+                resultCode = m_xrSystem->GetControllerPose(i, controllerPose);
+
+                if(resultCode == AZ::RHI::ResultCode::Success && !controllerPose.m_orientation.IsZero())
+                { 
+                    AZ::Vector3 camPosition;
+                    AZ::TransformBus::EventResult(camPosition, GetCameraEntityId(), &AZ::TransformBus::Events::GetWorldTranslation);
+                    AZ::Vector3 controllerPositionOffset = controllerPose.m_position * ControllerOffsetScale;
+                    AZ::Vector3 newControllerPos = camPosition + AZ::Vector3(controllerPositionOffset.GetX(), -controllerPositionOffset.GetZ(), controllerPositionOffset.GetY());
+
+                    // Go from y up to z up as a right handed coord system
+                    AZ::Quaternion controllerOrientation = controllerPose.m_orientation;
+                    controllerOrientation.SetX(controllerPose.m_orientation.GetX());
+                    controllerOrientation.SetY(-controllerPose.m_orientation.GetZ());
+                    controllerOrientation.SetZ(controllerPose.m_orientation.GetY());
+
+                    //Apply a Rotation of 90 deg around X axis in order to orient the model to face away from you as default pose
+                    AZ::Transform controllerTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
+                                controllerOrientation * AZ::Quaternion::CreateRotationX(-AZ::Constants::Pi / 2), AZ::Vector3(newControllerPos.GetX(), newControllerPos.GetY(),newControllerPos.GetZ()));
+                
+                    AZ::Render::MeshFeatureProcessorInterface::MeshHandle* controllerMeshhandle = &m_leftControllerMeshHandle;
+                    if (i == 1)
+                    {
+                        controllerMeshhandle = &m_rightControllerMeshHandle;
+                    }   
+                    GetMeshFeatureProcessor()->SetTransform(*controllerMeshhandle, controllerTransform, AZ::Vector3(m_xrSystem->GetControllerScale(i)));
+                }
+            }
+            
+            //Update Camera movement (left, right forward, back) based on left JoyStick controller
+            float m_xJoyStickValue = m_xrSystem->GetXJoyStickState(0);
+            float m_yJoyStickValue = m_xrSystem->GetYJoyStickState(0);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateForward, m_yJoyStickValue);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateBack, m_yJoyStickValue);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateLeft, m_xJoyStickValue);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateRight, m_xJoyStickValue);
+
+            //Update Camera movement (Up, Down) based on X,Y button presses on the left controller
+            float yButtonState = m_xrSystem->GetYButtonState();
+            float xButtonState = m_xrSystem->GetXButtonState();
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateUp, yButtonState);
+            AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetCameraStateDown, xButtonState);
+
+            // Switch to updating the view using right joystick controller if the Trigger button on the right controller is pressed
+            m_xrSystem->GetTriggerState(1) > 0.1f ? m_rightTriggerButtonPressed = true : m_rightTriggerButtonPressed = false;
+            if (m_rightTriggerButtonPressed)
+            { 
+                //Update Camera view based on right JoyStick controller
+                float m_xRightJoyStickValue = m_xrSystem->GetXJoyStickState(1);
+                float m_yRightJoyStickValue = m_xrSystem->GetYJoyStickState(1);
+                float heading, pitch = 0.0;
+                AZ::Debug::NoClipControllerRequestBus::EventResult(heading, GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::GetHeading);
+                AZ::Debug::NoClipControllerRequestBus::EventResult(pitch, GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::GetPitch);
+                heading -= m_xRightJoyStickValue * PixelToDegree * ViewOrientationScale;
+                pitch += m_yRightJoyStickValue * PixelToDegree * ViewOrientationScale;
+                pitch = AZStd::max(pitch, -AZ::Constants::HalfPi);
+                pitch = AZStd::min(pitch, AZ::Constants::HalfPi);
+                AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetHeading, heading);
+                AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequests::SetPitch, pitch);
+            }
+            else
+            {
+                for (AZ::u32 i = 0; i < m_numXrViews; i++)
+                {
+                    //Convert to O3de's coordinate system and update the camera orientation for the correct eye view
+                    AZ::Quaternion viewLocalPoseOrientation = frontPoseData.m_orientation;
+                    viewLocalPoseOrientation.SetX(-frontPoseData.m_orientation.GetX());
+                    viewLocalPoseOrientation.SetY(frontPoseData.m_orientation.GetZ());
+                    viewLocalPoseOrientation.SetZ(-frontPoseData.m_orientation.GetY());
+                    Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetXRViewQuaternion, viewLocalPoseOrientation, i);
+                }
+            }
+
+            // Switch to the next lighting preset using the B-button
+            if (m_xrSystem->GetBButtonState() > 0.0f)
+            {
+                if (!m_bButtonPressed)
+                {
+                    m_bButtonPressed = true;
+                    IterateToNextLightingPreset();
+                }
+            }
+            else
+            {
+                m_bButtonPressed = false;
+            }
+
+            // Switch to the next ground floor using the A-button
+            if (m_xrSystem->GetAButtonState() > 0.0f)
+            {
+                if (!m_aButtonPressed)
+                {
+                    m_aButtonPressed = true;
+                    m_groundPlaneMaterial = (m_groundPlaneMaterial + 1) % 4;
+                    CreateGroundPlane();
+                }
+            }
+            else
+            {
+                m_aButtonPressed = false;
+            }
+        }
+    }
+}

+ 86 - 0
Gem/Code/Source/XRRPIExampleComponent.h

@@ -0,0 +1,86 @@
+/*
+ * 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/TickBus.h>
+#include <Atom/Component/DebugCamera/CameraComponent.h>
+#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+#include <CommonSampleComponentBase.h>
+#include <Utils/ImGuiSidebar.h>
+#include <Utils/Utils.h>
+
+
+namespace AtomSampleViewer
+{
+    //!
+    //! This component creates a simple scene that tests VR using a special multi-view VR pipeline. We setup two pipelines, one for each eye and use stereoscopic view for this pipeline.
+    //! This sample supports Quest 2 controllers to fly around the world. It also has support to use button presses for specific functionality 
+    //! in the scene. The schema for each controller is below
+    //! Left controller
+    //!         Joystick - Camera movement, Button X - Camera Up (View space y-axis), Button Y - Camera Down (View space Y axis), Squeeze - Scales Controller model     
+     //! Right controller
+    //!         Joystick - View Orientation if Trigger button is pressed, otherwise it will use device for view tracking, 
+    //!         Button B - Iterate through lighting preset, Button B - Iterate through ground plane material, Squeeze - Scales Controller model     
+    //!
+    class XRRPIExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(AtomSampleViewer::XRRPIExampleComponent, "{3122B48E-2553-4568-8B8B-532C105CB83B}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        XRRPIExampleComponent() = default;
+        ~XRRPIExampleComponent() override = default;
+
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        AZ_DISABLE_COPY_MOVE(XRRPIExampleComponent);
+
+        void CreateModels();
+        void CreateGroundPlane();
+        
+        // AZ::TickBus::Handler
+        void OnTick(float deltaTime, AZ::ScriptTimePoint timePoint) override;
+
+        void OnModelReady(AZ::Data::Instance<AZ::RPI::Model> model);
+
+        // meshes
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_statueMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_boxMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_shaderBallMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_groundMeshHandle;
+        AZ::Data::Asset<AZ::RPI::MaterialAsset> m_groundMaterialAsset;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_leftControllerMeshHandle;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_rightControllerMeshHandle;
+
+        // ground plane default material setting index
+        int m_groundPlaneMaterial = 2;
+
+        // IBL and skybox
+        Utils::DefaultIBL m_defaultIbl;
+        AZ::Data::Asset<AZ::RPI::StreamingImageAsset> m_skyboxImageAsset;
+
+        bool m_resetCamera = true;
+        float m_originalFarClipDistance = 0.0f;
+
+        bool m_xButtonPressed = false;
+        bool m_yButtonPressed = false;
+        bool m_aButtonPressed = false;
+        bool m_bButtonPressed = false;
+        bool m_rightTriggerButtonPressed = false;
+
+        AZ::RPI::XRRenderingInterface* m_xrSystem = nullptr;
+        AZ::u32 m_numXrViews = 0;
+    };
+} // namespace AtomSampleViewer

+ 2 - 0
Gem/Code/atomsampleviewergem_private_files.cmake

@@ -196,4 +196,6 @@ set(FILES
     Source/Utils/Utils.h
     Source/Utils/ImGuiProgressList.cpp
     Source/Utils/ImGuiProgressList.h
+    Source/XRRPIExampleComponent.cpp
+    Source/XRRPIExampleComponent.h
 )

+ 26 - 0
Materials/XR/XR_Hand_Controller_ControlerMAT.material

@@ -0,0 +1,26 @@
+{
+    "materialType": "Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.color": [
+            0.800000011920929,
+            0.800000011920929,
+            0.800000011920929,
+            1.0
+        ],
+        "baseColor.textureBlendMode": "Lerp",
+        "baseColor.textureMap": "Materials/XR/Textures/ControlerMAT_BaseColor.png",
+        "emissive.color": [
+            0.0,
+            0.9666590094566345,
+            1.0,
+            1.0
+        ],
+        "emissive.enable": true,
+        "emissive.textureMap": "Materials/XR/Textures/ControlerMAT_Emissive.png",
+        "irradiance.irradianceColorSource": "BaseColor",
+        "opacity.factor": 1.0,
+        "roughness.textureMap": "Materials/XR/Textures/ControlerMAT_Roughness.png",
+        "specularF0.factor": 0.10000000149011612
+    }
+}

+ 3 - 0
Materials/XR/textures/ControlerMAT_BaseColor.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:95251161fa2692d790da835acb658eb7ce4c0712b10cb50bcbb04c11f37d827d
+size 8919

+ 3 - 0
Materials/XR/textures/ControlerMAT_Emissive.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c4192d49b4da2c9d35b533148cbc86860686d38b287c69a8b1a5868ce4194d70
+size 4533

+ 3 - 0
Materials/XR/textures/ControlerMAT_Roughness.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:00bfda11447ae3523ebcf38ccaa8b6b857c1c9143e0586a33c55349794131a7f
+size 4799

+ 3 - 0
Objects/Left_Hand_Controller.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cdbe3cac006e138534fe7d77a61e5fe40fc3de77472f51b76031e4ef98051549
+size 1957820

+ 3 - 0
Objects/Right_Hand_Controller.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df8523b3c8e2e194f3949937cc10a87ba2120ed0a3dad613910deaccf841101a
+size 1977484

+ 3 - 0
Textures/ControlerMAT_BaseColor.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:95251161fa2692d790da835acb658eb7ce4c0712b10cb50bcbb04c11f37d827d
+size 8919

+ 3 - 0
Textures/ControlerMAT_Emissive.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c4192d49b4da2c9d35b533148cbc86860686d38b287c69a8b1a5868ce4194d70
+size 4533

+ 3 - 0
Textures/ControlerMAT_Roughness.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:00bfda11447ae3523ebcf38ccaa8b6b857c1c9143e0586a33c55349794131a7f
+size 4799