From 66f93744779120e8957f7d0d4e579a4d5511ed97 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Jun 2024 13:14:20 +0200 Subject: [PATCH] Add improved terrain rasterization into Global SDF #754 --- Source/Shaders/GlobalSignDistanceField.shader | 52 +++++++++++++------ Source/Shaders/TerrainCommon.hlsl | 52 +++++++++++++++++++ 2 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 Source/Shaders/TerrainCommon.hlsl diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index 5c8e4ee4f..c8788e3be 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -3,6 +3,7 @@ #include "./Flax/Common.hlsl" #include "./Flax/Math.hlsl" #include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/TerrainCommon.hlsl" #define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 #define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 @@ -151,6 +152,9 @@ META_CS(true, FEATURE_LEVEL_SM5) [numthreads(GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE, GLOBAL_SDF_RASTERIZE_GROUP_SIZE)] void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) { +#if defined(PLATFORM_PS4) || defined(PLATFORM_PS5) + // TODO: fix shader compilation error +#else uint3 voxelCoord = ChunkCoord + DispatchThreadId; float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd; voxelCoord.x += CascadeIndex * CascadeResolution; @@ -161,33 +165,47 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]]; // Convert voxel world-space position into heightfield local-space position and get heightfield UV - float3 volumePos = mul(float4(voxelWorldPos, 1), ToMatrix4x4(objectData.WorldToVolume)).xyz; + 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 the heightfield -#if defined(PLATFORM_PS4) || defined(PLATFORM_PS5) - float4 heightmapValue = 0; // TODO: fix shader compilation error -#else - float4 heightmapValue = ObjectsTextures[i].SampleLevel(SamplerLinearClamp, heightfieldUV, objectData.MipOffset); -#endif - bool isHole = (heightmapValue.b + heightmapValue.a) >= 1.9f; - if (isHole || any(heightfieldUV < 0.0f) || any(heightfieldUV > 1.0f)) - continue; - float height = (float)((int)(heightmapValue.x * 255.0) + ((int)(heightmapValue.y * 255) << 8)) / 65535.0; - float2 positionXZ = volumePos.xz; - float3 position = float3(positionXZ.x, height, positionXZ.y); - float4x4 volumeToWorld = ToMatrix4x4(objectData.VolumeToWorld); - float3 heightfieldPosition = mul(float4(position, 1), volumeToWorld).xyz; - float3 heightfieldNormal = normalize(float3(volumeToWorld[0].y, volumeToWorld[1].y, volumeToWorld[2].y)); + // Sample heightfield around the voxel location (heightmap uses point sampler) + Texture2D heightmap = ObjectsTextures[i]; + float4 localToUV = float4(objectData.VolumeToUVWMul.xz, objectData.VolumeToUVWAdd.xz); + float3 n00, n10, n01, n11; + bool h00, h10, h01, h11; + float offset = CascadeVoxelSize * 2; + 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); + float3 p11 = SampleHeightmap(heightmap, volumePos + float3(0, 0, +offset), localToUV, n11, h11, objectData.MipOffset); + + // Calculate average sample (linear interpolation) + float3 heightfieldPosition = (p00 + p10 + p01 + p11) * 0.25f; + float3 heightfieldNormal = (n00 + n10 + n01 + n11) * 0.25f; + heightfieldNormal = normalize(heightfieldNormal); + bool isHole = h00 || h10 || h01 || h11; + + // Skip holes and pixels outside the heightfield + if (isHole) + continue; + + // Transform to world-space + float4x4 localToWorld = ToMatrix4x4(objectData.VolumeToWorld); + heightfieldPosition = mul(float4(heightfieldPosition, 1), localToWorld).xyz; + // TODO: rotate normal vector + //heightfieldNormal = normalize(float3(localToWorld[0].y, localToWorld[1].y, localToWorld[2].y)); + //heightfieldNormal = float3(0, 1, 0); // Calculate distance from voxel center to the heightfield float objectDistance = dot(heightfieldNormal, voxelWorldPos - heightfieldPosition); - if (objectDistance < thickness) + if (objectDistance < thickness * 0.5f) objectDistance = thickness - objectDistance; minDistance = CombineSDF(minDistance, objectDistance); } GlobalSDFTex[voxelCoord] = clamp(minDistance / MaxDistance, -1, 1); +#endif } #endif diff --git a/Source/Shaders/TerrainCommon.hlsl b/Source/Shaders/TerrainCommon.hlsl new file mode 100644 index 000000000..bbd4469f1 --- /dev/null +++ b/Source/Shaders/TerrainCommon.hlsl @@ -0,0 +1,52 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#ifndef __TERRAIN_COMMON__ +#define __TERRAIN_COMMON__ + +#include "./Flax/Common.hlsl" + +float SampleHeightmap(Texture2D heightmap, float2 uv, float mipOffset = 0.0f) +{ + // Sample heightmap + float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); + + // Decode heightmap + float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; + return height; +} + +float SampleHeightmap(Texture2D heightmap, float2 uv, out float3 normal, out bool isHole, float mipOffset = 0.0f) +{ + // Sample heightmap + float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); + + // Decode heightmap + float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; + float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f; + normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); + isHole = (value.b + value.a) >= 1.9f; + normal = normalize(normal); + return height; +} + +float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4 localToUV, out float3 normal, out bool isHole, float mipOffset = 0.0f) +{ + // Sample heightmap + float2 uv = localPosition.xz * localToUV.xy + localToUV.zw; + float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset); + + // Decode heightmap + isHole = (value.b + value.a) >= 1.9f; + float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0; + float3 position = float3(localPosition.x, height, localPosition.z); + float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f; + normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); + normal = normalize(normal); + + // UVs outside the heightmap are empty + isHole = isHole || any(uv < 0.0f) || any(uv > 1.0f); + + return position; +} + +#endif