Files
UnrealEngine/Engine/Shaders/Private/HairStrands/HairStrandsMaterialCommon.ush
2025-05-18 13:04:45 +08:00

294 lines
12 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
float3 DecodeVelocityFromTexture(float4 In);
#include "HairStrandsVisibilityCommon.ush"
// Note: Culled cluster is disabled as the input to the vertex factory from HairStrandsMaterialPS or HairStrandsMaterialGBufferPS
// are alredy culled vertex (coming from the visibility buffer). By disabling the cluster within this material pass, we insure that
// the provided primitive ID will be used for feteching attrinbute, rather than the one for conmputing the indirect ID
//
// This is required as the hair strands vertex factory enabled culled cluster by default (to support shadow & hit proxy shader for
// which we can't provide custom defines)
#define USE_CULLED_CLUSTER 0
#define USE_FORCE_TEXTURE_MIP 1
#define FORCED_TEXTURE_MIP 0.0f
#include "../Common.ush"
#include "../SceneTexturesCommon.ush"
#include "../DeferredShadingCommon.ush"
#include "../VelocityCommon.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"
#include "HairStrandsVertexFactoryCommon.ush"
#ifndef USE_HAIR_TRIANGLE_STRIP
#error Hair triangle geometry type needs to be defined
#endif
#define SUPPORT_MATERIAL_PROPERTY 1
uint MaterialPass_MacroGroupId;
uint MaterialPass_MaterialId;
uint MaterialPass_PrimitiveId;
uint MaterialPass_LightChannelMask;
uint MaterialPass_Flags;
float MaterialPass_HairCoverageScale;
#ifndef HAIR_MATERIAL_DEBUG_OUTPUT
#define HAIR_MATERIAL_DEBUG_OUTPUT 0
#endif
#if HAIR_STRAND_MESH_FACTORY
struct FEvaluateOutput
{
FHairSample NodeData;
float4 NodeVelocity;
};
struct FHairVertexData
{
float3 World_P;
float3 World_P_Prev;
float Radius;
bool bIsValid;
};
// GPUCULL_TODO: this should be switched over to use Instance ID instead of Primitive ID.
FVertexFactoryInterpolantsVSToPS GetInterpolants(uint PrimitiveId, uint HairControlPointId, inout FHairVertexData OutHairVertex)
{
FMaterialVertexParameters Empty = MakeInitializedMaterialVertexParameters();
FVertexFactoryInput Input;
// Assumes no actual instancing used for these draws. Will load the first instance data.
VF_GPUSCENE_SET_INPUT_FOR_HAIR(Input, PrimitiveId, 0U);
// * USE_HAIR_TRIANGLE_STRIP=0: the vertex factory expect triangles strip index. A quad is made of two triangles with indices: 0,1.
// * USE_HAIR_TRIANGLE_STRIP=1: the vertex factory expect triangles list index. A quad is made of two triangles with indices: 0,1,2,3,4,5.
Input.VertexId = HairControlPointId * HAIR_POINT_TO_VERTEX;
FVertexFactoryIntermediates Intermediates = GetVertexFactoryIntermediates(Input);
FVertexFactoryInterpolantsVSToPS Interpolants = VertexFactoryGetInterpolantsVSToPS(Input, Intermediates, Empty);
OutHairVertex.World_P = VertexFactoryGetWorldPositionRaw(Input, Intermediates, false).xyz;
OutHairVertex.World_P_Prev = VertexFactoryGetPreviousWorldPosition(Input, Intermediates, false).xyz;
const FHairControlPoint ControlPoint = GetVertexPosition(Input, false);
OutHairVertex.Radius = ControlPoint.WorldRadius;
OutHairVertex.bIsValid = ControlPoint.Type != HAIR_CONTROLPOINT_END;
return Interpolants;
}
FEvaluateOutput Evaluate(
in float2 SamplePixelCoord,
in float SampleDepth,
in uint SampleControlPointId,
in uint SampleCoverage8bit,
in uint PrimitiveId,
in bool bUpdateSampleCoverage,
in bool bInterpolationEnabled)
{
// World_P / World_P0 / World_P1 are in translated world space
const float SceneDepth = ConvertFromDeviceZ(SampleDepth);
const float2 UV = SamplePixelCoord / float2(View.BufferSizeAndInvSize.xy);
const float2 ScreenPosition = (UV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
const float3 World_P = mul(float4(GetScreenPositionForProjectionType(ScreenPosition, SceneDepth), SceneDepth, 1), View.ScreenToTranslatedWorld).xyz;
float3 Velocity_World_P;
float3 Velocity_World_P_Prev;
float WorldStrandRadius = 0;
FVertexFactoryInterpolantsVSToPS Interpolants;
{
const uint HairControlPointId0 = SampleControlPointId;
FHairVertexData Vertex0;
FVertexFactoryInterpolantsVSToPS Interpolants0 = GetInterpolants(PrimitiveId, HairControlPointId0, Vertex0);
Interpolants = Interpolants0;
float SegmentU = 0.0f;
float SegmentV = 0.5f;
if (bInterpolationEnabled)
{
const uint HairControlPointId1 = HairControlPointId0 + 1; //TODO: min(HairControlPointId0 + 1, MaxVertexCount-1);
FHairVertexData Vertex1;
FVertexFactoryInterpolantsVSToPS Interpolants1 = GetInterpolants(PrimitiveId, HairControlPointId1, Vertex1);
// Compute U
// Use the projection of the current sample point onto the hair segment (which is in the middle of the hair strands)
const float3 A = (World_P - Vertex0.World_P);
const float3 B = (Vertex1.World_P - Vertex0.World_P);
{
const float CosA = dot(A, B);
const float LengthB2 = dot(B, B);
SegmentU = LengthB2 > 0 ? saturate(CosA / LengthB2) : 0;
}
// Compute V
{
const float3 W = cross(A, B);
const bool bIsRight = dot(W, View.ViewForward) < 0;
const float WorldV = length(A - B * SegmentU);
WorldStrandRadius = lerp(Vertex0.Radius, Vertex1.Radius, SegmentU);
SegmentV = WorldV / WorldStrandRadius;
SegmentV = bIsRight ? SegmentV * 0.5f + 0.5f : (1 - SegmentV) * 0.5f;
}
Velocity_World_P = lerp(Vertex0.World_P, Vertex1.World_P, SegmentU);
Velocity_World_P_Prev = lerp(Vertex0.World_P_Prev, Vertex1.World_P_Prev, SegmentU);
Interpolants.TangentToWorld0 = lerp(Interpolants0.TangentToWorld0, Interpolants1.TangentToWorld0, SegmentU);
Interpolants.TangentToWorld2 = lerp(Interpolants0.TangentToWorld2, Interpolants1.TangentToWorld2, SegmentU);
}
else
{
WorldStrandRadius = Vertex0.Radius;
Interpolants.TangentToWorld0 = Interpolants0.TangentToWorld0;
Interpolants.TangentToWorld2 = Interpolants0.TangentToWorld2;
Velocity_World_P = Vertex0.World_P;
Velocity_World_P_Prev = Vertex0.World_P_Prev;
}
Interpolants.TangentToWorld0 = normalize(Interpolants.TangentToWorld0);
Interpolants.TangentToWorld2 = normalize(Interpolants.TangentToWorld2);
#if VF_USE_PRIMITIVE_SCENE_DATA
Interpolants.PrimitiveId = PrimitiveId;
#endif // VF_USE_PRIMITIVE_SCENE_DATA
Interpolants.HairControlPointId = HairControlPointId0;
Interpolants.HairPrimitiveUV = float2(SegmentU, SegmentV);
}
float4 EncodedVelocity = 0;
{
const float4 ScreenPos = mul(float4(Velocity_World_P.xyz, 1), ResolvedView.TranslatedWorldToClip);
const float4 PrevScreenPos = mul(float4(Velocity_World_P_Prev.xyz, 1), ResolvedView.PrevTranslatedWorldToClip);
const float3 Velocity = Calculate3DVelocity(ScreenPos, PrevScreenPos);
EncodedVelocity = EncodeVelocityToTexture(Velocity);
}
// Sample Position < consider to be the center??
// VS
const float4 SvPosition = float4(SamplePixelCoord, SampleDepth, 1);
// Coverage computation
// We don't use the coverage information
float Coverage = 1;
if (bUpdateSampleCoverage)
{
const bool bUseStableRasterization = UseStableRasterization();
FHairRenderInfo HairRenderInfo = GetHairRenderInfo(ResolvedView.HairRenderInfo, ResolvedView.HairRenderInfoBits, bUseStableRasterization);
const float LocalSceneDepth = ConvertFromDeviceZ(SvPosition.z); // Linear depth in world unit
const float PixelRadius = ConvertGivenDepthRadiusForProjectionType(HairRenderInfo.RadiusAtDepth1Primary, LocalSceneDepth); // Not correct but the coverage is not used (we count instead the number of sub-sample covered)
Coverage = saturate(WorldStrandRadius / max(WorldStrandRadius, PixelRadius) * MaterialPass_HairCoverageScale);
SampleCoverage8bit = min(uint(Coverage * 0xFF), 0xFFu);
}
// expressed the coverage relatively to the current pixel coverage?
// PS
FHairSample OutSample = (FHairSample)0;
OutSample.Depth = SampleDepth;
OutSample.Coverage8bit = SampleCoverage8bit;
OutSample.ControlPointId = SampleControlPointId;
OutSample.MacroGroupId = MaterialPass_MacroGroupId;
OutSample.LightChannelMask = MaterialPass_LightChannelMask;
OutSample.Flags = MaterialPass_Flags;
// Material computation
{
const bool bIsFrontFace = true;
FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition);
FPixelMaterialInputs PixelMaterialInputs;
float4 LocalScreenPosition = SvPositionToResolvedScreenPosition(SvPosition);
float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(SvPosition);
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, LocalScreenPosition, bIsFrontFace, TranslatedWorldPosition, TranslatedWorldPosition);
#if SUBTRATE_GBUFFER_FORMAT==1 && MATERIAL_IS_SUBSTRATE && MATERIAL_SHADINGMODEL_HAIR
FSubstratePixelHeader SubstratePixelHeader = MaterialParameters.GetFrontSubstrateHeader();
SubstratePixelHeader.SetCastContactShadow(GetPrimitiveData(MaterialParameters).Flags & 0x100);
SubstratePixelHeader.SetDynamicIndirectShadowCasterRepresentation(GetPrimitiveData(MaterialParameters).Flags & 0x80);
float3 Tangent = float3(0,1,0);
//if (SubstratePixelHeader.SubstrateTree.BSDFCount > 0)
{
// Using the tree data, instread of the inline BSDF does not seem to work. It is unclear why. It is not very
// important as we expect a single hair BSDF.
// Update tree (coverage/transmittance/luminace weights)
const float3 V = MaterialParameters.CameraVector;
const FSubstrateIntegrationSettings Settings = InitSubstrateIntegrationSettings(false /*bForceFullyRough*/, false /*bRoughDiffuse*/, -1 /*PeelLayersAboveDepth*/, false/*bRoughnessTracking*/);
SubstratePixelHeader.SubstrateUpdateTree(V, Settings);
FSubstrateBSDF HairBSDF = SubstratePixelHeader.SubstrateTree.BSDFs[0];
OutSample.BaseColor = HAIR_BASECOLOR(HairBSDF);
OutSample.Roughness = HAIR_ROUGHNESS(HairBSDF);
OutSample.Specular = HAIR_SPECULAR(HairBSDF);
OutSample.Backlit = HAIR_BACKLIT(HairBSDF);
OutSample.Emissive = BSDF_GETEMISSIVE(HairBSDF);
Tangent = normalize(MaterialParameters.SharedLocalBases.Normals[BSDF_GETSHAREDLOCALBASISID(HairBSDF)]);
}
#if HAIRSTRANDS_HAS_NORMAL_CONNECTED
#if MATERIAL_TANGENTSPACENORMAL
// Convert the tangent space input tangent
// * from a frame with Z/(0,0,1) being up and Y/(0,1,0) being the main tangent direction (called green)
// * to a frame with Y/(0,1,0) being up and Z/(0,0,1) being the main tangent direction (called blue)
// Tangent is in world space. First transform it into local space prior rotation)
float3 LocalTangent = mul(Tangent, transpose(MaterialParameters.TangentToWorld));
const float3 TangentSpaceGreen = LocalTangent;
const float3 TangentSpaceBlue = float3(-TangentSpaceGreen.x, TangentSpaceGreen.z, TangentSpaceGreen.y);
OutSample.Tangent = mul(TangentSpaceBlue, MaterialParameters.TangentToWorld);
OutSample.Tangent = normalize(OutSample.Tangent);
#else // Tangent in World space (likely provided by the HairAttributes node)
OutSample.Tangent = Tangent;
#endif
#else
OutSample.Tangent = normalize(Interpolants.TangentToWorld2.xyz);
#endif
#else
OutSample.BaseColor = GetMaterialBaseColor(PixelMaterialInputs);
OutSample.Roughness = GetMaterialRoughness(PixelMaterialInputs);
OutSample.Specular = GetMaterialSpecular(PixelMaterialInputs);
OutSample.Backlit = saturate(GetMaterialCustomData0(PixelMaterialInputs));
OutSample.Emissive = GetMaterialEmissive(PixelMaterialInputs);
float3 Tangent = normalize(GetMaterialNormalRaw(PixelMaterialInputs));
#if HAIRSTRANDS_HAS_NORMAL_CONNECTED
#if MATERIAL_TANGENTSPACENORMAL
// Convert the tangent space input tangent
// * from a frame with Z/(0,0,1) being up and Y/(0,1,0) being the main tangent direction (called green)
// * to a frame with Y/(0,1,0) being up and Z/(0,0,1) being the main tangent direction (called blue)
const float3 TangentSpaceGreen = Tangent;
const float3 TangentSpaceBlue = float3(-TangentSpaceGreen.x, TangentSpaceGreen.z, TangentSpaceGreen.y);
OutSample.Tangent = mul(TangentSpaceBlue, MaterialParameters.TangentToWorld);
OutSample.Tangent = normalize(OutSample.Tangent);
#else // Tangent in World space (likely provided by the HairAttributes node)
OutSample.Tangent = Tangent;
#endif
#else
OutSample.Tangent = normalize(Interpolants.TangentToWorld2.xyz);
#endif
#endif
// this feature is only needed for development/editor - we can compile it out for a shipping build (see r.CompileShadersForDevelopment cvar help)
#if USE_DEVELOPMENT_SHADERS
{
OutSample.BaseColor = OutSample.BaseColor * View.DiffuseOverrideParameter.w + View.DiffuseOverrideParameter.xyz;
}
#endif
}
FEvaluateOutput Out;
Out.NodeData = OutSample;
Out.NodeVelocity = EncodedVelocity;
return Out;
}
#endif