Add support for rasterizing terrain into Global SDF as heightfield
This commit is contained in:
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user