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

414 lines
17 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Common.ush"
#include "Landscape/LandscapeCommon.ush"
Texture2D ReadTexture1;
Texture2D ReadTexture2; // This one is optional, it's valid when OutputConfig.z == 1
SamplerState ReadTexture1Sampler;
SamplerState ReadTexture2Sampler;
float4 LayerInfo; // x == Layer Alpha value, y == Layer Visibility flag, z == Layer Blend Mode (0: Additive Blend Mode, 1: Alpha Blend Mode), w == Unused
float4 OutputConfig; // x == ApplyLayerModifiers, y == Alpha Blend Mode or Output weightmap as substractive/additive, z == Use ReadTexture2 and out Delta with ReadTexture1, w == Output Normals (heightmaps) or Unused (weightmaps)
float2 TextureSize; // x == ReadTexture1 width, y == ReadTexture1 height
float3 LandscapeGridScale; // x == LS Actor DrawScale.X, y == LS Actor DrawScale.y, z == LS Actor DrawScale.z / 128.0f (ZSCALE)
float CurrentMipComponentVertexCount;
float2 CurrentMipTextureSize;
float2 ParentMipTextureSize;
float4 SourceOffsetAndSizeUV;
uint ChannelSwizzleMask;
float ExtractAndScaleHeight(float2 InPackedHeight)
{
float Height = UnpackHeight(InPackedHeight) - 32768.0;
return Height * LandscapeGridScale.z * TERRAIN_ZSCALE;
}
float ExtractAlpha(float2 InPackedAlpha)
{
int AlphaWithFlags = ((int) round(InPackedAlpha.r * 255.0) << 8) | (int) round(InPackedAlpha.g * 255.0);
int Alpha = AlphaWithFlags & 0xFFFC; // Last 2 bits are used to store lower/raise flags
return float(Alpha) / 65532.0; // 0xFFFC in float
}
float4 CalculateNormalsFromHeights(float2 InTextureCoordinates, float4 OutColor)
{
float2 TexelSize = 1.0 / TextureSize;
float MaskMinBorderX = (InTextureCoordinates.x <= TexelSize.x) ? 0.0f : 1.0f; // first pixel X
float MaskMinBorderY = (InTextureCoordinates.y <= TexelSize.y) ? 0.0f : 1.0f; // first pixel Y
float MaskMaxBorderX = (InTextureCoordinates.x >= 1.0f - TexelSize.x) ? 0.0f : 1.0f; // last pixel X
float MaskMaxBorderY = (InTextureCoordinates.y >= 1.0f - TexelSize.y) ? 0.0f : 1.0f; // last pixel Y
// The triangle topology is the following (where C = center, T = top, B = bottom, L = left, R = right and Nx the normals we need to interpolate):
// TL ------ TT
// | \ | \
// | \ | \
// | \ | \
// | N0 \ N1 | N3 \
// | \ | \
// | \ | \
// | \ | \
// LL ------ CC ------ RR
// \ | \ |
// \ | \ |
// \ | \ |
// \ N2 | N4 \ N5 |
// \ | \ |
// \ | \ |
// \ | \ |
// BB ------ BR
// So we can retrieve all 7 samples using 4 gathers :
// Make sure we gather the 4 pixels in the bottom right direction by sampling at (0.75, 0.75) of the pixel (gather retrieves the 4 samples that would be used for bilinear interpolation):
float2 GatherLocation = InTextureCoordinates + 0.25f * TexelSize;
float4 Red0 = ReadTexture1.GatherRed(ReadTexture1Sampler, GatherLocation - TexelSize);
float4 Green0 = ReadTexture1.GatherGreen(ReadTexture1Sampler, GatherLocation - TexelSize);
float4 Mask0 = ReadTexture1.GatherAlpha(ReadTexture1Sampler, GatherLocation - TexelSize);
float4 Red1 = ReadTexture1.GatherRed(ReadTexture1Sampler, GatherLocation);
float4 Green1 = ReadTexture1.GatherGreen(ReadTexture1Sampler, GatherLocation);
float4 Mask1 = ReadTexture1.GatherAlpha(ReadTexture1Sampler, GatherLocation);
// [x,y,z] = World Position [w] = valid mask
float4 TL = float4((InTextureCoordinates * TextureSize + float2(-1, -1)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red0.w, Green0.w)), Mask0.w);
float4 TT = float4((InTextureCoordinates * TextureSize + float2(+0, -1)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red0.z, Green0.z)), Mask0.z);
float4 CC = float4((InTextureCoordinates * TextureSize + float2(+0, +0)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red0.y, Green0.y)), Mask0.y);
float4 LL = float4((InTextureCoordinates * TextureSize + float2(-1, +0)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red0.x, Green0.x)), Mask0.x);
float4 RR = float4((InTextureCoordinates * TextureSize + float2(+1, +0)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red1.z, Green1.z)), Mask1.z);
float4 BR = float4((InTextureCoordinates * TextureSize + float2(+1, +1)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red1.y, Green1.y)), Mask1.y);
float4 BB = float4((InTextureCoordinates * TextureSize + float2(+0, +1)) * LandscapeGridScale.xy, ExtractAndScaleHeight(float2(Red1.x, Green1.x)), Mask1.x);
// mask out samples that are off the edge of the texture
TL.w *= MaskMinBorderX * MaskMinBorderY;
TT.w *= MaskMinBorderY;
// CC should never be off the edge
LL.w *= MaskMinBorderX;
RR.w *= MaskMaxBorderX;
BR.w *= MaskMaxBorderX * MaskMaxBorderY;
BB.w *= MaskMaxBorderY;
float3 N0 = ComputeNullableTriangleNormal(CC, LL, TL);
float3 N1 = ComputeNullableTriangleNormal(TL, TT, CC);
float3 N2 = ComputeNullableTriangleNormal(LL, CC, BB);
float3 N3 = ComputeNullableTriangleNormal(RR, CC, TT);
float3 N4 = ComputeNullableTriangleNormal(BR, BB, CC);
float3 N5 = ComputeNullableTriangleNormal(CC, RR, BR);
// average the normals
float3 FinalNormal = normalize(N0 + N1 + N2 + N3 + N4 + N5);
// Scale back to be 0-1 normal
OutColor.ba = (FinalNormal.xy + 1.0) * 0.5;
return OutColor;
}
void PSHeightmapMain(float2 InTextureCoordinates : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float LayerVisibility = LayerInfo.y;
float4 SourceColor = ReadTexture1.Sample(ReadTexture1Sampler, InTextureCoordinates);
float Height = UnpackHeight(SourceColor.rg);
// Perform calculation 0 based
Height -= 32768.0f;
// Output as Layer, so apply Layer info
if (OutputConfig.x == 1.0)
{
float LayerAlpha = LayerInfo.x;
Height *= LayerAlpha * LayerVisibility;
}
// Output using 2nd heightmap
if (OutputConfig.z == 1.0)
{
float2 BaseColor = ReadTexture2.Sample(ReadTexture2Sampler, InTextureCoordinates).rg;
float BaseHeight = UnpackHeight(BaseColor.rg);
BaseHeight -= 32768.0;
float LayerBlendMode = LayerInfo.z;
if (LayerBlendMode == 1 && LayerVisibility == 1)
{
// Pre-multiplied Alpha Blend Mode
float HeightAlphaBlend = ExtractAlpha(SourceColor.ba);
float NewHeight = Height + (HeightAlphaBlend * BaseHeight);
uint RaiseLower = floor(SourceColor.a * 255.f + 0.5f);
bool bLowerTerrain = (RaiseLower & 1);
bool bRaiseTerrain = (RaiseLower & 2);
if ((bRaiseTerrain && NewHeight > BaseHeight) || (bLowerTerrain && NewHeight < BaseHeight))
{
Height = NewHeight;
}
else
{
Height = BaseHeight;
}
}
else
{
// Additive Blend Mode
Height += BaseHeight;
}
// clamp to valid range
Height = clamp(Height, -32768.0, 32767.0);
}
// convert back to positive range
Height += 32768.0f;
float2 PackedHeight = PackHeight(Height);
// prepare the output color
OutColor.r = PackedHeight.x;
OutColor.g = PackedHeight.y;
OutColor.b = SourceColor.b;
OutColor.a = SourceColor.a;
// we override alpha to indicate valid pixels before calculating normals
bool bSetAlphaOne = (OutputConfig.y != 0.0);
if (bSetAlphaOne)
{
OutColor.b = 0.0f;
OutColor.a = 1.0f;
}
// Output normals
bool bGenerateNormals = (OutputConfig.w >= 0.5);
if (bGenerateNormals)
{
OutColor = CalculateNormalsFromHeights(InTextureCoordinates.xy, OutColor);
}
}
void PSHeightmapMainMips(float2 InTextureCoordinates : TEXCOORD0, out float4 OutColor : SV_Target0)
{
bool IsMinBorderTexelX = false;
bool IsMinBorderTexelY = false;
bool IsMaxBorderTexelX = false;
bool IsMaxBorderTexelY = false;
// Special case of 1 texel size component
if (CurrentMipComponentVertexCount == 1)
{
if (CurrentMipTextureSize.x >= CurrentMipTextureSize.y) // x size 1 texel
{
IsMinBorderTexelY = true;
IsMinBorderTexelX = (InTextureCoordinates.x * CurrentMipTextureSize.x) <= 1.0;
IsMaxBorderTexelX = (InTextureCoordinates.x * CurrentMipTextureSize.x) >= CurrentMipTextureSize.x - 1.0;
}
else
{
IsMinBorderTexelX = true;
IsMinBorderTexelY = (InTextureCoordinates.y * CurrentMipTextureSize.y) <= 1.0;
IsMaxBorderTexelY = (InTextureCoordinates.y * CurrentMipTextureSize.y) >= CurrentMipTextureSize.y - 1.0;
}
}
else
{
float2 CurrentMipQuadTexelSize = 1.0 / CurrentMipComponentVertexCount;
float2 LandscapeQuadUV = frac(InTextureCoordinates * CurrentMipTextureSize / CurrentMipComponentVertexCount);
IsMinBorderTexelX = LandscapeQuadUV.x <= CurrentMipQuadTexelSize.x;
IsMinBorderTexelY = LandscapeQuadUV.y <= CurrentMipQuadTexelSize.y;
IsMaxBorderTexelX = LandscapeQuadUV.x >= (CurrentMipQuadTexelSize.x * max(CurrentMipComponentVertexCount - 1, 1.0));
IsMaxBorderTexelY = LandscapeQuadUV.y >= (CurrentMipQuadTexelSize.y * max(CurrentMipComponentVertexCount - 1, 1.0));
}
float2 ParentMipTexelSize = 1.0 / ParentMipTextureSize;
float2 SourceUV = InTextureCoordinates - (ParentMipTexelSize * 0.5);
float HeightResult = 0;
float2 NormalResult = 0;
float Alpha = 0.5;
OutColor = 0; // Default to 0
if (IsMinBorderTexelX) // on left border
{
float4 SourceSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV, 0);
float4 DownSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(0.0, ParentMipTexelSize.y), 0);
float SourceHeight = UnpackHeight(SourceSample.rg);
float DownHeight = UnpackHeight(DownSample.rg);
if (IsMinBorderTexelY) // on top border
{
HeightResult = SourceHeight;
NormalResult = float2(SourceSample.ba);
}
else if (IsMaxBorderTexelY) // on bottom border
{
HeightResult = DownHeight;
NormalResult = float2(DownSample.ba);
}
else
{
HeightResult = clamp(round(lerp(SourceHeight, DownHeight, Alpha)), 0.0, 65535.0);
NormalResult.x = lerp(SourceSample.b, DownSample.b, Alpha);
NormalResult.y = lerp(SourceSample.a, DownSample.a, Alpha);
}
}
else if (IsMaxBorderTexelX) // on right border
{
float4 RightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(ParentMipTexelSize.x, 0.0), 0);
float4 DownRightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + ParentMipTexelSize, 0);
float RightHeight = UnpackHeight(RightSample.rg);
float DownRightHeight = UnpackHeight(DownRightSample.rg);
if (IsMinBorderTexelY) // on top border
{
HeightResult = RightHeight;
NormalResult = float2(RightSample.ba);
}
else if (IsMaxBorderTexelY) // on bottom border
{
HeightResult = DownRightHeight;
NormalResult = float2(DownRightSample.ba);
}
else
{
HeightResult = clamp(round(lerp(RightHeight, DownRightHeight, Alpha)), 0.0, 65535.0);;
NormalResult.x = lerp(RightSample.b, DownRightSample.b, Alpha);
NormalResult.y = lerp(RightSample.a, DownRightSample.a, Alpha);
}
}
else // center texel
{
float4 SourceSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV, 0);
float4 DownSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(0.0, ParentMipTexelSize.y), 0);
float4 RightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(ParentMipTexelSize.x, 0.0), 0);
float4 DownRightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + ParentMipTexelSize, 0);
float SourceHeight = UnpackHeight(SourceSample.rg);
float DownHeight = UnpackHeight(DownSample.rg);
float RightHeight = UnpackHeight(RightSample.rg);
float DownRightHeight = UnpackHeight(DownRightSample.rg);
if (IsMinBorderTexelY) // on top border
{
HeightResult = clamp(round(lerp(SourceHeight, RightHeight, Alpha)), 0.0, 65535.0);
NormalResult.x = lerp(SourceSample.b, RightSample.b, Alpha);
NormalResult.y = lerp(SourceSample.a, RightSample.a, Alpha);
}
else if (IsMaxBorderTexelY) // on bottom border
{
HeightResult = clamp(round(lerp(DownHeight, DownRightHeight, Alpha)), 0.0, 65535.0);
NormalResult.x = lerp(DownSample.b, DownRightSample.b, Alpha);
NormalResult.y = lerp(DownSample.a, DownRightSample.a, Alpha);
}
else
{
float SourceToRightHeight = lerp(SourceHeight, RightHeight, Alpha);
float DownToDownRightHeight = lerp(DownHeight, DownRightHeight, Alpha);
HeightResult = clamp(round(lerp(SourceToRightHeight, DownToDownRightHeight, Alpha)), 0.0, 65535.0);
NormalResult.x = lerp(lerp(SourceSample.b, RightSample.b, Alpha), lerp(DownSample.b, DownRightSample.b, Alpha), Alpha);
NormalResult.y = lerp(lerp(SourceSample.a, RightSample.a, Alpha), lerp(DownSample.a, DownRightSample.a, Alpha), Alpha);
}
}
float2 PackedHeightResult = PackHeight(HeightResult);
OutColor = float4(PackedHeightResult.xy, NormalResult.xy);
}
void PSWeightmapMain(float2 InTextureCoordinates : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float4 SourceColor = ReadTexture1.Sample(ReadTexture1Sampler, InTextureCoordinates);
// Output as Layer, so apply Layer info
SourceColor = OutputConfig.x == 1.0 ? SourceColor * LayerInfo.x * LayerInfo.y : SourceColor;
// Output using 2nd heightmap
if (OutputConfig.z == 1.0)
{
float4 BaseColor = ReadTexture2.Sample(ReadTexture2Sampler, InTextureCoordinates);
// Apply addtive/substractive logic
SourceColor = OutputConfig.y == 0.0f ? clamp(BaseColor + SourceColor, 0.0, 1.0) : clamp(BaseColor - SourceColor, 0.0, 1.0);
}
OutColor = SourceColor;
}
void PSWeightmapMainMips(float2 InTextureCoordinates : TEXCOORD0, out float4 OutColor : SV_Target0)
{
bool IsMinBorderTexelX = false;
bool IsMinBorderTexelY = false;
bool IsMaxBorderTexelX = false;
bool IsMaxBorderTexelY = false;
// Special case of 1 texel size component
if (CurrentMipComponentVertexCount == 1)
{
if (CurrentMipTextureSize.x >= CurrentMipTextureSize.y) // x size 1 texel
{
IsMinBorderTexelY = true;
IsMinBorderTexelX = (InTextureCoordinates.x * CurrentMipTextureSize.x) <= 1.0;
IsMaxBorderTexelX = (InTextureCoordinates.x * CurrentMipTextureSize.x) >= CurrentMipTextureSize.x - 1.0;
}
else
{
IsMinBorderTexelX = true;
IsMinBorderTexelY = (InTextureCoordinates.y * CurrentMipTextureSize.y) <= 1.0;
IsMaxBorderTexelY = (InTextureCoordinates.y * CurrentMipTextureSize.y) >= CurrentMipTextureSize.y - 1.0;
}
}
else
{
float2 CurrentMipQuadTexelSize = 1.0 / CurrentMipComponentVertexCount;
float2 LandscapeQuadUV = frac(InTextureCoordinates * CurrentMipTextureSize / CurrentMipComponentVertexCount);
IsMinBorderTexelX = LandscapeQuadUV.x <= CurrentMipQuadTexelSize.x;
IsMinBorderTexelY = LandscapeQuadUV.y <= CurrentMipQuadTexelSize.y;
IsMaxBorderTexelX = LandscapeQuadUV.x >= (CurrentMipQuadTexelSize.x * max(CurrentMipComponentVertexCount - 1, 1.0));
IsMaxBorderTexelY = LandscapeQuadUV.y >= (CurrentMipQuadTexelSize.y * max(CurrentMipComponentVertexCount - 1, 1.0));
}
float2 ParentMipTexelSize = 1.0 / ParentMipTextureSize;
float2 SourceUV = InTextureCoordinates - (ParentMipTexelSize * 0.5);
float Alpha = 0.5;
OutColor = 0; // Default to 0
if (IsMinBorderTexelX) // on left border
{
float4 SourceSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV, 0);
float4 DownSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(0.0, ParentMipTexelSize.y), 0);
OutColor = IsMinBorderTexelY ? SourceSample : (IsMaxBorderTexelY ? DownSample : lerp(SourceSample, DownSample, Alpha));
}
else if (IsMaxBorderTexelX) // on right border
{
float4 RightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(ParentMipTexelSize.x, 0.0), 0);
float4 DownRightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + ParentMipTexelSize, 0);
OutColor = IsMinBorderTexelY ? RightSample : (IsMaxBorderTexelY ? DownRightSample : lerp(RightSample, DownRightSample, Alpha));
}
else // center texel
{
float4 SourceSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV, 0);
float4 DownSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(0.0, ParentMipTexelSize.y), 0);
float4 RightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + float2(ParentMipTexelSize.x, 0.0), 0);
float4 DownRightSample = ReadTexture1.SampleLevel(ReadTexture1Sampler, SourceUV + ParentMipTexelSize, 0);
OutColor = IsMinBorderTexelY ? lerp(SourceSample, RightSample, Alpha) : (IsMaxBorderTexelY ? lerp(DownSample, DownRightSample, Alpha) : lerp(lerp(SourceSample, RightSample, Alpha), lerp(DownSample, DownRightSample, Alpha), Alpha));
}
}
void CopyTextureVS(in uint VertexId : SV_VertexID, out float4 OutPosition : SV_POSITION, out float2 OutTexCoord : TEXCOORD0)
{
OutTexCoord = float2(((VertexId + 1) / 3) & 1, VertexId & 1);
OutPosition.xy = float2(OutTexCoord.x, 1.f - OutTexCoord.y) * 2.f - 1.f;
OutPosition.zw = float2(0, 1);
}
void CopyTexturePS(in float4 InPosition : SV_POSITION, in noperspective float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0)
{
// InUV is in the [0,1] range but in the viewport space, whose size is CopySize, so we need to adjust it to sample the corresponding range in the source texture :
float2 SampleUV = (InUV * SourceOffsetAndSizeUV.zw) + SourceOffsetAndSizeUV.xy;
float4 SampleColor = ReadTexture1.SampleLevel(ReadTexture1Sampler, SampleUV, 0);
// Swizzle channels as requested by ChannelSwizzleMask:
[unroll]
for (uint DestinationChannelIndex = 0; DestinationChannelIndex < 4; ++DestinationChannelIndex)
{
uint SourceChannelIndex = ((3u << (DestinationChannelIndex * 2u)) & ChannelSwizzleMask) >> (DestinationChannelIndex * 2u);
OutColor[DestinationChannelIndex] = SampleColor[SourceChannelIndex];
}
}