Skip to content

Commit 849c780

Browse files
authored
Gaussians CPU sorting (#2844)
1 parent 0504ce6 commit 849c780

File tree

11 files changed

+140
-52
lines changed

11 files changed

+140
-52
lines changed

application/F3DOptionsTools.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ static inline const std::array<CLIGroup, 8> CLIOptions = {{
183183
{"raytracing-denoise", "d", "Denoise the image", "<bool>", "1"} } },
184184
#endif
185185
{"PostFX (OpenGL)",
186-
{ {"blending", "p", R"(Select translucency blending mode ("none", "ddp", "sort" or "stochastic"))", "<string>", "ddp"},
186+
{ {"blending", "p", R"(Select translucency blending mode ("none", "ddp", "sort", "sort_cpu" or "stochastic"))", "<string>", "ddp"},
187187
{"translucency-support", "", "Enable translucency blending (deprecated)", "<bool>", "1"},
188188
{"ambient-occlusion", "q", "Enable ambient occlusion providing approximate shadows for better depth perception, implemented using SSAO", "<bool>", "1"},
189189
{"anti-aliasing", "a", R"(Select anti-aliasing method ("none", "fxaa", "ssaa" or "taa"))", "<string>", "fxaa"},

application/testing/tests.native.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ f3d_test(NAME TestVerboseQuakeMDLInvalid ARGS --verbose DATA w_medkit_hl.mdl REG
4242

4343
if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20231102)
4444
f3d_test(NAME TestSPLAT DATA small.splat ARGS -osy --up=-Y --point-sprites-absolute-size --point-sprites-size=1)
45+
f3d_test(NAME TestSPLATSortCPU DATA small.splat ARGS -osy --point-sprites=gaussian --up=-Y --point-sprites-absolute-size --point-sprites-size=1 --blending=sort_cpu --camera-position=2,0,0)
4546
f3d_test(NAME TestSPZ DATA hornedlizard_small_d0.spz ARGS -sy --point-sprites-absolute-size --point-sprites-size=1)
4647

4748
set(_splat_args -sy --up=-Y --point-sprites-absolute-size --point-sprites-size=1 --camera-position=-2.00335,1.09654,-0.459485 --camera-focal-point=-0.796712,2.22795,0.705742)

doc/libf3d/03-OPTIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ CLI: `--blending`.
260260

261261
### `render.effect.blending.mode` (_string_, default: `ddp`)
262262

263-
Set the blending technique. Valid options are: `ddp` (dual depth peeling, quality), `sort` (only for gaussians), `stochastic` (fast)
263+
Set the blending technique. Valid options are: `ddp` (dual depth peeling, quality), `sort` (only for gaussians), `sort_cpu` (only for gaussians, slow), `stochastic` (fast)
264264

265265
CLI: `--blending`.
266266

doc/user/03-OPTIONS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,12 +497,13 @@ _Denoise_ the image when using raytracing.
497497
### `-p`, `--blending` (_string_, default: `ddp`)
498498

499499
Enable _translucency blending support_.
500-
This is a technique used to correctly render translucent objects (`ddp`: dual depth peeling for quality, `sort`: for gaussians, `stochastic`: fast).
500+
This is a technique used to correctly render translucent objects (`ddp`: dual depth peeling for quality, `sort`: for gaussians, `sort_cpu`: for gaussians, `stochastic`: fast).
501501

502502
> [!WARNING]
503503
> `stochastic` is introducing a lot of noise with strong translucency.
504504
> It works better when combined with temporal anti-aliasing (when using `--anti-aliasing=taa` option)
505505
> `sort` is only working for 3D gaussians and requires compute shaders support.
506+
> Alternatively, `sort_cpu` will give the same result and work everywhere but it's much slower.
506507
507508
### `-q`, `--ambient-occlusion` (_bool_, default: `false`)
508509

library/src/interactor_impl.cxx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,7 @@ interactor& interactor_impl::initCommands()
831831
static const std::map<std::string, std::vector<std::string>> COMPL_OPTIONS_SET = {
832832
{ "model.point_sprites.type", { "sphere", "gaussian" } },
833833
{ "render.effect.antialiasing.mode", { "fxaa", "ssaa", "taa" } },
834-
{ "render.effect.blending.mode", { "ddp", "sort", "stochastic" } },
834+
{ "render.effect.blending.mode", { "ddp", "sort", "sort_cpu", "stochastic" } },
835835
};
836836
auto complOptionSet = [&](const std::vector<std::string>& args)
837837
{
@@ -1006,6 +1006,10 @@ interactor& interactor_impl::initCommands()
10061006
mode = "sort";
10071007
}
10081008
else if (mode == "sort")
1009+
{
1010+
mode = "sort_cpu";
1011+
}
1012+
else if (mode == "sort_cpu")
10091013
{
10101014
mode = "stochastic";
10111015
}
@@ -1017,7 +1021,7 @@ interactor& interactor_impl::initCommands()
10171021
this->Internals->Window.render();
10181022
},
10191023
command_documentation_t{
1020-
"cycle_blending", "cycle between the blending method (none,ddp,sort,stochastic)" });
1024+
"cycle_blending", "cycle between the blending method (none,ddp,sort,sort_cpu,stochastic)" });
10211025

10221026
std::vector<std::string> cycleColoringValidArgs = { "field", "array", "component" };
10231027
this->addCommand(

library/src/window_impl.cxx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,8 @@ void window_impl::UpdateDynamicOptions()
438438
renderer->SetPointSpritesType(splatType);
439439
renderer->SetPointSpritesSize(
440440
opt.model.point_sprites.absolute_size, opt.model.point_sprites.size);
441-
renderer->SetPointSpritesUseInstancing(opt.render.effect.blending.mode != "sort");
441+
renderer->SetPointSpritesUseInstancing(
442+
opt.render.effect.blending.mode != "sort" && opt.render.effect.blending.mode != "sort_cpu");
442443
}
443444

444445
renderer->SetLineWidth(opt.render.line_width);
@@ -570,14 +571,18 @@ void window_impl::UpdateDynamicOptions()
570571
{
571572
blendMode = vtkF3DRenderer::BlendingMode::SORT;
572573
}
574+
else if (opt.render.effect.blending.mode == "sort_cpu")
575+
{
576+
blendMode = vtkF3DRenderer::BlendingMode::SORT_CPU;
577+
}
573578
else if (opt.render.effect.blending.mode == "stochastic")
574579
{
575580
blendMode = vtkF3DRenderer::BlendingMode::STOCHASTIC;
576581
}
577582
else
578583
{
579584
log::warn(opt.render.effect.blending.mode,
580-
R"( is an invalid blending mode. Valid modes are: "ddp", "sort", "stochastic")");
585+
R"( is an invalid blending mode. Valid modes are: "ddp", "sort", "sort_cpu", "stochastic")");
581586
}
582587
}
583588

Lines changed: 3 additions & 0 deletions
Loading

testing/recordings/TestInteractionAndCLIBlending.log

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ KeyPressEvent -465 475 0 112 1 p 0
1313
CharEvent -465 475 0 112 1 p 0
1414
KeyReleaseEvent -465 475 0 112 1 p 0
1515

16+
# P
17+
KeyPressEvent -465 475 0 112 1 p 0
18+
CharEvent -465 475 0 112 1 p 0
19+
KeyReleaseEvent -465 475 0 112 1 p 0
20+
1621
# Right
1722
KeyPressEvent -465 475 0 0 1 Right 0
1823
CharEvent -465 475 0 0 1 Right 0

testing/recordings/TestInteractionCycleBlending.log

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ KeyReleaseEvent 589 299 0 118 1 p
1616
KeyPressEvent 589 299 0 118 1 p
1717
CharEvent 589 299 0 118 1 p
1818
KeyReleaseEvent 589 299 0 118 1 p
19+
KeyPressEvent 589 299 0 118 1 p
20+
CharEvent 589 299 0 118 1 p
21+
KeyReleaseEvent 589 299 0 118 1 p

vtkext/private/module/vtkF3DPointSplatMapper.cxx

Lines changed: 110 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
#include <vtk_glew.h>
3535
#endif
3636

37+
#include <algorithm>
3738
#include <sstream>
39+
#include <vector>
3840

3941
//----------------------------------------------------------------------------
4042
class vtkF3DSplatMapperHelper : public vtkOpenGLPointGaussianMapperHelper
@@ -76,17 +78,22 @@ class vtkF3DSplatMapperHelper : public vtkOpenGLPointGaussianMapperHelper
7678

7779
private:
7880
#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
79-
void SortSplats(vtkRenderer* ren);
80-
8181
vtkNew<vtkShader> DepthComputeShader;
8282
vtkNew<vtkShaderProgram> DepthProgram;
8383
vtkNew<vtkOpenGLBufferObject> DepthBuffer;
8484

8585
vtkNew<vtkF3DBitonicSort> Sorter;
86+
#endif
87+
88+
std::vector<unsigned int> CPUSortedIndices;
89+
std::vector<float> CPUDepths;
8690

87-
double DirectionThreshold = 0.999;
91+
static constexpr double DirectionThreshold = 0.999;
8892
double LastDirection[3] = { 0.0, 0.0, 0.0 };
89-
#endif
93+
94+
bool SortNeeded(vtkRenderer* ren);
95+
void SortSplats(vtkRenderer* ren);
96+
void SortSplatsCPU(vtkRenderer* ren);
9097

9198
bool OwnerUseInstancing();
9299

@@ -311,71 +318,129 @@ void vtkF3DSplatMapperHelper::SetCameraShaderParameters(
311318
this->Superclass::SetCameraShaderParameters(cellBO, ren, actor);
312319
}
313320

314-
#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
321+
//------------------------------------------------------------------------------
322+
bool vtkF3DSplatMapperHelper::SortNeeded(vtkRenderer* ren)
323+
{
324+
const double* focalPoint = ren->GetActiveCamera()->GetFocalPoint();
325+
const double* origin = ren->GetActiveCamera()->GetPosition();
326+
double direction[3];
327+
328+
for (int i = 0; i < 3; ++i)
329+
{
330+
// the orientation is reverted to sort splats back to front
331+
direction[i] = origin[i] - focalPoint[i];
332+
}
333+
334+
vtkMath::Normalize(direction);
335+
336+
if (vtkMath::Dot(this->LastDirection, direction) >= vtkF3DSplatMapperHelper::DirectionThreshold)
337+
{
338+
return false;
339+
}
340+
341+
this->LastDirection[0] = direction[0];
342+
this->LastDirection[1] = direction[1];
343+
this->LastDirection[2] = direction[2];
344+
345+
return true;
346+
}
347+
315348
//----------------------------------------------------------------------------
316349
void vtkF3DSplatMapperHelper::SortSplats(vtkRenderer* ren)
317350
{
318-
int numVerts = this->VBOs->GetNumberOfTuples("vertexMC");
351+
#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
319352

320-
if (numVerts)
353+
if (!this->SortNeeded(ren))
321354
{
322-
const double* focalPoint = ren->GetActiveCamera()->GetFocalPoint();
323-
const double* origin = ren->GetActiveCamera()->GetPosition();
324-
double direction[3];
355+
return;
356+
}
325357

326-
for (int i = 0; i < 3; ++i)
327-
{
328-
// the orientation is reverted to sort splats back to front
329-
direction[i] = origin[i] - focalPoint[i];
330-
}
358+
int numVerts = this->VBOs->GetNumberOfTuples("vertexMC");
331359

332-
vtkMath::Normalize(direction);
360+
vtkOpenGLShaderCache* shaderCache =
361+
vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow())->GetShaderCache();
333362

334-
// sort the splats only if the camera direction has changed
335-
if (vtkMath::Dot(this->LastDirection, direction) < this->DirectionThreshold)
336-
{
337-
vtkOpenGLShaderCache* shaderCache =
338-
vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow())->GetShaderCache();
363+
// compute next power of two
364+
unsigned int numVertsExt = vtkMath::NearestPowerOfTwo(numVerts);
339365

340-
// compute next power of two
341-
unsigned int numVertsExt = vtkMath::NearestPowerOfTwo(numVerts);
366+
// depth computation
367+
shaderCache->ReadyShaderProgram(this->DepthProgram);
342368

343-
// depth computation
344-
shaderCache->ReadyShaderProgram(this->DepthProgram);
369+
this->DepthProgram->SetUniform3f("viewDirection", this->LastDirection);
370+
this->DepthProgram->SetUniformi("count", numVerts);
371+
this->VBOs->GetVBO("vertexMC")->BindShaderStorage(0);
372+
this->Primitives[PrimitivePoints].IBO->BindShaderStorage(1);
373+
this->DepthBuffer->BindShaderStorage(2);
345374

346-
this->LastDirection[0] = direction[0];
347-
this->LastDirection[1] = direction[1];
348-
this->LastDirection[2] = direction[2];
375+
glDispatchCompute(numVertsExt / 32, 1, 1);
376+
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
349377

350-
this->DepthProgram->SetUniform3f("viewDirection", direction);
351-
this->DepthProgram->SetUniformi("count", numVerts);
352-
this->VBOs->GetVBO("vertexMC")->BindShaderStorage(0);
353-
this->Primitives[PrimitivePoints].IBO->BindShaderStorage(1);
354-
this->DepthBuffer->BindShaderStorage(2);
378+
// sort
379+
this->Sorter->Run(vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow()), numVerts,
380+
this->DepthBuffer, this->Primitives[PrimitivePoints].IBO);
381+
#endif
382+
}
355383

356-
glDispatchCompute(numVertsExt / 32, 1, 1);
357-
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
384+
//----------------------------------------------------------------------------
385+
void vtkF3DSplatMapperHelper::SortSplatsCPU(vtkRenderer* ren)
386+
{
387+
if (!this->SortNeeded(ren))
388+
{
389+
return;
390+
}
358391

359-
// sort
360-
this->Sorter->Run(vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow()), numVerts,
361-
this->DepthBuffer, this->Primitives[PrimitivePoints].IBO);
362-
}
392+
int numVerts = this->VBOs->GetNumberOfTuples("vertexMC");
393+
394+
this->CPUSortedIndices.resize(static_cast<size_t>(numVerts));
395+
396+
this->Primitives[PrimitivePoints].IBO->Download(
397+
this->CPUSortedIndices.data(), static_cast<size_t>(numVerts));
398+
399+
this->CPUDepths.resize(static_cast<size_t>(numVerts));
400+
// compute depth for each splat
401+
vtkPolyData* poly = this->CurrentInput;
402+
403+
vtkPoints* points = poly->GetPoints();
404+
405+
for (int i = 0; i < numVerts; ++i)
406+
{
407+
const double* pos = points->GetPoint(i);
408+
this->CPUDepths[i] = pos[0] * this->LastDirection[0] + pos[1] * this->LastDirection[1] +
409+
pos[2] * this->LastDirection[2];
363410
}
411+
412+
// Match bitonic sort ordering: sort ascending by depth (back-to-front given reversed direction)
413+
std::sort(this->CPUSortedIndices.begin(), this->CPUSortedIndices.end(),
414+
[&](const GLuint& a, const GLuint& b) { return this->CPUDepths[a] < this->CPUDepths[b]; });
415+
416+
this->Primitives[PrimitivePoints].IBO->Upload(this->CPUSortedIndices.data(),
417+
static_cast<size_t>(numVerts), vtkOpenGLBufferObject::ObjectType::ElementArrayBuffer);
364418
}
365-
#endif
366419

367420
//----------------------------------------------------------------------------
368421
void vtkF3DSplatMapperHelper::RenderPieceDraw(vtkRenderer* ren, vtkActor* actor)
369422
{
370-
#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
371423
const vtkF3DRenderer* renderer = vtkF3DRenderer::SafeDownCast(ren);
372424

373-
if (renderer->GetBlendingMode() == vtkF3DRenderer::BlendingMode::SORT &&
374-
vtkShader::IsComputeShaderSupported() && actor->HasTranslucentPolygonalGeometry())
425+
if (actor->HasTranslucentPolygonalGeometry())
375426
{
376-
this->SortSplats(ren);
427+
if (renderer->GetBlendingMode() == vtkF3DRenderer::BlendingMode::SORT)
428+
{
429+
if (vtkShader::IsComputeShaderSupported())
430+
{
431+
this->SortSplats(ren);
432+
}
433+
else
434+
{
435+
vtkWarningMacro("Compute shaders not supported, falling back to CPU sorting");
436+
this->SortSplatsCPU(ren);
437+
}
438+
}
439+
else if (renderer->GetBlendingMode() == vtkF3DRenderer::BlendingMode::SORT_CPU)
440+
{
441+
this->SortSplatsCPU(ren);
442+
}
377443
}
378-
#endif
379444

380445
if (this->OwnerUseInstancing())
381446
{

0 commit comments

Comments
 (0)