138 lines
4.8 KiB
HLSL
138 lines
4.8 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/**
|
|
Flakes texture access based on light angular direction, uses a pseduo(i.e. in atlas format) 2D texture array
|
|
to access flakes slices and finally applies bilinear interpolation.
|
|
|
|
@param ThetaAngles - normalized(by Pi/2) thetaF and thetaI angles
|
|
@param FlakesTexArray - texture 2d array in an atlas format
|
|
@param FlakesTexCount - number of textures in the array
|
|
@param FlakesTexValues - x = texture size of an entry in the 2d array, y = ThetaSliceLUT texel size
|
|
@param TexCoord - UV coordinates
|
|
@param Tiling - UV tiling factor
|
|
@param NumThetaF - number of ThetaF bins
|
|
@param NumThetaI- number of thetaI bins
|
|
@param MaxThetaI - max thetaI bin that is used
|
|
@param ThetaSliceLUT - 1d texture with the index into flake texture array for a given thetaI bin
|
|
@return Bilinear interpolated value of the flakes.
|
|
*/
|
|
|
|
#define PI2 1.5707963267948966
|
|
#define USE_GRADIENTS 1
|
|
|
|
// NOTE. had to use macros as function structs aren't portable on certain platforms such as Vulkan
|
|
|
|
#if USE_GRADIENTS == 1
|
|
#define SAMPLE_TEX_COORD(SampleTexCoord, Result) \
|
|
float2 DdxUV = DDX(SampleTexCoord); \
|
|
float2 DdyUV = DDY(SampleTexCoord); \
|
|
Result = Texture2DSampleGrad(FlakesTexArray, FlakesTexArraySampler, SampleTexCoord, DdxUV, DdyUV);
|
|
#else
|
|
Result = Texture2DSample(FlakesTexArray, FlakesTexArraySampler, SampleTexCoord);
|
|
#endif
|
|
|
|
#define SAMPLE_TEX_ARRAY(UV, Index, Result) \
|
|
{ \
|
|
float SideCount = sqrt(FlakesTexCount); \
|
|
float InvSideCount = 1.0 / SideCount; \
|
|
float TexPadding = 8.0; \
|
|
float TexSize = FlakesTexValues.x; \
|
|
float PaddedTexSize = TexSize + TexPadding * 2.0; \
|
|
float AtlasTexelSize = 1.0 / (SideCount * PaddedTexSize); \
|
|
\
|
|
float2 TexOffset = Index; \
|
|
TexOffset.x = fmod(TexOffset.x, SideCount); \
|
|
TexOffset.y = floor(TexOffset.y * InvSideCount); \
|
|
TexOffset *= InvSideCount; \
|
|
TexOffset += TexPadding * AtlasTexelSize; \
|
|
\
|
|
float2 SampleTexCoord = frac(UV); \
|
|
SampleTexCoord *= InvSideCount * (TexSize / PaddedTexSize); \
|
|
SampleTexCoord += TexOffset; \
|
|
SAMPLE_TEX_COORD(SampleTexCoord, Result); \
|
|
}
|
|
|
|
#define SAMPLE_LUT(BinThetaI, Result) \
|
|
{ \
|
|
float TexelSize = FlakesTexValues.y; \
|
|
float Padding = 4.0; \
|
|
float Index = (BinThetaI) * Padding * TexelSize + TexelSize; \
|
|
float Value = Texture2DSampleLevel(ThetaSliceLUT, ThetaSliceLUTSampler, float2(Index, 0.0), 0.0).x; \
|
|
Result = floor(Value * 255.0 + 0.5); \
|
|
}
|
|
|
|
//ThetaF sampling defines the angular sampling, i.e. angular flake lifetime
|
|
float ThetaF = clamp(ThetaAngles.x / PI2, 0.0, 1.0) * NumThetaF + 0.5;
|
|
float ThetaI = clamp(ThetaAngles.y / PI2, 0.0, 1.0) * NumThetaF + 0.5;
|
|
|
|
//bilinear interpolation indices
|
|
float LowThetaF = floor(ThetaF);
|
|
float HighThetaF = LowThetaF + 1.0;
|
|
float LowThetaI = floor(ThetaI);
|
|
float HighThetaI = LowThetaI + 1.0;
|
|
|
|
float2 UV = TexCoord * Tiling;
|
|
float2 DdxUV = DDX(UV);
|
|
float2 DdyUV = DDY(UV);
|
|
if (NumThetaI < NumThetaF)
|
|
{
|
|
if ((LowThetaI % 2) == 1 || (HighThetaI % 2) == 1)
|
|
{
|
|
//tweak for extra randomness, in the case of almost planar geometries with repeating patches
|
|
UV.xy = UV.yx;
|
|
}
|
|
|
|
//map to the original sampling
|
|
float InvNumThetaF = 1.0 / NumThetaF;
|
|
LowThetaI = int(floor(float(LowThetaI) * NumThetaI * InvNumThetaF));
|
|
HighThetaI = int(floor(float(HighThetaI) * NumThetaI * InvNumThetaF));
|
|
}
|
|
|
|
half3 ColorBottomLeft = 0.0;
|
|
half3 ColorTopLeft = 0.0;
|
|
half3 ColorBottomRight = 0.0;
|
|
half3 ColorTopRight = 0.0;
|
|
//access flakes texture and stay in the correct slices(no slip over)
|
|
if (LowThetaI < MaxThetaI)
|
|
{
|
|
float BaseThetaIndex, UpperIndexLimit;
|
|
SAMPLE_LUT(LowThetaI, BaseThetaIndex);
|
|
SAMPLE_LUT(LowThetaI + 1.0, UpperIndexLimit);
|
|
|
|
float LowThetaIndex = BaseThetaIndex + LowThetaF;
|
|
if (LowThetaIndex < UpperIndexLimit)
|
|
{
|
|
SAMPLE_TEX_ARRAY(UV, LowThetaIndex, ColorBottomLeft.xyz);
|
|
}
|
|
float HighThetaIndex = BaseThetaIndex + HighThetaF;
|
|
if (HighThetaIndex < UpperIndexLimit)
|
|
{
|
|
SAMPLE_TEX_ARRAY(UV, HighThetaIndex, ColorBottomRight.xyz);
|
|
}
|
|
}
|
|
|
|
if (HighThetaI < MaxThetaI)
|
|
{
|
|
float BaseThetaIndex, UpperIndexLimit;
|
|
SAMPLE_LUT(HighThetaI, BaseThetaIndex);
|
|
SAMPLE_LUT(HighThetaI + 1.0, UpperIndexLimit);
|
|
|
|
float LowThetaIndex = BaseThetaIndex + LowThetaF;
|
|
if (LowThetaIndex < UpperIndexLimit)
|
|
{
|
|
SAMPLE_TEX_ARRAY(UV, LowThetaIndex, ColorTopLeft.xyz);
|
|
}
|
|
float HighThetaIndex = BaseThetaIndex + HighThetaF;
|
|
if (HighThetaIndex < UpperIndexLimit)
|
|
{
|
|
SAMPLE_TEX_ARRAY(UV, HighThetaIndex, ColorTopRight.xyz);
|
|
}
|
|
}
|
|
|
|
//bilinear interpolation
|
|
float DiffThetaF = ThetaF - float(LowThetaF);
|
|
float DiffThetaI = ThetaI - float(LowThetaI);
|
|
half3 Bottom = (1.0 - DiffThetaF) * ColorBottomLeft + DiffThetaF * ColorBottomRight;
|
|
half3 Top = (1.0 - DiffThetaF) * ColorTopLeft + DiffThetaF * ColorTopRight;
|
|
return (1.0 - DiffThetaI) * Bottom + DiffThetaI * Top;
|