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

296 lines
12 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
// ----------------------------------------------------------------------------------
#if defined (__INTELLISENSE__)
// Uncomment the appropriate define for enabling syntax highlighting with HLSL Tools for Visual Studio :
//#define MERGE_EDIT_LAYERS 1
//#define FINALIZE_WEIGHTMAP 1
//#define GENERATE_MIPS 1
#endif // defined (__INTELLISENSE__)
// ----------------------------------------------------------------------------------
#if MERGE_EDIT_LAYERS
// MergeEditLayers inputs/outputs :
/** EEditLayerWeightmapBlendMode enum */
#define EEDITLAYERWEIGHTMAPBLENDMODE_ADDITIVE 0
#define EEDITLAYERWEIGHTMAPBLENDMODE_SUBSTRACTIVE 1
// Contains the information related to a given edit layers and how it should merge into the final weightmap
struct FEditLayerWeightmapMergeInfo
{
uint SourceWeightmapTextureIndex; // The index in InPackedWeightmaps of the texture to read from for this layer
uint SourceWeightmapTextureChannel; // The channel of the texture to read from for this layer
uint BlendMode; // See EEditLayerWeightmapBlendMode
float Alpha; // Alpha value to be used in the blend
};
uint InNumEditLayers; // Number of layers to merge into the destination texture
uint InStartIndexInEditLayersMergeInfos; // Start index for edit layers merge infos in the big buffer InEditLayersMergeInfos
Texture2DArray<float4> InPackedWeightmaps; // Source -packed- RGBA weightmaps
StructuredBuffer<FEditLayerWeightmapMergeInfo> InEditLayersMergeInfos; // Describes how to merge each individual edit layer (effective size == sum of InNumEditLayers of all passes)
#endif // MERGE_EDIT_LAYERS
#if FINALIZE_WEIGHTMAP
/** EWeightmapPaintLayerFlags enum */
#define EWEIGHTMAPPAINTLAYERFLAGS_ISVISIBILITYLAYER (1 << 0)
#define EWEIGHTMAPPAINTLAYERFLAGS_ISWEIGHTBLENDED (1 << 1)
// Contains the information related to a given edit layers and how it should merge into the final weightmap
struct FEditLayerWeightmapPaintLayerInfo
{
uint Flags; // See EWeightmapPaintLayerFlags
};
// FinalizeWeightmap inputs/outputs :
uint InValidTextureChannelsMask; // 4 bits-wide bitmask that indicates which of the 4 channels need to be processed
// Contains (per channel : .r for the first channel, .g for the 2nd, etc.) the index of the paint layer being processed for this channel. Note : this is an index for the channel's InPerChannelPaintLayerWeightmaps
// (which is a subset of all paint layers), not InPaintLayerInfos (i.e. all paint layers)
uint4 InPerChannelPaintLayerIndexInWeightmaps;
// Contains (per channel : .r for the first channel, .g for the 2nd, etc.) the start index for addressing the indices of actual paint layers contained in the channel's InPerChannelPaintLayerWeightmaps
uint4 InPerChannelStartPaintLayerIndex;
// Contains (per channel : .r for the first channel, .g for the 2nd, etc.) the number of actual paint layers contained in the channel's InPerChannelPaintLayerWeightmaps
uint4 InPerChannelNumPaintLayers;
// 4 input texture arrays (R8) (one per channel) we're about to pack on the output weightmap (RGBA), each one contains a list of all paint layers (after having been edit layer-merged) for a given component,
// since we need to weight-blend some of them. For each channel, NumElements = InPerChannelNumPaintLayers[ChannelIndex]
Texture2DArray<float> InPerChannelPaintLayerWeightmaps_0;
Texture2DArray<float> InPerChannelPaintLayerWeightmaps_1;
Texture2DArray<float> InPerChannelPaintLayerWeightmaps_2;
Texture2DArray<float> InPerChannelPaintLayerWeightmaps_3;
// Input uint buffers that contains all the indices of the paint layer in InPaintLayerInfos. This buffer is shared between all texture resolve passes and is therefore adressed using
// InPerChannelStartPaintLayerIndex and InPerChannelNumPaintLayers (one entry per channel each)
StructuredBuffer<uint> InPaintLayerInfoIndices;
// Array that contains per-paint layer information (e.g. paint layer's blend mode)
StructuredBuffer<FEditLayerWeightmapPaintLayerInfo> InPaintLayerInfos;
#endif // FINALIZE_WEIGHTMAP
#if GENERATE_MIPS
// GenerateMips inputs/outputs :
uint2 InCurrentMipSize; // Size of the texture mip currently being generated
uint InNumSubsections; // Number of sub-sections for this landscape (1 or 2)
Texture2D<float4> InSourceWeightmap;
#endif // GENERATE_MIPS
// ----------------------------------------------------------------------------------
// Util functions :
#if MERGE_EDIT_LAYERS
// For a given edit layer, samples the appropriate texture in the input texture array and returns the appropriate channel, attenuated by the layer's alpha value :
float SampleEditLayerWeightmap(uint2 InSampleCoordinates, uint InIndex)
{
FEditLayerWeightmapMergeInfo LayerMergeInfo = InEditLayersMergeInfos[InStartIndexInEditLayersMergeInfos + InIndex];
int4 LoadCoordinates = int4(InSampleCoordinates, LayerMergeInfo.SourceWeightmapTextureIndex, 0); // xy = relative coordinates, z = index in texture array, w = mip level
float4 Packed = InPackedWeightmaps.Load(LoadCoordinates);
float Weight = Packed[LayerMergeInfo.SourceWeightmapTextureChannel];
return Weight * LayerMergeInfo.Alpha;
}
#endif // #if MERGE_EDIT_LAYERS
#if FINALIZE_WEIGHTMAP
float LoadPaintLayerWeightmapForChannel(uint InChannelIndex, uint InPaintLayerIndex, uint2 InSampleCoordinates)
{
int4 LoadCoordinates = int4(InSampleCoordinates, InPaintLayerIndex, 0); // xy = coordinates, z = index in texture array, w = mip level
// We have to use a switch case because we cannot sample a static array of textures without a literal expression:
switch (InChannelIndex)
{
case 0: return InPerChannelPaintLayerWeightmaps_0.Load(LoadCoordinates);
case 1: return InPerChannelPaintLayerWeightmaps_1.Load(LoadCoordinates);
case 2: return InPerChannelPaintLayerWeightmaps_2.Load(LoadCoordinates);
default: return InPerChannelPaintLayerWeightmaps_3.Load(LoadCoordinates);
}
}
uint LoadPaintLayerInfoIndexForChannel(uint InChannelIndex, uint InPaintLayerIndex)
{
uint PaintLayerInfoIndexInBigBuffer = InPerChannelStartPaintLayerIndex[InChannelIndex] + InPaintLayerIndex;
return InPaintLayerInfoIndices[PaintLayerInfoIndexInBigBuffer];
}
bool IsWeightBlendedPaintLayer(FEditLayerWeightmapPaintLayerInfo InPaintLayerInfo)
{
// For the visibility layer, deactivate weight blending altogether :
return (((InPaintLayerInfo.Flags & EWEIGHTMAPPAINTLAYERFLAGS_ISVISIBILITYLAYER) == 0)
&& (InPaintLayerInfo.Flags & EWEIGHTMAPPAINTLAYERFLAGS_ISWEIGHTBLENDED));
}
float FinalizeWeightmapChannel(uint2 InSampleCoordinates, uint InChannelIndex)
{
float Result = 0.0f;
if (InValidTextureChannelsMask & (1u << InChannelIndex))
{
uint PaintLayerIndexInWeightmaps = InPerChannelPaintLayerIndexInWeightmaps[InChannelIndex];
uint NumPaintLayers = InPerChannelNumPaintLayers[InChannelIndex];
uint ActivePaintLayerInfoIndex = LoadPaintLayerInfoIndexForChannel(InChannelIndex, PaintLayerIndexInWeightmaps);
FEditLayerWeightmapPaintLayerInfo ActivePaintLayerInfo = InPaintLayerInfos[ActivePaintLayerInfoIndex];
if (!IsWeightBlendedPaintLayer(ActivePaintLayerInfo))
{
return LoadPaintLayerWeightmapForChannel(InChannelIndex, PaintLayerIndexInWeightmaps, InSampleCoordinates);
}
float ActivePaintLayerWeight = 0.0f;
float BlendedWeightsSum = 0.0f;
LOOP
for (int i = 0; i < NumPaintLayers; ++i)
{
bool bIsOutputPaintLayer = (i == PaintLayerIndexInWeightmaps);
uint PaintLayerInfoIndex = LoadPaintLayerInfoIndexForChannel(InChannelIndex, i);
FEditLayerWeightmapPaintLayerInfo PaintLayerInfo = InPaintLayerInfos[PaintLayerInfoIndex];
// Only weight blended (and non-visibility) paint layers participate to weight blending :
if (IsWeightBlendedPaintLayer(PaintLayerInfo))
{
float Weight = LoadPaintLayerWeightmapForChannel(InChannelIndex, i, InSampleCoordinates);
if (bIsOutputPaintLayer)
{
ActivePaintLayerWeight = Weight;
}
BlendedWeightsSum += Weight;
}
}
Result = (BlendedWeightsSum > 0.0f) ? saturate(ActivePaintLayerWeight / BlendedWeightsSum) : ActivePaintLayerWeight;
}
return Result;
}
#endif // FINALIZE_WEIGHTMAP
// ----------------------------------------------------------------------------------
// Pixel shaders :
#if MERGE_EDIT_LAYERS
void MergeEditLayers(in float4 SVPos : SV_POSITION, out float OutColor : SV_Target0)
{
uint2 TextureCoordinates = floor(SVPos.xy);
// Take the base weight straight from the first layer :
float Weight = SampleEditLayerWeightmap(TextureCoordinates, 0);
// Then merge the subsequent layers according to their merge info :
LOOP
for (uint i = 1; i < InNumEditLayers; ++i)
{
FEditLayerWeightmapMergeInfo LayerMergeInfo = InEditLayersMergeInfos[InStartIndexInEditLayersMergeInfos + i];
float LayerWeight = SampleEditLayerWeightmap(TextureCoordinates, i);
if (LayerMergeInfo.BlendMode == EEDITLAYERWEIGHTMAPBLENDMODE_ADDITIVE)
{
Weight += LayerWeight;
}
else if (LayerMergeInfo.BlendMode == EEDITLAYERWEIGHTMAPBLENDMODE_SUBSTRACTIVE)
{
Weight -= LayerWeight;
}
}
OutColor = Weight;
}
#endif // MERGE_EDIT_LAYERS
// ----------------------------------------------------------------------------------
#if FINALIZE_WEIGHTMAP
void FinalizeWeightmap(in float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0)
{
uint2 TextureCoordinates = floor(SVPos.xy);
OutColor = 0.0f;
UNROLL
for (uint i = 0; i < 4; ++i)
{
OutColor[i] = FinalizeWeightmapChannel(TextureCoordinates, i);
}
}
#endif // FINALIZE_WEIGHTMAP
// ----------------------------------------------------------------------------------
#if GENERATE_MIPS
void GenerateMips(in float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0)
{
uint2 TextureCoordinates = floor(SVPos.xy);
float4 SourceSamples[4] =
{
InSourceWeightmap.Load(int3(2 * TextureCoordinates + int2(+0, +0), 0)),
InSourceWeightmap.Load(int3(2 * TextureCoordinates + int2(+1, +0), 0)),
InSourceWeightmap.Load(int3(2 * TextureCoordinates + int2(+0, +1), 0)),
InSourceWeightmap.Load(int3(2 * TextureCoordinates + int2(+1, +1), 0)),
};
// Because the borders of each landscape components are shared between neighbors, we must ensure that the parent mip's inner row/column of pixels don't contribute :
// 9 possible cases (only the samples marked with a * must be kept):
// bIsMinBorder.x = true
// | bIsMaxBorder.x = true
// | |
// v v
// +-------+ +-------+ +-------+
// | * : | | * : * | | : * |
// | - + - |...| - + - |...| - + - | <-- bIsMinBorder.y = true
// | : | | : | | : |
// +-------+ +-------+ +-------+
// ... ... ...
// +-------+ +-------+ +-------+
// | * : | | * : * | | : * |
// | - + - |...| - + - |...| - + - |
// | * : | | * : * | | : * |
// +-------+ +-------+ +-------+
// ... ... ...
// +-------+ +-------+ +-------+
// | : | | : | | : |
// | - + - |...| - + - |...| - + - |
// | * : | | * : * | | : * | <-- bIsMaxBorder.y = true
// +-------+ +-------+ +-------+
bool bIsLastMip = all(InCurrentMipSize == 1);
bool2 bIsMinBorder = (TextureCoordinates == 0);
bool2 bIsMaxBorder = (TextureCoordinates == (InCurrentMipSize - 1));
// If we use subsections, we'll have an additional column/row of min border and another of max border next to them
if (InNumSubsections == 2)
{
uint2 SubsectionSize = InCurrentMipSize / 2;
bIsMinBorder = or(bIsMinBorder, TextureCoordinates == SubsectionSize);
bIsMaxBorder = or(bIsMaxBorder, TextureCoordinates == SubsectionSize - 1);
}
float SampleWeights[4] =
{
// On the last mip, it's ok to keep all 4 samples : all neighbors components share them :
((bIsMaxBorder.x || bIsMaxBorder.y) && !bIsLastMip) ? 0.0f : 1.0f,
((bIsMinBorder.x || bIsMaxBorder.y) && !bIsLastMip) ? 0.0f : 1.0f,
((bIsMaxBorder.x || bIsMinBorder.y) && !bIsLastMip) ? 0.0f : 1.0f,
((bIsMinBorder.x || bIsMinBorder.y) && !bIsLastMip) ? 0.0f : 1.0f,
};
float TotalSampleWeight = 0.0f;
OutColor = 0.0f;
UNROLL
for (int i = 0; i < 4; ++i)
{
OutColor += SourceSamples[i] * SampleWeights[i];
TotalSampleWeight += SampleWeights[i];
}
OutColor /= TotalSampleWeight;
}
#endif // GENERATE_MIPS