Merge branch 'development' into Atom/guthadam/material_editor_replace_modified_color_with_indicator
This commit is contained in:
@@ -381,12 +381,22 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
//orphan any children that remain attached to the entity
|
||||
auto children = entityInfo.GetChildren();
|
||||
for (auto childId : children)
|
||||
bool isPrefabSystemEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
||||
|
||||
// For slices, orphan any children that remain attached to the entity
|
||||
// For prefabs, this is an unneeded operation because the prefab system handles the orphans
|
||||
// and the extra reparenting operation can be problematic for consumers subscribed to entity
|
||||
// events, such as the entity outliner.
|
||||
if (!isPrefabSystemEnabled)
|
||||
{
|
||||
ReparentChild(childId, AZ::EntityId(), entityId);
|
||||
m_entityOrphanTable[entityId].insert(childId);
|
||||
auto children = entityInfo.GetChildren();
|
||||
for (auto childId : children)
|
||||
{
|
||||
ReparentChild(childId, AZ::EntityId(), entityId);
|
||||
m_entityOrphanTable[entityId].insert(childId);
|
||||
}
|
||||
}
|
||||
|
||||
m_savedOrderInfo[entityId] = AZStd::make_pair(entityInfo.GetParent(), entityInfo.GetIndexForSorting());
|
||||
|
||||
+2
@@ -56,5 +56,7 @@ namespace AzToolsFramework
|
||||
virtual void StopPlayInEditor() = 0;
|
||||
|
||||
virtual void CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) = 0;
|
||||
|
||||
virtual bool IsRootPrefabAssigned() const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
+9
@@ -79,6 +79,8 @@ namespace AzToolsFramework
|
||||
|
||||
void PrefabEditorEntityOwnershipService::Reset()
|
||||
{
|
||||
m_isRootPrefabAssigned = false;
|
||||
|
||||
if (m_rootInstance)
|
||||
{
|
||||
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
|
||||
@@ -203,6 +205,7 @@ namespace AzToolsFramework
|
||||
m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GenerateRelativePath(filename));
|
||||
m_rootInstance->SetContainerEntityName("Level");
|
||||
m_prefabSystemComponent->PropagateTemplateChanges(templateId);
|
||||
m_isRootPrefabAssigned = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -302,6 +305,12 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
m_prefabSystemComponent->PropagateTemplateChanges(templateId);
|
||||
m_isRootPrefabAssigned = true;
|
||||
}
|
||||
|
||||
bool PrefabEditorEntityOwnershipService::IsRootPrefabAssigned() const
|
||||
{
|
||||
return m_isRootPrefabAssigned;
|
||||
}
|
||||
|
||||
Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::CreatePrefab(
|
||||
|
||||
+2
@@ -167,6 +167,7 @@ namespace AzToolsFramework
|
||||
void StopPlayInEditor() override;
|
||||
|
||||
void CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) override;
|
||||
bool IsRootPrefabAssigned() const override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -215,5 +216,6 @@ namespace AzToolsFramework
|
||||
Prefab::PrefabLoaderInterface* m_loaderInterface;
|
||||
AzFramework::EntityContextId m_entityContextId;
|
||||
AZ::SerializeContext m_serializeContext;
|
||||
bool m_isRootPrefabAssigned = false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -329,6 +329,11 @@ namespace AzToolsFramework
|
||||
return AZ::Failure(AZStd::string("Could not instantiate prefab - internal error "
|
||||
"(PrefabEditorEntityOwnershipInterface unavailable)."));
|
||||
}
|
||||
if (!prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Could not instantiate prefab - no root prefab assigned. "
|
||||
"Currently, prefabs can only be instantiated inside a level"));
|
||||
}
|
||||
|
||||
InstanceOptionalReference instanceToParentUnder;
|
||||
|
||||
|
||||
+7
-7
@@ -82,6 +82,8 @@ namespace AzToolsFramework
|
||||
, m_entityExpansionState()
|
||||
, m_entityFilteredState()
|
||||
{
|
||||
m_focusModeInterface = AZ::Interface<FocusModeInterface>::Get();
|
||||
AZ_Assert(m_focusModeInterface != nullptr, "EntityOutlinerListModel requires a FocusModeInterface instance on construction.");
|
||||
}
|
||||
|
||||
EntityOutlinerListModel::~EntityOutlinerListModel()
|
||||
@@ -106,11 +108,6 @@ namespace AzToolsFramework
|
||||
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
|
||||
AZ_Assert(m_editorEntityUiInterface != nullptr,
|
||||
"EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize.");
|
||||
|
||||
m_focusModeInterface = AZ::Interface<FocusModeInterface>::Get();
|
||||
AZ_Assert(
|
||||
m_focusModeInterface != nullptr,
|
||||
"EntityOutlinerListModel requires a FocusModeInterface instance on Initialize.");
|
||||
}
|
||||
|
||||
int EntityOutlinerListModel::rowCount(const QModelIndex& parent) const
|
||||
@@ -1347,7 +1344,10 @@ namespace AzToolsFramework
|
||||
//add/remove operations trigger selection change signals which assert and break undo/redo operations in progress in inspector etc.
|
||||
//so disallow selection updates until change is complete
|
||||
emit EnableSelectionUpdates(false);
|
||||
beginResetModel();
|
||||
|
||||
auto parentIndex = GetIndexFromEntity(parentId);
|
||||
auto childIndex = GetIndexFromEntity(childId);
|
||||
beginRemoveRows(parentIndex, childIndex.row(), childIndex.row());
|
||||
}
|
||||
|
||||
void EntityOutlinerListModel::OnEntityInfoUpdatedRemoveChildEnd(AZ::EntityId parentId, AZ::EntityId childId)
|
||||
@@ -1355,7 +1355,7 @@ namespace AzToolsFramework
|
||||
(void)childId;
|
||||
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
||||
|
||||
endResetModel();
|
||||
endRemoveRows();
|
||||
|
||||
//must refresh partial lock/visibility of parents
|
||||
m_isFilterDirty = true;
|
||||
|
||||
@@ -232,6 +232,9 @@ namespace AzToolsFramework
|
||||
m_gui->m_objectTree->header()->setSortIndicatorShown(false);
|
||||
m_gui->m_objectTree->header()->setStretchLastSection(false);
|
||||
|
||||
// Always expand root entity (level entity) - needed if the widget is re-created while a level is already open.
|
||||
m_gui->m_objectTree->expand(m_proxyModel->index(0, 0));
|
||||
|
||||
// resize the icon columns so that the Visibility and Lock toggle icon columns stay right-justified
|
||||
m_gui->m_objectTree->header()->setStretchLastSection(false);
|
||||
m_gui->m_objectTree->header()->setMinimumSectionSize(0);
|
||||
|
||||
@@ -358,8 +358,9 @@ namespace O3DE::ProjectManager
|
||||
painter.drawPixmap(backgroundRect, m_background);
|
||||
|
||||
// Draw a semi-transparent overlay to darken down the colors.
|
||||
painter.setCompositionMode (QPainter::CompositionMode_DestinationIn);
|
||||
const float overlayTransparency = 0.7f;
|
||||
// Use SourceOver, DestinationIn will make background transparent on Mac
|
||||
painter.setCompositionMode (QPainter::CompositionMode_SourceOver);
|
||||
const float overlayTransparency = 0.3f;
|
||||
painter.fillRect(backgroundRect, QColor(0, 0, 0, static_cast<int>(255.0f * overlayTransparency)));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContextConstants.inl>
|
||||
|
||||
#include <Atom/Feature/RenderCommon.h>
|
||||
#include <Atom/Feature/CoreLights/EsmShadowmapsPassData.h>
|
||||
#include <Atom/Feature/CoreLights/PhotometricValue.h>
|
||||
#include <Atom/Feature/CoreLights/ShadowConstants.h>
|
||||
@@ -33,8 +34,6 @@
|
||||
#include <CoreLights/ShadowmapPass.h>
|
||||
#include <CoreLights/ProjectedShadowmapsPass.h>
|
||||
|
||||
#include <RenderCommon.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <RenderCommon.h>
|
||||
|
||||
#include <Atom/RHI/RHIUtils.h>
|
||||
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
|
||||
#include <Atom/Feature/RenderCommon.h>
|
||||
#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
|
||||
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
||||
#include <Atom/RPI.Public/Model/ModelLodUtils.h>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <RenderCommon.h>
|
||||
#include <Atom/Feature/RenderCommon.h>
|
||||
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
|
||||
#include <Atom/RPI.Public/Base.h>
|
||||
#include <Atom/RPI.Public/Model/Model.h>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Include/Atom/Feature/RenderCommon.h
|
||||
Include/Atom/Feature/Utils/EditorRenderComponentAdapter.h
|
||||
Include/Atom/Feature/Utils/EditorRenderComponentAdapter.inl
|
||||
Include/Atom/Feature/Utils/EditorLightingPreset.h
|
||||
@@ -16,7 +17,6 @@ set(FILES
|
||||
Source/EditorCommonSystemComponent.cpp
|
||||
Source/EditorCommonSystemComponent.h
|
||||
Source/CommonModule.cpp
|
||||
Source/RenderCommon.h
|
||||
Source/Material/ConvertEmissiveUnitFunctorSourceData.cpp
|
||||
Source/Material/ConvertEmissiveUnitFunctorSourceData.h
|
||||
Source/Material/MaterialConverterSystemComponent.cpp
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
set(FILES
|
||||
3rdParty/ACES/ACES/Aces.h
|
||||
Include/Atom/Feature/RenderCommon.h
|
||||
Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h
|
||||
Include/Atom/Feature/Automation/AtomAutomationBus.h
|
||||
Include/Atom/Feature/AuxGeom/AuxGeomFeatureProcessor.h
|
||||
@@ -52,7 +53,6 @@ set(FILES
|
||||
Source/FrameCaptureSystemComponent.h
|
||||
Source/ProfilingCaptureSystemComponent.cpp
|
||||
Source/ProfilingCaptureSystemComponent.h
|
||||
Source/RenderCommon.h
|
||||
3rdParty/ACES/ACES/Aces.cpp
|
||||
Source/ACES/AcesDisplayMapperFeatureProcessor.cpp
|
||||
Source/AuxGeom/AuxGeomBase.h
|
||||
|
||||
@@ -31,20 +31,26 @@ namespace AtomToolsFramework
|
||||
|
||||
void InspectorWidget::AddHeading(QWidget* headingWidget)
|
||||
{
|
||||
headingWidget->setParent(m_ui->m_headingSection);
|
||||
m_ui->m_headingSectionLayout->addWidget(headingWidget);
|
||||
headingWidget->setParent(this);
|
||||
m_ui->m_headingContentsLayout->addWidget(headingWidget);
|
||||
}
|
||||
|
||||
void InspectorWidget::ClearHeading()
|
||||
{
|
||||
qDeleteAll(m_ui->m_headingSection->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly));
|
||||
qDeleteAll(m_ui->m_headingSectionLayout->children());
|
||||
while (QLayoutItem* child = m_ui->m_headingContentsLayout->takeAt(0))
|
||||
{
|
||||
delete child->widget();
|
||||
delete child;
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorWidget::Reset()
|
||||
{
|
||||
qDeleteAll(m_ui->m_groupContents->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly));
|
||||
qDeleteAll(m_ui->m_groupContentsLayout->children());
|
||||
while (QLayoutItem* child = m_ui->m_groupContentsLayout->takeAt(0))
|
||||
{
|
||||
delete child->widget();
|
||||
delete child;
|
||||
}
|
||||
m_groups.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,104 +30,85 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_sections">
|
||||
<layout class="QVBoxLayout" name="m_sectionsLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_headingSection" native="true">
|
||||
<layout class="QVBoxLayout" name="m_headingSectionLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
<layout class="QVBoxLayout" name="m_headingContentsLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="m_groupSectionLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="m_groupScrollArea">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="m_groupSection" native="true">
|
||||
<layout class="QVBoxLayout" name="m_groupSectionLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="m_groupScrollArea">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
<widget class="QWidget" name="m_groupContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>679</width>
|
||||
<height>767</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="m_groupContentsLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="m_groupContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>683</width>
|
||||
<height>763</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="m_groupContentsLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "PbrTerrain.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 1,
|
||||
"properties": {
|
||||
"settings": {
|
||||
"baseColor": [ 0.78, 0.59, 0.48 ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"description": "A material for rendering terrain with a physically-based rendering (PBR) material shading model.",
|
||||
"propertyLayout": {
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"id": "settings",
|
||||
"displayName": "Settings"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"id": "applySpecularAA",
|
||||
"displayName": "Apply Specular AA",
|
||||
"description": "Whether to apply specular anti-aliasing in the shader.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_applySpecularAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enableShadows",
|
||||
"displayName": "Enable Shadows",
|
||||
"description": "Whether to use the shadow maps.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_enableShadows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enableDirectionalLights",
|
||||
"displayName": "Enable Directional Lights",
|
||||
"description": "Whether to use directional lights.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_enableDirectionalLights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enablePunctualLights",
|
||||
"displayName": "Enable Punctual Lights",
|
||||
"description": "Whether to use punctual lights.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_enablePunctualLights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enableAreaLights",
|
||||
"displayName": "Enable Area Lights",
|
||||
"description": "Whether to use area lights.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_enableAreaLights"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "enableIBL",
|
||||
"displayName": "Enable IBL",
|
||||
"description": "Whether to use Image Based Lighting (IBL).",
|
||||
"type": "Bool",
|
||||
"defaultValue": true,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_enableIBL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "forwardPassIBLSpecular",
|
||||
"displayName": "Forward Pass IBL Specular",
|
||||
"description": "Whether to apply IBL specular in the forward pass.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "ShaderOption",
|
||||
"id": "o_materialUseForwardPassIBLSpecular"
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"id": "heightmapImage",
|
||||
"displayName": "Heightmap Image",
|
||||
"description": "Heightmap of the terrain, controlled by the runtime.",
|
||||
"type": "Image",
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_heightmapImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "baseColor",
|
||||
"displayName": "Base Color",
|
||||
"type": "Color",
|
||||
"defaultValue": [ 0.18, 0.18, 0.18 ],
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_baseColor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "roughness",
|
||||
"displayName": "Roughness",
|
||||
"type": "Float",
|
||||
"defaultValue": 1.0,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_roughness"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"shaders": [
|
||||
{
|
||||
"file": "../../Shaders/Terrain/TerrainPBR_ForwardPass.shader"
|
||||
},
|
||||
{
|
||||
"file": "../../Shaders/Terrain/Terrain_Shadowmap.shader"
|
||||
},
|
||||
{
|
||||
"file": "../../Shaders/Terrain/Terrain_DepthPass.shader"
|
||||
}
|
||||
],
|
||||
"functors": [
|
||||
]
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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 <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
#include "TerrainCommon.azsli"
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
linear centroid float4 m_position : SV_Position;
|
||||
float3 m_normal : NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float m_height : UV;
|
||||
};
|
||||
|
||||
struct ForwardPassOutput
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(in VertexInput input)
|
||||
{
|
||||
VertexOutput output;
|
||||
ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData;
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
output.m_diffuseColor.rgb = 0.5 * lightIntensity;
|
||||
output.m_diffuseColor.a = 0.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,33 +0,0 @@
|
||||
{
|
||||
"Source" : "./Terrain.azsl",
|
||||
|
||||
"DepthStencilState" :
|
||||
{
|
||||
"Depth" :
|
||||
{
|
||||
"Enable" : true,
|
||||
"CompareFunc" : "GreaterEqual"
|
||||
}
|
||||
},
|
||||
|
||||
"DrawList" : "forward",
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
},
|
||||
{
|
||||
"name": "MainPS",
|
||||
"type": "Fragment"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"BlendState" : {
|
||||
"Enable" : false
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,6 @@
|
||||
|
||||
ShaderResourceGroup ObjectSrg : SRG_PerObject
|
||||
{
|
||||
Texture2D<float> m_heightmapImage;
|
||||
|
||||
Sampler PointSampler
|
||||
{
|
||||
MinFilter = Point;
|
||||
MagFilter = Point;
|
||||
MipFilter = Point;
|
||||
AddressU = Clamp;
|
||||
AddressV = Clamp;
|
||||
AddressW = Clamp;
|
||||
};
|
||||
|
||||
row_major float3x4 m_modelToWorld;
|
||||
|
||||
struct TerrainData
|
||||
@@ -33,14 +21,131 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
|
||||
};
|
||||
|
||||
TerrainData m_terrainData;
|
||||
|
||||
// The below shouldn't be in this SRG but needs to be for now because the lighting functions depend on them.
|
||||
|
||||
//! Reflection Probe (smallest probe volume that overlaps the object position)
|
||||
struct ReflectionProbeData
|
||||
{
|
||||
row_major float3x4 m_modelToWorld;
|
||||
row_major float3x4 m_modelToWorldInverse; // does not include extents
|
||||
float3 m_outerObbHalfLengths;
|
||||
float3 m_innerObbHalfLengths;
|
||||
float m_padding;
|
||||
bool m_useReflectionProbe;
|
||||
bool m_useParallaxCorrection;
|
||||
};
|
||||
|
||||
ReflectionProbeData m_reflectionProbeData;
|
||||
TextureCube m_reflectionProbeCubeMap;
|
||||
|
||||
float4x4 GetReflectionProbeWorldMatrix()
|
||||
{
|
||||
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] = m_reflectionProbeData.m_modelToWorld[0];
|
||||
modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1];
|
||||
modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2];
|
||||
return modelToWorld;
|
||||
}
|
||||
|
||||
float4x4 GetReflectionProbeWorldMatrixInverse()
|
||||
{
|
||||
float4x4 modelToWorldInverse = float4x4(
|
||||
float4(1, 0, 0, 0),
|
||||
float4(0, 1, 0, 0),
|
||||
float4(0, 0, 1, 0),
|
||||
float4(0, 0, 0, 1));
|
||||
|
||||
modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0];
|
||||
modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1];
|
||||
modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2];
|
||||
return modelToWorldInverse;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
|
||||
{
|
||||
Texture2D<float> m_heightmapImage;
|
||||
|
||||
Sampler HeightmapSampler
|
||||
{
|
||||
MinFilter = Linear;
|
||||
MagFilter = Linear;
|
||||
MipFilter = Point;
|
||||
AddressU = Clamp;
|
||||
AddressV = Clamp;
|
||||
AddressW = Clamp;
|
||||
};
|
||||
|
||||
float3 m_baseColor;
|
||||
float m_roughness;
|
||||
}
|
||||
|
||||
option bool o_useTerrainSmoothing = false;
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float2 m_position : POSITION;
|
||||
float2 m_uv : UV;
|
||||
};
|
||||
|
||||
// Sample a texture with a 5 tap B-Spline. Consider ripping this out and putting in a more general location.
|
||||
// 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<float> 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.
|
||||
|
||||
// Find the position in texture space then round it to get the center of the 1,1 pixel (tc1)
|
||||
float2 texelPos = uv * textureSize;
|
||||
float2 tc1= floor(texelPos - 0.5) + 0.5;
|
||||
|
||||
// Offset from center position to texel
|
||||
float2 f = texelPos - tc1;
|
||||
|
||||
// Compute B-Spline weights based on the offset
|
||||
float2 OneMinusF = (1.0 - f);
|
||||
float2 OneMinusF2 = OneMinusF * OneMinusF;
|
||||
float2 OneMinusF3 = OneMinusF2 * OneMinusF;
|
||||
float2 w0 = OneMinusF3;
|
||||
float2 w1 = 4.0 + 3.0 * f * f * f - 6.0 * f * f;
|
||||
float2 w2 = 4.0 + 3.0 * OneMinusF3 - 6.0 * OneMinusF2;
|
||||
float2 w3 = f * f * f;
|
||||
|
||||
float2 w12 = w1 + w2;
|
||||
|
||||
// Compute uv coordinates for sampling the texture
|
||||
float2 tc0 = (tc1 - 1.0f) * rcpTextureSize;
|
||||
float2 tc3 = (tc1 + 2.0f) * rcpTextureSize;
|
||||
float2 tc12 = (tc1 + w2 / w12) * rcpTextureSize;
|
||||
|
||||
// Compute sample weights
|
||||
float sw0 = w12.x * w12.y; // middle
|
||||
float sw1 = w12.x * w0.y; // top
|
||||
float sw2 = w0.x * w12.y; // left
|
||||
float sw3 = w12.x * w3.y; // bottom
|
||||
float sw4 = w3.x * w12.y; // right
|
||||
|
||||
// total weight of samples to normalize result.
|
||||
float totalWeight = sw0 + sw1 + sw2 + sw3 + sw4;
|
||||
|
||||
float result = 0.0f;
|
||||
result += texture.SampleLevel(textureSampler, float2(tc12.x, tc12.y), 0.0).r * sw0;
|
||||
result += texture.SampleLevel(textureSampler, float2(tc12.x, tc0.y), 0.0).r * sw1;
|
||||
result += texture.SampleLevel(textureSampler, float2( tc0.x, tc12.y), 0.0).r * sw2;
|
||||
result += texture.SampleLevel(textureSampler, float2(tc12.x, tc3.y), 0.0).r * sw3;
|
||||
result += texture.SampleLevel(textureSampler, float2( tc3.x, tc12.y), 0.0).r * sw4;
|
||||
|
||||
return result / totalWeight;
|
||||
}
|
||||
|
||||
float4x4 GetObject_WorldMatrix()
|
||||
{
|
||||
float4x4 modelToWorld = float4x4(
|
||||
@@ -58,10 +163,23 @@ float4x4 GetObject_WorldMatrix()
|
||||
float GetHeight(float2 origUv)
|
||||
{
|
||||
float2 uv = clamp(origUv + (ObjectSrg::m_terrainData.m_uvStep * 0.5f), 0.0f, 1.0f);
|
||||
return ObjectSrg::m_terrainData.m_heightScale * (ObjectSrg::m_heightmapImage.SampleLevel(ObjectSrg::PointSampler, uv, 0).r - 0.5f);
|
||||
float height = 0.0f;
|
||||
|
||||
if (o_useTerrainSmoothing)
|
||||
{
|
||||
float2 textureSize;
|
||||
TerrainMaterialSrg::m_heightmapImage.GetDimensions(textureSize.x, textureSize.y);
|
||||
height = SampleBSpline5Tap(TerrainMaterialSrg::m_heightmapImage, TerrainMaterialSrg::HeightmapSampler, uv, textureSize, rcp(textureSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
height = TerrainMaterialSrg::m_heightmapImage.SampleLevel(TerrainMaterialSrg::HeightmapSampler, uv, 0).r;
|
||||
}
|
||||
|
||||
return ObjectSrg::m_terrainData.m_heightScale * (height - 0.5f);
|
||||
}
|
||||
|
||||
float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv)
|
||||
float3 GetTerrainWorldPosition(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))
|
||||
@@ -71,6 +189,10 @@ float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 ve
|
||||
|
||||
// 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);
|
||||
return mul(GetObject_WorldMatrix(), float4(vertexPosition, height, 1.0f)).xyz;
|
||||
}
|
||||
|
||||
float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv)
|
||||
{
|
||||
return mul(ViewSrg::m_viewProjectionMatrix, float4(GetTerrainWorldPosition(terrainData, vertexPosition, uv), 1.0));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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>
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
|
||||
#include <Atom/Features/PBR/ForwardPassOutput.azsli>
|
||||
#include <Atom/Features/PBR/AlphaUtils.azsli>
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
#include <Atom/Features/PBR/Lighting/StandardLighting.azsli>
|
||||
#include <Atom/Features/Shadow/DirectionalLightShadow.azsli>
|
||||
#include <Atom/Features/PBR/Decals.azsli>
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
float3 m_normal: NORMAL;
|
||||
float3 m_tangent : TANGENT;
|
||||
float3 m_bitangent : BITANGENT;
|
||||
float3 m_worldPosition : UV0;
|
||||
float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2;
|
||||
float2 m_uv : UV1;
|
||||
};
|
||||
|
||||
VSOutput TerrainPBR_MainPassVS(VertexInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData;
|
||||
|
||||
float2 uv = IN.m_uv;
|
||||
float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, uv);
|
||||
float3 worldPosition = GetTerrainWorldPosition(terrainData, IN.m_position, origUv);
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
OUT.m_worldPosition = worldPosition;
|
||||
|
||||
// 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));
|
||||
|
||||
OUT.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up));
|
||||
OUT.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left));
|
||||
OUT.m_normal = cross(OUT.m_tangent, OUT.m_bitangent);
|
||||
OUT.m_uv = uv;
|
||||
|
||||
// directional light shadow
|
||||
const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight;
|
||||
if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount)
|
||||
{
|
||||
DirectionalLightShadow::GetShadowCoords(
|
||||
shadowIndex,
|
||||
worldPosition,
|
||||
OUT.m_shadowCoords);
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
|
||||
{
|
||||
// ------- Surface -------
|
||||
|
||||
Surface surface;
|
||||
|
||||
// Position, Normal, Roughness
|
||||
surface.position = IN.m_worldPosition.xyz;
|
||||
surface.normal = normalize(IN.m_normal);
|
||||
surface.roughnessLinear = TerrainMaterialSrg::m_roughness;
|
||||
surface.CalculateRoughnessA();
|
||||
|
||||
// Albedo, SpecularF0
|
||||
const float specularF0Factor = 0.5f;
|
||||
float3 color = TerrainMaterialSrg::m_baseColor;
|
||||
surface.SetAlbedoAndSpecularF0(color, specularF0Factor, 0.0);
|
||||
|
||||
// Clear Coat, Transmission
|
||||
surface.clearCoat.InitializeToZero();
|
||||
surface.transmission.InitializeToZero();
|
||||
|
||||
// ------- LightingData -------
|
||||
|
||||
LightingData lightingData;
|
||||
|
||||
// Light iterator
|
||||
lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData);
|
||||
lightingData.Init(surface.position, surface.normal, surface.roughnessLinear);
|
||||
|
||||
// Shadow, Occlusion
|
||||
lightingData.shadowCoords = IN.m_shadowCoords;
|
||||
|
||||
// Diffuse and Specular response
|
||||
lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear);
|
||||
lightingData.diffuseResponse = 1.0f - lightingData.specularResponse;
|
||||
|
||||
const float alpha = 1.0f;
|
||||
|
||||
// ------- Lighting Calculation -------
|
||||
|
||||
// Apply Decals
|
||||
ApplyDecals(lightingData.tileIterator, surface);
|
||||
|
||||
// Apply Direct Lighting
|
||||
ApplyDirectLighting(surface, lightingData);
|
||||
|
||||
// Apply Image Based Lighting (IBL)
|
||||
ApplyIBL(surface, lightingData);
|
||||
|
||||
// Finalize Lighting
|
||||
lightingData.FinalizeLighting(surface.transmission.tint);
|
||||
|
||||
PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
|
||||
|
||||
// ------- Output -------
|
||||
|
||||
ForwardPassOutput OUT;
|
||||
|
||||
OUT.m_diffuseColor = lightingOutput.m_diffuseColor;
|
||||
OUT.m_diffuseColor.w = -1; // Subsurface scattering is disabled
|
||||
OUT.m_specularColor = lightingOutput.m_specularColor;
|
||||
OUT.m_specularF0 = lightingOutput.m_specularF0;
|
||||
OUT.m_albedo = lightingOutput.m_albedo;
|
||||
OUT.m_normal = lightingOutput.m_normal;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"Source" : "./TerrainPBR_ForwardPass.azsl",
|
||||
|
||||
"DepthStencilState" :
|
||||
{
|
||||
"Depth" :
|
||||
{
|
||||
"Enable" : true,
|
||||
"CompareFunc" : "GreaterEqual"
|
||||
},
|
||||
"Stencil" :
|
||||
{
|
||||
"Enable" : true,
|
||||
"ReadMask" : "0x00",
|
||||
"WriteMask" : "0xFF",
|
||||
"FrontFace" :
|
||||
{
|
||||
"Func" : "Always",
|
||||
"DepthFailOp" : "Keep",
|
||||
"FailOp" : "Keep",
|
||||
"PassOp" : "Replace"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "TerrainPBR_MainPassVS",
|
||||
"type": "Vertex"
|
||||
},
|
||||
{
|
||||
"name": "TerrainPBR_MainPassPS",
|
||||
"type": "Fragment"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"DrawList" : "forward"
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <Atom/Features/SrgSemantics.azsli>
|
||||
#include <viewsrg.srgi>
|
||||
#include "TerrainCommon.azsli"
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"Source" : "Terrain_DepthPass",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : true, "CompareFunc" : "LessEqual" }
|
||||
},
|
||||
|
||||
"DrawList" : "shadow",
|
||||
|
||||
"RasterState" :
|
||||
{
|
||||
"depthBias" : "10",
|
||||
"depthBiasSlopeScale" : "4"
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ ly_add_target(
|
||||
AZ::AzFramework
|
||||
Gem::Atom_RPI.Public
|
||||
Gem::Atom_Utils.Static
|
||||
Gem::Atom_Feature_Common
|
||||
Gem::GradientSignal
|
||||
Gem::SurfaceData
|
||||
Gem::LmbrCentral
|
||||
|
||||
@@ -20,28 +20,39 @@
|
||||
#include <Atom/RPI.Public/RPIUtils.h>
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
#include <Atom/RPI.Public/MeshDrawPacket.h>
|
||||
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
|
||||
#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
|
||||
#include <Atom/RPI.Public/Buffer/BufferSystem.h>
|
||||
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
||||
#include <Atom/RPI.Public/Image/StreamingImagePool.h>
|
||||
#include <Atom/RPI.Public/Model/Model.h>
|
||||
#include <Atom/RPI.Public/Material/Material.h>
|
||||
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
|
||||
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
|
||||
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
|
||||
#include <Atom/RHI.Reflect/InputStreamLayout.h>
|
||||
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
|
||||
|
||||
#include <Atom/Feature/RenderCommon.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const uint32_t DEFAULT_UploadBufferSize = 512 * 1024; // 512k
|
||||
[[maybe_unused]] const char* TerrainFPName = "TerrainFeatureProcessor";
|
||||
}
|
||||
|
||||
namespace MaterialInputs
|
||||
{
|
||||
static const char* const HeightmapImage("settings.heightmapImage");
|
||||
}
|
||||
|
||||
namespace ShaderInputs
|
||||
{
|
||||
static const char* const HeightmapImage("m_heightmapImage");
|
||||
static const char* const ModelToWorld("m_modelToWorld");
|
||||
static const char* const TerrainData("m_terrainData");
|
||||
}
|
||||
@@ -60,142 +71,44 @@ namespace Terrain
|
||||
void TerrainFeatureProcessor::Activate()
|
||||
{
|
||||
m_areaData = {};
|
||||
|
||||
InitializeAtomStuff();
|
||||
EnableSceneNotification();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail)
|
||||
void TerrainFeatureProcessor::Initialize()
|
||||
{
|
||||
if (shaderState.m_shader == nullptr)
|
||||
{
|
||||
AZ_Assert(shaderState.m_shader || !assertOnFail, "Terrain shader failed to load correctly.");
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
{
|
||||
auto LoadShader = [this](const char* filePath, ShaderState& shaderState)
|
||||
{
|
||||
shaderState.m_shader = AZ::RPI::LoadShader(filePath);
|
||||
if (!shaderState.m_shader)
|
||||
// Load the terrain material asynchronously
|
||||
const AZStd::string materialFilePath = "Materials/Terrain/DefaultPbrTerrain.azmaterial";
|
||||
m_materialAssetLoader = AZStd::make_unique<AZ::RPI::AssetUtils::AsyncAssetLoader>();
|
||||
*m_materialAssetLoader = AZ::RPI::AssetUtils::AsyncAssetLoader::Create<AZ::RPI::MaterialAsset>(materialFilePath, 0u,
|
||||
[&](AZ::Data::Asset<AZ::Data::AssetData> assetData, bool success) -> void
|
||||
{
|
||||
AZ_Error(TerrainFPName, false, "Failed to find or create a shader instance from shader asset '%s'", filePath);
|
||||
return;
|
||||
const AZ::Data::Asset<AZ::RPI::MaterialAsset>& materialAsset = static_cast<AZ::Data::Asset<AZ::RPI::MaterialAsset>>(assetData);
|
||||
if (success)
|
||||
{
|
||||
m_materialInstance = AZ::RPI::Material::FindOrCreate(assetData);
|
||||
if (!materialAsset->GetObjectSrgLayout())
|
||||
{
|
||||
AZ_Error("TerrainFeatureProcessor", false, "No per-object ShaderResourceGroup found on terrain material.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.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;
|
||||
}
|
||||
|
||||
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 = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld));
|
||||
AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld);
|
||||
|
||||
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;
|
||||
dmaPoolDescriptor.m_heapMemoryLevel = AZ::RHI::HeapMemoryLevel::Host;
|
||||
dmaPoolDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly;
|
||||
|
||||
m_hostPool = AZ::RHI::Factory::Get().CreateBufferPool();
|
||||
m_hostPool->SetName(AZ::Name("TerrainVertexPool"));
|
||||
AZ::RHI::ResultCode resultCode = m_hostPool->Init(*m_rhiSystem->GetDevice(), dmaPoolDescriptor);
|
||||
|
||||
if (resultCode != AZ::RHI::ResultCode::Success)
|
||||
{
|
||||
AZ_Error(TerrainFPName, false, "Failed to create host buffer pool from RPI");
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeTerrainPatch();
|
||||
|
||||
if (!InitializeRenderBuffers())
|
||||
if (!InitializePatchModel())
|
||||
{
|
||||
AZ_Error(TerrainFPName, false, "Failed to create Terrain render buffers!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] AZ::RPI::RenderPipelinePtr pipeline)
|
||||
{
|
||||
for (ShaderState& shaderState: m_shaderStates)
|
||||
{
|
||||
ConfigurePipelineState(shaderState, true);
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] AZ::RPI::RenderPipeline* pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] AZ::RPI::RenderPipeline* renderPipeline)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void TerrainFeatureProcessor::Deactivate()
|
||||
{
|
||||
DisableSceneNotification();
|
||||
|
||||
DestroyRenderBuffers();
|
||||
m_patchModel = {};
|
||||
m_areaData = {};
|
||||
|
||||
if (m_hostPool)
|
||||
{
|
||||
m_hostPool.reset();
|
||||
}
|
||||
|
||||
m_rhiSystem = nullptr;
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet)
|
||||
@@ -254,240 +167,269 @@ namespace Terrain
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzRender);
|
||||
|
||||
if ((m_shaderStates[ShaderType::Forward].m_shader == nullptr) ||
|
||||
(m_shaderStates[ShaderType::Depth].m_shader == nullptr) ||
|
||||
m_shaderStates[ShaderType::Forward].m_shader->GetDrawListTag().IsNull() ||
|
||||
m_shaderStates[ShaderType::Depth].m_shader->GetDrawListTag().IsNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_areaData.m_terrainBounds.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_areaData.m_propertiesDirty)
|
||||
if (m_areaData.m_propertiesDirty && m_materialInstance)
|
||||
{
|
||||
m_areaData.m_propertiesDirty = false;
|
||||
m_sectorData.clear();
|
||||
|
||||
AZ::RHI::DrawPacketBuilder drawPacketBuilder;
|
||||
AZ::RPI::MaterialPropertyIndex heightmapPropertyIndex =
|
||||
m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage));
|
||||
AZ_Error(TerrainFPName, heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage);
|
||||
AZ::Data::Instance<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage;
|
||||
m_materialInstance->SetPropertyValue(heightmapPropertyIndex, heightmapImage);
|
||||
m_materialInstance->Compile();
|
||||
|
||||
uint32_t numIndices = static_cast<uint32_t>(m_gridIndices.size());
|
||||
const auto layout = m_materialInstance->GetAsset()->GetObjectSrgLayout();
|
||||
|
||||
AZ::RHI::DrawIndexed drawIndexed;
|
||||
drawIndexed.m_indexCount = numIndices;
|
||||
drawIndexed.m_indexOffset = 0;
|
||||
drawIndexed.m_vertexOffset = 0;
|
||||
m_modelToWorldIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld));
|
||||
AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld);
|
||||
|
||||
m_terrainDataIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData));
|
||||
AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData);
|
||||
|
||||
float xFirstPatchStart =
|
||||
m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), m_gridMeters);
|
||||
float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), m_gridMeters);
|
||||
m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), GridMeters);
|
||||
float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), GridMeters);
|
||||
float yFirstPatchStart =
|
||||
m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), m_gridMeters);
|
||||
float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), m_gridMeters);
|
||||
m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), GridMeters);
|
||||
float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), GridMeters);
|
||||
|
||||
for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += m_gridMeters)
|
||||
for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += GridMeters)
|
||||
{
|
||||
for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += m_gridMeters)
|
||||
for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += GridMeters)
|
||||
{
|
||||
drawPacketBuilder.Begin(nullptr);
|
||||
drawPacketBuilder.SetDrawArguments(drawIndexed);
|
||||
drawPacketBuilder.SetIndexBufferView(m_indexBufferView);
|
||||
auto& forwardShader = m_shaderStates[ShaderType::Forward].m_shader;
|
||||
|
||||
auto resourceGroup = AZ::RPI::ShaderResourceGroup::Create(forwardShader->GetAsset(), forwardShader->GetSupervariantIndex(), AZ::Name("ObjectSrg"));
|
||||
if (!resourceGroup)
|
||||
const auto& materialAsset = m_materialInstance->GetAsset();
|
||||
auto& shaderAsset = materialAsset->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg();
|
||||
auto objectSrg = AZ::RPI::ShaderResourceGroup::Create(shaderAsset, materialAsset->GetObjectSrgLayout()->GetName());
|
||||
if (!objectSrg)
|
||||
{
|
||||
AZ_Error(TerrainFPName, false, "Failed to create shader resource group");
|
||||
return;
|
||||
AZ_Warning("TerrainFeatureProcessor", false, "Failed to create a new shader resource group, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
AZStd::array<float, 2> uvMin = { 0.0f, 0.0f };
|
||||
AZStd::array<float, 2> uvMax = { 1.0f, 1.0f };
|
||||
{ // Update SRG
|
||||
|
||||
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());
|
||||
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());
|
||||
|
||||
uvMax[0] =
|
||||
(float)(((xPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent());
|
||||
uvMax[1] =
|
||||
(float)(((yPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent());
|
||||
uvMax[0] =
|
||||
(float)(((xPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent());
|
||||
uvMax[1] =
|
||||
(float)(((yPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent());
|
||||
|
||||
AZStd::array<float, 2> uvStep =
|
||||
{
|
||||
1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight,
|
||||
};
|
||||
AZStd::array<float, 2> uvStep =
|
||||
{
|
||||
1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight,
|
||||
};
|
||||
|
||||
AZ::Transform transform = m_areaData.m_transform;
|
||||
transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ());
|
||||
AZ::Transform transform = m_areaData.m_transform;
|
||||
transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ());
|
||||
|
||||
AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform);
|
||||
AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform);
|
||||
|
||||
resourceGroup->SetImage(m_heightmapImageIndex, m_areaData.m_heightmapImage);
|
||||
resourceGroup->SetConstant(m_modelToWorldIndex, matrix3x4);
|
||||
objectSrg->SetConstant(m_modelToWorldIndex, matrix3x4);
|
||||
|
||||
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);
|
||||
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;
|
||||
objectSrg->SetConstant(m_terrainDataIndex, terrainDataForSrg);
|
||||
|
||||
resourceGroup->Compile();
|
||||
drawPacketBuilder.AddShaderResourceGroup(resourceGroup->GetRHIShaderResourceGroup());
|
||||
|
||||
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);
|
||||
objectSrg->Compile();
|
||||
}
|
||||
//addDrawItem(m_shaderStates[ShaderType::Forward]);
|
||||
|
||||
m_sectorData.emplace_back(
|
||||
drawPacketBuilder.End(),
|
||||
|
||||
m_sectorData.push_back();
|
||||
SectorData& sectorData = m_sectorData.back();
|
||||
|
||||
for (auto& lod : m_patchModel->GetLods())
|
||||
{
|
||||
AZ::RPI::ModelLod& modelLod = *lod.get();
|
||||
sectorData.m_drawPackets.emplace_back(modelLod, 0, m_materialInstance, objectSrg);
|
||||
AZ::RPI::MeshDrawPacket& drawPacket = sectorData.m_drawPackets.back();
|
||||
|
||||
// set the shader option to select forward pass IBL specular if necessary
|
||||
if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ false }))
|
||||
{
|
||||
AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet");
|
||||
}
|
||||
uint8_t stencilRef = AZ::Render::StencilRefs::UseDiffuseGIPass | AZ::Render::StencilRefs::UseIBLSpecularPass;
|
||||
drawPacket.SetStencilRef(stencilRef);
|
||||
drawPacket.Update(*GetParentScene(), true);
|
||||
}
|
||||
|
||||
sectorData.m_aabb =
|
||||
AZ::Aabb::CreateFromMinMax(
|
||||
AZ::Vector3(xPatch, yPatch, m_areaData.m_terrainBounds.GetMin().GetZ()),
|
||||
AZ::Vector3(xPatch + m_gridMeters, yPatch + m_gridMeters, m_areaData.m_terrainBounds.GetMax().GetZ())
|
||||
),
|
||||
resourceGroup
|
||||
);
|
||||
AZ::Vector3(xPatch + GridMeters, yPatch + GridMeters, m_areaData.m_terrainBounds.GetMax().GetZ())
|
||||
);
|
||||
sectorData.m_srg = objectSrg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& view : process.m_views)
|
||||
|
||||
for (auto& sectorData : m_sectorData)
|
||||
{
|
||||
AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix());
|
||||
for (auto& sectorData : m_sectorData)
|
||||
uint8_t lodChoice = AZ::RPI::ModelLodAsset::LodCountMax;
|
||||
|
||||
// Go through all cameras and choose an LOD based on the closest camera.
|
||||
for (auto& view : process.m_views)
|
||||
{
|
||||
if ((view->GetUsageFlags() & AZ::RPI::View::UsageFlags::UsageCamera) > 0)
|
||||
{
|
||||
AZ::Vector3 cameraPosition = view->GetCameraTransform().GetTranslation();
|
||||
AZ::Vector2 cameraPositionXY = AZ::Vector2(cameraPosition.GetX(), cameraPosition.GetY());
|
||||
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))));
|
||||
lodChoice = AZ::GetMin(lodChoice, aznumeric_cast<uint8_t>(lodForCamera));
|
||||
}
|
||||
}
|
||||
|
||||
// Add the correct LOD draw packet for visible sectors.
|
||||
for (auto& view : process.m_views)
|
||||
{
|
||||
AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix());
|
||||
if (viewFrustum.IntersectAabb(sectorData.m_aabb) != AZ::IntersectResult::Exterior)
|
||||
{
|
||||
view->AddDrawPacket(sectorData.m_drawPacket.get());
|
||||
uint8_t lodToRender = AZ::GetMin(lodChoice, aznumeric_cast<uint8_t>(sectorData.m_drawPackets.size() - 1));
|
||||
view->AddDrawPacket(sectorData.m_drawPackets.at(lodToRender).GetRHIDrawPacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::InitializeTerrainPatch()
|
||||
void TerrainFeatureProcessor::InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata)
|
||||
{
|
||||
m_gridVertices.clear();
|
||||
m_gridIndices.clear();
|
||||
patchdata.m_positions.clear();
|
||||
patchdata.m_uvs.clear();
|
||||
patchdata.m_indices.clear();
|
||||
|
||||
for (float y = 0.0f; y < m_gridMeters; y += m_gridSpacing)
|
||||
uint16_t gridVertices = gridSize + 1; // For m_gridSize quads, (m_gridSize + 1) vertices are needed.
|
||||
size_t size = gridVertices * gridVertices;
|
||||
|
||||
patchdata.m_positions.reserve(size);
|
||||
patchdata.m_uvs.reserve(size);
|
||||
|
||||
for (uint16_t y = 0; y < gridVertices; ++y)
|
||||
{
|
||||
for (float x = 0.0f; x < m_gridMeters; x += m_gridSpacing)
|
||||
for (uint16_t x = 0; x < gridVertices; ++x)
|
||||
{
|
||||
float x0 = x;
|
||||
float x1 = x + m_gridSpacing;
|
||||
float y0 = y;
|
||||
float y1 = y + m_gridSpacing;
|
||||
patchdata.m_positions.push_back({ aznumeric_cast<float>(x) * gridSpacing, aznumeric_cast<float>(y) * gridSpacing });
|
||||
patchdata.m_uvs.push_back({ aznumeric_cast<float>(x) / gridSize, aznumeric_cast<float>(y) / gridSize });
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t startIndex = (uint16_t)(m_gridVertices.size());
|
||||
patchdata.m_indices.reserve(gridSize * gridSize * 6); // total number of quads, 2 triangles with 6 indices per quad.
|
||||
|
||||
for (uint16_t y = 0; y < gridSize; ++y)
|
||||
{
|
||||
for (uint16_t x = 0; x < gridSize; ++x)
|
||||
{
|
||||
uint16_t topLeft = y * gridVertices + x;
|
||||
uint16_t topRight = topLeft + 1;
|
||||
uint16_t bottomLeft = (y + 1) * gridVertices + x;
|
||||
uint16_t bottomRight = bottomLeft + 1;
|
||||
|
||||
m_gridVertices.emplace_back(x0, y0, x0 / m_gridMeters, y0 / 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 + 3));
|
||||
m_gridIndices.emplace_back(aznumeric_cast<uint16_t>(startIndex + 2));
|
||||
patchdata.m_indices.emplace_back(topLeft);
|
||||
patchdata.m_indices.emplace_back(topRight);
|
||||
patchdata.m_indices.emplace_back(bottomLeft);
|
||||
patchdata.m_indices.emplace_back(bottomLeft);
|
||||
patchdata.m_indices.emplace_back(topRight);
|
||||
patchdata.m_indices.emplace_back(bottomRight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TerrainFeatureProcessor::InitializeRenderBuffers()
|
||||
|
||||
AZ::Outcome<AZ::Data::Asset<AZ::RPI::BufferAsset>> TerrainFeatureProcessor::CreateBufferAsset(
|
||||
const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName)
|
||||
{
|
||||
AZ::RHI::ResultCode result = AZ::RHI::ResultCode::Fail;
|
||||
AZ::RPI::BufferAssetCreator creator;
|
||||
creator.Begin(AZ::Uuid::CreateRandom());
|
||||
|
||||
// Create geometry buffers
|
||||
m_indexBuffer = AZ::RHI::Factory::Get().CreateBuffer();
|
||||
m_vertexBuffer = AZ::RHI::Factory::Get().CreateBuffer();
|
||||
AZ::RHI::BufferDescriptor bufferDescriptor;
|
||||
bufferDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly | AZ::RHI::BufferBindFlags::ShaderRead;
|
||||
bufferDescriptor.m_byteCount = static_cast<uint64_t>(bufferViewDescriptor.m_elementSize) * static_cast<uint64_t>(bufferViewDescriptor.m_elementCount);
|
||||
|
||||
m_indexBuffer->SetName(AZ::Name("TerrainIndexBuffer"));
|
||||
m_vertexBuffer->SetName(AZ::Name("TerrainVertexBuffer"));
|
||||
creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor);
|
||||
creator.SetBufferViewDescriptor(bufferViewDescriptor);
|
||||
creator.SetUseCommonPool(AZ::RPI::CommonBufferPoolType::StaticInputAssembly);
|
||||
|
||||
AZStd::vector<AZ::RHI::Ptr<AZ::RHI::Buffer>> buffers = { m_indexBuffer , m_vertexBuffer };
|
||||
|
||||
// Fill our buffers with the vertex/index data
|
||||
for (size_t bufferIndex = 0; bufferIndex < buffers.size(); ++bufferIndex)
|
||||
AZ::Data::Asset<AZ::RPI::BufferAsset> bufferAsset;
|
||||
if (creator.End(bufferAsset))
|
||||
{
|
||||
AZ::RHI::Ptr<AZ::RHI::Buffer> buffer = buffers[bufferIndex];
|
||||
bufferAsset.SetHint(bufferName);
|
||||
return AZ::Success(bufferAsset);
|
||||
}
|
||||
|
||||
// Initialize the buffer
|
||||
return AZ::Failure();
|
||||
}
|
||||
|
||||
AZ::RHI::BufferInitRequest bufferRequest;
|
||||
bufferRequest.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, DEFAULT_UploadBufferSize };
|
||||
bufferRequest.m_buffer = buffer.get();
|
||||
bool TerrainFeatureProcessor::InitializePatchModel()
|
||||
{
|
||||
AZ::RPI::ModelAssetCreator modelAssetCreator;
|
||||
modelAssetCreator.Begin(AZ::Uuid::CreateRandom());
|
||||
|
||||
result = m_hostPool->InitBuffer(bufferRequest);
|
||||
uint16_t gridSize = GridSize;
|
||||
float gridSpacing = GridSpacing;
|
||||
|
||||
if (result != AZ::RHI::ResultCode::Success)
|
||||
for (uint32_t i = 0; i < AZ::RPI::ModelLodAsset::LodCountMax && gridSize > 0; ++i)
|
||||
{
|
||||
PatchData patchData;
|
||||
InitializeTerrainPatch(gridSize, gridSpacing, patchData);
|
||||
|
||||
auto positionBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_positions.size()), AZ::RHI::Format::R32G32_FLOAT);
|
||||
auto positionsOutcome = CreateBufferAsset(patchData.m_positions.data(), positionBufferViewDesc, "TerrainPatchPositions");
|
||||
|
||||
auto uvBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_uvs.size()), AZ::RHI::Format::R32G32_FLOAT);
|
||||
auto uvsOutcome = CreateBufferAsset(patchData.m_uvs.data(), uvBufferViewDesc, "TerrainPatchUvs");
|
||||
|
||||
auto indexBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_indices.size()), AZ::RHI::Format::R16_UINT);
|
||||
auto indicesOutcome = CreateBufferAsset(patchData.m_indices.data(), indexBufferViewDesc, "TerrainPatchIndices");
|
||||
|
||||
if (!positionsOutcome.IsSuccess() || !uvsOutcome.IsSuccess() || !indicesOutcome.IsSuccess())
|
||||
{
|
||||
AZ_Error(TerrainFPName, false, "Failed to create GPU buffers for Terrain");
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::RPI::ModelLodAssetCreator modelLodAssetCreator;
|
||||
modelLodAssetCreator.Begin(AZ::Uuid::CreateRandom());
|
||||
|
||||
// Grab a pointer to the buffer's data
|
||||
modelLodAssetCreator.BeginMesh();
|
||||
modelLodAssetCreator.AddMeshStreamBuffer(AZ::RHI::ShaderSemantic{ "POSITION" }, AZ::Name(), {positionsOutcome.GetValue(), positionBufferViewDesc});
|
||||
modelLodAssetCreator.AddMeshStreamBuffer(AZ::RHI::ShaderSemantic{ "UV" }, AZ::Name(), {uvsOutcome.GetValue(), uvBufferViewDesc});
|
||||
modelLodAssetCreator.SetMeshIndexBuffer({indicesOutcome.GetValue(), indexBufferViewDesc});
|
||||
|
||||
m_hostPool->OrphanBuffer(*buffer);
|
||||
AZ::Aabb aabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0, 0.0, 0.0), AZ::Vector3(GridMeters, GridMeters, 0.0));
|
||||
modelLodAssetCreator.SetMeshAabb(AZStd::move(aabb));
|
||||
modelLodAssetCreator.SetMeshName(AZ::Name("Terrain Patch"));
|
||||
modelLodAssetCreator.EndMesh();
|
||||
|
||||
AZ::RHI::BufferMapResponse mapResponse;
|
||||
m_hostPool->MapBuffer(AZ::RHI::BufferMapRequest(*buffer, 0, DEFAULT_UploadBufferSize), mapResponse);
|
||||
AZ::Data::Asset<AZ::RPI::ModelLodAsset> modelLodAsset;
|
||||
modelLodAssetCreator.End(modelLodAsset);
|
||||
|
||||
modelAssetCreator.AddLodAsset(AZStd::move(modelLodAsset));
|
||||
|
||||
auto* mappedData = reinterpret_cast<uint8_t*>(mapResponse.m_data);
|
||||
|
||||
//0th index should always be the index buffer
|
||||
if (bufferIndex == 0)
|
||||
{
|
||||
// Fill the index buffer with our terrain patch indices
|
||||
const uint64_t idxSize = m_gridIndices.size() * sizeof(uint16_t);
|
||||
memcpy(mappedData, m_gridIndices.data(), idxSize);
|
||||
|
||||
m_indexBufferView = AZ::RHI::IndexBufferView(
|
||||
*buffer, 0, static_cast<uint32_t>(idxSize), AZ::RHI::IndexFormat::Uint16);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill the vertex buffer with our terrain patch vertices
|
||||
const uint64_t elementSize = m_gridVertices.size() * sizeof(Vertex);
|
||||
memcpy(mappedData, m_gridVertices.data(), elementSize);
|
||||
|
||||
m_vertexBufferView = AZ::RHI::StreamBufferView(
|
||||
*buffer, 0, static_cast<uint32_t>(elementSize), static_cast<uint32_t>(sizeof(Vertex)));
|
||||
}
|
||||
|
||||
m_hostPool->UnmapBuffer(*buffer);
|
||||
gridSize = gridSize / 2;
|
||||
gridSpacing *= 2.0f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset;
|
||||
bool success = modelAssetCreator.End(modelAsset);
|
||||
|
||||
void TerrainFeatureProcessor::DestroyRenderBuffers()
|
||||
{
|
||||
m_indexBuffer.reset();
|
||||
m_vertexBuffer.reset();
|
||||
m_patchModel = AZ::RPI::Model::FindOrCreate(modelAsset);
|
||||
|
||||
m_indexBufferView = {};
|
||||
m_vertexBufferView = {};
|
||||
|
||||
for (ShaderState& shaderState : m_shaderStates)
|
||||
{
|
||||
shaderState.Reset();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
||||
|
||||
#include <Atom/RPI.Public/FeatureProcessor.h>
|
||||
#include <Atom/RPI.Public/Material/Material.h>
|
||||
#include <Atom/RPI.Public/Shader/Shader.h>
|
||||
|
||||
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
||||
#include <Atom/RPI.Public/MeshDrawPacket.h>
|
||||
#include <Atom/RHI/ShaderResourceGroup.h>
|
||||
#include <Atom/RHI/BufferPool.h>
|
||||
#include <Atom/RHI/DrawPacket.h>
|
||||
@@ -26,6 +28,15 @@
|
||||
#include <Atom/RHI/RHISystemInterface.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
|
||||
|
||||
namespace AZ::RPI
|
||||
{
|
||||
namespace AssetUtils
|
||||
{
|
||||
class AsyncAssetLoader;
|
||||
}
|
||||
class Model;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class TerrainFeatureProcessor final
|
||||
@@ -57,28 +68,6 @@ 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;
|
||||
@@ -87,62 +76,47 @@ namespace Terrain
|
||||
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();
|
||||
|
||||
bool InitializeRenderBuffers();
|
||||
void DestroyRenderBuffers();
|
||||
|
||||
void ProcessSurfaces(const FeatureProcessor::RenderPacket& process);
|
||||
|
||||
// System-level parameters
|
||||
const float m_gridSpacing{ 1.0f };
|
||||
const float m_gridMeters{ 32.0f };
|
||||
|
||||
// System-level cached reference to the Atom RHI
|
||||
AZ::RHI::RHISystemInterface* m_rhiSystem = nullptr;
|
||||
|
||||
AZStd::array<ShaderState, ShaderType::Count> m_shaderStates;
|
||||
|
||||
AZ::RHI::ShaderInputImageIndex m_heightmapImageIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex;
|
||||
|
||||
// Pos_float_2 + UV_float_2
|
||||
struct Vertex
|
||||
|
||||
struct VertexPosition
|
||||
{
|
||||
float m_posx;
|
||||
float m_posy;
|
||||
float m_u;
|
||||
float m_v;
|
||||
|
||||
Vertex(float posx, float posy, float u, float v)
|
||||
: m_posx(posx)
|
||||
, m_posy(posy)
|
||||
, m_u(u)
|
||||
, m_v(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// System-level definition of a grid patch. (ex: 32m x 32m)
|
||||
AZStd::vector<Vertex> m_gridVertices;
|
||||
AZStd::vector<uint16_t> m_gridIndices;
|
||||
struct VertexUv
|
||||
{
|
||||
float m_u;
|
||||
float m_v;
|
||||
};
|
||||
|
||||
// System-level data related to the grid patch
|
||||
AZ::RHI::Ptr<AZ::RHI::BufferPool> m_hostPool = nullptr;
|
||||
AZ::RHI::Ptr<AZ::RHI::Buffer> m_indexBuffer;
|
||||
AZ::RHI::Ptr<AZ::RHI::Buffer> m_vertexBuffer;
|
||||
AZ::RHI::IndexBufferView m_indexBufferView;
|
||||
AZ::RHI::StreamBufferView m_vertexBufferView;
|
||||
struct PatchData
|
||||
{
|
||||
AZStd::vector<VertexPosition> m_positions;
|
||||
AZStd::vector<VertexUv> m_uvs;
|
||||
AZStd::vector<uint16_t> m_indices;
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata);
|
||||
bool InitializePatchModel();
|
||||
|
||||
void ProcessSurfaces(const FeatureProcessor::RenderPacket& process);
|
||||
|
||||
AZ::Outcome<AZ::Data::Asset<AZ::RPI::BufferAsset>> CreateBufferAsset(
|
||||
const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName);
|
||||
|
||||
// System-level parameters
|
||||
static constexpr float GridSpacing{ 1.0f };
|
||||
static constexpr uint32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1)
|
||||
static constexpr float GridMeters{ GridSpacing * GridSize };
|
||||
|
||||
AZStd::unique_ptr<AZ::RPI::AssetUtils::AsyncAssetLoader> m_materialAssetLoader;
|
||||
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
|
||||
|
||||
AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex;
|
||||
|
||||
AZ::Data::Instance<AZ::RPI::Model> m_patchModel;
|
||||
|
||||
// Per-area data
|
||||
struct TerrainAreaData
|
||||
@@ -161,15 +135,9 @@ namespace Terrain
|
||||
|
||||
struct SectorData
|
||||
{
|
||||
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_srg;
|
||||
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_srg; // Hold on to ref so it's not dropped
|
||||
AZ::Aabb m_aabb;
|
||||
AZStd::unique_ptr<const AZ::RHI::DrawPacket> m_drawPacket;
|
||||
|
||||
SectorData(const AZ::RHI::DrawPacket* drawPacket, AZ::Aabb aabb, AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> srg)
|
||||
: m_srg(srg)
|
||||
, m_aabb(aabb)
|
||||
, m_drawPacket(drawPacket)
|
||||
{}
|
||||
AZStd::fixed_vector<AZ::RPI::MeshDrawPacket, AZ::RPI::ModelLodAsset::LodCountMax> m_drawPackets;
|
||||
};
|
||||
|
||||
AZStd::vector<SectorData> m_sectorData;
|
||||
|
||||
@@ -39,6 +39,14 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
|
||||
TEST_SUITE smoke
|
||||
COMPONENT TestTools
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME LyTestTools_IntegTests_ProcessUtilsLinux_smoke_no_gpu
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/integ/test_process_utils_linux.py
|
||||
TEST_SERIAL
|
||||
TEST_SUITE smoke
|
||||
COMPONENT TestTools
|
||||
)
|
||||
|
||||
# Regression tests.
|
||||
ly_add_pytest(
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
import subprocess
|
||||
import pytest
|
||||
|
||||
import ly_test_tools.environment.process_utils as process_utils
|
||||
import ly_test_tools.environment.waiter as waiter
|
||||
from ly_test_tools import LINUX
|
||||
|
||||
if LINUX:
|
||||
pytestmark = pytest.mark.SUITE_smoke
|
||||
else:
|
||||
pytestmark = pytest.mark.skipif(not LINUX, reason="Only runs on Linux")
|
||||
|
||||
|
||||
class TestProcessUtils(object):
|
||||
|
||||
def test_KillLinuxProcess_ProcessStarted_KilledSuccessfully(self):
|
||||
# Construct a simple timeout command
|
||||
linux_executable = 'timeout'
|
||||
command = [linux_executable, '5s', 'echo']
|
||||
|
||||
# Verification function for the waiter to call
|
||||
def process_killed():
|
||||
return not process_utils.process_exists(linux_executable, ignore_extensions=True)
|
||||
|
||||
# Create a new process with no output in a new session
|
||||
with subprocess.Popen(command, stdout=subprocess.DEVNULL, start_new_session=True):
|
||||
# Ensure that the process was started
|
||||
assert process_utils.process_exists(linux_executable, ignore_extensions=True), \
|
||||
f"Process '{linux_executable}' was expected to exist, but could not be found."
|
||||
# Kill the process using the process_utils module
|
||||
process_utils.kill_processes_named(linux_executable, ignore_extensions=True)
|
||||
# Verify that the process was killed
|
||||
waiter.wait_for(process_killed, timeout=2) # Raises exception if the process is alive.
|
||||
Reference in New Issue
Block a user