533 lines
14 KiB
HLSL
533 lines
14 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "/Engine/Public/Platform.ush"
|
|
#include "/Engine/Private/PackUnpack.ush"
|
|
#include "/Engine/Shared/HairStrandsDefinitions.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Hair control points
|
|
|
|
FPackedHairPosition ReadPackedHairPosition(ByteAddressBuffer InBuffer, uint InPointIndex)
|
|
{
|
|
return InBuffer.Load2(InPointIndex * FPackedHairPositionStrideInBytes);
|
|
}
|
|
|
|
void WritePackedHairPosition(RWByteAddressBuffer InBuffer, uint InPointIndex, FPackedHairPosition In)
|
|
{
|
|
InBuffer.Store2(InPointIndex * FPackedHairPositionStrideInBytes, In);
|
|
}
|
|
|
|
void CopyPackedAttribute(inout FPackedHairPosition Out, in FPackedHairPosition In)
|
|
{
|
|
// Copy the last 16bits which contains the CoordU|Radius|Type
|
|
Out.y = (Out.y & 0x0000FFFF) | (In.y & 0xFFFF0000);
|
|
}
|
|
|
|
void CopyPackedHairControlPoint(RWByteAddressBuffer OutBuffer, ByteAddressBuffer InBuffer, uint Index)
|
|
{
|
|
const uint AddressInBytes = Index * FPackedHairPositionStrideInBytes;
|
|
OutBuffer.Store2(AddressInBytes, InBuffer.Load2(AddressInBytes));
|
|
}
|
|
|
|
struct FHairControlPoint
|
|
{
|
|
float3 Position;
|
|
float WorldRadius;
|
|
float UCoord;
|
|
uint Type;
|
|
};
|
|
|
|
FPackedHairPosition PackHairControlPoint(
|
|
FHairControlPoint CP,
|
|
float3 InVF_PositionOffset,
|
|
float InVF_Radius)
|
|
{
|
|
const uint UCoord8bits = PackR8(CP.UCoord);
|
|
const uint Radius6bits = PackR6(CP.WorldRadius / InVF_Radius);
|
|
const uint Type2bits = CP.Type & 0x3;
|
|
CP.Position -= InVF_PositionOffset;
|
|
|
|
FPackedHairPosition Out = 0;
|
|
Out.x = PackFloat2ToUInt(CP.Position.x, CP.Position.y);
|
|
Out.y = f32tof16(CP.Position.z) | (UCoord8bits << 16u) | (Radius6bits << 24u) | (Type2bits << 30u);
|
|
return Out;
|
|
}
|
|
|
|
FHairControlPoint UnpackHairControlPoint(
|
|
FPackedHairPosition In,
|
|
float3 InVF_PositionOffset=0,
|
|
float InVF_Radius=1,
|
|
float InVF_RootScale=1,
|
|
float InVF_TipScale=1)
|
|
{
|
|
FHairControlPoint Out = (FHairControlPoint)0;
|
|
Out.Position = float3(UnpackFloat2FromUInt(In.x), f16tof32(BitFieldExtractU32(In.y, 16, 0))) + InVF_PositionOffset;
|
|
Out.UCoord = UnpackR8(BitFieldExtractU32(In.y, 8, 16));
|
|
Out.WorldRadius = UnpackR6(BitFieldExtractU32(In.y, 6, 24));
|
|
Out.Type = BitFieldExtractU32(In.y, 2, 30);
|
|
|
|
Out.WorldRadius *= InVF_Radius * lerp(InVF_RootScale, InVF_TipScale, Out.UCoord);
|
|
return Out;
|
|
}
|
|
|
|
FHairControlPoint ReadHairControlPoint(
|
|
ByteAddressBuffer InBuffer,
|
|
uint InIndex,
|
|
float3 InVF_PositionOffset=0,
|
|
float InVF_Radius=1,
|
|
float InVF_RootScale=1,
|
|
float InVF_TipScale=1)
|
|
{
|
|
return UnpackHairControlPoint(
|
|
InBuffer.Load2(InIndex * FPackedHairPositionStrideInBytes),
|
|
InVF_PositionOffset,
|
|
InVF_Radius,
|
|
InVF_RootScale,
|
|
InVF_TipScale);
|
|
}
|
|
|
|
FHairControlPoint ReadHairControlPoint(
|
|
RWByteAddressBuffer InBuffer,
|
|
uint InIndex,
|
|
float3 InVF_PositionOffset=0,
|
|
float InVF_Radius=1,
|
|
float InVF_RootScale=1,
|
|
float InVF_TipScale=1)
|
|
{
|
|
return UnpackHairControlPoint(
|
|
InBuffer.Load2(InIndex * FPackedHairPositionStrideInBytes),
|
|
InVF_PositionOffset,
|
|
InVF_Radius,
|
|
InVF_RootScale,
|
|
InVF_TipScale);
|
|
}
|
|
|
|
void WriteHairControlPointPosition(
|
|
RWByteAddressBuffer OutBuffer,
|
|
uint InIndex,
|
|
FHairControlPoint InCP,
|
|
float3 InVF_PositionOffset=0,
|
|
float InVF_Radius=1)
|
|
{
|
|
OutBuffer.Store2(InIndex * FPackedHairPositionStrideInBytes, PackHairControlPoint(InCP, InVF_PositionOffset, InVF_Radius));
|
|
}
|
|
|
|
float UnpackHairControlPointCoordU(FPackedHairPosition In)
|
|
{
|
|
return UnpackR8(BitFieldExtractU32(In.y, 8, 16));
|
|
}
|
|
|
|
float3 UnpackHairControlPointPosition(FPackedHairPosition In, float3 InVF_PositionOffset=0)
|
|
{
|
|
return float3(UnpackFloat2FromUInt(In.x), f16tof32(BitFieldExtractU32(In.y, 16, 0))) + InVF_PositionOffset;
|
|
}
|
|
|
|
float3 ReadHairControlPointPosition(ByteAddressBuffer InBuffer, uint InPointIndex, float3 InVF_PositionOffset=0)
|
|
{
|
|
return UnpackHairControlPointPosition(InBuffer.Load2(InPointIndex * FPackedHairPositionStrideInBytes), InVF_PositionOffset);
|
|
}
|
|
|
|
float3 ReadHairControlPointPosition(RWByteAddressBuffer InBuffer, uint InPointIndex, float3 InVF_PositionOffset=0)
|
|
{
|
|
return UnpackHairControlPointPosition(InBuffer.Load2(InPointIndex * FPackedHairPositionStrideInBytes), InVF_PositionOffset);
|
|
}
|
|
|
|
FPackedHairPosition PackHairControlPointPosition(FPackedHairPosition InPackedData, float3 InNewPosition, float3 InPositionOffset=0)
|
|
{
|
|
InNewPosition -= InPositionOffset;
|
|
|
|
FPackedHairPosition Out = 0;
|
|
Out.x = PackFloat2ToUInt(InNewPosition.x, InNewPosition.y);
|
|
Out.y = f32tof16(InNewPosition.z) | (InPackedData.y & 0xFFFF0000);
|
|
return Out;
|
|
}
|
|
|
|
void WritePackedHairControlPointPosition(RWByteAddressBuffer OutBuffer, uint InIndex, FPackedHairPosition InPackedData, float3 InNewPosition, float3 InPositionOffset=0)
|
|
{
|
|
OutBuffer.Store2(InIndex * FPackedHairPositionStrideInBytes, PackHairControlPointPosition(InPackedData, InNewPosition, InPositionOffset));
|
|
}
|
|
|
|
FPackedHairPosition PackHairControlPointRadius(FPackedHairPosition InPackedData, float InNewWorldRadius, float InVFRadius)
|
|
{
|
|
const uint Radius6bits = PackR6(InNewWorldRadius / InVFRadius);
|
|
|
|
FPackedHairPosition Out = InPackedData;
|
|
Out.y = (Out.y & 0xC0FFFFFF) | (Radius6bits << 24);
|
|
return Out;
|
|
}
|
|
|
|
bool IsValidHairStrandsSegment(const FHairControlPoint CP0, const FHairControlPoint CP1)
|
|
{
|
|
return !(CP0.Type == HAIR_CONTROLPOINT_END && CP1.Type == HAIR_CONTROLPOINT_START);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Hair attributes
|
|
|
|
uint PackHairRootUV(float2 In)
|
|
{
|
|
In.y = 1.0f - In.y;
|
|
|
|
return (min(uint(saturate(In.x) * 0x7FFu), 0x7FFu) )|
|
|
(min(uint(saturate(In.y) * 0x7FFu), 0x7FFu) << 11);
|
|
}
|
|
|
|
float2 UnpackHairRootUV(uint In)
|
|
{
|
|
float2 RootUV, RootIndex;
|
|
RootUV.x = ((In) & 0x7FF) / 2047.f; // Coord are encoded on 11 bits
|
|
RootUV.y = ((In >> 11) & 0x7FF) / 2047.f; // Coord are encoded on 11 bits
|
|
RootIndex.x = ((In >> 22) & 0x1F); // UDIM tile are encoded on 5bits
|
|
RootIndex.y = ((In >> 27) & 0x1F); // UDIM tile are encoded on 5bits
|
|
|
|
// Invert V to compensate image origin flip. Similar to GetHairStrandsRootUV in HairCardsAttributeCommon.ush
|
|
RootUV.y = 1.0f - RootUV.y;
|
|
|
|
return RootUV + RootIndex;
|
|
}
|
|
|
|
float UnpackHairLength(uint In)
|
|
{
|
|
return f16tof32(In & 0xFFFF);
|
|
}
|
|
|
|
uint PackHairSeed(float In)
|
|
{
|
|
return PackR8(In);
|
|
}
|
|
|
|
float UnpackHairSeed(uint In)
|
|
{
|
|
return UnpackR8(In);
|
|
}
|
|
|
|
uint PackHairClumpID(uint In)
|
|
{
|
|
return min(In, 0xFFFFu);
|
|
}
|
|
|
|
uint3 UnpackHairClumpID(uint In)
|
|
{
|
|
return (In & 0xFFFF).xxx;
|
|
}
|
|
|
|
uint3 UnpackHairClumpID(uint2 In)
|
|
{
|
|
return uint3(In.x & 0xFFFF, (In.x>>16) & 0xFFFF, In.y & 0xFFFF);
|
|
}
|
|
|
|
uint PackHairColor(float3 In)
|
|
{
|
|
return (min(uint(In.x * 0x7FFu), 0x7FFu) )|
|
|
(min(uint(In.y * 0x7FFu), 0x7FFu)<<11)|
|
|
(min(uint(In.z * 0x3FFu), 0x3FFu)<<22);
|
|
}
|
|
|
|
float3 UnpackHairColor(uint In)
|
|
{
|
|
float3 Out;
|
|
Out.x = (In & 0x7FFu) / 2047.f;
|
|
Out.y = ((In>>11) & 0x7FFu) / 2047.f;
|
|
Out.z = ((In>>22) & 0x3FFu) / 1023.f;
|
|
return Out;
|
|
}
|
|
|
|
uint PackHairRoughness(float In)
|
|
{
|
|
return PackR8(In);
|
|
}
|
|
|
|
float UnpackHairRoughness(uint In)
|
|
{
|
|
return UnpackR8(In);
|
|
}
|
|
|
|
uint PackHairAO(float In)
|
|
{
|
|
return PackR8(In);
|
|
}
|
|
|
|
float UnpackHairAO(uint In)
|
|
{
|
|
return UnpackR8(In);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Guide
|
|
|
|
struct FGuideCurveData
|
|
{
|
|
uint2 CurveIndices; // Index of the sim curve
|
|
float2 CurveWeights; // Weight of the sim curve
|
|
uint2 RootPointIndices; // Index of the first/root point of the sim curve (stored to avoid indirect fetch on CurveBuffer)
|
|
};
|
|
|
|
FGuideCurveData UnpackGuideCurveData(ByteAddressBuffer InBuffer, uint InCurveIndex, bool bUseSingleGuide)
|
|
{
|
|
// This code assumes:
|
|
// * HAIR_INTERPOLATION_CURVE_STRIDE == 8
|
|
// * HAIR_INTERPOLATION_MAX_GUIDE_COUNT == 2
|
|
|
|
FGuideCurveData Out = (FGuideCurveData)0;
|
|
if (bUseSingleGuide)
|
|
{
|
|
const uint AddressInBytes = InCurveIndex * HAIR_INTERPOLATION_CURVE_STRIDE;
|
|
const uint2 Data = InBuffer.Load2(AddressInBytes);
|
|
|
|
Out.CurveIndices = Data.x & 0xFFFFFF;
|
|
Out.CurveWeights = UnpackR8(Data.x>>24);
|
|
Out.RootPointIndices = Data.y & 0xFFFFFF;
|
|
}
|
|
else
|
|
{
|
|
const uint AddressInBytes = InCurveIndex * HAIR_INTERPOLATION_CURVE_STRIDE * HAIR_INTERPOLATION_MAX_GUIDE_COUNT;
|
|
const uint4 Data = InBuffer.Load4(AddressInBytes);
|
|
|
|
Out.CurveIndices.x = Data.x & 0xFFFFFF;
|
|
Out.CurveWeights.x = UnpackR8(Data.x>>24);
|
|
Out.RootPointIndices.x = Data.y & 0xFFFFFF;
|
|
|
|
Out.CurveIndices.y = Data.z & 0xFFFFFF;
|
|
Out.CurveWeights.y = UnpackR8(Data.z>>24);
|
|
Out.RootPointIndices.y = Data.w & 0xFFFFFF;
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
struct FGuidePointData
|
|
{
|
|
uint2 PointIndices;
|
|
float2 PointLerps;
|
|
};
|
|
|
|
FGuidePointData UnpackGuidePointData(ByteAddressBuffer InBuffer, uint InPointIndex, bool bUseSingleGuide)
|
|
{
|
|
// This code assumes:
|
|
// * HAIR_INTERPOLATION_POINT_STRIDE == 2
|
|
// * HAIR_INTERPOLATION_MAX_GUIDE_COUNT == 2
|
|
|
|
FGuidePointData Out = (FGuidePointData)0;
|
|
if (bUseSingleGuide)
|
|
{
|
|
// Ensure address is aligned on 4
|
|
// Since the data is 16bit wide, odd index needs to be shifted out of the 32bit input
|
|
// [ uint 32bits ][ uint 32bits ] ...
|
|
// [ Point0 ][ Point1 ][ Point2 ][ Point3 ] ...
|
|
const uint AddressInBytes = InPointIndex * HAIR_INTERPOLATION_POINT_STRIDE;
|
|
const uint AlignedAddressInBytes = AddressInBytes & 0xFFFFFFFE;
|
|
uint Data = InBuffer.Load(AlignedAddressInBytes);
|
|
Data = InPointIndex & 1 ? (Data>>16u) : Data;
|
|
|
|
Out.PointIndices = Data & 0xFF;
|
|
Out.PointLerps = UnpackR8(Data >> 8);
|
|
}
|
|
else
|
|
{
|
|
const uint AddressInBytes = InPointIndex * HAIR_INTERPOLATION_POINT_STRIDE * HAIR_INTERPOLATION_MAX_GUIDE_COUNT;
|
|
const uint Data = InBuffer.Load(AddressInBytes);
|
|
|
|
Out.PointIndices.x = Data & 0xFF;
|
|
Out.PointLerps.x = UnpackR8(Data >> 8);
|
|
|
|
Out.PointIndices.y = (Data>>16) & 0xFF;
|
|
Out.PointLerps.y = UnpackR8(Data >> 24);
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Cards guides
|
|
|
|
struct FCardsGuideData
|
|
{
|
|
uint VertexIndex;
|
|
float VertexLerp;
|
|
};
|
|
|
|
FCardsGuideData ReadCardGuideData(ByteAddressBuffer InBuffer, uint InVertexId)
|
|
{
|
|
const uint Data = InBuffer.Load(InVertexId * HAIR_INTERPOLATION_CARDS_GUIDE_STRIDE);
|
|
|
|
FCardsGuideData Out = (FCardsGuideData)0;
|
|
Out.VertexIndex = Data & 0xFFFFFF;
|
|
Out.VertexLerp = UnpackR8(Data >> 24);
|
|
return Out;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Curve
|
|
|
|
struct FHairCurve
|
|
{
|
|
uint PointIndex;
|
|
uint PointCount;
|
|
};
|
|
|
|
uint PackHairCurve(FHairCurve In)
|
|
{
|
|
#if HAIR_MAX_NUM_POINT_PER_CURVE != 0xFF || HAIR_MAX_NUM_POINT_PER_GROUP != 0xFFFFFF
|
|
#error Update hair curve ENCODING_TYPE
|
|
#endif
|
|
|
|
return (In.PointIndex & HAIR_MAX_NUM_POINT_PER_GROUP) | (In.PointCount << 24);
|
|
}
|
|
|
|
FHairCurve UnpackHairCurve(uint In)
|
|
{
|
|
#if HAIR_MAX_NUM_POINT_PER_CURVE != 0xFF || HAIR_MAX_NUM_POINT_PER_GROUP != 0xFFFFFF
|
|
#error Update hair curve ENCODING_TYPE
|
|
#endif
|
|
|
|
FHairCurve Out = (FHairCurve)0;
|
|
Out.PointIndex = In & HAIR_MAX_NUM_POINT_PER_GROUP;
|
|
Out.PointCount = In >> 24;
|
|
return Out;
|
|
}
|
|
|
|
FHairCurve ReadHairCurve(ByteAddressBuffer InBuffer, uint InIndex)
|
|
{
|
|
return UnpackHairCurve(InBuffer.Load(InIndex * 4u));
|
|
}
|
|
|
|
void WriteHairCurve(RWByteAddressBuffer InBuffer, uint InIndex, FHairCurve In)
|
|
{
|
|
InBuffer.Store(InIndex * 4u, PackHairCurve(In));
|
|
}
|
|
|
|
uint ReadHairPointToCurveIndex(ByteAddressBuffer InBuffer, uint InPointIndex)
|
|
{
|
|
// Encoding: base curve index + delta encoding for 8 consecutive control points
|
|
|
|
const uint PointIndex8 = InPointIndex >> 3u;
|
|
const uint LocalIndex8 = InPointIndex & 0x7;
|
|
const uint Data = InBuffer.Load(PointIndex8 * 4u);
|
|
|
|
// Extract the curve index delta encoding
|
|
const uint Mask = BitFieldMaskU32(LocalIndex8+1, 0);
|
|
const uint Bitfield = BitFieldExtractI32(Data, 8u, 24u);
|
|
const uint Deltas = countbits(Mask & Bitfield);
|
|
|
|
return (Data & 0xFFFFFF) + Deltas;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// UVs
|
|
|
|
uint PackUVs(float2 UV)
|
|
{
|
|
return (f32tof16(UV.x) & 0xFFFF) | ((f32tof16(UV.y) & 0xFFFF) << 16);
|
|
}
|
|
|
|
float PackUVsToFloat(float2 UV)
|
|
{
|
|
return asfloat(PackUVs(UV));
|
|
}
|
|
|
|
float2 UnPackUVs(uint InUV)
|
|
{
|
|
float2 Out;
|
|
Out.x = f16tof32(InUV & 0xFFFF);
|
|
Out.y = f16tof32((InUV >> 16) & 0xFFFF);
|
|
return Out;
|
|
}
|
|
|
|
float2 UnPackUVs(float InUV)
|
|
{
|
|
return UnPackUVs(asuint(InUV));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Normal
|
|
|
|
uint PackVertexNormal(float3 In)
|
|
{
|
|
return
|
|
(PackUnorm10(In.x * 0.5f + 0.5f) )|
|
|
(PackUnorm10(In.y * 0.5f + 0.5f)<<10)|
|
|
(PackUnorm10(In.z * 0.5f + 0.5f)<<20);
|
|
}
|
|
|
|
float PackVertexNormalToFloat(float3 In)
|
|
{
|
|
return asfloat(PackVertexNormal(In));
|
|
}
|
|
|
|
float3 UnpackVertexNormal(uint In)
|
|
{
|
|
const float3 N = float3(
|
|
UnpackUnorm10(In ) * 2.f - 1.f,
|
|
UnpackUnorm10(In>>10) * 2.f - 1.f,
|
|
UnpackUnorm10(In>>20) * 2.f - 1.f);
|
|
return normalize(N);
|
|
}
|
|
|
|
float3 UnpackVertexNormal(float In)
|
|
{
|
|
return UnpackVertexNormal(asuint(In));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Barycentrics
|
|
|
|
uint PackBarycentrics(float2 B)
|
|
{
|
|
return f32tof16(B.x) | (f32tof16(B.y) << 16);
|
|
}
|
|
|
|
float3 UnpackBarycentrics(uint E)
|
|
{
|
|
float3 Out;
|
|
Out.x = f16tof32(E & 0xFFFF);
|
|
Out.y = f16tof32((E >> 16) & 0xFFFF);
|
|
Out.z = 1 - Out.x - Out.y;
|
|
return Out;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Triangle
|
|
|
|
struct FHairTriangleIndex
|
|
{
|
|
uint TriangleIndex;
|
|
uint SectionIndex;
|
|
};
|
|
|
|
uint PackTriangleIndex(uint TriangleIndex, uint SectionIndex)
|
|
{
|
|
return ((SectionIndex & 0xFF)<<24) | (TriangleIndex & 0xFFFFFF);
|
|
}
|
|
|
|
FHairTriangleIndex UnpackTriangleIndex(uint Encoded)
|
|
{
|
|
FHairTriangleIndex Out;
|
|
Out.SectionIndex = (Encoded>>24) & 0xFF;
|
|
Out.TriangleIndex = Encoded & 0xFFFFFF;
|
|
return Out;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Source point/curve remapping
|
|
// Translate a curve/point index into its source data index
|
|
|
|
uint ReadHairSourceCurveIndex(ByteAddressBuffer InBuffer, uint InIndex, bool bIsBufferValid)
|
|
{
|
|
uint Out = ~0;
|
|
if (bIsBufferValid)
|
|
{
|
|
Out = InBuffer.Load(InIndex * 4u);
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
uint ReadHairSourcePointIndex(ByteAddressBuffer InBuffer, uint InIndex, bool bIsBufferValid)
|
|
{
|
|
uint Out = ~0;
|
|
if (bIsBufferValid)
|
|
{
|
|
Out = InBuffer.Load(InIndex * 4u);
|
|
}
|
|
return Out;
|
|
} |