Added occlusion culling plane visualization

This commit is contained in:
Doug McDiarmid
2021-05-31 01:38:13 -07:00
parent ddbed2f222
commit 59ab6edaef
14 changed files with 321 additions and 37 deletions
@@ -0,0 +1,22 @@
{
"materialType": "Materials\\Types\\StandardPBR.materialtype",
"propertyLayoutVersion": 3,
"properties": {
"general": {
"enableShadows": false,
"enableDirectionalLights": false,
"enablePunctualLights": false,
"enableAreaLights": false,
"enableIBL": true
},
"baseColor": {
"color": [ 0.0, 1.0, 0.0 ]
},
"opacity": {
"alphaSource": "None",
"doubleSided": true,
"factor": 0.25,
"mode": "TintedTransparent"
}
}
}
@@ -0,0 +1,22 @@
{
"materialType": "Materials\\Types\\StandardPBR.materialtype",
"propertyLayoutVersion": 3,
"properties": {
"general": {
"enableShadows": false,
"enableDirectionalLights": false,
"enablePunctualLights": false,
"enableAreaLights": false,
"enableIBL": true
},
"baseColor": {
"color": [ 0.0, 1.0, 0.0 ]
},
"opacity": {
"alphaSource": "None",
"doubleSided": true,
"factor": 1.0,
"mode": "TintedTransparent"
}
}
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0a1f8d75dcd85e8b4aa57f6c0c81af0300ff96915ba3c2b591095c215d5e1d8c
size 12072
@@ -36,6 +36,8 @@ namespace AZ
virtual bool IsValidOcclusionCullingPlaneHandle(const OcclusionCullingPlaneHandle& occlusionCullingPlane) const = 0;
virtual void SetTransform(const OcclusionCullingPlaneHandle& occlusionCullingPlane, const AZ::Transform& transform) = 0;
virtual void SetEnabled(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool enabled) = 0;
virtual void ShowVisualization(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool showVisualization) = 0;
virtual void SetTransparentVisualization(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool transparentVisualization) = 0;
};
} // namespace Render
} // namespace AZ
@@ -0,0 +1,113 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h>
#include <AzCore/Math/Random.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
namespace AZ
{
namespace Render
{
static const char* OcclusionCullingPlaneDrawListTag("occlusioncullingplanevisualization");
OcclusionCullingPlane::~OcclusionCullingPlane()
{
Data::AssetBus::MultiHandler::BusDisconnect();
m_meshFeatureProcessor->ReleaseMesh(m_visualizationMeshHandle);
}
void OcclusionCullingPlane::Init(RPI::Scene* scene)
{
AZ_Assert(scene, "OcclusionCullingPlane::Init called with a null Scene pointer");
m_meshFeatureProcessor = scene->GetFeatureProcessor<Render::MeshFeatureProcessorInterface>();
// load visualization plane model and material
m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(
"Models/OcclusionCullingPlane.azmodel",
AZ::RPI::AssetUtils::TraceLevel::Assert);
m_visualizationMeshHandle = m_meshFeatureProcessor->AcquireMesh(m_visualizationModelAsset);
m_meshFeatureProcessor->SetExcludeFromReflectionCubeMaps(m_visualizationMeshHandle, true);
m_meshFeatureProcessor->SetRayTracingEnabled(m_visualizationMeshHandle, false);
m_meshFeatureProcessor->SetTransform(m_visualizationMeshHandle, AZ::Transform::CreateIdentity());
SetVisualizationMaterial();
}
void OcclusionCullingPlane::SetVisualizationMaterial()
{
AZStd::string materialAssetPath;
if (m_transparentVisualization)
{
materialAssetPath = "Materials/OcclusionCullingPlane/OcclusionCullingPlaneTransparentVisualization.azmaterial";
}
else
{
materialAssetPath = "Materials/OcclusionCullingPlane/OcclusionCullingPlaneVisualization.azmaterial";
}
RPI::AssetUtils::TraceLevel traceLevel = AZ::RPI::AssetUtils::TraceLevel::Assert;
m_visualizationMaterialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>(materialAssetPath.c_str(), traceLevel);
m_visualizationMaterialAsset.QueueLoad();
Data::AssetBus::MultiHandler::BusConnect(m_visualizationMaterialAsset.GetId());
}
void OcclusionCullingPlane::OnAssetReady(Data::Asset<Data::AssetData> asset)
{
if (m_visualizationMaterialAsset.GetId() == asset.GetId())
{
m_visualizationMaterialAsset = asset;
Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
m_visualizationMaterial = AZ::RPI::Material::FindOrCreate(m_visualizationMaterialAsset);
m_meshFeatureProcessor->SetMaterialAssignmentMap(m_visualizationMeshHandle, m_visualizationMaterial);
}
}
void OcclusionCullingPlane::OnAssetError(Data::Asset<Data::AssetData> asset)
{
AZ_Error("OcclusionCullingPlane", false, "Failed to load OcclusionCullingPlane visualization asset %s", asset.ToString<AZStd::string>().c_str());
Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
}
void OcclusionCullingPlane::SetTransform(const AZ::Transform& transform)
{
m_transform = transform;
// update visualization plane transform
m_meshFeatureProcessor->SetTransform(m_visualizationMeshHandle, transform);
}
void OcclusionCullingPlane::ShowVisualization(bool showVisualization)
{
if (m_showVisualization != showVisualization)
{
m_meshFeatureProcessor->SetVisible(m_visualizationMeshHandle, showVisualization);
SetVisualizationMaterial();
}
}
void OcclusionCullingPlane::SetTransparentVisualization(bool transparentVisualization)
{
if (m_transparentVisualization != transparentVisualization)
{
m_transparentVisualization = transparentVisualization;
SetVisualizationMaterial();
}
}
} // namespace Render
} // namespace AZ
@@ -0,0 +1,65 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h>
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
namespace AZ
{
namespace Render
{
//! This class represents an OcclusionCullingPlane which is used to cull meshes that are inside the view frustum
class OcclusionCullingPlane final
: public AZ::Data::AssetBus::MultiHandler
{
public:
OcclusionCullingPlane() = default;
~OcclusionCullingPlane();
void Init(RPI::Scene* scene);
void SetTransform(const AZ::Transform& transform);
const AZ::Transform& GetTransform() const { return m_transform; }
void SetEnabled(bool enabled) { m_enabled = enabled; }
bool GetEnabled() const { return m_enabled; }
// enables or disables rendering of the visualization plane
void ShowVisualization(bool showVisualization);
// sets the visualization to transparent mode
void SetTransparentVisualization(bool transparentVisualization);
private:
void SetVisualizationMaterial();
// AZ::Data::AssetBus::Handler overrides...
void OnAssetReady(Data::Asset<Data::AssetData> asset) override;
void OnAssetError(Data::Asset<Data::AssetData> asset) override;
AZ::Transform m_transform;
bool m_enabled = true;
bool m_showVisualization = true;
bool m_transparentVisualization = false;
// visualization
AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
Data::Asset<RPI::ModelAsset> m_visualizationModelAsset;
Data::Asset<RPI::MaterialAsset> m_visualizationMaterialAsset;
Data::Instance<RPI::Material> m_visualizationMaterial;
AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_visualizationMeshHandle;
};
} // namespace Render
} // namespace AZ
@@ -60,6 +60,7 @@ namespace AZ
OcclusionCullingPlaneHandle OcclusionCullingPlaneFeatureProcessor::AddOcclusionCullingPlane(const AZ::Transform& transform)
{
AZStd::shared_ptr<OcclusionCullingPlane> occlusionCullingPlane = AZStd::make_shared<OcclusionCullingPlane>();
occlusionCullingPlane->Init(GetParentScene());
occlusionCullingPlane->SetTransform(transform);
m_occlusionCullingPlanes.push_back(occlusionCullingPlane);
return occlusionCullingPlane;
@@ -90,5 +91,17 @@ namespace AZ
AZ_Assert(occlusionCullingPlane.get(), "Enable called with an invalid handle");
occlusionCullingPlane->SetEnabled(enabled);
}
void OcclusionCullingPlaneFeatureProcessor::ShowVisualization(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool showVisualization)
{
AZ_Assert(occlusionCullingPlane.get(), "ShowVisualization called with an invalid handle");
occlusionCullingPlane->ShowVisualization(showVisualization);
}
void OcclusionCullingPlaneFeatureProcessor::SetTransparentVisualization(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool transparentVisualization)
{
AZ_Assert(occlusionCullingPlane.get(), "SetTransparentVisualization called with an invalid handle");
occlusionCullingPlane->SetTransparentVisualization(transparentVisualization);
}
} // namespace Render
} // namespace AZ
@@ -13,29 +13,12 @@
#pragma once
#include <Atom/Feature/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessorInterface.h>
#include <OcclusionCullingPlane/OcclusionCullingPlane.h>
namespace AZ
{
namespace Render
{
//! This class represents an OcclusionCullingPlane which is used to cull meshes that are inside the view frustum
class OcclusionCullingPlane final
{
public:
OcclusionCullingPlane() = default;
~OcclusionCullingPlane() = default;
void SetTransform(const AZ::Transform& transform) { m_transform = transform; }
const AZ::Transform& GetTransform() const { return m_transform; }
void SetEnabled(bool enabled) { m_enabled = enabled; }
bool GetEnabled() const { return m_enabled; }
private:
AZ::Transform m_transform;
bool m_enabled = true;
};
//! This class manages OcclusionCullingPlanes which are used to cull meshes that are inside the view frustum
class OcclusionCullingPlaneFeatureProcessor final
: public OcclusionCullingPlaneFeatureProcessorInterface
@@ -54,6 +37,8 @@ namespace AZ
bool IsValidOcclusionCullingPlaneHandle(const OcclusionCullingPlaneHandle& occlusionCullingPlane) const override { return (occlusionCullingPlane.get() != nullptr); }
void SetTransform(const OcclusionCullingPlaneHandle& occlusionCullingPlane, const AZ::Transform& transform) override;
void SetEnabled(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool enable) override;
void ShowVisualization(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool showVisualization) override;
void SetTransparentVisualization(const OcclusionCullingPlaneHandle& occlusionCullingPlane, bool transparentVisualization) override;
// FeatureProcessor overrides
void Activate() override;
@@ -175,6 +175,8 @@ set(FILES
Source/MorphTargets/MorphTargetDispatchItem.h
Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.h
Source/OcclusionCullingPlane/OcclusionCullingPlaneFeatureProcessor.cpp
Source/OcclusionCullingPlane/OcclusionCullingPlane.h
Source/OcclusionCullingPlane/OcclusionCullingPlane.cpp
Source/PostProcess/PostProcessBase.cpp
Source/PostProcess/PostProcessBase.h
Source/PostProcess/PostProcessFeatureProcessor.cpp
@@ -428,20 +428,52 @@ namespace AZ
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
// convert the bounding box of the visibility entry to NDC
AZ::Vector4 clipSpaceMin = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(visibleEntry->m_boundingVolume.GetMin());
float depth = clipSpaceMin.GetW();
AZ::Vector4 ndcMin = clipSpaceMin / clipSpaceMin.GetW();
if (visibleEntry->m_boundingVolume.Contains(m_jobData->m_view->GetCameraTransform().GetTranslation()))
{
// camera is inside bounding volume
return MaskedOcclusionCulling::CullingResult::VISIBLE;
}
AZ::Vector4 clipSpaceMax = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(visibleEntry->m_boundingVolume.GetMax());
depth = AZStd::min(depth, clipSpaceMax.GetW());
AZ::Vector4 ndcMax = clipSpaceMax / clipSpaceMax.GetW();
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
Vector2 rectMin(AZStd::min(ndcMin.GetX(), ndcMax.GetX()), AZStd::min(ndcMin.GetY(), ndcMax.GetY()));
Vector2 rectMax(AZStd::max(ndcMin.GetX(), ndcMax.GetX()), AZStd::max(ndcMin.GetY(), ndcMax.GetY()));
// compute bounding volume corners
Vector4 corners[8];
corners[0] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[1] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[2] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
corners[3] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
corners[4] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
corners[5] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[6] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
corners[7] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
// find min clip-space depth and NDC min/max
float ndcMinX = FLT_MAX;
float ndcMinY = FLT_MAX;
float ndcMaxX = -FLT_MAX;
float ndcMaxY = -FLT_MAX;
float minDepth = FLT_MAX;
for (uint32_t index = 0; index < 8; ++index)
{
minDepth = AZStd::min(minDepth, corners[index].GetW());
// convert to NDC
corners[index] /= corners[index].GetW();
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
}
if (minDepth < 0.00000001f)
{
return MaskedOcclusionCulling::VISIBLE;
}
// test against the occlusion buffer, which contains only the manually placed occlusion planes
return m_jobData->m_maskedOcclusionCulling->TestRect(rectMin.GetX(), rectMin.GetY(), rectMax.GetX(), rectMax.GetY(), depth);
return m_jobData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
}
};
@@ -480,15 +512,22 @@ namespace AZ
MaskedOcclusionCulling* maskedOcclusionCulling = m_occlusionCullingPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
if (maskedOcclusionCulling)
{
// frustum cull and sort the occlusion planes by view space distance, front-to-back
// frustum cull occlusion planes
using OccluderEntry = AZStd::pair<AZ::Transform, float>;
AZStd::vector<OccluderEntry> visibleOccluders;
for (const AZ::Transform& transform : m_occlusionCullingPlanes)
{
Aabb occluderAabb = Aabb::CreateCenterHalfExtents(transform.GetTranslation(), AZ::Vector3(AZ::Vector2(transform.GetUniformScale() / 2.0f)));
occluderAabb.SetMin(transform.TransformPoint(occluderAabb.GetMin()));
occluderAabb.SetMax(transform.TransformPoint(occluderAabb.GetMax()));
if (ShapeIntersection::Contains(frustum, occluderAabb))
static const AZ::Vector3 BL(-0.5f, -0.5f, 0.0f);
static const AZ::Vector3 TR(0.5f, 0.5f, 0.0f);
AZ::Vector3 P1 = transform.TransformPoint(BL);
AZ::Vector3 P2 = transform.TransformPoint(TR);
AZ::Vector3 aabbMin = P1.GetMin(P2);
AZ::Vector3 aabbMax = P1.GetMax(P2);
AZ::Aabb occluderAabb = Aabb::CreateFromMinMax(aabbMin, aabbMax);
if (ShapeIntersection::Overlaps(frustum, occluderAabb))
{
// occluder is visible, compute view space distance and add to list
float depth = (view.GetWorldToViewMatrix() * occluderAabb.GetMin()).GetZ();
@@ -498,9 +537,10 @@ namespace AZ
}
}
// sort the occlusion planes by view space distance, front-to-back
AZStd::sort(visibleOccluders.begin(), visibleOccluders.end(), [](const OccluderEntry& LHS, const OccluderEntry& RHS)
{
return LHS.second < RHS.second;
return LHS.second > RHS.second;
});
for (const OccluderEntry& occluder : visibleOccluders)
@@ -28,6 +28,10 @@ namespace AZ
{
namespace RPI
{
// fixed-size software occlusion culling buffer
const uint32_t MaskedSoftwareOcclusionCullingWidth = 1920;
const uint32_t MaskedSoftwareOcclusionCullingHeight = 1080;
ViewPtr View::CreateView(const AZ::Name& name, UsageFlags usage)
{
View* view = aznew View(name, usage);
@@ -54,7 +58,7 @@ namespace AZ
}
m_maskedOcclusionCulling = MaskedOcclusionCulling::Create();
m_maskedOcclusionCulling->SetResolution(1920, 1080);
m_maskedOcclusionCulling->SetResolution(MaskedSoftwareOcclusionCullingWidth, MaskedSoftwareOcclusionCullingHeight);
}
View::~View()
@@ -53,8 +53,12 @@ namespace AZ
editContext->Class<OcclusionCullingPlaneComponentConfig>(
"OcclusionCullingPlaneComponentConfig", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->ClassElement(AZ::Edit::ClassElements::Group, "Settings")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::CheckBox, &OcclusionCullingPlaneComponentConfig::m_showVisualization, "Show Visualization", "Show the occlusion culling plane visualization")
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::CheckBox, &OcclusionCullingPlaneComponentConfig::m_transparentVisualization, "Transparent Visualization", "Sets the occlusion culling plane visualization as transparent")
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
;
}
}
@@ -38,7 +38,9 @@ namespace AZ
{
serializeContext->Class<OcclusionCullingPlaneComponentConfig>()
->Version(0)
;
->Field("ShowVisualization", &OcclusionCullingPlaneComponentConfig::m_showVisualization)
->Field("TransparentVisualization", &OcclusionCullingPlaneComponentConfig::m_transparentVisualization)
;
}
}
@@ -98,6 +100,10 @@ namespace AZ
// add this occlusion plane to the feature processor
const AZ::Transform& transform = m_transformInterface->GetWorldTM();
m_handle = m_featureProcessor->AddOcclusionCullingPlane(transform);
// set visualization
m_featureProcessor->ShowVisualization(m_handle, m_configuration.m_showVisualization);
m_featureProcessor->SetTransparentVisualization(m_handle, m_configuration.m_transparentVisualization);
}
void OcclusionCullingPlaneComponentController::Deactivate()
@@ -32,6 +32,9 @@ namespace AZ
AZ_CLASS_ALLOCATOR(OcclusionCullingPlaneComponentConfig, SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
bool m_showVisualization = true;
bool m_transparentVisualization = false;
OcclusionCullingPlaneComponentConfig() = default;
};