Various terrain improvements: (#3942)
* Various terrain improvements: - Height now stored in a R16_unorm to decrease the amount of memory / memory bandwidth needed for the height field - Many simplifications to the feature processor - Added a shader to render to the depth pre pass - Pulled common functionality needed by depth and forward to a common include shader - Forward shader now outputs to all the expected render targets - Adjusted the way normals and lighting are being calculated Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Adding missing shader files. Updated terrain shader to alter the color slightly with height. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Removed pixel shader code from terrain depth pass Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Renamed the depth pass shaders to no longer indicate they include a pixel shader. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Removing unneeded code from TerrainCommon.azsli Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
-1
@@ -35,4 +35,4 @@ float ApplyReceiverPlaneDepthBias(const float2 receiverPlaneDepthBias, const flo
|
||||
// Clamping this will remove this effect
|
||||
fragmentDepthBias = min(fragmentDepthBias, 0.0);
|
||||
return fragmentDepthBias;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,120 +6,90 @@
|
||||
*/
|
||||
|
||||
#include <Atom/Features/SrgSemantics.azsli>
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
|
||||
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<float4> 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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<float> 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);
|
||||
}
|
||||
@@ -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 <Atom/Features/SrgSemantics.azsli>
|
||||
#include <viewsrg.srgi>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"Source" : "./Terrain_DepthPass.azsl",
|
||||
|
||||
"DepthStencilState" :
|
||||
{
|
||||
"Depth" :
|
||||
{
|
||||
"Enable" : true,
|
||||
"CompareFunc" : "GreaterEqual"
|
||||
}
|
||||
},
|
||||
|
||||
"DrawList" : "depth",
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<AZ::RHI::ShaderResourceGroupLayout> 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<float>& 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<uint16_t> 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<uint16_t>(clampedSample * MaxUint16));
|
||||
}
|
||||
|
||||
AZ::Data::Instance<AZ::RPI::StreamingImagePool> 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<float, 2> uvMin = { 0.0f, 0.0f };
|
||||
AZStd::array<float, 2> 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<float, 2> 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<AZ::RHI::StreamBufferView>(&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<AZ::RHI::StreamBufferView>(&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<uint16_t>(startIndex + 1));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 1));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 3));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,8 +461,6 @@ namespace Terrain
|
||||
|
||||
m_vertexBufferView = AZ::RHI::StreamBufferView(
|
||||
*buffer, 0, static_cast<uint32_t>(elementSize), static_cast<uint32_t>(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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<AZ::RPI::Shader> m_shader;
|
||||
AZ::RHI::ConstPtr<AZ::RHI::PipelineState> 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<float, 2> m_uvMin;
|
||||
AZStd::array<float, 2> m_uvMax;
|
||||
AZStd::array<float, 2> 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<AZ::RPI::Shader> m_shader{};
|
||||
AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor;
|
||||
AZ::RHI::ConstPtr<AZ::RHI::PipelineState> m_pipelineState = nullptr;
|
||||
AZ::RHI::DrawListTag m_drawListTag;
|
||||
AZ::RHI::Ptr<AZ::RHI::ShaderResourceGroupLayout> m_perObjectSrgAsset;
|
||||
AZStd::array<ShaderState, ShaderType::Count> 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<AZ::RPI::StreamingImage> 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;
|
||||
|
||||
Reference in New Issue
Block a user