64bbc700fa
- This rendering technique reduces the memory required per pipeline by 750MB compared to the PPLL technique!! - Using 3 screen buffers representing the closest 3 hair gragments depths. - Going through second geometry pass that disqualify any fragment further than the third depth, the technique blends the three closes shaders hair fragments. - Supports the Marschner lighting model (big change from the original TressFX 4.1 implementation) - The current technique is almost twice the performance of the PPLL but not quite as advacned when come to final visual quality. Remarks: Unlike the PPLL, this technique is still lacking some of the advanced features added to the PPLL such as 1. Back lobe (TT) conseal by depth comparison 2. Thickness dependency in light transfer (mainly TT) 3. Allowing TT transfer for thin separated hair strands (might be supported by default with no distinction) Signed-off-by: Adi-Amazon <Adi Bar-Lev 82479970+Adi-Amazon@users.noreply.github.com> Signed-off-by: Adi-Amazon <Adi Bar-Lev barlev@amazon.com> Signed-off-by: Adi-Amazon <Adi Bar-Lev 82479970+Adi-Amazon@users.noreply.github.com> Signed-off-by: Adi-Amazon <Adi Bar-Lev barlev@amazon.com> Co-authored-by: Adi-Amazon <Adi Bar-Lev 82479970+Adi-Amazon@users.noreply.github.com>
311 lines
13 KiB
C++
311 lines
13 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/CommandList.h>
|
|
#include <Atom/RHI/RHISystemInterface.h>
|
|
#include <Atom/RHI/DrawPacketBuilder.h>
|
|
#include <Atom/RHI/PipelineState.h>
|
|
|
|
#include <Atom/RPI.Public/View.h>
|
|
#include <Atom/RPI.Public/RPIUtils.h>
|
|
#include <Atom/RPI.Public/RenderPipeline.h>
|
|
#include <Atom/RPI.Public/RPISystemInterface.h>
|
|
#include <Atom/RPI.Public/Pass/PassUtils.h>
|
|
#include <Atom/RPI.Public/Scene.h>
|
|
|
|
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
|
|
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
|
|
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
|
|
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
|
|
|
|
#include <Passes/HairGeometryRasterPass.h>
|
|
#include <Rendering/HairRenderObject.h>
|
|
#include <Rendering/HairFeatureProcessor.h>
|
|
|
|
namespace AZ
|
|
{
|
|
namespace Render
|
|
{
|
|
namespace Hair
|
|
{
|
|
|
|
// --- Creation & Initialization ---
|
|
RPI::Ptr<HairGeometryRasterPass> HairGeometryRasterPass::Create(const RPI::PassDescriptor& descriptor)
|
|
{
|
|
RPI::Ptr<HairGeometryRasterPass> pass = aznew HairGeometryRasterPass(descriptor);
|
|
return pass;
|
|
}
|
|
|
|
HairGeometryRasterPass::HairGeometryRasterPass(const RPI::PassDescriptor& descriptor)
|
|
: RasterPass(descriptor),
|
|
m_passDescriptor(descriptor)
|
|
{
|
|
// For inherited classes, override this method and set the proper path.
|
|
// Example: "Shaders/hairrenderingfillppll.azshader"
|
|
SetShaderPath("dummyShaderPath");
|
|
}
|
|
|
|
bool HairGeometryRasterPass::AcquireFeatureProcessor()
|
|
{
|
|
if (m_featureProcessor)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
RPI::Scene* scene = GetScene();
|
|
if (scene)
|
|
{
|
|
m_featureProcessor = scene->GetFeatureProcessor<HairFeatureProcessor>();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!m_featureProcessor)
|
|
{
|
|
AZ_Warning("Hair Gem", false,
|
|
"HairGeometryRasterPass [%s] - Failed to retrieve Hair feature processor from the scene",
|
|
GetName().GetCStr());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void HairGeometryRasterPass::InitializeInternal()
|
|
{
|
|
if (GetScene())
|
|
{
|
|
RasterPass::InitializeInternal();
|
|
}
|
|
}
|
|
|
|
bool HairGeometryRasterPass::IsEnabled() const
|
|
{
|
|
return (RPI::RasterPass::IsEnabled() && m_initialized) ? true : false;
|
|
}
|
|
|
|
bool HairGeometryRasterPass::LoadShaderAndPipelineState()
|
|
{
|
|
RPI::ShaderReloadNotificationBus::Handler::BusDisconnect();
|
|
|
|
const RPI::RasterPassData* passData = RPI::PassUtils::GetPassData<RPI::RasterPassData>(m_passDescriptor);
|
|
|
|
// If we successfully retrieved our custom data, use it to set the DrawListTag
|
|
if (!passData)
|
|
{
|
|
AZ_Error("Hair Gem", false, "Missing pass raster data");
|
|
return false;
|
|
}
|
|
|
|
// Load Shader
|
|
const char* shaderFilePath = m_shaderPath.c_str();
|
|
Data::Asset<RPI::ShaderAsset> shaderAsset =
|
|
RPI::AssetUtils::LoadAssetByProductPath<RPI::ShaderAsset>(shaderFilePath, RPI::AssetUtils::TraceLevel::Error);
|
|
|
|
if (!shaderAsset.GetId().IsValid())
|
|
{
|
|
AZ_Error("Hair Gem", false, "Invalid shader asset for shader '%s'!", shaderFilePath);
|
|
return false;
|
|
}
|
|
|
|
m_shader = RPI::Shader::FindOrCreate(shaderAsset);
|
|
if (m_shader == nullptr)
|
|
{
|
|
AZ_Error("Hair Gem", false, "Pass failed to load shader '%s'!", shaderFilePath);
|
|
return false;
|
|
}
|
|
|
|
// Per Pass Srg
|
|
{
|
|
// Using 'PerPass' naming since currently RasterPass assumes that the pass Srg is always named 'PassSrg'
|
|
// [To Do] - RasterPass should use srg slot index and not name - currently this will
|
|
// result in a crash in one of the Atom existing MSAA passes that requires further dive.
|
|
// m_shaderResourceGroup = UtilityClass::CreateShaderResourceGroup(m_shader, "HairPerPassSrg", "Hair Gem");
|
|
m_shaderResourceGroup = UtilityClass::CreateShaderResourceGroup(m_shader, "PassSrg", "Hair Gem");
|
|
if (!m_shaderResourceGroup)
|
|
{
|
|
AZ_Error("Hair Gem", false, "Failed to create the per pass srg");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const RPI::ShaderVariant& shaderVariant = m_shader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId);
|
|
RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
|
|
shaderVariant.ConfigurePipelineState(pipelineStateDescriptor);
|
|
|
|
RPI::Scene* scene = GetScene();
|
|
if (!scene)
|
|
{
|
|
AZ_Error("Hair Gem", false, "Scene could not be acquired" );
|
|
return false;
|
|
}
|
|
RHI::DrawListTag drawListTag = m_shader->GetDrawListTag();
|
|
scene->ConfigurePipelineState(drawListTag, pipelineStateDescriptor);
|
|
|
|
pipelineStateDescriptor.m_renderAttachmentConfiguration = GetRenderAttachmentConfiguration();
|
|
pipelineStateDescriptor.m_inputStreamLayout.SetTopology(AZ::RHI::PrimitiveTopology::TriangleList);
|
|
pipelineStateDescriptor.m_inputStreamLayout.Finalize();
|
|
|
|
m_pipelineState = m_shader->AcquirePipelineState(pipelineStateDescriptor);
|
|
if (!m_pipelineState)
|
|
{
|
|
AZ_Error("Hair Gem", false, "Pipeline state could not be acquired");
|
|
return false;
|
|
}
|
|
|
|
RPI::ShaderReloadNotificationBus::Handler::BusConnect(shaderAsset.GetId());
|
|
|
|
m_initialized = true;
|
|
return true;
|
|
}
|
|
|
|
Data::Instance<RPI::Shader> HairGeometryRasterPass::GetShader()
|
|
{
|
|
if (!m_initialized || !m_shader)
|
|
{
|
|
AZ_Error("Hair Gem", LoadShaderAndPipelineState(), "HairGeometryRasterPass could not initialize pipeline or shader");
|
|
}
|
|
return m_shader;
|
|
}
|
|
|
|
void HairGeometryRasterPass::SchedulePacketBuild(HairRenderObject* hairObject)
|
|
{
|
|
m_newRenderObjects.insert(hairObject);
|
|
}
|
|
|
|
bool HairGeometryRasterPass::BuildDrawPacket(HairRenderObject* hairObject)
|
|
{
|
|
if (!m_initialized)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
RHI::DrawPacketBuilder::DrawRequest drawRequest;
|
|
drawRequest.m_listTag = m_drawListTag;
|
|
drawRequest.m_pipelineState = m_pipelineState;
|
|
// drawRequest.m_streamBufferViews = // no explicit vertex buffer. shader is using the srg buffers
|
|
drawRequest.m_stencilRef = 0;
|
|
drawRequest.m_sortKey = 0;
|
|
|
|
// Seems that the PerView and PerScene are gathered through RenderPass::CollectSrgs()
|
|
// The PerPass is gathered through the RasterPass::m_shaderResourceGroup
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
|
|
|
|
return hairObject->BuildDrawPacket(m_shader.get(), drawRequest);
|
|
}
|
|
|
|
bool HairGeometryRasterPass::AddDrawPackets(AZStd::list<Data::Instance<HairRenderObject>>& hairRenderObjects)
|
|
{
|
|
bool overallSuccess = true;
|
|
|
|
if (!m_currentView &&
|
|
(!(m_currentView = GetView()) || !m_currentView->HasDrawListTag(m_drawListTag)))
|
|
{
|
|
m_currentView = nullptr; // set it to nullptr to prevent further attempts this frame
|
|
AZ_Warning("Hair Gem", false, "AddDrawPackets: failed to acquire or match the DrawListTag - check that your pass and shader tag name match");
|
|
return false;
|
|
}
|
|
|
|
for (auto& renderObject : hairRenderObjects)
|
|
{
|
|
const RHI::DrawPacket* drawPacket = renderObject->GetGeometrylDrawPacket(m_shader.get());
|
|
if (!drawPacket)
|
|
{ // might not be an error - the object might have just been added and the DrawPacket is
|
|
// scheduled to be built when the render frame begins
|
|
AZ_Warning("Hair Gem", !m_newRenderObjects.empty(), "HairGeometryRasterPass - DrawPacket wasn't built");
|
|
overallSuccess = false;
|
|
continue;
|
|
}
|
|
|
|
m_currentView->AddDrawPacket(drawPacket);
|
|
}
|
|
return overallSuccess;
|
|
}
|
|
|
|
void HairGeometryRasterPass::FrameBeginInternal(FramePrepareParams params)
|
|
{
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
|
|
if (!m_initialized && AcquireFeatureProcessor())
|
|
{
|
|
LoadShaderAndPipelineState();
|
|
m_featureProcessor->ForceRebuildRenderData();
|
|
}
|
|
}
|
|
|
|
if (!m_initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bind the Per Object resources and trigger the RHI validation that will use attachment
|
|
// for its validation. The attachments are invalidated outside the render begin/end frame.
|
|
for (HairRenderObject* newObject : m_newRenderObjects)
|
|
{
|
|
newObject->BindPerObjectSrgForRaster();
|
|
BuildDrawPacket(newObject);
|
|
}
|
|
|
|
// Clear the new added objects - BuildDrawPacket should only be carried out once per
|
|
// object/shader lifetime
|
|
m_newRenderObjects.clear();
|
|
|
|
// Refresh current view every frame
|
|
if (!(m_currentView = GetView()) || !m_currentView->HasDrawListTag(m_drawListTag))
|
|
{
|
|
m_currentView = nullptr; // set it to null if view exists but no tag match
|
|
AZ_Warning("Hair Gem", false, "FrameBeginInternal: failed to acquire or match the DrawListTag - check that your pass and shader tag name match");
|
|
return;
|
|
}
|
|
|
|
RPI::RasterPass::FrameBeginInternal(params);
|
|
}
|
|
|
|
void HairGeometryRasterPass::CompileResources(const RHI::FrameGraphCompileContext& context)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzRender);
|
|
|
|
if (!m_featureProcessor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Compilation of remaining srgs will be done by the parent class
|
|
RPI::RasterPass::CompileResources(context);
|
|
}
|
|
|
|
void HairGeometryRasterPass::BuildShaderAndRenderData()
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
|
|
m_initialized = false; // make sure we initialize it even if not in this frame
|
|
if (AcquireFeatureProcessor())
|
|
{
|
|
LoadShaderAndPipelineState();
|
|
m_featureProcessor->ForceRebuildRenderData();
|
|
}
|
|
}
|
|
|
|
void HairGeometryRasterPass::OnShaderReinitialized([[maybe_unused]] const RPI::Shader & shader)
|
|
{
|
|
BuildShaderAndRenderData();
|
|
}
|
|
|
|
void HairGeometryRasterPass::OnShaderAssetReinitialized([[maybe_unused]] const Data::Asset<RPI::ShaderAsset>& shaderAsset)
|
|
{
|
|
BuildShaderAndRenderData();
|
|
}
|
|
|
|
void HairGeometryRasterPass::OnShaderVariantReinitialized([[maybe_unused]] const AZ::RPI::ShaderVariant& shaderVariant)
|
|
{
|
|
BuildShaderAndRenderData();
|
|
}
|
|
} // namespace Hair
|
|
} // namespace Render
|
|
} // namespace AZ
|