Add support for rasterizing terrain into Global SDF as heightfield

This commit is contained in:
2022-04-28 14:17:10 +02:00
parent 3ec856778c
commit 70b9db7598
6 changed files with 258 additions and 69 deletions
Binary file not shown.
Binary file not shown.
@@ -18,6 +18,7 @@
// TODO: try using R8 format for Global SDF
#define GLOBAL_SDF_FORMAT PixelFormat::R16_Float
#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF.
#define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 // The maximum amount of heightfields to store in a single chunk.
#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8
#define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 // Global SDF chunk size in voxels.
#define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 // The margin in voxels around objects for culling. Reduces artifacts but reduces performance.
@@ -32,7 +33,7 @@ static_assert(GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT % 4 == 0, "Must be multiple o
#include "Engine/Debug/DebugDraw.h"
#endif
PACK_STRUCT(struct ModelRasterizeData
PACK_STRUCT(struct ObjectRasterizeData
{
Matrix WorldToVolume; // TODO: use 3x4 matrix
Matrix VolumeToWorld; // TODO: use 3x4 matrix
@@ -59,13 +60,14 @@ PACK_STRUCT(struct ModelsRasterizeData
Int3 ChunkCoord;
float MaxDistance;
Vector3 CascadeCoordToPosMul;
int ModelsCount;
int ObjectsCount;
Vector3 CascadeCoordToPosAdd;
int32 CascadeResolution;
Vector2 Padding0;
float Padding0;
float CascadeVoxelSize;
int32 CascadeMipResolution;
int32 CascadeMipFactor;
uint32 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT];
uint32 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT];
});
struct RasterizeModel
@@ -82,6 +84,7 @@ struct RasterizeModel
struct RasterizeChunk
{
uint16 ModelsCount;
uint16 HeightfieldsCount : 15;
uint16 Dynamic : 1;
uint16 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT];
uint16 Heightfields[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT];
@@ -89,6 +92,7 @@ struct RasterizeChunk
RasterizeChunk()
{
ModelsCount = 0;
HeightfieldsCount = 0;
Dynamic = false;
}
};
@@ -292,13 +296,14 @@ bool GlobalSignDistanceFieldPass::setupResources()
return true;
_csRasterizeModel0 = shader->GetCS("CS_RasterizeModel", 0);
_csRasterizeModel1 = shader->GetCS("CS_RasterizeModel", 1);
_csRasterizeHeightfield = shader->GetCS("CS_RasterizeHeightfield");
_csClearChunk = shader->GetCS("CS_ClearChunk");
_csGenerateMip0 = shader->GetCS("CS_GenerateMip", 0);
_csGenerateMip1 = shader->GetCS("CS_GenerateMip", 1);
// Init buffer
if (!_modelsBuffer)
_modelsBuffer = New<DynamicStructuredBuffer>(64u * (uint32)sizeof(ModelRasterizeData), (uint32)sizeof(ModelRasterizeData), false, TEXT("GlobalSDF.ModelsBuffer"));
if (!_objectsBuffer)
_objectsBuffer = New<DynamicStructuredBuffer>(64u * (uint32)sizeof(ObjectRasterizeData), (uint32)sizeof(ObjectRasterizeData), false, TEXT("GlobalSDF.ObjectsBuffer"));
// Create pipeline state
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
@@ -320,6 +325,7 @@ void GlobalSignDistanceFieldPass::OnShaderReloading(Asset* obj)
SAFE_DELETE_GPU_RESOURCE(_psDebug);
_csRasterizeModel0 = nullptr;
_csRasterizeModel1 = nullptr;
_csRasterizeHeightfield = nullptr;
_csClearChunk = nullptr;
_csGenerateMip0 = nullptr;
_csGenerateMip1 = nullptr;
@@ -335,8 +341,8 @@ void GlobalSignDistanceFieldPass::Dispose()
RendererPass::Dispose();
// Cleanup
SAFE_DELETE(_modelsBuffer);
_modelsTextures.Resize(0);
SAFE_DELETE(_objectsBuffer);
_objectsTextures.Resize(0);
SAFE_DELETE_GPU_RESOURCE(_psDebug);
_shader = nullptr;
ChunksCache.Clear();
@@ -467,8 +473,8 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
{
PROFILE_CPU_NAMED("Clear");
chunks.Clear();
_modelsBuffer->Clear();
_modelsTextures.Clear();
_objectsBuffer->Clear();
_objectsTextures.Clear();
}
// Check if cascade center has been moved
@@ -482,9 +488,10 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
cascade.Bounds = cascadeBounds;
// Draw all objects from all scenes into the cascade
_modelsBufferCount = 0;
_objectsBufferCount = 0;
_voxelSize = voxelSize;
_cascadeBounds = cascadeBounds;
_cascadeIndex = cascadeIndex;
_sdfData = &sdfData;
{
PROFILE_CPU_NAMED("Draw");
@@ -516,6 +523,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
data.CascadeResolution = resolution;
data.CascadeMipResolution = resolutionMip;
data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR;
data.CascadeVoxelSize = voxelSize;
context->BindUA(0, cascadeView);
context->BindCB(1, _cb1);
const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE;
@@ -569,42 +577,61 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
// Send models data to the GPU
if (chunks.Count() != 0)
{
PROFILE_GPU_CPU("Update Models");
_modelsBuffer->Flush(context);
PROFILE_GPU_CPU("Update Objects");
_objectsBuffer->Flush(context);
}
context->BindSR(0, _modelsBuffer->GetBuffer() ? _modelsBuffer->GetBuffer()->View() : nullptr);
context->BindSR(0, _objectsBuffer->GetBuffer() ? _objectsBuffer->GetBuffer()->View() : nullptr);
// Rasterize non-empty chunk (first layer so can override existing chunk data)
// Rasterize non-empty chunks (first layer so can override existing chunk data)
for (const auto& e : chunks)
{
if (e.Key.Layer != 0)
continue;
auto& chunk = e.Value;
cascade.NonEmptyChunks.Add(e.Key);
for (int32 i = 0; i < chunk.ModelsCount; i++)
{
int32 model = chunk.Models[i];
data.Models[i] = model;
context->BindSR(i + 1, _modelsTextures[model]);
auto objectIndex = chunk.Models[i];
data.Objects[i] = objectIndex;
context->BindSR(i + 1, _objectsTextures[objectIndex]);
}
ASSERT_LOW_LAYER(chunk.ModelsCount != 0);
for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++)
context->UnBindSR(i + 1);
data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
data.ModelsCount = chunk.ModelsCount;
data.ObjectsCount = chunk.ModelsCount;
context->UpdateCB(_cb1, &data);
cascade.NonEmptyChunks.Add(e.Key);
context->Dispatch(_csRasterizeModel0, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
auto cs = data.ObjectsCount != 0 ? _csRasterizeModel0 : _csClearChunk; // Terrain-only chunk can be quickly cleared
context->Dispatch(cs, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
anyChunkDispatch = true;
// TODO: don't stall with UAV barrier on D3D12/Vulkan if UAVs don't change between dispatches (maybe cache per-shader write/read flags for all UAVs?)
if (chunk.HeightfieldsCount != 0)
{
// Inject heightfield (additive)
for (int32 i = 0; i < chunk.HeightfieldsCount; i++)
{
auto objectIndex = chunk.Heightfields[i];
data.Objects[i] = objectIndex;
context->BindSR(i + 1, _objectsTextures[objectIndex]);
}
for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++)
context->UnBindSR(i + 1);
data.ObjectsCount = chunk.HeightfieldsCount;
context->UpdateCB(_cb1, &data);
context->Dispatch(_csRasterizeHeightfield, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
}
#if GLOBAL_SDF_DEBUG_CHUNKS
// Debug draw chunk bounds in world space with number of models in it
if (cascadeIndex + 1 == GLOBAL_SDF_DEBUG_CHUNKS)
{
int32 count = chunk.ModelsCount;
int32 count = chunk.ModelsCount + chunk.HeightfieldsCount;
RasterizeChunkKey tmp = e.Key;
tmp.NextLayer();
while (chunks.ContainsKey(tmp))
{
count += chunks[tmp].ModelsCount;
count += chunks[tmp].ModelsCount + chunks[tmp].HeightfieldsCount;
tmp.NextLayer();
}
Vector3 chunkMin = cascadeBounds.Minimum + Vector3(e.Key.Coord) * chunkSize;
@@ -615,23 +642,45 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
#endif
}
// Rasterize non-empty chunk (additive layers so so need combine with existing chunk data)
// Rasterize non-empty chunks (additive layers so so need combine with existing chunk data)
for (const auto& e : chunks)
{
if (e.Key.Layer == 0)
continue;
auto& chunk = e.Value;
for (int32 i = 0; i < chunk.ModelsCount; i++)
{
int32 model = chunk.Models[i];
data.Models[i] = model;
context->BindSR(i + 1, _modelsTextures[model]);
}
ASSERT_LOW_LAYER(chunk.ModelsCount != 0);
data.ChunkCoord = e.Key.Coord * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
data.ModelsCount = chunk.ModelsCount;
context->UpdateCB(_cb1, &data);
context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
if (chunk.ModelsCount != 0)
{
// Inject models (additive)
for (int32 i = 0; i < chunk.ModelsCount; i++)
{
auto objectIndex = chunk.Models[i];
data.Objects[i] = objectIndex;
context->BindSR(i + 1, _objectsTextures[objectIndex]);
}
for (int32 i = chunk.ModelsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++)
context->UnBindSR(i + 1);
data.ObjectsCount = chunk.ModelsCount;
context->UpdateCB(_cb1, &data);
context->Dispatch(_csRasterizeModel1, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
}
if (chunk.HeightfieldsCount != 0)
{
// Inject heightfields (additive)
for (int32 i = 0; i < chunk.HeightfieldsCount; i++)
{
auto objectIndex = chunk.Heightfields[i];
data.Objects[i] = objectIndex;
context->BindSR(i + 1, _objectsTextures[objectIndex]);
}
for (int32 i = chunk.HeightfieldsCount; i < GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT; i++)
context->UnBindSR(i + 1);
data.ObjectsCount = chunk.HeightfieldsCount;
context->UpdateCB(_cb1, &data);
context->Dispatch(_csRasterizeHeightfield, chunkDispatchGroups, chunkDispatchGroups, chunkDispatchGroups);
}
anyChunkDispatch = true;
}
}
@@ -762,19 +811,19 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas
Vector3 volumeToUVWMul = sdf.LocalToUVWMul;
Vector3 volumeToUVWAdd = sdf.LocalToUVWAdd + (localVolumeBounds.Minimum + volumeLocalBoundsExtent) * sdf.LocalToUVWMul;
// Add model data for the GPU buffer
uint16 modelIndex = _modelsBufferCount++;
ModelRasterizeData modelData;
Matrix::Transpose(worldToVolume, modelData.WorldToVolume);
Matrix::Transpose(volumeToWorld, modelData.VolumeToWorld);
modelData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent;
modelData.VolumeToUVWMul = volumeToUVWMul;
modelData.VolumeToUVWAdd = volumeToUVWAdd;
modelData.MipOffset = (float)mipLevelIndex;
modelData.DecodeMul = 2.0f * sdf.MaxDistance;
modelData.DecodeAdd = -sdf.MaxDistance;
_modelsBuffer->Write(modelData);
_modelsTextures.Add(sdf.Texture->ViewVolume());
// Add object data for the GPU buffer
uint16 objectIndex = _objectsBufferCount++;
ObjectRasterizeData objectData;
Matrix::Transpose(worldToVolume, objectData.WorldToVolume);
Matrix::Transpose(volumeToWorld, objectData.VolumeToWorld);
objectData.VolumeLocalBoundsExtent = volumeLocalBoundsExtent;
objectData.VolumeToUVWMul = volumeToUVWMul;
objectData.VolumeToUVWAdd = volumeToUVWAdd;
objectData.MipOffset = (float)mipLevelIndex;
objectData.DecodeMul = 2.0f * sdf.MaxDistance;
objectData.DecodeAdd = -sdf.MaxDistance;
_objectsBuffer->Write(objectData);
_objectsTextures.Add(sdf.Texture->ViewVolume());
// Inject object into the intersecting cascade chunks
_sdfData->ObjectTypes.Add(actor->GetTypeHandle());
@@ -799,7 +848,7 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas
chunk = &chunks[key];
}
chunk->Models[chunk->ModelsCount++] = modelIndex;
chunk->Models[chunk->ModelsCount++] = objectIndex;
}
}
}
@@ -812,3 +861,69 @@ void GlobalSignDistanceFieldPass::RasterizeModelSDF(Actor* actor, const ModelBas
_sdfData->SDFTextures.Add(sdf.Texture);
}
}
void GlobalSignDistanceFieldPass::RasterizeHeightfield(Actor* actor, GPUTexture* heightfield, const Matrix& localToWorld, const BoundingBox& objectBounds, const Vector4& localToUV)
{
if (!heightfield || heightfield->ResidentMipLevels() == 0)
return;
// Setup object data
BoundingBox objectBoundsCascade;
const float objectMargin = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN;
Vector3::Clamp(objectBounds.Minimum - objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Minimum);
Vector3::Subtract(objectBoundsCascade.Minimum, _cascadeBounds.Minimum, objectBoundsCascade.Minimum);
Vector3::Clamp(objectBounds.Maximum + objectMargin, _cascadeBounds.Minimum, _cascadeBounds.Maximum, objectBoundsCascade.Maximum);
Vector3::Subtract(objectBoundsCascade.Maximum, _cascadeBounds.Minimum, objectBoundsCascade.Maximum);
const float chunkSize = _voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
const Int3 objectChunkMin(objectBoundsCascade.Minimum / chunkSize);
const Int3 objectChunkMax(objectBoundsCascade.Maximum / chunkSize);
// Add object data for the GPU buffer
uint16 objectIndex = _objectsBufferCount++;
ObjectRasterizeData objectData;
Matrix worldToLocal;
Matrix::Invert(localToWorld, worldToLocal);
Matrix::Transpose(worldToLocal, objectData.WorldToVolume);
Matrix::Transpose(localToWorld, objectData.VolumeToWorld);
objectData.VolumeToUVWMul = Vector3(localToUV.X, 1.0f, localToUV.Y);
objectData.VolumeToUVWAdd = Vector3(localToUV.Z, 0.0f, localToUV.W);
objectData.MipOffset = (float)_cascadeIndex * 0.5f; // Use lower-quality mip for far cascades
_objectsBuffer->Write(objectData);
_objectsTextures.Add(heightfield->View());
// Inject object into the intersecting cascade chunks
_sdfData->ObjectTypes.Add(actor->GetTypeHandle());
RasterizeChunkKey key;
auto& chunks = ChunksCache;
const bool dynamic = !GLOBAL_SDF_ACTOR_IS_STATIC(actor);
for (key.Coord.Z = objectChunkMin.Z; key.Coord.Z <= objectChunkMax.Z; key.Coord.Z++)
{
for (key.Coord.Y = objectChunkMin.Y; key.Coord.Y <= objectChunkMax.Y; key.Coord.Y++)
{
for (key.Coord.X = objectChunkMin.X; key.Coord.X <= objectChunkMax.X; key.Coord.X++)
{
key.Layer = 0;
key.Hash = key.Coord.Z * (RasterizeChunkKeyHashResolution * RasterizeChunkKeyHashResolution) + key.Coord.Y * RasterizeChunkKeyHashResolution + key.Coord.X;
RasterizeChunk* chunk = &chunks[key];
chunk->Dynamic |= dynamic;
// Move to the next layer if chunk has overflown
while (chunk->HeightfieldsCount == GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT)
{
key.NextLayer();
chunk = &chunks[key];
}
chunk->Heightfields[chunk->HeightfieldsCount++] = objectIndex;
}
}
}
// Track streaming for textures used in static chunks to invalidate cache
if (!dynamic && heightfield->ResidentMipLevels() != heightfield->MipLevels() && !_sdfData->SDFTextures.Contains(heightfield))
{
heightfield->Deleted.Bind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureDeleted>(_sdfData);
heightfield->ResidentMipsChanged.Bind<GlobalSignDistanceFieldCustomBuffer, &GlobalSignDistanceFieldCustomBuffer::OnSDFTextureResidentMipsChanged>(_sdfData);
_sdfData->SDFTextures.Add(heightfield);
}
}
@@ -33,6 +33,7 @@ private:
GPUPipelineState* _psDebug = nullptr;
GPUShaderProgramCS* _csRasterizeModel0 = nullptr;
GPUShaderProgramCS* _csRasterizeModel1 = nullptr;
GPUShaderProgramCS* _csRasterizeHeightfield = nullptr;
GPUShaderProgramCS* _csClearChunk = nullptr;
GPUShaderProgramCS* _csGenerateMip0 = nullptr;
GPUShaderProgramCS* _csGenerateMip1 = nullptr;
@@ -40,9 +41,10 @@ private:
GPUConstantBuffer* _cb1 = nullptr;
// Rasterization cache
class DynamicStructuredBuffer* _modelsBuffer = nullptr;
Array<GPUTextureView*> _modelsTextures;
uint16 _modelsBufferCount;
class DynamicStructuredBuffer* _objectsBuffer = nullptr;
Array<GPUTextureView*> _objectsTextures;
uint16 _objectsBufferCount;
int32 _cascadeIndex;
float _voxelSize;
BoundingBox _cascadeBounds;
class GlobalSignDistanceFieldCustomBuffer* _sdfData;
@@ -76,6 +78,8 @@ public:
// Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF.
void RasterizeModelSDF(Actor* actor, const ModelBase::SDFData& sdf, const Matrix& localToWorld, const BoundingBox& objectBounds);
void RasterizeHeightfield(Actor* actor, GPUTexture* heightfield, const Matrix& localToWorld, const BoundingBox& objectBounds, const Vector4& localToUV);
private:
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj);
+20 -1
View File
@@ -13,6 +13,7 @@
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
Terrain::Terrain(const SpawnParams& params)
: PhysicsColliderActor(params)
@@ -506,7 +507,25 @@ void Terrain::Draw(RenderContext& renderContext)
if (drawModes == DrawPass::None)
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Terrain rendering to Global SDF
{
const float chunkSize = TERRAIN_UNITS_PER_VERTEX * (float)_chunkSize;
const float posToUV = 0.25f / chunkSize;
Vector4 localToUV(posToUV, posToUV, 0.0f, 0.0f);
Matrix localToWorld;
for (const TerrainPatch* patch : _patches)
{
if (!patch->Heightmap)
continue;
Transform patchTransform;
patchTransform.Translation = patch->_offset + Vector3(0, patch->_yOffset, 0);
patchTransform.Orientation = Quaternion::Identity;
patchTransform.Scale = Vector3(1.0f, patch->_yHeight, 1.0f);
patchTransform = _transform.LocalToWorld(patchTransform);
patchTransform.GetWorld(localToWorld);
GlobalSignDistanceFieldPass::Instance()->RasterizeHeightfield(this, patch->Heightmap->GetTexture(), localToWorld, patch->_bounds, localToUV);
}
return;
}
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
return; // TODO: Terrain rendering to Global Surface Atlas
+63 -12
View File
@@ -5,10 +5,11 @@
#include "./Flax/GlobalSignDistanceField.hlsl"
#define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28
#define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2
#define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8
#define GLOBAL_SDF_MIP_GROUP_SIZE 4
struct ModelRasterizeData
struct ObjectRasterizeData
{
float4x4 WorldToVolume; // TODO: use 3x4 matrix
float4x4 VolumeToWorld; // TODO: use 3x4 matrix
@@ -33,13 +34,14 @@ META_CB_BEGIN(1, ModelsRasterizeData)
int3 ChunkCoord;
float MaxDistance;
float3 CascadeCoordToPosMul;
int ModelsCount;
int ObjectsCount;
float3 CascadeCoordToPosAdd;
int CascadeResolution;
float2 Padding0;
float Padding0;
float CascadeVoxelSize;
int CascadeMipResolution;
int CascadeMipFactor;
uint4 Models[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4];
uint4 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4];
META_CB_END
float CombineDistanceToSDF(float sdf, float distanceToSDF)
@@ -54,13 +56,18 @@ float CombineDistanceToSDF(float sdf, float distanceToSDF)
return sqrt(Square(max(sdf, 0)) + Square(distanceToSDF));
}
#if defined(_CS_RasterizeModel)
#if defined(_CS_RasterizeModel) || defined(_CS_RasterizeHeightfield)
RWTexture3D<float> GlobalSDFTex : register(u0);
StructuredBuffer<ModelRasterizeData> ModelsBuffer : register(t0);
Texture3D<float> ModelSDFTex[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1);
StructuredBuffer<ObjectRasterizeData> ObjectsBuffer : register(t0);
float DistanceToModelSDF(float minDistance, ModelRasterizeData modelData, Texture3D<float> modelSDFTex, float3 worldPos)
#endif
#if defined(_CS_RasterizeModel)
Texture3D<float> ObjectsTextures[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT] : register(t1);
float DistanceToModelSDF(float minDistance, ObjectRasterizeData modelData, Texture3D<float> modelSDFTex, float3 worldPos)
{
// Compute SDF volume UVs and distance in world-space to the volume bounds
float3 volumePos = mul(float4(worldPos, 1), modelData.WorldToVolume).xyz;
@@ -98,11 +105,55 @@ void CS_RasterizeModel(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_D
#if READ_SDF
minDistance *= GlobalSDFTex[voxelCoord];
#endif
for (int i = 0; i < ModelsCount; i++)
for (uint i = 0; i < ObjectsCount; i++)
{
ModelRasterizeData modelData = ModelsBuffer[Models[i / 4][i % 4]];
float modelDistance = DistanceToModelSDF(minDistance, modelData, ModelSDFTex[i], voxelWorldPos);
minDistance = min(minDistance, modelDistance);
ObjectRasterizeData objectData = ObjectsBuffer[Objects[i / 4][i % 4]];
float objectDistance = DistanceToModelSDF(minDistance, objectData, ObjectsTextures[i], voxelWorldPos);
minDistance = min(minDistance, objectDistance);
}
GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance);
}
#endif
#if defined(_CS_RasterizeHeightfield)
Texture2D<float4> ObjectsTextures[GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT] : register(t1);
// Compute shader for rasterizing heightfield into Global SDF
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 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
{
uint3 voxelCoord = ChunkCoord + DispatchThreadId;
float3 voxelWorldPos = voxelCoord * CascadeCoordToPosMul + CascadeCoordToPosAdd;
float minDistance = MaxDistance * GlobalSDFTex[voxelCoord];
float thickness = CascadeVoxelSize * -4;
for (uint i = 0; i < ObjectsCount; i++)
{
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), objectData.WorldToVolume).xyz;
float3 volumeUV = volumePos * objectData.VolumeToUVWMul + objectData.VolumeToUVWAdd;
float2 heightfieldUV = float2(volumeUV.x, volumeUV.z);
// Sample the heightfield
float4 heightmapValue = ObjectsTextures[i].SampleLevel(SamplerLinearClamp, heightfieldUV, objectData.MipOffset);
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);
float3 heightfieldPosition = mul(float4(position, 1), objectData.VolumeToWorld).xyz;
float3 heightfieldNormal = normalize(float3(objectData.VolumeToWorld[0].y, objectData.VolumeToWorld[1].y, objectData.VolumeToWorld[2].y));
// Calculate distance from voxel center to the heightfield
float objectDistance = dot(heightfieldNormal, voxelWorldPos - heightfieldPosition);
if (objectDistance < thickness)
objectDistance = thickness - objectDistance;
minDistance = min(minDistance, objectDistance);
}
GlobalSDFTex[voxelCoord] = saturate(minDistance / MaxDistance);
}