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

514 lines
24 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "LandscapeCommon.ush"
// ----------------------------------------------------------------------------------
#if defined (__INTELLISENSE__)
// Uncomment the appropriate define for enabling syntax highlighting with HLSL Tools for Visual Studio :
//#define MERGE_EDIT_LAYERS 1
//#define STITCH_HEIGHTMAP 1
//#define FINALIZE_HEIGHTMAP 1
//#define GENERATE_MIPS 1
#endif // defined (__INTELLISENSE__)
#define INDEX_NONE -1
// ----------------------------------------------------------------------------------
// All shaders inputs/outputs
uint InNumSubsections; // Number of sub-sections for this landscape (1 or 2)
#if MERGE_EDIT_LAYERS
// MergeEditLayers inputs/outputs :
/** EEditLayerHeightmapBlendMode enum */
#define EEDITLAYERHEIGHTMAPBLENDMODE_ADDITIVE 0
#define EEDITLAYERHEIGHTMAPBLENDMODE_ALPHABLEND 1
// Contains the information related to a given edit layers and how it should merge into the final heightmap
struct FEditLayerHeightmapMergeInfo
{
int4 TextureSubregion; // Subregion of the source (edit layer) texture to use
uint BlendMode; // See EEditLayerHeightmapBlendMode
float Alpha; // Alpha value to be used in the blend
float Padding0; // Align to next float4
float Padding1;
};
uint InNumEditLayers; // Number of layers to merge into the destination texture
Texture2DArray<float4> InEditLayersTextures; // Source texture for each individual edit layer (effective size == InNumEditLayers)
StructuredBuffer<FEditLayerHeightmapMergeInfo> InEditLayersMergeInfos; // Describes how to merge each individual edit layer (effective size == InNumEditLayers)
#endif // MERGE_EDIT_LAYERS
#if STITCH_HEIGHTMAP || FINALIZE_HEIGHTMAP
// StitchHeightmap/FinalizeHeightmaps shared inputs/outputs :
uint2 InSourceTextureSize; // Texture size we read from (XY texture size of InSourceHeightmaps)
// Texture array containing all the source heightmaps that can participate to the stitching operation (i.e. the component's heightmap, its 8 neighbors and potentially many other component
// that are needed for this batch).
Texture2DArray<float2> InSourceHeightmaps;
// The index of the input heightmap to process and its 8 neighbors arranged in a 3x3 array (linear : starting from the top-left neighbor [index 0] and ending with the bottom-right [index 8])
// (index relative to InSourceHeightmaps)
DECLARE_SCALAR_ARRAY(uint, InNeighborHeightmapIndices, 9);
#endif // STITCH_HEIGHTMAP || FINALIZE_HEIGHTMAP
#if FINALIZE_HEIGHTMAP
// FinalizeHeightmap inputs/outputs :
uint4 InDestinationTextureSubregion; // Subregion of the destination texture to write to
float3 InLandscapeGridScale; // x == LS Actor DrawScale.X, y == LS Actor DrawScale.y, z == LS Actor DrawScale.z / 128.0f (ZSCALE)
#endif // FINALIZE_HEIGHTMAP
#if GENERATE_MIPS
// GenerateMips inputs/outputs :
uint2 InCurrentMipSubregionSize; // Size of the the texture subregion that corresponds to a single landscape component (texture sharing)
Texture2D<float4> InSourceHeightmap;
#endif // GENERATE_MIPS
// ----------------------------------------------------------------------------------
// Util functions :
float ExtractAlpha(float2 InPackedAlpha)
{
int AlphaWithFlags = ((int) round(InPackedAlpha.r * 255.0f) << 8) | (int) round(InPackedAlpha.g * 255.0f);
int Alpha = AlphaWithFlags & 0xFFFC; // Last 2 bits are used to store lower/raise flags
return float(Alpha) / 65532.0f; // 0xFFFC in float
}
#if MERGE_EDIT_LAYERS
float4 SampleEditLayerHeightmap(uint2 InRelativeTextureCoordinates, uint InIndex)
{
FEditLayerHeightmapMergeInfo LayerMergeInfo = InEditLayersMergeInfos[InIndex];
int4 LoadCoordinates = int4(InRelativeTextureCoordinates, InIndex, 0); // xy = relative coordinates, z = index in texture array, w = mip level
LoadCoordinates.xy += LayerMergeInfo.TextureSubregion.xy;
return InEditLayersTextures.Load(LoadCoordinates);
}
#endif // #if MERGE_EDIT_LAYERS
#if STITCH_HEIGHTMAP || FINALIZE_HEIGHTMAP
// InOffset between (-1,-1) and (1,1) : output between 0 (TL) and 8 (BR) :
uint NeighborOffsetToNeighborLinearIndex(int2 InOffset)
{
int2 PositiveOffset = InOffset + 1;
return PositiveOffset.y * 3 + PositiveOffset.x;
}
// InLinearIndex between 0 (TL) and 8 (BR) : output between between (-1,-1) and (1,1) :
int2 NeighborLinearIndexToOffset(uint InLinearIndex)
{
int2 NeighborOffsets[9] = { int2(-1, -1), int2(+0, -1), int2(+1, -1), int2(-1, +0), int2(+0, +0), int2(+1, +0), int2(-1, +1), int2(+0, +1), int2(+1, +1) };
return NeighborOffsets[InLinearIndex];
}
// Load the appropriate heightmap at the given location and returns the position and packed height:
// (handles the edge cases when we need to sample from neighboring heightmaps) :
// bSkipDuplicateRowColumn allows to skip the first/last row/column on the borders, since they're duplicates of the center texture :
// Returns the unpacked height (.x) and whether the sample is valid or not (.y = 1 if valid, 0 otherwise)
float2 LoadNeighborHeightmapUnpackedHeight(bool2 bInIsMinComponentBorder, bool2 bInIsMaxComponentBorder, uint2 InRelativeTextureCoordinates, int2 InNeighborOffset, bool bSkipDuplicateRowColumn)
{
// Which of the 9 input textures should be sampled (as a texture index offset between (-1, -1) and (1, 1)):
// In the default case (not on a texture border), we sample only the center texture so no texture offset needed:
int2 TextureIndexOffset = InNeighborOffset;
TextureIndexOffset.x *= (!bInIsMinComponentBorder.x && (TextureIndexOffset.x == -1)) ? 0 : 1;
TextureIndexOffset.y *= (!bInIsMinComponentBorder.y && (TextureIndexOffset.y == -1)) ? 0 : 1;
TextureIndexOffset.x *= (!bInIsMaxComponentBorder.x && (TextureIndexOffset.x == +1)) ? 0 : 1;
TextureIndexOffset.y *= (!bInIsMaxComponentBorder.y && (TextureIndexOffset.y == +1)) ? 0 : 1;
// Retrieve the linear index between 0 (TL) and 8 (BR) that corresponds to this neighbor:
uint NeighborLinearIndex = NeighborOffsetToNeighborLinearIndex(TextureIndexOffset);
uint NeighborHeightmapIndex = GET_SCALAR_ARRAY_ELEMENT(InNeighborHeightmapIndices, NeighborLinearIndex);
bool bIsValidNeighbor = (NeighborHeightmapIndex != INDEX_NONE);
float Height = 0.0f;
if (bIsValidNeighbor)
{
int2 SampleCoordinates = InRelativeTextureCoordinates + InNeighborOffset;
if (TextureIndexOffset.x == -1)
{
// If we use the textures from the left, we need to skip the rightmost column as it's a duplicate of our the center texture's leftmost column :
SampleCoordinates.x = InSourceTextureSize.x - 1 - (bSkipDuplicateRowColumn ? 1 : 0);
}
if (TextureIndexOffset.x == +1)
{
// If we use the textures from the right, we need to skip the leftmost column as it's a duplicate of our the center texture's rightmost column :
SampleCoordinates.x = bSkipDuplicateRowColumn ? +1 : 0;
}
if (TextureIndexOffset.y == -1)
{
// If we use the textures from the top, we need to skip the downmost row as it's a duplicate of our the center texture's upmost row :
SampleCoordinates.y = InSourceTextureSize.y - 1 - (bSkipDuplicateRowColumn ? 1 : 0);
}
if (TextureIndexOffset.y == +1)
{
// If we use the textures from the bottom, we need to skip the upmost row as it's a duplicate of our the center texture's downmost row :
SampleCoordinates.y = bSkipDuplicateRowColumn ? +1 : 0;
}
// Handle the sub-section case, where there's a duplicate row/column of vertices in the middle :
if (InNumSubsections == 2)
{
uint2 SubSectionSize = InSourceTextureSize / 2;
// If we need the neighbor on the right and we're on the left (duplicate) column of the subsection, skip a column to the right :
if ((InRelativeTextureCoordinates.x == SubSectionSize.x - 1) && (InNeighborOffset.x > 0))
{
SampleCoordinates.x += 1;
}
// If we need the neighbor on the left and we're on the right (duplicate) column of the subsection, skip a column to the left :
else if ((InRelativeTextureCoordinates.x == SubSectionSize.x) && (InNeighborOffset.x < 0))
{
SampleCoordinates.x -= 1;
}
// If we need the neighbor on the bottom and we're on the top (duplicate) row of the subsection, skip a row to the bottom :
if ((InRelativeTextureCoordinates.y == SubSectionSize.y - 1) && (InNeighborOffset.y > 0))
{
SampleCoordinates.y += 1;
}
// If we need the neighbor on the top and we're on the bottom (duplicate) row of the subsection, skip a row to the top :
else if ((InRelativeTextureCoordinates.y == SubSectionSize.y) && (InNeighborOffset.y < 0))
{
SampleCoordinates.y -= 1;
}
}
int4 LoadCoordinates = int4(SampleCoordinates, NeighborHeightmapIndex, 0); // xy = relative coordinates, z = index in texture array, w = mip level
float2 PackedHeight = InSourceHeightmaps.Load(LoadCoordinates);
Height = UnpackHeight(PackedHeight);
}
return float2(Height, bIsValidNeighbor ? 1.0f : 0.0f);
}
// Load from the main heightmap and returns the unpacked height (.x) and whether the sample is valid or not (.y = 1 since the main heightmap is always valid):
float2 LoadHeightmapUnpackedHeight(uint2 InRelativeTextureCoordinates)
{
// Load from the main input/output heightmap (linear neighbor index 4 == CC == center point) :
uint HeightmapIndex = GET_SCALAR_ARRAY_ELEMENT(InNeighborHeightmapIndices, 4);
int4 LoadCoordinates = int4(InRelativeTextureCoordinates, HeightmapIndex, 0); // xy = relative coordinates, z = index in texture array, w = mip level
float2 PackedHeight = InSourceHeightmaps.Load(LoadCoordinates);
return float2(UnpackHeight(PackedHeight), 1.0f);
}
#endif // STITCH_HEIGHTMAP || FINALIZE_HEIGHTMAP
#if FINALIZE_HEIGHTMAP
float ScaleHeight(float InHeight)
{
return (InHeight - 32768.0f) * InLandscapeGridScale.z * TERRAIN_ZSCALE;
}
float UnscaleHeight(float InScaledHeight)
{
return (InScaledHeight / InLandscapeGridScale.z / TERRAIN_ZSCALE) + 32768.0f;
}
// Load from the main heightmap and returns the position (.xyz) and whether the sample is valid or not (.w = 1 because the main heightmap is always valid)
float4 LoadHeightmapScaledPosition(uint2 InRelativeTextureCoordinates)
{
float Height = LoadHeightmapUnpackedHeight(InRelativeTextureCoordinates).x;
return float4(InRelativeTextureCoordinates * InLandscapeGridScale.xy, ScaleHeight(Height), 1.0f);
}
// Returns the position (.xyz) and whether the sample is valid or not (.w = 1 if valid, 0 otherwise)
float4 LoadNeighborHeightmapScaledPosition(bool2 bInIsMinComponentBorder, bool2 bInIsMaxComponentBorder, uint2 InRelativeTextureCoordinates, int2 InNeighborOffset, bool bSkipDuplicateRowColumn)
{
float2 NeighborHeight = LoadNeighborHeightmapUnpackedHeight(bInIsMinComponentBorder, bInIsMaxComponentBorder, InRelativeTextureCoordinates, InNeighborOffset, bSkipDuplicateRowColumn);
return float4(((int2) InRelativeTextureCoordinates + InNeighborOffset) * InLandscapeGridScale.xy, ScaleHeight(NeighborHeight.x), NeighborHeight.y);
}
#endif // FINALIZE_HEIGHTMAP
// ----------------------------------------------------------------------------------
// Pixel shaders :
#if MERGE_EDIT_LAYERS
// Take all source edit layers heightmap regions and merge them all into a (almost) final heightmap
// Note : reads from a potentially shared heightmaps (i.e. from subregions of the heightmaps) and writes to R8G8 non-shared scratch texture
void MergeEditLayers(in float4 SVPos : SV_POSITION, out float2 OutColor : SV_Target0)
{
uint2 RelativeTextureCoordinates = floor(SVPos.xy);
// Take the base height straight from the first layer :
float4 EditLayerSample = SampleEditLayerHeightmap(RelativeTextureCoordinates, 0);
float Height = UnpackHeight(EditLayerSample.xy) - 32768.0f;
// Then merge the subsequent layers according to their merge info :
LOOP
for (uint i = 1; i < InNumEditLayers; ++i)
{
FEditLayerHeightmapMergeInfo LayerMergeInfo = InEditLayersMergeInfos[i];
EditLayerSample = SampleEditLayerHeightmap(RelativeTextureCoordinates, i);
float LayerHeight = UnpackHeight(EditLayerSample.xy) - 32768.0f;
LayerHeight *= LayerMergeInfo.Alpha;
if (LayerMergeInfo.BlendMode == EEDITLAYERHEIGHTMAPBLENDMODE_ADDITIVE)
{
Height += LayerHeight;
}
else if (LayerMergeInfo.BlendMode == EEDITLAYERHEIGHTMAPBLENDMODE_ALPHABLEND)
{
float LayerHeightAlphaBlend = ExtractAlpha(EditLayerSample.ba);
float NewHeight = LayerHeight + (LayerHeightAlphaBlend * Height);
uint RaiseLower = floor(EditLayerSample.a * 255.f + 0.5f);
bool bLowerTerrain = (RaiseLower & 1);
bool bRaiseTerrain = (RaiseLower & 2);
if ((bRaiseTerrain && NewHeight > Height) || (bLowerTerrain && NewHeight < Height))
{
Height = NewHeight;
}
else
{
Height = Height;
}
}
}
Height = clamp(Height, -32768.0, 32767.0);
Height += 32768.0f;
OutColor = PackHeight(Height);
}
#endif // MERGE_EDIT_LAYERS
// ----------------------------------------------------------------------------------
#if STITCH_HEIGHTMAP
void StitchHeightmap(in float4 SVPos : SV_POSITION, out float2 OutColor : SV_Target0)
{
uint2 RelativeTextureCoordinates = floor(SVPos.xy);
bool2 bIsMinComponentBorder = (RelativeTextureCoordinates == 0);
bool2 bIsMaxComponentBorder = (RelativeTextureCoordinates == (InSourceTextureSize - 1));
// On borders, average the neighbors' unpacked height so that in order to repair potential discrepancies between 2 neighboring rows/columns from different textures (effectively stitching them together) :
// They should mostly be identical, but because it's possible that one neighbor component is not loaded while one is being recomputed, we might end up with different values there :
float2 TL = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(-1, -1), /*bSkipDuplicateRowColumn = */false);
float2 TT = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+0, -1), /*bSkipDuplicateRowColumn = */false);
float2 TR = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+1, -1), /*bSkipDuplicateRowColumn = */false);
float2 LL = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(-1, +0), /*bSkipDuplicateRowColumn = */false);
float2 CC = LoadHeightmapUnpackedHeight(RelativeTextureCoordinates);
float2 RR = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+1, +0), /*bSkipDuplicateRowColumn = */false);
float2 BL = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(-1, +1), /*bSkipDuplicateRowColumn = */false);
float2 BB = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+0, +1), /*bSkipDuplicateRowColumn = */false);
float2 BR = LoadNeighborHeightmapUnpackedHeight(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+1, +1), /*bSkipDuplicateRowColumn = */false);
// .x = scaled height, .y = whether the sample is valid or not :
float2 ValidScaledHeights[9] = { TL, TT, TR, LL, CC, RR, BL, BB, BR };
// 9 possible cases : only pixels on the border (A..H) need stitching
// bIsMinComponentBorder.x = true
// | bIsMaxComponentBorder.x = true
// | |
// v v
// +---+ +---+ +---+
// | A |...| B |...| C | <-- bIsMinComponentBorder.y = true
// +---+ +---+ +---+
// ... ... ...
// +---+ +---+ +---+
// | H |...| I |...| D |
// +---+ +---+ +---+
// ... ... ...
// +---+ +---+ +---+
// | G |...| F |...| E | <-- bIsMaxComponentBorder.y = true
// +---+ +---+ +---+
// Truth table (x == True) that indicates which neigbor sample (TL, TT, ...) is needed for each of the 9 possible cases (whether the pixel is on a border or not) :
// | A | B | C | D | E | F | G | H | I |
// +---+---+---+---+---+---+---+---+---+
// TL | x | | | | | | | | |
// TT | x | x | x | | | | | | |
// TR | | | x | | | | | | |
// LL | x | | | | | | x | x | |
// CC | x | x | x | x | x | x | x | x | x |
// RR | | | x | x | x | | | | |
// BL | | | | | | | x | | |
// BB | | | | | x | x | x | | |
// BR | | | | | x | | | | |
// +---+---+---+---+---+---+---+---+---+
// bIsMinComponentBorder.x | x | | | | | | x | x | |
// bIsMinComponentBorder.y | x | x | x | | | | | | |
// bIsMaxComponentBorder.x | | | x | x | x | | | | |
// bIsMaxComponentBorder.y | | | | | x | x | x | | |
// +---+---+---+---+---+---+---+---+---+
bool bA = bIsMinComponentBorder.x && bIsMinComponentBorder.y;
bool bB = bIsMinComponentBorder.y && !bIsMinComponentBorder.x && !bIsMaxComponentBorder.x;
bool bC = bIsMaxComponentBorder.x && bIsMinComponentBorder.y;
bool bD = bIsMaxComponentBorder.x && !bIsMinComponentBorder.y && !bIsMaxComponentBorder.y;
bool bE = bIsMaxComponentBorder.x && bIsMaxComponentBorder.y;
bool bF = bIsMaxComponentBorder.y && !bIsMinComponentBorder.x && !bIsMaxComponentBorder.x;
bool bG = bIsMinComponentBorder.x && bIsMaxComponentBorder.y;
bool bH = bIsMinComponentBorder.x && !bIsMinComponentBorder.y && !bIsMaxComponentBorder.y;
// Describes whether to consider each neighbor sample depending on whether we're a pixel on the border or not :
bool bValidHeights[9] =
{
/* TL = */bA,
/* TT = */bA || bB || bC,
/* TR = */bC,
/* LL = */bA || bG || bH,
/* CC = */true,
/* RR = */bC || bD || bE,
/* BL = */bG,
/* BB = */bE || bF || bG,
/* BR = */bE,
};
float FinalHeight = 0.0;
float WeightSum = 0.0f;
UNROLL
for (int Index = 0; Index < 9; ++Index)
{
float Height = ValidScaledHeights[Index].x;
float Weight = bValidHeights[Index] ? 1.0f : 0.0f;
Weight *= ValidScaledHeights[Index].y; // .y of ValidScaledHeights indicates whether the sample is valid or not (i.e. whether there's a neighboring heighmap or not)
FinalHeight += Height * Weight;
WeightSum += Weight;
}
FinalHeight /= WeightSum;
OutColor = PackHeight(FinalHeight);
}
#endif // STITCH_HEIGHTMAP
// ----------------------------------------------------------------------------------
#if FINALIZE_HEIGHTMAP
void FinalizeHeightmap(in float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0)
{
uint2 DestinationTextureSize = InDestinationTextureSubregion.zw - InDestinationTextureSubregion.xy;
uint2 RelativeTextureCoordinates = floor(SVPos.xy) - InDestinationTextureSubregion.xy;
bool2 bIsMinComponentBorder = (RelativeTextureCoordinates == 0);
bool2 bIsMaxComponentBorder = (RelativeTextureCoordinates == (DestinationTextureSize - 1));
// 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
float4 CC = LoadHeightmapScaledPosition(RelativeTextureCoordinates);
float4 TL = LoadNeighborHeightmapScaledPosition(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(-1, -1), /*bSkipDuplicateRowColumn = */true);
float4 TT = LoadNeighborHeightmapScaledPosition(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+0, -1), /*bSkipDuplicateRowColumn = */true);
float4 LL = LoadNeighborHeightmapScaledPosition(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(-1, +0), /*bSkipDuplicateRowColumn = */true);
float4 RR = LoadNeighborHeightmapScaledPosition(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+1, +0), /*bSkipDuplicateRowColumn = */true);
float4 BR = LoadNeighborHeightmapScaledPosition(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+1, +1), /*bSkipDuplicateRowColumn = */true);
float4 BB = LoadNeighborHeightmapScaledPosition(bIsMinComponentBorder, bIsMaxComponentBorder, RelativeTextureCoordinates, /*NeighborOffset = */int2(+0, +1), /*bSkipDuplicateRowColumn = */true);
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);
float3 Normal = normalize(N0 + N1 + N2 + N3 + N4 + N5);
// Scale back to be 0-1 normal
Normal = 0.5f * Normal + 0.5f;
float FinalHeight = UnscaleHeight(CC.z);
OutColor = float4(PackHeight(FinalHeight), Normal.xy);
}
#endif // FINALIZE_HEIGHTMAP
// ----------------------------------------------------------------------------------
#if GENERATE_MIPS
void GenerateMips(in float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0)
{
uint2 TextureCoordinates = floor(SVPos.xy);
float4 SourceSamples[4] =
{
InSourceHeightmap.Load(int3(2 * TextureCoordinates + int2(+0, +0), 0)),
InSourceHeightmap.Load(int3(2 * TextureCoordinates + int2(+1, +0), 0)),
InSourceHeightmap.Load(int3(2 * TextureCoordinates + int2(+0, +1), 0)),
InSourceHeightmap.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(InCurrentMipSubregionSize == 1);
uint2 RelativeTextureCoordinates = TextureCoordinates % InCurrentMipSubregionSize;
bool2 bIsMinBorder = (RelativeTextureCoordinates == 0);
bool2 bIsMaxBorder = (RelativeTextureCoordinates == (InCurrentMipSubregionSize - 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 = InCurrentMipSubregionSize / 2;
bIsMinBorder = or(bIsMinBorder, RelativeTextureCoordinates == SubsectionSize);
bIsMaxBorder = or(bIsMaxBorder, RelativeTextureCoordinates == 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;
float3 HeightAndNormal = 0.0f;
UNROLL
for (int i = 0; i < 4; ++i)
{
HeightAndNormal += float3(UnpackHeight(SourceSamples[i].xy), SourceSamples[i].zw) * SampleWeights[i];
TotalSampleWeight += SampleWeights[i];
}
HeightAndNormal /= TotalSampleWeight;
OutColor.xy = PackHeight(HeightAndNormal.x);
OutColor.zw = HeightAndNormal.yz;
}
#endif // GENERATE_MIPS