diff --git a/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material b/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material index 095fbba21a..c0de5969b2 100644 --- a/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material +++ b/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material @@ -4,8 +4,27 @@ "parentMaterial": "", "propertyLayoutVersion": 1, "properties": { - "settings": { - "baseColor": [ 0.78, 0.59, 0.48 ] + "macroColor": { + "useTexture": false + }, + "macroNormal": { + "useTexture": false + }, + "baseColor": { + "color": [ 0.18, 0.18, 0.18 ], + "useTexture": false + }, + "normal": + { + "useTexture": false + }, + "roughness": + { + "useTexture": false + }, + "specularF0": + { + "useTexture": false } } } diff --git a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype index bf6cbbb850..191fa7a731 100644 --- a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype +++ b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype @@ -100,25 +100,269 @@ } }, { - "id": "baseColor", - "displayName": "Base Color", + "id": "detailTextureMultiplier", + "displayName": "Detail Texture UV Multiplier", + "description": "How many times to repeat the detail texture per sector", + "type": "Float", + "defaultValue": 8.0, + "connection": { + "type": "ShaderInput", + "id": "m_detailTextureMultiplier" + } + }, + { + "id": "detailFadeDistance", + "displayName": "Detail Fade Distance (meters)", + "description": "The distance in meters that the detail texture starts to fade", + "type": "Float", + "defaultValue": 100.0, + "connection": { + "type": "ShaderInput", + "id": "m_detailFadeDistance" + } + }, + { + "id": "detailFadeLength", + "displayName": "Detail Fade Length", + "description": "How far beyond Detail Fade Distance where the detail texture no longer applies.", + "type": "Float", + "defaultValue": 50.0, + "connection": { + "type": "ShaderInput", + "id": "m_detailFadeLength" + } + } + ], + "macroColor": [ + { + "id": "textureMap", + "displayName": "Texture", + "description": "Macro color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_macroColorMap" + } + }, + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + } + + ], + "macroNormal": [ + { + "id": "textureMap", + "displayName": "Texture", + "description": "Macro normal texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_macroNormalMap" + } + }, + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "id": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "id": "m_flipMacroNormalX" + } + }, + { + "id": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "id": "m_flipMacroNormalY" + } + } + ], + "baseColor": [ + { + "id": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", "type": "Color", - "defaultValue": [ 0.18, 0.18, 0.18 ], + "defaultValue": [ 1.0, 1.0, 1.0 ], "connection": { "type": "ShaderInput", "id": "m_baseColor" } }, { - "id": "roughness", - "displayName": "Roughness", + "id": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", "type": "Float", "defaultValue": 1.0, "min": 0.0, "max": 1.0, "connection": { "type": "ShaderInput", - "id": "m_roughness" + "id": "m_baseColorFactor" + } + }, + { + "id": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_baseColorMap" + } + }, + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "id": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Overlay", + "connection": { + "type": "ShaderOption", + "id": "o_baseColorTextureBlendMode" + } + } + ], + "normal": [ + { + "id": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_normalMap" + } + }, + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "id": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "id": "m_flipNormalX" + } + }, + { + "id": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "id": "m_flipNormalY" + } + }, + { + "id": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "id": "m_normalFactor" + } + } + ], + "roughness": [ + { + "id": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_roughnessMap" + } + }, + { + "id": "useTexture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "id": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "id": "m_roughnessFactor" + } + } + ], + "specularF0": [ + { + "id": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_specularF0Map" + } + }, + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "id": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "id": "m_specularF0Factor" } } ] @@ -136,5 +380,55 @@ } ], "functors": [ + { + "type": "UseTexture", + "args": { + "textureProperty": "macroColor.textureMap", + "useTextureProperty": "macroColor.useTexture", + "shaderOption": "o_macroColor_useTexture" + } + }, + { + "type": "UseTexture", + "args": { + "textureProperty": "macroNormal.textureMap", + "useTextureProperty": "macroNormal.useTexture", + "shaderOption": "o_macroNormal_useTexture" + } + }, + { + "type": "UseTexture", + "args": { + "textureProperty": "baseColor.textureMap", + "useTextureProperty": "baseColor.useTexture", + "dependentProperties": ["baseColor.textureBlendMode"], + "shaderOption": "o_baseColor_useTexture" + } + }, + { + "type": "UseTexture", + "args": { + "textureProperty": "specularF0.textureMap", + "useTextureProperty": "specularF0.useTexture", + "shaderOption": "o_specularF0_useTexture" + } + }, + { + "type": "UseTexture", + "args": { + "textureProperty": "normal.textureMap", + "useTextureProperty": "normal.useTexture", + "dependentProperties": ["normal.factor", "normal.flipX", "normal.flipY"], + "shaderOption": "o_normal_useTexture" + } + }, + { + "type": "UseTexture", + "args": { + "textureProperty": "roughness.textureMap", + "useTextureProperty": "roughness.useTexture", + "shaderOption": "o_roughness_useTexture" + } + } ] } diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli index 9df7b35ce8..95d1a73bbd 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli @@ -7,6 +7,12 @@ #pragma once +#include <../Materials/Types/MaterialInputs/BaseColorInput.azsli> +#include <../Materials/Types/MaterialInputs/RoughnessInput.azsli> +#include <../Materials/Types/MaterialInputs/MetallicInput.azsli> +#include <../Materials/Types/MaterialInputs/SpecularInput.azsli> +#include <../Materials/Types/MaterialInputs/NormalInput.azsli> + ShaderResourceGroup ObjectSrg : SRG_PerObject { row_major float3x4 m_modelToWorld; @@ -70,7 +76,10 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial { - Texture2D m_heightmapImage; + Texture2D m_heightmapImage; + float m_detailTextureMultiplier; + float m_detailFadeDistance; + float m_detailFadeLength; Sampler HeightmapSampler { @@ -82,11 +91,52 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial AddressW = Clamp; }; + Sampler m_sampler + { + AddressU = Wrap; + AddressV = Wrap; + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + MaxAnisotropy = 16; + }; + + // Macro Color + Texture2D m_macroColorMap; + + // Macro normal + Texture2D m_macroNormalMap; + bool m_flipMacroNormalX; + bool m_flipMacroNormalY; + + // Base Color float3 m_baseColor; - float m_roughness; + float m_baseColorFactor; + Texture2D m_baseColorMap; + + // Normal + Texture2D m_normalMap; + bool m_flipNormalX; + bool m_flipNormalY; + float m_normalFactor; + + // Roughness + Texture2D m_roughnessMap; + float m_roughnessFactor; + + // Specular + Texture2D m_specularF0Map; + float m_specularF0Factor; } option bool o_useTerrainSmoothing = false; +option bool o_macroColor_useTexture = true; +option bool o_macroNormal_useTexture = true; +option bool o_baseColor_useTexture = true; +option bool o_specularF0_useTexture = true; +option bool o_normal_useTexture = true; +option bool o_roughness_useTexture = true; +option TextureBlendMode o_baseColorTextureBlendMode = TextureBlendMode::Multiply; struct VertexInput { @@ -98,7 +148,7 @@ struct VertexInput // This function samples a 4x4 neighborhood around the uv. Normally this would take 16 samples, but by taking // advantage of bilinear filtering this can be done with 9 taps on the edges between pixels. The cost is further // reduced by dropping the diagonals. -float SampleBSpline5Tap(Texture2D texture, SamplerState textureSampler, float2 uv, float2 textureSize, float2 rcpTextureSize) +float SampleBSpline5Tap(Texture2D texture, SamplerState textureSampler, float2 uv, float2 textureSize, float2 rcpTextureSize) { // Think of sample locations in the 4x4 neighborhood as having a top left coordinate of 0,0 and // a bottom right coordinate of 3,3. diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index d77b944498..df2a801969 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -71,18 +71,49 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) Surface surface; - // Position, Normal, Roughness + // Position surface.position = IN.m_worldPosition.xyz; - surface.normal = normalize(IN.m_normal); - surface.roughnessLinear = TerrainMaterialSrg::m_roughness; + float viewDistance = length(ViewSrg::m_worldPosition - surface.position); + float detailFactor = saturate((viewDistance - TerrainMaterialSrg::m_detailFadeDistance) / max(TerrainMaterialSrg::m_detailFadeLength, EPSILON)); + + ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; + float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, IN.m_uv); + origUv.y = 1.0 - origUv.y; + float2 detailUv = IN.m_uv * TerrainMaterialSrg::m_detailTextureMultiplier; + + // ------- Normal ------- + float3 macroNormal = IN.m_normal; + if (o_macroNormal_useTexture) + { + macroNormal = GetNormalInputTS(TerrainMaterialSrg::m_macroNormalMap, TerrainMaterialSrg::m_sampler, + origUv, TerrainMaterialSrg::m_flipMacroNormalX, TerrainMaterialSrg::m_flipMacroNormalY, CreateIdentity3x3(), true, 1.0); + } + + float3 detailNormal = GetNormalInputTS(TerrainMaterialSrg::m_normalMap, TerrainMaterialSrg::m_sampler, + detailUv, TerrainMaterialSrg::m_flipNormalX, TerrainMaterialSrg::m_flipNormalY, CreateIdentity3x3(), o_normal_useTexture, TerrainMaterialSrg::m_normalFactor); + + detailNormal = ReorientTangentSpaceNormal(macroNormal, detailNormal); + surface.normal = lerp(detailNormal, macroNormal, detailFactor); + surface.normal = normalize(surface.normal); + + // ------- Macro Color ------- + float3 macroColor = GetBaseColorInput(TerrainMaterialSrg::m_macroColorMap, TerrainMaterialSrg::m_sampler, origUv, TerrainMaterialSrg::m_baseColor.rgb, o_baseColor_useTexture); + + // ------- Base Color ------- + float3 detailColor = GetBaseColorInput(TerrainMaterialSrg::m_baseColorMap, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_baseColor.rgb, o_baseColor_useTexture); + float3 blendedColor = BlendBaseColor(lerp(detailColor, TerrainMaterialSrg::m_baseColor.rgb, detailFactor), macroColor, TerrainMaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); + + // ------- Specular ------- + float specularF0Factor = GetSpecularInput(TerrainMaterialSrg::m_specularF0Map, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_specularF0Factor, o_specularF0_useTexture); + specularF0Factor = lerp(specularF0Factor, 0.5, detailFactor); + surface.SetAlbedoAndSpecularF0(blendedColor, specularF0Factor, 0.0); + + // ------- Roughness ------- + surface.roughnessLinear = GetRoughnessInput(TerrainMaterialSrg::m_roughnessMap, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_roughnessFactor, 0.0, 1.0, o_roughness_useTexture); + surface.roughnessLinear = lerp(surface.roughnessLinear, 1.0, detailFactor); surface.CalculateRoughnessA(); - // Albedo, SpecularF0 - const float specularF0Factor = 0.5f; - float3 color = TerrainMaterialSrg::m_baseColor; - surface.SetAlbedoAndSpecularF0(color, specularF0Factor, 0.0); - - // Clear Coat, Transmission + // Clear Coat, Transmission (Not used for terrain) surface.clearCoat.InitializeToZero(); surface.transmission.InitializeToZero(); diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp index 3bf34fa019..f3eed41717 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp @@ -28,16 +28,27 @@ namespace Terrain AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { - serialize->Class()->Version(1); + serialize->Class() + ->Version(1) + ->Field("WorldSize", &TerrainWorldRendererConfig::m_worldSize) + ; - AZ::EditContext* edit = serialize->GetEditContext(); - if (edit) + AZ::EditContext* editContext = serialize->GetEditContext(); + if (editContext) { - edit->Class("Terrain World Renderer Component", "Enables terrain rendering") + editContext->Class("Terrain World Renderer Component", "Enables terrain rendering") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true); + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainWorldRendererConfig::m_worldSize, "Rendered world size", "The maximum amount of terrain that's rendered") + ->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_512Meters, "512 Meters") + ->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_1024Meters, "1 Kilometer") + ->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_2048Meters, "2 Kilometers") + ->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_4096Meters, "4 Kilometers") + ->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_8192Meters, "8 Kilometers") + ->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_16384Meters, "16 Kilometers") + ; } } } @@ -72,6 +83,28 @@ namespace Terrain TerrainWorldRendererComponent::TerrainWorldRendererComponent(const TerrainWorldRendererConfig& configuration) : m_configuration(configuration) { + switch (configuration.m_worldSize) + { + case TerrainWorldRendererConfig::WorldSize::_512Meters: + m_terrainFeatureProcessor->SetWorldSize(AZ::Vector2(512.0f, 512.0f)); + break; + case TerrainWorldRendererConfig::WorldSize::_1024Meters: + m_terrainFeatureProcessor->SetWorldSize(AZ::Vector2(1024.0f, 1024.0f)); + break; + case TerrainWorldRendererConfig::WorldSize::_2048Meters: + m_terrainFeatureProcessor->SetWorldSize(AZ::Vector2(2048.0f, 2048.0f)); + break; + case TerrainWorldRendererConfig::WorldSize::_4096Meters: + m_terrainFeatureProcessor->SetWorldSize(AZ::Vector2(4096.0f, 4096.0f)); + break; + case TerrainWorldRendererConfig::WorldSize::_8192Meters: + m_terrainFeatureProcessor->SetWorldSize(AZ::Vector2(8192.0f, 8192.0f)); + break; + case TerrainWorldRendererConfig::WorldSize::_16384Meters: + m_terrainFeatureProcessor->SetWorldSize(AZ::Vector2(16384.0f, 16384.0f)); + break; + + } } TerrainWorldRendererComponent::~TerrainWorldRendererComponent() diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h index a1e9498be8..cd2a4ea5aa 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h @@ -27,13 +27,28 @@ namespace Terrain { class TerrainFeatureProcessor; - class TerrainWorldRendererConfig + struct TerrainWorldRendererConfig final : public AZ::ComponentConfig { - public: AZ_CLASS_ALLOCATOR(TerrainWorldRendererConfig, AZ::SystemAllocator, 0); AZ_RTTI(TerrainWorldRendererConfig, "{08C5863C-092D-4A69-8226-4978E4F6E343}", AZ::ComponentConfig); static void Reflect(AZ::ReflectContext* context); + + enum class WorldSize : uint8_t + { + Unknown, + + _512Meters, + _1024Meters, + _2048Meters, + _4096Meters, + _8192Meters, + _16384Meters, + + WorldSizeCount, + }; + + WorldSize m_worldSize; }; diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp index 2d42585fa5..3dd8d1e911 100644 --- a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp @@ -29,10 +29,10 @@ namespace Terrain editContext->Class( "Terrain World Renderer", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Category, "Terrain") - ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/TerrainWorldRenderer.svg") - ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg") - ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ->Attribute(AZ::Edit::Attributes::Category, "Terrain") + ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/TerrainWorldRenderer.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) ; } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 3e5f9a0608..59984a1b92 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -76,26 +76,24 @@ namespace Terrain void TerrainFeatureProcessor::Initialize() { - { - // Load the terrain material asynchronously - const AZStd::string materialFilePath = "Materials/Terrain/DefaultPbrTerrain.azmaterial"; - m_materialAssetLoader = AZStd::make_unique(); - *m_materialAssetLoader = AZ::RPI::AssetUtils::AsyncAssetLoader::Create(materialFilePath, 0u, - [&](AZ::Data::Asset assetData, bool success) -> void + // Load the terrain material asynchronously + const AZStd::string materialFilePath = "Materials/Terrain/DefaultPbrTerrain.azmaterial"; + m_materialAssetLoader = AZStd::make_unique(); + *m_materialAssetLoader = AZ::RPI::AssetUtils::AsyncAssetLoader::Create(materialFilePath, 0u, + [&](AZ::Data::Asset assetData, bool success) -> void + { + const AZ::Data::Asset& materialAsset = static_cast>(assetData); + if (success) { - const AZ::Data::Asset& materialAsset = static_cast>(assetData); - if (success) + m_materialInstance = AZ::RPI::Material::FindOrCreate(assetData); + AZ::RPI::MaterialReloadNotificationBus::Handler::BusConnect(materialAsset->GetId()); + if (!materialAsset->GetObjectSrgLayout()) { - m_materialInstance = AZ::RPI::Material::FindOrCreate(assetData); - if (!materialAsset->GetObjectSrgLayout()) - { - AZ_Error("TerrainFeatureProcessor", false, "No per-object ShaderResourceGroup found on terrain material."); - } + AZ_Error("TerrainFeatureProcessor", false, "No per-object ShaderResourceGroup found on terrain material."); } } - ); - } - + } + ); if (!InitializePatchModel()) { AZ_Error(TerrainFPName, false, "Failed to create Terrain render buffers!"); @@ -105,10 +103,9 @@ namespace Terrain void TerrainFeatureProcessor::Deactivate() { - DisableSceneNotification(); - m_patchModel = {}; m_areaData = {}; + AZ::RPI::MaterialReloadNotificationBus::Handler::BusDisconnect(); } void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) @@ -291,7 +288,7 @@ namespace Terrain AZ::Vector2 sectorCenterXY = AZ::Vector2(sectorData.m_aabb.GetCenter().GetX(), sectorData.m_aabb.GetCenter().GetY()); float sectorDistance = sectorCenterXY.GetDistance(cameraPositionXY); - float lodForCamera = ceilf(AZ::GetMax(0.0f, log2f(sectorDistance / (GridMeters * 4.0f)))); + float lodForCamera = floorf(AZ::GetMax(0.0f, log2f(sectorDistance / (GridMeters * 4.0f)))); lodChoice = AZ::GetMin(lodChoice, aznumeric_cast(lodForCamera)); } } @@ -317,6 +314,7 @@ namespace Terrain uint16_t gridVertices = gridSize + 1; // For m_gridSize quads, (m_gridSize + 1) vertices are needed. size_t size = gridVertices * gridVertices; + size *= size; patchdata.m_positions.reserve(size); patchdata.m_uvs.reserve(size); @@ -432,4 +430,21 @@ namespace Terrain return success; } + + void TerrainFeatureProcessor::OnMaterialReinitialized([[maybe_unused]] const AZ::Data::Instance& material) + { + for (auto& sectorData : m_sectorData) + { + for (auto& drawPacket : sectorData.m_drawPackets) + { + drawPacket.Update(*GetParentScene()); + } + } + } + + void TerrainFeatureProcessor::SetWorldSize([[maybe_unused]] AZ::Vector2 sizeInMeters) + { + // This will control the max rendering size. Actual terrain size can be much + // larger but this will limit how much is rendered. + } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 160d3113c2..32f88565c2 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace AZ::RPI { @@ -41,6 +42,7 @@ namespace Terrain { class TerrainFeatureProcessor final : public AZ::RPI::FeatureProcessor + , private AZ::RPI::MaterialReloadNotificationBus::Handler { public: AZ_RTTI(TerrainFeatureProcessor, "{D7DAC1F9-4A9F-4D3C-80AE-99579BF8AB1C}", AZ::RPI::FeatureProcessor); @@ -52,12 +54,18 @@ namespace Terrain TerrainFeatureProcessor() = default; ~TerrainFeatureProcessor() = default; - ////////////////////////////////////////////////////////////////////////// - // AZ::Component interface implementation + // AZ::Component overrides... void Activate() override; void Deactivate() override; + + // AZ::RPI::FeatureProcessor overrides... void Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) override; + // AZ::RPI::MaterialReloadNotificationBus::Handler overrides... + void OnMaterialReinitialized(const AZ::Data::Instance& material) override; + + void SetWorldSize(AZ::Vector2 sizeInMeters); + void UpdateTerrainData(const AZ::Transform& transform, const AZ::Aabb& worldBounds, float sampleSpacing, uint32_t width, uint32_t height, const AZStd::vector& heightData);