From 05af3d3fc09e0e1839d02c6beff651f76a33e002 Mon Sep 17 00:00:00 2001 From: Alvaro Sanchez <alvaro.sanchez@kitware.com> Date: Wed, 29 Mar 2017 15:30:04 -0400 Subject: [PATCH] Added test for transfer function 2D. Split functionality in ComputeGradient::Dec into two functions, one containing all the reusable parts between 1D and 2D transfer functions (gradient computations) and one containing 1D specific calls. Changed transfer function type to VTK_FLOAT. --- Rendering/Core/vtkVolumeProperty.cxx | 11 +- Rendering/Core/vtkVolumeProperty.h | 2 +- Rendering/Volume/Testing/Cxx/CMakeLists.txt | 1 + .../Testing/Cxx/TestGPURayCastTransfer2D.cxx | 164 ++++++++++++++++++ .../Baseline/TestGPURayCastTransfer2D.png.md5 | 1 + .../VolumeOpenGL2/shaders/raycasterfs.glsl | 6 +- .../vtkOpenGLGPUVolumeRayCastMapper.cxx | 83 ++++++--- .../vtkOpenGLTransferFunction2D.h | 6 +- .../VolumeOpenGL2/vtkVolumeShaderComposer.h | 46 +++-- Testing/Data/tooth.nhdr.md5 | 1 + Testing/Data/tooth.raw.md5 | 1 + 11 files changed, 269 insertions(+), 53 deletions(-) create mode 100644 Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx create mode 100644 Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5 create mode 100644 Testing/Data/tooth.nhdr.md5 create mode 100644 Testing/Data/tooth.raw.md5 diff --git a/Rendering/Core/vtkVolumeProperty.cxx b/Rendering/Core/vtkVolumeProperty.cxx index 9473bc1155..153db73304 100644 --- a/Rendering/Core/vtkVolumeProperty.cxx +++ b/Rendering/Core/vtkVolumeProperty.cxx @@ -455,11 +455,14 @@ void vtkVolumeProperty::SetTransferFunction2D(int index, vtkImageData* function) if (this->TransferFunction2D[index] != function) { vtkDataArray* dataArr = function->GetPointData()->GetScalars(); - if (dataArr->GetNumberOfComponents() != 4 || - dataArr->GetArrayType() != VTK_UNSIGNED_CHAR) + if (!dataArr || dataArr->GetNumberOfComponents() != 4 || + dataArr->GetDataType() != VTK_FLOAT) { - vtkErrorMacro(<< "Invalid type or number of components for a 2D Transfer" - " Function, VTK_UNSIGNED_CHAR 4 Components expected!"); + const int type = dataArr->GetDataType(); + const int comp = dataArr->GetNumberOfComponents(); + vtkErrorMacro(<< "Invalid type (" << type << ") or number of components (" + << comp << ") for a 2D Transfer Function, VTK_FLOAT 4 Components" + " expected!"); return; } diff --git a/Rendering/Core/vtkVolumeProperty.h b/Rendering/Core/vtkVolumeProperty.h index c0bf4ef521..297e6b633b 100644 --- a/Rendering/Core/vtkVolumeProperty.h +++ b/Rendering/Core/vtkVolumeProperty.h @@ -215,7 +215,7 @@ public: //@{ /** * Set/Get a 2D transfer function. Volume mappers interpret the x-axis of - * of this transfer function scalar value and the y-axis as gradient + * of this transfer function as scalar value and the y-axis as gradient * magnitude. The value at (X, Y) corresponds to the color and opacity * for a salar value of X and a gradient magnitude of Y. */ diff --git a/Rendering/Volume/Testing/Cxx/CMakeLists.txt b/Rendering/Volume/Testing/Cxx/CMakeLists.txt index e24c51332b..d2855a834e 100644 --- a/Rendering/Volume/Testing/Cxx/CMakeLists.txt +++ b/Rendering/Volume/Testing/Cxx/CMakeLists.txt @@ -88,6 +88,7 @@ set (VolumeOpenGL2CxxTests TestGPURayCastTextureStreaming.cxx TestGPURayCastThreeComponentsAdditive.cxx TestGPURayCastThreeComponentsIndependent.cxx + TestGPURayCastTransfer2D.cxx TestGPURayCastTwoComponentsDependent.cxx TestGPURayCastTwoComponentsDependentGradient.cxx TestGPURayCastTwoComponentsGradient.cxx diff --git a/Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx b/Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx new file mode 100644 index 0000000000..f736ccf856 --- /dev/null +++ b/Rendering/Volume/Testing/Cxx/TestGPURayCastTransfer2D.cxx @@ -0,0 +1,164 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: TestGPURayCastTransfer2D.cxx + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * Test 2D transfer function support in GPUVolumeRayCastMapper. The transfer + * function is created manually using known value/gradient histogram information + * of the test data (tooth.hdr). A filter to create these histograms will be + * added in the future. + */ + +#include "vtkCamera.h" +#include "vtkColorTransferFunction.h" +#include "vtkGPUVolumeRayCastMapper.h" +#include "vtkImageData.h" +#include "vtkInteractorStyleTrackballCamera.h" +#include "vtkNew.h" +#include "vtkNrrdReader.h" +#include "vtkPiecewiseFunction.h" +#include "vtkPointData.h" +#include "vtkRegressionTestImage.h" +#include "vtkRenderer.h" +#include "vtkRenderWindow.h" +#include "vtkRenderWindowInteractor.h" +#include "vtkTestUtilities.h" +#include "vtkFloatArray.h" +#include "vtkVolume.h" +#include "vtkVolumeProperty.h" + + +typedef vtkSmartPointer<vtkImageData> Transfer2DPtr; +Transfer2DPtr Create2DTransfer() +{ + int bins[2] = {256, 256}; + Transfer2DPtr image = Transfer2DPtr::New(); + image->SetDimensions(bins[0], bins[1], 1); + image->AllocateScalars(VTK_FLOAT, 4); + vtkFloatArray* arr = vtkFloatArray::SafeDownCast( + image->GetPointData()->GetScalars()); + + // Initialize to zero + void* dataPtr = arr->GetVoidPointer(0); + memset(dataPtr, 0, bins[0] * bins[1] * 4 * sizeof(float)); + + // Setting RGBA [1.0, 0,0, 0.05] for a square in the histogram (known) + // containing some of the interesting edges (e.g. tooth root). + for (int j = 0; j < bins[1]; j++) + for (int i = 0; i < bins[0]; i++) + { + if (i > 130 && i < 190 && j < 50) + { + double const jFactor = 256.0 / 50; + + vtkIdType const index = bins[0] * j + i; + double const red = static_cast<double>(i) / bins[0]; + double const green = jFactor * static_cast<double>(j) / bins[1]; + double const blue = jFactor * static_cast<double>(j) / bins[1]; + double const alpha = 0.25 * jFactor * static_cast<double>(j) / bins[0]; + + double color[4] = {red, green, blue, alpha}; + arr->SetTuple(index, color); + } + } + + return image; +} + +//////////////////////////////////////////////////////////////////////////////// +int TestGPURayCastTransfer2D(int argc, char* argv[]) +{ + cout << "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)" << endl; + + // Load data + char* fname = vtkTestUtilities::ExpandDataFileName(argc, argv, + "Data/tooth.nhdr"); + vtkNew<vtkNrrdReader> reader; + reader->SetFileName(fname); + reader->Update(); + delete[] fname; + + vtkNew<vtkVolumeProperty> volumeProperty; + volumeProperty->ShadeOn(); + volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); + + vtkDataArray* arr = reader->GetOutput()->GetPointData()->GetScalars(); + double range[2]; + arr->GetRange(range); + + // Prepare 1D Transfer Functions + vtkNew<vtkColorTransferFunction> ctf; + ctf->AddRGBPoint(0, 0.0, 0.0, 0.0); + ctf->AddRGBPoint(510, 0.4, 0.4, 1.0); + ctf->AddRGBPoint(640, 1.0, 1.0, 1.0); + ctf->AddRGBPoint(range[1], 0.9, 0.1, 0.1); + + vtkNew<vtkPiecewiseFunction> pf; + pf->AddPoint(0, 0.00); + pf->AddPoint(510, 0.00); + pf->AddPoint(640, 0.5); + pf->AddPoint(range[1], 0.4); + + vtkNew<vtkPiecewiseFunction> gf; + gf->AddPoint(0, 0.0); + gf->AddPoint(range[1] / 4.0, 1.0); + + volumeProperty->SetScalarOpacity(pf.GetPointer()); + volumeProperty->SetGradientOpacity(gf.GetPointer()); + volumeProperty->SetColor(ctf.GetPointer()); + + // Prepare 2D Transfer Functions + Transfer2DPtr tf2d = Create2DTransfer(); + + volumeProperty->SetTransferFunction2D(tf2d); + + // Setup rendering context + vtkNew<vtkRenderWindow> renWin; + renWin->SetSize(512, 512); + renWin->SetMultiSamples(0); + + vtkNew<vtkRenderer> ren; + renWin->AddRenderer(ren.GetPointer()); + ren->SetBackground(0.0, 0.0, 0.0); + + vtkNew<vtkGPUVolumeRayCastMapper> mapper; + mapper->SetInputConnection(reader->GetOutputPort()); + mapper->SetUseJittering(1); + + vtkNew<vtkVolume> volume; + volume->SetMapper(mapper.GetPointer()); + volume->SetProperty(volumeProperty.GetPointer()); + ren->AddVolume(volume.GetPointer()); + + ren->ResetCamera(); + ren->GetActiveCamera()->Elevation(-90.0); + ren->GetActiveCamera()->Zoom(1.4); + + // Interactor + vtkNew<vtkRenderWindowInteractor> iren; + iren->SetRenderWindow(renWin.GetPointer()); + + vtkNew<vtkInteractorStyleTrackballCamera> style; + iren->SetInteractorStyle(style.GetPointer()); + + renWin->Render(); + + int retVal = vtkTesting::Test(argc, argv, renWin.GetPointer(), 90); + if (retVal == vtkRegressionTester::DO_INTERACTOR) + { + iren->Start(); + } + + return !((retVal == vtkTesting::PASSED) || + (retVal == vtkTesting::DO_INTERACTOR)); +} diff --git a/Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5 b/Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5 new file mode 100644 index 0000000000..ca65ba6dda --- /dev/null +++ b/Rendering/Volume/Testing/Data/Baseline/TestGPURayCastTransfer2D.png.md5 @@ -0,0 +1 @@ +528e99e377f471a6d00515fa2af93c82 diff --git a/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl b/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl index 443eea67ec..068f763b95 100644 --- a/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl +++ b/Rendering/VolumeOpenGL2/shaders/raycasterfs.glsl @@ -66,13 +66,15 @@ uniform vec4 in_volume_bias; //VTK::CompositeMask::Dec +//VTK::GradientCache::Dec + //VTK::ComputeOpacity::Dec //VTK::ComputeGradient::Dec -//VTK::ComputeLighting::Dec +//VTK::ComputeGradientOpacity1D::Dec -//VTK::PreComputeGradients::Dec +//VTK::ComputeLighting::Dec //VTK::ComputeColor::Dec diff --git a/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx b/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx index 857660f713..52238b7976 100644 --- a/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx +++ b/Rendering/VolumeOpenGL2/vtkOpenGLGPUVolumeRayCastMapper.cxx @@ -1054,7 +1054,7 @@ int vtkOpenGLGPUVolumeRayCastMapper::vtkInternal:: return 0; } -//---------------------------------------------------------------------------- +//------------------------------------------------------------------------------ void vtkOpenGLGPUVolumeRayCastMapper::vtkInternal:: UpdateTransferFunction2D(vtkRenderer* ren, vtkVolume* vol, unsigned int component) @@ -2898,9 +2898,15 @@ void vtkOpenGLGPUVolumeRayCastMapper::BuildShader(vtkRenderer* ren, independentComponents), true); - // Compute methods replacements //-------------------------------------------------------------------------- + fragmentShader = vtkvolume::replace( + fragmentShader, + "//VTK::ComputeGradient::Dec", + vtkvolume::ComputeGradientDeclaration(vol, noOfComponents, + independentComponents), + true); + switch(volumeProperty->GetTransferFunctionMode()) { case vtkVolumeProperty::TF_1D: @@ -2914,10 +2920,10 @@ void vtkOpenGLGPUVolumeRayCastMapper::BuildShader(vtkRenderer* ren, fragmentShader = vtkvolume::replace( fragmentShader, - "//VTK::ComputeGradient::Dec", - vtkvolume::ComputeGradientDeclaration(ren, this, vol, noOfComponents, - independentComponents, - this->Impl->GradientOpacityTablesMap), + "//VTK::ComputeGradientOpacity1D::Dec", + vtkvolume::ComputeGradientOpacity1DDecl(vol, noOfComponents, + independentComponents, + this->Impl->GradientOpacityTablesMap), true); fragmentShader = vtkvolume::replace( @@ -2942,8 +2948,8 @@ void vtkOpenGLGPUVolumeRayCastMapper::BuildShader(vtkRenderer* ren, true); fragmentShader = vtkvolume::replace(fragmentShader, - "//VTK::PreComputeGradients::Dec", - vtkvolume::PreComputeGradientsDec(ren, this, vol, noOfComponents, + "//VTK::GradientCache::Dec", + vtkvolume::GradientCacheDec(ren, this, vol, noOfComponents, independentComponents), true); @@ -3341,27 +3347,48 @@ void vtkOpenGLGPUVolumeRayCastMapper::GPURender(vtkRenderer* ren, this->ComputeReductionFactor(vol->GetAllocatedRenderTime()); this->Impl->UpdateSamplingDistance(input, ren, vol); - // Update the transfer functions - if (independentComponents) - { - for (int i = 0; i < noOfComponents; ++i) - { - this->Impl->UpdateOpacityTransferFunction(ren, vol, i); - this->Impl->UpdateGradientOpacityTransferFunction(ren, vol, i); - this->Impl->UpdateColorTransferFunction(ren, vol, i); - this->Impl->UpdateTransferFunction2D(ren, vol, i); - } - } - else + /// TODO Move this into a function + int const transferMode = vol->GetProperty()->GetTransferFunctionMode(); + switch(transferMode) { - if (noOfComponents == 2 || noOfComponents == 4) - { - this->Impl->UpdateOpacityTransferFunction(ren, vol, noOfComponents - 1); - this->Impl->UpdateGradientOpacityTransferFunction(ren, vol, - noOfComponents - 1); - this->Impl->UpdateColorTransferFunction(ren, vol, 0); - this->Impl->UpdateTransferFunction2D(ren, vol, 0); - } + case vtkVolumeProperty::TF_1D: + if (independentComponents) + { + for (int i = 0; i < noOfComponents; ++i) + { + this->Impl->UpdateOpacityTransferFunction(ren, vol, i); + this->Impl->UpdateGradientOpacityTransferFunction(ren, vol, i); + this->Impl->UpdateColorTransferFunction(ren, vol, i); + } + } + else + { + if (noOfComponents == 2 || noOfComponents == 4) + { + this->Impl->UpdateOpacityTransferFunction(ren, vol, noOfComponents - 1); + this->Impl->UpdateGradientOpacityTransferFunction(ren, vol, + noOfComponents - 1); + this->Impl->UpdateColorTransferFunction(ren, vol, 0); + } + } + break; + + case vtkVolumeProperty::TF_2D: + if (independentComponents) + { + for (int i = 0; i < noOfComponents; ++i) + { + this->Impl->UpdateTransferFunction2D(ren, vol, i); + } + } + else + { + if (noOfComponents == 2 || noOfComponents == 4) + { + this->Impl->UpdateTransferFunction2D(ren, vol, 0); + } + } + break; } // Update noise sampler texture diff --git a/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h b/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h index 7b0f433424..fd22d15959 100644 --- a/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h +++ b/Rendering/VolumeOpenGL2/vtkOpenGLTransferFunction2D.h @@ -32,8 +32,8 @@ * * Manages the texture fetched by the fragment shader when TransferFunction2D * mode is active. Update() assumes the vtkImageData instance used as source - * is of type VTK_UNSIGNED_CHAR and has 4 components (this is checked in vtkImageData - * when setting the function). + * is of type VTK_FLOAT and has 4 components (vtkVolumeProperty ensures this + * is the case when the function is set). * * \sa vtkVolumeProperty::SetTransferFunction2D */ @@ -98,7 +98,7 @@ public: this->TextureObject->SetWrapT(vtkTextureObject::ClampToEdge); this->TextureObject->SetMagnificationFilter(interpolation); this->TextureObject->SetMinificationFilter(interpolation); - this->TextureObject->Create2DFromRaw(width, height, 4, VTK_UNSIGNED_CHAR, + this->TextureObject->Create2DFromRaw(width, height, 4, VTK_FLOAT, data); this->LastInterpolation = interpolation; this->BuildTime.Modified(); diff --git a/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h b/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h index 3dbd64ad1d..18fbf27762 100644 --- a/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h +++ b/Rendering/VolumeOpenGL2/vtkVolumeShaderComposer.h @@ -424,13 +424,9 @@ namespace vtkvolume } //-------------------------------------------------------------------------- - std::string ComputeGradientDeclaration(vtkRenderer* vtkNotUsed(ren), - vtkVolumeMapper* vtkNotUsed(mapper), - vtkVolume* vol, - int noOfComponents, - int independentComponents, - std::map<int, std::string> - gradientTableMap) + std::string ComputeGradientOpacity1DDecl(vtkVolume* vol, + int noOfComponents, int independentComponents, + std::map<int, std::string> gradientTableMap) { std::string shaderStr; if (vol->GetProperty()->HasGradientOpacity() && @@ -479,6 +475,14 @@ namespace vtkvolume \n }"); } + return shaderStr; + } + + //-------------------------------------------------------------------------- + std::string ComputeGradientDeclaration(vtkVolume* vol, int noOfComponents, + int independentComponents) + { + std::string shaderStr; if (vol->GetProperty()->GetShade() && !vol->GetProperty()->HasGradientOpacity()) { @@ -592,10 +596,20 @@ namespace vtkvolume " // Compute gradient function only once\n" " vec4 gradient = computeGradient(component);\n"); break; - case vtkVolumeProperty::TF_2D: shaderStr += std::string( + case vtkVolumeProperty::TF_2D: + shaderStr += std::string( " // TransferFunction2D is enabled so the gradient for\n" - " // each component has already been cached\n" - " vec4 gradient = g_gradients[component];\n"); + " // each component has already been cached\n"); + if (independentComponents && noOfComponents > 1) + { + shaderStr += + " vec4 gradient = g_gradients[component];\n"; + } + else + { + shaderStr += + " vec4 gradient = g_gradients;\n"; + } break; } } @@ -1001,7 +1015,7 @@ namespace vtkvolume "vec4 computeColor(vec4 scalar, float opacity)\n" "{\n" " vec4 color = texture2D(" + colorTableMap[0] + ",\n" - " vec2(scalar.w, g_gradients.w));\n" + " vec2(scalar.w, g_gradients.w));\n" /// TODO Scale all gradients with the actual texture size (?) or is this done already through the normalized magnitude to Range/4? " return computeLighting(color, 0);\n" "}\n"); } @@ -1213,7 +1227,7 @@ namespace vtkvolume } //-------------------------------------------------------------------------- - std::string PreComputeGradientsDec(vtkRenderer* vtkNotUsed(ren), + std::string GradientCacheDec(vtkRenderer* vtkNotUsed(ren), vtkVolumeMapper* mapper, vtkVolume* vtkNotUsed(vol), int noOfComponents, @@ -1224,7 +1238,7 @@ namespace vtkvolume { if (noOfComponents == 1) { - shader = std::string("uniform vec4 g_gradients;"); + shader = std::string("vec4 g_gradients;"); } else { @@ -1233,7 +1247,7 @@ namespace vtkvolume numss << noOfComponents; std::string const num = numss.str(); shader = std::string( - "uniform vec4 g_gradients[" + num + "];"); + "vec4 g_gradients[" + num + "];"); } } else @@ -1242,9 +1256,11 @@ namespace vtkvolume // use a look-up-table). if (noOfComponents == 2) { - shader = std::string("uniform vec4 g_gradients;"); + shader = std::string("vec4 g_gradients;"); } } + + return shader; } //-------------------------------------------------------------------------- diff --git a/Testing/Data/tooth.nhdr.md5 b/Testing/Data/tooth.nhdr.md5 new file mode 100644 index 0000000000..06c44d1663 --- /dev/null +++ b/Testing/Data/tooth.nhdr.md5 @@ -0,0 +1 @@ +7777ca90d9ee0ce9777fadf56d76fbae diff --git a/Testing/Data/tooth.raw.md5 b/Testing/Data/tooth.raw.md5 new file mode 100644 index 0000000000..765db12750 --- /dev/null +++ b/Testing/Data/tooth.raw.md5 @@ -0,0 +1 @@ +3cc1b1d8882ea6b61a4e36c19042a189 -- GitLab