diff --git a/engine/Sandbox.Engine/Resources/Terrain/TerrainMaterial.cs b/engine/Sandbox.Engine/Resources/Terrain/TerrainMaterial.cs index 6d3dd9e6..77e4f5de 100644 --- a/engine/Sandbox.Engine/Resources/Terrain/TerrainMaterial.cs +++ b/engine/Sandbox.Engine/Resources/Terrain/TerrainMaterial.cs @@ -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 +} + /// /// Description of a Terrain Material. /// @@ -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() diff --git a/engine/Sandbox.Engine/Scene/Components/Terrain/Terrain.Rendering.cs b/engine/Sandbox.Engine/Scene/Components/Terrain/Terrain.Rendering.cs index 69b32dd6..d1868da6 100644 --- a/engine/Sandbox.Engine/Scene/Components/Terrain/Terrain.Rendering.cs +++ b/engine/Sandbox.Engine/Scene/Components/Terrain/Terrain.Rendering.cs @@ -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 ); } } diff --git a/game/addons/base/Assets/shaders/terrain.shader b/game/addons/base/Assets/shaders/terrain.shader index 93983eb6..c072ccb2 100644 --- a/game/addons/base/Assets/shaders/terrain.shader +++ b/game/addons/base/Assets/shaders/terrain.shader @@ -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 ); } -} \ No newline at end of file +} diff --git a/game/addons/base/Assets/shaders/terrain/TerrainCommon.hlsl b/game/addons/base/Assets/shaders/terrain/TerrainCommon.hlsl index c7c9451b..49ff33f5 100644 --- a/game/addons/base/Assets/shaders/terrain/TerrainCommon.hlsl +++ b/game/addons/base/Assets/shaders/terrain/TerrainCommon.hlsl @@ -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 \ No newline at end of file +#endif