diff --git a/.codespellrc b/.codespellrc index ef3a145cfd..9c122f00ec 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] skip = ./testing/**/*,./.git/**/*,./**/*.patch,./external/cxxopts/cxxopts.hpp,./external/nlohmann_json/nlohmann/json.hpp -ignore-words-list=nnumber,unknwn,dota,modle +ignore-words-list=nnumber,unknwn,dota,modle,inout diff --git a/.cppcheck.supp b/.cppcheck.supp index 8ffe62874d..9f4f95a83b 100644 --- a/.cppcheck.supp +++ b/.cppcheck.supp @@ -16,3 +16,4 @@ preprocessorErrorDirective:plugins/draco/module/vtkF3DDracoReader.cxx unknownMacro:library/VTKExtensions/Applicative/vtkF3DObjectFactory.cxx unusedVariable:*factory.cxx* constParameter:library/src/image.cxx +invalidPointerCast:plugins/native/module/vtkF3DSplatReader.cxx diff --git a/.github/actions/mesa-install-bin/action.yml b/.github/actions/mesa-install-bin/action.yml index 25befae0a8..d19a107a21 100644 --- a/.github/actions/mesa-install-bin/action.yml +++ b/.github/actions/mesa-install-bin/action.yml @@ -18,9 +18,10 @@ runs: run: | mkdir mesa cd mesa - curl.exe -L --output mesa.7z --url https://github.com/pal1000/mesa-dist-win/releases/download/22.0.1/mesa3d-22.0.1-release-msvc.7z + curl.exe -L --output mesa.7z --url https://github.com/pal1000/mesa-dist-win/releases/download/23.3.5/mesa3d-23.3.5-release-msvc.7z C:\'Program Files'\7-Zip\7z.exe x mesa.7z # A * is added next line to force Get-ChildItem to look for directory within the path Get-ChildItem -Directory ${{inputs.path}}* | ForEach-Object { Copy-Item -Path .\x64\opengl32.dll, .\x64\libglapi.dll, .\x64\libgallium_wgl.dll -Destination $_ } + echo "GALLIUM_DRIVER=llvmpipe"| Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append cd .. rm .\mesa -r -force diff --git a/.github/actions/vtk_commit_sha b/.github/actions/vtk_commit_sha index 37ea1c8f91..8cecb69cb3 100644 --- a/.github/actions/vtk_commit_sha +++ b/.github/actions/vtk_commit_sha @@ -1 +1 @@ -a4cb9c6c90c18a31e225aa4a18df6a591bf8cd3b +be69fa3a50de292857e69702885576ac6f114805 diff --git a/.lsan.supp b/.lsan.supp index 8ed603c6cc..afd47b4b27 100644 --- a/.lsan.supp +++ b/.lsan.supp @@ -19,6 +19,7 @@ leak:TKernel leak:libtbb # Potential mesa/VTK leak with incomplete callstack -# forces us to hide all leaks from the libf3dSDKTests +# forces us to hide all leaks from the tests using a render window # https://gitlab.kitware.com/vtk/vtk/-/issues/18504 leak:libf3dSDKTests +leak:VTKExtensionsRenderingTests diff --git a/application/F3DOptionsParser.cxx b/application/F3DOptionsParser.cxx index 5689e60c52..46726e6b9d 100644 --- a/application/F3DOptionsParser.cxx +++ b/application/F3DOptionsParser.cxx @@ -341,6 +341,7 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o auto grp2 = cxxOptions.add_options("Material"); this->DeclareOption(grp2, "point-sprites", "o", "Show sphere sprites instead of geometry", options.getAsBoolRef("model.point-sprites.enable"), HasDefault::YES, MayHaveConfig::YES); + this->DeclareOption(grp2, "point-type", "", "Point splat type when showing point sprites", options.getAsStringRef("render.splat-type"), HasDefault::YES, MayHaveConfig::YES, ""); this->DeclareOption(grp2, "point-size", "", "Point size when showing vertices or point sprites", options.getAsDoubleRef("render.point-size"), HasDefault::YES, MayHaveConfig::YES, ""); this->DeclareOption(grp2, "line-width", "", "Line width when showing edges", options.getAsDoubleRef("render.line-width"), HasDefault::YES, MayHaveConfig::YES, ""); this->DeclareOption(grp2, "color", "", "Solid color", options.getAsDoubleVectorRef("model.color.rgb"), HasDefault::YES, MayHaveConfig::YES, ""); diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 181d3cdfb0..2078cdc43d 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -212,6 +212,13 @@ else() endif() endif() +# Needs splat sorting with compute shaders +if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240203) + if(NOT APPLE) # MacOS does not support compute shaders + f3d_test(NAME Test3DGaussiansSplatting DATA small.splat ARGS -osy --up=-Y --point-size=1 --point-type=gaussian --camera-position=-3.6,0.5,-4.2) + endif() +endif() + if (NOT APPLE) # This test is broken on apple because of #792 f3d_test(NAME TestScalarsCell DATA f3d.vtp ARGS --scalars --cells --comp=-2 --up=+Z DEFAULT_LIGHTS) diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 524f989575..03d8261f86 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -2,6 +2,13 @@ ## Ongoing development +For F3D users: +- Added a new option `--point-type` used to specify how to display points +- Add support for 3D Gaussians Splatting in binary .splat format + +For libf3d users: +- Added a new option `render.splat-type` used to specify how to display points (only if `model.point-sprites.enable` is true) + ## v2.3.0 For F3D users: diff --git a/doc/GALLERY.md b/doc/GALLERY.md index 1a03590266..5743a25340 100644 --- a/doc/GALLERY.md +++ b/doc/GALLERY.md @@ -15,6 +15,10 @@ Images and videos displayed below use public datasets, you can download them [he *Animated scientific visualization rendering*: `f3d can.ex2 -xtgans --up=+Z --scalars=VEL` + + +*3D Gaussians Splatting*: `f3d counter.splat --point-size=1 --point-type=gaussian -soynxz --up=-Y --camera-position=0,1,-5.2 --camera-focal-point=0,1,0` + *Direct scalars rendering of a point cloud*: `f3d Carola_PointCloud.ply --point-size=0 --comp=-2 -so --up=+Z --hdri-skybox --hdri-ambient --hdri-file=venice_sunset_8k.hdr` diff --git a/doc/libf3d/OPTIONS.md b/doc/libf3d/OPTIONS.md index 34e2b30226..37d271c138 100644 --- a/doc/libf3d/OPTIONS.md +++ b/doc/libf3d/OPTIONS.md @@ -66,6 +66,7 @@ render.effect.tone-mapping|bool
false
render|Enable generic filmic *Tone M render.line-width|double
1.0
render|Set the *width* of lines when showing edges.|\-\-line-width render.show-edges|bool
false
render|Show the *cell edges*|\-\-edges render.point-size|double
10.0
render|Set the *size* of points when showing vertices and point sprites.|\-\-point-size +render.splat-type|string
sphere
render|Set the splat type when showing point sprites (can be `sphere` or `gaussian`).|\-\-point-type render.grid.enable|bool
false
render|Show *a grid* aligned with the horizontal (orthogonal to the Up direction) plane.|\-\-grid render.grid.absolute|bool
false
render|Position the grid at the *absolute origin* of the model's coordinate system instead of below the model.|\-\-grid render.grid.unit|double
0
render|Set the size of the *unit square* for the grid. If set to non-positive (the default) a suitable value will be automatically computed.|\-\-grid\-unit diff --git a/doc/user/OPTIONS.md b/doc/user/OPTIONS.md index ce3b4ed739..229b44ae0a 100644 --- a/doc/user/OPTIONS.md +++ b/doc/user/OPTIONS.md @@ -45,6 +45,7 @@ Options|Default|Description ------|------|------ -o, \-\-point-sprites||Show sphere *points sprites* instead of the geometry. \-\-point-size=\|10.0|Set the *size* of points when showing vertices and point sprites. +\-\-point-type=\|sphere|Set the splat type when showing point sprites. \-\-line-width=\|1.0|Set the *width* of lines when showing edges. \-\-color=\|1.0, 1.0, 1.0| Set a *color* on the geometry. Multiplied with the base color texture when present.
Requires a default scene. \-\-opacity=\|1.0|Set *opacity* on the geometry. Multiplied with the base color texture when present.
Requires a default scene. Usually used with Depth Peeling option. diff --git a/library/VTKExtensions/Applicative/Testing/TestF3DObjectFactory.cxx b/library/VTKExtensions/Applicative/Testing/TestF3DObjectFactory.cxx index 64715a294a..3435bb99d1 100644 --- a/library/VTKExtensions/Applicative/Testing/TestF3DObjectFactory.cxx +++ b/library/VTKExtensions/Applicative/Testing/TestF3DObjectFactory.cxx @@ -13,12 +13,20 @@ #include #include +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) +#include "vtkF3DPointSplatMapper.h" +#endif + int TestF3DObjectFactory(int argc, char* argv[]) { vtkNew factory; vtkObjectFactory::RegisterFactory(factory); vtkObjectFactory::SetAllEnableFlags(0, "vtkPolyDataMapper", "vtkOpenGLPolyDataMapper"); +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) + vtkObjectFactory::SetAllEnableFlags(0, "vtkPointGaussianMapper", "vtkOpenGLPointGaussianMapper"); +#endif + // Check factory utility methods if (strcmp(factory->GetVTKSourceVersion(), VTK_SOURCE_VERSION) != 0) { @@ -40,6 +48,17 @@ int TestF3DObjectFactory(int argc, char* argv[]) return EXIT_FAILURE; } +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) + vtkNew pointMapper; + pointMapper->Print(cout); + vtkF3DPointSplatMapper* pointMapperPtr = vtkF3DPointSplatMapper::SafeDownCast(pointMapper); + if (pointMapperPtr == nullptr) + { + std::cerr << "vtkF3DObjectFactory failed to create a vtkF3DPointSplatMapper" << std::endl; + return EXIT_FAILURE; + } +#endif + vtkNew window; #if F3D_WINDOWS_GUI vtkF3DWin32OutputWindow* windowPtr = vtkF3DWin32OutputWindow::SafeDownCast(window); diff --git a/library/VTKExtensions/Applicative/vtkF3DObjectFactory.cxx b/library/VTKExtensions/Applicative/vtkF3DObjectFactory.cxx index b94c231b80..5d51f25a6e 100644 --- a/library/VTKExtensions/Applicative/vtkF3DObjectFactory.cxx +++ b/library/VTKExtensions/Applicative/vtkF3DObjectFactory.cxx @@ -1,28 +1,38 @@ #include "vtkF3DObjectFactory.h" +#include + #include "vtkF3DConfigure.h" #include "vtkF3DPolyDataMapper.h" +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) && \ + VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) +#include +#endif + #ifdef __ANDROID__ -#include "vtkF3DAndroidLogOutputWindow.h" +#include #elif F3D_WINDOWS_GUI -#include "vtkF3DWin32OutputWindow.h" +#include #else -#include "vtkF3DConsoleOutputWindow.h" +#include #endif #ifdef __EMSCRIPTEN__ -#include "vtkSDL2OpenGLRenderWindow.h" -#include "vtkSDL2RenderWindowInteractor.h" +#include +#include #endif -#include - vtkStandardNewMacro(vtkF3DObjectFactory); // Now create the functions to create overrides with. VTK_CREATE_CREATE_FUNCTION(vtkF3DPolyDataMapper) +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) && \ + VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) +VTK_CREATE_CREATE_FUNCTION(vtkF3DPointSplatMapper) +#endif + #ifdef __ANDROID__ VTK_CREATE_CREATE_FUNCTION(vtkF3DAndroidLogOutputWindow) #elif F3D_WINDOWS_GUI @@ -42,6 +52,12 @@ vtkF3DObjectFactory::vtkF3DObjectFactory() this->RegisterOverride("vtkPolyDataMapper", "vtkF3DPolyDataMapper", "vtkPolyDataMapper override for F3D", 1, vtkObjectFactoryCreatevtkF3DPolyDataMapper); +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) && \ + VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) + this->RegisterOverride("vtkPointGaussianMapper", "vtkF3DPointSplatMapper", + "vtkPointGaussianMapper override for F3D", 1, vtkObjectFactoryCreatevtkF3DPointSplatMapper); +#endif + #ifdef __ANDROID__ this->RegisterOverride("vtkOutputWindow", "vtkF3DAndroidLogOutputWindow", "vtkOutputWindow override for F3D", 1, vtkObjectFactoryCreatevtkF3DAndroidLogOutputWindow); diff --git a/library/VTKExtensions/Readers/vtkF3DGenericImporter.cxx b/library/VTKExtensions/Readers/vtkF3DGenericImporter.cxx index f6027bc99e..e5e86556ff 100644 --- a/library/VTKExtensions/Readers/vtkF3DGenericImporter.cxx +++ b/library/VTKExtensions/Readers/vtkF3DGenericImporter.cxx @@ -34,17 +34,6 @@ struct ReaderPipeline this->GeometryActor->GetProperty()->SetInterpolationToPBR(); this->VolumeMapper->SetRequestedRenderModeToGPU(); this->PolyDataMapper->InterpolateScalarsBeforeMappingOn(); - this->PointGaussianMapper->EmissiveOff(); - this->PointGaussianMapper->SetSplatShaderCode( - "//VTK::Color::Impl\n" - "float dist = dot(offsetVCVSOutput.xy, offsetVCVSOutput.xy);\n" - "if (dist > 1.0) {\n" - " discard;\n" - "} else {\n" - " float scale = (1.0 - dist);\n" - " ambientColor *= scale;\n" - " diffuseColor *= scale;\n" - "}\n"); } std::string Name; diff --git a/library/VTKExtensions/Rendering/CMakeLists.txt b/library/VTKExtensions/Rendering/CMakeLists.txt index 0c622bf54e..26d35916a9 100644 --- a/library/VTKExtensions/Rendering/CMakeLists.txt +++ b/library/VTKExtensions/Rendering/CMakeLists.txt @@ -10,6 +10,25 @@ set(sources set(private_headers ${CMAKE_CURRENT_BINARY_DIR}/F3DDefaultHDRI.h) +set(shader_files + glsl/vtkF3DBitonicSortGlobalDisperseCS.glsl + glsl/vtkF3DBitonicSortGlobalFlipCS.glsl + glsl/vtkF3DBitonicSortLocalDisperseCS.glsl + glsl/vtkF3DBitonicSortLocalSortCS.glsl + glsl/vtkF3DBitonicSortFunctions.glsl + glsl/vtkF3DComputeDepthCS.glsl) + +foreach(file IN LISTS shader_files) + vtk_encode_string( + INPUT "${file}" + HEADER_OUTPUT header + SOURCE_OUTPUT source) + list(APPEND sources + "${source}") + list(APPEND private_headers + "${header}") +endforeach() + set(classes vtkF3DCachedLUTTexture vtkF3DCachedSpecularTexture @@ -25,8 +44,13 @@ set(classes vtkF3DRendererWithColoring ) +# Needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10675 +if(NOT ANDROID AND NOT EMSCRIPTEN AND VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240203) + set(classes ${classes} vtkF3DBitonicSort vtkF3DPointSplatMapper) +endif() + if(NOT VTK_VERSION VERSION_GREATER_EQUAL 9.2.20220907) - set(classes ${classes} vtkF3DOrientationMarkerWidget) + set(classes ${classes} vtkF3DOrientationMarkerWidget) endif() vtk_module_add_module(f3d::VTKExtensionsRendering diff --git a/library/VTKExtensions/Rendering/Testing/CMakeLists.txt b/library/VTKExtensions/Rendering/Testing/CMakeLists.txt index a3f6c4b733..6ce40e049b 100644 --- a/library/VTKExtensions/Rendering/Testing/CMakeLists.txt +++ b/library/VTKExtensions/Rendering/Testing/CMakeLists.txt @@ -3,12 +3,22 @@ if(VTK_VERSION VERSION_LESS_EQUAL 9.1.0) cmake_policy(SET CMP0115 OLD) endif() +list(APPEND VTKExtensionsRenderingTests_list + TestF3DInteractorEventRecorder.cxx + TestF3DOpenGLGridMapper.cxx + TestF3DRenderPass.cxx + TestF3DRendererWithColoring.cxx + TestF3DCachedTexturesPrint.cxx + ) + +# Also needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10675 +if(NOT ANDROID AND NOT EMSCRIPTEN AND VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240203) + list(APPEND VTKExtensionsRenderingTests_list + TestF3DBitonicSort.cxx) +endif() + vtk_add_test_cxx(VTKExtensionsRenderingTests tests NO_DATA NO_VALID NO_OUTPUT - TestF3DInteractorEventRecorder.cxx - TestF3DOpenGLGridMapper.cxx - TestF3DRenderPass.cxx - TestF3DRendererWithColoring.cxx - TestF3DCachedTexturesPrint.cxx + ${VTKExtensionsRenderingTests_list} ${F3D_SOURCE_DIR}/testing/ ${CMAKE_BINARY_DIR}/Testing/Temporary/) vtk_test_cxx_executable(VTKExtensionsRenderingTests tests) diff --git a/library/VTKExtensions/Rendering/Testing/TestF3DBitonicSort.cxx b/library/VTKExtensions/Rendering/Testing/TestF3DBitonicSort.cxx new file mode 100644 index 0000000000..f5eca637f4 --- /dev/null +++ b/library/VTKExtensions/Rendering/Testing/TestF3DBitonicSort.cxx @@ -0,0 +1,107 @@ +#include +#include +#include + +#include "vtkF3DBitonicSort.h" +#include "vtkF3DBitonicSortFunctions.h" +#include "vtkF3DBitonicSortGlobalDisperseCS.h" +#include "vtkF3DBitonicSortGlobalFlipCS.h" +#include "vtkF3DBitonicSortLocalDisperseCS.h" +#include "vtkF3DBitonicSortLocalSortCS.h" + +#include +#include + +int TestF3DBitonicSort(int argc, char* argv[]) +{ + // Turn off VTK error reporting to avoid unwanted failure detection by ctest + vtkObject::GlobalWarningDisplayOff(); + + // we need an OpenGL context + vtkNew renWin; + renWin->OffScreenRenderingOn(); + renWin->Start(); + + if (!vtkShader::IsComputeShaderSupported()) + { + std::cerr << "Compute shaders are not supported on this system, skipping the test.\n"; + return EXIT_SUCCESS; + } + + constexpr int nbElements = 10000; + + // fill CPU keys and values buffers + std::vector keys(nbElements); + std::vector values(nbElements); + + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_real_distribution dist(0.0, 1.0); + + std::generate(std::begin(keys), std::end(keys), [&]() { return dist(rng); }); + std::fill(std::begin(values), std::end(values), 0); // we do not care about the values + + // upload these buffers to the GPU + vtkNew bufferKeys; + vtkNew bufferValues; + + bufferKeys->Upload(keys, vtkOpenGLBufferObject::ArrayBuffer); + bufferValues->Upload(values, vtkOpenGLBufferObject::ArrayBuffer); + + // sort + vtkNew sorter; + + // check invalid workgroup size + if (sorter->Initialize(-1, VTK_FLOAT, VTK_FLOAT)) + { + std::cerr << "The invalid workgroup size is not failing" << std::endl; + return EXIT_FAILURE; + } + + // check invalid types + if (sorter->Initialize(128, VTK_CHAR, VTK_FLOAT)) + { + std::cerr << "The invalid key type is not failing" << std::endl; + return EXIT_FAILURE; + } + + if (sorter->Initialize(128, VTK_FLOAT, VTK_CHAR)) + { + std::cerr << "The invalid key type is not failing" << std::endl; + return EXIT_FAILURE; + } + + if (sorter->Run( + vtkOpenGLRenderWindow::SafeDownCast(renWin), nbElements, bufferKeys, bufferValues)) + { + std::cerr << "Uninitialized run is not failing" << std::endl; + return EXIT_FAILURE; + } + + if (!sorter->Initialize(128, VTK_DOUBLE, VTK_INT)) + { + std::cerr << "Valid Initialize call failed" << std::endl; + return EXIT_FAILURE; + } + + if (!sorter->Run( + vtkOpenGLRenderWindow::SafeDownCast(renWin), nbElements, bufferKeys, bufferValues)) + { + std::cerr << "Sorter Run call failed" << std::endl; + return EXIT_FAILURE; + } + + // download sorted key buffer to CPU + bufferKeys->Download(keys.data(), keys.size()); + + // check if correctly sorted + for (int i = 1; i < nbElements; i++) + { + if (keys[i - 1] > keys[i]) + { + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortFunctions.glsl b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortFunctions.glsl new file mode 100644 index 0000000000..94d3f8c3e2 --- /dev/null +++ b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortFunctions.glsl @@ -0,0 +1,44 @@ +ivec2 disperse(uint h) +{ + uint t = gl_GlobalInvocationID.x; + uint q = (t / h) * 2 * h; + + uint x = q + t % h; + uint y = q + t % h + h; + + return ivec2(x, y); +} + +ivec2 flip(uint h) +{ + uint t = gl_GlobalInvocationID.x; + uint q = (t / h) * 2 * h; + + uint x = q + t % h; + uint y = q + 2 * h - (t % h) - 1; + + return ivec2(x, y); +} + +void swap_key(inout KeyType key1, inout KeyType key2) +{ + KeyType tmp = key1; + key1 = key2; + key2 = tmp; +} + +void swap_value(inout ValueType value1, inout ValueType value2) +{ + ValueType tmp = value1; + value1 = value2; + value2 = tmp; +} + +void compare_and_swap(ivec2 idx) +{ + if (key[idx.x] > key[idx.y] && idx.y < count) + { + swap_key(key[idx.x], key[idx.y]); + swap_value(value[idx.x], value[idx.y]); + } +} diff --git a/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortGlobalDisperseCS.glsl b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortGlobalDisperseCS.glsl new file mode 100644 index 0000000000..c5c8120020 --- /dev/null +++ b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortGlobalDisperseCS.glsl @@ -0,0 +1,26 @@ +#version 430 + +//VTK::BitonicDefines::Dec + +layout(local_size_x = WorkgroupSize) in; +layout(std430) buffer; + +layout(binding = 0) buffer Keys +{ + KeyType key[]; +}; + +layout(binding = 1) buffer Values +{ + ValueType value[]; +}; + +layout(location = 0) uniform int count; +layout(location = 1) uniform int height; + +//VTK::BitonicFunctions::Dec + +void main() +{ + compare_and_swap(disperse(height)); +} diff --git a/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortGlobalFlipCS.glsl b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortGlobalFlipCS.glsl new file mode 100644 index 0000000000..c9c92976d3 --- /dev/null +++ b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortGlobalFlipCS.glsl @@ -0,0 +1,26 @@ +#version 430 + +//VTK::BitonicDefines::Dec + +layout(local_size_x = WorkgroupSize) in; +layout(std430) buffer; + +layout(binding = 0) buffer Keys +{ + KeyType key[]; +}; + +layout(binding = 1) buffer Values +{ + ValueType value[]; +}; + +layout(location = 0) uniform int count; +layout(location = 1) uniform int height; + +//VTK::BitonicFunctions::Dec + +void main() +{ + compare_and_swap(flip(height)); +} diff --git a/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortLocalDisperseCS.glsl b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortLocalDisperseCS.glsl new file mode 100644 index 0000000000..d97cdb255d --- /dev/null +++ b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortLocalDisperseCS.glsl @@ -0,0 +1,29 @@ +#version 430 + +//VTK::BitonicDefines::Dec + +layout(local_size_x = WorkgroupSize) in; +layout(std430) buffer; + +layout(binding = 0) buffer Keys +{ + KeyType key[]; +}; + +layout(binding = 1) buffer Values +{ + ValueType value[]; +}; + +layout(location = 0) uniform int count; + +//VTK::BitonicFunctions::Dec + +void main() +{ + for (uint h = WorkgroupSize; h >= 1; h /= 2) + { + barrier(); + compare_and_swap(disperse(h)); + } +} diff --git a/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortLocalSortCS.glsl b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortLocalSortCS.glsl new file mode 100644 index 0000000000..fa9d8ee65a --- /dev/null +++ b/library/VTKExtensions/Rendering/glsl/vtkF3DBitonicSortLocalSortCS.glsl @@ -0,0 +1,35 @@ +#version 430 + +//VTK::BitonicDefines::Dec + +layout(local_size_x = WorkgroupSize) in; +layout(std430) buffer; + +layout(binding = 0) buffer Keys +{ + KeyType key[]; +}; + +layout(binding = 1) buffer Values +{ + ValueType value[]; +}; + +layout(location = 0) uniform int count; + +//VTK::BitonicFunctions::Dec + +void main() +{ + for (uint h = 1; h <= WorkgroupSize; h *= 2) + { + barrier(); + compare_and_swap(flip(h)); + + for (uint hh = h; hh >= 1; hh /= 2) + { + barrier(); + compare_and_swap(disperse(hh)); + } + } +} diff --git a/library/VTKExtensions/Rendering/glsl/vtkF3DComputeDepthCS.glsl b/library/VTKExtensions/Rendering/glsl/vtkF3DComputeDepthCS.glsl new file mode 100644 index 0000000000..337f1a524c --- /dev/null +++ b/library/VTKExtensions/Rendering/glsl/vtkF3DComputeDepthCS.glsl @@ -0,0 +1,38 @@ +#version 430 +layout(local_size_x = 32) in; +layout(std430) buffer; + +struct vertex +{ + float x; + float y; + float z; +}; + +layout(binding = 0) readonly buffer Points +{ + vertex point[]; +}; + +layout(binding = 1) readonly buffer Indices +{ + uint index[]; +}; + +layout(binding = 2) writeonly buffer Depths +{ + float depth[]; +}; + +layout (location = 0) uniform vec3 viewDirection; +layout (location = 1) uniform int count; + +void main() +{ + uint i = gl_GlobalInvocationID.x; + if (i < count) + { + vertex v = point[index[i]]; + depth[i] = dot(viewDirection, vec3(v.x, v.y, v.z)); + } +} diff --git a/library/VTKExtensions/Rendering/vtkF3DBitonicSort.cxx b/library/VTKExtensions/Rendering/vtkF3DBitonicSort.cxx new file mode 100644 index 0000000000..cd08c78e5b --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DBitonicSort.cxx @@ -0,0 +1,162 @@ +#include "vtkF3DBitonicSort.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "vtkF3DBitonicSortFunctions.h" +#include "vtkF3DBitonicSortGlobalDisperseCS.h" +#include "vtkF3DBitonicSortGlobalFlipCS.h" +#include "vtkF3DBitonicSortLocalDisperseCS.h" +#include "vtkF3DBitonicSortLocalSortCS.h" + +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkF3DBitonicSort); + +//---------------------------------------------------------------------------- +bool vtkF3DBitonicSort::Initialize(int workgroupSize, int keyType, int valueType) +{ + if (workgroupSize < 0) + { + vtkErrorMacro("Invalid workgroupSize"); + return false; + } + + auto GetStringShaderType = [](int vtkType) -> std::string + { + switch (vtkType) + { + case VTK_INT: + return "int"; + case VTK_UNSIGNED_INT: + return "uint"; + case VTK_FLOAT: + return "float"; + case VTK_DOUBLE: + return "double"; + } + return ""; + }; + + std::string keyTypeShader = GetStringShaderType(keyType); + if (keyTypeShader.empty()) + { + vtkErrorMacro("Invalid keyType"); + return false; + } + + std::string valueTypeShader = GetStringShaderType(valueType); + if (valueTypeShader.empty()) + { + vtkErrorMacro("Invalid valueType"); + return false; + } + + std::stringstream defines; + defines << "#define KeyType " << keyTypeShader << "\n"; + defines << "#define ValueType " << valueTypeShader << "\n"; + defines << "#define WorkgroupSize " << workgroupSize << "\n"; + + std::string localSort = vtkF3DBitonicSortLocalSortCS; + vtkShaderProgram::Substitute( + localSort, "//VTK::BitonicFunctions::Dec", vtkF3DBitonicSortFunctions); + vtkShaderProgram::Substitute(localSort, "//VTK::BitonicDefines::Dec", defines.str()); + + std::string localDisperse = vtkF3DBitonicSortLocalDisperseCS; + vtkShaderProgram::Substitute( + localDisperse, "//VTK::BitonicFunctions::Dec", vtkF3DBitonicSortFunctions); + vtkShaderProgram::Substitute(localDisperse, "//VTK::BitonicDefines::Dec", defines.str()); + + std::string globalDisperse = vtkF3DBitonicSortGlobalDisperseCS; + vtkShaderProgram::Substitute( + globalDisperse, "//VTK::BitonicFunctions::Dec", vtkF3DBitonicSortFunctions); + vtkShaderProgram::Substitute(globalDisperse, "//VTK::BitonicDefines::Dec", defines.str()); + + std::string globalFlip = vtkF3DBitonicSortGlobalFlipCS; + vtkShaderProgram::Substitute( + globalFlip, "//VTK::BitonicFunctions::Dec", vtkF3DBitonicSortFunctions); + vtkShaderProgram::Substitute(globalFlip, "//VTK::BitonicDefines::Dec", defines.str()); + + this->BitonicSortLocalSortComputeShader->SetType(vtkShader::Compute); + this->BitonicSortLocalSortComputeShader->SetSource(localSort); + this->BitonicSortLocalSortProgram->SetComputeShader(this->BitonicSortLocalSortComputeShader); + + this->BitonicSortLocalDisperseComputeShader->SetType(vtkShader::Compute); + this->BitonicSortLocalDisperseComputeShader->SetSource(localDisperse); + this->BitonicSortLocalDisperseProgram->SetComputeShader( + this->BitonicSortLocalDisperseComputeShader); + + this->BitonicSortGlobalDisperseComputeShader->SetType(vtkShader::Compute); + this->BitonicSortGlobalDisperseComputeShader->SetSource(globalDisperse); + this->BitonicSortGlobalDisperseProgram->SetComputeShader( + this->BitonicSortGlobalDisperseComputeShader); + + this->BitonicSortGlobalFlipComputeShader->SetType(vtkShader::Compute); + this->BitonicSortGlobalFlipComputeShader->SetSource(globalFlip); + this->BitonicSortGlobalFlipProgram->SetComputeShader(this->BitonicSortGlobalFlipComputeShader); + + this->WorkgroupSize = workgroupSize; + + return true; +} + +//---------------------------------------------------------------------------- +bool vtkF3DBitonicSort::Run(vtkOpenGLRenderWindow* context, int nbPairs, + vtkOpenGLBufferObject* keys, vtkOpenGLBufferObject* values) +{ + if (this->WorkgroupSize < 0) + { + vtkErrorMacro("Shaders are not initialized"); + return false; + } + + vtkOpenGLShaderCache* shaderCache = context->GetShaderCache(); + + // compute next power of two + unsigned int nbPairsExt = vtkMath::NearestPowerOfTwo(nbPairs); + + const int workgroupCount = std::max(nbPairsExt / (this->WorkgroupSize * 2), 1U); + + keys->BindShaderStorage(0); + values->BindShaderStorage(1); + + // first, sort all workgroups locally + shaderCache->ReadyShaderProgram(this->BitonicSortLocalSortProgram); + this->BitonicSortLocalSortProgram->SetUniformi("count", nbPairs); + glDispatchCompute(workgroupCount, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + // we must now double h, as this happens before every flip + for (unsigned int outerHeight = this->WorkgroupSize * 2; outerHeight < nbPairsExt; + outerHeight *= 2) + { + shaderCache->ReadyShaderProgram(this->BitonicSortGlobalFlipProgram); + this->BitonicSortGlobalFlipProgram->SetUniformi("count", nbPairs); + this->BitonicSortGlobalFlipProgram->SetUniformi("height", outerHeight); + glDispatchCompute(workgroupCount, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + for (int innerHeight = outerHeight / 2; innerHeight > this->WorkgroupSize; innerHeight /= 2) + { + shaderCache->ReadyShaderProgram(this->BitonicSortGlobalDisperseProgram); + this->BitonicSortGlobalDisperseProgram->SetUniformi("count", nbPairs); + this->BitonicSortGlobalDisperseProgram->SetUniformi("height", innerHeight); + glDispatchCompute(workgroupCount, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + } + + // handle the remaining disperse loop locally to the workgroup + shaderCache->ReadyShaderProgram(this->BitonicSortLocalDisperseProgram); + this->BitonicSortLocalDisperseProgram->SetUniformi("count", nbPairs); + glDispatchCompute(workgroupCount, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + } + + return true; +} diff --git a/library/VTKExtensions/Rendering/vtkF3DBitonicSort.h b/library/VTKExtensions/Rendering/vtkF3DBitonicSort.h new file mode 100644 index 0000000000..390eba87a1 --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DBitonicSort.h @@ -0,0 +1,60 @@ +/** + * @class vtkF3DBitonicSort + * @brief Compute shader used to sort key/value pairs + * + * This class is used to sort buffers based on the Bitonic Sort algorithm. + * Inspired by https://poniesandlight.co.uk/reflect/bitonic_merge_sort/ + * The original code can be found there: https://github.com/tgfrerer/island + * It's mostly rewritten but some parts are copied (MIT license, Tim Gfrerer) + */ +#ifndef vtkF3DBitonicSort_h +#define vtkF3DBitonicSort_h + +#include +#include + +class vtkShader; +class vtkShaderProgram; +class vtkOpenGLBufferObject; +class vtkOpenGLRenderWindow; + +class vtkF3DBitonicSort : public vtkObject +{ +public: + static vtkF3DBitonicSort* New(); + vtkTypeMacro(vtkF3DBitonicSort, vtkObject); + + /** + * Initialize the compute shaders. + * workgroupSize is the number of threads running in a single GPU workgroup + * keyType and valueType are the VTK types of the key and value to sort respectively + * Only VTK_DOUBLE, VTK_FLOAT, VTK_INT and VTK_UNSIGNED_INT are supported + * Returns true if succeeded + */ + bool Initialize(int workgroupSize, int keyType, int valueType); + + /** + * Run the compute shader and sort the buffers. + * An OpenGL context must exists and given as input in the first argument + * nbPairs is the number of element in the buffer keys and values + * OpenGL buffers keys and values must be valid and containing data types specified when + * this class has been initialized + * Returns true if succeeded + */ + bool Run(vtkOpenGLRenderWindow* context, int nbPairs, vtkOpenGLBufferObject* keys, + vtkOpenGLBufferObject* values); + +private: + vtkNew BitonicSortLocalSortComputeShader; + vtkNew BitonicSortLocalSortProgram; + vtkNew BitonicSortLocalDisperseComputeShader; + vtkNew BitonicSortLocalDisperseProgram; + vtkNew BitonicSortGlobalFlipComputeShader; + vtkNew BitonicSortGlobalFlipProgram; + vtkNew BitonicSortGlobalDisperseComputeShader; + vtkNew BitonicSortGlobalDisperseProgram; + + int WorkgroupSize = -1; +}; + +#endif diff --git a/library/VTKExtensions/Rendering/vtkF3DPointSplatMapper.cxx b/library/VTKExtensions/Rendering/vtkF3DPointSplatMapper.cxx new file mode 100644 index 0000000000..9b2c347aaf --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DPointSplatMapper.cxx @@ -0,0 +1,156 @@ +#include "vtkF3DPointSplatMapper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vtkF3DBitonicSort.h" +#include "vtkF3DComputeDepthCS.h" + +//---------------------------------------------------------------------------- +class vtkF3DSplatMapperHelper : public vtkOpenGLPointGaussianMapperHelper +{ +public: + static vtkF3DSplatMapperHelper* New(); + vtkTypeMacro(vtkF3DSplatMapperHelper, vtkOpenGLPointGaussianMapperHelper); + + vtkF3DSplatMapperHelper(const vtkF3DSplatMapperHelper&) = delete; + void operator=(const vtkF3DSplatMapperHelper&) = delete; + +protected: + vtkF3DSplatMapperHelper(); + + // overridden to create the OpenGL depth buffer + void BuildBufferObjects(vtkRenderer* ren, vtkActor* act) override; + + // overridden to sort splats + void RenderPieceDraw(vtkRenderer* ren, vtkActor* act) override; + +private: + void SortSplats(vtkRenderer* ren); + + vtkNew DepthComputeShader; + vtkNew DepthProgram; + vtkNew DepthBuffer; + + vtkNew Sorter; + + double DirectionThreshold = 0.999; + double LastDirection[3] = { 0.0, 0.0, 0.0 }; +}; + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkF3DSplatMapperHelper); + +//---------------------------------------------------------------------------- +vtkF3DSplatMapperHelper::vtkF3DSplatMapperHelper() +{ + this->DepthComputeShader->SetType(vtkShader::Compute); + this->DepthComputeShader->SetSource(vtkF3DComputeDepthCS); + this->DepthProgram->SetComputeShader(this->DepthComputeShader); + + this->Sorter->Initialize(512, VTK_FLOAT, VTK_UNSIGNED_INT); +} + +//---------------------------------------------------------------------------- +void vtkF3DSplatMapperHelper::BuildBufferObjects(vtkRenderer* ren, vtkActor* act) +{ + vtkPolyData* poly = this->CurrentInput; + + if (poly == nullptr) + { + return; + } + + int splatCount = poly->GetPoints()->GetNumberOfPoints(); + + vtkOpenGLPointGaussianMapperHelper::BuildBufferObjects(ren, act); + + this->DepthBuffer->Allocate(splatCount * sizeof(float), vtkOpenGLBufferObject::ArrayBuffer, + vtkOpenGLBufferObject::DynamicCopy); +} + +//---------------------------------------------------------------------------- +void vtkF3DSplatMapperHelper::SortSplats(vtkRenderer* ren) +{ + int numVerts = this->VBOs->GetNumberOfTuples("vertexMC"); + + if (numVerts) + { + double* focalPoint = ren->GetActiveCamera()->GetFocalPoint(); + double* origin = ren->GetActiveCamera()->GetPosition(); + double direction[3]; + + for (int i = 0; i < 3; ++i) + { + // the orientation is reverted to sort splats back to front + direction[i] = origin[i] - focalPoint[i]; + } + + vtkMath::Normalize(direction); + + // sort the splats only if the camera direction has changed + if (vtkMath::Dot(this->LastDirection, direction) < this->DirectionThreshold) + { + vtkOpenGLShaderCache* shaderCache = + vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow())->GetShaderCache(); + + // compute next power of two + unsigned int numVertsExt = vtkMath::NearestPowerOfTwo(numVerts); + + // depth computation + shaderCache->ReadyShaderProgram(this->DepthProgram); + + this->LastDirection[0] = direction[0]; + this->LastDirection[1] = direction[1]; + this->LastDirection[2] = direction[2]; + + this->DepthProgram->SetUniform3f("viewDirection", direction); + this->DepthProgram->SetUniformi("count", numVerts); + this->VBOs->GetVBO("vertexMC")->BindShaderStorage(0); + this->Primitives[PrimitivePoints].IBO->BindShaderStorage(1); + this->DepthBuffer->BindShaderStorage(2); + + glDispatchCompute(numVertsExt / 32, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + // sort + this->Sorter->Run(vtkOpenGLRenderWindow::SafeDownCast(ren->GetRenderWindow()), numVerts, + this->DepthBuffer, this->Primitives[PrimitivePoints].IBO); + } + } +} + +//---------------------------------------------------------------------------- +void vtkF3DSplatMapperHelper::RenderPieceDraw(vtkRenderer* ren, vtkActor* actor) +{ + if (vtkShader::IsComputeShaderSupported() && actor->GetForceTranslucent()) + { + this->SortSplats(ren); + } + + vtkOpenGLPointGaussianMapperHelper::RenderPieceDraw(ren, actor); +} + +//---------------------------------------------------------------------------- +vtkOpenGLPointGaussianMapperHelper* vtkF3DPointSplatMapper::CreateHelper() +{ + auto helper = vtkF3DSplatMapperHelper::New(); + helper->Owner = this; + return helper; +} + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkF3DPointSplatMapper); diff --git a/library/VTKExtensions/Rendering/vtkF3DPointSplatMapper.h b/library/VTKExtensions/Rendering/vtkF3DPointSplatMapper.h new file mode 100644 index 0000000000..2828df1b4b --- /dev/null +++ b/library/VTKExtensions/Rendering/vtkF3DPointSplatMapper.h @@ -0,0 +1,23 @@ +/** + * @class vtkF3DPointSplatMapper + * @brief Custom F3D gaussian mapper + * + * This mapper is used to add a depth sort compute shader pass + */ +#ifndef vtkF3DPointSplatMapper_h +#define vtkF3DPointSplatMapper_h + +#include +#include + +class vtkF3DPointSplatMapper : public vtkOpenGLPointGaussianMapper +{ +public: + static vtkF3DPointSplatMapper* New(); + vtkTypeMacro(vtkF3DPointSplatMapper, vtkOpenGLPointGaussianMapper); + +protected: + vtkOpenGLPointGaussianMapperHelper* CreateHelper() override; +}; + +#endif diff --git a/library/VTKExtensions/Rendering/vtkF3DRenderer.h b/library/VTKExtensions/Rendering/vtkF3DRenderer.h index c8a878bb0b..d6b739300d 100644 --- a/library/VTKExtensions/Rendering/vtkF3DRenderer.h +++ b/library/VTKExtensions/Rendering/vtkF3DRenderer.h @@ -49,7 +49,7 @@ class vtkF3DRenderer : public vtkOpenGLRenderer * Set different actors parameters */ void SetLineWidth(double lineWidth); - virtual void SetPointSize(double pointSize); + void SetPointSize(double pointSize); void SetFontFile(const std::string& fontFile); void SetHDRIFile(const std::string& hdriFile); void SetUseImageBasedLighting(bool use) override; diff --git a/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.cxx b/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.cxx index 8445c9b0ad..29ed92cfe9 100644 --- a/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.cxx +++ b/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.cxx @@ -250,9 +250,9 @@ void vtkF3DRendererWithColoring::ConfigureColoringActorsProperties() } //---------------------------------------------------------------------------- -void vtkF3DRendererWithColoring::SetPointSize(double pointSize) +void vtkF3DRendererWithColoring::SetPointProperties(SplatType type, double pointSize) { - this->Superclass::SetPointSize(pointSize); + this->SetPointSize(pointSize); if (!this->Importer) { @@ -260,16 +260,59 @@ void vtkF3DRendererWithColoring::SetPointSize(double pointSize) } const vtkBoundingBox& bbox = this->Importer->GetGeometryBoundingBox(); - double gaussianPointSize = 1.0; + + double scaleFactor = 1.0; if (bbox.IsValid()) { - gaussianPointSize = pointSize * bbox.GetDiagonalLength() * 0.001; + scaleFactor = pointSize * bbox.GetDiagonalLength() * 0.001; } const auto& psActorsAndMappers = this->Importer->GetPointSpritesActorsAndMappers(); - for (auto& psActorAndMapper : psActorsAndMappers) + for (auto& [actor, mapper] : psActorsAndMappers) { - psActorAndMapper.second->SetScaleFactor(gaussianPointSize); + mapper->EmissiveOff(); + + if (type == SplatType::GAUSSIAN) + { + mapper->SetScaleFactor(1.0); + mapper->SetSplatShaderCode(nullptr); // gaussian is the default VTK shader + +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20231102) + mapper->AnisotropicOn(); + mapper->SetBoundScale(3.0); + mapper->SetScaleArray("scale"); + mapper->SetRotationArray("rotation"); + + int* viewport = this->GetSize(); + + float lowPass[3] = { 0.3f / (viewport[0] * viewport[0]), 0.f, + 0.3f / (viewport[1] * viewport[1]) }; + mapper->SetLowpassMatrix(lowPass); +#endif + + actor->ForceTranslucentOn(); + } + else + { +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20231102) + mapper->AnisotropicOff(); + mapper->SetLowpassMatrix(0., 0., 0.); +#endif + + mapper->SetScaleFactor(scaleFactor); + + mapper->SetSplatShaderCode("//VTK::Color::Impl\n" + "float dist = dot(offsetVCVSOutput.xy, offsetVCVSOutput.xy);\n" + "if (dist > 1.0) {\n" + " discard;\n" + "} else {\n" + " float scale = (1.0 - dist);\n" + " ambientColor *= scale;\n" + " diffuseColor *= scale;\n" + "}\n"); + + actor->ForceTranslucentOff(); + } } } diff --git a/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.h b/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.h index 79052ae183..d8063dd5af 100644 --- a/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.h +++ b/library/VTKExtensions/Rendering/vtkF3DRendererWithColoring.h @@ -89,10 +89,16 @@ class vtkF3DRendererWithColoring : public vtkF3DRenderer */ void SetTextureNormal(const std::string& tex); + enum class SplatType + { + SPHERE, + GAUSSIAN + }; + /** - * Set the pointSize on the pointGaussianMapper as well as calls superclass implementation. + * Set the pointSize and the splat type on the pointGaussianMapper */ - void SetPointSize(double pointSize) override; + void SetPointProperties(SplatType splatType, double pointSize); /** * Set the visibility of the scalar bar. diff --git a/library/src/init.cxx b/library/src/init.cxx index 6c635a5a03..8a7de6be83 100644 --- a/library/src/init.cxx +++ b/library/src/init.cxx @@ -47,6 +47,10 @@ init::init() vtkObjectFactory::RegisterFactory(factory); vtkObjectFactory::SetAllEnableFlags(0, "vtkPolyDataMapper", "vtkOpenGLPolyDataMapper"); +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 20240203) + vtkObjectFactory::SetAllEnableFlags(0, "vtkPointGaussianMapper", "vtkOpenGLPointGaussianMapper"); +#endif + #ifdef __EMSCRIPTEN__ vtkObjectFactory::SetAllEnableFlags(0, "vtkRenderWindow", "vtkOpenGLRenderWindow"); vtkObjectFactory::SetAllEnableFlags( diff --git a/library/src/options.cxx b/library/src/options.cxx index b5e6a4d7e4..35de51ab87 100644 --- a/library/src/options.cxx +++ b/library/src/options.cxx @@ -146,6 +146,7 @@ options::options() this->Internals->init("render.show-edges", false); this->Internals->init("render.line-width", 1.0); this->Internals->init("render.point-size", 10.0); + this->Internals->init("render.splat-type", std::string("sphere")); this->Internals->init("render.grid.enable", false); this->Internals->init("render.grid.absolute", false); this->Internals->init("render.grid.unit", 0.0); diff --git a/library/src/window_impl.cxx b/library/src/window_impl.cxx index 64da9e3a48..07136c601f 100644 --- a/library/src/window_impl.cxx +++ b/library/src/window_impl.cxx @@ -238,10 +238,18 @@ void window_impl::UpdateDynamicOptions() this->Internals->Renderer->SetInvertZoom( this->Internals->Options.getAsBool("interactor.invert-zoom")); + std::string splatTypeStr = this->Internals->Options.getAsString("render.splat-type"); + int pointSize = this->Internals->Options.getAsDouble("render.point-size"); + vtkF3DRendererWithColoring::SplatType splatType = vtkF3DRendererWithColoring::SplatType::SPHERE; + if (splatTypeStr == "gaussian") + { + splatType = vtkF3DRendererWithColoring::SplatType::GAUSSIAN; + } + + this->Internals->Renderer->SetPointProperties(splatType, pointSize); + this->Internals->Renderer->SetLineWidth( this->Internals->Options.getAsDouble("render.line-width")); - this->Internals->Renderer->SetPointSize( - this->Internals->Options.getAsDouble("render.point-size")); this->Internals->Renderer->ShowEdge(this->Internals->Options.getAsBool("render.show-edges")); this->Internals->Renderer->ShowTimer(this->Internals->Options.getAsBool("ui.fps")); this->Internals->Renderer->ShowFilename(this->Internals->Options.getAsBool("ui.filename")); diff --git a/plugins/native/CMakeLists.txt b/plugins/native/CMakeLists.txt index 02a3ad9b4b..14c3e39c43 100644 --- a/plugins/native/CMakeLists.txt +++ b/plugins/native/CMakeLists.txt @@ -179,6 +179,16 @@ f3d_plugin_declare_reader( FORMAT_DESCRIPTION "VTK XML MultiBlock" ) + +f3d_plugin_declare_reader( + NAME Splat + SCORE 90 + EXTENSIONS splat + MIMETYPES application/vnd.splat + VTK_READER vtkF3DSplatReader + FORMAT_DESCRIPTION "3D Gaussian splats" +) + f3d_plugin_build( NAME native VERSION 1.0 diff --git a/plugins/native/configs/config.d/10_native.json b/plugins/native/configs/config.d/10_native.json index 58cfc27de5..add5b8f60b 100644 --- a/plugins/native/configs/config.d/10_native.json +++ b/plugins/native/configs/config.d/10_native.json @@ -22,5 +22,22 @@ { "comp": "-2", "translucency-support": true + }, + ".*(splat)": + { + "point-sprites": true, + "point-size": 1, + "point-type": "gaussian", + "scalars": "color", + "comp": "-2", + "bg-color": "0, 0, 0", + "up": "-Y", + "trackball": true, + "camera-position": "0,1,-5.2", + "camera-focal-point": "0,1,0", + "tone-mapping": false, + "grid": false, + "anti-aliasing": false, + "translucency-support": false } } diff --git a/plugins/native/configs/thumbnail.d/10_native.json b/plugins/native/configs/thumbnail.d/10_native.json index 66275851e7..f876443978 100644 --- a/plugins/native/configs/thumbnail.d/10_native.json +++ b/plugins/native/configs/thumbnail.d/10_native.json @@ -12,5 +12,20 @@ { "comp": "-2", "translucency-support": true + }, + ".*(splat)": + { + "point-sprites": true, + "point-size": 1, + "point-type": "gaussian", + "scalars": "color", + "comp": "-2", + "up": "-Y", + "camera-position": "0,1,-5.2", + "camera-focal-point": "0,1,0", + "tone-mapping": false, + "grid": false, + "anti-aliasing": false, + "translucency-support": false } } diff --git a/plugins/native/f3d-3d-formats.xml b/plugins/native/f3d-3d-formats.xml index 784092340e..714d926cb4 100644 --- a/plugins/native/f3d-3d-formats.xml +++ b/plugins/native/f3d-3d-formats.xml @@ -33,4 +33,8 @@ Polygon File Format + + 3D Gaussians Splat File Format + + diff --git a/plugins/native/module/CMakeLists.txt b/plugins/native/module/CMakeLists.txt new file mode 100644 index 0000000000..fb36db096c --- /dev/null +++ b/plugins/native/module/CMakeLists.txt @@ -0,0 +1,13 @@ +set(classes + vtkF3DSplatReader + ) + +set(_no_install "") +if(VTK_VERSION VERSION_GREATER_EQUAL 9.2.20220928) + set(_no_install "NO_INSTALL") +endif() + +vtk_module_add_module(f3d::VTKExtensionsSplatReader + ${_no_install} + FORCE_STATIC + CLASSES ${classes}) diff --git a/plugins/native/module/vtk.module b/plugins/native/module/vtk.module new file mode 100644 index 0000000000..61e4e3fbdc --- /dev/null +++ b/plugins/native/module/vtk.module @@ -0,0 +1,10 @@ +NAME + f3d::VTKExtensionsSplatReader +DESCRIPTION + A VTK module for the native plugin +DEPENDS + VTK::CommonCore + VTK::CommonExecutionModel +TEST_DEPENDS + VTK::TestingCore + VTK::CommonDataModel diff --git a/plugins/native/module/vtkF3DSplatReader.cxx b/plugins/native/module/vtkF3DSplatReader.cxx new file mode 100644 index 0000000000..4d1a262df3 --- /dev/null +++ b/plugins/native/module/vtkF3DSplatReader.cxx @@ -0,0 +1,95 @@ +#include "vtkF3DSplatReader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkF3DSplatReader); + +//---------------------------------------------------------------------------- +vtkF3DSplatReader::vtkF3DSplatReader() +{ + this->SetNumberOfInputPorts(0); +} + +//---------------------------------------------------------------------------- +int vtkF3DSplatReader::RequestData( + vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector) +{ + vtkPolyData* output = vtkPolyData::GetData(outputVector); + + std::ifstream inputStream(this->FileName, std::ios::binary); + + std::vector buffer(std::istreambuf_iterator(inputStream), {}); + + // position: 3 floats (12 bytes) + // scale: 3 floats (12 bytes) + // rotation: 4 chars (4 bytes) + // color+opacity: 4 chars (4 bytes) + constexpr size_t splatSize = 32; + constexpr size_t positionOffset = 0; + constexpr size_t scaleOffset = 12; + constexpr size_t colorOffset = 24; + constexpr size_t rotationOffset = 28; + + size_t nbSplats = buffer.size() / splatSize; + + vtkNew positionArray; + positionArray->SetNumberOfComponents(3); + positionArray->SetNumberOfTuples(nbSplats); + positionArray->SetName("position"); + + vtkNew scaleArray; + scaleArray->SetNumberOfComponents(3); + scaleArray->SetNumberOfTuples(nbSplats); + scaleArray->SetName("scale"); + + vtkNew colorArray; + colorArray->SetNumberOfComponents(4); + colorArray->SetNumberOfTuples(nbSplats); + colorArray->SetName("color"); + + vtkNew rotationArray; + rotationArray->SetNumberOfComponents(4); + rotationArray->SetNumberOfTuples(nbSplats); + rotationArray->SetName("rotation"); + + for (size_t i = 0; i < nbSplats; i++) + { + float* position = reinterpret_cast(buffer.data() + (splatSize * i) + positionOffset); + float* scale = reinterpret_cast(buffer.data() + (splatSize * i) + scaleOffset); + unsigned char* rotation = buffer.data() + (splatSize * i) + rotationOffset; + unsigned char* color = buffer.data() + (splatSize * i) + colorOffset; + + positionArray->SetTypedTuple(i, position); + scaleArray->SetTypedTuple(i, scale); + colorArray->SetTypedTuple(i, color); + + for (int c = 0; c < 4; c++) + { + rotationArray->SetTypedComponent(i, c, (static_cast(rotation[c]) - 128.f) / 128.f); + } + } + + vtkNew points; + points->SetDataTypeToFloat(); + points->SetData(positionArray); + output->SetPoints(points); + + output->GetPointData()->SetScalars(colorArray); + output->GetPointData()->AddArray(scaleArray); + output->GetPointData()->AddArray(rotationArray); + + return 1; +} diff --git a/plugins/native/module/vtkF3DSplatReader.h b/plugins/native/module/vtkF3DSplatReader.h new file mode 100644 index 0000000000..5de602305d --- /dev/null +++ b/plugins/native/module/vtkF3DSplatReader.h @@ -0,0 +1,40 @@ +/** + * @class vtkF3DSplatReader + * @brief VTK Reader for 3D Gaussians in binary .splat file format + * + * Reader for binary .splat files as defined in https://github.com/antimatter15/splat + * This reader will probably evolve until there is no standard defined yet + * An interesting discussion can be followed here: + * https://github.com/mkkellogg/GaussianSplats3D/issues/47 + */ + +#ifndef vtkF3DSplatReader_h +#define vtkF3DSplatReader_h + +#include + +class vtkF3DSplatReader : public vtkPolyDataAlgorithm +{ +public: + static vtkF3DSplatReader* New(); + vtkTypeMacro(vtkF3DSplatReader, vtkPolyDataAlgorithm); + + /** + * Set the file name. + */ + vtkSetMacro(FileName, std::string); + +protected: + vtkF3DSplatReader(); + ~vtkF3DSplatReader() override = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + +private: + vtkF3DSplatReader(const vtkF3DSplatReader&) = delete; + void operator=(const vtkF3DSplatReader&) = delete; + + std::string FileName; +}; + +#endif diff --git a/testing/baselines/Test3DGaussiansSplatting.png b/testing/baselines/Test3DGaussiansSplatting.png new file mode 100644 index 0000000000..c26801ab8b --- /dev/null +++ b/testing/baselines/Test3DGaussiansSplatting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7179ffb838595c548bb6adba9f858457af2126d410f1dad006c863619e7bfa3 +size 131588 diff --git a/testing/data/small.splat b/testing/data/small.splat new file mode 100644 index 0000000000..53ecceda63 --- /dev/null +++ b/testing/data/small.splat @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5e178e7b10aa326c1574c481dbb03f34c82d755f4a5fe45812ad1263b7698fa +size 1673376