diff --git a/Code/Editor/Include/ObjectEvent.h b/Code/Editor/Include/ObjectEvent.h index 7d137d7b84..e59bca6111 100644 --- a/Code/Editor/Include/ObjectEvent.h +++ b/Code/Editor/Include/ObjectEvent.h @@ -27,7 +27,6 @@ enum ObjectEvent EVENT_OUTOFGAME, //!< Signals that editor is switching out of the game mode. EVENT_REFRESH, //!< Signals that editor is refreshing level. EVENT_DBLCLICK, //!< Signals that object have been double clicked. - EVENT_KEEP_HEIGHT, //!< Signals that object must preserve its height over changed terrain. EVENT_RELOAD_ENTITY,//!< Signals that entities scripts must be reloaded. EVENT_RELOAD_GEOM, //!< Signals that all possible geometries should be reloaded. EVENT_UNLOAD_GEOM, //!< Signals that all possible geometries should be unloaded. diff --git a/Code/Editor/Objects/BaseObject.cpp b/Code/Editor/Objects/BaseObject.cpp index 14291a3e4d..4ae01faddf 100644 --- a/Code/Editor/Objects/BaseObject.cpp +++ b/Code/Editor/Objects/BaseObject.cpp @@ -618,12 +618,6 @@ bool CBaseObject::SetPos(const Vec3& pos, int flags) StoreUndo("Position", true, flags); } - float terrainElevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight(); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainElevation - , &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats - , pos.x, pos.y, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, nullptr); - m_height = pos.z - terrainElevation; - if (!bPositionDelegated) { m_pos = pos; @@ -1275,14 +1269,6 @@ void CBaseObject::OnEvent(ObjectEvent event) { switch (event) { - case EVENT_KEEP_HEIGHT: - { - float h = m_height; - float newz = GetIEditor()->GetTerrainElevation(m_pos.x, m_pos.y) + m_height; - SetPos(Vec3(m_pos.x, m_pos.y, newz)); - m_height = h; - } - break; case EVENT_CONFIG_SPEC_CHANGE: UpdateVisibility(!IsHidden()); break; diff --git a/Code/Editor/Objects/BaseObject.h b/Code/Editor/Objects/BaseObject.h index 89e47ae881..ea58b4e78d 100644 --- a/Code/Editor/Objects/BaseObject.h +++ b/Code/Editor/Objects/BaseObject.h @@ -798,8 +798,6 @@ private: ////////////////////////////////////////////////////////////////////////// //! Area radius around object, where terrain is flatten and static objects removed. float m_flattenArea; - //! Every object keeps for itself height above terrain. - float m_height; //! Object's name. QString m_name; //! Class description for this object. diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ReceiverPlaneDepthBias.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ReceiverPlaneDepthBias.azsli index 6225bb3434..9d785e2086 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ReceiverPlaneDepthBias.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ReceiverPlaneDepthBias.azsli @@ -35,4 +35,4 @@ float ApplyReceiverPlaneDepthBias(const float2 receiverPlaneDepthBias, const flo // Clamping this will remove this effect fragmentDepthBias = min(fragmentDepthBias, 0.0); return fragmentDepthBias; -} \ No newline at end of file +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl index 57d0909ae4..50d11ca610 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl @@ -6,120 +6,90 @@ */ #include +#include #include - - -struct VertexInput -{ - float2 Position : POSITION; - float2 UV : UV; -}; +#include "TerrainCommon.azsli" struct VertexOutput { - float4 Position : SV_Position; - float3 Normal : NORMAL; - float2 UV : UV; + linear centroid float4 m_position : SV_Position; + float3 m_normal : NORMAL; + float3 m_tangent : TANGENT; + float3 m_bitangent : BITANGENT; + float m_height : UV; }; -ShaderResourceGroup ObjectSrg : SRG_PerObject +struct ForwardPassOutput { - Texture2D HeightmapImage; - - Sampler LinearSampler - { - MinFilter = Linear; - MagFilter = Linear; - MipFilter = Linear; - AddressU = Clamp; - AddressV = Clamp; - AddressW = Clamp; - }; - - row_major float3x4 m_modelToWorld; - float m_heightScale; - float2 m_uvMin; - float2 m_uvMax; - float2 m_uvStep; -} - -float4x4 GetObject_WorldMatrix() -{ - float4x4 modelToWorld = float4x4( - float4(1, 0, 0, 0), - float4(0, 1, 0, 0), - float4(0, 0, 1, 0), - float4(0, 0, 0, 1)); - - modelToWorld[0] = ObjectSrg::m_modelToWorld[0]; - modelToWorld[1] = ObjectSrg::m_modelToWorld[1]; - modelToWorld[2] = ObjectSrg::m_modelToWorld[2]; - return modelToWorld; -} - -float GetHeight(float2 origUv) -{ - float2 uv = clamp(origUv, 0.0f, 1.0f); - return ObjectSrg::m_heightScale * (ObjectSrg::HeightmapImage.SampleLevel(ObjectSrg::LinearSampler, uv, 0).r - 0.5f); -} - -VertexOutput MainVS(in VertexInput input) -{ - VertexOutput output; - - // Clamp the UVs *after* lerping to ensure that everything aligns properly right to the edge. - // We use out-of-bounds UV values to denote vertices that need to be removed. - float2 origUv = lerp(ObjectSrg::m_uvMin, ObjectSrg::m_uvMax, input.UV); - float2 uv = clamp(origUv, 0.0f, 1.0f); - - // Loop up the height and calculate our final position. - float height = GetHeight(uv); - float3 worldPosition = mul(GetObject_WorldMatrix(), float4(input.Position, height, 1.0f)).xyz; - output.Position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0f)); - - // Remove all vertices outside our bounds by turning them into NaN positions. - output.Position = output.Position / ((origUv.x >= 0.0f && origUv.x < 1.0f && origUv.y >= 0.0f && origUv.y < 1.0f) ? 1.0f : 0.0f); - - // Calculate normal - float2 gridSize = {1.0f, 1.0f}; - float up = GetHeight(uv + ObjectSrg::m_uvStep * float2(-1.0f, 0.0f)); - float right = GetHeight(uv + ObjectSrg::m_uvStep * float2( 0.0f, 1.0f)); - float down = GetHeight(uv + ObjectSrg::m_uvStep * float2( 1.0f, 0.0f)); - float left = GetHeight(uv + ObjectSrg::m_uvStep * float2( 0.0f, -1.0f)); - - float dydx = (right - left) * gridSize[0]; - float dydz = (down - up) * gridSize[1]; - - output.Normal = normalize(float3(dydx, 2.0f, dydz)); - - output.UV = uv; - return output; -} + float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering + float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused + float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion + float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness + float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled +}; struct PixelOutput { float4 m_color : SV_Target0; }; - -PixelOutput MainPS(in VertexOutput input) + +VertexOutput MainVS(in VertexInput input) { - PixelOutput output; + VertexOutput output; + ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; - // Hard-coded fake light direction - float3 lightDirection = normalize(float3(1.0, -1.0, 1.0)); + float2 uv = input.m_uv; + float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, uv); + output.m_position = GetTerrainProjectedPosition(terrainData, input.m_position, origUv); + // Calculate normal + float up = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, -1.0f)); + float right = GetHeight(origUv + terrainData.m_uvStep * float2( 1.0f, 0.0f)); + float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); + float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); + + output.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); + output.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); + output.m_normal = cross(output.m_tangent, output.m_bitangent); + + output.m_height = GetHeight(origUv); + return output; +} + +ForwardPassOutput MainPS(in VertexOutput input) +{ + ForwardPassOutput output; + + float3 lightDirection = normalize(float3(-1.0, 1.0, -1.0)); + float3 lightIntensity = float3(1.0, 1.0, 1.0); + if (SceneSrg::m_directionalLightCount > 0) + { + lightDirection = SceneSrg::m_directionalLights[0].m_direction; + lightIntensity = SceneSrg::m_directionalLights[0].m_rgbIntensityLux; + } + // Fake light intensity ranges from 1.0 for normals directly facing the light to zero for those // directly facing away. - float lightDot = dot(normalize(input.Normal), lightDirection); - float lightIntensity = lightDot * 0.5 + 0.5; + const float minLight = 0.01; + const float midLight = 0.1; + float lightDot = dot(normalize(input.m_normal), -lightDirection); + lightIntensity *= lightDot > 0.0 ? + lightDot * (1.0 - midLight) + midLight : // surface facing light + (lightDot + 1.0) * (midLight - minLight) + minLight; // surface facing away - // add a small amount of ambient and reduce direct light to keep in 0-1 range - lightIntensity = saturate(0.1 + lightIntensity * 0.9); + output.m_diffuseColor.rgb = 0.5 * lightIntensity; + output.m_diffuseColor.a = 0.0f; - // The lightIntensity should not affect alpha so only apply it to rgb. - //output.m_color.rgb = ((input.Normal + float3(1.0, 1.0, 1.0)) / 2.0); - output.m_color.rgb = float3(1.0, 1.0, 1.0) * lightIntensity; - output.m_color.a = 1.0f; + output.m_specularColor.rgb = 0.0; + + output.m_albedo.rgb = 0.25 + input.m_height * 0.5; + output.m_albedo.a = 0.0; + + output.m_specularF0.rgb = 0.04; + output.m_specularF0.a = 1.0; + + output.m_normal.rgb = input.m_normal; + output.m_normal.a = 0.0; return output; } diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader index f20f4201ae..e844a54a21 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader @@ -1,21 +1,13 @@ { - - "Source" : "Terrain", + "Source" : "./Terrain.azsl", - - "RasterState" : { "CullMode" : "None" }, - - "DepthStencilState" : { - "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } - }, - - "BlendState" : { - "Enable" : true, - "BlendSource" : "One", - "BlendAlphaSource" : "One", - "BlendDest" : "AlphaSourceInverse", - "BlendAlphaDest" : "AlphaSourceInverse", - "BlendAlphaOp" : "Add" + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + } }, "DrawList" : "forward", @@ -33,5 +25,9 @@ "type": "Fragment" } ] + }, + + "BlendState" : { + "Enable" : false } } diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli new file mode 100644 index 0000000000..6e489796d7 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli @@ -0,0 +1,76 @@ +/* + * 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 + +ShaderResourceGroup ObjectSrg : SRG_PerObject +{ + Texture2D m_heightmapImage; + + Sampler LinearSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + row_major float3x4 m_modelToWorld; + + struct TerrainData + { + float2 m_uvMin; + float2 m_uvMax; + float2 m_uvStep; + float m_sampleSpacing; + float m_heightScale; + }; + + TerrainData m_terrainData; +} + +struct VertexInput +{ + float2 m_position : POSITION; + float2 m_uv : UV; +}; + +float4x4 GetObject_WorldMatrix() +{ + float4x4 modelToWorld = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorld[0] = ObjectSrg::m_modelToWorld[0]; + modelToWorld[1] = ObjectSrg::m_modelToWorld[1]; + modelToWorld[2] = ObjectSrg::m_modelToWorld[2]; + return modelToWorld; +} + +float GetHeight(float2 origUv) +{ + float2 uv = clamp(origUv, 0.0f, 1.0f); + return ObjectSrg::m_terrainData.m_heightScale * (ObjectSrg::m_heightmapImage.SampleLevel(ObjectSrg::LinearSampler, uv, 0).r - 0.5f); +} + +float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) +{ + // Remove all vertices outside our bounds by turning them into NaN positions. + if (any(uv > 1.0) || any (uv < 0.0)) + { + return asfloat(0x7fc00000); // NaN + } + + // Loop up the height and calculate our final position. + float height = GetHeight(uv); + float4 worldPosition = mul(GetObject_WorldMatrix(), float4(vertexPosition, height, 1.0f)); + return mul(ViewSrg::m_viewProjectionMatrix, worldPosition); +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl new file mode 100644 index 0000000000..20c56323ac --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl @@ -0,0 +1,25 @@ +/* + * 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 +#include +#include "TerrainCommon.azsli" + +struct VSDepthOutput +{ + float4 m_position : SV_Position; +}; + +VSDepthOutput MainVS(in VertexInput input) +{ + VSDepthOutput output; + ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; + + float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, input.m_uv); + output.m_position = GetTerrainProjectedPosition(terrainData, input.m_position, origUv); + return output; +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.shader new file mode 100644 index 0000000000..f1bcdbc1d3 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.shader @@ -0,0 +1,26 @@ +{ + "Source" : "./Terrain_DepthPass.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + } + }, + + "DrawList" : "depth", + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + } + ] + } + +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index c4aaa6db04..f0743cfb52 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -41,12 +41,9 @@ namespace Terrain namespace ShaderInputs { - static const char* const HeightmapImage("HeightmapImage"); + static const char* const HeightmapImage("m_heightmapImage"); static const char* const ModelToWorld("m_modelToWorld"); - static const char* const HeightScale("m_heightScale"); - static const char* const UvMin("m_uvMin"); - static const char* const UvMax("m_uvMax"); - static const char* const UvStep("m_uvStep"); + static const char* const TerrainData("m_terrainData"); } @@ -68,76 +65,76 @@ namespace Terrain EnableSceneNotification(); } + void TerrainFeatureProcessor::ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail) + { + bool success = GetParentScene()->ConfigurePipelineState(shaderState.m_shader->GetDrawListTag(), shaderState.m_pipelineStateDescriptor); + AZ_Assert(success || !assertOnFail, "Couldn't configure the pipeline state."); + if (success) + { + shaderState.m_pipelineState = shaderState.m_shader->AcquirePipelineState(shaderState.m_pipelineStateDescriptor); + AZ_Assert(shaderState.m_pipelineState, "Failed to acquire default pipeline state."); + } + } + void TerrainFeatureProcessor::InitializeAtomStuff() { m_rhiSystem = AZ::RHI::RHISystemInterface::Get(); - { - // Load the shader - constexpr const char* TerrainShaderFilePath = "Shaders/Terrain/Terrain.azshader"; - m_shader = AZ::RPI::LoadShader(TerrainShaderFilePath); - if (!m_shader) + { + auto LoadShader = [this](const char* filePath, ShaderState& shaderState) { - AZ_Error(TerrainFPName, false, "Failed to find or create a shader instance from shader asset '%s'", TerrainShaderFilePath); + shaderState.m_shader = AZ::RPI::LoadShader(filePath); + if (!shaderState.m_shader) + { + AZ_Error(TerrainFPName, false, "Failed to find or create a shader instance from shader asset '%s'", filePath); + return; + } + + // Create the data layout + shaderState.m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; + { + AZ::RHI::InputStreamLayoutBuilder layoutBuilder; + + layoutBuilder.AddBuffer() + ->Channel("POSITION", AZ::RHI::Format::R32G32_FLOAT) + ->Channel("UV", AZ::RHI::Format::R32G32_FLOAT) + ; + shaderState.m_pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End(); + } + + auto shaderVariant = shaderState.m_shader->GetVariant(AZ::RPI::ShaderAsset::RootShaderVariantStableId); + shaderVariant.ConfigurePipelineState(shaderState.m_pipelineStateDescriptor); + + // If this fails to run now, it's ok, we'll initialize it in OnRenderPipelineAdded later. + ConfigurePipelineState(shaderState, false); + }; + + LoadShader("Shaders/Terrain/Terrain.azshader", m_shaderStates[ShaderType::Forward]); + LoadShader("Shaders/Terrain/Terrain_DepthPass_WithPS.azshader", m_shaderStates[ShaderType::Depth]); + + // Forward and depth shader use same srg layout. + AZ::RHI::Ptr perObjectSrgLayout = + m_shaderStates[ShaderType::Forward].m_shader->FindShaderResourceGroupLayout(AZ::Name{"ObjectSrg"}); + + if (!perObjectSrgLayout) + { + AZ_Error(TerrainFPName, false, "Failed to get shader resource group layout"); + return; + } + else if (!perObjectSrgLayout->IsFinalized()) + { + AZ_Error(TerrainFPName, false, "Shader resource group layout is not loaded"); return; } - // Create the data layout - m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; - { - AZ::RHI::InputStreamLayoutBuilder layoutBuilder; - - layoutBuilder.AddBuffer() - ->Channel("POSITION", AZ::RHI::Format::R32G32_FLOAT) - ->Channel("UV", AZ::RHI::Format::R32G32_FLOAT) - ; - m_pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End(); - } - - auto shaderVariant = m_shader->GetVariant(AZ::RPI::ShaderAsset::RootShaderVariantStableId); - shaderVariant.ConfigurePipelineState(m_pipelineStateDescriptor); - - m_drawListTag = m_shader->GetDrawListTag(); - - m_perObjectSrgAsset = m_shader->FindShaderResourceGroupLayout(AZ::Name{"ObjectSrg"}); - if (!m_perObjectSrgAsset) - { - AZ_Error(TerrainFPName, false, "Failed to get shader resource group asset"); - return; - } - else if (!m_perObjectSrgAsset->IsFinalized()) - { - AZ_Error(TerrainFPName, false, "Shader resource group asset is not loaded"); - return; - } - - const AZ::RHI::ShaderResourceGroupLayout* shaderResourceGroupLayout = &(*m_perObjectSrgAsset); - - m_heightmapImageIndex = shaderResourceGroupLayout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::HeightmapImage)); + m_heightmapImageIndex = perObjectSrgLayout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::HeightmapImage)); AZ_Error(TerrainFPName, m_heightmapImageIndex.IsValid(), "Failed to find shader input image %s.", ShaderInputs::HeightmapImage); - m_modelToWorldIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); + m_modelToWorldIndex = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld); - m_heightScaleIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::HeightScale)); - AZ_Error(TerrainFPName, m_heightScaleIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::HeightScale); - - m_uvMinIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::UvMin)); - AZ_Error(TerrainFPName, m_uvMinIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::UvMin); - - m_uvMaxIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::UvMax)); - AZ_Error(TerrainFPName, m_uvMaxIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::UvMax); - - m_uvStepIndex = shaderResourceGroupLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::UvStep)); - AZ_Error(TerrainFPName, m_uvStepIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::UvStep); - - // If this fails to run now, it's ok, we'll initialize it in OnRenderPipelineAdded later. - bool success = GetParentScene()->ConfigurePipelineState(m_shader->GetDrawListTag(), m_pipelineStateDescriptor); - if (success) - { - m_pipelineState = m_shader->AcquirePipelineState(m_pipelineStateDescriptor); - AZ_Assert(m_pipelineState, "Failed to acquire default pipeline state for shader '%s'", TerrainShaderFilePath); - } + m_terrainDataIndex = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData)); + AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData); } AZ::RHI::BufferPoolDescriptor dmaPoolDescriptor; @@ -165,12 +162,9 @@ namespace Terrain void TerrainFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] AZ::RPI::RenderPipelinePtr pipeline) { - bool success = GetParentScene()->ConfigurePipelineState(m_drawListTag, m_pipelineStateDescriptor); - AZ_Assert(success, "Couldn't configure the pipeline state."); - if (success) + for (ShaderState& shaderState: m_shaderStates) { - m_pipelineState = m_shader->AcquirePipelineState(m_pipelineStateDescriptor); - AZ_Assert(m_pipelineState, "Failed to acquire default pipeline state."); + ConfigurePipelineState(shaderState, true); } } @@ -206,7 +200,7 @@ namespace Terrain void TerrainFeatureProcessor::UpdateTerrainData( const AZ::Transform& transform, const AZ::Aabb& worldBounds, - [[maybe_unused]] float sampleSpacing, + float sampleSpacing, uint32_t width, uint32_t height, const AZStd::vector& heightData) { if (!worldBounds.IsValid()) @@ -219,6 +213,7 @@ namespace Terrain m_areaData.m_terrainBounds = worldBounds; m_areaData.m_heightmapImageHeight = height; m_areaData.m_heightmapImageWidth = width; + m_areaData.m_sampleSpacing = sampleSpacing; // Create heightmap image data { @@ -228,13 +223,22 @@ namespace Terrain imageSize.m_width = width; imageSize.m_height = height; + AZStd::vector uint16Heights; + uint16Heights.reserve(heightData.size()); + for (float sampleHeight : heightData) + { + float clampedSample = AZ::GetClamp(sampleHeight, 0.0f, 1.0f); + constexpr uint16_t MaxUint16 = 0xFFFF; + uint16Heights.push_back(aznumeric_cast(clampedSample * MaxUint16)); + } + AZ::Data::Instance streamingImagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemStreamingPool(); m_areaData.m_heightmapImage = AZ::RPI::StreamingImage::CreateFromCpuData(*streamingImagePool, AZ::RHI::ImageDimension::Image2D, imageSize, - AZ::RHI::Format::R32_FLOAT, - (uint8_t*)heightData.data(), - heightData.size() * sizeof(float)); + AZ::RHI::Format::R16_UNORM, + (uint8_t*)uint16Heights.data(), + heightData.size() * sizeof(uint16_t)); AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image!"); } @@ -244,7 +248,8 @@ namespace Terrain { AZ_PROFILE_FUNCTION(AzRender); - if (m_drawListTag.IsNull()) + if (m_shaderStates[ShaderType::Forward].m_shader->GetDrawListTag().IsNull() || + m_shaderStates[ShaderType::Depth].m_shader->GetDrawListTag().IsNull()) { return; } @@ -256,6 +261,7 @@ namespace Terrain if (m_areaData.m_propertiesDirty) { + m_areaData.m_propertiesDirty = false; m_sectorData.clear(); AZ::RHI::DrawPacketBuilder drawPacketBuilder; @@ -281,17 +287,17 @@ namespace Terrain drawPacketBuilder.Begin(nullptr); drawPacketBuilder.SetDrawArguments(drawIndexed); drawPacketBuilder.SetIndexBufferView(m_indexBufferView); + auto& forwardShader = m_shaderStates[ShaderType::Forward].m_shader; - auto resourceGroup = AZ::RPI::ShaderResourceGroup::Create(m_shader->GetAsset(), m_shader->GetSupervariantIndex(), AZ::Name("ObjectSrg")); - //auto m_resourceGroup = AZ::RPI::ShaderResourceGroup::Create(m_shader->GetAsset(), AZ::Name("ObjectSrg")); + auto resourceGroup = AZ::RPI::ShaderResourceGroup::Create(forwardShader->GetAsset(), forwardShader->GetSupervariantIndex(), AZ::Name("ObjectSrg")); if (!resourceGroup) { AZ_Error(TerrainFPName, false, "Failed to create shader resource group"); return; } - float uvMin[2] = { 0.0f, 0.0f }; - float uvMax[2] = { 1.0f, 1.0f }; + AZStd::array uvMin = { 0.0f, 0.0f }; + AZStd::array uvMax = { 1.0f, 1.0f }; uvMin[0] = (float)((xPatch - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); uvMin[1] = (float)((yPatch - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); @@ -301,7 +307,7 @@ namespace Terrain uvMax[1] = (float)(((yPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); - float uvStep[2] = + AZStd::array uvStep = { 1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight, }; @@ -313,18 +319,32 @@ namespace Terrain resourceGroup->SetImage(m_heightmapImageIndex, m_areaData.m_heightmapImage); resourceGroup->SetConstant(m_modelToWorldIndex, matrix3x4); - resourceGroup->SetConstant(m_heightScaleIndex, m_areaData.m_heightScale); - resourceGroup->SetConstant(m_uvMinIndex, uvMin); - resourceGroup->SetConstant(m_uvMaxIndex, uvMax); - resourceGroup->SetConstant(m_uvStepIndex, uvStep); + + ShaderTerrainData terrainDataForSrg; + terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing; + terrainDataForSrg.m_heightScale = m_areaData.m_heightScale; + terrainDataForSrg.m_uvMin = uvMin; + terrainDataForSrg.m_uvMax = uvMax; + terrainDataForSrg.m_uvStep = uvStep; + resourceGroup->SetConstant(m_terrainDataIndex, terrainDataForSrg); + resourceGroup->Compile(); drawPacketBuilder.AddShaderResourceGroup(resourceGroup->GetRHIShaderResourceGroup()); - AZ::RHI::DrawPacketBuilder::DrawRequest drawRequest; - drawRequest.m_listTag = m_drawListTag; - drawRequest.m_pipelineState = m_pipelineState.get(); - drawRequest.m_streamBufferViews = AZStd::array_view(&m_vertexBufferView, 1); - drawPacketBuilder.AddDrawItem(drawRequest); + auto addDrawItem = [&](ShaderState& shaderState) + { + AZ::RHI::DrawPacketBuilder::DrawRequest drawRequest; + drawRequest.m_listTag = shaderState.m_shader->GetDrawListTag(); + drawRequest.m_pipelineState = shaderState.m_pipelineState.get(); + drawRequest.m_streamBufferViews = AZStd::array_view(&m_vertexBufferView, 1); + drawPacketBuilder.AddDrawItem(drawRequest); + }; + + for (ShaderState& shaderState : m_shaderStates) + { + addDrawItem(shaderState); + } + //addDrawItem(m_shaderStates[ShaderType::Forward]); m_sectorData.emplace_back( drawPacketBuilder.End(), @@ -368,16 +388,16 @@ namespace Terrain uint16_t startIndex = (uint16_t)(m_gridVertices.size()); m_gridVertices.emplace_back(x0, y0, x0 / m_gridMeters, y0 / m_gridMeters); - m_gridVertices.emplace_back(x0, y1, x0 / m_gridMeters, y1 / m_gridMeters); m_gridVertices.emplace_back(x1, y0, x1 / m_gridMeters, y0 / m_gridMeters); + m_gridVertices.emplace_back(x0, y1, x0 / m_gridMeters, y1 / m_gridMeters); m_gridVertices.emplace_back(x1, y1, x1 / m_gridMeters, y1 / m_gridMeters); m_gridIndices.emplace_back(startIndex); m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); m_gridIndices.emplace_back(aznumeric_cast(startIndex + 3)); + m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); } } } @@ -441,8 +461,6 @@ namespace Terrain m_vertexBufferView = AZ::RHI::StreamBufferView( *buffer, 0, static_cast(elementSize), static_cast(sizeof(Vertex))); - - AZ::RHI::ValidateStreamBufferViews(m_pipelineStateDescriptor.m_inputStreamLayout, { { m_vertexBufferView } }); } m_hostPool->UnmapBuffer(*buffer); @@ -459,8 +477,9 @@ namespace Terrain m_indexBufferView = {}; m_vertexBufferView = {}; - m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; - m_pipelineState = nullptr; + for (ShaderState& shaderState : m_shaderStates) + { + shaderState.Reset(); + } } - } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 7a7b63b634..382f473b25 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -56,12 +56,45 @@ namespace Terrain } private: + + // System-level references to the shader, pipeline, and shader-related information + enum ShaderType + { + Depth, + Forward, + Count, + }; + + struct ShaderState + { + AZ::Data::Instance m_shader; + AZ::RHI::ConstPtr m_pipelineState; + AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor; + + void Reset() + { + m_shader.reset(); + m_pipelineState.reset(); + m_pipelineStateDescriptor = {}; + } + }; + + struct ShaderTerrainData // Must align with struct in Object Srg + { + AZStd::array m_uvMin; + AZStd::array m_uvMax; + AZStd::array m_uvStep; + float m_sampleSpacing; + float m_heightScale; + }; + // RPI::SceneNotificationBus overrides ... void OnRenderPipelineAdded(AZ::RPI::RenderPipelinePtr pipeline) override; void OnRenderPipelineRemoved(AZ::RPI::RenderPipeline* pipeline) override; void OnRenderPipelinePassesChanged(AZ::RPI::RenderPipeline* renderPipeline) override; void InitializeAtomStuff(); + void ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail); void InitializeTerrainPatch(); @@ -77,20 +110,11 @@ namespace Terrain // System-level cached reference to the Atom RHI AZ::RHI::RHISystemInterface* m_rhiSystem = nullptr; - // System-level references to the shader, pipeline, and shader-related information - AZ::Data::Instance m_shader{}; - AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor; - AZ::RHI::ConstPtr m_pipelineState = nullptr; - AZ::RHI::DrawListTag m_drawListTag; - AZ::RHI::Ptr m_perObjectSrgAsset; + AZStd::array m_shaderStates; AZ::RHI::ShaderInputImageIndex m_heightmapImageIndex; AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex; - AZ::RHI::ShaderInputConstantIndex m_heightScaleIndex; - AZ::RHI::ShaderInputConstantIndex m_uvMinIndex; - AZ::RHI::ShaderInputConstantIndex m_uvMaxIndex; - AZ::RHI::ShaderInputConstantIndex m_uvStepIndex; - + AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex; // Pos_float_2 + UV_float_2 struct Vertex @@ -125,11 +149,12 @@ namespace Terrain { AZ::Transform m_transform{ AZ::Transform::CreateIdentity() }; AZ::Aabb m_terrainBounds{ AZ::Aabb::CreateNull() }; - float m_heightScale; + float m_heightScale{ 0.0f }; AZ::Data::Instance m_heightmapImage; - uint32_t m_heightmapImageWidth; - uint32_t m_heightmapImageHeight; + uint32_t m_heightmapImageWidth{ 0 }; + uint32_t m_heightmapImageHeight{ 0 }; bool m_propertiesDirty{ true }; + float m_sampleSpacing{ 0.0f }; }; TerrainAreaData m_areaData;