diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass index 84bb85c3e1..9bfa746bf1 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass @@ -212,7 +212,11 @@ // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated // .azsl file will need to be updated. "Name": "HairParentPass", - "TemplateName": "HairParentPassTemplate", + // Note: The following two lines represent the choice of rendering pipeline for the hair. + // You can either choose to use PPLL or ShortCut and accordingly change the flag + // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' +// "TemplateName": "HairParentPassTemplate", + "TemplateName": "HairParentShortCutPassTemplate", "Enabled": true, "Connections": [ // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset index a287664286..1580a4e1a9 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset @@ -8,6 +8,11 @@ "Name": "HairParentPassTemplate", "Path": "Passes/HairParentPass.pass" }, + { + "Name": "HairParentShortCutPassTemplate", + "Path": "Passes/HairParentShortCutPass.pass" + }, + { "Name": "HairGlobalShapeConstraintsComputePassTemplate", "Path": "Passes/HairGlobalShapeConstraintsCompute.pass" @@ -32,6 +37,7 @@ "Name": "HairUpdateFollowHairComputePassTemplate", "Path": "Passes/HairUpdateFollowHairCompute.pass" }, + { "Name": "HairPPLLRasterPassTemplate", "Path": "Passes/HairFillPPLL.pass" @@ -39,6 +45,23 @@ { "Name": "HairPPLLResolvePassTemplate", "Path": "Passes/HairResolvePPLL.pass" + }, + + { + "Name": "HairShortCutGeometryDepthAlphaPassTemplate", + "Path": "Passes/HairShortCutGeometryDepthAlpha.pass" + }, + { + "Name": "HairShortCutResolveDepthPassTemplate", + "Path": "Passes/HairShortCutResolveDepth.pass" + }, + { + "Name": "HairShortCutGeometryShadingPassTemplate", + "Path": "Passes/HairShortCutGeometryShading.pass" + }, + { + "Name": "HairShortCutResolveColorPassTemplate", + "Path": "Passes/HairShortCutResolveColor.pass" } ] } diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass new file mode 100644 index 0000000000..7322611e2a --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass @@ -0,0 +1,391 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairParentShortCutPassTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "RenderTargetInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { // used for copy from MSAA to regular RT + "Name": "RenderTargetInputOnly", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + // This is the depth stencil buffer that is to be used by the fill pass + // to early reject pixels by depth and in the resolve pass to write the + // the hair depth + { + "Name": "Depth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + // Keep DepthLinear as input - used to set the size of the Head PPLL image buffer. + // If DepthLinear is not availbale - connect to another viewport (non MSAA) image. + { + "Name": "DepthLinearInput", + "SlotType": "InputOutput" + }, + { + "Name": "DepthLinear", + "SlotType": "Output" + }, + + // Lights & Shadows resources + { + "Name": "DirectionalShadowmap", + "SlotType": "Input" + }, + { + "Name": "DirectionalESM", + "SlotType": "Input" + }, + { + "Name": "ProjectedShadowmap", + "SlotType": "Input" + }, + { + "Name": "ProjectedESM", + "SlotType": "Input" + }, + { + "Name": "TileLightData", + "SlotType": "Input" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input" + } + ], + "Connections": [ + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "DepthToDepthLinearPass", + "Attachment": "Output" + } + } + ], + "PassRequests": [ + { + "Name": "HairGlobalShapeConstraintsComputePass", + "TemplateName": "HairGlobalShapeConstraintsComputePassTemplate", + "Enabled": true + }, + { + "Name": "HairCalculateStrandLevelDataComputePass", + "TemplateName": "HairCalculateStrandLevelDataComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairGlobalShapeConstraintsComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairVelocityShockPropagationComputePass", + "TemplateName": "HairVelocityShockPropagationComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairCalculateStrandLevelDataComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairLocalShapeConstraintsComputePass", + "TemplateName": "HairLocalShapeConstraintsComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairVelocityShockPropagationComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairLengthConstraintsWindAndCollisionComputePass", + "TemplateName": "HairLengthConstraintsWindAndCollisionComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairLocalShapeConstraintsComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairUpdateFollowHairComputePass", + "TemplateName": "HairUpdateFollowHairComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairLengthConstraintsWindAndCollisionComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + + // Render Target Copy from MS to Regular + { + "Name": "RenderTargetCopyPass", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOnly" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "This", + "Attachment": "Output" + } + } + ], + "ImageAttachments": [ + { + "Name": "Output", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "Input" + } + }, + "FormatSource": { + "Pass": "This", + "Attachment": "Input" + }, + "GenerateFullMipChain": false + } + ] + }, + + // Rendering Passes + { + "Name": "HairShortCutGeometryDepthAlphaPass", + "TemplateName": "HairShortCutGeometryDepthAlphaPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairUpdateFollowHairComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InverseAlphaRTOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "InverseAlphaRTOutput" + } + }, + { + "LocalSlot": "HairDepthsTextureArray", + "AttachmentRef": { + "Pass": "This", + "Attachment": "HairDepthsTextureArray" + } + } + ] + }, + + { + "Name": "HairShortCutResolveDepthPass", + "TemplateName": "HairShortCutResolveDepthPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "HairDepthsTextureArray", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "HairDepthsTextureArray" + } + } + ] + }, + + { + "Name": "HairShortCutGeometryShadingPass", + "TemplateName": "HairShortCutGeometryShadingPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "HairColorRenderTarget", + "AttachmentRef": { + "Pass": "This", + "Attachment": "HairColorRenderTarget" + } + }, + { // The final render target - this is MSAA mode RT - would it be cheaper to + // use non-MSAA and then copy? + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthLinearInput" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairUpdateFollowHairComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + }, + + // Shadows resources + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ProjectedESM" + } + }, + + // Lights Resources + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightListRemapped" + } + } + ] + }, + + { + "Name": "HairShortCutResolveColorPass", + "TemplateName": "HairShortCutResolveColorPassTemplate", + "Enabled": true, + "Connections": [ + { // The final render target - this is MSAA mode RT - would it be cheaper to + // use non-MSAA and then copy? + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "AccumulatedInverseAlpha", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "InverseAlphaRTOutput" + } + }, + { + "LocalSlot": "HairColorTexture", + "AttachmentRef": { + "Pass": "HairShortCutGeometryShadingPass", + "Attachment": "HairColorRenderTarget" + } + } + ] + }, + + { + // This pass copies the updated depth buffer (now contains hair depth) to linear depth texture + // for downstream passes to use. This can be optimized even further by writing into the stencil + // buffer pixels that were touched by HairPPLLResolvePass hence preventing depth update unless + // it is hair. + "Name": "DepthToDepthLinearPass", + "TemplateName": "DepthToLinearTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "HairShortCutResolveDepthPass", + "Attachment": "Depth" + } + } + ] + } + + ] + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass new file mode 100644 index 0000000000..559224faa6 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass @@ -0,0 +1,101 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutGeometryDepthAlphaPassTemplate", + "PassClass": "HairShortCutGeometryDepthAlphaPass", + "Slots": [ + { + "Name": "SkinnedHairSharedBuffer", + "ShaderInputName": "m_skinnedHairSharedBuffer", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // DepthStencil for early disqualifying the pixel based on depth. No write. + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + // the regular render target is blended using inverse alpha to reduce the + // incoming color contribution based on the hair thickness and alpha. + "Name": "InverseAlphaRTOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { + "Value": [ 1.0, 1.0, 1.0, 1.0 ] + }, + "StoreAction": "Store" + } + }, + { + "Name": "HairDepthsTextureArray", + "SlotType": "Output", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_RWFragmentDepthsTexture", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { // reverse depth order: closer --> 1.0 + "Value": [ 0.0, 0.0, 0.0, 0.0 ] + }, + "StoreAction": "Store" + } + } + ], + "ImageAttachments": [ + { + // This buffer is used as the render target and should be at non-MSAA screen resolution + // to make sure no overwork is done. + "Name": "InverseAlphaRTOutput", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } + }, + "ImageDescriptor": { + "Format": "R32_FLOAT", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "Color", + "ShaderRead" + ] + } + }, + { + "Name": "HairDepthsTextureArray", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } + }, + "ImageDescriptor": { + "Format": "R32_UINT", + "ArraySize": "3", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "ShaderReadWrite", + "ShaderWrite", + "ShaderRead" + ] + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "HairGeometryDepthAlphaDrawList", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutGeometryDepthAlpha.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass new file mode 100644 index 0000000000..5940f8c549 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass @@ -0,0 +1,155 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutGeometryShadingPassTemplate", + "PassClass": "HairShortCutGeometryShadingPass", + "Slots": [ + + { // Temporary color buffer to store the gathered shaded hair color - MSAA + "Name": "HairColorRenderTarget", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { // reverse depth order: closer --> 1.0 + "Value": [ 0.0, 0.0, 0.0, 0.0 ] + }, + "StoreAction": "Store" + } + }, + + { + // This RT is MSAA - is it cheaper to avoid doing this work and only do a copy at a separate pass? + "Name": "RenderTargetInputOutput", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // Used to get the transform from screen space to world space. + "Name": "DepthLinear", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // For comparing the depth to early disqualify but not to write + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + "Name": "SkinnedHairSharedBuffer", + "ShaderInputName": "m_skinnedHairSharedBuffer", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + + //------------- Shadowing Resources ------------- + { + "Name": "DirectionalShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "DirectionalESM", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedESM", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + + //------------- Lighting Resources ------------- + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + } + + ], + "ImageAttachments": [ + { + // The shader hair color render target - important to have at a non-MSAA mode + // so that no overwork is done on sampling. + "Name": "HairColorRenderTarget", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "Color", + "ShaderRead" + ] + } + }, + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + } + ], + "Connections": [ + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "HairGeometryShadingDrawList", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutGeometryShading.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass new file mode 100644 index 0000000000..efbf993307 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass @@ -0,0 +1,45 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutResolveColorPassTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + // This RT is MSAA - is it cheaper to avoid doing this work and only do a copy at a separate pass? + "Name": "RenderTargetInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Load", + "StoreAction": "Store" + } + }, + { + "Name": "HairColorTexture", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_hairColorTexture" + }, + { + "Name": "AccumulatedInverseAlpha", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_accumInvAlpha" + } + ], + "Connections": [ + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutResolveColor.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass new file mode 100644 index 0000000000..021d711f1f --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass @@ -0,0 +1,37 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutResolveDepthPassTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + //------ General Input/Output resources and Render Target ------ + { + "Name": "Depth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil", + "LoadStoreAction": { + "LoadAction": "Load", + "StoreAction": "Store" + } + }, + { // This holds the K nearset depths. The furthest depth will be taken to be written in the depth buffer. + "Name": "HairDepthsTextureArray", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_fragmentDepthsTexture" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutResolveDepth.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli b/Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli similarity index 99% rename from Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli rename to Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli index cb0bdc2c6c..a4568588d5 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli @@ -29,7 +29,7 @@ // THE SOFTWARE. // //------------------------------------------------------------------------------ -// File: HairSRGs.azsli +// File: HairComputeSrgs.azsli // // Declarations of SRGs used by the hair shaders. //------------------------------------------------------------------------------ diff --git a/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli b/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli new file mode 100644 index 0000000000..7e57e8102c --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli @@ -0,0 +1,60 @@ +/* +* Modifications 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) AND MIT +* +*/ + +#include +#include +#include + +//============================================================================== +// Generate a fullscreen triangle from pipeline provided vertex id +VSOutput FullScreenVS(VSInput input) +{ + VSOutput OUT; + + float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID); + + OUT.m_texCoord = float2(posTex.z, posTex.w); // [To Do] - test sign of Y based on original code + OUT.m_position = float4(posTex.xy, 0.0, 1.0); + + return OUT; +} + +//============================================================================== +// Given the depth buffer depth of the current pixel and the fragment XY position, +// reconstruct the NDC. +// screenCoords - from 0.. dimension of the screen of the current pixel +// screenTexture - screen buffer texture representing the same resolution we work in +// sDepth - the depth buffer depth at the fragment location +// NDC - Normalized Device Coordinates = warped screen space ( -1.1, -1..1, 0..1 ) +float3 ScreenPosToNDC( Texture2D screenTexture, float2 screenCoords, float depth ) +{ + uint2 dimensions; + screenTexture.GetDimensions(dimensions.x, dimensions.y); + float2 UV = saturate(screenCoords / dimensions.xy); + + float x = UV.x * 2.0f - 1.0f; + float y = (1.0f - UV.y) * 2.0f - 1.0f; + float3 NDC = float3(x, y, depth); + + return NDC; +} + +// Given the depth buffer depth of the current pixel and the fragment XY position, +// reconstruct the world space position +float3 ScreenPosToWorldPos( + Texture2D screenTexture, float2 screenCoords, float depth, + inout float3 screenPosNDC ) +{ + screenPosNDC = ScreenPosToNDC(screenTexture, screenCoords, depth); + float4 projectedPos = float4(screenPosNDC, 1.0f); // warped projected space [0..1] + float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos); + positionVS /= positionVS.w; // notice the normalization factor - crucial! + float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS); + + return positionWS.xyz; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli index dcdcf43243..7beba248f7 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli @@ -230,7 +230,7 @@ float3 CalculateLighting( return lightingData.diffuseLighting + lightingData.specularLighting; } -float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, float3 baseColor, float thickness, int shaderParamIndex) +float3 TressFXShading(float2 pixelCoord, float depth, float3 tangent, float3 baseColor, float thickness, int shaderParamIndex) { float3 vNDC; // normalized device / screen coordinates: [-1..1, -1..1, 0..1] float3 vPositionWS = ScreenPosToWorldPos(PassSrg::m_linearDepth, pixelCoord, depth, vNDC); @@ -241,9 +241,6 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f float3 vViewDirWS = g_vEye - vPositionWS; - // Need to expand the tangent that was compressed to store in the buffer - float3 vTangent = normalize(vTangentCoverage.xyz * 2.f - 1.f); - //---- TressFX original lighting params setting ---- HairShadeParams params; params.m_color = baseColor; @@ -266,11 +263,19 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f if (o_hairLightingModel == HairLightingModel::Kajiya) { // This option should be removed and the Kajiya-Kay model should be operated from within // the Atom lighting loop. - accumulatedLight = SimplifiedHairLighting(vTangent, vPositionWS, vViewDirWS, params, vNDC); + accumulatedLight = SimplifiedHairLighting(tangent, vPositionWS, vViewDirWS, params, vNDC); } else { - accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, vTangent, thickness, params); + accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, tangent, thickness, params); } return accumulatedLight; } + +float3 TressFXShadingFullScreen(float2 pixelCoord, float depth, float3 compressedTangent, float3 baseColor, float thickness, int shaderParamIndex) +{ + // The tangent that was compressed to store in the PPLL structure + float3 tangent = normalize(compressedTangent.xyz * 2.f - 1.f); + + return TressFXShading(pixelCoord, depth, tangent, baseColor, thickness, shaderParamIndex); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli index 83ebc04521..4e4245f765 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli @@ -66,7 +66,7 @@ option bool o_enableAzimuthCoeff = true; float M_R(Surface surface, float Lh, float sinLiPlusSinLr) { float a = 1.0f * surface.cuticleTilt; // Tilt is translate as the mean offset - float b = 0.5 * surface.roughnessA2; // Roughness is used as the standard deviation + float b = 0.5f * surface.roughnessA2; // Roughness is used as the standard deviation // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); @@ -74,8 +74,8 @@ float M_R(Surface surface, float Lh, float sinLiPlusSinLr) float M_TT(Surface surface, float Lh, float sinLiPlusSinLr) { - float a = 1.0 * surface.cuticleTilt; - float b = 0.5 * surface.roughnessA2; + float a = 1.0f * surface.cuticleTilt; + float b = 0.5f * surface.roughnessA2; // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); @@ -83,8 +83,8 @@ float M_TT(Surface surface, float Lh, float sinLiPlusSinLr) float M_TRT(Surface surface, float Lh, float sinLiPlusSinLr) { - float a = 1.5 * surface.cuticleTilt; - float b = 1.0 * surface.roughnessA2; + float a = 1.5f * surface.cuticleTilt; + float b = 1.0f * surface.roughnessA2; // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl index 82c677faad..02777435f5 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl @@ -40,7 +40,8 @@ //! that can change between passes due to the application of skinning, simulation //! and physics affect and is then read by the rendering shaders. ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback -{ //! This shared buffer needs to match the SharedBuffer structure +{ + //! This shared buffer needs to match the SharedBuffer structure //! shared between all draw calls / dispatches for the hair skinning StructuredBuffer m_skinnedHairSharedBuffer; @@ -101,39 +102,8 @@ ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance #define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents //============================================================================== -#include +#include // VS resides here //============================================================================== -//! Hair input structure to Pixel shaders -struct PS_INPUT_HAIR -{ - float4 Position : SV_POSITION; - float4 Tangent : Tangent; - float4 p0p1 : TEXCOORD0; - float4 StrandColor : TEXCOORD1; -}; - -//! Hair Render VS -PS_INPUT_HAIR RenderHairVS(uint vertexId : SV_VertexID) -{ -// uint2 scrSize; -// PassSrg::m_linearDepth.GetDimensions(scrSize.x, scrSize.y); -// TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, float2(scrSize), g_mVP); - - // [To Do] Hair: the above code should replace the existing but requires modifications to - // the function GetExpandedTressFXVert. - // Note that in Atom g_vViewport is aspect ratio and NOT size. - TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, g_vViewport.zw, g_mVP); - - - PS_INPUT_HAIR Output; - - Output.Position = tressfxVert.Position; - Output.Tangent = tressfxVert.Tangent; - Output.p0p1 = tressfxVert.p0p1; - Output.StrandColor = tressfxVert.StrandColor; - - return Output; -} // Allocate a new fragment location in fragment color, depth, and link buffers int AllocateFragment(int2 vScreenAddress) @@ -202,9 +172,9 @@ void PPLLFillPS(PS_INPUT_HAIR input) ////////////////////////////////////////////////////////////////////// // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now - float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); - uint2 dimensions; - PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); // float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); float coverage = 1.0; ///////////////////////////////////////////////////////////////////// diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl index 82c75280f6..3d72f1e21e 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl @@ -58,7 +58,7 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback // in the OIT process. // It can also be used to avoid the HW blend done at the end of the pixel // shader stage but HW blend might be cheaper than additional PS blend. - Texture2D m_frameBuffer; // The merged MSAA output + Texture2D m_frameBuffer; // The merged non-MSAA input // Linear depth is used for getting the screen to world transform Texture2D m_linearDepth; @@ -93,26 +93,11 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback #define HairParams PassSrg::m_hairParams //============================================================================== +#include // provides the Vertex Shader #include -#include -#include - -// Generates a fullscreen triangle from pipeline provided vertex id -VSOutput FullScreenVS(VSInput input) -{ - VSOutput OUT; - - float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID); - - OUT.m_texCoord = float2(posTex.z, posTex.w); - OUT.m_position = float4(posTex.x, posTex.y, 0.0, 1.0); - - return OUT; -} ////////////////////////////////////////////////////////////// // Bind data for PPLLResolvePS - #define NODE_DATA(x) LinkedListNodes[x].data #define NODE_NEXT(x) LinkedListNodes[x].uNext #define NODE_DEPTH(x) LinkedListNodes[x].depth @@ -298,7 +283,7 @@ float4 GatherLinkedList(float2 vfScreenAddress, float2 screenUV, inout float out uint shadeParamIndex; // So we know what settings to shade with float3 vColor = UnpackUintIntoFloat3Byte(color, shadeParamIndex); - float3 fragmentColor = TressFXShading(vfScreenAddress, fDepth, vTangent, vColor, fcolor.w, shadeParamIndex); + float3 fragmentColor = TressFXShadingFullScreen(vfScreenAddress, fDepth, vTangent, vColor, fcolor.w, shadeParamIndex); // Blend in the fragment color fcolor.xyz = fcolor.xyz * (1.f - alpha) + fragmentColor * alpha; @@ -355,7 +340,7 @@ float4 GetClosestFragment(float2 vfScreenAddress, float2 screenUV, inout float c float alpha = 1.0; uint shadeParamIndex; // the material index float3 vColor = UnpackUintIntoFloat3Byte(curColor, shadeParamIndex); - float3 fragmentColor = TressFXShading(vfScreenAddress, curDepth, vTangent, vColor, fcolor.w, shadeParamIndex); + float3 fragmentColor = TressFXShadingFullScreen(vfScreenAddress, curDepth, vTangent, vColor, fcolor.w, shadeParamIndex); // Blend in the fragment color fcolor.xyz = fcolor.xyz * (1.f - alpha) + (fragmentColor * alpha); diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl new file mode 100644 index 0000000000..23c926406f --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl @@ -0,0 +1,131 @@ +/* +* Modifications 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) AND MIT +* +*/ + +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + //! This shared buffer needs to match the SharedBuffer structure + //! shared between all draw calls / dispatches for the hair skinning + StructuredBuffer m_skinnedHairSharedBuffer; + + //! Based on [[vk::binding(0, 3)]] RWTexture2DArray RWFragmentDepthsTexture : register(u0, space3); + RWTexture2DArray m_RWFragmentDepthsTexture; +} +//============================================================================== + +//!------------------------------ SRG Structure -------------------------------- +//! Per instance/draw SRG representing dynamic read-write set of buffers +//! that are unique per instance and are shared and changed between passes due +//! to the application of skinning, simulation and physics affect. +//! It is then also read by the rendering shaders. +//! This Srg is NOT shared by the passes since it requires having barriers between +//! both passes and draw calls, instead, all buffers are allocated from a single +//! shared buffer (through BufferViews) and that buffer is then shared between +//! the passes via the PerPass Srg frequency. +ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance / object +{ + Buffer m_hairVertexPositions; + Buffer m_hairVertexTangents; + + //! Per hair object offset to the start location of each buffer within + //! 'm_skinnedHairSharedBuffer'. The offset is in bytes! + uint m_positionBufferOffset; + uint m_tangentBufferOffset; +}; +//------------------------------------------------------------------------------ +// Allow for the code to run with minimal changes - skinning / simulation compute passes +// Usage of per-instance buffer +#define g_GuideHairVertexPositions HairDynamicDataSrg::m_hairVertexPositions +#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents +//------------------------------------------------------------------------------ + +#include // VS resides here + +//!============================================================================= +//! Geometry Depth Alpha - First Pass of ShortCut Render +//! It is a Geometry pass that stores the K=3 front fragment depths, and accumulates +//! product of 1-alpha multiplications (fade out) of the input render target. +//! +//! Short explanation: in the original AMD implementation 1-alpha is multiplied +//! repeatedly with the incoming render target (back buffer) hence blending out +//! the existing back buffer color based on the density and transparency of the hair. +//! This implies that later on the hair color should be added based on the inverse +//! of this operation. +//!============================================================================= +[earlydepthstencil] +float HairShortCutDepthsAlphaPS(PS_INPUT_HAIR input) : SV_Target +{ + ////////////////////////////////////////////////////////////////////// + // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); + float coverage = 1.0; + ///////////////////////////////////////////////////////////////////// + + float alpha = coverage * MatBaseColor.a; + + if (alpha < SHORTCUT_MIN_ALPHA) + return 1.0; + + int2 vScreenAddress = int2(input.Position.xy); + uint uDepth = asuint(input.Position.z); + uint uDepth0Prev, uDepth1Prev, uDepth2Prev; + + // Min of depth 0 and input depth - in Atom the Z order is reverse + // Original value is uDepth0Prev + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 0)], uDepth, uDepth0Prev); + + // Min of depth 1 and greater of the last compare - in Atom the Z order is reverse + // If fragment opaque, always use input depth (don't need greater depths) + uDepth = (alpha > 0.98) ? uDepth : max(uDepth, uDepth0Prev); + + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 1)], uDepth, uDepth1Prev); + + // Min of depth 2 and greater of the last compare - in Atom the Z order is reverse + // If fragment opaque, always use input depth (don't need greater depths) + uDepth = (alpha > 0.98) ? uDepth : max(uDepth, uDepth1Prev); + + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 2)], uDepth, uDepth2Prev); + + // Accumulate the alpha multiplication from all hair components by multiplying the inverse and + // therefore going down towards 0. At the end product, the inverse will be taken as the hair + // alpha and the remainder will be used to blend the back buffer. + return 1.0 - alpha; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader new file mode 100644 index 0000000000..7cd1f44510 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader @@ -0,0 +1,45 @@ +{ + "Source" : "HairShortCutGeometryDepthAlpha.azsl", + "DrawList" : "HairGeometryDepthAlphaDrawList", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "WriteMask" : "Zero", // Avoid writing the depth + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "Zero", + "BlendDest" : "ColorSource", + "BlendOp" : "Add", + "BlendAlphaSource" : "Zero", + "BlendAlphaDest" : "AlphaSource", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "RenderHairVS", + "type": "Vertex" + }, + { + "name": "HairShortCutDepthsAlphaPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl new file mode 100644 index 0000000000..c2a2958dfe --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl @@ -0,0 +1,176 @@ +/* +* Modifications 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) AND MIT +* +*/ + +//------------------------------------------------------------------------------ +// Shader code related to lighting and shadowing for TressFX +//------------------------------------------------------------------------------ +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +#define AMD_TRESSFX_MAX_HAIR_GROUP_RENDER 16 + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + //! This shared buffer needs to match the SharedBuffer structure + //! shared between all draw calls / dispatches for the hair skinning + StructuredBuffer m_skinnedHairSharedBuffer; + + //! Per hair object material array used by the PPLL resolve pass + //! Originally in TressFXRendering.hlsl this is space 0 + HairObjectShadeParams m_hairParams[AMD_TRESSFX_MAX_HAIR_GROUP_RENDER]; + + // Linear depth is used for getting the screen to world transform + Texture2D m_linearDepth; + + //------------------------------ + // Lighting Data + //------------------------------ + Sampler LinearSampler + { // Required by LightingData.azsli + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + Texture2DArray m_directionalLightShadowmap; + Texture2DArray m_directionalLightExponentialShadowmap; + Texture2DArray m_projectedShadowmaps; + Texture2DArray m_projectedExponentialShadowmap; + Texture2D m_brdfMap; + Texture2D m_tileLightData; + StructuredBuffer m_lightListRemapped; +} + +//------------------------------------------------------------------------------ +//! The hair objects' material array buffer used by the rendering resolve pass +#define HairParams PassSrg::m_hairParams +//============================================================================== + +//!------------------------------ SRG Structure -------------------------------- +//! Per instance/draw SRG representing dynamic read-write set of buffers +//! that are unique per instance and are shared and changed between passes due +//! to the application of skinning, simulation and physics affect. +//! It is then also read by the rendering shaders. +//! This Srg is NOT shared by the passes since it requires having barriers between +//! both passes and draw calls, instead, all buffers are allocated from a single +//! shared buffer (through BufferViews) and that buffer is then shared between +//! the passes via the PerPass Srg frequency. +ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance / object +{ + Buffer m_hairVertexPositions; + Buffer m_hairVertexTangents; + + //! Per hair object offset to the start location of each buffer within + //! 'm_skinnedHairSharedBuffer'. The offset is in bytes! + uint m_positionBufferOffset; + uint m_tangentBufferOffset; +}; +//------------------------------------------------------------------------------ +// Allow for the code to run with minimal changes - skinning / simulation compute passes +// Usage of per-instance buffer +#define g_GuideHairVertexPositions HairDynamicDataSrg::m_hairVertexPositions +#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents +//------------------------------------------------------------------------------ + +#include // VS resides here +#include // Required for world coordinates calculation +#include + +//!============================================================================= +//! Geometry Shading - Third Pass of ShortCut Render +//! Geometry pass that shades pixels that passes the early depth test. Due to this, it +//! is limited to the stored K near fragments due to previous depth write pass that +//! wrote the furthest depth of the K stored depths. +//! Colors are accumulated in the render target for a weighted average in final pass. +//! [To Do] - in the original short cut, the alpha is taken from the depth alpha pass +//!============================================================================= +[earlydepthstencil] +float4 HairShortCutGeometryColorPS(PS_INPUT_HAIR input) : SV_Target +{ + // Strand Color read in is either the BaseMatColor, or BaseMatColor modulated with a color read from texture + // on vertex shader for base color along with modulation by the tip color + float4 strandColor = float4(input.StrandColor.rgb, MatBaseColor.a); + + // If we are supporting strand UV texturing, further blend in the texture color/alpha + // Do this while computing NDC and coverage to hide latency from texture lookup + if (EnableStrandUV) + { + // Grab the uv in case we need it + float2 uv = float2(input.Tangent.w, input.StrandColor.w); + + // Apply StrandUVTiling + float2 strandUV = float2(uv.x, (uv.y * StrandUVTilingFactor) - floor(uv.y * StrandUVTilingFactor)); + + strandColor.rgb *= StrandAlbedoTexture.Sample(LinearWrapSampler, strandUV).rgb; + } + + ////////////////////////////////////////////////////////////////////// + // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float2 screenCoords = saturate(pixelCoord / dimensions.xy); +// float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); +// original: float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, g_vViewport.zw - g_vViewport.xy); + float coverage = 1.0; + ///////////////////////////////////////////////////////////////////// + + float alpha = coverage; + + // Update the alpha to have proper value (accounting for coverage, base alpha, and strand alpha) + alpha *= strandColor.w; + + // Early out + if (alpha < SHORTCUT_MIN_ALPHA) + { + return float4(0, 0, 0, 0); + } + + float2 pixelCoord = input.Position.xy; + float depth = input.Position.z; + // [To Do] - the thickness will need to be corrected somehow since this technique doesn't + // keeps track of the accumulated alpha / thickness + float thickness = alpha; + float3 shadedFragment = TressFXShading(pixelCoord, depth, input.Tangent.xyz, strandColor.rgb, thickness, RenderParamsIndex); + + // Color channel: Pre-multiply with alpha to create non-normalized weighted sum. + // Alpha Channel: Sum up all the hair alphas - this will be used to normalize the color + // per fragment at the next pass. + return float4(shadedFragment * alpha, alpha); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader new file mode 100644 index 0000000000..40e56006cd --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader @@ -0,0 +1,45 @@ +{ + "Source" : "HairShortCutGeometryShading.azsl", + "DrawList" : "HairGeometryShadingDrawList", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "WriteMask" : "Zero", // Avoid writing the depth + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "One", + "BlendDest" : "One", + "BlendOp" : "Add", + "BlendAlphaSource" : "One", + "BlendAlphaDest" : "One", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "RenderHairVS", + "type": "Vertex" + }, + { + "name": "HairShortCutGeometryColorPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl new file mode 100644 index 0000000000..f25c9dd711 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl @@ -0,0 +1,63 @@ +/* +* Modifications 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) AND MIT +* +*/ + +#include +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + // oiriginally: [[vk::binding(0, 0)]] Texture2D HaiColorTexture : register(t0, space0); + // oiriginally: [[vk::binding(1, 0)]] Texture2D AccumInvAlpha : register(t1, space0); + Texture2D m_hairColorTexture; + Texture2D m_accumInvAlpha; +} +//------------------------------------------------------------------------------ + +#include // provides the Vertex Shader + +//!============================================================================= +//! HairColorPS - Fourth Pass of ShortCut Render +//! Full-screen pass that finalizes the weighted average, and blends using the +//! accumulated 1-alpha product. +//!============================================================================= +[earlydepthstencil] +float4 HairShortCutResolveColorPS(VSOutput input) : SV_Target +{ + int2 vScreenAddress = int2(input.m_position.xy); + + float fInvAlpha = PassSrg::m_accumInvAlpha[vScreenAddress]; + float fAlpha = 1.0 - fInvAlpha; + + if (fAlpha < SHORTCUT_MIN_ALPHA) + { + // next we discard of non-hair pixels to avoid manipulating them depending + // on the alpha blend state - this is the safer and faster approach as there + // is no hair in these pixels + discard; + } + + float4 finalColor; + float weightSum = PassSrg::m_hairColorTexture[vScreenAddress].w; + + // Normalize the sum of the shaded fragment from the previous pass and + // then multiply it by the alpha blend of the hairs done in the depth-alpha pass. + finalColor.xyz = PassSrg::m_hairColorTexture[vScreenAddress] * fAlpha / weightSum; + + // The alpha is set to the inverse alpha of the hair so that the original + // background will be blended using this factor emulating single step alpha blend + // over the sum of all hair fragment blends. + finalColor.w = fInvAlpha; + + return finalColor; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader new file mode 100644 index 0000000000..6703c63f18 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader @@ -0,0 +1,41 @@ +{ + "Source" : "HairShortCutResolveColor.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : false // Avoid comparing depth + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "One", + "BlendDest" : "AlphaSource", + "BlendOp" : "Add", + "BlendAlphaSource" : "Zero", + "BlendAlphaDest" : "Zero", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "FullScreenVS", + "type": "Vertex" + }, + { + "name": "HairShortCutResolveColorPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl new file mode 100644 index 0000000000..862b7c9015 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl @@ -0,0 +1,65 @@ +/* +* Modifications 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) AND MIT +* +*/ + +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + // Originally: [[vk::binding(0, 0)]] Texture2DArray FragmentDepthsTexture : register(t0, space0); + Texture2DArray m_fragmentDepthsTexture; +} +//------------------------------------------------------------------------------ + +#include // provides the Vertex Shader + +//!============================================================================= +//! Resolve Depth - Second Pass of ShortCut +//! Full-screen pass that writes the farthest of the stored K near depths so it +//! could be used for depth culling during the following geometry shading pass. +//!============================================================================= +float HairShortCutResolveDepthPS(VSOutput input) : SV_Depth +{ + // Blend the layers of fragments from back to front + int2 vScreenAddress = int2(input.m_position.xy); + + // Write farthest depth value for culling in the next pass. + // It may be the initial value of 1.0 if there were not enough fragments to write all depths, but then culling not important. + const int farthestDepthIndex = 2; + uint uDepth = PassSrg::m_fragmentDepthsTexture[uint3(vScreenAddress, farthestDepthIndex)]; + + // The following line is writing the depth into the actual depth buffer + return asfloat(uDepth); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader new file mode 100644 index 0000000000..5b518c3bfc --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader @@ -0,0 +1,37 @@ +{ + "Source" : "HairShortCutResolveDepth.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, // test the written depth and accept/discard based on the depth buffer + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "FullScreenVS", + "type": "Vertex" + }, + { + "name": "HairShortCutResolveDepthPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl index 496aa5c7da..ed6ec5de27 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl @@ -31,7 +31,7 @@ // THE SOFTWARE. // //-------------------------------------------------------------------------------------- -#include +#include #include //-------------------------------------------------------------------------------------- diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli b/Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli similarity index 99% rename from Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli rename to Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli index f95dac92cf..3ded3ab2af 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli @@ -29,13 +29,13 @@ // THE SOFTWARE. // //------------------------------------------------------------------------------ -// File: HairSRGs.azsli +// File: HairSimulationComputeSrgs.azsli // // Declarations of SRGs used by the hair shaders. //------------------------------------------------------------------------------ #pragma once -#include +#include //!----------------------------------------------------------------------------- //! diff --git a/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli b/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli index b8c4ea9eb4..abbbc8c0de 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli @@ -66,12 +66,22 @@ float3 GetSharedTangent(int tangentIndex) ); } +//! Hair vertex geometry output - input structure for the Pixel shaders struct TressFXVertex { float4 Position; - float4 Tangent; + float4 Tangent; // xyz = Tangent, w = Strand U float4 p0p1; - float4 StrandColor; + float4 StrandColor; // xyz = Strand Color, w = Strand V +}; + +//! Matching structure to carry out as VS output / PS input +struct PS_INPUT_HAIR +{ + float4 Position : SV_POSITION; + float4 Tangent : Tangent; + float4 p0p1 : TEXCOORD0; + float4 StrandColor : TEXCOORD1; }; float3 GetStrandColor(int index, float fractionOfStrand) @@ -178,5 +188,26 @@ TressFXVertex GetExpandedTressFXShadowVert(uint vertexId, float3 eye, float2 win return Output; } -// EndHLSL +//!============================================================================= +//! Hair Render VS - Used by all geometry hair shaders +//!============================================================================= +PS_INPUT_HAIR RenderHairVS(uint vertexId : SV_VertexID) +{ + PS_INPUT_HAIR vsOutput; + + // uint2 scrSize; + // PassSrg::m_linearDepth.GetDimensions(scrSize.x, scrSize.y); + // TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, float2(scrSize), g_mVP); + + // [To Do] Hair: the above code should replace the existing but requires modifications to + // the function GetExpandedTressFXVert. + // Note that in Atom g_vViewport is aspect ratio and NOT size. + TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, g_vViewport.zw, g_mVP); + vsOutput.Position = tressfxVert.Position; + vsOutput.Tangent = tressfxVert.Tangent; + vsOutput.p0p1 = tressfxVert.p0p1; + vsOutput.StrandColor = tressfxVert.StrandColor; + + return vsOutput; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli b/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli index 1d50d3c040..f91ba1ff3b 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli @@ -34,9 +34,6 @@ #pragma once -#include - - #define SHORTCUT_MIN_ALPHA 0.02 #define TRESSFX_FLOAT_EPSILON 1e-7 @@ -52,40 +49,6 @@ float4 MatrixMult(float4x4 m, float4 v) return mul(m, v); } -// Given the depth buffer depth of the current pixel and the fragment XY position, -// reconstruct the NDC. -// screenCoords - from 0.. dimension of the screen of the current pixel -// screenTexture - screen buffer texture representing the same resolution we work in -// sDepth - the depth buffer depth at the fragment location -// NDC - Normalized Device Coordinates = warped screen space ( -1.1, -1..1, 0..1 ) -float3 ScreenPosToNDC( Texture2D screenTexture, float2 screenCoords, float depth ) -{ - uint2 dimensions; - screenTexture.GetDimensions(dimensions.x, dimensions.y); - float2 UV = saturate(screenCoords / dimensions.xy); - - float x = UV.x * 2.0f - 1.0f; - float y = (1.0f - UV.y) * 2.0f - 1.0f; - float3 NDC = float3(x, y, depth); - - return NDC; -} - -// Given the depth buffer depth of the current pixel and the fragment XY position, -// reconstruct the world space position -float3 ScreenPosToWorldPos( - Texture2D screenTexture, float2 screenCoords, float depth, - inout float3 screenPosNDC ) -{ - screenPosNDC = ScreenPosToNDC(PassSrg::m_linearDepth, screenCoords, depth); - float4 projectedPos = float4(screenPosNDC, 1.0f); // warped projected space [0..1] - float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos); - positionVS /= positionVS.w; // notice the normalization factor - crucial! - float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS); - - return positionWS.xyz; -} - // Pack a float4 into an uint uint PackFloat4IntoUint(float4 vValue) { diff --git a/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp b/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp index 66ac3d2e09..2ae8b9d97d 100644 --- a/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp +++ b/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp @@ -80,8 +80,14 @@ namespace AZ // Load the AtomTressFX pass classes passSystem->AddPassCreator(Name("HairSkinningComputePass"), &HairSkinningComputePass::Create); + + // Load the PPLL render method passes passSystem->AddPassCreator(Name("HairPPLLRasterPass"), &HairPPLLRasterPass::Create); passSystem->AddPassCreator(Name("HairPPLLResolvePass"), &HairPPLLResolvePass::Create); + + // Load the ShortCut render method passes + passSystem->AddPassCreator(Name("HairShortCutGeometryDepthAlphaPass"), &HairShortCutGeometryDepthAlphaPass::Create); + passSystem->AddPassCreator(Name("HairShortCutGeometryShadingPass"), &HairShortCutGeometryShadingPass::Create); } void HairSystemComponent::Deactivate() diff --git a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp index 7af5903cf3..b9c0d2e379 100644 --- a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp @@ -165,6 +165,15 @@ namespace AZ return true; } + Data::Instance 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); @@ -188,7 +197,7 @@ namespace AZ // The PerPass is gathered through the RasterPass::m_shaderResourceGroup AZStd::lock_guard lock(m_mutex); - return hairObject->BuildPPLLDrawPacket(drawRequest); + return hairObject->BuildDrawPacket(m_shader.get(), drawRequest); } bool HairGeometryRasterPass::AddDrawPackets(AZStd::list>& hairRenderObjects) @@ -205,7 +214,7 @@ namespace AZ for (auto& renderObject : hairRenderObjects) { - const RHI::DrawPacket* drawPacket = renderObject->GetFillDrawPacket(); + 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 diff --git a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h index a226d2c294..d3ba22039d 100644 --- a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h +++ b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h @@ -51,7 +51,7 @@ namespace AZ //! The following will be called when an object was added or shader has been compiled void SchedulePacketBuild(HairRenderObject* hairObject); - Data::Instance GetShader() { return m_shader; } + Data::Instance GetShader(); void SetFeatureProcessor(HairFeatureProcessor* featureProcessor) { @@ -76,7 +76,6 @@ namespace AZ // Pass behavior overrides void InitializeInternal() override; -// void BuildInternal() override; void FrameBeginInternal(FramePrepareParams params) override; // Scope producer functions... diff --git a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp index fa643a5488..de66d91e0b 100644 --- a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp @@ -30,6 +30,17 @@ namespace AZ HairPPLLResolvePass::HairPPLLResolvePass(const RPI::PassDescriptor& descriptor) : RPI::FullscreenTrianglePass(descriptor) { + o_enableShadows = AZ::Name("o_enableShadows"); + o_enableDirectionalLights = AZ::Name("o_enableDirectionalLights"); + o_enablePunctualLights = AZ::Name("o_enablePunctualLights"); + o_enableAreaLights = AZ::Name("o_enableAreaLights"); + o_enableIBL = AZ::Name("o_enableIBL"); + o_hairLightingModel = AZ::Name("o_hairLightingModel"); + o_enableMarschner_R = AZ::Name("o_enableMarschner_R"); + o_enableMarschner_TRT = AZ::Name("o_enableMarschner_TRT"); + o_enableMarschner_TT = AZ::Name("o_enableMarschner_TT"); + o_enableLongtitudeCoeff = AZ::Name("o_enableLongtitudeCoeff"); + o_enableAzimuthCoeff = AZ::Name("o_enableAzimuthCoeff"); } void HairPPLLResolvePass::UpdateGlobalShaderOptions() @@ -38,17 +49,17 @@ namespace AZ m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); - shaderOption.SetValue(AZ::Name("o_enableShadows"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); - shaderOption.SetValue(AZ::Name("o_enableDirectionalLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); - shaderOption.SetValue(AZ::Name("o_enablePunctualLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); - shaderOption.SetValue(AZ::Name("o_enableAreaLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); - shaderOption.SetValue(AZ::Name("o_enableIBL"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); - shaderOption.SetValue(AZ::Name("o_hairLightingModel"), AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_R"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_TRT"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_TT"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); - shaderOption.SetValue(AZ::Name("o_enableLongtitudeCoeff"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); - shaderOption.SetValue(AZ::Name("o_enableAzimuthCoeff"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); + shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); + shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); + shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); + shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); + shaderOption.SetValue(o_enableIBL, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); + shaderOption.SetValue(o_hairLightingModel, AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); + shaderOption.SetValue(o_enableMarschner_R, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); + shaderOption.SetValue(o_enableMarschner_TRT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); + shaderOption.SetValue(o_enableMarschner_TT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); + shaderOption.SetValue(o_enableLongtitudeCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); + shaderOption.SetValue(o_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); } diff --git a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h index bf1a3f768e..036659b9a5 100644 --- a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h +++ b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h @@ -58,9 +58,20 @@ namespace AZ void CompileResources(const RHI::FrameGraphCompileContext& context) override; private: + AZ::Name o_enableShadows; + AZ::Name o_enableDirectionalLights; + AZ::Name o_enablePunctualLights; + AZ::Name o_enableAreaLights; + AZ::Name o_enableIBL; + AZ::Name o_hairLightingModel; + AZ::Name o_enableMarschner_R; + AZ::Name o_enableMarschner_TRT; + AZ::Name o_enableMarschner_TT; + AZ::Name o_enableLongtitudeCoeff; + AZ::Name o_enableAzimuthCoeff; + HairPPLLResolvePass(const RPI::PassDescriptor& descriptor); - private: void UpdateGlobalShaderOptions(); HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp b/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp index 24cd6465dd..1b0b034dba 100644 --- a/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace AZ { diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp new file mode 100644 index 0000000000..5e402e33f6 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp @@ -0,0 +1,50 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + namespace Hair + { + + HairShortCutGeometryDepthAlphaPass::HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor) + : HairGeometryRasterPass(descriptor) + { + SetShaderPath("Shaders/hairshortcutgeometrydepthalpha.azshader"); + } + + RPI::Ptr HairShortCutGeometryDepthAlphaPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew HairShortCutGeometryDepthAlphaPass(descriptor); + return pass; + } + + void HairShortCutGeometryDepthAlphaPass::BuildInternal() + { + RasterPass::BuildInternal(); // change this to call parent if the method exists + + if (!AcquireFeatureProcessor()) + { + return; + } + + LoadShaderAndPipelineState(); + } + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h new file mode 100644 index 0000000000..da6e28fe31 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h @@ -0,0 +1,49 @@ +/* + * 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 + * + */ +#pragma once + +#include + +namespace AZ +{ + namespace RHI + { + struct DrawItem; + } + + namespace Render + { + namespace Hair + { + //! This geometry pass uses the following Srgs: + //! - PerPassSrg shared by all hair passes for the shared dynamic buffer + //! - PerMaterialSrg - used solely by this pass to alter the vertices and apply the visual + //! hair properties to each fragment. + //! - HairDynamicDataSrg (PerObjectSrg) - shared buffers views for this hair object only. + //! - PerViewSrg and PerSceneSrg - as per the data from Atom. + class HairShortCutGeometryDepthAlphaPass + : public HairGeometryRasterPass + { + AZ_RPI_PASS(HairShortCutGeometryDepthAlphaPass); + + public: + AZ_RTTI(HairShortCutGeometryDepthAlphaPass, "{F09A0411-B1FF-4085-98E7-6B8B0E1B2C3D}", HairGeometryRasterPass); + AZ_CLASS_ALLOCATOR(HairShortCutGeometryDepthAlphaPass, SystemAllocator, 0); + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + explicit HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor); + + // Pass behavior overrides + void BuildInternal() override; + }; + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp new file mode 100644 index 0000000000..eda7f9f83a --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp @@ -0,0 +1,111 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + namespace Hair + { + + HairShortCutGeometryShadingPass::HairShortCutGeometryShadingPass(const RPI::PassDescriptor& descriptor) + : HairGeometryRasterPass(descriptor) + { + o_enableShadows = AZ::Name("o_enableShadows"); + o_enableDirectionalLights = AZ::Name("o_enableDirectionalLights"); + o_enablePunctualLights = AZ::Name("o_enablePunctualLights"); + o_enableAreaLights = AZ::Name("o_enableAreaLights"); + o_enableIBL = AZ::Name("o_enableIBL"); + o_hairLightingModel = AZ::Name("o_hairLightingModel"); + o_enableMarschner_R = AZ::Name("o_enableMarschner_R"); + o_enableMarschner_TRT = AZ::Name("o_enableMarschner_TRT"); + o_enableMarschner_TT = AZ::Name("o_enableMarschner_TT"); + o_enableLongtitudeCoeff = AZ::Name("o_enableLongtitudeCoeff"); + o_enableAzimuthCoeff = AZ::Name("o_enableAzimuthCoeff"); + + SetShaderPath("Shaders/hairshortcutgeometryshading.azshader"); + } + + RPI::Ptr HairShortCutGeometryShadingPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew HairShortCutGeometryShadingPass(descriptor); + return pass; + } + + void HairShortCutGeometryShadingPass::UpdateGlobalShaderOptions() + { + RPI::ShaderOptionGroup shaderOption = m_shader->CreateShaderOptionGroup(); + + m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); + + shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); + shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); + shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); + shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); + shaderOption.SetValue(o_enableIBL, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); + shaderOption.SetValue(o_hairLightingModel, AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); + shaderOption.SetValue(o_enableMarschner_R, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); + shaderOption.SetValue(o_enableMarschner_TRT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); + shaderOption.SetValue(o_enableMarschner_TT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); + shaderOption.SetValue(o_enableLongtitudeCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); + shaderOption.SetValue(o_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); + + m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); + } + + void HairShortCutGeometryShadingPass::CompileResources(const RHI::FrameGraphCompileContext& context) + { + if (!m_shaderResourceGroup || !AcquireFeatureProcessor()) + { + AZ_Error("Hair Gem", m_shaderResourceGroup, "HairShortCutGeometryShadingPass: missing Srg or no feature processor yet"); + return; // no error message due to FP - initialization not complete yet, wait for the next frame + } + + UpdateGlobalShaderOptions(); + + if (m_shaderResourceGroup->HasShaderVariantKeyFallbackEntry()) + { + m_shaderResourceGroup->SetShaderVariantKeyFallbackValue(m_shaderOptions); + } + + // Update the material array constant buffer within the per pass srg + SrgBufferDescriptor descriptor = SrgBufferDescriptor( + RPI::CommonBufferPoolType::Constant, RHI::Format::Unknown, + sizeof(AMD::TressFXShadeParams), 1, + Name{ "HairMaterialsArray" }, Name{ "m_hairParams" }, 0, 0 + ); + + m_featureProcessor->GetMaterialsArray().UpdateGPUData(m_shaderResourceGroup, descriptor); + + // Compilation of remaining srgs will be done by the parent class + RPI::RasterPass::CompileResources(context); + } + + void HairShortCutGeometryShadingPass::BuildInternal() + { + RasterPass::BuildInternal(); // change this to call parent if the method exists + + if (!AcquireFeatureProcessor()) + { + return; + } + + LoadShaderAndPipelineState(); + } + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h new file mode 100644 index 0000000000..d728803473 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h @@ -0,0 +1,69 @@ +/* + * 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 + * + */ +#pragma once + +#include +#include + +namespace AZ +{ + namespace RHI + { + struct DrawItem; + } + + namespace Render + { + namespace Hair + { + //! This geometry pass uses the following Srgs: + //! - PerPassSrg shared by all hair passes for the shared dynamic buffer + //! - PerMaterialSrg - used solely by this pass to alter the vertices and apply the visual + //! hair properties to each fragment. + //! - HairDynamicDataSrg (PerObjectSrg) - shared buffers views for this hair object only. + //! - PerViewSrg and PerSceneSrg - as per the data from Atom. + class HairShortCutGeometryShadingPass + : public HairGeometryRasterPass + { + AZ_RPI_PASS(HairShortCutGeometryShadingPass); + + public: + AZ_RTTI(HairShortCutGeometryShadingPass, "{11BA673D-0788-4B25-978D-9737BF4E48FE}", HairGeometryRasterPass); + AZ_CLASS_ALLOCATOR(HairShortCutGeometryShadingPass, SystemAllocator, 0); + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + void CompileResources(const RHI::FrameGraphCompileContext& context) override; + + protected: + AZ::Name o_enableShadows; + AZ::Name o_enableDirectionalLights; + AZ::Name o_enablePunctualLights; + AZ::Name o_enableAreaLights; + AZ::Name o_enableIBL; + AZ::Name o_hairLightingModel; + AZ::Name o_enableMarschner_R; + AZ::Name o_enableMarschner_TRT; + AZ::Name o_enableMarschner_TT; + AZ::Name o_enableLongtitudeCoeff; + AZ::Name o_enableAzimuthCoeff; + + explicit HairShortCutGeometryShadingPass(const RPI::PassDescriptor& descriptor); + + void UpdateGlobalShaderOptions(); + + // Pass behavior overrides + void BuildInternal() override; + + HairGlobalSettings m_hairGlobalSettings; + AZ::RPI::ShaderVariantKey m_shaderOptions; + }; + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp index 7a317dc09c..b7c6bbce13 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp @@ -47,11 +47,11 @@ namespace AZ HairFeatureProcessor::HairFeatureProcessor() { - HairParentPassName = Name{ "HairParentPass" }; + m_usePPLLRenderTechnique = false; // Use the ShortCut rendering technique - HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; - HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" }; + HairParentPassName = Name{ "HairParentPass" }; + // Hair Skinning and Simulation Compute passes GlobalShapeConstraintsPassName = Name{ "HairGlobalShapeConstraintsComputePass" }; CalculateStrandDataPassName = Name{ "HairCalculateStrandLevelDataComputePass" }; VelocityShockPropagationPassName = Name{ "HairVelocityShockPropagationComputePass" }; @@ -59,12 +59,21 @@ namespace AZ LengthConstriantsWindAndCollisionPassName = Name{ "HairLengthConstraintsWindAndCollisionComputePass" }; UpdateFollowHairPassName = Name{ "HairUpdateFollowHairComputePass" }; + // PPLL render technique pases + HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; + HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" }; + + // ShortCut render technique pases + HairShortCutGeometryDepthAlphaPassName = Name{ "HairShortCutGeometryDepthAlphaPass" }; + HairShortCutResolveDepthPassName = Name{ "HairShortCutResolveDepthPass" }; + HairShortCutGeometryShadingPassName = Name{ "HairShortCutGeometryShadingPass" }; + HairShortCutResolveColorPassName = Name{ "HairShortCutResolveColorPass" }; + ++s_instanceCount; if (!CreatePerPassResources()) { // this might not be an error - if the pass system is still empty / minimal - // and these passes are not part of the minimal pipeline, they will not - // be created. + // and these passes are not part of the minimal pipeline, they will not be created. AZ_Error("Hair Gem", false, "Failed to create the hair shared buffer resource"); } } @@ -127,25 +136,33 @@ namespace AZ m_hairRenderObjects.push_back(renderObject); + // Adding the object will schedule Srgs binding and the DrawItem build for the geometry passes. BuildDispatchAndDrawItems(renderObject); EnablePasses(true); } - void HairFeatureProcessor::EnablePasses(bool enable) + void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable) { + return; + + // [To Do] - This part should be enabled (remove the return) to reduce overhead + // when Hair is disabled / doesn't exist in the scene. + // Currently it might break features such as fog that depend on the output and for some + // reason doesn't quite work for ShortCut. + // The current overhead is minimal (< 0.1 msec) and this Gem is disabled by default. +/* if (!m_initialized) { return; } - for (auto& [passName, pass] : m_computePasses) + RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName); + if (desiredPass) { - pass->SetEnabled(enable); + desiredPass->SetEnabled(enable); } - - m_hairPPLLRasterPass->SetEnabled(enable); - m_hairPPLLResolvePass->SetEnabled(enable); +*/ } bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance renderObject) @@ -214,7 +231,8 @@ namespace AZ } if (m_forceRebuildRenderData) - { + { // In the case of a force build, schedule Srgs binding and the DrawItem build for + // the geometry passes of all existing hair objects. for (auto& hairRenderObject : m_hairRenderObjects) { BuildDispatchAndDrawItems(hairRenderObject); @@ -276,17 +294,32 @@ namespace AZ pass->AddDispatchItems(m_hairRenderObjects); } - // Add all hair objects to the Render / Raster Pass - m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); + if (m_usePPLLRenderTechnique) + { + // Add all hair objects to the Render / Raster Pass + m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); + } + else + { + m_hairShortCutGeometryDepthAlphaPass->AddDrawPackets(m_hairRenderObjects); + m_hairShortCutGeometryShadingPass->AddDrawPackets(m_hairRenderObjects); + } } void HairFeatureProcessor::ClearPasses() { m_initialized = false; // Avoid simulation or render m_computePasses.clear(); + + // PPLL geometry and resolve full screen passes m_hairPPLLRasterPass = nullptr; m_hairPPLLResolvePass = nullptr; + // ShortCut passes - Special handling of geometry passes only, and using the regular + // full screen pass for the resolve + m_hairShortCutGeometryDepthAlphaPass = nullptr; + m_hairShortCutGeometryShadingPass = nullptr; + // Mark for all passes to evacuate their render data and recreate it. m_forceRebuildRenderData = true; m_forceClearRenderData = true; @@ -338,6 +371,12 @@ namespace AZ ClearPasses(); + if (!m_renderPipeline) + { + AZ_Error("Hair Gem", false, "HairFeatureProcessor does NOT have render pipeline set yet"); + return false; + } + // Compute Passes - populate the passes map bool resultSuccess = InitComputePass(GlobalShapeConstraintsPassName); resultSuccess &= InitComputePass(CalculateStrandDataPassName); @@ -347,8 +386,15 @@ namespace AZ resultSuccess &= InitComputePass(UpdateFollowHairPassName); // Rendering Passes - resultSuccess &= InitPPLLFillPass(); - resultSuccess &= InitPPLLResolvePass(); + if (m_usePPLLRenderTechnique) + { + resultSuccess &= InitPPLLFillPass(); + resultSuccess &= InitPPLLResolvePass(); + } + else + { + resultSuccess &= InitShortCutRenderPasses(); + } m_initialized = resultSuccess; @@ -388,7 +434,8 @@ namespace AZ } } - // PPLL nodes buffer + // PPLL nodes buffer - created only if the PPLL technique is used + if (m_usePPLLRenderTechnique) { descriptor = SrgBufferDescriptor( RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown, @@ -425,11 +472,6 @@ namespace AZ bool HairFeatureProcessor::InitComputePass(const Name& passName, bool allowIterations) { m_computePasses[passName] = nullptr; - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "%s does NOT have render pipeline set yet", passName.GetCStr()); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(passName); if (desiredPass) @@ -452,11 +494,6 @@ namespace AZ bool HairFeatureProcessor::InitPPLLFillPass() { m_hairPPLLRasterPass = nullptr; // reset it to null, just in case it fails to load the assets properly - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "Hair Fill Pass does NOT have render pipeline set yet"); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLRasterPassName); if (desiredPass) @@ -466,7 +503,7 @@ namespace AZ } else { - AZ_Error("Hair Gem", false, "HairPPLLRasterPass does not have any valid passes. Check your game project's .pass assets."); + AZ_Error("Hair Gem", false, "HairPPLLRasterPass cannot be found. Check your game project's .pass assets."); return false; } return true; @@ -475,11 +512,6 @@ namespace AZ bool HairFeatureProcessor::InitPPLLResolvePass() { m_hairPPLLResolvePass = nullptr; // reset it to null, just in case it fails to load the assets properly - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "Hair Fill Pass does NOT have render pipeline set yet"); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLResolvePassName); if (desiredPass) @@ -489,12 +521,46 @@ namespace AZ } else { - AZ_Error("Hair Gem", false, "HairPPLLResolvePassTemplate does not have valid passes. Check your game project's .pass assets."); + AZ_Error("Hair Gem", false, "HairPPLLResolvePass cannot be found. Check your game project's .pass assets."); return false; } return true; } + //! Set the two short cut geometry pases and assign them the FP. The other two full screen passes + //! are generic full screen passes and don't need any interaction with the FP. + bool HairFeatureProcessor::InitShortCutRenderPasses() + { + m_hairShortCutGeometryDepthAlphaPass = nullptr; + m_hairShortCutGeometryShadingPass = nullptr; + + m_hairShortCutGeometryDepthAlphaPass = static_cast( + m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryDepthAlphaPassName).get()); + if (m_hairShortCutGeometryDepthAlphaPass) + { + m_hairShortCutGeometryDepthAlphaPass->SetFeatureProcessor(this); + } + else + { + AZ_Error("Hair Gem", false, "HairShortCutResolveDepthPass cannot be found. Check your game project's .pass assets."); + return false; + } + + m_hairShortCutGeometryShadingPass = static_cast( + m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryShadingPassName).get()); + if (m_hairShortCutGeometryShadingPass) + { + m_hairShortCutGeometryShadingPass->SetFeatureProcessor(this); + } + else + { + AZ_Error("Hair Gem", false, "HairShortCutGeometryShadingPass cannot be found. Check your game project's .pass assets."); + return false; + } + + return true; + } + void HairFeatureProcessor::BuildDispatchAndDrawItems(Data::Instance renderObject) { HairRenderObject* renderObjectPtr = renderObject.get(); @@ -513,9 +579,18 @@ namespace AZ m_computePasses[UpdateFollowHairPassName]->BuildDispatchItem( renderObjectPtr, DispatchLevel::DISPATCHLEVEL_VERTEX); - // Render / Raster pass - adding the object will schedule Srgs binding - // and DrawItem build. - m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); + // Schedule Srgs binding and the DrawItem build. + // Since this does not bind the PerPass srg but prepare the rest of the Srgs + // such as the dynamic srg, it should only be done once per object per frame. + if (m_usePPLLRenderTechnique) + { + m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); + } + else + { + m_hairShortCutGeometryDepthAlphaPass->SchedulePacketBuild(renderObjectPtr); + m_hairShortCutGeometryShadingPass->SchedulePacketBuild(renderObjectPtr); + } } Data::Instance HairFeatureProcessor::GetHairSkinningComputegPass() @@ -527,14 +602,28 @@ namespace AZ return m_computePasses[GlobalShapeConstraintsPassName]; } - Data::Instance HairFeatureProcessor::GetHairPPLLRasterPass() + Data::Instance HairFeatureProcessor::GetGeometryRasterShader() { - if (!m_hairPPLLRasterPass) + if (m_usePPLLRenderTechnique) { - Init(m_renderPipeline); + if (!m_hairPPLLRasterPass && !Init(m_renderPipeline)) + { + AZ_Error("Hair Gem", false, + "GetGeometryRasterShader - m_hairPPLLRasterPass was not created"); + return nullptr; + } + return m_hairPPLLRasterPass->GetShader(); } - return m_hairPPLLRasterPass; + + if (!m_hairShortCutGeometryDepthAlphaPass && !Init(m_renderPipeline)) + { + AZ_Error("Hair Gem", false, + "GetGeometryRasterShader - m_hairShortCutGeometryDepthAlphaPass was not created"); + return nullptr; + } + return m_hairShortCutGeometryDepthAlphaPass->GetShader(); } + } // namespace Hair } // namespace Render } // namespace AZ diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h index c56dc5c921..46660a6623 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h @@ -17,14 +17,19 @@ #include #include +#include // Hair specific #include #include + #include #include +#include +#include + #include #include #include @@ -73,9 +78,7 @@ namespace AZ { Name HairParentPassName; - Name HairPPLLRasterPassName; - Name HairPPLLResolvePassName; - + // Compute passes Name GlobalShapeConstraintsPassName; Name CalculateStrandDataPassName; Name VelocityShockPropagationPassName; @@ -83,6 +86,16 @@ namespace AZ Name LengthConstriantsWindAndCollisionPassName; Name UpdateFollowHairPassName; + // PPLL render passes + Name HairPPLLRasterPassName; + Name HairPPLLResolvePassName; + + // ShortCut render passes + Name HairShortCutGeometryDepthAlphaPassName; + Name HairShortCutResolveDepthPassName; + Name HairShortCutGeometryShadingPassName; + Name HairShortCutResolveColorPassName; + public: AZ_RTTI(AZ::Render::Hair::HairFeatureProcessor, "{5F9DDA81-B43F-4E30-9E56-C7C3DC517A4C}", RPI::FeatureProcessor); @@ -117,6 +130,7 @@ namespace AZ Data::Instance GetHairSkinningComputegPass(); Data::Instance GetHairPPLLRasterPass(); + Data::Instance GetGeometryRasterShader(); //! Update the hair objects materials array. void FillHairMaterialsArray(std::vector& renderSettings); @@ -144,6 +158,7 @@ namespace AZ bool InitPPLLFillPass(); bool InitPPLLResolvePass(); + bool InitShortCutRenderPasses(); bool InitComputePass(const Name& passName, bool allowIterations = false); void BuildDispatchAndDrawItems(Data::Instance renderObject); @@ -168,10 +183,14 @@ namespace AZ //! Simulation Compute Passes AZStd::unordered_map > m_computePasses; - // Render Passes + // PPLL Render Passes Data::Instance m_hairPPLLRasterPass = nullptr; Data::Instance m_hairPPLLResolvePass = nullptr; + // ShortCut Render Passes - special case for the geometry render passes + Data::Instance m_hairShortCutGeometryDepthAlphaPass = nullptr; + Data::Instance m_hairShortCutGeometryShadingPass = nullptr; + //-------------------------------------------------------------- // Per Pass Resources //-------------------------------------------------------------- @@ -196,6 +215,7 @@ namespace AZ bool m_forceClearRenderData = false; bool m_initialized = false; bool m_isEnabled = true; + bool m_usePPLLRenderTechnique = true; static uint32_t s_instanceCount; HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp index baf986daf6..4d615e31ae 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp @@ -993,7 +993,7 @@ namespace AZ //------------------------------------- // Dynamic buffers, data and Srg creation - shared between passes and changed on the GPU if (!m_dynamicHairData.CreateDynamicGPUResources( - m_skinningShader, m_PPLLFillShader, + m_skinningShader, m_geometryRasterShader, m_NumTotalVertices, m_NumTotalStrands)) { AZ_Error("Hair Gem", false, "Hair - Error creating dynamic resources [%s]", assetName ); @@ -1028,7 +1028,7 @@ namespace AZ // Rendering setup bool renderResourcesSuccess; - renderResourcesSuccess = CreateRenderingGPUResources(m_PPLLFillShader, *asset, assetName); + renderResourcesSuccess = CreateRenderingGPUResources(m_geometryRasterShader, *asset, assetName); renderResourcesSuccess &= PopulateDrawStrandsBindSet(renderSettings); renderResourcesSuccess &= UploadRenderingGPUResources(*asset); @@ -1057,17 +1057,10 @@ namespace AZ } { - Data::Instance rasterPass = m_featureProcessor->GetHairPPLLRasterPass(); - if (!rasterPass.get()) + m_geometryRasterShader = m_featureProcessor->GetGeometryRasterShader(); + if (!m_geometryRasterShader) { - AZ_Error("Hair Gem", false, "Failed to get PPLL raster fill Pass."); - return false; - } - - m_PPLLFillShader = rasterPass->GetShader(); - if (!m_PPLLFillShader) - { - AZ_Error("Hair Gem", false, "Failed to get hair raster fill shader from raster pass"); + AZ_Error("Hair Gem", false, "Failed to get hair geometry raster shader"); return false; } } @@ -1116,7 +1109,7 @@ namespace AZ return updatedCB; } - bool HairRenderObject::BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest) + bool HairRenderObject::BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest) { RHI::DrawPacketBuilder drawPacketBuilder; RHI::DrawIndexed drawIndexed; @@ -1159,21 +1152,38 @@ namespace AZ drawPacketBuilder.AddShaderResourceGroup(simSrg->GetRHIShaderResourceGroup()); drawPacketBuilder.AddDrawItem(drawRequest); - if (m_fillDrawPacket) + const RHI::DrawPacket* drawPacket = drawPacketBuilder.End(); + if (!drawPacket) { - delete m_fillDrawPacket; + AZ_Error("Hair Gem", false, "Failed to build the hair DrawPacket."); + return false; } - m_fillDrawPacket = drawPacketBuilder.End(); - if (!m_fillDrawPacket) + // Insert the newly created draw packet to the map based on its shader + auto iter = m_geometryDrawPackets.find(geometryShader); + if (iter != m_geometryDrawPackets.end()) { - AZ_Error("Hair Gem", false, "Failed to build the hair DrawPacket."); - return false; + delete iter->second; + iter->second = drawPacket; + } + else + { + m_geometryDrawPackets[geometryShader] = drawPacket; } return true; } + const RHI::DrawPacket* HairRenderObject::GetGeometrylDrawPacket(RPI::Shader* geometryShader) + { + auto iter = m_geometryDrawPackets.find(geometryShader); + if (iter == m_geometryDrawPackets.end()) + { + return nullptr; + } + return iter->second; + } + const RHI::DispatchItem* HairRenderObject::GetDispatchItem(RPI::Shader* computeShader) { auto dispatchIter = m_dispatchItems.find(computeShader); @@ -1210,4 +1220,3 @@ namespace AZ } // namespace Hair } // namespace Render } // namespace AZ - diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h index fa4095eed0..817ebd89fa 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h @@ -179,14 +179,9 @@ namespace AZ AMD::TressFXSimulationSettings* simSettings, AMD::TressFXRenderingSettings* renderSettings ); - //! Creates and fill the draw item associated with the PPLL render of the - //! current hair object - const RHI::DrawPacket* GetFillDrawPacket() - { - return m_fillDrawPacket; - } + bool BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest); - bool BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest); + const RHI::DrawPacket* GetGeometrylDrawPacket(RPI::Shader* geometryShader); //! Creates and fill the dispatch item associated with the compute shader bool BuildDispatchItem(RPI::Shader* computeShader, DispatchLevel dispatchLevel); @@ -302,17 +297,20 @@ namespace AZ //! responsible for the various stages and passes' updates HairFeatureProcessor* m_featureProcessor = nullptr; - //! The dispatch item used for the skinning - HairDispatchItem m_skinningDispatchItem; + //! Skinning compute shader used for creation of the compute Srgs and dispatch item + Data::Instance m_skinningShader = nullptr; //! Compute dispatch items map per the existing passes - AZStd::map> m_dispatchItems; + AZStd::unordered_map> m_dispatchItems; - //! Skinning compute shader used for creation of the compute Srgs and dispatch item - Data::Instance m_skinningShader = nullptr; + //! Geometry raster shader used for creation of the raster Srgs. + //! Since the Srgs for geometry raster are the same across the shaders we keep + //! only a single shader - if this to change in the future, several shaders and sets + //! of dynamic Srgs should be created. + Data::Instance m_geometryRasterShader = nullptr; - //! PPLL fill shader used for creation of the raster Srgs and draw item - Data::Instance m_PPLLFillShader = nullptr; + //! DrawPacket for the multi object geometry raster pass. + AZStd::unordered_map m_geometryDrawPackets; float m_frameDeltaTime = 0.02; @@ -378,9 +376,6 @@ namespace AZ //! Index buffer for the render pass via draw calls - naming was kept Data::Instance m_indexBuffer; RHI::IndexBufferView m_indexBufferView; - - //! DrawPacket for the multi object raster fill pass. - const RHI::DrawPacket* m_fillDrawPacket = nullptr; //------------------------------------------------------------------- AZStd::mutex m_mutex; diff --git a/Gems/AtomTressFX/Hair_files.cmake b/Gems/AtomTressFX/Hair_files.cmake index 000abeb559..180685d499 100644 --- a/Gems/AtomTressFX/Hair_files.cmake +++ b/Gems/AtomTressFX/Hair_files.cmake @@ -67,6 +67,13 @@ set(FILES # Base class of all geometry raster passes Code/Passes/HairGeometryRasterPass.h Code/Passes/HairGeometryRasterPass.cpp + + # ShortCut rendering technique - pass classes + Code/Passes/HairShortCutGeometryDepthAlphaPass.h + Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp + Code/Passes/HairShortCutGeometryShadingPass.h + Code/Passes/HairShortCutGeometryShadingPass.cpp + # PPLL rendering technique - geometry raster pass Code/Passes/HairPPLLRasterPass.h Code/Passes/HairPPLLRasterPass.cpp @@ -84,30 +91,37 @@ set(FILES Code/Assets/HairAsset.cpp #) #set(shaders_sources - # Srgs and Utility files - Assets/Shaders/HairSrgs.azsli - Assets/Shaders/HairSimulationSrgs.azsli + # Geometry and Full Screen azsl utility files Assets/Shaders/HairRenderingSrgs.azsli - Assets/Shaders/HairSimulationCommon.azsli Assets/Shaders/HairStrands.azsli Assets/Shaders/HairUtilities.azsli + Assets/Shaders/HairFullScreenUtils.azsli Assets/Shaders/HairLighting.azsli Assets/Shaders/HairLightingEquations.azsli Assets/Shaders/HairLightTypes.azsli Assets/Shaders/HairSurface.azsli - # Simulation Compute shaders + # ShortCut technique shaders (using multiple RTs instead of PPLL for GPU memory reduction) + Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl + Assets/Shaders/HairShortCutResolveDepth.azsl + Assets/Shaders/HairShortCutGeometryShading.azsl + Assets/Shaders/HairShortCutResolveColor.azsl + + # Rendering azsl files + Assets/Shaders/HairRenderingFillPPLL.azsl + Assets/Shaders/HairRenderingResolvePPLL.azsl + + # Simulation Compute azsl files + Assets/Shaders/HairComputeSrgs.azsli + Assets/Shaders/HairSimulationComputeSrgs.azsli + Assets/Shaders/HairSimulationCommon.azsli Assets/Shaders/HairSimulationCompute.azsl - # Collision shaders - to be included soon + # Collision azsl files - to be included soon # Assets/Shaders/HairCollisionPrepareSDF.azsl # Assets/Shaders/HairCollisionWithSDF.azsl - # Rendering shaders - Assets/Shaders/HairRenderingFillPPLL.azsl - Assets/Shaders/HairRenderingResolvePPLL.azsl - - # Simulation .shader files + # Simulation Compute .shader files Assets/Shaders/HairGlobalShapeConstraintsCompute.shader Assets/Shaders/HairCalculateStrandLevelDataCompute.shader Assets/Shaders/HairVelocityShockPropagationCompute.shader @@ -115,9 +129,15 @@ set(FILES Assets/Shaders/HairLengthConstraintsWindAndCollisionCompute.shader Assets/Shaders/HairUpdateFollowHairCompute.shader - # Rendering .shader file + # PPLL Render .shader file Assets/Shaders/HairRenderingFillPPLL.shader Assets/Shaders/HairRenderingResolvePPLL.shader + + # ShortCut Render .shader file + Assets/Shaders/HairShortCutGeometryDepthAlpha.shader + Assets/Shaders/HairShortCutResolveDepth.shader + Assets/Shaders/HairShortCutGeometryShading.shader + Assets/Shaders/HairShortCutResolveColor.shader # Colisions .shader files - to be included soon # Assets/Shaders/HairCollisionInitializeSDF.shader @@ -127,15 +147,25 @@ set(FILES #) # #set(atom_hair_passes + # Compute simulation and skinning passes Assets/Passes/HairParentPass.pass + Assets/Passes/HairParentShortCutPass.pass Assets/Passes/HairGlobalShapeConstraintsCompute.pass Assets/Passes/HairCalculateStrandLevelDataCompute.pass Assets/Passes/HairVelocityShockPropagationCompute.pass Assets/Passes/HairLocalShapeConstraintsCompute.pass Assets/Passes/HairLengthConstraintsWindAndCollisionCompute.pass Assets/Passes/HairUpdateFollowHairCompute.pass + + # PPLL render passes Assets/Passes/HairFillPPLL.pass Assets/Passes/HairResolvePPLL.pass + + # Shortcut render passes + Assets/Passes/HairShortCutGeometryDepthAlpha.pass + Assets/Passes/HairShortCutResolveDepth.pass + Assets/Passes/HairShortCutGeometryShading.pass + Assets/Passes/HairShortCutResolveColor.pass ) set(SKIP_UNITY_BUILD_INCLUSION_FILES