Hair - ShortCut rendering technique and pipeline with Marschner lighting model (#4871)

- This rendering technique reduces the memory required per pipeline by 750MB compared to the PPLL technique!!
- Using 3 screen buffers representing the closest 3 hair gragments depths.
- Going through second geometry pass that disqualify any fragment further than the third depth, the technique blends the three closes shaders hair fragments.
- Supports the Marschner lighting model (big change from the original TressFX 4.1 implementation)
- The current technique is almost twice the performance of the PPLL but not quite as advacned when come to final visual quality.

Remarks:
Unlike the PPLL, this technique is still lacking some of the advanced features added to the PPLL such as
1. Back lobe (TT) conseal by depth comparison
2. Thickness dependency in light transfer (mainly TT)
3. Allowing TT transfer for thin separated hair strands (might be supported by default with no distinction)

Signed-off-by: Adi-Amazon <Adi Bar-Lev 82479970+Adi-Amazon@users.noreply.github.com>
Signed-off-by: Adi-Amazon <Adi Bar-Lev barlev@amazon.com>
Signed-off-by: Adi-Amazon <Adi Bar-Lev 82479970+Adi-Amazon@users.noreply.github.com>
Signed-off-by: Adi-Amazon <Adi Bar-Lev barlev@amazon.com>

Co-authored-by: Adi-Amazon <Adi Bar-Lev 82479970+Adi-Amazon@users.noreply.github.com>
monroegm-disable-blank-issue-2
Adi Bar-Lev 4 years ago committed by GitHub
parent 546102077f
commit 64bbc700fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -212,7 +212,11 @@
// instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated
// .azsl file will need to be updated. // .azsl file will need to be updated.
"Name": "HairParentPass", "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, "Enabled": true,
"Connections": [ "Connections": [
// Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer.

@ -8,6 +8,11 @@
"Name": "HairParentPassTemplate", "Name": "HairParentPassTemplate",
"Path": "Passes/HairParentPass.pass" "Path": "Passes/HairParentPass.pass"
}, },
{
"Name": "HairParentShortCutPassTemplate",
"Path": "Passes/HairParentShortCutPass.pass"
},
{ {
"Name": "HairGlobalShapeConstraintsComputePassTemplate", "Name": "HairGlobalShapeConstraintsComputePassTemplate",
"Path": "Passes/HairGlobalShapeConstraintsCompute.pass" "Path": "Passes/HairGlobalShapeConstraintsCompute.pass"
@ -32,6 +37,7 @@
"Name": "HairUpdateFollowHairComputePassTemplate", "Name": "HairUpdateFollowHairComputePassTemplate",
"Path": "Passes/HairUpdateFollowHairCompute.pass" "Path": "Passes/HairUpdateFollowHairCompute.pass"
}, },
{ {
"Name": "HairPPLLRasterPassTemplate", "Name": "HairPPLLRasterPassTemplate",
"Path": "Passes/HairFillPPLL.pass" "Path": "Passes/HairFillPPLL.pass"
@ -39,6 +45,23 @@
{ {
"Name": "HairPPLLResolvePassTemplate", "Name": "HairPPLLResolvePassTemplate",
"Path": "Passes/HairResolvePPLL.pass" "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"
} }
] ]
} }

@ -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"
}
}
]
}
]
}
}
}

@ -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"
}
}
}
}
}

@ -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"
}
}
}
}
}

@ -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"
}
}
}
}
}

@ -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"
}
}
}
}
}

@ -29,7 +29,7 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// File: HairSRGs.azsli // File: HairComputeSrgs.azsli
// //
// Declarations of SRGs used by the hair shaders. // Declarations of SRGs used by the hair shaders.
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

@ -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 <Atom/Features/PostProcessing/FullscreenVertexInfo.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertexUtil.azsli>
#include <viewsrg.srgi>
//==============================================================================
// 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<float> 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<float> 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;
}

@ -230,7 +230,7 @@ float3 CalculateLighting(
return lightingData.diffuseLighting + lightingData.specularLighting; 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 vNDC; // normalized device / screen coordinates: [-1..1, -1..1, 0..1]
float3 vPositionWS = ScreenPosToWorldPos(PassSrg::m_linearDepth, pixelCoord, depth, vNDC); 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; 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 ---- //---- TressFX original lighting params setting ----
HairShadeParams params; HairShadeParams params;
params.m_color = baseColor; params.m_color = baseColor;
@ -266,11 +263,19 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f
if (o_hairLightingModel == HairLightingModel::Kajiya) if (o_hairLightingModel == HairLightingModel::Kajiya)
{ // This option should be removed and the Kajiya-Kay model should be operated from within { // This option should be removed and the Kajiya-Kay model should be operated from within
// the Atom lighting loop. // the Atom lighting loop.
accumulatedLight = SimplifiedHairLighting(vTangent, vPositionWS, vViewDirWS, params, vNDC); accumulatedLight = SimplifiedHairLighting(tangent, vPositionWS, vViewDirWS, params, vNDC);
} }
else else
{ {
accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, vTangent, thickness, params); accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, tangent, thickness, params);
} }
return accumulatedLight; 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);
}

@ -66,7 +66,7 @@ option bool o_enableAzimuthCoeff = true;
float M_R(Surface surface, float Lh, float sinLiPlusSinLr) float M_R(Surface surface, float Lh, float sinLiPlusSinLr)
{ {
float a = 1.0f * surface.cuticleTilt; // Tilt is translate as the mean offset 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(sinLiPlusSinLr, a, b); // reference
return GaussianNormalized(Lh, a, b); 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 M_TT(Surface surface, float Lh, float sinLiPlusSinLr)
{ {
float a = 1.0 * surface.cuticleTilt; float a = 1.0f * surface.cuticleTilt;
float b = 0.5 * surface.roughnessA2; float b = 0.5f * surface.roughnessA2;
// return GaussianNormalized(sinLiPlusSinLr, a, b); // reference // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference
return GaussianNormalized(Lh, a, b); 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 M_TRT(Surface surface, float Lh, float sinLiPlusSinLr)
{ {
float a = 1.5 * surface.cuticleTilt; float a = 1.5f * surface.cuticleTilt;
float b = 1.0 * surface.roughnessA2; float b = 1.0f * surface.roughnessA2;
// return GaussianNormalized(sinLiPlusSinLr, a, b); // reference // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference
return GaussianNormalized(Lh, a, b); return GaussianNormalized(Lh, a, b);

@ -40,7 +40,8 @@
//! that can change between passes due to the application of skinning, simulation //! that can change between passes due to the application of skinning, simulation
//! and physics affect and is then read by the rendering shaders. //! and physics affect and is then read by the rendering shaders.
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback 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 //! shared between all draw calls / dispatches for the hair skinning
StructuredBuffer<int> m_skinnedHairSharedBuffer; StructuredBuffer<int> m_skinnedHairSharedBuffer;
@ -101,39 +102,8 @@ ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance
#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents #define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents
//============================================================================== //==============================================================================
#include <HairStrands.azsli> #include <HairStrands.azsli> // 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 // Allocate a new fragment location in fragment color, depth, and link buffers
int AllocateFragment(int2 vScreenAddress) 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 // [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); // float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z);
uint2 dimensions; // uint2 dimensions;
PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); // 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 = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y));
float coverage = 1.0; float coverage = 1.0;
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////

@ -58,7 +58,7 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
// in the OIT process. // in the OIT process.
// It can also be used to avoid the HW blend done at the end of the pixel // 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. // shader stage but HW blend might be cheaper than additional PS blend.
Texture2D<float4> m_frameBuffer; // The merged MSAA output Texture2D<float4> m_frameBuffer; // The merged non-MSAA input
// Linear depth is used for getting the screen to world transform // Linear depth is used for getting the screen to world transform
Texture2D<float> m_linearDepth; Texture2D<float> m_linearDepth;
@ -93,26 +93,11 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
#define HairParams PassSrg::m_hairParams #define HairParams PassSrg::m_hairParams
//============================================================================== //==============================================================================
#include <HairFullScreenUtils.azsli> // provides the Vertex Shader
#include <HairLighting.azsli> #include <HairLighting.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertexInfo.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertexUtil.azsli>
// 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 // Bind data for PPLLResolvePS
#define NODE_DATA(x) LinkedListNodes[x].data #define NODE_DATA(x) LinkedListNodes[x].data
#define NODE_NEXT(x) LinkedListNodes[x].uNext #define NODE_NEXT(x) LinkedListNodes[x].uNext
#define NODE_DEPTH(x) LinkedListNodes[x].depth #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 uint shadeParamIndex; // So we know what settings to shade with
float3 vColor = UnpackUintIntoFloat3Byte(color, shadeParamIndex); 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 // Blend in the fragment color
fcolor.xyz = fcolor.xyz * (1.f - alpha) + fragmentColor * alpha; 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; float alpha = 1.0;
uint shadeParamIndex; // the material index uint shadeParamIndex; // the material index
float3 vColor = UnpackUintIntoFloat3Byte(curColor, shadeParamIndex); 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 // Blend in the fragment color
fcolor.xyz = fcolor.xyz * (1.f - alpha) + (fragmentColor * alpha); fcolor.xyz = fcolor.xyz * (1.f - alpha) + (fragmentColor * alpha);

@ -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 <Atom/Features/SrgSemantics.azsli>
#include <HairRenderingSrgs.azsli>
//!------------------------------ 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<int> m_skinnedHairSharedBuffer;
//! Based on [[vk::binding(0, 3)]] RWTexture2DArray<uint> RWFragmentDepthsTexture : register(u0, space3);
RWTexture2DArray<uint> 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<float4> m_hairVertexPositions;
Buffer<float4> 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 <HairStrands.azsli> // 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;
}

@ -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"
}
]
}
}

@ -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 <Atom/Features/SrgSemantics.azsli>
#include <HairRenderingSrgs.azsli>
#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<int> 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<float> m_linearDepth;
//------------------------------
// Lighting Data
//------------------------------
Sampler LinearSampler
{ // Required by LightingData.azsli
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
Texture2DArray<float> m_directionalLightShadowmap;
Texture2DArray<float> m_directionalLightExponentialShadowmap;
Texture2DArray<float> m_projectedShadowmaps;
Texture2DArray<float> m_projectedExponentialShadowmap;
Texture2D m_brdfMap;
Texture2D<uint4> m_tileLightData;
StructuredBuffer<uint> 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<float4> m_hairVertexPositions;
Buffer<float4> 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 <HairStrands.azsli> // VS resides here
#include <HairFullScreenUtils.azsli> // Required for world coordinates calculation
#include <HairLighting.azsli>
//!=============================================================================
//! 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);
}

@ -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"
}
]
}
}

@ -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 <Atom/Features/SrgSemantics.azsli>
#include <HairUtilities.azsli>
//!------------------------------ 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<float4> HaiColorTexture : register(t0, space0);
// oiriginally: [[vk::binding(1, 0)]] Texture2D<float> AccumInvAlpha : register(t1, space0);
Texture2D<float4> m_hairColorTexture;
Texture2D<float> m_accumInvAlpha;
}
//------------------------------------------------------------------------------
#include <HairFullScreenUtils.azsli> // 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;
}

@ -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"
}
]
}
}

@ -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 <Atom/Features/SrgSemantics.azsli>
//!------------------------------ 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<uint> FragmentDepthsTexture : register(t0, space0);
Texture2DArray<uint> m_fragmentDepthsTexture;
}
//------------------------------------------------------------------------------
#include <HairFullScreenUtils.azsli> // 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);
}

@ -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"
}
]
}
}

@ -31,7 +31,7 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
#include <HairSimulationSrgs.azsli> #include <HairSimulationComputeSrgs.azsli>
#include <HairSimulationCommon.azsli> #include <HairSimulationCommon.azsli>
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------

@ -29,13 +29,13 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// File: HairSRGs.azsli // File: HairSimulationComputeSrgs.azsli
// //
// Declarations of SRGs used by the hair shaders. // Declarations of SRGs used by the hair shaders.
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#pragma once #pragma once
#include <HairSrgs.azsli> #include <HairComputeSrgs.azsli>
//!----------------------------------------------------------------------------- //!-----------------------------------------------------------------------------
//! //!

@ -66,12 +66,22 @@ float3 GetSharedTangent(int tangentIndex)
); );
} }
//! Hair vertex geometry output - input structure for the Pixel shaders
struct TressFXVertex struct TressFXVertex
{ {
float4 Position; float4 Position;
float4 Tangent; float4 Tangent; // xyz = Tangent, w = Strand U
float4 p0p1; 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) float3 GetStrandColor(int index, float fractionOfStrand)
@ -178,5 +188,26 @@ TressFXVertex GetExpandedTressFXShadowVert(uint vertexId, float3 eye, float2 win
return Output; 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;
}

@ -34,9 +34,6 @@
#pragma once #pragma once
#include <viewsrg.srgi>
#define SHORTCUT_MIN_ALPHA 0.02 #define SHORTCUT_MIN_ALPHA 0.02
#define TRESSFX_FLOAT_EPSILON 1e-7 #define TRESSFX_FLOAT_EPSILON 1e-7
@ -52,40 +49,6 @@ float4 MatrixMult(float4x4 m, float4 v)
return mul(m, 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<float> 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<float> 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 // Pack a float4 into an uint
uint PackFloat4IntoUint(float4 vValue) uint PackFloat4IntoUint(float4 vValue)
{ {

@ -80,8 +80,14 @@ namespace AZ
// Load the AtomTressFX pass classes // Load the AtomTressFX pass classes
passSystem->AddPassCreator(Name("HairSkinningComputePass"), &HairSkinningComputePass::Create); passSystem->AddPassCreator(Name("HairSkinningComputePass"), &HairSkinningComputePass::Create);
// Load the PPLL render method passes
passSystem->AddPassCreator(Name("HairPPLLRasterPass"), &HairPPLLRasterPass::Create); passSystem->AddPassCreator(Name("HairPPLLRasterPass"), &HairPPLLRasterPass::Create);
passSystem->AddPassCreator(Name("HairPPLLResolvePass"), &HairPPLLResolvePass::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() void HairSystemComponent::Deactivate()

@ -165,6 +165,15 @@ namespace AZ
return true; return true;
} }
Data::Instance<RPI::Shader> HairGeometryRasterPass::GetShader()
{
if (!m_initialized || !m_shader)
{
AZ_Error("Hair Gem", LoadShaderAndPipelineState(), "HairGeometryRasterPass could not initialize pipeline or shader");
}
return m_shader;
}
void HairGeometryRasterPass::SchedulePacketBuild(HairRenderObject* hairObject) void HairGeometryRasterPass::SchedulePacketBuild(HairRenderObject* hairObject)
{ {
m_newRenderObjects.insert(hairObject); m_newRenderObjects.insert(hairObject);
@ -188,7 +197,7 @@ namespace AZ
// The PerPass is gathered through the RasterPass::m_shaderResourceGroup // The PerPass is gathered through the RasterPass::m_shaderResourceGroup
AZStd::lock_guard<AZStd::mutex> lock(m_mutex); AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
return hairObject->BuildPPLLDrawPacket(drawRequest); return hairObject->BuildDrawPacket(m_shader.get(), drawRequest);
} }
bool HairGeometryRasterPass::AddDrawPackets(AZStd::list<Data::Instance<HairRenderObject>>& hairRenderObjects) bool HairGeometryRasterPass::AddDrawPackets(AZStd::list<Data::Instance<HairRenderObject>>& hairRenderObjects)
@ -205,7 +214,7 @@ namespace AZ
for (auto& renderObject : hairRenderObjects) for (auto& renderObject : hairRenderObjects)
{ {
const RHI::DrawPacket* drawPacket = renderObject->GetFillDrawPacket(); const RHI::DrawPacket* drawPacket = renderObject->GetGeometrylDrawPacket(m_shader.get());
if (!drawPacket) if (!drawPacket)
{ // might not be an error - the object might have just been added and the DrawPacket is { // 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 // scheduled to be built when the render frame begins

@ -51,7 +51,7 @@ namespace AZ
//! The following will be called when an object was added or shader has been compiled //! The following will be called when an object was added or shader has been compiled
void SchedulePacketBuild(HairRenderObject* hairObject); void SchedulePacketBuild(HairRenderObject* hairObject);
Data::Instance<RPI::Shader> GetShader() { return m_shader; } Data::Instance<RPI::Shader> GetShader();
void SetFeatureProcessor(HairFeatureProcessor* featureProcessor) void SetFeatureProcessor(HairFeatureProcessor* featureProcessor)
{ {
@ -76,7 +76,6 @@ namespace AZ
// Pass behavior overrides // Pass behavior overrides
void InitializeInternal() override; void InitializeInternal() override;
// void BuildInternal() override;
void FrameBeginInternal(FramePrepareParams params) override; void FrameBeginInternal(FramePrepareParams params) override;
// Scope producer functions... // Scope producer functions...

@ -30,6 +30,17 @@ namespace AZ
HairPPLLResolvePass::HairPPLLResolvePass(const RPI::PassDescriptor& descriptor) HairPPLLResolvePass::HairPPLLResolvePass(const RPI::PassDescriptor& descriptor)
: RPI::FullscreenTrianglePass(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() void HairPPLLResolvePass::UpdateGlobalShaderOptions()
@ -38,17 +49,17 @@ namespace AZ
m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings);
shaderOption.SetValue(AZ::Name("o_enableShadows"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows });
shaderOption.SetValue(AZ::Name("o_enableDirectionalLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights });
shaderOption.SetValue(AZ::Name("o_enablePunctualLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights });
shaderOption.SetValue(AZ::Name("o_enableAreaLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights });
shaderOption.SetValue(AZ::Name("o_enableIBL"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); shaderOption.SetValue(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(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(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(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(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(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_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff });
m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue();
} }

@ -58,9 +58,20 @@ namespace AZ
void CompileResources(const RHI::FrameGraphCompileContext& context) override; void CompileResources(const RHI::FrameGraphCompileContext& context) override;
private: 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); HairPPLLResolvePass(const RPI::PassDescriptor& descriptor);
private:
void UpdateGlobalShaderOptions(); void UpdateGlobalShaderOptions();
HairGlobalSettings m_hairGlobalSettings; HairGlobalSettings m_hairGlobalSettings;

@ -9,7 +9,6 @@
#include <Atom/RHI/DrawListTagRegistry.h> #include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RHI/RHISystemInterface.h> #include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/RenderPipeline.h> #include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
namespace AZ namespace AZ
{ {

@ -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 <Atom/RPI.Reflect/Pass/RasterPassData.h>
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Passes/HairShortCutGeometryDepthAlphaPass.h>
#include <Rendering/HairRenderObject.h>
#include <Rendering/HairFeatureProcessor.h>
namespace AZ
{
namespace Render
{
namespace Hair
{
HairShortCutGeometryDepthAlphaPass::HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor)
: HairGeometryRasterPass(descriptor)
{
SetShaderPath("Shaders/hairshortcutgeometrydepthalpha.azshader");
}
RPI::Ptr<HairShortCutGeometryDepthAlphaPass> HairShortCutGeometryDepthAlphaPass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<HairShortCutGeometryDepthAlphaPass> 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

@ -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 <Passes/HairGeometryRasterPass.h>
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<HairShortCutGeometryDepthAlphaPass> Create(const RPI::PassDescriptor& descriptor);
protected:
explicit HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor);
// Pass behavior overrides
void BuildInternal() override;
};
} // namespace Hair
} // namespace Render
} // namespace AZ

@ -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 <Atom/RPI.Reflect/Pass/RasterPassData.h>
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Passes/HairShortCutGeometryShadingPass.h>
#include <Rendering/HairRenderObject.h>
#include <Rendering/HairFeatureProcessor.h>
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> HairShortCutGeometryShadingPass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<HairShortCutGeometryShadingPass> 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

@ -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 <Passes/HairGeometryRasterPass.h>
#include <Rendering/HairGlobalSettings.h>
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<HairShortCutGeometryShadingPass> 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

@ -47,11 +47,11 @@ namespace AZ
HairFeatureProcessor::HairFeatureProcessor() HairFeatureProcessor::HairFeatureProcessor()
{ {
HairParentPassName = Name{ "HairParentPass" }; m_usePPLLRenderTechnique = false; // Use the ShortCut rendering technique
HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; HairParentPassName = Name{ "HairParentPass" };
HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" };
// Hair Skinning and Simulation Compute passes
GlobalShapeConstraintsPassName = Name{ "HairGlobalShapeConstraintsComputePass" }; GlobalShapeConstraintsPassName = Name{ "HairGlobalShapeConstraintsComputePass" };
CalculateStrandDataPassName = Name{ "HairCalculateStrandLevelDataComputePass" }; CalculateStrandDataPassName = Name{ "HairCalculateStrandLevelDataComputePass" };
VelocityShockPropagationPassName = Name{ "HairVelocityShockPropagationComputePass" }; VelocityShockPropagationPassName = Name{ "HairVelocityShockPropagationComputePass" };
@ -59,12 +59,21 @@ namespace AZ
LengthConstriantsWindAndCollisionPassName = Name{ "HairLengthConstraintsWindAndCollisionComputePass" }; LengthConstriantsWindAndCollisionPassName = Name{ "HairLengthConstraintsWindAndCollisionComputePass" };
UpdateFollowHairPassName = Name{ "HairUpdateFollowHairComputePass" }; 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; ++s_instanceCount;
if (!CreatePerPassResources()) if (!CreatePerPassResources())
{ // this might not be an error - if the pass system is still empty / minimal { // 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 // and these passes are not part of the minimal pipeline, they will not be created.
// be created.
AZ_Error("Hair Gem", false, "Failed to create the hair shared buffer resource"); AZ_Error("Hair Gem", false, "Failed to create the hair shared buffer resource");
} }
} }
@ -127,25 +136,33 @@ namespace AZ
m_hairRenderObjects.push_back(renderObject); m_hairRenderObjects.push_back(renderObject);
// Adding the object will schedule Srgs binding and the DrawItem build for the geometry passes.
BuildDispatchAndDrawItems(renderObject); BuildDispatchAndDrawItems(renderObject);
EnablePasses(true); 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) if (!m_initialized)
{ {
return; return;
} }
for (auto& [passName, pass] : m_computePasses) RPI::Ptr<RPI::Pass> 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<HairRenderObject> renderObject) bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance<HairRenderObject> renderObject)
@ -214,7 +231,8 @@ namespace AZ
} }
if (m_forceRebuildRenderData) 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) for (auto& hairRenderObject : m_hairRenderObjects)
{ {
BuildDispatchAndDrawItems(hairRenderObject); BuildDispatchAndDrawItems(hairRenderObject);
@ -276,17 +294,32 @@ namespace AZ
pass->AddDispatchItems(m_hairRenderObjects); pass->AddDispatchItems(m_hairRenderObjects);
} }
// Add all hair objects to the Render / Raster Pass if (m_usePPLLRenderTechnique)
m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); {
// 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() void HairFeatureProcessor::ClearPasses()
{ {
m_initialized = false; // Avoid simulation or render m_initialized = false; // Avoid simulation or render
m_computePasses.clear(); m_computePasses.clear();
// PPLL geometry and resolve full screen passes
m_hairPPLLRasterPass = nullptr; m_hairPPLLRasterPass = nullptr;
m_hairPPLLResolvePass = 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. // Mark for all passes to evacuate their render data and recreate it.
m_forceRebuildRenderData = true; m_forceRebuildRenderData = true;
m_forceClearRenderData = true; m_forceClearRenderData = true;
@ -338,6 +371,12 @@ namespace AZ
ClearPasses(); 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 // Compute Passes - populate the passes map
bool resultSuccess = InitComputePass(GlobalShapeConstraintsPassName); bool resultSuccess = InitComputePass(GlobalShapeConstraintsPassName);
resultSuccess &= InitComputePass(CalculateStrandDataPassName); resultSuccess &= InitComputePass(CalculateStrandDataPassName);
@ -347,8 +386,15 @@ namespace AZ
resultSuccess &= InitComputePass(UpdateFollowHairPassName); resultSuccess &= InitComputePass(UpdateFollowHairPassName);
// Rendering Passes // Rendering Passes
resultSuccess &= InitPPLLFillPass(); if (m_usePPLLRenderTechnique)
resultSuccess &= InitPPLLResolvePass(); {
resultSuccess &= InitPPLLFillPass();
resultSuccess &= InitPPLLResolvePass();
}
else
{
resultSuccess &= InitShortCutRenderPasses();
}
m_initialized = resultSuccess; 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( descriptor = SrgBufferDescriptor(
RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown, RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown,
@ -425,11 +472,6 @@ namespace AZ
bool HairFeatureProcessor::InitComputePass(const Name& passName, bool allowIterations) bool HairFeatureProcessor::InitComputePass(const Name& passName, bool allowIterations)
{ {
m_computePasses[passName] = nullptr; 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<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(passName); RPI::Ptr<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(passName);
if (desiredPass) if (desiredPass)
@ -452,11 +494,6 @@ namespace AZ
bool HairFeatureProcessor::InitPPLLFillPass() bool HairFeatureProcessor::InitPPLLFillPass()
{ {
m_hairPPLLRasterPass = nullptr; // reset it to null, just in case it fails to load the assets properly 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<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLRasterPassName); RPI::Ptr<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLRasterPassName);
if (desiredPass) if (desiredPass)
@ -466,7 +503,7 @@ namespace AZ
} }
else 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 false;
} }
return true; return true;
@ -475,11 +512,6 @@ namespace AZ
bool HairFeatureProcessor::InitPPLLResolvePass() bool HairFeatureProcessor::InitPPLLResolvePass()
{ {
m_hairPPLLResolvePass = nullptr; // reset it to null, just in case it fails to load the assets properly 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<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLResolvePassName); RPI::Ptr<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLResolvePassName);
if (desiredPass) if (desiredPass)
@ -489,12 +521,46 @@ namespace AZ
} }
else 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 false;
} }
return true; 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<HairShortCutGeometryDepthAlphaPass*>(
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<HairShortCutGeometryShadingPass*>(
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<HairRenderObject> renderObject) void HairFeatureProcessor::BuildDispatchAndDrawItems(Data::Instance<HairRenderObject> renderObject)
{ {
HairRenderObject* renderObjectPtr = renderObject.get(); HairRenderObject* renderObjectPtr = renderObject.get();
@ -513,9 +579,18 @@ namespace AZ
m_computePasses[UpdateFollowHairPassName]->BuildDispatchItem( m_computePasses[UpdateFollowHairPassName]->BuildDispatchItem(
renderObjectPtr, DispatchLevel::DISPATCHLEVEL_VERTEX); renderObjectPtr, DispatchLevel::DISPATCHLEVEL_VERTEX);
// Render / Raster pass - adding the object will schedule Srgs binding // Schedule Srgs binding and the DrawItem build.
// and DrawItem build. // Since this does not bind the PerPass srg but prepare the rest of the Srgs
m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); // 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<HairSkinningComputePass> HairFeatureProcessor::GetHairSkinningComputegPass() Data::Instance<HairSkinningComputePass> HairFeatureProcessor::GetHairSkinningComputegPass()
@ -527,14 +602,28 @@ namespace AZ
return m_computePasses[GlobalShapeConstraintsPassName]; return m_computePasses[GlobalShapeConstraintsPassName];
} }
Data::Instance<HairPPLLRasterPass> HairFeatureProcessor::GetHairPPLLRasterPass() Data::Instance<RPI::Shader> 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 Hair
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ

@ -17,14 +17,19 @@
#include <Atom/RPI.Public/FeatureProcessor.h> #include <Atom/RPI.Public/FeatureProcessor.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h> #include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
// Hair specific // Hair specific
#include <TressFX/TressFXConstantBuffers.h> #include <TressFX/TressFXConstantBuffers.h>
#include <Passes/HairSkinningComputePass.h> #include <Passes/HairSkinningComputePass.h>
#include <Passes/HairPPLLRasterPass.h> #include <Passes/HairPPLLRasterPass.h>
#include <Passes/HairPPLLResolvePass.h> #include <Passes/HairPPLLResolvePass.h>
#include <Passes/HairShortCutGeometryDepthAlphaPass.h>
#include <Passes/HairShortCutGeometryShadingPass.h>
#include <Rendering/HairRenderObject.h> #include <Rendering/HairRenderObject.h>
#include <Rendering/SharedBuffer.h> #include <Rendering/SharedBuffer.h>
#include <Rendering/HairCommon.h> #include <Rendering/HairCommon.h>
@ -73,9 +78,7 @@ namespace AZ
{ {
Name HairParentPassName; Name HairParentPassName;
Name HairPPLLRasterPassName; // Compute passes
Name HairPPLLResolvePassName;
Name GlobalShapeConstraintsPassName; Name GlobalShapeConstraintsPassName;
Name CalculateStrandDataPassName; Name CalculateStrandDataPassName;
Name VelocityShockPropagationPassName; Name VelocityShockPropagationPassName;
@ -83,6 +86,16 @@ namespace AZ
Name LengthConstriantsWindAndCollisionPassName; Name LengthConstriantsWindAndCollisionPassName;
Name UpdateFollowHairPassName; Name UpdateFollowHairPassName;
// PPLL render passes
Name HairPPLLRasterPassName;
Name HairPPLLResolvePassName;
// ShortCut render passes
Name HairShortCutGeometryDepthAlphaPassName;
Name HairShortCutResolveDepthPassName;
Name HairShortCutGeometryShadingPassName;
Name HairShortCutResolveColorPassName;
public: public:
AZ_RTTI(AZ::Render::Hair::HairFeatureProcessor, "{5F9DDA81-B43F-4E30-9E56-C7C3DC517A4C}", RPI::FeatureProcessor); AZ_RTTI(AZ::Render::Hair::HairFeatureProcessor, "{5F9DDA81-B43F-4E30-9E56-C7C3DC517A4C}", RPI::FeatureProcessor);
@ -117,6 +130,7 @@ namespace AZ
Data::Instance<HairSkinningComputePass> GetHairSkinningComputegPass(); Data::Instance<HairSkinningComputePass> GetHairSkinningComputegPass();
Data::Instance<HairPPLLRasterPass> GetHairPPLLRasterPass(); Data::Instance<HairPPLLRasterPass> GetHairPPLLRasterPass();
Data::Instance<RPI::Shader> GetGeometryRasterShader();
//! Update the hair objects materials array. //! Update the hair objects materials array.
void FillHairMaterialsArray(std::vector<const AMD::TressFXRenderParams*>& renderSettings); void FillHairMaterialsArray(std::vector<const AMD::TressFXRenderParams*>& renderSettings);
@ -144,6 +158,7 @@ namespace AZ
bool InitPPLLFillPass(); bool InitPPLLFillPass();
bool InitPPLLResolvePass(); bool InitPPLLResolvePass();
bool InitShortCutRenderPasses();
bool InitComputePass(const Name& passName, bool allowIterations = false); bool InitComputePass(const Name& passName, bool allowIterations = false);
void BuildDispatchAndDrawItems(Data::Instance<HairRenderObject> renderObject); void BuildDispatchAndDrawItems(Data::Instance<HairRenderObject> renderObject);
@ -168,10 +183,14 @@ namespace AZ
//! Simulation Compute Passes //! Simulation Compute Passes
AZStd::unordered_map<Name, Data::Instance<HairSkinningComputePass> > m_computePasses; AZStd::unordered_map<Name, Data::Instance<HairSkinningComputePass> > m_computePasses;
// Render Passes // PPLL Render Passes
Data::Instance<HairPPLLRasterPass> m_hairPPLLRasterPass = nullptr; Data::Instance<HairPPLLRasterPass> m_hairPPLLRasterPass = nullptr;
Data::Instance<HairPPLLResolvePass> m_hairPPLLResolvePass = nullptr; Data::Instance<HairPPLLResolvePass> m_hairPPLLResolvePass = nullptr;
// ShortCut Render Passes - special case for the geometry render passes
Data::Instance<HairShortCutGeometryDepthAlphaPass> m_hairShortCutGeometryDepthAlphaPass = nullptr;
Data::Instance<HairShortCutGeometryShadingPass> m_hairShortCutGeometryShadingPass = nullptr;
//-------------------------------------------------------------- //--------------------------------------------------------------
// Per Pass Resources // Per Pass Resources
//-------------------------------------------------------------- //--------------------------------------------------------------
@ -196,6 +215,7 @@ namespace AZ
bool m_forceClearRenderData = false; bool m_forceClearRenderData = false;
bool m_initialized = false; bool m_initialized = false;
bool m_isEnabled = true; bool m_isEnabled = true;
bool m_usePPLLRenderTechnique = true;
static uint32_t s_instanceCount; static uint32_t s_instanceCount;
HairGlobalSettings m_hairGlobalSettings; HairGlobalSettings m_hairGlobalSettings;

@ -993,7 +993,7 @@ namespace AZ
//------------------------------------- //-------------------------------------
// Dynamic buffers, data and Srg creation - shared between passes and changed on the GPU // Dynamic buffers, data and Srg creation - shared between passes and changed on the GPU
if (!m_dynamicHairData.CreateDynamicGPUResources( if (!m_dynamicHairData.CreateDynamicGPUResources(
m_skinningShader, m_PPLLFillShader, m_skinningShader, m_geometryRasterShader,
m_NumTotalVertices, m_NumTotalStrands)) m_NumTotalVertices, m_NumTotalStrands))
{ {
AZ_Error("Hair Gem", false, "Hair - Error creating dynamic resources [%s]", assetName ); AZ_Error("Hair Gem", false, "Hair - Error creating dynamic resources [%s]", assetName );
@ -1028,7 +1028,7 @@ namespace AZ
// Rendering setup // Rendering setup
bool renderResourcesSuccess; bool renderResourcesSuccess;
renderResourcesSuccess = CreateRenderingGPUResources(m_PPLLFillShader, *asset, assetName); renderResourcesSuccess = CreateRenderingGPUResources(m_geometryRasterShader, *asset, assetName);
renderResourcesSuccess &= PopulateDrawStrandsBindSet(renderSettings); renderResourcesSuccess &= PopulateDrawStrandsBindSet(renderSettings);
renderResourcesSuccess &= UploadRenderingGPUResources(*asset); renderResourcesSuccess &= UploadRenderingGPUResources(*asset);
@ -1057,17 +1057,10 @@ namespace AZ
} }
{ {
Data::Instance<HairPPLLRasterPass> rasterPass = m_featureProcessor->GetHairPPLLRasterPass(); m_geometryRasterShader = m_featureProcessor->GetGeometryRasterShader();
if (!rasterPass.get()) if (!m_geometryRasterShader)
{ {
AZ_Error("Hair Gem", false, "Failed to get PPLL raster fill Pass."); AZ_Error("Hair Gem", false, "Failed to get hair geometry raster shader");
return false;
}
m_PPLLFillShader = rasterPass->GetShader();
if (!m_PPLLFillShader)
{
AZ_Error("Hair Gem", false, "Failed to get hair raster fill shader from raster pass");
return false; return false;
} }
} }
@ -1116,7 +1109,7 @@ namespace AZ
return updatedCB; return updatedCB;
} }
bool HairRenderObject::BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest) bool HairRenderObject::BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest)
{ {
RHI::DrawPacketBuilder drawPacketBuilder; RHI::DrawPacketBuilder drawPacketBuilder;
RHI::DrawIndexed drawIndexed; RHI::DrawIndexed drawIndexed;
@ -1159,21 +1152,38 @@ namespace AZ
drawPacketBuilder.AddShaderResourceGroup(simSrg->GetRHIShaderResourceGroup()); drawPacketBuilder.AddShaderResourceGroup(simSrg->GetRHIShaderResourceGroup());
drawPacketBuilder.AddDrawItem(drawRequest); 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."); delete iter->second;
return false; iter->second = drawPacket;
}
else
{
m_geometryDrawPackets[geometryShader] = drawPacket;
} }
return true; 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) const RHI::DispatchItem* HairRenderObject::GetDispatchItem(RPI::Shader* computeShader)
{ {
auto dispatchIter = m_dispatchItems.find(computeShader); auto dispatchIter = m_dispatchItems.find(computeShader);
@ -1210,4 +1220,3 @@ namespace AZ
} // namespace Hair } // namespace Hair
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ

@ -179,14 +179,9 @@ namespace AZ
AMD::TressFXSimulationSettings* simSettings, AMD::TressFXRenderingSettings* renderSettings AMD::TressFXSimulationSettings* simSettings, AMD::TressFXRenderingSettings* renderSettings
); );
//! Creates and fill the draw item associated with the PPLL render of the bool BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest);
//! current hair object
const RHI::DrawPacket* GetFillDrawPacket()
{
return m_fillDrawPacket;
}
bool BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest); const RHI::DrawPacket* GetGeometrylDrawPacket(RPI::Shader* geometryShader);
//! Creates and fill the dispatch item associated with the compute shader //! Creates and fill the dispatch item associated with the compute shader
bool BuildDispatchItem(RPI::Shader* computeShader, DispatchLevel dispatchLevel); bool BuildDispatchItem(RPI::Shader* computeShader, DispatchLevel dispatchLevel);
@ -302,17 +297,20 @@ namespace AZ
//! responsible for the various stages and passes' updates //! responsible for the various stages and passes' updates
HairFeatureProcessor* m_featureProcessor = nullptr; HairFeatureProcessor* m_featureProcessor = nullptr;
//! The dispatch item used for the skinning //! Skinning compute shader used for creation of the compute Srgs and dispatch item
HairDispatchItem m_skinningDispatchItem; Data::Instance<RPI::Shader> m_skinningShader = nullptr;
//! Compute dispatch items map per the existing passes //! Compute dispatch items map per the existing passes
AZStd::map<RPI::Shader*, Data::Instance<HairDispatchItem>> m_dispatchItems; AZStd::unordered_map<RPI::Shader*, Data::Instance<HairDispatchItem>> m_dispatchItems;
//! Skinning compute shader used for creation of the compute Srgs and dispatch item //! Geometry raster shader used for creation of the raster Srgs.
Data::Instance<RPI::Shader> m_skinningShader = nullptr; //! 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<RPI::Shader> m_geometryRasterShader = nullptr;
//! PPLL fill shader used for creation of the raster Srgs and draw item //! DrawPacket for the multi object geometry raster pass.
Data::Instance<RPI::Shader> m_PPLLFillShader = nullptr; AZStd::unordered_map<RPI::Shader*, const RHI::DrawPacket*> m_geometryDrawPackets;
float m_frameDeltaTime = 0.02; float m_frameDeltaTime = 0.02;
@ -378,9 +376,6 @@ namespace AZ
//! Index buffer for the render pass via draw calls - naming was kept //! Index buffer for the render pass via draw calls - naming was kept
Data::Instance<RHI::Buffer> m_indexBuffer; Data::Instance<RHI::Buffer> m_indexBuffer;
RHI::IndexBufferView m_indexBufferView; RHI::IndexBufferView m_indexBufferView;
//! DrawPacket for the multi object raster fill pass.
const RHI::DrawPacket* m_fillDrawPacket = nullptr;
//------------------------------------------------------------------- //-------------------------------------------------------------------
AZStd::mutex m_mutex; AZStd::mutex m_mutex;

@ -67,6 +67,13 @@ set(FILES
# Base class of all geometry raster passes # Base class of all geometry raster passes
Code/Passes/HairGeometryRasterPass.h Code/Passes/HairGeometryRasterPass.h
Code/Passes/HairGeometryRasterPass.cpp 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 # PPLL rendering technique - geometry raster pass
Code/Passes/HairPPLLRasterPass.h Code/Passes/HairPPLLRasterPass.h
Code/Passes/HairPPLLRasterPass.cpp Code/Passes/HairPPLLRasterPass.cpp
@ -84,30 +91,37 @@ set(FILES
Code/Assets/HairAsset.cpp Code/Assets/HairAsset.cpp
#) #)
#set(shaders_sources #set(shaders_sources
# Srgs and Utility files # Geometry and Full Screen azsl utility files
Assets/Shaders/HairSrgs.azsli
Assets/Shaders/HairSimulationSrgs.azsli
Assets/Shaders/HairRenderingSrgs.azsli Assets/Shaders/HairRenderingSrgs.azsli
Assets/Shaders/HairSimulationCommon.azsli
Assets/Shaders/HairStrands.azsli Assets/Shaders/HairStrands.azsli
Assets/Shaders/HairUtilities.azsli Assets/Shaders/HairUtilities.azsli
Assets/Shaders/HairFullScreenUtils.azsli
Assets/Shaders/HairLighting.azsli Assets/Shaders/HairLighting.azsli
Assets/Shaders/HairLightingEquations.azsli Assets/Shaders/HairLightingEquations.azsli
Assets/Shaders/HairLightTypes.azsli Assets/Shaders/HairLightTypes.azsli
Assets/Shaders/HairSurface.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 Assets/Shaders/HairSimulationCompute.azsl
# Collision shaders - to be included soon # Collision azsl files - to be included soon
# Assets/Shaders/HairCollisionPrepareSDF.azsl # Assets/Shaders/HairCollisionPrepareSDF.azsl
# Assets/Shaders/HairCollisionWithSDF.azsl # Assets/Shaders/HairCollisionWithSDF.azsl
# Rendering shaders # Simulation Compute .shader files
Assets/Shaders/HairRenderingFillPPLL.azsl
Assets/Shaders/HairRenderingResolvePPLL.azsl
# Simulation .shader files
Assets/Shaders/HairGlobalShapeConstraintsCompute.shader Assets/Shaders/HairGlobalShapeConstraintsCompute.shader
Assets/Shaders/HairCalculateStrandLevelDataCompute.shader Assets/Shaders/HairCalculateStrandLevelDataCompute.shader
Assets/Shaders/HairVelocityShockPropagationCompute.shader Assets/Shaders/HairVelocityShockPropagationCompute.shader
@ -115,9 +129,15 @@ set(FILES
Assets/Shaders/HairLengthConstraintsWindAndCollisionCompute.shader Assets/Shaders/HairLengthConstraintsWindAndCollisionCompute.shader
Assets/Shaders/HairUpdateFollowHairCompute.shader Assets/Shaders/HairUpdateFollowHairCompute.shader
# Rendering .shader file # PPLL Render .shader file
Assets/Shaders/HairRenderingFillPPLL.shader Assets/Shaders/HairRenderingFillPPLL.shader
Assets/Shaders/HairRenderingResolvePPLL.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 # Colisions .shader files - to be included soon
# Assets/Shaders/HairCollisionInitializeSDF.shader # Assets/Shaders/HairCollisionInitializeSDF.shader
@ -127,15 +147,25 @@ set(FILES
#) #)
# #
#set(atom_hair_passes #set(atom_hair_passes
# Compute simulation and skinning passes
Assets/Passes/HairParentPass.pass Assets/Passes/HairParentPass.pass
Assets/Passes/HairParentShortCutPass.pass
Assets/Passes/HairGlobalShapeConstraintsCompute.pass Assets/Passes/HairGlobalShapeConstraintsCompute.pass
Assets/Passes/HairCalculateStrandLevelDataCompute.pass Assets/Passes/HairCalculateStrandLevelDataCompute.pass
Assets/Passes/HairVelocityShockPropagationCompute.pass Assets/Passes/HairVelocityShockPropagationCompute.pass
Assets/Passes/HairLocalShapeConstraintsCompute.pass Assets/Passes/HairLocalShapeConstraintsCompute.pass
Assets/Passes/HairLengthConstraintsWindAndCollisionCompute.pass Assets/Passes/HairLengthConstraintsWindAndCollisionCompute.pass
Assets/Passes/HairUpdateFollowHairCompute.pass Assets/Passes/HairUpdateFollowHairCompute.pass
# PPLL render passes
Assets/Passes/HairFillPPLL.pass Assets/Passes/HairFillPPLL.pass
Assets/Passes/HairResolvePPLL.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 set(SKIP_UNITY_BUILD_INCLUSION_FILES

Loading…
Cancel
Save