// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "/Engine/Shared/HairStrandsDefinitions.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // Vertex factory Flags #define EHairCardsVFlags_Texture0 0x1 #define EHairCardsVFlags_Texture1 0x2 #define EHairCardsVFlags_Texture2 0x4 #define EHairCardsVFlags_Texture3 0x8 #define EHairCardsVFlags_Texture4 0x10 #define EHairCardsVFlags_Texture5 0x20 #define EHairCardsVFlags_InvertedUV 0x40 #define EHairCardsVFlags_VertexColor 0x80 // Attribute index #define EHairCardsVFAttribute_Depth 0 #define EHairCardsVFAttribute_Seed 1 #define EHairCardsVFAttribute_CoordU 2 #define EHairCardsVFAttribute_GroupIndex 3 #define EHairCardsVFAttribute_Roughness 4 #define EHairCardsVFAttribute_RootUV 5 #define EHairCardsVFAttribute_Color 6 #define EHairCardsVFAttribute_Tangent 7 #define EHairCardsVFAttribute_Coverage 8 #define EHairCardsVFAttribute_Auxilary 9 #define LAYOUT_VALUES(LayoutIdx, L0, L1, L2, L3) \ (LayoutIdx == 0 ? L0 : \ (LayoutIdx == 1 ? L1 : \ (LayoutIdx == 2 ? L2 : L3))) uint GetHairCardTextureIndex(uint InLayoutIndex, uint InAttribute) { // See GroomAssetCards.cpp, EHairTextureLayout::Layout0/1/2/3 for mapping details. (9 = not present/not available) switch (InAttribute) { case EHairCardsVFAttribute_Depth : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 1, 1); case EHairCardsVFAttribute_Seed : return LAYOUT_VALUES(InLayoutIndex, 3, 3, 1, 2); case EHairCardsVFAttribute_CoordU : return LAYOUT_VALUES(InLayoutIndex, 3, 3, 0, 0); case EHairCardsVFAttribute_GroupIndex : return LAYOUT_VALUES(InLayoutIndex, 9, 4, 9, 1); // Only used for mesh, not for cards case EHairCardsVFAttribute_Roughness : return LAYOUT_VALUES(InLayoutIndex, 9, 9, 9, 9); case EHairCardsVFAttribute_RootUV : return LAYOUT_VALUES(InLayoutIndex, 9, 3, 9, 2); case EHairCardsVFAttribute_Color : return LAYOUT_VALUES(InLayoutIndex, 9, 4, 9, 1); case EHairCardsVFAttribute_Tangent : return LAYOUT_VALUES(InLayoutIndex, 2, 2, 0, 0); case EHairCardsVFAttribute_Coverage : return LAYOUT_VALUES(InLayoutIndex, 1, 1, 1, 2); case EHairCardsVFAttribute_Auxilary : return LAYOUT_VALUES(InLayoutIndex, 5, 5, 9, 9); } return 0; } bool IsHairCardTextureIndexValid(uint InLayoutIndex, uint InAttribute) { return GetHairCardTextureIndex(InLayoutIndex, InAttribute) < HAIR_CARDS_MAX_TEXTURE_COUNT; } uint GetHairCardChannelIndex(uint InLayoutIndex, uint InAttribute) { // See GroomAssetCards.cpp, EHairTextureLayout::Layout0/1/2/3 for mapping details switch (InAttribute) { case EHairCardsVFAttribute_Depth : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 1, 2); case EHairCardsVFAttribute_Seed : return LAYOUT_VALUES(InLayoutIndex, 3, 3, 2, 2); case EHairCardsVFAttribute_CoordU : return LAYOUT_VALUES(InLayoutIndex, 2, 2, 3, 3); case EHairCardsVFAttribute_GroupIndex : return LAYOUT_VALUES(InLayoutIndex, 3, 3, 0, 3); case EHairCardsVFAttribute_Roughness : return LAYOUT_VALUES(InLayoutIndex, 3, 0, 3, 3); case EHairCardsVFAttribute_RootUV : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 0, 0); case EHairCardsVFAttribute_Color : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 0, 0); // For layout 3, Color is only Color.XY The Z channel is overriden by Seed. See GetHairCardsColor() case EHairCardsVFAttribute_Tangent : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 0, 0); case EHairCardsVFAttribute_Coverage : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 0, 3); case EHairCardsVFAttribute_Auxilary : return LAYOUT_VALUES(InLayoutIndex, 0, 0, 0, 0); } return 0; } #if HAIR_CARD_MESH_FACTORY #include "/Engine/Private/HairStrands/HairStrandsVertexFactoryCommon.ush" /////////////////////////////////////////////////////////////////////////////////////////////////// // Helper functions for fetching attributes // If we need custom dynamic layout: // uint GetHairCardTextureIndex(uint In) { return ((HairCards.AttributeTextureIndex) >> (In*3u) & 0x7u); } // uint GetHairCardChannelIndex(uint In) { return ((HairCards.AttributeChannelIndex) >> (In*2u) & 0x3u); } float2 GetAtlasUV(float2 In) { return float2(In.x, (HairCardsVF.Flags & EHairCardsVFlags_InvertedUV) ? 1 - In.y : In.y); } bool HasHairCardsVertexColor() { return (HairCardsVF.Flags & EHairCardsVFlags_VertexColor); } bool HasTextureBound(uint In) { // HairCardsVF.Flags stores InverttUV on the first bit, texture bound after const uint TextureIndex = GetHairCardTextureIndex(HairCardsVF.LayoutIndex, In); return (TextureIndex < HairCardsVF.TextureCount) && (HairCardsVF.Flags & (1u << TextureIndex)) != 0; } // Generic attribute texture fetches float4 FetchAttributeTexture(uint InTextureIndex, float2 InAtlasUV, float InBias) { float4 Out = 0; if (InTextureIndex == 0) { Out = Texture2DSampleBias(HairCardsVF.Texture0Texture, HairCardsVF.Texture0Sampler, InAtlasUV, InBias); } else if (InTextureIndex == 1) { Out = Texture2DSampleBias(HairCardsVF.Texture1Texture, HairCardsVF.Texture1Sampler, InAtlasUV, InBias); } else if (InTextureIndex == 2) { Out = Texture2DSampleBias(HairCardsVF.Texture2Texture, HairCardsVF.Texture2Sampler, InAtlasUV, InBias); } else if (InTextureIndex == 3) { Out = Texture2DSampleBias(HairCardsVF.Texture3Texture, HairCardsVF.Texture3Sampler, InAtlasUV, InBias); } else if (InTextureIndex == 4) { Out = Texture2DSampleBias(HairCardsVF.Texture4Texture, HairCardsVF.Texture4Sampler, InAtlasUV, InBias); } else if (InTextureIndex == 5) { Out = Texture2DSampleBias(HairCardsVF.Texture5Texture, HairCardsVF.Texture5Sampler, InAtlasUV, InBias); } return Out; } float4 FetchAttributeTexture(uint InTextureIndex, float2 InAtlasUV) { float4 Out = 0; if (InTextureIndex == 0) { Out = Texture2DSample(HairCardsVF.Texture0Texture, HairCardsVF.Texture0Sampler, InAtlasUV); } else if (InTextureIndex == 1) { Out = Texture2DSample(HairCardsVF.Texture1Texture, HairCardsVF.Texture1Sampler, InAtlasUV); } else if (InTextureIndex == 2) { Out = Texture2DSample(HairCardsVF.Texture2Texture, HairCardsVF.Texture2Sampler, InAtlasUV); } else if (InTextureIndex == 3) { Out = Texture2DSample(HairCardsVF.Texture3Texture, HairCardsVF.Texture3Sampler, InAtlasUV); } else if (InTextureIndex == 4) { Out = Texture2DSample(HairCardsVF.Texture4Texture, HairCardsVF.Texture4Sampler, InAtlasUV); } else if (InTextureIndex == 5) { Out = Texture2DSample(HairCardsVF.Texture5Texture, HairCardsVF.Texture5Sampler, InAtlasUV); } return Out; } // Generic attribute fetch functions float FetchAttribute1(uint In, float2 InAtlasUV, float InBias) { const float4 Value = FetchAttributeTexture(GetHairCardTextureIndex(HairCardsVF.LayoutIndex, In), InAtlasUV, InBias); return Value[GetHairCardChannelIndex(HairCardsVF.LayoutIndex, In)]; } float FetchAttribute1(uint In, float2 InAtlasUV) { const float4 Value = FetchAttributeTexture(GetHairCardTextureIndex(HairCardsVF.LayoutIndex, In), InAtlasUV); return Value[GetHairCardChannelIndex(HairCardsVF.LayoutIndex, In)]; } float2 FetchAttribute2(uint In, float2 InAtlasUV) { const float4 Value = FetchAttributeTexture(GetHairCardTextureIndex(HairCardsVF.LayoutIndex, In), InAtlasUV); return GetHairCardChannelIndex(HairCardsVF.LayoutIndex, In) == 0 ? Value.xy : Value.zw; } // float2 alignemnt is forced onto .xy or .zw float3 FetchAttribute3(uint In, float2 InAtlasUV) { const float4 Value = FetchAttributeTexture(GetHairCardTextureIndex(HairCardsVF.LayoutIndex, In), InAtlasUV); return Value.xyz; } // float3 alignemnt forced onto .xyz float4 FetchAttribute4(uint In, float2 InAtlasUV) { const float4 Value = FetchAttributeTexture(GetHairCardTextureIndex(HairCardsVF.LayoutIndex, In), InAtlasUV); return Value; } // Special attribute fetch functions float GetDepthOffset (float2 InAtlasUV) { float Out = 0; if (HasTextureBound(EHairCardsVFAttribute_Depth)) { Out = FetchAttribute1(EHairCardsVFAttribute_Depth, InAtlasUV); } return Out; } float GetSeed (float2 InAtlasUV) { float Out = 0; if (HasTextureBound(EHairCardsVFAttribute_Seed)) { Out = FetchAttribute1(EHairCardsVFAttribute_Seed, InAtlasUV); } return Out; } float GetCoordU (float2 InAtlasUV) { float Out = 0; if (HasTextureBound(EHairCardsVFAttribute_CoordU)) { Out = FetchAttribute1(EHairCardsVFAttribute_CoordU, InAtlasUV); } return Out; } float GetGroupIndex (float2 InAtlasUV, float InDefault) { float Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_GroupIndex)) { Out = FetchAttribute1(EHairCardsVFAttribute_GroupIndex,InAtlasUV); Out *= 255.f; } return Out; } float GetRoughness (float2 InAtlasUV, float InDefault) { float Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_Roughness)) { Out = FetchAttribute1(EHairCardsVFAttribute_Roughness, InAtlasUV); } return Out; } float GetCoverage (float2 InAtlasUV, float InDefault) { float Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_Coverage)) { Out = FetchAttribute1(EHairCardsVFAttribute_Coverage, InAtlasUV, HairCardsVF.CoverageBias);} return Out; } float2 GetRootUV (float2 InAtlasUV, float2 InDefault) { float2 Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_RootUV)) { Out = FetchAttribute2(EHairCardsVFAttribute_RootUV, InAtlasUV); } return Out; } float3 GetColor (float2 InAtlasUV, float3 InDefault) { float3 Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_Color)) { Out = FetchAttribute3(EHairCardsVFAttribute_Color, InAtlasUV); Out = Pow2(Out); } return Out; } // Cheap sRGB -> Linear encoding float3 GetTangent (float2 InAtlasUV, float3 InDefault) { float3 Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_Tangent)) { Out = FetchAttribute3(EHairCardsVFAttribute_Tangent, InAtlasUV); Out = Out*2-1.f; } return Out; } float4 GetAuxilary (float2 InAtlasUV, float4 InDefault) { float4 Out = InDefault; if (HasTextureBound(EHairCardsVFAttribute_Auxilary)) { Out = FetchAttribute4(EHairCardsVFAttribute_Auxilary, InAtlasUV); } return Out; } /////////////////////////////////////////////////////////////////////////////////////////////////// // MaterialTemplate interface float GetHairCardsDepth(float2 InAtlasUV, float InDeviceZ) { const float SceneDepthOffset = GetDepthOffset(InAtlasUV); const float SceneDepth = ConvertFromDeviceZ(InDeviceZ); return ConvertToDeviceZ(SceneDepth + SceneDepthOffset); } float3 GetHairCardsTangent(float2 InAtlasUV, half3x3 TangentToWorld, bool bUseTangentSpace) { const float3 LocalTangent = GetTangent(InAtlasUV, float3(0, 1, 0)); const float3 WorldTangent = mul(LocalTangent, TangentToWorld); return bUseTangentSpace ? LocalTangent : WorldTangent; } float GetHairCardsCoverage(float2 InAtlasUV) { return GetCoverage(InAtlasUV, 1.f); } float4 GetHairCardsAuxilaryData(float2 InAtlasUV) { return GetAuxilary(InAtlasUV, 0.f); } float2 GetHairCardsUV(float2 InAtlasUV) { return float2(GetCoordU(InAtlasUV), 0.5f); } float2 GetHairCardsDimensions(float2 InAtlasUV, float HairPrimitiveLength) { return float2(HairPrimitiveLength, 0.01f/*WorldRadius*/); } float2 GetHairCardsRootUV(float2 InAtlasUV, float2 InRootUV) { // Invert V to compensate image origin flip // Similar to DecodeHairAttribute in HairStrandVertexFactoryCommon.ush return GetRootUV(InAtlasUV, float2(InRootUV.x, 1.0 - InRootUV.y)); } float GetHairCardsSeed(float2 InAtlasUV) { return GetSeed(InAtlasUV); } uint3 GetHairCardsClumpID(float2 InAtlasUV) { return 0; } float3 GetHairCardsColor(float2 InAtlasUV, float3 InColor) { float3 OutColor = GetColor(InAtlasUV, InColor); return HairCardsVF.LayoutIndex == 3 ? float3(OutColor.xy, 0) : OutColor; } float GetHairCardsRoughness(float2 InAtlasUV, float InCardRoughness) { //return GetRoughness(InAtlasUV, InCardRoughness); return (HairCardsVF.Flags & EHairCardsVFAttribute_Roughness) ? 0 : InCardRoughness; } float GetHairCardsAO(float2 InAtlasUV, float InAO) { return 1.0f; } float GetHairCardsGroupIndex(float2 InAtlasUV, float InCardGroupIndex) { // * For hair cards, group index is stored onto vertices // * For hair meshes, group index is stored within a texture return GetGroupIndex(InAtlasUV, InCardGroupIndex); } //////////////////////////////////////////////////////////////////////////////// #endif // HAIR_CARD_MESH_FACTORY