Files
UnrealEngine/Engine/Plugins/Experimental/Water/Shaders/Private/WaterQuadTreeMerge.usf
2025-05-18 13:04:45 +08:00

106 lines
3.9 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "/Engine/Private/Common.ush"
#include "/Engine/Private/PackUnpack.ush"
#include "WaterQuadTreeCommon.ush"
#ifndef NUM_MSAA_SAMPLES
#define NUM_MSAA_SAMPLES 1
#endif
#if NUM_MSAA_SAMPLES > 1
Texture2DMS<float4, NUM_MSAA_SAMPLES> WaterBodyRasterTextureMS;
Texture2DMS<float4, NUM_MSAA_SAMPLES> ZBoundsRasterTextureMS;
#else
Texture2D<float4> WaterBodyRasterTexture;
Texture2D<float4> ZBoundsRasterTexture;
#endif
StructuredBuffer<FWaterBodyRenderData> WaterBodyRenderData;
int SuperSamplingFactor;
float RcpCaptureDepthRange;
float ZBoundsPadding; // We base our tile Z bounds on rasterization, however with sloped rivers we will likely miss part of the Z extent. Add some padding to account for that.
uint UnpackWaterBodyRenderDataIndex(float Packed)
{
const uint PackedUI = saturate(Packed) * 1024.0f;
return PackedUI & 0x7Fu;
}
void Main(
in float4 Position : SV_Position,
out float4 OutPackedNode : SV_Target0,
out float4 OutWaterZBounds : SV_Target1)
{
// Downsample super- and multisampled input texture. The data is set up to correctly resolve with MAX blending.
float NonRiverWaterBodyPacked = 0.0f;
float RiverWaterBodyPacked = 0.0f;
float MinZ = 0.0f;
float MaxZ = 0.0f;
for (int y = 0; y < SuperSamplingFactor; ++y)
{
for (int x = 0; x < SuperSamplingFactor; ++x)
{
#if NUM_MSAA_SAMPLES > 1
for (int SampleIdx = 0; SampleIdx < NUM_MSAA_SAMPLES; ++SampleIdx)
#endif
{
const int2 Coord = int2(Position.xy) * SuperSamplingFactor + int2(x, y);
#if NUM_MSAA_SAMPLES > 1
const float2 WaterBodySample = WaterBodyRasterTextureMS.Load(Coord, SampleIdx).xy;
const float2 ZBoundsSample = ZBoundsRasterTextureMS.Load(Coord, SampleIdx).xy;
#else
const float2 WaterBodySample = WaterBodyRasterTexture.Load(int3(Coord, 0)).xy;
const float2 ZBoundsSample = ZBoundsRasterTexture.Load(int3(Coord, 0)).xy;
#endif
NonRiverWaterBodyPacked = max(NonRiverWaterBodyPacked, WaterBodySample.x);
RiverWaterBodyPacked = max(RiverWaterBodyPacked, WaterBodySample.y);
MinZ = max(MinZ, ZBoundsSample.x); // MinZ is actually 1 - MinZ
MaxZ = max(MaxZ, ZBoundsSample.y);
}
}
}
const uint NonRiverWaterBodyRenderDataIndex = UnpackWaterBodyRenderDataIndex(NonRiverWaterBodyPacked);
const uint RiverWaterBodyRenderDataIndex = UnpackWaterBodyRenderDataIndex(RiverWaterBodyPacked);
const bool bIsNonRiverWaterBodyValid = NonRiverWaterBodyRenderDataIndex > 0;
const bool bIsRiverWaterBodyValid = RiverWaterBodyRenderDataIndex > 0;
const bool bIsAnyValid = bIsRiverWaterBodyValid || bIsNonRiverWaterBodyValid;
FWaterQuadTreeNode ResultNode;
ResultNode.WaterBodyRenderDataIndex = 0;
ResultNode.TransitionWaterBodyRenderDataIndex = 0;
ResultNode.bHasCompleteSubtree = bIsAnyValid;
ResultNode.bIsSubtreeSameWaterBody = bIsAnyValid;
// Rivers always have priority and can have transitions to non-river water bodies
if (bIsRiverWaterBodyValid)
{
ResultNode.WaterBodyRenderDataIndex = RiverWaterBodyRenderDataIndex;
ResultNode.TransitionWaterBodyRenderDataIndex = bIsNonRiverWaterBodyValid ? NonRiverWaterBodyRenderDataIndex : 0;
}
else if (bIsNonRiverWaterBodyValid)
{
ResultNode.WaterBodyRenderDataIndex = NonRiverWaterBodyRenderDataIndex;
ResultNode.TransitionWaterBodyRenderDataIndex = 0;
}
OutPackedNode = WaterQuadTreePackNodeRGBA8(ResultNode);
const float2 MinMaxZ = bIsAnyValid ? saturate(float2((1.0f - MinZ) - ZBoundsPadding, MaxZ + ZBoundsPadding)) : float2(0.0f, 0.0f);
float SurfaceBaseHeight = 0.0f;
// In case of rivers transitioning to other water bodies, we want to use the SurfaceBaseHeight of that other water body
if (bIsNonRiverWaterBodyValid)
{
const FWaterBodyRenderData WBRenderData = WaterBodyRenderData[NonRiverWaterBodyRenderDataIndex];
SurfaceBaseHeight = WBRenderData.SurfaceBaseHeight * RcpCaptureDepthRange;
}
else if (bIsRiverWaterBodyValid)
{
SurfaceBaseHeight = MinMaxZ.y;
}
OutWaterZBounds = float4(MinMaxZ, SurfaceBaseHeight, 1.0f);
}