294 lines
12 KiB
HLSL
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
|