From 22c88eb59d818e1bf8404a012ce65cc9310bfc42 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 19:25:18 +0100 Subject: [PATCH] Fix blocky terrain SDF #3975 --- Content/Shaders/GlobalSignDistanceField.flax | 4 ++-- Source/Engine/Terrain/Terrain.cpp | 11 +++++++---- Source/Shaders/GlobalSignDistanceField.shader | 10 +++++++--- Source/Shaders/TerrainCommon.hlsl | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax index 5afcb4bf4..01dae76b1 100644 --- a/Content/Shaders/GlobalSignDistanceField.flax +++ b/Content/Shaders/GlobalSignDistanceField.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f07ebb16820897e8598ae7a0627cb75b3d28e9dceea3ad4bd9ff543d5cdd01c -size 13979 +oid sha256:0506a5485a0107f67714ceb8c1714f18cb5718bacfde36fad91d53ea3cb60de9 +size 14044 diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 77b4ee8a6..f0ad65894 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -549,19 +549,22 @@ bool Terrain::DrawSetup(RenderContext& renderContext) const DrawPass drawModes = DrawModes & renderContext.View.Pass; if (drawModes == DrawPass::GlobalSDF) { - const float chunkSize = TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize; - const float posToUV = 0.25f / chunkSize; - Float4 localToUV(posToUV, posToUV, 0.0f, 0.0f); + const float chunkScale = 0.25f / (TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize); // Patch heightfield is divided into 4x4 chunks for (const TerrainPatch* patch : _patches) { if (!patch->Heightmap) continue; + GPUTexture* heightfield = patch->Heightmap->GetTexture(); + float size = (float)heightfield->Width(); + Float4 localToUV; + localToUV.X = localToUV.Y = chunkScale * (size - 1) / size; // Skip the last edge texel + localToUV.Z = localToUV.W = 0.5f / size; // Include half-texel offset Transform patchTransform; patchTransform.Translation = patch->_offset + Vector3(0, patch->_yOffset, 0); patchTransform.Orientation = Quaternion::Identity; patchTransform.Scale = Float3(1.0f, patch->_yHeight, 1.0f); patchTransform = _transform.LocalToWorld(patchTransform); - GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, patch->Heightmap->GetTexture(), patchTransform, patch->_bounds, localToUV); + GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, heightfield, patchTransform, patch->_bounds, localToUV); } return true; } diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index fe4bafda5..e8bb1b1f7 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -170,15 +170,14 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) // Convert voxel world-space position into heightfield local-space position and get heightfield UV float4x4 worldToLocal = ToMatrix4x4(objectData.WorldToVolume); float3 volumePos = mul(float4(voxelWorldPos, 1), worldToLocal).xyz; - float3 volumeUV = volumePos * objectData.VolumeToUVWMul + objectData.VolumeToUVWAdd; - float2 heightfieldUV = float2(volumeUV.x, volumeUV.z); // Sample heightfield around the voxel location (heightmap uses point sampler) Texture2D heightmap = ObjectsTextures[i]; float4 localToUV = float4(objectData.VolumeToUVWMul.xz, objectData.VolumeToUVWAdd.xz); +#if 1 float3 n00, n10, n01, n11; bool h00, h10, h01, h11; - float offset = CascadeVoxelSize * 2; + float offset = CascadeVoxelSize; float3 p00 = SampleHeightmap(heightmap, volumePos + float3(-offset, 0, 0), localToUV, n00, h00, objectData.MipOffset); float3 p10 = SampleHeightmap(heightmap, volumePos + float3(+offset, 0, 0), localToUV, n10, h10, objectData.MipOffset); float3 p01 = SampleHeightmap(heightmap, volumePos + float3(0, 0, -offset), localToUV, n01, h01, objectData.MipOffset); @@ -189,6 +188,11 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) float3 heightfieldNormal = (n00 + n10 + n01 + n11) * 0.25f; heightfieldNormal = normalize(heightfieldNormal); bool isHole = h00 || h10 || h01 || h11; +#else + float3 heightfieldNormal; + bool isHole; + float3 heightfieldPosition = SampleHeightmap(heightmap, volumePos, localToUV, heightfieldNormal, isHole, objectData.MipOffset); +#endif // Skip holes and pixels outside the heightfield if (isHole) diff --git a/Source/Shaders/TerrainCommon.hlsl b/Source/Shaders/TerrainCommon.hlsl index 0c2f57168..f883e8033 100644 --- a/Source/Shaders/TerrainCommon.hlsl +++ b/Source/Shaders/TerrainCommon.hlsl @@ -35,11 +35,11 @@ float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4 { // Sample heightmap float2 uv = localPosition.xz * localToUV.xy + localToUV.zw; - float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); + float4 value = heightmap.SampleLevel(SamplerLinearClamp, uv, mipOffset); // Decode heightmap normal = DecodeHeightmapNormal(value, isHole); - float height = DecodeHeightmapHeight(value);; + float height = DecodeHeightmapHeight(value); float3 position = float3(localPosition.x, height, localPosition.z); // UVs outside the heightmap are empty