311 lines
12 KiB
HLSL
311 lines
12 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_LAYER 1
|
|
//#define GENERATE_NORMALS 1
|
|
//#define GENERATE_MIPS 1
|
|
#endif // defined (__INTELLISENSE__)
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
#if MERGE_EDIT_LAYER
|
|
// MergeEditLayerPS inputs/outputs :
|
|
|
|
/** EHeightmapBlendMode enum */
|
|
#define EHEIGHTMAPBLENDMODE_ADDITIVE 0
|
|
#define EHEIGHTMAPBLENDMODE_LEGACYALPHABLEND 1
|
|
#define EHEIGHTMAPBLENDMODE_ALPHABLEND 2
|
|
|
|
uint InEditLayerBlendMode; // See EHeightmapBlendMode
|
|
float InEditLayerAlpha; // Alpha value to be used in the blend
|
|
|
|
Texture2D<float4> InCurrentEditLayerHeightmap; // The height of the current edit layer to merge
|
|
Texture2D<float4> InPreviousEditLayersHeightmap; // The resulting height from the merge of all prior edit layers
|
|
#endif // MERGE_EDIT_LAYER
|
|
|
|
#if GENERATE_NORMALS
|
|
// GenerateNormalsPS inputs/outputs :
|
|
|
|
uint4 InTextureSize; // .xy == Effective size in pixels of InSourceHeightmap, .zw == actual texture size in pixels of InSourceHeightmap
|
|
float3 InLandscapeGridScale; // x == LS Actor DrawScale.X, y == LS Actor DrawScale.y, z == LS Actor DrawScale.z / 128.0f (ZSCALE)
|
|
uint InComponentSizeQuads; // Number of quads per component
|
|
uint2 InNumComponents; // Number of components in the effective region of the texture (.xy)
|
|
|
|
// TODO [jonathan.bard] : this can be replaced by GlobalPointClampedSampler :
|
|
SamplerState InSourceHeightmapSampler;
|
|
Texture2D<float4> InSourceHeightmap;
|
|
Texture2D<float> InValidityTexture;
|
|
#endif // GENERATE_NORMALS
|
|
|
|
#if GENERATE_MIPS
|
|
// GenerateMipsPS inputs/outputs :
|
|
|
|
uint2 InCurrentMipSubsectionSize; // Size of the the subsection at the currently generated mip level
|
|
|
|
Texture2D<float4> InSourceHeightmap; // Source heightmap (containing the current mip level - 1)
|
|
#endif // GENERATE_MIPS
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
// Util functions :
|
|
|
|
#if MERGE_EDIT_LAYER
|
|
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
|
|
}
|
|
#endif // MERGE_EDIT_LAYER
|
|
|
|
#if GENERATE_NORMALS
|
|
float ScaleHeight(float InHeight)
|
|
{
|
|
return (InHeight - LANDSCAPE_MID_VALUE) * InLandscapeGridScale.z * TERRAIN_ZSCALE;
|
|
}
|
|
|
|
float ExtractAndScaleHeight(float2 InPackedHeight)
|
|
{
|
|
return ScaleHeight(UnpackHeight(InPackedHeight));
|
|
}
|
|
|
|
float UnscaleHeight(float InScaledHeight)
|
|
{
|
|
return (InScaledHeight / InLandscapeGridScale.z / TERRAIN_ZSCALE) + LANDSCAPE_MID_VALUE;
|
|
}
|
|
|
|
float4 ExtractSampleInfo(int2 InTextureCoordinates, float2 InPackedHeight)
|
|
{
|
|
// Note: don't use a bool here, as some compilers don't support it :
|
|
int bIsValidSample = (int)(all(InTextureCoordinates >= 0) && all(InTextureCoordinates < (int2)InTextureSize.xy));
|
|
bIsValidSample &= (InValidityTexture.Load(int3(InTextureCoordinates, 0)).x > 0.0f);
|
|
return float4((InTextureCoordinates + 0.5f) * InLandscapeGridScale.xy, ExtractAndScaleHeight(InPackedHeight), bIsValidSample);
|
|
}
|
|
#endif // GENERATE_NORMALS
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
// Pixel shaders :
|
|
|
|
#if MERGE_EDIT_LAYER
|
|
void MergeEditLayerPS(in float4 SVPos : SV_POSITION, out float4 OutPackedHeight : SV_Target0)
|
|
{
|
|
uint2 TextureCoordinates = floor(SVPos.xy);
|
|
|
|
float4 CurrentLayerSample = InCurrentEditLayerHeightmap.Load(int3(TextureCoordinates, 0));
|
|
float CurrentLayerHeight = UnpackHeight(CurrentLayerSample.xy) - LANDSCAPE_MID_VALUE; // xy = relative coordinates, z = mip level
|
|
float4 PreviousLayersSample = InPreviousEditLayersHeightmap.Load(int3(TextureCoordinates, 0));
|
|
float PreviousLayersHeight = UnpackHeight(PreviousLayersSample.xy) - LANDSCAPE_MID_VALUE; // xy = relative coordinates, z = mip level
|
|
float FinalHeight = PreviousLayersHeight;
|
|
|
|
if (InEditLayerBlendMode == EHEIGHTMAPBLENDMODE_ADDITIVE)
|
|
{
|
|
FinalHeight += InEditLayerAlpha * CurrentLayerHeight;
|
|
}
|
|
else if (InEditLayerBlendMode == EHEIGHTMAPBLENDMODE_LEGACYALPHABLEND)
|
|
{
|
|
float LayerHeightAlphaBlend = ExtractAlpha(CurrentLayerSample.ba);
|
|
float NewHeight = InEditLayerAlpha * CurrentLayerHeight + (LayerHeightAlphaBlend * PreviousLayersHeight);
|
|
uint RaiseLower = floor(CurrentLayerSample.a * 255.f + 0.5f);
|
|
bool bLowerTerrain = (RaiseLower & 1);
|
|
bool bRaiseTerrain = (RaiseLower & 2);
|
|
if ((bRaiseTerrain && NewHeight > PreviousLayersHeight) || (bLowerTerrain && NewHeight < PreviousLayersHeight))
|
|
{
|
|
FinalHeight = NewHeight;
|
|
}
|
|
else
|
|
{
|
|
FinalHeight = PreviousLayersHeight;
|
|
}
|
|
}
|
|
else if (InEditLayerBlendMode == EHEIGHTMAPBLENDMODE_ALPHABLEND)
|
|
{
|
|
float LayerHeightAlphaBlend = 0.0f;
|
|
uint HeightFlags = 0;
|
|
UnpackHeightAlpha(CurrentLayerSample.ba, LayerHeightAlphaBlend, HeightFlags);
|
|
float FinalAlpha = InEditLayerAlpha * LayerHeightAlphaBlend;
|
|
if (HeightFlags == EHEIGHTMAPALPHAFLAGS_ALPHABLEND)
|
|
{
|
|
FinalHeight = lerp(PreviousLayersHeight, CurrentLayerHeight, FinalAlpha);
|
|
}
|
|
else if (HeightFlags & EHEIGHTMAPALPHAFLAGS_MIN)
|
|
{
|
|
FinalHeight = lerp(PreviousLayersHeight, min(PreviousLayersHeight, CurrentLayerHeight), FinalAlpha);
|
|
}
|
|
else if (HeightFlags & EHEIGHTMAPALPHAFLAGS_MAX)
|
|
{
|
|
FinalHeight = lerp(PreviousLayersHeight, max(PreviousLayersHeight, CurrentLayerHeight), FinalAlpha);
|
|
}
|
|
// Additive case (== no flag) :
|
|
else if (HeightFlags == EHEIGHTMAPALPHAFLAGS_ADDITIVE)
|
|
{
|
|
FinalHeight += CurrentLayerHeight * FinalAlpha;
|
|
}
|
|
}
|
|
|
|
FinalHeight += LANDSCAPE_MID_VALUE;
|
|
FinalHeight = clamp(FinalHeight, 0.0f, LANDSCAPE_MAX_VALUE);
|
|
|
|
OutPackedHeight = float4(PackHeight(FinalHeight), 0.0f, 0.0f);
|
|
}
|
|
#endif // MERGE_EDIT_LAYER
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
#if GENERATE_NORMALS
|
|
void GenerateNormalsPS(in float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0)
|
|
{
|
|
uint2 TextureCoordinates = floor(SVPos.xy);
|
|
uint2 TextureEffectiveSize = InTextureSize.xy;
|
|
uint2 TextureActualSize = InTextureSize.zw;
|
|
float2 TexelSize = 1.0f / TextureActualSize;
|
|
|
|
//float MaskMinBorderX = (TextureCoordinates.x == 0) ? 0.0f : 1.0f; // first pixel X
|
|
//float MaskMinBorderY = (TextureCoordinates.y == 0) ? 0.0f : 1.0f; // first pixel Y
|
|
//float MaskMaxBorderX = (TextureCoordinates.x >= TextureEffectiveSize.x - 1) ? 0.0f : 1.0f; // last pixel X
|
|
//float MaskMaxBorderY = (TextureCoordinates.y >= TextureEffectiveSize.y - 1) ? 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 = ((float2) TextureCoordinates + 0.75f) * TexelSize;
|
|
float4 Red0 = InSourceHeightmap.GatherRed(InSourceHeightmapSampler, GatherLocation - TexelSize);
|
|
float4 Green0 = InSourceHeightmap.GatherGreen(InSourceHeightmapSampler, GatherLocation - TexelSize);
|
|
float4 Red1 = InSourceHeightmap.GatherRed(InSourceHeightmapSampler, GatherLocation);
|
|
float4 Green1 = InSourceHeightmap.GatherGreen(InSourceHeightmapSampler, GatherLocation);
|
|
|
|
// [x,y,z] = World Position [w] = valid mask
|
|
float4 TL = ExtractSampleInfo(TextureCoordinates + int2(-1, -1), float2(Red0.w, Green0.w));
|
|
float4 TT = ExtractSampleInfo(TextureCoordinates + int2(+0, -1), float2(Red0.z, Green0.z));
|
|
float4 CC = ExtractSampleInfo(TextureCoordinates + int2(+0, +0), float2(Red0.y, Green0.y));
|
|
float4 LL = ExtractSampleInfo(TextureCoordinates + int2(-1, +0), float2(Red0.x, Green0.x));
|
|
float4 RR = ExtractSampleInfo(TextureCoordinates + int2(+1, +0), float2(Red1.z, Green1.z));
|
|
float4 BR = ExtractSampleInfo(TextureCoordinates + int2(+1, +1), float2(Red1.y, Green1.y));
|
|
float4 BB = ExtractSampleInfo(TextureCoordinates + int2(+0, +1), float2(Red1.x, Green1.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
|
|
FinalNormal = (FinalNormal + 1.0) * 0.5;
|
|
|
|
float FinalHeight = UnscaleHeight(CC.z);
|
|
OutColor = float4(PackHeight(FinalHeight), FinalNormal.xy);
|
|
}
|
|
#endif // GENERATE_NORMALS
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
#if GENERATE_MIPS
|
|
void GenerateMipsPS(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 subsection are shared between neighbors, we must ensure that the parent mip's inner row/column of pixels don't contribute,
|
|
// so that pixels on the subsection borders for neighboring subsections for mips have an equal value :
|
|
// 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(InCurrentMipSubsectionSize == 1);
|
|
|
|
uint2 SubsectionRelativeTextureCoordinates = TextureCoordinates % InCurrentMipSubsectionSize;
|
|
bool2 bIsMinBorder = (SubsectionRelativeTextureCoordinates == 0);
|
|
bool2 bIsMaxBorder = (SubsectionRelativeTextureCoordinates == (InCurrentMipSubsectionSize - 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 |