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

939 lines
28 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "../MonteCarlo.ush"
#include "HairStrandsVisibilityCommon.ush"
#include "HairCardsTextureCommon.ush"
#define DEBUG_ENABLE 0
#if DEBUG_ENABLE
#include "../ShaderPrint.ush"
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers
#if HAIR_CARDS_MAX_TEXTURE_COUNT != 6
#error Max cards texture count is != 6. This codes needs to be updated.
#endif
void AssignValue1(inout float4 OutTexture, uint ChannelIndex, float Value)
{
switch (ChannelIndex)
{
case 0: OutTexture.x = Value; break;
case 1: OutTexture.y = Value; break;
case 2: OutTexture.z = Value; break;
case 3: OutTexture.w = Value; break;
default: break;
}
}
void AssignValue2(inout float4 OutTexture, uint ChannelIndex, float2 Value)
{
switch (ChannelIndex)
{
case 0: OutTexture.xy = Value; break;
case 1: OutTexture.zw = Value; break;
default: break;
}
}
#define WriteValue1(InLayout, InAttribute, In)\
{ \
if (IsHairCardTextureIndexValid(InLayout, InAttribute))\
{\
const uint TexIndex = GetHairCardTextureIndex(InLayout, InAttribute);\
const uint ChannelIndex = GetHairCardChannelIndex(InLayout, InAttribute);\
switch (TexIndex)\
{ \
case 0: AssignValue1(OutTexture0, ChannelIndex, In); break;\
case 1: AssignValue1(OutTexture1, ChannelIndex, In); break;\
case 2: AssignValue1(OutTexture2, ChannelIndex, In); break;\
case 3: AssignValue1(OutTexture3, ChannelIndex, In); break;\
case 4: AssignValue1(OutTexture4, ChannelIndex, In); break;\
case 5: AssignValue1(OutTexture5, ChannelIndex, In); break;\
}\
}\
}
#define WriteValue2(InLayout, InAttribute, In)\
{\
if (IsHairCardTextureIndexValid(InLayout, InAttribute))\
{\
const uint TexIndex = GetHairCardTextureIndex(InLayout, InAttribute);\
const uint ChannelIndex = GetHairCardChannelIndex(InLayout, InAttribute);\
switch (TexIndex)\
{\
case 0: AssignValue2(OutTexture0, ChannelIndex, In); break;\
case 1: AssignValue2(OutTexture1, ChannelIndex, In); break;\
case 2: AssignValue2(OutTexture2, ChannelIndex, In); break;\
case 3: AssignValue2(OutTexture3, ChannelIndex, In); break;\
case 4: AssignValue2(OutTexture4, ChannelIndex, In); break;\
case 5: AssignValue2(OutTexture5, ChannelIndex, In); break;\
}\
}\
}
#define WriteValue3(InLayout, InAttribute, In)\
{\
if (IsHairCardTextureIndexValid(InLayout, InAttribute))\
{\
const uint TexIndex = GetHairCardTextureIndex(InLayout, InAttribute);\
switch (TexIndex)\
{\
case 0: OutTexture0.xyz = In; break;\
case 1: OutTexture1.xyz = In; break;\
case 2: OutTexture2.xyz = In; break;\
case 3: OutTexture3.xyz = In; break;\
case 4: OutTexture4.xyz = In; break;\
case 5: OutTexture5.xyz = In; break;\
}\
}\
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if SHADER_VERTEX
int2 OutputResolution;
uint VertexCount;
uint PrimitiveCount;
uint UVsChannelIndex;
uint UVsChannelCount;
Buffer<float> VertexBuffer;
Buffer<float4> NormalsBuffer;
Buffer<float2> UVsBuffer;
void MainVS(
uint VertexId : SV_VertexID,
out float4 UVPosition : SV_POSITION,
out float2 OutUV : MESH_UV,
out float3 OutPosition : MESH_POSITION,
out float3 OutTangent : MESH_TANGENT,
out float3 OutBitangent : MESH_BITANGENT,
out float3 OutNormal : MESH_NORMAL)
{
const float3 Position = float3(
VertexBuffer.Load(VertexId*3 + 0),
VertexBuffer.Load(VertexId*3 + 1),
VertexBuffer.Load(VertexId*3 + 2));
float2 UVs = UVsBuffer.Load(VertexId * UVsChannelCount + UVsChannelIndex);
UVs.y = 1 - UVs.y;
UVPosition = float4(UVs *2-1, 0.5f, 1);
OutUV = UVs;
OutPosition = Position;
const float4 VY = NormalsBuffer.Load(VertexId * 2);
const float4 VZ = NormalsBuffer.Load(VertexId * 2 + 1);
OutTangent = cross(VY.xyz, VZ.xyz) * VZ.w;
OutBitangent= VY.xyz;
OutNormal = VZ.xyz;
}
#endif // VERTEXSHADER
#if SHADER_PIXEL
void MainPS(
in float4 SvPosition : SV_Position,
in float2 InUV : MESH_UV,
in float3 InPosition : MESH_POSITION,
in float3 InTangent : MESH_TANGENT,
in float3 InBitangent : MESH_BITANGENT,
in float3 InNormal : MESH_NORMAL,
out float4 OutPosition : SV_Target0,
out float4 OutTangent : SV_Target1,
out float4 OutBitangent : SV_Target2,
out float4 OutNormal : SV_Target3,
out float2 OutUV : SV_Target4,
out uint OutTriangleMask : SV_Target5)
{
const float3 T = normalize(InTangent);
const float3 B = normalize(InBitangent);
const float3 N = normalize(InNormal);
OutPosition = float4(InPosition,1);
OutTangent = float4(InTangent,1);
OutBitangent = float4(InBitangent,1);
OutNormal = float4(InNormal,1);
OutUV = InUV;
OutTriangleMask = 1;
}
#endif //SHADER_PIXEL
#if SHADER_COMPUTE
#include "HairStrandsVertexFactoryCommon.ush"
#include "HairStrandsAttributeCommon.ush"
#include "HairCardsAttributeCommon.ush"
HAIR_STRANDS_INSTANCE_PARAMETERS(InVF)
#include "HairStrandsAttributeCommon.ush"
#define HAIR_STRANDS_ATTRIBUTE_ACCESSORS(Name) InVF_##Name
#include "/Engine/Private/HairStrands/HairStrandsAttributeTemplate.ush"
float3 ComputeTangent(
const FHairControlPoint CP0,
const FHairControlPoint CP1,
const FHairControlPoint CP2)
{
const float3 T0 = normalize(CP1.Position - CP0.Position);
const float3 T1 = normalize(CP2.Position - CP1.Position);
return normalize(T0 + T1);
}
float3 ComputeTangent(
const FHairControlPoint CP0,
const FHairControlPoint CP1)
{
return normalize(CP1.Position - CP0.Position);
}
int2 TileSize;
int2 TileOffsetInPixels;
uint LayoutIndex;
int2 OutputResolution;
uint VertexCount;
uint PrimitiveCount;
uint bHasMaterialData;
Buffer<float> VertexBuffer;
Buffer<float4> NormalsBuffer;
Buffer<float2> UVsBuffer;
float MaxDistance;
int TracingDirection;
float3 Voxel_MinBound;
float3 Voxel_MaxBound;
int3 Voxel_Resolution;
float Voxel_Size;
uint Voxel_MaxSegmentPerVoxel;
uint Voxel_OffsetAndCount_MaxCount;
uint Voxel_Data_MaxCount;
StructuredBuffer<uint2> Voxel_OffsetAndCount;
StructuredBuffer<uint2> Voxel_Data;
RWTexture2DArray<float4> OutTexture;
Texture2D<float4> PositionTexture;
Texture2D<float4> TangentTexture;
Texture2D<float4> BitangentTexture;
Texture2D<float4> NormalTexture;
Texture2D<float2> UVTexture;
uint3 PositionToCoord(float3 P, float3 InMinAABB, float3 InMaxAABB, uint3 InResolution)
{
return clamp(
saturate((P - InMinAABB) / (InMaxAABB - InMinAABB)) * InResolution,
uint3(0, 0, 0),
InResolution - 1);
}
uint CoordToIndex(uint3 InCoord, uint3 InResolution, uint LinearOffset)
{
return
InCoord.x +
InCoord.y * InResolution.x +
InCoord.z * InResolution.x * InResolution.y +
LinearOffset;
}
bool ClipRaySegment(float3 AABBMin, float3 AABBMax, float4 P0, float4 P1, out float2 T, out bool3 bClipped)
{
bClipped = false;
T = float2(0.0f, 1.0f);
const bool bP0Outside = any(P0.xyz < AABBMin) || any(P0.xyz > AABBMax);
const bool bP1Outside = any(P1.xyz < AABBMin) || any(P1.xyz > AABBMax);
if (!bP0Outside && !bP1Outside)
{
return true;
}
const float3 Origin = P0.xyz;
const float3 Dir = P1.xyz - P0.xyz;
const float3 RcpDir = 1.0f / Dir;
const float3 T0 = (AABBMin - Origin) * RcpDir;
const float3 T1 = (AABBMax - Origin) * RcpDir;
T.x = max3(min(T0.x, T1.x), min(T0.y, T1.y), min(T0.z, T1.z));
T.y = min3(max(T0.x, T1.x), max(T0.y, T1.y), max(T0.z, T1.z));
// Ray intersects the AABB but the segment is completely outside or no intersection at all.
if (T.y < 0.0f || T.x > T.y)
{
bClipped = true;
return false;
}
if (bP0Outside && T.x > 0.0f && T.x < 1.0f)
{
bClipped.x = true;
}
if (bP1Outside && T.y > 0.0f && T.y < 1.0f)
{
bClipped.y = true;
}
return true;
}
bool ClipRaySegment(float3 AABBMin, float3 AABBMax, inout float4 P0, inout float4 P1, inout float Rad0, inout float Rad1, out bool3 bClipped)
{
float2 T;
bool bIsValid = ClipRaySegment(AABBMin, AABBMax, P0, P1, T, bClipped);
if (bIsValid)
{
const bool bP0Outside = any(P0.xyz < AABBMin) || any(P0.xyz > AABBMax);
const bool bP1Outside = any(P1.xyz < AABBMin) || any(P1.xyz > AABBMax);
float4 P0New = P0;
float4 P1New = P1;
float Rad0New = Rad0;
float Rad1New = Rad1;
if (bP0Outside && T.x > 0.0f && T.x < 1.0f)
{
P0New = lerp(P0, P1, T.x);
Rad0New = lerp(Rad0, Rad1, T.x);
bClipped.x = true;
}
if (bP1Outside && T.y > 0.0f && T.y < 1.0f)
{
P1New = lerp(P0, P1, T.y);
Rad1New = lerp(Rad0, Rad1, T.y);
bClipped.y = true;
}
P0 = P0New;
P1 = P1New;
Rad0 = Rad0New;
Rad1 = Rad1New;
}
return bIsValid;
}
bool Trace(
float3 InRayO,
float3 InRayD,
float InMaxDistance,
float InPixelRadius,
bool bDebugEnable,
inout float OutDepth,
inout float OutCoverage,
inout float3 OutTangent,
inout float2 OutRootUV,
inout float OutU,
inout float OutSeed,
inout float4 OutMaterial)
{
bool bIsValid = false;
const float3 WP0 = InRayO;
const float3 WP1 = InRayO + InRayD * InMaxDistance;
#if DEBUG_ENABLE
if (bDebugEnable)
{
const float4 Color0 = float4(0, 1, 0, 1);
const float4 Color1 = float4(1, 0, 0, 1);
AddLineWS(WP0, WP1, Color0, Color1);
}
#endif
const float2 HitT = LineBoxIntersect(WP0, WP1, Voxel_MinBound, Voxel_MaxBound);
if (HitT.x < HitT.y)
{
#if DEBUG_ENABLE
if (bDebugEnable)
{
//AddLine(true, O, E);
AddAABBWS(Voxel_MinBound, Voxel_MaxBound, float4(1, 1, 1, 1));
}
#endif
// Count the number of fibers which are within a cylinder defined by the voxel size,
// and the distance between the origin and the extent of the volume
// This assumes that the voxel volume is cubic (i.e. equal dimensions on all sides)
const float3 O = WP0; // lerp(WorldPosition, IntersectEndPoint, HitT.xxx);
const float3 E = WP1; // lerp(WorldPosition, IntersectEndPoint, HitT.yyy);
const float OELength = min(length(E - O), InMaxDistance);
const int3 StartCoord = PositionToCoord(WP0, Voxel_MinBound, Voxel_MaxBound, Voxel_Resolution);
const int3 EndCoord = PositionToCoord(WP1, Voxel_MinBound, Voxel_MaxBound, Voxel_Resolution);
// Init to 1 or -1 depending of the orientation of stepping
const float3 UNormD = WP1 - WP0;
const int3 Step = sign(UNormD);
// Step according to voxel size
const float3 D = normalize(UNormD) * Voxel_Size;
float t = HitT.x;
// this is slop coeff for each axis: how far we need to move in units of t for each axis
const float3 tDelta = Step * Voxel_Size / UNormD;
// Init to the starting voxel
int3 PageIndexCoord = -1;
float3 tMax = 0;
{
const float3 HitP = O + HitT.x * UNormD;
const float Epsilon = 0.000001f;
const float3 Coords = clamp(
saturate((HitP - Voxel_MinBound) / (Voxel_MaxBound - Voxel_MinBound)) * Voxel_Resolution,
0,
Voxel_Resolution - Epsilon);
const float3 FractCoords = max(Step, 0) - Step * frac(Coords);
tMax = FractCoords * tDelta;
PageIndexCoord = clamp(uint3(Coords), uint3(0, 0, 0), Voxel_Resolution - 1);
}
const uint LoopCount = 256u;
for (uint LoopIt = 0; LoopIt < LoopCount; ++LoopIt)
{
const bool bIsInside =
PageIndexCoord.x >= 0 &&
PageIndexCoord.y >= 0 &&
PageIndexCoord.z >= 0 &&
PageIndexCoord.x < int(Voxel_Resolution.x) &&
PageIndexCoord.y < int(Voxel_Resolution.y) &&
PageIndexCoord.z < int(Voxel_Resolution.z);
if (!bIsInside)
{
return bIsValid;
}
const float3 WPCurr = (PageIndexCoord + 0.5f) * Voxel_Size + Voxel_MinBound;
const bool bExit = dot((WPCurr - O), InRayD) > (InMaxDistance + Voxel_Size);
if (bExit)
{
return bIsValid;
}
const uint LinearPageIndexCoord = CoordToIndex(PageIndexCoord, Voxel_Resolution, 0);
#if DEBUG_ENABLE
if (bDebugEnable)
{
const float3 MinVoxelAABB = Voxel_MinBound + PageIndexCoord * Voxel_Size;
const float3 MaxVoxelAABB = Voxel_MinBound + (PageIndexCoord + 1) * Voxel_Size;
AddAABBWS(MinVoxelAABB, MaxVoxelAABB, float4(0, 1, 1, 1));
}
#endif
// Trace against voxel content
{
const int3 VolumeCoord = PageIndexCoord;
// Update page index only when needed
uint2 OffsetAndCount = 0;
{
const uint LinearIndexCoord = CoordToIndex(VolumeCoord, Voxel_Resolution, 0);
OffsetAndCount = Voxel_OffsetAndCount[clamp(LinearIndexCoord, 0, Voxel_OffsetAndCount_MaxCount-1)];
OffsetAndCount.y = min(OffsetAndCount.y, Voxel_MaxSegmentPerVoxel);
}
if (OffsetAndCount.y > 0)
{
for (uint PointIt = 0; PointIt < OffsetAndCount.y; ++PointIt)
{
const uint VoxelIndex = clamp(OffsetAndCount.x + PointIt, 0, Voxel_Data_MaxCount-1);
const uint2 P0_P1 = Voxel_Data[VoxelIndex].xx + uint2(0,1);
const uint P0 = P0_P1.x;
const uint P1 = P0_P1.y;
FHairControlPoint CP_Curr = ReadHairControlPoint(InVF_PositionBuffer, P0, InVF_PositionOffset, InVF_Radius, InVF_RootScale, InVF_TipScale);
FHairControlPoint CP_Next = ReadHairControlPoint(InVF_PositionBuffer, P1, InVF_PositionOffset, InVF_Radius, InVF_RootScale, InVF_TipScale);
// Clip segment to voxel boundary in order to avoid counting multiple time the same segment which would cause the coverage to be incorrect
// as the ray trace several voxels
{
const float3 MinVoxelAABB = Voxel_MinBound + PageIndexCoord * Voxel_Size;
const float3 MaxVoxelAABB = Voxel_MinBound + (PageIndexCoord + 1) * Voxel_Size;
float4 ClippedP0 = float4(CP_Curr.Position, 1);
float4 ClippedP1 = float4(CP_Next.Position, 1);
float ClippedRad0 = CP_Curr.WorldRadius;
float ClippedRad1 = CP_Next.WorldRadius;
bool3 bClipped = false;
ClipRaySegment(MinVoxelAABB, MaxVoxelAABB, ClippedP0, ClippedP1, ClippedRad0, ClippedRad1, bClipped);
if (any(bClipped))
{
CP_Curr.Position = ClippedP0.xyz;
CP_Next.Position = ClippedP1.xyz;
CP_Curr.WorldRadius = ClippedRad0;
CP_Next.WorldRadius = ClippedRad1;
}
}
const float3 Tangent = ComputeTangent(CP_Curr, CP_Next);
const float3 RayP0 = InRayO;
const float3 RayP1 = InRayO + InRayD * InMaxDistance;
float VertexU = 0;
const float WorldRadius = 2 * max(CP_Curr.WorldRadius, CP_Next.WorldRadius); // 0.05f;
const float Distance = Intersection(CP_Curr.Position, CP_Next.Position, RayP0, RayP1, WorldRadius, VertexU) * InMaxDistance;
const bool bIntersect = Distance > 0;
const bool bClosest = Distance < OutDepth;
const bool bIntersectAndClosest = bIntersect && bClosest;
if (bIntersect)
{
const float SegmentCoverage = saturate(WorldRadius / InPixelRadius);
//OutCoverage = saturate(OutCoverage + saturate(1.f-OutCoverage) * SegmentCoverage);
OutCoverage = saturate(OutCoverage + SegmentCoverage);
//OutCoverage = 1;
}
if (bIntersectAndClosest)
{
OutTangent = Tangent;
OutDepth = Distance;
OutSeed = GetHairStrandsSeed(P0);
OutRootUV = GetHairStrandsRootUV(P0);
OutU = GetHairStrandsUV(P0, VertexU).x;
OutMaterial = 0;
if (HasHairStrandsColor())
{
OutMaterial.xyz = GetHairStrandsColor(P0, VertexU);
}
if (HasHairStrandsRoughness())
{
OutMaterial.w = GetHairStrandsRoughness(P0, VertexU);
}
bIsValid = true;
#if DEBUG_ENABLE
if (bDebugEnable)
{
const float4 Color = float4(1, 0, 0, 1);
AddLineWS(CP_Curr.Position, CP_Next.Position, Color, Color);
AddCrossWS(CP_Curr.Position, 0.05f, Color);
AddCrossWS(CP_Next.Position, 0.05f, Color);
}
#endif
}
}
}
}
// t is used for defining the intersection point at the entry of a valid page
t = min(tMax.x, min(tMax.y, tMax.z));
// Find the next page indx to visit and update the tmax, accordingly
const float3 Mask = tMax.x < tMax.y ?
(tMax.x < tMax.z ? float3(1, 0, 0) : float3(0, 0, 1)) :
(tMax.y < tMax.z ? float3(0, 1, 0) : float3(0, 0, 1));
PageIndexCoord += Step * Mask;
tMax += tDelta * Mask;
}
}
return bIsValid;
}
[numthreads(8, 8, 1)]
void MainCS(
uint GroupIndex : SV_GroupIndex,
uint3 DispatchThreadId : SV_DispatchThreadID)
{
const int2 PixelCoord = DispatchThreadId.xy + TileOffsetInPixels;
if (any(PixelCoord > OutputResolution))
{
return;
}
const float2 InUV = UVTexture[PixelCoord];
const float3 InPosition = PositionTexture[PixelCoord].xyz;
const float3 InTangent = TangentTexture[PixelCoord].xyz;
const float3 InBitangent= BitangentTexture[PixelCoord].xyz;
const float3 InNormal = NormalTexture[PixelCoord].xyz;
const float3 T = normalize(InTangent);
const float3 B = normalize(InBitangent);
const float3 N = normalize(InNormal);
// Manual ddx/ddy
float3 PixelAxisX = 0;
float3 PixelAxisY = 0;
if (PixelCoord.x + 1 < OutputResolution.x)
{
PixelAxisX = PositionTexture[float2(PixelCoord.x+1, PixelCoord.y)].xyz - InPosition;
}
if (PixelCoord.y + 1 < OutputResolution.y)
{
PixelAxisY = PositionTexture[float2(PixelCoord.x, PixelCoord.y+1)].xyz - InPosition;
}
const float PixelWorldRadius = 0.25f * (length(PixelAxisY) + length(PixelAxisX)); // Average + pixel radius (vs. diameter)
float4 OutTexture0 = 0;
float4 OutTexture1 = 0;
float4 OutTexture2 = 0;
float4 OutTexture3 = 0;
float4 OutTexture4 = 0;
float4 OutTexture5 = 0;
float OutDepth = max(0, MaxDistance);
float OutSeed = 0;
float OutCoordU = 0;
float OutGroupIndex = 0;
float OutRoughness = 0;
float2 OutRootUV = 0;
float3 OutColor = 0;
float3 OutTangent = 0;
float OutCoverage = 0;
float3 Tangent_ObjectSpace = 0;
const bool bDebugEnable =
#if DEBUG_ENABLE
all(PixelCoord == GetCursorPos());
#else
false;
#endif
uint bValidCount = 0;
const uint SampleCount = 16;
for (uint SampleIt = 0; SampleIt < SampleCount; ++SampleIt)
{
const float2 Jitter = 0.5f * (Hammersley(SampleIt, SampleCount, 0) * 2 - 1);
float3 RayO = InPosition + Jitter.x * PixelAxisX + Jitter.y * PixelAxisY;
float3 RayD = N;
// If the tracing distance is:
// * Positive: Tracing is done outside towards the surface
// * Negative: Tracing is done inside, starting from the surface
// * Zero : Tracing is from outside towards the surface, but continus within the surface
if (TracingDirection > 0)
{
RayO = RayO + RayD * MaxDistance;
RayD =-RayD;
}
else if (TracingDirection < 0)
{
RayD = -RayD;
}
else // (TracingDirection == 0)
{
RayO = RayO + RayD * MaxDistance * 0.5f;
RayD = -RayD;
}
float SampleDepth = MaxDistance;
float SampleCoverage = 0;
float3 SampleTangent = 0;
float2 SampleRootUV = 0;
float SampleU = 0;
float SampleSeed = 0;
float4 SampleMaterial = 0;
if (Trace(RayO, RayD, MaxDistance, PixelWorldRadius, bDebugEnable, SampleDepth, SampleCoverage, SampleTangent, SampleRootUV, SampleU, SampleSeed, SampleMaterial))
{
if (SampleDepth < OutDepth)
{
OutRootUV = SampleRootUV;
OutCoordU = SampleU;
OutSeed = SampleSeed;
OutColor = SampleMaterial.xyz;
OutRoughness = SampleMaterial.w;
}
OutDepth = min(OutDepth, SampleDepth);
OutCoverage += SampleCoverage;
Tangent_ObjectSpace += SampleTangent;
++bValidCount;
}
}
if (bValidCount > 0 && OutCoverage > 0)
{
Tangent_ObjectSpace = normalize(Tangent_ObjectSpace);
const float3 Tangent_FrameSpace = float3(
dot(Tangent_ObjectSpace, T),
dot(Tangent_ObjectSpace, B),
dot(Tangent_ObjectSpace, N));
OutCoverage /= SampleCount;
OutTangent = (Tangent_FrameSpace + 1) * 0.5f;
OutGroupIndex = InVF_GroupIndex / 255.f;
}
else
{
OutDepth = MaxDistance;
OutSeed = 0;
OutCoordU = 0;
OutGroupIndex = 0;
OutRoughness = 0;
OutRootUV = 0;
OutColor = 0;
OutTangent = 0;
OutCoverage = 0;
}
OutDepth = saturate(OutDepth / MaxDistance);
if (bValidCount > 0)
{
WriteValue3(LayoutIndex, EHairCardsVFAttribute_Color, OutColor);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Depth, OutDepth);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Seed, OutSeed);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_CoordU, OutCoordU);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_GroupIndex, OutGroupIndex);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Roughness, OutRoughness);
WriteValue2(LayoutIndex, EHairCardsVFAttribute_RootUV, OutRootUV);
WriteValue3(LayoutIndex, EHairCardsVFAttribute_Tangent, OutTangent);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Coverage, OutCoverage);
OutTexture[uint3(PixelCoord, 0)] = OutTexture0;
OutTexture[uint3(PixelCoord, 1)] = OutTexture1;
OutTexture[uint3(PixelCoord, 2)] = OutTexture2;
OutTexture[uint3(PixelCoord, 3)] = OutTexture3;
OutTexture[uint3(PixelCoord, 4)] = OutTexture4;
OutTexture[uint3(PixelCoord, 5)] = OutTexture5;
}
}
#endif // SHADER_COMPUTE
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if SHADER_TEXTURE_DILATION
#include "HairCardsAttributeCommon.ush"
uint LayoutIndex;
uint bClearPass;
uint TextureCount;
int2 Resolution;
int ClearDistance;
Texture2D<float> DepthTestTexture;
Texture2D<uint> OriginalTriangleMaskTexture;
Texture2D<uint> SourceTriangleMaskTexture;
RWTexture2D<uint> TargetTriangleMaskTexture;
Texture2DArray<float4> SourceTexture;
RWTexture2DArray<float4> TargetTexture;
// Dilate the source texture (Depth/Coverage/Tanget/Attribute), using a distance to triangle edge metric.
// Original valid pixel are marked into the triangleMasktexture during the texture generation. Then
// successive dilation pass are run. At each pass the triangleMaskTexure stored the distance to triangle
// edge in order to propagate correctly the pixel edge information.
[numthreads(8, 8, 1)]
void MainCS(int2 DispatchThreadId : SV_DispatchThreadID)
{
const uint2 PixelCoord = DispatchThreadId.xy;
if (any(PixelCoord >= uint2(Resolution)))
{
return;
}
bool bIsValid = false;
// Pixel within the source, which are not written due to lack of data, will be dialated as well
{
const uint CoverageTextureIndex = GetHairCardTextureIndex(LayoutIndex, EHairCardsVFAttribute_Coverage);
const uint CoverageChannelIndex = GetHairCardChannelIndex(LayoutIndex, EHairCardsVFAttribute_Coverage);
const float4 Value = SourceTexture[uint3(PixelCoord, CoverageTextureIndex)];
float CoverageValue = 1.f;
switch (CoverageChannelIndex)
{
case 0 : CoverageValue = Value.x; break;
case 1 : CoverageValue = Value.y; break;
case 2 : CoverageValue = Value.z; break;
case 3 : CoverageValue = Value.z; break;
}
// * When doing the clear pass, only mark as valid, pixels whose coverage is > 0
// * For other passes, use the source mask (i.e, the mask updated at every pass) for identifying valid/already processed pixels
if (bClearPass)
{
bIsValid = CoverageValue > 0.f;
}
else
{
bIsValid = SourceTriangleMaskTexture.Load(uint3(PixelCoord,0)) > 0;
}
}
if (bClearPass || bIsValid)
{
// Fast path: the pixel is valid, we copy value
for (uint TextureIt = 0; TextureIt < TextureCount; ++TextureIt)
{
TargetTexture[uint3(PixelCoord, TextureIt)] = SourceTexture[uint3(PixelCoord, TextureIt)];
}
if (bClearPass)
{
TargetTriangleMaskTexture[PixelCoord] = (bIsValid && OriginalTriangleMaskTexture.Load(uint3(PixelCoord,0)) > 0.5f) ? 1u : 0u;
}
else
{
TargetTriangleMaskTexture[PixelCoord] = SourceTriangleMaskTexture.Load(uint3(PixelCoord,0));
}
}
else
{
const uint CoverageTextureIndex = GetHairCardTextureIndex(LayoutIndex, EHairCardsVFAttribute_Coverage);
const uint CoverageChannelIndex = GetHairCardChannelIndex(LayoutIndex, EHairCardsVFAttribute_Coverage);
// Find the closest neighbor from triangle, amoung valid neighbord
float4 Values[HAIR_CARDS_MAX_TEXTURE_COUNT];
for (uint TextureIt = 0; TextureIt < HAIR_CARDS_MAX_TEXTURE_COUNT; ++TextureIt)
{
Values[TextureIt] = 0;
}
bool bIsDilatedPixelValid = false;
float MinDistance = 9999999;
for (int y = -1; y <= 1; ++y)
for (int x = -1; x <= 1; ++x)
{
if (x == 0 && y == 0)
continue;
const uint2 NPixelCoord = clamp(int2(PixelCoord) + int2(x, y), int2(0,0), int2(Resolution-1));
const bool bNIsValid = SourceTriangleMaskTexture.Load(uint3(NPixelCoord,0)) > 0;
if (bNIsValid)
{
for (uint TextureIt = 0; TextureIt < TextureCount; ++TextureIt)
{
Values[TextureIt] = SourceTexture[uint3(NPixelCoord, TextureIt)];
}
bIsDilatedPixelValid= true;
}
}
// For coverage dialation, we only allow dilation outside of the triangles
const bool bIsInsideTriangle = OriginalTriangleMaskTexture.Load(uint3(PixelCoord,0)) > 0;
// If a valid neighbord has been bound, update pixel value
if (bIsDilatedPixelValid)
{
for (uint TextureIt = 0; TextureIt < TextureCount; ++TextureIt)
{
// Write all attributes but coverage, as coverage should remain untouched.
float4 WriteOut = Values[TextureIt];
if (bIsInsideTriangle && TextureIt == CoverageTextureIndex)
{
switch (CoverageChannelIndex)
{
case 0 : WriteOut.x = 0.f; break;
case 1 : WriteOut.y = 0.f; break;
case 2 : WriteOut.z = 0.f; break;
case 3 : WriteOut.w = 0.f; break;
}
}
TargetTexture[uint3(PixelCoord, TextureIt)] = WriteOut;
}
TargetTriangleMaskTexture[PixelCoord] = 1.f;
}
}
}
#endif // SHADER_TEXTURE_DILATION
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if SHADER_READ_POSITIONS
#include "HairStrandsVertexFactoryCommon.ush"
uint MaxVertexCount;
ByteAddressBuffer InPositions;
Buffer<float4> InPositionOffset;
RWBuffer<float4> OutPositions;
[numthreads(128, 1, 1)]
void MainCS(int2 DispatchThreadId : SV_DispatchThreadID)
{
uint VertexIndex = DispatchThreadId.x;
if (VertexIndex < MaxVertexCount)
{
const float3 PositionOffset = InPositionOffset[0].xyz;
const FHairControlPoint CP = ReadHairControlPoint(InPositions, VertexIndex, PositionOffset, 1.0f /*InVF_Radius*/, 1.0f /*InVF_RootScale*/, 1.0f /*InVF_TipScale*/);
OutPositions[VertexIndex] = float4(CP.Position, CP.Type);
}
}
#endif // SHADER_READ_POSITIONS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if SHADER_TEXTURE_CLEAR
#include "HairCardsAttributeCommon.ush"
int2 Resolution;
uint LayoutIndex;
RWTexture2DArray<float4> OutTexture;
[numthreads(8, 8, 1)]
void MainCS(int2 DispatchThreadId : SV_DispatchThreadID)
{
int2 PixelCoord = DispatchThreadId.xy;
if (all(PixelCoord < Resolution))
{
float4 OutTexture0 = 0;
float4 OutTexture1 = 0;
float4 OutTexture2 = 0;
float4 OutTexture3 = 0;
float4 OutTexture4 = 0;
float4 OutTexture5 = 0;
WriteValue3(LayoutIndex, EHairCardsVFAttribute_Color, 0.f);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Depth, 1.f);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Seed, 0.f);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_CoordU, 0.f);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_GroupIndex, 0.f);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Roughness, 0.f);
WriteValue2(LayoutIndex, EHairCardsVFAttribute_RootUV, 0.f);
WriteValue3(LayoutIndex, EHairCardsVFAttribute_Tangent, 0.f);
WriteValue1(LayoutIndex, EHairCardsVFAttribute_Coverage, 0.f);
OutTexture[uint3(PixelCoord, 0)] = OutTexture0;
OutTexture[uint3(PixelCoord, 1)] = OutTexture1;
OutTexture[uint3(PixelCoord, 2)] = OutTexture2;
OutTexture[uint3(PixelCoord, 3)] = OutTexture3;
OutTexture[uint3(PixelCoord, 4)] = OutTexture4;
OutTexture[uint3(PixelCoord, 5)] = OutTexture5;
}
}
#endif // SHADER_TEXTURE_CLEAR
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if SHADER_TEXTURE_COPY
uint TextureIndex;
int2 Resolution;
Texture2DArray<float4> InTexture;
RWTexture2D<float4> OutTexture;
[numthreads(8, 8, 1)]
void MainCS(int2 DispatchThreadId : SV_DispatchThreadID)
{
int2 PixelCoord = DispatchThreadId.xy;
if (all(PixelCoord < Resolution))
{
OutTexture[PixelCoord] = InTexture[uint3(PixelCoord, TextureIndex)];
}
}
#endif // SHADER_TEXTURE_COPY