mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
Add terrain flags and make NoTile an option (#3575)
* Add terrain flags and make NoTile an option This makes removes unused uvrotation from material Fixes NRE if storage is null Adds flags enum per material for future use https://files.facepunch.com/sampavlovic/1b0911b1/ShareX_fKdgnanxbh.mp4 * Default NoTiling to false Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The changes from commite0c1c801c0to fix normals on notile got accidentally reverted on87a6fde918, readd normals rotation for notile, update shaders --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Sandbox;
|
||||
|
||||
[Flags]
|
||||
public enum TerrainFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
NoTile = 1 << 0
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Description of a Terrain Material.
|
||||
/// </summary>
|
||||
@@ -25,7 +32,6 @@ public class TerrainMaterial : GameResource
|
||||
[JsonIgnore, Hide] public Texture NHOTexture { get; private set; }
|
||||
|
||||
[Category( "Material" ), Title( "UV Scale" )] public float UVScale { get; set; } = 1.0f;
|
||||
[Category( "Material" ), Title( "UV Rotation" )] public float UVRotation { get; set; } = 0.0f;
|
||||
[Category( "Material" ), Range( 0.0f, 1.0f )] public float Metalness { get; set; } = 0.0f;
|
||||
[Category( "Material" ), Range( 0.1f, 10 )] public float NormalStrength { get; set; } = 1.0f;
|
||||
[Category( "Material" ), Range( 0.1f, 10 )] public float HeightBlendStrength { get; set; } = 1.0f;
|
||||
@@ -36,6 +42,23 @@ public class TerrainMaterial : GameResource
|
||||
[Category( "Material" ), Range( 0.0f, 10.0f ), Title( "Displacement Scale" ), ShowIf( nameof( HasHeightTexture ), true )]
|
||||
public float DisplacementScale { get; set; } = 0.0f;
|
||||
|
||||
[Category( "Material" ), Title( "No Tiling" )]
|
||||
public bool NoTiling { get; set; } = false;
|
||||
|
||||
[JsonIgnore, Hide]
|
||||
public TerrainFlags Flags
|
||||
{
|
||||
get
|
||||
{
|
||||
var flags = TerrainFlags.None;
|
||||
|
||||
if ( NoTiling )
|
||||
flags |= TerrainFlags.NoTile;
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
[Category( "Misc" )] public Surface Surface { get; set; }
|
||||
|
||||
void LoadGeneratedTextures()
|
||||
|
||||
@@ -46,7 +46,7 @@ public partial class Terrain
|
||||
_so.Flags.CastShadows = RenderType == ShadowRenderType.On || RenderType == ShadowRenderType.ShadowsOnly;
|
||||
|
||||
// If we have no textures, push a grid texture (SUCKS)
|
||||
_so.Attributes.SetCombo( "D_GRID", Storage.Materials.Count == 0 );
|
||||
_so.Attributes.SetCombo( "D_GRID", Storage?.Materials.Count == 0 );
|
||||
|
||||
_clipMapLodLevels = ClipMapLodLevels;
|
||||
_clipMapLodExtentTexels = ClipMapLodExtentTexels;
|
||||
@@ -70,13 +70,13 @@ public partial class Terrain
|
||||
public float HeightBlendSharpness;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 0 )]
|
||||
[StructLayout( LayoutKind.Sequential )]
|
||||
private struct GPUTerrainMaterial
|
||||
{
|
||||
public int BCRTextureID;
|
||||
public int NHOTextureID;
|
||||
public float UVScale;
|
||||
public float UVRotation;
|
||||
public TerrainFlags Flags;
|
||||
public float Metalness;
|
||||
public float HeightBlendStrength;
|
||||
public float NormalStrength;
|
||||
@@ -153,11 +153,11 @@ public partial class Terrain
|
||||
BCRTextureID = layer?.BCRTexture?.Index ?? 0,
|
||||
NHOTextureID = layer?.NHOTexture?.Index ?? 0,
|
||||
UVScale = 1.0f / (layer?.UVScale ?? 1.0f),
|
||||
UVRotation = layer?.UVRotation ?? 1.0f,
|
||||
Metalness = layer?.Metalness ?? 0.0f,
|
||||
NormalStrength = 1.0f / (layer?.NormalStrength ?? 1.0f),
|
||||
HeightBlendStrength = layer?.HeightBlendStrength ?? 1.0f,
|
||||
DisplacementScale = layer?.DisplacementScale ?? 0.0f,
|
||||
Flags = layer?.Flags ?? TerrainFlags.None,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -167,6 +167,6 @@ public partial class Terrain
|
||||
Scene.RenderAttributes.Set( "TerrainMaterials", MaterialsBuffer );
|
||||
|
||||
// If we have no textures, push a grid texture (SUCKS)
|
||||
_so.Attributes.SetCombo( "D_GRID", Storage.Materials.Count == 0 );
|
||||
_so.Attributes.SetCombo( "D_GRID", Storage?.Materials.Count == 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,14 +97,24 @@ VS
|
||||
CompactTerrainMaterial material = CompactTerrainMaterial::DecodeFromFloat( rawPixel );
|
||||
|
||||
// Sample base material displacement
|
||||
float2 baseLayerUV = ( o.LocalPosition.xy / 32.0f ) * g_TerrainMaterials[material.BaseTextureId].uvscale;
|
||||
float4 baseNho = Bindless::GetTexture2D( g_TerrainMaterials[material.BaseTextureId].nho_texid ).SampleLevel( g_sAnisotropic, baseLayerUV, 0 );
|
||||
float baseDisplacement = baseNho.b * g_TerrainMaterials[material.BaseTextureId].displacementscale;
|
||||
TerrainMaterial mat = g_TerrainMaterials[material.BaseTextureId];
|
||||
float2 baseLayerUV = ( o.LocalPosition.xy / 32.0f ) * mat.uvscale;
|
||||
|
||||
if( mat.HasFlag( TerrainFlags::NoTile ) )
|
||||
baseLayerUV = Terrain_SampleSeamlessUV( baseLayerUV );
|
||||
|
||||
float4 baseNho = Bindless::GetTexture2D( mat.nho_texid ).SampleLevel( g_sAnisotropic, baseLayerUV, 0 );
|
||||
float baseDisplacement = baseNho.b * mat.displacementscale;
|
||||
|
||||
// Sample overlay material displacement
|
||||
float2 overlayLayerUV = ( o.LocalPosition.xy / 32.0f ) * g_TerrainMaterials[material.OverlayTextureId].uvscale;
|
||||
float4 overlayNho = Bindless::GetTexture2D( g_TerrainMaterials[material.OverlayTextureId].nho_texid ).SampleLevel( g_sAnisotropic, overlayLayerUV, 0 );
|
||||
float overlayDisplacement = overlayNho.b * g_TerrainMaterials[material.OverlayTextureId].displacementscale;
|
||||
mat = g_TerrainMaterials[material.OverlayTextureId];
|
||||
float2 overlayLayerUV = ( o.LocalPosition.xy / 32.0f ) * mat.uvscale;
|
||||
|
||||
if( mat.HasFlag( TerrainFlags::NoTile ) )
|
||||
overlayLayerUV = Terrain_SampleSeamlessUV( overlayLayerUV );
|
||||
|
||||
float4 overlayNho = Bindless::GetTexture2D( mat.nho_texid ).SampleLevel( g_sAnisotropic, overlayLayerUV, 0 );
|
||||
float overlayDisplacement = overlayNho.b * mat.displacementscale;
|
||||
|
||||
// Blend between base and overlay displacement
|
||||
float blend = material.GetNormalizedBlend();
|
||||
@@ -265,21 +275,33 @@ PS
|
||||
// Sample materials by index
|
||||
for ( int i = 0; i < 4; i++ )
|
||||
{
|
||||
float2 layerUV = texUV * g_TerrainMaterials[ indices[i] ].uvscale;
|
||||
TerrainMaterial mat = g_TerrainMaterials[ i ];
|
||||
float2 layerUV = texUV * mat.uvscale;
|
||||
float2x2 uvAngle = float2x2( 1, 0, 0, 1 );
|
||||
|
||||
float4 bcr = Bindless::GetTexture2D( g_TerrainMaterials[ indices[i] ].bcr_texid ).Sample( g_sAnisotropic, layerUV );
|
||||
float4 nho = Bindless::GetTexture2D( g_TerrainMaterials[ indices[i] ].nho_texid ).Sample( g_sAnisotropic, layerUV );
|
||||
// Apply NoTile if needed
|
||||
if ( mat.HasFlag( TerrainFlags::NoTile ) )
|
||||
{
|
||||
layerUV = Terrain_SampleSeamlessUV( layerUV, uvAngle );
|
||||
}
|
||||
|
||||
float3 n = ComputeNormalFromRGTexture( nho.rg );
|
||||
n.xz *= g_TerrainMaterials[ indices[i] ].normalstrength;
|
||||
n = normalize( n );
|
||||
Texture2D tBcr = Bindless::GetTexture2D( mat.bcr_texid );
|
||||
Texture2D tNho = Bindless::GetTexture2D( mat.nho_texid );
|
||||
|
||||
float4 bcr = tBcr.Sample( g_sAnisotropic, layerUV );
|
||||
float4 nho = tNho.Sample( g_sAnisotropic, layerUV );
|
||||
|
||||
float3 normal = ComputeNormalFromRGTexture( nho.rg );
|
||||
normal.xy = mul( uvAngle, normal.xy );
|
||||
normal.xz *= mat.normalstrength;
|
||||
normal = normalize( normal );
|
||||
|
||||
albedos[i] = SrgbGammaToLinear( bcr.rgb );
|
||||
normals[i] = n;
|
||||
normals[i] = normal;
|
||||
roughnesses[i] = bcr.a;
|
||||
heights[i] = nho.b * g_TerrainMaterials[ indices[i] ].heightstrength;
|
||||
heights[i] = nho.b * mat.heightstrength;
|
||||
aos[i] = nho.a;
|
||||
metalness[i] = g_TerrainMaterials[ indices[i] ].metalness;
|
||||
metalness[i] = mat.metalness;
|
||||
}
|
||||
|
||||
// Normalize base weights
|
||||
@@ -354,26 +376,42 @@ PS
|
||||
{
|
||||
texUV /= 32;
|
||||
|
||||
// Sample base material using seamless UV
|
||||
float2 baseUV = texUV * g_TerrainMaterials[material.BaseTextureId].uvscale;
|
||||
float2 baseSeamlessUV = Terrain_SampleSeamlessUV( baseUV );
|
||||
// Sample base material with optional seamless UVs when requested
|
||||
TerrainMaterial baseMat = g_TerrainMaterials[material.BaseTextureId];
|
||||
float2 baseUV = texUV * baseMat.uvscale;
|
||||
float2x2 baseUvAngle = float2x2( 1, 0, 0, 1 );
|
||||
float2 baseSampleUV = baseUV;
|
||||
|
||||
if ( baseMat.HasFlag( TerrainFlags::NoTile ) )
|
||||
{
|
||||
baseSampleUV = Terrain_SampleSeamlessUV( baseUV, baseUvAngle );
|
||||
}
|
||||
|
||||
float4 baseBcr = Bindless::GetTexture2D( g_TerrainMaterials[material.BaseTextureId].bcr_texid ).Sample( g_sAnisotropic, baseSeamlessUV );
|
||||
float4 baseNho = Bindless::GetTexture2D( g_TerrainMaterials[material.BaseTextureId].nho_texid ).Sample( g_sAnisotropic, baseSeamlessUV );
|
||||
float4 baseBcr = Bindless::GetTexture2D( baseMat.bcr_texid ).Sample( g_sAnisotropic, baseSampleUV );
|
||||
float4 baseNho = Bindless::GetTexture2D( baseMat.nho_texid ).Sample( g_sAnisotropic, baseSampleUV );
|
||||
|
||||
float3 baseNormal = ComputeNormalFromRGTexture( baseNho.rg );
|
||||
baseNormal.xz *= g_TerrainMaterials[material.BaseTextureId].normalstrength;
|
||||
baseNormal.xy = mul( baseUvAngle, baseNormal.xy );
|
||||
baseNormal.xz *= baseMat.normalstrength;
|
||||
baseNormal = normalize( baseNormal );
|
||||
|
||||
// Sample overlay material using seamless UV
|
||||
float2 overlayUV = texUV * g_TerrainMaterials[material.OverlayTextureId].uvscale;
|
||||
float2 overlaySeamlessUV = Terrain_SampleSeamlessUV( overlayUV );
|
||||
// Sample overlay material with optional seamless UVs when requested
|
||||
TerrainMaterial overlayMat = g_TerrainMaterials[material.OverlayTextureId];
|
||||
float2 overlayUV = texUV * overlayMat.uvscale;
|
||||
float2x2 overlayUvAngle = float2x2( 1, 0, 0, 1 );
|
||||
float2 overlaySampleUV = overlayUV;
|
||||
|
||||
if ( overlayMat.HasFlag( TerrainFlags::NoTile ) )
|
||||
{
|
||||
overlaySampleUV = Terrain_SampleSeamlessUV( overlayUV, overlayUvAngle );
|
||||
}
|
||||
|
||||
float4 overlayBcr = Bindless::GetTexture2D( g_TerrainMaterials[material.OverlayTextureId].bcr_texid ).Sample( g_sAnisotropic, overlaySeamlessUV );
|
||||
float4 overlayNho = Bindless::GetTexture2D( g_TerrainMaterials[material.OverlayTextureId].nho_texid ).Sample( g_sAnisotropic, overlaySeamlessUV );
|
||||
float4 overlayBcr = Bindless::GetTexture2D( overlayMat.bcr_texid ).Sample( g_sAnisotropic, overlaySampleUV );
|
||||
float4 overlayNho = Bindless::GetTexture2D( overlayMat.nho_texid ).Sample( g_sAnisotropic, overlaySampleUV );
|
||||
|
||||
float3 overlayNormal = ComputeNormalFromRGTexture( overlayNho.rg );
|
||||
overlayNormal.xz *= g_TerrainMaterials[material.OverlayTextureId].normalstrength;
|
||||
overlayNormal.xy = mul( overlayUvAngle, overlayNormal.xy );
|
||||
overlayNormal.xz *= overlayMat.normalstrength;
|
||||
overlayNormal = normalize( overlayNormal );
|
||||
|
||||
// Get normalized blend factor
|
||||
@@ -382,8 +420,8 @@ PS
|
||||
// Height blending if enabled
|
||||
if ( Terrain::Get().HeightBlending )
|
||||
{
|
||||
float baseHeight = baseNho.b * g_TerrainMaterials[material.BaseTextureId].heightstrength;
|
||||
float overlayHeight = overlayNho.b * g_TerrainMaterials[material.OverlayTextureId].heightstrength;
|
||||
float baseHeight = baseNho.b * baseMat.heightstrength;
|
||||
float overlayHeight = overlayNho.b * overlayMat.heightstrength;
|
||||
|
||||
float heightDiff = overlayHeight - baseHeight;
|
||||
float sharpness = Terrain::Get().HeightBlendSharpness * 10.0;
|
||||
@@ -395,7 +433,7 @@ PS
|
||||
normal = lerp( baseNormal, overlayNormal, blend );
|
||||
roughness = lerp( baseBcr.a, overlayBcr.a, blend );
|
||||
ao = lerp( baseNho.a, overlayNho.a, blend );
|
||||
metal = lerp( g_TerrainMaterials[material.BaseTextureId].metalness, g_TerrainMaterials[material.OverlayTextureId].metalness, blend );
|
||||
metal = lerp( baseMat.metalness, overlayMat.metalness, blend );
|
||||
}
|
||||
|
||||
//
|
||||
@@ -528,4 +566,4 @@ PS
|
||||
|
||||
return ShadingModelStandard::Shade( p );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// Not stable, shit will change and custom shaders using this API will break until I'm satisfied.
|
||||
// But they will break for good reason and I will tell you why and how to update.
|
||||
//
|
||||
// 12/9/25: Added NoTile Flag
|
||||
// 23/07/24: Initial global structured buffers
|
||||
//
|
||||
|
||||
@@ -31,16 +32,26 @@ struct TerrainStruct
|
||||
float HeightBlendSharpness;
|
||||
};
|
||||
|
||||
enum TerrainFlags
|
||||
{
|
||||
NoTile = 1 // (1 << 0)
|
||||
};
|
||||
|
||||
struct TerrainMaterial
|
||||
{
|
||||
int bcr_texid;
|
||||
int nho_texid;
|
||||
float uvscale;
|
||||
float uvrotation;
|
||||
uint flags;
|
||||
float metalness;
|
||||
float heightstrength;
|
||||
float normalstrength;
|
||||
float displacementscale;
|
||||
|
||||
bool HasFlag( TerrainFlags flag )
|
||||
{
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
SamplerState g_sBilinearBorder < Filter( BILINEAR ); AddressU( BORDER ); AddressV( BORDER ); >;
|
||||
@@ -71,7 +82,7 @@ class Terrain
|
||||
|
||||
// Get UV with per-tile UV offset to reduce visible tiling
|
||||
// Works by offsetting UVs within each tile using a hash of the tile coordinate
|
||||
float2 Terrain_SampleSeamlessUV( float2 uv )
|
||||
float2 Terrain_SampleSeamlessUV( float2 uv, out float2x2 uvAngle )
|
||||
{
|
||||
float2 tileCoord = floor( uv );
|
||||
float2 localUV = frac( uv );
|
||||
@@ -87,6 +98,9 @@ float2 Terrain_SampleSeamlessUV( float2 uv )
|
||||
float sinA = sin(angle);
|
||||
float2x2 rot = float2x2(cosA, -sinA, sinA, cosA);
|
||||
|
||||
// Output rotation matrix
|
||||
uvAngle = rot;
|
||||
|
||||
// Rotate around center
|
||||
localUV = mul(rot, localUV - 0.5) + 0.5;
|
||||
|
||||
@@ -94,6 +108,12 @@ float2 Terrain_SampleSeamlessUV( float2 uv )
|
||||
return tileCoord + frac(localUV + hash);
|
||||
}
|
||||
|
||||
float2 Terrain_SampleSeamlessUV( float2 uv )
|
||||
{
|
||||
float2x2 dummy;
|
||||
return Terrain_SampleSeamlessUV( uv, dummy );
|
||||
}
|
||||
|
||||
//
|
||||
// Takes 4 samples
|
||||
// This is easy for now, an optimization would be to generate this once in a compute shader
|
||||
@@ -165,4 +185,4 @@ float4 Terrain_WireframeColor( uint lodLevel )
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user