You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1257 lines
57 KiB
C++
1257 lines
57 KiB
C++
/*
|
|
* 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/RHI/RHIUtils.h>
|
|
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
|
|
#include <Atom/Feature/RenderCommon.h>
|
|
#include <Atom/Feature/Mesh/MeshFeatureProcessor.h>
|
|
#include <Atom/Feature/Mesh/ModelReloaderSystemInterface.h>
|
|
#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
|
|
#include <Atom/RPI.Public/Model/ModelLodUtils.h>
|
|
#include <Atom/RPI.Public/Scene.h>
|
|
#include <Atom/RPI.Public/Culling.h>
|
|
#include <Atom/Utils/StableDynamicArray.h>
|
|
|
|
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
|
|
|
|
#include <AzFramework/Asset/AssetSystemBus.h>
|
|
|
|
#include <AtomCore/Instance/InstanceDatabase.h>
|
|
|
|
#include <AzCore/Console/IConsole.h>
|
|
#include <AzCore/Debug/EventTrace.h>
|
|
#include <AzCore/Jobs/Algorithms.h>
|
|
#include <AzCore/Jobs/JobCompletion.h>
|
|
#include <AzCore/Jobs/JobFunction.h>
|
|
#include <AzCore/Math/ShapeIntersection.h>
|
|
#include <AzCore/RTTI/TypeInfo.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Asset/AssetCommon.h>
|
|
|
|
namespace AZ
|
|
{
|
|
namespace Render
|
|
{
|
|
void MeshFeatureProcessor::Reflect(ReflectContext* context)
|
|
{
|
|
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
|
{
|
|
serializeContext
|
|
->Class<MeshFeatureProcessor, FeatureProcessor>()
|
|
->Version(0);
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::Activate()
|
|
{
|
|
m_transformService = GetParentScene()->GetFeatureProcessor<TransformServiceFeatureProcessor>();
|
|
AZ_Assert(m_transformService, "MeshFeatureProcessor requires a TransformServiceFeatureProcessor on its parent scene.");
|
|
|
|
m_rayTracingFeatureProcessor = GetParentScene()->GetFeatureProcessor<RayTracingFeatureProcessor>();
|
|
|
|
m_handleGlobalShaderOptionUpdate = RPI::ShaderSystemInterface::GlobalShaderOptionUpdatedEvent::Handler
|
|
{
|
|
[this](const AZ::Name&, RPI::ShaderOptionValue) { m_forceRebuildDrawPackets = true; }
|
|
};
|
|
RPI::ShaderSystemInterface::Get()->Connect(m_handleGlobalShaderOptionUpdate);
|
|
EnableSceneNotification();
|
|
}
|
|
|
|
void MeshFeatureProcessor::Deactivate()
|
|
{
|
|
m_handleGlobalShaderOptionUpdate.Disconnect();
|
|
|
|
DisableSceneNotification();
|
|
AZ_Warning("MeshFeatureProcessor", m_modelData.size() == 0,
|
|
"Deactivaing the MeshFeatureProcessor, but there are still outstanding mesh handles.\n"
|
|
);
|
|
m_transformService = nullptr;
|
|
m_forceRebuildDrawPackets = false;
|
|
}
|
|
|
|
void MeshFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
|
|
{
|
|
AZ_PROFILE_SCOPE(RPI, "MeshFeatureProcessor: Simulate");
|
|
AZ_UNUSED(packet);
|
|
|
|
AZStd::concurrency_check_scope scopeCheck(m_meshDataChecker);
|
|
|
|
const auto iteratorRanges = m_modelData.GetParallelRanges();
|
|
AZ::JobCompletion jobCompletion;
|
|
for (const auto& iteratorRange : iteratorRanges)
|
|
{
|
|
const auto jobLambda = [&]() -> void
|
|
{
|
|
for (auto meshDataIter = iteratorRange.first; meshDataIter != iteratorRange.second; ++meshDataIter)
|
|
{
|
|
if (!meshDataIter->m_model)
|
|
{
|
|
continue; // model not loaded yet
|
|
}
|
|
|
|
if (!meshDataIter->m_visible)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (meshDataIter->m_objectSrgNeedsUpdate)
|
|
{
|
|
meshDataIter->UpdateObjectSrg();
|
|
}
|
|
|
|
// [GFX TODO] [ATOM-1357] Currently all of the draw packets have to be checked for material ID changes because
|
|
// material properties can impact which actual shader is used, which impacts the SRG in the draw packet.
|
|
// This is scheduled to be optimized so the work is only done on draw packets that need it instead of having
|
|
// to check every one.
|
|
meshDataIter->UpdateDrawPackets(m_forceRebuildDrawPackets);
|
|
|
|
if (meshDataIter->m_cullableNeedsRebuild)
|
|
{
|
|
meshDataIter->BuildCullable();
|
|
}
|
|
}
|
|
};
|
|
Job* executeGroupJob = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
|
|
executeGroupJob->SetDependent(&jobCompletion);
|
|
executeGroupJob->Start();
|
|
}
|
|
jobCompletion.StartAndWaitForCompletion();
|
|
|
|
m_forceRebuildDrawPackets = false;
|
|
|
|
// CullingSystem::RegisterOrUpdateCullable() is not threadsafe, so need to do those updates in a single thread
|
|
for (ModelDataInstance& modelDataInstance : m_modelData)
|
|
{
|
|
if (modelDataInstance.m_model && modelDataInstance.m_cullBoundsNeedsUpdate)
|
|
{
|
|
modelDataInstance.UpdateCullBounds(m_transformService);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::OnBeginPrepareRender()
|
|
{
|
|
m_meshDataChecker.soft_lock();
|
|
}
|
|
|
|
void MeshFeatureProcessor::OnEndPrepareRender()
|
|
{
|
|
m_meshDataChecker.soft_unlock();
|
|
}
|
|
|
|
MeshFeatureProcessor::MeshHandle MeshFeatureProcessor::AcquireMesh(
|
|
const MeshHandleDescriptor& descriptor,
|
|
const MaterialAssignmentMap& materials)
|
|
{
|
|
AZ_PROFILE_SCOPE(AzRender, "MeshFeatureProcessor: AcquireMesh");
|
|
|
|
// don't need to check the concurrency during emplace() because the StableDynamicArray won't move the other elements during insertion
|
|
MeshHandle meshDataHandle = m_modelData.emplace();
|
|
|
|
meshDataHandle->m_descriptor = descriptor;
|
|
meshDataHandle->m_scene = GetParentScene();
|
|
meshDataHandle->m_materialAssignments = materials;
|
|
meshDataHandle->m_objectId = m_transformService->ReserveObjectId();
|
|
meshDataHandle->m_originalModelAsset = descriptor.m_modelAsset;
|
|
meshDataHandle->m_meshLoader = AZStd::make_unique<ModelDataInstance::MeshLoader>(descriptor.m_modelAsset, &*meshDataHandle);
|
|
|
|
return meshDataHandle;
|
|
}
|
|
|
|
MeshFeatureProcessor::MeshHandle MeshFeatureProcessor::AcquireMesh(
|
|
const MeshHandleDescriptor& descriptor,
|
|
const Data::Instance<RPI::Material>& material)
|
|
{
|
|
Render::MaterialAssignmentMap materials;
|
|
Render::MaterialAssignment& defaultMaterial = materials[AZ::Render::DefaultMaterialAssignmentId];
|
|
defaultMaterial.m_materialInstance = material;
|
|
|
|
return AcquireMesh(descriptor, materials);
|
|
}
|
|
|
|
bool MeshFeatureProcessor::ReleaseMesh(MeshHandle& meshHandle)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->m_meshLoader.reset();
|
|
meshHandle->DeInit();
|
|
m_transformService->ReleaseObjectId(meshHandle->m_objectId);
|
|
|
|
AZStd::concurrency_check_scope scopeCheck(m_meshDataChecker);
|
|
m_modelData.erase(meshHandle);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MeshFeatureProcessor::MeshHandle MeshFeatureProcessor::CloneMesh(const MeshHandle& meshHandle)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
MeshHandle clone = AcquireMesh(meshHandle->m_descriptor, meshHandle->m_materialAssignments);
|
|
return clone;
|
|
}
|
|
return MeshFeatureProcessor::MeshHandle();
|
|
}
|
|
|
|
Data::Instance<RPI::Model> MeshFeatureProcessor::GetModel(const MeshHandle& meshHandle) const
|
|
{
|
|
return meshHandle.IsValid() ? meshHandle->m_model : nullptr;
|
|
}
|
|
|
|
Data::Asset<RPI::ModelAsset> MeshFeatureProcessor::GetModelAsset(const MeshHandle& meshHandle) const
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
return meshHandle->m_originalModelAsset;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
const AZStd::vector<Data::Instance<RPI::ShaderResourceGroup>>& MeshFeatureProcessor::GetObjectSrgs(const MeshHandle& meshHandle) const
|
|
{
|
|
static AZStd::vector<Data::Instance<RPI::ShaderResourceGroup>> staticEmptyList;
|
|
return meshHandle.IsValid() ? meshHandle->m_objectSrgList : staticEmptyList;
|
|
}
|
|
|
|
void MeshFeatureProcessor::QueueObjectSrgForCompile(const MeshHandle& meshHandle) const
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->m_objectSrgNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetMaterialAssignmentMap(const MeshHandle& meshHandle, const Data::Instance<RPI::Material>& material)
|
|
{
|
|
Render::MaterialAssignmentMap materials;
|
|
Render::MaterialAssignment& defaultMaterial = materials[AZ::Render::DefaultMaterialAssignmentId];
|
|
defaultMaterial.m_materialInstance = material;
|
|
|
|
return SetMaterialAssignmentMap(meshHandle, materials);
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetMaterialAssignmentMap(const MeshHandle& meshHandle, const MaterialAssignmentMap& materials)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
if (meshHandle->m_model)
|
|
{
|
|
Data::Instance<RPI::Model> model = meshHandle->m_model;
|
|
meshHandle->DeInit();
|
|
meshHandle->m_materialAssignments = materials;
|
|
meshHandle->Init(model);
|
|
}
|
|
else
|
|
{
|
|
meshHandle->m_materialAssignments = materials;
|
|
}
|
|
|
|
meshHandle->m_objectSrgNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
const MaterialAssignmentMap& MeshFeatureProcessor::GetMaterialAssignmentMap(const MeshHandle& meshHandle) const
|
|
{
|
|
return meshHandle.IsValid() ? meshHandle->m_materialAssignments : DefaultMaterialAssignmentMap;
|
|
}
|
|
|
|
void MeshFeatureProcessor::ConnectModelChangeEventHandler(const MeshHandle& meshHandle, ModelChangedEvent::Handler& handler)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
handler.Connect(meshHandle->m_meshLoader->GetModelChangedEvent());
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetTransform(const MeshHandle& meshHandle, const AZ::Transform& transform, const AZ::Vector3& nonUniformScale)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
ModelDataInstance& modelData = *meshHandle;
|
|
modelData.m_cullBoundsNeedsUpdate = true;
|
|
modelData.m_objectSrgNeedsUpdate = true;
|
|
|
|
m_transformService->SetTransformForId(meshHandle->m_objectId, transform, nonUniformScale);
|
|
|
|
// ray tracing data needs to be updated with the new transform
|
|
if (m_rayTracingFeatureProcessor)
|
|
{
|
|
m_rayTracingFeatureProcessor->SetMeshTransform(meshHandle->m_objectId, transform, nonUniformScale);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
ModelDataInstance& modelData = *meshHandle;
|
|
modelData.m_aabb = localAabb;
|
|
modelData.m_cullBoundsNeedsUpdate = true;
|
|
modelData.m_objectSrgNeedsUpdate = true;
|
|
}
|
|
};
|
|
|
|
AZ::Aabb MeshFeatureProcessor::GetLocalAabb(const MeshHandle& meshHandle) const
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
return meshHandle->m_aabb;
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Invalid mesh handle");
|
|
return Aabb::CreateNull();
|
|
}
|
|
}
|
|
|
|
Transform MeshFeatureProcessor::GetTransform(const MeshHandle& meshHandle)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
return m_transformService->GetTransformForId(meshHandle->m_objectId);
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Invalid mesh handle");
|
|
return Transform::CreateIdentity();
|
|
}
|
|
}
|
|
|
|
Vector3 MeshFeatureProcessor::GetNonUniformScale(const MeshHandle& meshHandle)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
return m_transformService->GetNonUniformScaleForId(meshHandle->m_objectId);
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Invalid mesh handle");
|
|
return Vector3::CreateOne();
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetSortKey(const MeshHandle& meshHandle, RHI::DrawItemSortKey sortKey)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->SetSortKey(sortKey);
|
|
}
|
|
}
|
|
|
|
RHI::DrawItemSortKey MeshFeatureProcessor::GetSortKey(const MeshHandle& meshHandle) const
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
return meshHandle->GetSortKey();
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Invalid mesh handle");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetMeshLodConfiguration(const MeshHandle& meshHandle, const RPI::Cullable::LodConfiguration& meshLodConfig)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->SetMeshLodConfiguration(meshLodConfig);
|
|
}
|
|
}
|
|
|
|
RPI::Cullable::LodConfiguration MeshFeatureProcessor::GetMeshLodConfiguration(const MeshHandle& meshHandle) const
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
return meshHandle->GetMeshLodConfiguration();
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Invalid mesh handle");
|
|
return {RPI::Cullable::LodType::Default, 0, 0.0f, 0.0f };
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetExcludeFromReflectionCubeMaps(const MeshHandle& meshHandle, bool excludeFromReflectionCubeMaps)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->m_excludeFromReflectionCubeMaps = excludeFromReflectionCubeMaps;
|
|
if (excludeFromReflectionCubeMaps)
|
|
{
|
|
meshHandle->m_cullable.m_cullData.m_hideFlags |= RPI::View::UsageReflectiveCubeMap;
|
|
}
|
|
else
|
|
{
|
|
meshHandle->m_cullable.m_cullData.m_hideFlags &= ~RPI::View::UsageReflectiveCubeMap;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetRayTracingEnabled(const MeshHandle& meshHandle, bool rayTracingEnabled)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
// update the ray tracing data based on the current state and the new state
|
|
if (rayTracingEnabled && !meshHandle->m_descriptor.m_isRayTracingEnabled)
|
|
{
|
|
// add to ray tracing
|
|
meshHandle->SetRayTracingData();
|
|
}
|
|
else if (!rayTracingEnabled && meshHandle->m_descriptor.m_isRayTracingEnabled)
|
|
{
|
|
// remove from ray tracing
|
|
if (m_rayTracingFeatureProcessor)
|
|
{
|
|
m_rayTracingFeatureProcessor->RemoveMesh(meshHandle->m_objectId);
|
|
}
|
|
}
|
|
|
|
// set new state
|
|
meshHandle->m_descriptor.m_isRayTracingEnabled = rayTracingEnabled;
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetVisible(const MeshHandle& meshHandle, bool visible)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->SetVisible(visible);
|
|
SetRayTracingEnabled(meshHandle, visible);
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::SetUseForwardPassIblSpecular(const MeshHandle& meshHandle, bool useForwardPassIblSpecular)
|
|
{
|
|
if (meshHandle.IsValid())
|
|
{
|
|
meshHandle->m_descriptor.m_useForwardPassIblSpecular = useForwardPassIblSpecular;
|
|
meshHandle->m_objectSrgNeedsUpdate = true;
|
|
|
|
if (meshHandle->m_model)
|
|
{
|
|
const size_t modelLodCount = meshHandle->m_model->GetLodCount();
|
|
for (size_t modelLodIndex = 0; modelLodIndex < modelLodCount; ++modelLodIndex)
|
|
{
|
|
meshHandle->BuildDrawPacketList(modelLodIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshFeatureProcessor::ForceRebuildDrawPackets([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
|
{
|
|
m_forceRebuildDrawPackets = true;
|
|
}
|
|
|
|
void MeshFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline)
|
|
{
|
|
m_forceRebuildDrawPackets = true;;
|
|
}
|
|
|
|
void MeshFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] RPI::RenderPipeline* pipeline)
|
|
{
|
|
m_forceRebuildDrawPackets = true;
|
|
}
|
|
|
|
void MeshFeatureProcessor::UpdateMeshReflectionProbes()
|
|
{
|
|
// we need to rebuild the Srg for any meshes that are using the forward pass IBL specular option
|
|
for (auto& meshInstance : m_modelData)
|
|
{
|
|
if (meshInstance.m_descriptor.m_useForwardPassIblSpecular)
|
|
{
|
|
meshInstance.m_objectSrgNeedsUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ModelDataInstance::MeshLoader...
|
|
ModelDataInstance::MeshLoader::MeshLoader(const Data::Asset<RPI::ModelAsset>& modelAsset, ModelDataInstance* parent)
|
|
: m_modelAsset(modelAsset)
|
|
, m_parent(parent)
|
|
{
|
|
if (!m_modelAsset.GetId().IsValid())
|
|
{
|
|
AZ_Error("ModelDataInstance::MeshLoader", false, "Invalid model asset Id.");
|
|
return;
|
|
}
|
|
|
|
if (!m_modelAsset.IsReady())
|
|
{
|
|
m_modelAsset.QueueLoad();
|
|
}
|
|
|
|
Data::AssetBus::Handler::BusConnect(modelAsset.GetId());
|
|
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
|
|
}
|
|
|
|
ModelDataInstance::MeshLoader::~MeshLoader()
|
|
{
|
|
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
|
|
Data::AssetBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
MeshFeatureProcessorInterface::ModelChangedEvent& ModelDataInstance::MeshLoader::GetModelChangedEvent()
|
|
{
|
|
return m_modelChangedEvent;
|
|
}
|
|
|
|
//! AssetBus::Handler overrides...
|
|
void ModelDataInstance::MeshLoader::OnAssetReady(Data::Asset<Data::AssetData> asset)
|
|
{
|
|
Data::Asset<RPI::ModelAsset> modelAsset = asset;
|
|
|
|
// Assign the fully loaded asset back to the mesh handle to not only hold asset id, but the actual data as well.
|
|
m_parent->m_originalModelAsset = asset;
|
|
|
|
Data::Instance<RPI::Model> model;
|
|
// Check if a requires cloning callback got set and if so check if cloning the model asset is requested.
|
|
if (m_parent->m_descriptor.m_requiresCloneCallback &&
|
|
m_parent->m_descriptor.m_requiresCloneCallback(modelAsset))
|
|
{
|
|
// Clone the model asset to force create another model instance.
|
|
AZ::Data::AssetId newId(AZ::Uuid::CreateRandom(), /*subId=*/0);
|
|
Data::Asset<RPI::ModelAsset> clonedAsset;
|
|
if (AZ::RPI::ModelAssetCreator::Clone(modelAsset, clonedAsset, newId))
|
|
{
|
|
model = RPI::Model::FindOrCreate(clonedAsset);
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("ModelDataInstance", false, "Cannot clone model for '%s'. Cloth simulation results won't be individual per entity.", modelAsset->GetName().GetCStr());
|
|
model = RPI::Model::FindOrCreate(modelAsset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Static mesh, no cloth buffer present.
|
|
model = RPI::Model::FindOrCreate(modelAsset);
|
|
}
|
|
|
|
if (model)
|
|
{
|
|
m_parent->RemoveRayTracingData();
|
|
m_parent->Init(model);
|
|
m_modelChangedEvent.Signal(AZStd::move(model));
|
|
}
|
|
else
|
|
{
|
|
//when running with null renderer, the RPI::Model::FindOrCreate(...) is expected to return nullptr, so suppress this error.
|
|
AZ_Error(
|
|
"ModelDataInstance::OnAssetReady", RHI::IsNullRenderer(), "Failed to create model instance for '%s'",
|
|
asset.GetHint().c_str());
|
|
}
|
|
}
|
|
|
|
|
|
void ModelDataInstance::MeshLoader::OnModelReloaded(Data::Asset<Data::AssetData> asset)
|
|
{
|
|
OnAssetReady(asset);
|
|
}
|
|
|
|
void ModelDataInstance::MeshLoader::OnAssetError(Data::Asset<Data::AssetData> asset)
|
|
{
|
|
// Note: m_modelAsset and asset represents same asset, but only m_modelAsset contains the file path in its hint from serialization
|
|
AZ_Error(
|
|
"ModelDataInstance::MeshLoader", false, "Failed to load asset %s. It may be missing, or not be finished processing",
|
|
m_modelAsset.GetHint().c_str());
|
|
|
|
AzFramework::AssetSystemRequestBus::Broadcast(
|
|
&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetByUuid, m_modelAsset.GetId().m_guid);
|
|
}
|
|
|
|
void ModelDataInstance::MeshLoader::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
|
|
{
|
|
if (assetId == m_modelAsset.GetId())
|
|
{
|
|
Data::Asset<RPI::ModelAsset> modelAssetReference = m_modelAsset;
|
|
|
|
// If the asset was modified, reload it
|
|
AZ::SystemTickBus::QueueFunction(
|
|
[=]() mutable
|
|
{
|
|
ModelReloaderSystemInterface::Get()->ReloadModel(modelAssetReference, m_modelReloadedEventHandler);
|
|
});
|
|
}
|
|
}
|
|
|
|
void ModelDataInstance::MeshLoader::OnCatalogAssetAdded(const AZ::Data::AssetId& assetId)
|
|
{
|
|
if (assetId == m_modelAsset.GetId())
|
|
{
|
|
Data::Asset<RPI::ModelAsset> modelAssetReference = m_modelAsset;
|
|
|
|
// If the asset didn't exist in the catalog when it first attempted to load, we need to try loading it again
|
|
AZ::SystemTickBus::QueueFunction(
|
|
[=]() mutable
|
|
{
|
|
ModelReloaderSystemInterface::Get()->ReloadModel(modelAssetReference, m_modelReloadedEventHandler);
|
|
});
|
|
}
|
|
}
|
|
|
|
// ModelDataInstance...
|
|
|
|
void ModelDataInstance::DeInit()
|
|
{
|
|
m_scene->GetCullingScene()->UnregisterCullable(m_cullable);
|
|
|
|
RemoveRayTracingData();
|
|
|
|
m_drawPacketListsByLod.clear();
|
|
m_materialAssignments.clear();
|
|
m_objectSrgList = {};
|
|
m_model = {};
|
|
}
|
|
|
|
void ModelDataInstance::Init(Data::Instance<RPI::Model> model)
|
|
{
|
|
m_model = model;
|
|
const size_t modelLodCount = m_model->GetLodCount();
|
|
m_drawPacketListsByLod.resize(modelLodCount);
|
|
for (size_t modelLodIndex = 0; modelLodIndex < modelLodCount; ++modelLodIndex)
|
|
{
|
|
BuildDrawPacketList(modelLodIndex);
|
|
}
|
|
|
|
for(auto& objectSrg : m_objectSrgList)
|
|
{
|
|
// Set object Id once since it never changes
|
|
RHI::ShaderInputNameIndex objectIdIndex = "m_objectId";
|
|
objectSrg->SetConstant(objectIdIndex, m_objectId.GetIndex());
|
|
objectIdIndex.AssertValid();
|
|
}
|
|
|
|
if (m_descriptor.m_isRayTracingEnabled)
|
|
{
|
|
SetRayTracingData();
|
|
}
|
|
|
|
m_aabb = model->GetModelAsset()->GetAabb();
|
|
|
|
m_cullableNeedsRebuild = true;
|
|
m_cullBoundsNeedsUpdate = true;
|
|
m_objectSrgNeedsUpdate = true;
|
|
}
|
|
|
|
void ModelDataInstance::BuildDrawPacketList(size_t modelLodIndex)
|
|
{
|
|
RPI::ModelLod& modelLod = *m_model->GetLods()[modelLodIndex];
|
|
const size_t meshCount = modelLod.GetMeshes().size();
|
|
|
|
ModelDataInstance::DrawPacketList& drawPacketListOut = m_drawPacketListsByLod[modelLodIndex];
|
|
drawPacketListOut.clear();
|
|
drawPacketListOut.reserve(meshCount);
|
|
|
|
m_hasForwardPassIblSpecularMaterial = false;
|
|
|
|
for (size_t meshIndex = 0; meshIndex < meshCount; ++meshIndex)
|
|
{
|
|
const RPI::ModelLod::Mesh& mesh = modelLod.GetMeshes()[meshIndex];
|
|
|
|
Data::Instance<RPI::Material> material = mesh.m_material;
|
|
|
|
// Determine if there is a material override specified for this sub mesh
|
|
const MaterialAssignmentId materialAssignmentId(modelLodIndex, mesh.m_materialSlotStableId);
|
|
const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId);
|
|
if (materialAssignment.m_materialInstance.get())
|
|
{
|
|
material = materialAssignment.m_materialInstance;
|
|
}
|
|
|
|
if (!material)
|
|
{
|
|
AZ_Warning("MeshFeatureProcessor", false, "No material provided for mesh. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
auto& objectSrgLayout = material->GetAsset()->GetObjectSrgLayout();
|
|
|
|
if (!objectSrgLayout)
|
|
{
|
|
AZ_Warning("MeshFeatureProcessor", false, "No per-object ShaderResourceGroup found.");
|
|
continue;
|
|
}
|
|
|
|
Data::Instance<RPI::ShaderResourceGroup> meshObjectSrg;
|
|
|
|
// See if the object SRG for this mesh is already in our list of object SRGs
|
|
for (auto& objectSrgIter : m_objectSrgList)
|
|
{
|
|
if (objectSrgIter->GetLayout()->GetHash() == objectSrgLayout->GetHash())
|
|
{
|
|
meshObjectSrg = objectSrgIter;
|
|
}
|
|
}
|
|
|
|
// If the object SRG for this mesh was not already in the list, create it and add it to the list
|
|
if (!meshObjectSrg)
|
|
{
|
|
auto& shaderAsset = material->GetAsset()->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg();
|
|
meshObjectSrg = RPI::ShaderResourceGroup::Create(shaderAsset, objectSrgLayout->GetName());
|
|
if (!meshObjectSrg)
|
|
{
|
|
AZ_Warning("MeshFeatureProcessor", false, "Failed to create a new shader resource group, skipping.");
|
|
continue;
|
|
}
|
|
m_objectSrgList.push_back(meshObjectSrg);
|
|
}
|
|
|
|
// setup the mesh draw packet
|
|
RPI::MeshDrawPacket drawPacket(modelLod, meshIndex, material, meshObjectSrg, materialAssignment.m_matModUvOverrides);
|
|
|
|
// set the shader option to select forward pass IBL specular if necessary
|
|
if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ m_descriptor.m_useForwardPassIblSpecular }))
|
|
{
|
|
AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet");
|
|
}
|
|
|
|
bool materialRequiresForwardPassIblSpecular = MaterialRequiresForwardPassIblSpecular(material);
|
|
|
|
// track whether any materials in this mesh require ForwardPassIblSpecular, we need this information when the ObjectSrg is updated
|
|
m_hasForwardPassIblSpecularMaterial |= materialRequiresForwardPassIblSpecular;
|
|
|
|
// stencil bits
|
|
uint8_t stencilRef = m_descriptor.m_useForwardPassIblSpecular || materialRequiresForwardPassIblSpecular ? Render::StencilRefs::None : Render::StencilRefs::UseIBLSpecularPass;
|
|
stencilRef |= Render::StencilRefs::UseDiffuseGIPass;
|
|
|
|
drawPacket.SetStencilRef(stencilRef);
|
|
drawPacket.SetSortKey(m_sortKey);
|
|
drawPacket.Update(*m_scene, false);
|
|
drawPacketListOut.emplace_back(AZStd::move(drawPacket));
|
|
}
|
|
}
|
|
|
|
void ModelDataInstance::SetRayTracingData()
|
|
{
|
|
if (!m_model)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RayTracingFeatureProcessor* rayTracingFeatureProcessor = m_scene->GetFeatureProcessor<RayTracingFeatureProcessor>();
|
|
if (!rayTracingFeatureProcessor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const AZStd::array_view<Data::Instance<RPI::ModelLod>>& modelLods = m_model->GetLods();
|
|
if (modelLods.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// use the lowest LOD for raytracing
|
|
uint32_t rayTracingLod = aznumeric_cast<uint32_t>(modelLods.size() - 1);
|
|
const Data::Instance<RPI::ModelLod>& modelLod = modelLods[rayTracingLod];
|
|
|
|
// setup a stream layout and shader input contract for the vertex streams
|
|
static const char* PositionSemantic = "POSITION";
|
|
static const char* NormalSemantic = "NORMAL";
|
|
static const char* TangentSemantic = "TANGENT";
|
|
static const char* BitangentSemantic = "BITANGENT";
|
|
static const char* UVSemantic = "UV";
|
|
static const RHI::Format PositionStreamFormat = RHI::Format::R32G32B32_FLOAT;
|
|
static const RHI::Format NormalStreamFormat = RHI::Format::R32G32B32_FLOAT;
|
|
static const RHI::Format TangentStreamFormat = RHI::Format::R32G32B32A32_FLOAT;
|
|
static const RHI::Format BitangentStreamFormat = RHI::Format::R32G32B32_FLOAT;
|
|
static const RHI::Format UVStreamFormat = RHI::Format::R32G32_FLOAT;
|
|
|
|
RHI::InputStreamLayoutBuilder layoutBuilder;
|
|
layoutBuilder.AddBuffer()->Channel(PositionSemantic, PositionStreamFormat);
|
|
layoutBuilder.AddBuffer()->Channel(NormalSemantic, NormalStreamFormat);
|
|
layoutBuilder.AddBuffer()->Channel(UVSemantic, UVStreamFormat);
|
|
layoutBuilder.AddBuffer()->Channel(TangentSemantic, TangentStreamFormat);
|
|
layoutBuilder.AddBuffer()->Channel(BitangentSemantic, BitangentStreamFormat);
|
|
RHI::InputStreamLayout inputStreamLayout = layoutBuilder.End();
|
|
|
|
RPI::ShaderInputContract::StreamChannelInfo positionStreamChannelInfo;
|
|
positionStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(PositionSemantic));
|
|
positionStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(PositionStreamFormat);
|
|
|
|
RPI::ShaderInputContract::StreamChannelInfo normalStreamChannelInfo;
|
|
normalStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(NormalSemantic));
|
|
normalStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(NormalStreamFormat);
|
|
|
|
RPI::ShaderInputContract::StreamChannelInfo tangentStreamChannelInfo;
|
|
tangentStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(TangentSemantic));
|
|
tangentStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(TangentStreamFormat);
|
|
tangentStreamChannelInfo.m_isOptional = true;
|
|
|
|
RPI::ShaderInputContract::StreamChannelInfo bitangentStreamChannelInfo;
|
|
bitangentStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(BitangentSemantic));
|
|
bitangentStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(BitangentStreamFormat);
|
|
bitangentStreamChannelInfo.m_isOptional = true;
|
|
|
|
RPI::ShaderInputContract::StreamChannelInfo uvStreamChannelInfo;
|
|
uvStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(UVSemantic));
|
|
uvStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(UVStreamFormat);
|
|
uvStreamChannelInfo.m_isOptional = true;
|
|
|
|
RPI::ShaderInputContract shaderInputContract;
|
|
shaderInputContract.m_streamChannels.emplace_back(positionStreamChannelInfo);
|
|
shaderInputContract.m_streamChannels.emplace_back(normalStreamChannelInfo);
|
|
shaderInputContract.m_streamChannels.emplace_back(tangentStreamChannelInfo);
|
|
shaderInputContract.m_streamChannels.emplace_back(bitangentStreamChannelInfo);
|
|
shaderInputContract.m_streamChannels.emplace_back(uvStreamChannelInfo);
|
|
|
|
// setup the raytracing data for each sub-mesh
|
|
const size_t meshCount = modelLod->GetMeshes().size();
|
|
RayTracingFeatureProcessor::SubMeshVector subMeshes;
|
|
for (uint32_t meshIndex = 0; meshIndex < meshCount; ++meshIndex)
|
|
{
|
|
const RPI::ModelLod::Mesh& mesh = modelLod->GetMeshes()[meshIndex];
|
|
|
|
// retrieve the material
|
|
Data::Instance<RPI::Material> material = mesh.m_material;
|
|
|
|
const MaterialAssignmentId materialAssignmentId(rayTracingLod, mesh.m_materialSlotStableId);
|
|
const MaterialAssignment& materialAssignment = GetMaterialAssignmentFromMapWithFallback(m_materialAssignments, materialAssignmentId);
|
|
if (materialAssignment.m_materialInstance.get())
|
|
{
|
|
material = materialAssignment.m_materialInstance;
|
|
}
|
|
|
|
if (!material)
|
|
{
|
|
AZ_Warning("MeshFeatureProcessor", false, "No material provided for mesh. Skipping.");
|
|
continue;
|
|
}
|
|
|
|
// retrieve vertex/index buffers
|
|
RPI::ModelLod::StreamBufferViewList streamBufferViews;
|
|
[[maybe_unused]] bool result = modelLod->GetStreamsForMesh(
|
|
inputStreamLayout,
|
|
streamBufferViews,
|
|
nullptr,
|
|
shaderInputContract,
|
|
meshIndex,
|
|
materialAssignment.m_matModUvOverrides,
|
|
material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap());
|
|
AZ_Assert(result, "Failed to retrieve mesh stream buffer views");
|
|
|
|
// note that the element count is the size of the entire buffer, even though this mesh may only
|
|
// occupy a portion of the vertex buffer. This is necessary since we are accessing it using
|
|
// a ByteAddressBuffer in the raytracing shaders and passing the byte offset to the shader in a constant buffer.
|
|
uint32_t positionBufferByteCount = static_cast<uint32_t>(const_cast<RHI::Buffer*>(streamBufferViews[0].GetBuffer())->GetDescriptor().m_byteCount);
|
|
RHI::BufferViewDescriptor positionBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, positionBufferByteCount);
|
|
|
|
uint32_t normalBufferByteCount = static_cast<uint32_t>(const_cast<RHI::Buffer*>(streamBufferViews[1].GetBuffer())->GetDescriptor().m_byteCount);
|
|
RHI::BufferViewDescriptor normalBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, normalBufferByteCount);
|
|
|
|
uint32_t tangentBufferByteCount = static_cast<uint32_t>(const_cast<RHI::Buffer*>(streamBufferViews[2].GetBuffer())->GetDescriptor().m_byteCount);
|
|
RHI::BufferViewDescriptor tangentBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, tangentBufferByteCount);
|
|
|
|
uint32_t bitangentBufferByteCount = static_cast<uint32_t>(const_cast<RHI::Buffer*>(streamBufferViews[3].GetBuffer())->GetDescriptor().m_byteCount);
|
|
RHI::BufferViewDescriptor bitangentBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, bitangentBufferByteCount);
|
|
|
|
uint32_t uvBufferByteCount = static_cast<uint32_t>(const_cast<RHI::Buffer*>(streamBufferViews[4].GetBuffer())->GetDescriptor().m_byteCount);
|
|
RHI::BufferViewDescriptor uvBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, uvBufferByteCount);
|
|
|
|
const RHI::IndexBufferView& indexBufferView = mesh.m_indexBufferView;
|
|
uint32_t indexElementSize = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? 2 : 4;
|
|
uint32_t indexElementCount = (uint32_t)indexBufferView.GetBuffer()->GetDescriptor().m_byteCount / indexElementSize;
|
|
RHI::BufferViewDescriptor indexBufferDescriptor;
|
|
indexBufferDescriptor.m_elementOffset = 0;
|
|
indexBufferDescriptor.m_elementCount = indexElementCount;
|
|
indexBufferDescriptor.m_elementSize = indexElementSize;
|
|
indexBufferDescriptor.m_elementFormat = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? RHI::Format::R16_UINT : RHI::Format::R32_UINT;
|
|
|
|
// set the SubMesh data to pass to the RayTracingFeatureProcessor, starting with vertex/index data
|
|
RayTracingFeatureProcessor::SubMesh subMesh;
|
|
subMesh.m_positionFormat = PositionStreamFormat;
|
|
subMesh.m_positionVertexBufferView = streamBufferViews[0];
|
|
subMesh.m_positionShaderBufferView = const_cast<RHI::Buffer*>(streamBufferViews[0].GetBuffer())->GetBufferView(positionBufferDescriptor);
|
|
|
|
subMesh.m_normalFormat = NormalStreamFormat;
|
|
subMesh.m_normalVertexBufferView = streamBufferViews[1];
|
|
subMesh.m_normalShaderBufferView = const_cast<RHI::Buffer*>(streamBufferViews[1].GetBuffer())->GetBufferView(normalBufferDescriptor);
|
|
|
|
if (tangentBufferByteCount > 0)
|
|
{
|
|
subMesh.m_bufferFlags |= RayTracingSubMeshBufferFlags::Tangent;
|
|
subMesh.m_tangentFormat = TangentStreamFormat;
|
|
subMesh.m_tangentVertexBufferView = streamBufferViews[2];
|
|
subMesh.m_tangentShaderBufferView = const_cast<RHI::Buffer*>(streamBufferViews[2].GetBuffer())->GetBufferView(tangentBufferDescriptor);
|
|
}
|
|
|
|
if (bitangentBufferByteCount > 0)
|
|
{
|
|
subMesh.m_bufferFlags |= RayTracingSubMeshBufferFlags::Bitangent;
|
|
subMesh.m_bitangentFormat = BitangentStreamFormat;
|
|
subMesh.m_bitangentVertexBufferView = streamBufferViews[3];
|
|
subMesh.m_bitangentShaderBufferView = const_cast<RHI::Buffer*>(streamBufferViews[3].GetBuffer())->GetBufferView(bitangentBufferDescriptor);
|
|
}
|
|
|
|
if (uvBufferByteCount > 0)
|
|
{
|
|
subMesh.m_bufferFlags |= RayTracingSubMeshBufferFlags::UV;
|
|
subMesh.m_uvFormat = UVStreamFormat;
|
|
subMesh.m_uvVertexBufferView = streamBufferViews[4];
|
|
subMesh.m_uvShaderBufferView = const_cast<RHI::Buffer*>(streamBufferViews[4].GetBuffer())->GetBufferView(uvBufferDescriptor);
|
|
}
|
|
|
|
subMesh.m_indexBufferView = mesh.m_indexBufferView;
|
|
subMesh.m_indexShaderBufferView = const_cast<RHI::Buffer*>(mesh.m_indexBufferView.GetBuffer())->GetBufferView(indexBufferDescriptor);
|
|
|
|
// add material data
|
|
if (material)
|
|
{
|
|
// irradiance color
|
|
RPI::MaterialPropertyIndex propertyIndex = material->FindPropertyIndex(AZ::Name("irradiance.color"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
subMesh.m_irradianceColor = material->GetPropertyValue<AZ::Color>(propertyIndex);
|
|
}
|
|
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("irradiance.factor"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
subMesh.m_irradianceColor *= material->GetPropertyValue<float>(propertyIndex);
|
|
}
|
|
|
|
// base color
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.color"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
subMesh.m_baseColor = material->GetPropertyValue<AZ::Color>(propertyIndex);
|
|
}
|
|
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.factor"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
subMesh.m_baseColor *= material->GetPropertyValue<float>(propertyIndex);
|
|
}
|
|
|
|
// metallic
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("metallic.factor"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
subMesh.m_metallicFactor = material->GetPropertyValue<float>(propertyIndex);
|
|
}
|
|
|
|
// roughness
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("roughness.factor"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
subMesh.m_roughnessFactor = material->GetPropertyValue<float>(propertyIndex);
|
|
}
|
|
|
|
// textures
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.textureMap"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
Data::Instance<RPI::Image> image = material->GetPropertyValue<Data::Instance<RPI::Image>>(propertyIndex);
|
|
if (image.get())
|
|
{
|
|
subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::BaseColor;
|
|
subMesh.m_baseColorImageView = image->GetImageView();
|
|
}
|
|
}
|
|
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("normal.textureMap"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
Data::Instance<RPI::Image> image = material->GetPropertyValue<Data::Instance<RPI::Image>>(propertyIndex);
|
|
if (image.get())
|
|
{
|
|
subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Normal;
|
|
subMesh.m_normalImageView = image->GetImageView();
|
|
}
|
|
}
|
|
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("metallic.textureMap"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
Data::Instance<RPI::Image> image = material->GetPropertyValue<Data::Instance<RPI::Image>>(propertyIndex);
|
|
if (image.get())
|
|
{
|
|
subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Metallic;
|
|
subMesh.m_metallicImageView = image->GetImageView();
|
|
}
|
|
}
|
|
|
|
propertyIndex = material->FindPropertyIndex(AZ::Name("roughness.textureMap"));
|
|
if (propertyIndex.IsValid())
|
|
{
|
|
Data::Instance<RPI::Image> image = material->GetPropertyValue<Data::Instance<RPI::Image>>(propertyIndex);
|
|
if (image.get())
|
|
{
|
|
subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Roughness;
|
|
subMesh.m_roughnessImageView = image->GetImageView();
|
|
}
|
|
}
|
|
}
|
|
|
|
subMeshes.push_back(subMesh);
|
|
}
|
|
|
|
rayTracingFeatureProcessor->SetMesh(m_objectId, m_model->GetModelAsset()->GetId(), subMeshes);
|
|
}
|
|
|
|
void ModelDataInstance::RemoveRayTracingData()
|
|
{
|
|
// remove from ray tracing
|
|
RayTracingFeatureProcessor* rayTracingFeatureProcessor = m_scene->GetFeatureProcessor<RayTracingFeatureProcessor>();
|
|
if (rayTracingFeatureProcessor)
|
|
{
|
|
rayTracingFeatureProcessor->RemoveMesh(m_objectId);
|
|
}
|
|
}
|
|
|
|
void ModelDataInstance::SetSortKey(RHI::DrawItemSortKey sortKey)
|
|
{
|
|
m_sortKey = sortKey;
|
|
for (auto& drawPacketList : m_drawPacketListsByLod)
|
|
{
|
|
for (auto& drawPacket : drawPacketList)
|
|
{
|
|
drawPacket.SetSortKey(sortKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
RHI::DrawItemSortKey ModelDataInstance::GetSortKey() const
|
|
{
|
|
return m_sortKey;
|
|
}
|
|
|
|
void ModelDataInstance::SetMeshLodConfiguration(RPI::Cullable::LodConfiguration meshLodConfig)
|
|
{
|
|
m_cullable.m_lodData.m_lodConfiguration = meshLodConfig;
|
|
}
|
|
|
|
RPI::Cullable::LodConfiguration ModelDataInstance::GetMeshLodConfiguration() const
|
|
{
|
|
return m_cullable.m_lodData.m_lodConfiguration;
|
|
}
|
|
|
|
void ModelDataInstance::UpdateDrawPackets(bool forceUpdate /*= false*/)
|
|
{
|
|
AZ_PROFILE_SCOPE(AzRender, "ModelDataInstance:: UpdateDrawPackets");
|
|
for (auto& drawPacketList : m_drawPacketListsByLod)
|
|
{
|
|
for (auto& drawPacket : drawPacketList)
|
|
{
|
|
if (drawPacket.Update(*m_scene, forceUpdate))
|
|
{
|
|
m_cullableNeedsRebuild = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelDataInstance::BuildCullable()
|
|
{
|
|
AZ_PROFILE_SCOPE(AzRender, "ModelDataInstance: BuildCullable");
|
|
AZ_Assert(m_cullableNeedsRebuild, "This function only needs to be called if the cullable to be rebuilt");
|
|
AZ_Assert(m_model, "The model has not finished loading yet");
|
|
|
|
RPI::Cullable::CullData& cullData = m_cullable.m_cullData;
|
|
RPI::Cullable::LodData& lodData = m_cullable.m_lodData;
|
|
|
|
const Aabb& localAabb = m_aabb;
|
|
lodData.m_lodSelectionRadius = 0.5f*localAabb.GetExtents().GetMaxElement();
|
|
|
|
const size_t modelLodCount = m_model->GetLodCount();
|
|
const auto& lodAssets = m_model->GetModelAsset()->GetLodAssets();
|
|
AZ_Assert(lodAssets.size() == modelLodCount, "Number of asset lods must match number of model lods");
|
|
|
|
lodData.m_lods.resize(modelLodCount);
|
|
cullData.m_drawListMask.reset();
|
|
|
|
const size_t lodCount = lodAssets.size();
|
|
for (size_t lodIndex = 0; lodIndex < lodCount; ++lodIndex)
|
|
{
|
|
//initialize the lod
|
|
RPI::Cullable::LodData::Lod& lod = lodData.m_lods[lodIndex];
|
|
if (lodIndex == 0)
|
|
{
|
|
//first lod
|
|
lod.m_screenCoverageMax = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
//every other lod: use the previous lod's min
|
|
lod.m_screenCoverageMax = AZStd::GetMax(lodData.m_lods[lodIndex - 1].m_screenCoverageMin, lodData.m_lodConfiguration.m_minimumScreenCoverage);
|
|
}
|
|
|
|
if (lodIndex < lodAssets.size() - 1)
|
|
{
|
|
//first and middle lods: compute a stepdown value for the min
|
|
lod.m_screenCoverageMin = AZStd::GetMax(lodData.m_lodConfiguration.m_qualityDecayRate * lod.m_screenCoverageMax, lodData.m_lodConfiguration.m_minimumScreenCoverage);
|
|
}
|
|
else
|
|
{
|
|
//last lod: use MinimumScreenCoverage for the min
|
|
lod.m_screenCoverageMin = lodData.m_lodConfiguration.m_minimumScreenCoverage;
|
|
}
|
|
|
|
lod.m_drawPackets.clear();
|
|
for (const RPI::MeshDrawPacket& meshDrawPacket : m_drawPacketListsByLod[lodIndex])
|
|
{
|
|
const RHI::DrawPacket* rhiDrawPacket = meshDrawPacket.GetRHIDrawPacket();
|
|
|
|
if (rhiDrawPacket)
|
|
{
|
|
//OR-together all the drawListMasks (so we know which views to cull against)
|
|
cullData.m_drawListMask |= rhiDrawPacket->GetDrawListMask();
|
|
|
|
lod.m_drawPackets.push_back(rhiDrawPacket);
|
|
}
|
|
}
|
|
}
|
|
|
|
cullData.m_hideFlags = RPI::View::UsageNone;
|
|
if (m_excludeFromReflectionCubeMaps)
|
|
{
|
|
cullData.m_hideFlags |= RPI::View::UsageReflectiveCubeMap;
|
|
}
|
|
|
|
cullData.m_scene = m_scene; //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
|
|
|
|
#ifdef AZ_CULL_DEBUG_ENABLED
|
|
m_cullable.SetDebugName(AZ::Name(AZStd::string::format("%s - objectId: %u", m_model->GetModelAsset()->GetName().GetCStr(), m_objectId.GetIndex())));
|
|
#endif
|
|
|
|
m_cullableNeedsRebuild = false;
|
|
m_cullBoundsNeedsUpdate = true;
|
|
}
|
|
|
|
void ModelDataInstance::UpdateCullBounds(const TransformServiceFeatureProcessor* transformService)
|
|
{
|
|
AZ_PROFILE_SCOPE(AzRender, "ModelDataInstance: UpdateCullBounds");
|
|
AZ_Assert(m_cullBoundsNeedsUpdate, "This function only needs to be called if the culling bounds need to be rebuilt");
|
|
AZ_Assert(m_model, "The model has not finished loading yet");
|
|
|
|
Transform localToWorld = transformService->GetTransformForId(m_objectId);
|
|
Vector3 nonUniformScale = transformService->GetNonUniformScaleForId(m_objectId);
|
|
|
|
Vector3 center;
|
|
float radius;
|
|
Aabb localAabb = m_aabb;
|
|
localAabb.MultiplyByScale(nonUniformScale);
|
|
|
|
localAabb.GetTransformedAabb(localToWorld).GetAsSphere(center, radius);
|
|
|
|
m_cullable.m_cullData.m_boundingSphere = Sphere(center, radius);
|
|
m_cullable.m_cullData.m_boundingObb = localAabb.GetTransformedObb(localToWorld);
|
|
m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = localAabb.GetTransformedAabb(localToWorld);
|
|
m_cullable.m_cullData.m_visibilityEntry.m_userData = &m_cullable;
|
|
m_cullable.m_cullData.m_visibilityEntry.m_typeFlags = AzFramework::VisibilityEntry::TYPE_RPI_Cullable;
|
|
m_scene->GetCullingScene()->RegisterOrUpdateCullable(m_cullable);
|
|
|
|
m_cullBoundsNeedsUpdate = false;
|
|
}
|
|
|
|
void ModelDataInstance::UpdateObjectSrg()
|
|
{
|
|
for (auto& objectSrg : m_objectSrgList)
|
|
{
|
|
ReflectionProbeFeatureProcessor* reflectionProbeFeatureProcessor = m_scene->GetFeatureProcessor<ReflectionProbeFeatureProcessor>();
|
|
|
|
if (reflectionProbeFeatureProcessor && (m_descriptor.m_useForwardPassIblSpecular || m_hasForwardPassIblSpecularMaterial))
|
|
{
|
|
// retrieve probe constant indices
|
|
AZ::RHI::ShaderInputConstantIndex modelToWorldConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorld"));
|
|
AZ_Error("ModelDataInstance", modelToWorldConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
AZ::RHI::ShaderInputConstantIndex modelToWorldInverseConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorldInverse"));
|
|
AZ_Error("ModelDataInstance", modelToWorldInverseConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
AZ::RHI::ShaderInputConstantIndex outerObbHalfLengthsConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerObbHalfLengths"));
|
|
AZ_Error("ModelDataInstance", outerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
AZ::RHI::ShaderInputConstantIndex innerObbHalfLengthsConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerObbHalfLengths"));
|
|
AZ_Error("ModelDataInstance", innerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
AZ::RHI::ShaderInputConstantIndex useReflectionProbeConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_useReflectionProbe"));
|
|
AZ_Error("ModelDataInstance", useReflectionProbeConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
AZ::RHI::ShaderInputConstantIndex useParallaxCorrectionConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_useParallaxCorrection"));
|
|
AZ_Error("ModelDataInstance", useParallaxCorrectionConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
AZ::RHI::ShaderInputConstantIndex exposureConstantIndex = objectSrg->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_exposure"));
|
|
AZ_Error("ModelDataInstance", exposureConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
|
|
|
|
// retrieve probe cubemap index
|
|
Name reflectionCubeMapImageName = Name("m_reflectionProbeCubeMap");
|
|
RHI::ShaderInputImageIndex reflectionCubeMapImageIndex = objectSrg->FindShaderInputImageIndex(reflectionCubeMapImageName);
|
|
AZ_Error("ModelDataInstance", reflectionCubeMapImageIndex.IsValid(), "Failed to find shader image index [%s]", reflectionCubeMapImageName.GetCStr());
|
|
|
|
// retrieve the list of probes that contain the centerpoint of the mesh
|
|
TransformServiceFeatureProcessor* transformServiceFeatureProcessor = m_scene->GetFeatureProcessor<TransformServiceFeatureProcessor>();
|
|
Transform transform = transformServiceFeatureProcessor->GetTransformForId(m_objectId);
|
|
|
|
ReflectionProbeFeatureProcessor::ReflectionProbeVector reflectionProbes;
|
|
reflectionProbeFeatureProcessor->FindReflectionProbes(transform.GetTranslation(), reflectionProbes);
|
|
|
|
if (!reflectionProbes.empty() && reflectionProbes[0])
|
|
{
|
|
objectSrg->SetConstant(modelToWorldConstantIndex, reflectionProbes[0]->GetTransform());
|
|
objectSrg->SetConstant(modelToWorldInverseConstantIndex, Matrix3x4::CreateFromTransform(reflectionProbes[0]->GetTransform()).GetInverseFull());
|
|
objectSrg->SetConstant(outerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetOuterObbWs().GetHalfLengths());
|
|
objectSrg->SetConstant(innerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetInnerObbWs().GetHalfLengths());
|
|
objectSrg->SetConstant(useReflectionProbeConstantIndex, true);
|
|
objectSrg->SetConstant(useParallaxCorrectionConstantIndex, reflectionProbes[0]->GetUseParallaxCorrection());
|
|
objectSrg->SetConstant(exposureConstantIndex, reflectionProbes[0]->GetRenderExposure());
|
|
|
|
objectSrg->SetImage(reflectionCubeMapImageIndex, reflectionProbes[0]->GetCubeMapImage());
|
|
}
|
|
else
|
|
{
|
|
objectSrg->SetConstant(useReflectionProbeConstantIndex, false);
|
|
}
|
|
}
|
|
|
|
objectSrg->Compile();
|
|
}
|
|
|
|
// Set m_objectSrgNeedsUpdate to false if there are object SRGs in the list
|
|
m_objectSrgNeedsUpdate = m_objectSrgNeedsUpdate && (m_objectSrgList.size() == 0);
|
|
}
|
|
|
|
bool ModelDataInstance::MaterialRequiresForwardPassIblSpecular(Data::Instance<RPI::Material> material) const
|
|
{
|
|
// look for a shader that has the o_materialUseForwardPassIBLSpecular option set
|
|
// Note: this should be changed to have the material automatically set the forwardPassIBLSpecular
|
|
// property and look for that instead of the shader option.
|
|
// [GFX TODO][ATOM-5040] Address Property Metadata Feedback Loop
|
|
for (auto& shaderItem : material->GetShaderCollection())
|
|
{
|
|
if (shaderItem.IsEnabled())
|
|
{
|
|
RPI::ShaderOptionIndex index = shaderItem.GetShaderOptionGroup().GetShaderOptionLayout()->FindShaderOptionIndex(Name{ "o_materialUseForwardPassIBLSpecular" });
|
|
if (index.IsValid())
|
|
{
|
|
RPI::ShaderOptionValue value = shaderItem.GetShaderOptionGroup().GetValue(Name{ "o_materialUseForwardPassIBLSpecular" });
|
|
if (value.GetIndex() == 1)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ModelDataInstance::SetVisible(bool isVisible)
|
|
{
|
|
m_visible = isVisible;
|
|
m_cullable.m_isHidden = !isVisible;
|
|
}
|
|
} // namespace Render
|
|
} // namespace AZ
|