// 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]; } }