Add **Model SDF baking on GPU** via Compute Shader

This commit is contained in:
Wojtek Figat
2024-05-29 14:53:13 +02:00
parent 5f4c57d3eb
commit 53d77d3421
11 changed files with 793 additions and 92 deletions

View File

@@ -8,15 +8,20 @@
#include "Engine/Core/RandomStream.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Ray.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Platform/ConditionVariable.h"
#include "Engine/Profiler/Profiler.h"
#include "Engine/Threading/JobSystem.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Shader.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Engine/Units.h"
#if USE_EDITOR
@@ -71,7 +76,261 @@ ModelSDFMip::ModelSDFMip(int32 mipIndex, const TextureMipData& mip)
{
}
bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold)
class GPUModelSDFTask : public GPUTask
{
ConditionVariable* _signal;
AssetReference<Shader> _shader;
Model* _inputModel;
ModelData* _modelData;
int32 _lodIndex;
Int3 _resolution;
ModelBase::SDFData* _sdf;
GPUBuffer *_sdfSrc, *_sdfDst;
GPUTexture* _sdfResult;
Float3 _xyzToLocalMul, _xyzToLocalAdd;
const uint32 ThreadGroupSize = 64;
PACK_STRUCT(struct alignas(GPU_SHADER_DATA_ALIGNMENT) Data
{
Int3 Resolution;
uint32 ResolutionSize;
float MaxDistance;
uint32 VertexStride;
int32 Index16bit;
uint32 TriangleCount;
Float3 VoxelToPosMul;
float WorldUnitsPerVoxel;
Float3 VoxelToPosAdd;
uint32 ThreadGroupsX;
});
public:
GPUModelSDFTask(ConditionVariable& signal, Model* inputModel, ModelData* modelData, int32 lodIndex, const Int3& resolution, ModelBase::SDFData* sdf, GPUTexture* sdfResult, const Float3& xyzToLocalMul, const Float3& xyzToLocalAdd)
: GPUTask(Type::Custom)
, _signal(&signal)
, _shader(Content::LoadAsyncInternal<Shader>(TEXT("Shaders/SDF")))
, _inputModel(inputModel)
, _modelData(modelData)
, _lodIndex(lodIndex)
, _resolution(resolution)
, _sdf(sdf)
, _sdfSrc(GPUBuffer::New())
, _sdfDst(GPUBuffer::New())
, _sdfResult(sdfResult)
, _xyzToLocalMul(xyzToLocalMul)
, _xyzToLocalAdd(xyzToLocalAdd)
{
}
~GPUModelSDFTask()
{
SAFE_DELETE_GPU_RESOURCE(_sdfSrc);
SAFE_DELETE_GPU_RESOURCE(_sdfDst);
}
Result run(GPUTasksContext* tasksContext) override
{
PROFILE_GPU_CPU("GPUModelSDFTask");
GPUContext* context = tasksContext->GPU;
// Allocate resources
if (_shader == nullptr || _shader->WaitForLoaded())
return Result::Failed;
GPUShader* shader = _shader->GetShader();
const uint32 resolutionSize = _resolution.X * _resolution.Y * _resolution.Z;
auto desc = GPUBufferDescription::Typed(resolutionSize, PixelFormat::R32_UInt, true);
// TODO: use transient texture (single frame)
if (_sdfSrc->Init(desc) || _sdfDst->Init(desc))
return Result::Failed;
auto cb = shader->GetCB(0);
Data data;
data.Resolution = _resolution;
data.ResolutionSize = resolutionSize;
data.MaxDistance = _sdf->MaxDistance;
data.WorldUnitsPerVoxel = _sdf->WorldUnitsPerVoxel;
data.VoxelToPosMul = _xyzToLocalMul;
data.VoxelToPosAdd = _xyzToLocalAdd;
// Dispatch in 1D and fallback to 2D when using large resolution
Int3 threadGroups(Math::CeilToInt((float)resolutionSize / ThreadGroupSize), 1, 1);
if (threadGroups.X > GPU_MAX_CS_DISPATCH_THREAD_GROUPS)
{
const uint32 groups = threadGroups.X;
threadGroups.X = Math::CeilToInt(Math::Sqrt((float)groups));
threadGroups.Y = Math::CeilToInt((float)groups / threadGroups.X);
}
data.ThreadGroupsX = threadGroups.X;
// Init SDF volume
context->BindCB(0, cb);
context->UpdateCB(cb, &data);
context->BindUA(0, _sdfSrc->View());
context->Dispatch(shader->GetCS("CS_Init"), threadGroups.X, threadGroups.Y, threadGroups.Z);
// Rendering input triangles into the SDF volume
if (_inputModel)
{
PROFILE_GPU_CPU_NAMED("Rasterize");
const ModelLOD& lod = _inputModel->LODs[Math::Clamp(_lodIndex, _inputModel->HighestResidentLODIndex(), _inputModel->LODs.Count() - 1)];
GPUBuffer *vbTemp = nullptr, *ibTemp = nullptr;
for (int32 i = 0; i < lod.Meshes.Count(); i++)
{
const Mesh& mesh = lod.Meshes[i];
const MaterialSlot& materialSlot = _inputModel->MaterialSlots[mesh.GetMaterialSlotIndex()];
if (materialSlot.Material && !materialSlot.Material->WaitForLoaded())
{
// Skip transparent materials
if (materialSlot.Material->GetInfo().BlendMode != MaterialBlendMode::Opaque)
continue;
}
GPUBuffer* vb = mesh.GetVertexBuffer(0);
GPUBuffer* ib = mesh.GetIndexBuffer();
data.Index16bit = mesh.Use16BitIndexBuffer() ? 1 : 0;
data.VertexStride = vb->GetStride();
data.TriangleCount = mesh.GetTriangleCount();
const uint32 groups = Math::CeilToInt((float)data.TriangleCount / ThreadGroupSize);
if (groups > GPU_MAX_CS_DISPATCH_THREAD_GROUPS)
{
// TODO: support larger meshes via 2D dispatch
LOG(Error, "Not supported mesh with {} triangles.", data.TriangleCount);
continue;
}
context->UpdateCB(cb, &data);
if (!EnumHasAllFlags(vb->GetDescription().Flags, GPUBufferFlags::RawBuffer | GPUBufferFlags::ShaderResource))
{
desc = GPUBufferDescription::Raw(vb->GetSize(), GPUBufferFlags::ShaderResource);
// TODO: use transient buffer (single frame)
if (!vbTemp)
vbTemp = GPUBuffer::New();
vbTemp->Init(desc);
context->CopyBuffer(vbTemp, vb, desc.Size);
vb = vbTemp;
}
if (!EnumHasAllFlags(ib->GetDescription().Flags, GPUBufferFlags::RawBuffer | GPUBufferFlags::ShaderResource))
{
desc = GPUBufferDescription::Raw(ib->GetSize(), GPUBufferFlags::ShaderResource);
// TODO: use transient buffer (single frame)
if (!ibTemp)
ibTemp = GPUBuffer::New();
ibTemp->Init(desc);
context->CopyBuffer(ibTemp, ib, desc.Size);
ib = ibTemp;
}
context->BindSR(0, vb->View());
context->BindSR(1, ib->View());
context->Dispatch(shader->GetCS("CS_RasterizeTriangle"), groups, 1, 1);
}
SAFE_DELETE_GPU_RESOURCE(vbTemp);
SAFE_DELETE_GPU_RESOURCE(ibTemp);
}
else if (_modelData)
{
PROFILE_GPU_CPU_NAMED("Rasterize");
const ModelLodData& lod = _modelData->LODs[Math::Clamp(_lodIndex, 0, _modelData->LODs.Count() - 1)];
auto vb = GPUBuffer::New();
auto ib = GPUBuffer::New();
for (int32 i = 0; i < lod.Meshes.Count(); i++)
{
const MeshData* mesh = lod.Meshes[i];
const MaterialSlotEntry& materialSlot = _modelData->Materials[mesh->MaterialSlotIndex];
auto material = Content::LoadAsync<MaterialBase>(materialSlot.AssetID);
if (material && !material->WaitForLoaded())
{
// Skip transparent materials
if (material->GetInfo().BlendMode != MaterialBlendMode::Opaque)
continue;
}
data.Index16bit = 0;
data.VertexStride = sizeof(Float3);
data.TriangleCount = mesh->Indices.Count() / 3;
const uint32 groups = Math::CeilToInt((float)data.TriangleCount / ThreadGroupSize);
if (groups > GPU_MAX_CS_DISPATCH_THREAD_GROUPS)
{
// TODO: support larger meshes via 2D dispatch
LOG(Error, "Not supported mesh with {} triangles.", data.TriangleCount);
continue;
}
context->UpdateCB(cb, &data);
desc = GPUBufferDescription::Raw(mesh->Positions.Count() * sizeof(Float3), GPUBufferFlags::ShaderResource);
desc.InitData = mesh->Positions.Get();
// TODO: use transient buffer (single frame)
vb->Init(desc);
desc = GPUBufferDescription::Raw(mesh->Indices.Count() * sizeof(uint32), GPUBufferFlags::ShaderResource);
desc.InitData = mesh->Indices.Get();
// TODO: use transient buffer (single frame)
ib->Init(desc);
context->BindSR(0, vb->View());
context->BindSR(1, ib->View());
context->Dispatch(shader->GetCS("CS_RasterizeTriangle"), groups, 1, 1);
}
SAFE_DELETE_GPU_RESOURCE(vb);
SAFE_DELETE_GPU_RESOURCE(ib);
}
// Convert SDF volume data back to floats
context->Dispatch(shader->GetCS("CS_Resolve"), threadGroups.X, threadGroups.Y, threadGroups.Z);
// Run linear flood-fill loop to populate all voxels with valid distances (spreads the initial values from triangles rasterization)
{
PROFILE_GPU_CPU_NAMED("FloodFill");
auto csFloodFill = shader->GetCS("CS_FloodFill");
const int32 floodFillIterations = Math::Max(_resolution.MaxValue() / 2 + 1, 8);
for (int32 floodFill = 0; floodFill < floodFillIterations; floodFill++)
{
context->ResetUA();
context->BindUA(0, _sdfDst->View());
context->BindSR(0, _sdfSrc->View());
context->Dispatch(csFloodFill, threadGroups.X, threadGroups.Y, threadGroups.Z);
Swap(_sdfSrc, _sdfDst);
}
}
// Encode SDF values into output storage
context->ResetUA();
context->BindSR(0, _sdfSrc->View());
// TODO: update GPU SDF texture within this task to skip additional CPU->GPU copy
auto sdfTextureDesc = GPUTextureDescription::New3D(_resolution.X, _resolution.Y, _resolution.Z, PixelFormat::R16_UNorm, GPUTextureFlags::UnorderedAccess | GPUTextureFlags::RenderTarget);
// TODO: use transient texture (single frame)
auto sdfTexture = GPUTexture::New();
sdfTexture->Init(sdfTextureDesc);
context->BindUA(1, sdfTexture->ViewVolume());
context->Dispatch(shader->GetCS("CS_Encode"), threadGroups.X, threadGroups.Y, threadGroups.Z);
// Copy result data into readback buffer
if (_sdfResult)
{
sdfTextureDesc = sdfTextureDesc.ToStagingReadback();
_sdfResult->Init(sdfTextureDesc);
context->CopyTexture(_sdfResult, 0, 0, 0, 0, sdfTexture, 0);
}
SAFE_DELETE_GPU_RESOURCE(sdfTexture);
return Result::Ok;
}
void OnSync() override
{
GPUTask::OnSync();
_signal->NotifyOne();
}
void OnFail() override
{
GPUTask::OnFail();
_signal->NotifyOne();
}
void OnCancel() override
{
GPUTask::OnCancel();
_signal->NotifyOne();
}
};
bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float resolutionScale, int32 lodIndex, ModelBase::SDFData* outputSDF, MemoryWriteStream* outputStream, const StringView& assetName, float backfacesThreshold, bool useGPU)
{
PROFILE_CPU();
auto startTime = Platform::GetTimeSeconds();
@@ -127,7 +386,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
*(uint8*)ptr = (uint8)v;
};
}
GPUTextureDescription textureDesc = GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource, mipCount);
auto textureDesc = GPUTextureDescription::New3D(resolution.X, resolution.Y, resolution.Z, format, GPUTextureFlags::ShaderResource, mipCount);
if (outputSDF)
{
*outputSDF = sdf;
@@ -143,19 +402,10 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
#endif
}
// TODO: support GPU to generate model SDF on-the-fly (if called during rendering)
// Setup acceleration structure for fast ray tracing the mesh triangles
MeshAccelerationStructure scene;
if (inputModel)
scene.Add(inputModel, lodIndex);
else if (modelData)
scene.Add(modelData, lodIndex);
scene.BuildBVH();
// Allocate memory for the distant field
const int32 voxelsSize = resolution.X * resolution.Y * resolution.Z * formatStride;
void* voxels = Allocator::Allocate(voxelsSize);
BytesContainer voxels;
voxels.Allocate(voxelsSize);
Float3 xyzToLocalMul = uvwToLocalMul / Float3(resolution - 1);
Float3 xyzToLocalAdd = uvwToLocalAdd;
const Float2 encodeMAD(0.5f / sdf.MaxDistance * formatMaxValue, 0.5f * formatMaxValue);
@@ -163,74 +413,125 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
int32 voxelSizeSum = voxelsSize;
// TODO: use optimized sparse storage for SDF data as hierarchical bricks as in papers below:
// https://gpuopen.com/gdc-presentations/2023/GDC-2023-Sparse-Distance-Fields-For-Games.pdf + https://www.youtube.com/watch?v=iY15xhuuHPQ&ab_channel=AMD
// https://graphics.pixar.com/library/IrradianceAtlas/paper.pdf
// http://maverick.inria.fr/Membres/Cyril.Crassin/thesis/CCrassinThesis_EN_Web.pdf
// http://ramakarl.com/pdfs/2016_Hoetzlein_GVDB.pdf
// https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf
// Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel
constexpr int32 sampleCount = 12;
Float3 sampleDirections[sampleCount];
// Check if run SDF generation on a GPU via Compute Shader or on a Job System
useGPU &= GPUDevice::Instance
&& GPUDevice::Instance->GetState() == GPUDevice::DeviceState::Ready
&& GPUDevice::Instance->Limits.HasCompute
&& format == PixelFormat::R16_UNorm
&& !IsInMainThread() // TODO: support GPU to generate model SDF on-the-fly directly into virtual model (if called during rendering)
&& resolution.MaxValue() > 8;
if (useGPU)
{
RandomStream rand;
sampleDirections[0] = Float3::Up;
sampleDirections[1] = Float3::Down;
sampleDirections[2] = Float3::Left;
sampleDirections[3] = Float3::Right;
sampleDirections[4] = Float3::Forward;
sampleDirections[5] = Float3::Backward;
for (int32 i = 6; i < sampleCount; i++)
sampleDirections[i] = rand.GetUnitVector();
}
Function<void(int32)> sdfJob = [&sdf, &resolution, &backfacesThreshold, sampleDirections, &sampleCount, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z)
{
PROFILE_CPU_NAMED("Model SDF Job");
Real hitDistance;
Vector3 hitNormal, hitPoint;
Triangle hitTriangle;
const int32 zAddress = resolution.Y * resolution.X * z;
for (int32 y = 0; y < resolution.Y; y++)
PROFILE_CPU_NAMED("GPU");
// TODO: skip using sdfResult and downloading SDF from GPU when updating virtual model
auto sdfResult = GPUTexture::New();
// Run SDF generation via GPU async task
ConditionVariable signal;
CriticalSection mutex;
Task* task = New<GPUModelSDFTask>(signal, inputModel, modelData, lodIndex, resolution, &sdf, sdfResult, xyzToLocalMul, xyzToLocalAdd);
task->Start();
mutex.Lock();
signal.Wait(mutex);
mutex.Unlock();
bool failed = task->IsFailed();
// Gather result data from GPU to CPU
if (!failed && sdfResult)
{
const int32 yAddress = resolution.X * y + zAddress;
for (int32 x = 0; x < resolution.X; x++)
{
Real minDistance = sdf.MaxDistance;
Vector3 voxelPos = Float3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd;
// Point query to find the distance to the closest surface
scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle);
// Raycast samples around voxel to count triangle backfaces hit
int32 hitBackCount = 0, hitCount = 0;
for (int32 sample = 0; sample < sampleCount; sample++)
{
Ray sampleRay(voxelPos, sampleDirections[sample]);
sampleRay.Position -= sampleRay.Direction * 0.0001f; // Apply small margin
if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle))
{
if (hitDistance < minDistance)
minDistance = hitDistance;
hitCount++;
const bool backHit = Float3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0;
if (backHit)
hitBackCount++;
}
}
float distance = (float)minDistance;
// TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry
// if ((float)hitBackCount > (float)hitCount * 0.3f && hitCount != 0)
if ((float)hitBackCount > (float)sampleCount * backfacesThreshold && hitCount != 0)
{
// Voxel is inside the geometry so turn it into negative distance to the surface
distance *= -1;
}
const int32 xAddress = x + yAddress;
formatWrite((byte*)voxels + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y);
}
TextureMipData mipData;
const uint32 rowPitch = resolution.X * formatStride;
failed = sdfResult->GetData(0, 0, mipData, rowPitch);
failed |= voxels.Length() != mipData.Data.Length();
if (!failed)
voxels = mipData.Data;
}
};
JobSystem::Execute(sdfJob, resolution.Z);
SAFE_DELETE_GPU_RESOURCE(sdfResult);
if (failed)
return true;
}
else
{
// Setup acceleration structure for fast ray tracing the mesh triangles
MeshAccelerationStructure scene;
if (inputModel)
scene.Add(inputModel, lodIndex);
else if (modelData)
scene.Add(modelData, lodIndex);
scene.BuildBVH();
// Brute-force for each voxel to calculate distance to the closest triangle with point query and distance sign by raycasting around the voxel
constexpr int32 sampleCount = 12;
Float3 sampleDirections[sampleCount];
{
RandomStream rand;
sampleDirections[0] = Float3::Up;
sampleDirections[1] = Float3::Down;
sampleDirections[2] = Float3::Left;
sampleDirections[3] = Float3::Right;
sampleDirections[4] = Float3::Forward;
sampleDirections[5] = Float3::Backward;
for (int32 i = 6; i < sampleCount; i++)
sampleDirections[i] = rand.GetUnitVector();
}
Function<void(int32)> sdfJob = [&sdf, &resolution, &backfacesThreshold, sampleDirections, &sampleCount, &scene, &voxels, &xyzToLocalMul, &xyzToLocalAdd, &encodeMAD, &formatStride, &formatWrite](int32 z)
{
PROFILE_CPU_NAMED("Model SDF Job");
Real hitDistance;
Vector3 hitNormal, hitPoint;
Triangle hitTriangle;
const int32 zAddress = resolution.Y * resolution.X * z;
for (int32 y = 0; y < resolution.Y; y++)
{
const int32 yAddress = resolution.X * y + zAddress;
for (int32 x = 0; x < resolution.X; x++)
{
Real minDistance = sdf.MaxDistance;
Vector3 voxelPos = Float3((float)x, (float)y, (float)z) * xyzToLocalMul + xyzToLocalAdd;
// Point query to find the distance to the closest surface
scene.PointQuery(voxelPos, minDistance, hitPoint, hitTriangle);
// Raycast samples around voxel to count triangle backfaces hit
int32 hitBackCount = 0, hitCount = 0;
for (int32 sample = 0; sample < sampleCount; sample++)
{
Ray sampleRay(voxelPos, sampleDirections[sample]);
sampleRay.Position -= sampleRay.Direction * 0.0001f; // Apply small margin
if (scene.RayCast(sampleRay, hitDistance, hitNormal, hitTriangle))
{
if (hitDistance < minDistance)
minDistance = hitDistance;
hitCount++;
const bool backHit = Float3::Dot(sampleRay.Direction, hitTriangle.GetNormal()) > 0;
if (backHit)
hitBackCount++;
}
}
float distance = (float)minDistance;
// TODO: surface thickness threshold? shift reduce distance for all voxels by something like 0.01 to enlarge thin geometry
// if ((float)hitBackCount > (float)hitCount * 0.3f && hitCount != 0)
if ((float)hitBackCount > (float)sampleCount * backfacesThreshold && hitCount != 0)
{
// Voxel is inside the geometry so turn it into negative distance to the surface
distance *= -1;
}
const int32 xAddress = x + yAddress;
formatWrite(voxels.Get() + xAddress * formatStride, distance * encodeMAD.X + encodeMAD.Y);
}
}
};
JobSystem::Execute(sdfJob, resolution.Z);
}
// Cache SDF data on a CPU
if (outputStream)
@@ -240,20 +541,19 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
outputStream->WriteBytes(&data, sizeof(data));
ModelSDFMip mipData(0, resolution.X * formatStride, voxelsSize);
outputStream->WriteBytes(&mipData, sizeof(mipData));
outputStream->WriteBytes(voxels, voxelsSize);
outputStream->WriteBytes(voxels.Get(), voxelsSize);
}
// Upload data to the GPU
if (outputSDF)
{
BytesContainer data;
data.Link((byte*)voxels, voxelsSize);
auto task = outputSDF->Texture->UploadMipMapAsync(data, 0, resolution.X * formatStride, voxelsSize, true);
auto task = outputSDF->Texture->UploadMipMapAsync(voxels, 0, resolution.X * formatStride, voxelsSize, true);
if (task)
task->Start();
}
// Generate mip maps
void* voxelsMipSrc = voxels.Get();
void* voxelsMip = nullptr;
for (int32 mipLevel = 1; mipLevel < mipCount; mipLevel++)
{
@@ -263,7 +563,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
voxelsMip = Allocator::Allocate(voxelsMipSize);
// Downscale mip
Function<void(int32)> mipJob = [&voxelsMip, &voxels, &resolution, &resolutionMip, &encodeMAD, &decodeMAD, &formatStride, &formatRead, &formatWrite](int32 z)
Function<void(int32)> mipJob = [&voxelsMip, &voxelsMipSrc, &resolution, &resolutionMip, &encodeMAD, &decodeMAD, &formatStride, &formatRead, &formatWrite](int32 z)
{
PROFILE_CPU_NAMED("Model SDF Mip Job");
const int32 zAddress = resolutionMip.Y * resolutionMip.X * z;
@@ -284,7 +584,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
for (int32 dx = 0; dx < 2; dx++)
{
const int32 dxAddress = (x * 2 + dx) + dyAddress;
const float d = formatRead((byte*)voxels + dxAddress * formatStride) * decodeMAD.X + decodeMAD.Y;
const float d = formatRead((byte*)voxelsMipSrc + dxAddress * formatStride) * decodeMAD.X + decodeMAD.Y;
distance += d;
}
}
@@ -318,12 +618,11 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, ModelData* modelData, float
// Go down
voxelSizeSum += voxelsSize;
Swap(voxelsMip, voxels);
Swap(voxelsMip, voxelsMipSrc);
resolution = resolutionMip;
}
Allocator::Free(voxelsMip);
Allocator::Free(voxels);
#if !BUILD_RELEASE
auto endTime = Platform::GetTimeSeconds();