[Note: English is not my first language, so please let me know if anything is unclear.]
I’m working on a scientific visualization project using VTK 9.1.0 with C++, where I need to render two intersecting, completely opaque spheres (one red, one blue). I’m facing significant challenges with achieving physically correct depth occlusion.
Current Problem:
The spheres don’t occlude each other based on their actual geometric relationships. Additionally, the occlusion pattern remains static when I rotate the camera, which breaks the 3D perception.
What Should Happen:
- The sphere (or part of a sphere) that’s geometrically closer to the camera should appear in front
- This occlusion relationship should update dynamically as I rotate the viewpoint
What I’ve Tried:
vtkMultiBlockVolumeMapper: I combined both spheres into a multi-block dataset, but this didn’t improve depth ordering.- Depth Peeling: Enabled via
renderer->SetUseDepthPeeling(1), but this technique appears ineffective for volume rendering scenarios.
Minimal Code Example:
#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkColorTransferFunction.h>
#include <vtkImageData.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkNew.h>
#include <vtkNamedColors.h>
#include <vtkPiecewiseFunction.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkVolume.h>
#include <vtkVolumeProperty.h>
#include <vtkOpenGLGPUVolumeRayCastMapper.h>
#include <vtkImageMathematics.h>
#include <vtkImageBlend.h>
#include <iostream>
#include <cmath>
int main(int argc, char* argv[])
{
vtkNew<vtkNamedColors> colors;
vtkNew<vtkRenderer> renderer;
renderer->SetBackground(colors->GetColor3d("Black").GetData());
vtkNew<vtkRenderWindow> renderWindow;
renderWindow->AddRenderer(renderer);
renderWindow->SetSize(800, 600);
renderWindow->SetWindowName("Two Solid Interpenetrating Spheres");
renderWindow->SetMultiSamples(8);
vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
renderWindowInteractor->SetRenderWindow(renderWindow);
vtkNew<vtkInteractorStyleTrackballCamera> style;
renderWindowInteractor->SetInteractorStyle(style);
const double sphereRadius = 1.0;
const int volumeSize = 128;
const double spacing = 0.05;
double center1[3] = { 0.0, 0.0, 0.0 };
double center2[3] = { sphereRadius * 0.6, sphereRadius * 0.1, 0.0 };
vtkNew<vtkImageData> sphere1Volume;
sphere1Volume->SetDimensions(volumeSize, volumeSize, volumeSize);
sphere1Volume->SetSpacing(spacing, spacing, spacing);
sphere1Volume->SetOrigin(
center1[0] - sphereRadius * 1.2,
center1[1] - sphereRadius * 1.2,
center1[2] - sphereRadius * 1.2
);
sphere1Volume->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
vtkNew<vtkImageData> sphere2Volume;
sphere2Volume->SetDimensions(volumeSize, volumeSize, volumeSize);
sphere2Volume->SetSpacing(spacing, spacing, spacing);
sphere2Volume->SetOrigin(
center2[0] - sphereRadius * 1.2,
center2[1] - sphereRadius * 1.2,
center2[2] - sphereRadius * 1.2
);
sphere2Volume->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
unsigned char* sphere1Data = static_cast<unsigned char*>(sphere1Volume->GetScalarPointer());
unsigned char* sphere2Data = static_cast<unsigned char*>(sphere2Volume->GetScalarPointer());
for (int z = 0; z < volumeSize; z++) {
double worldZ = sphere1Volume->GetOrigin()[2] + z * spacing;
for (int y = 0; y < volumeSize; y++) {
double worldY = sphere1Volume->GetOrigin()[1] + y * spacing;
for (int x = 0; x < volumeSize; x++) {
double worldX = sphere1Volume->GetOrigin()[0] + x * spacing;
int idx = z * volumeSize * volumeSize + y * volumeSize + x;
double dx1 = worldX - center1[0];
double dy1 = worldY - center1[1];
double dz1 = worldZ - center1[2];
double dist1 = sqrt(dx1 * dx1 + dy1 * dy1 + dz1 * dz1);
double dx2 = worldX - center2[0];
double dy2 = worldY - center2[1];
double dz2 = worldZ - center2[2];
double dist2 = sqrt(dx2 * dx2 + dy2 * dy2 + dz2 * dz2);
if (dist1 <= sphereRadius) {
sphere1Data[idx] = 255;
}
else {
sphere1Data[idx] = 0;
}
if (dist2 <= sphereRadius) {
sphere2Data[idx] = 255;
}
else {
sphere2Data[idx] = 0;
}
}
}
}
vtkNew<vtkOpenGLGPUVolumeRayCastMapper> volumeMapper1;
volumeMapper1->SetInputData(sphere1Volume);
volumeMapper1->SetSampleDistance(0.01);
volumeMapper1->AutoAdjustSampleDistancesOff();
vtkNew<vtkOpenGLGPUVolumeRayCastMapper> volumeMapper2;
volumeMapper2->SetInputData(sphere2Volume);
volumeMapper2->SetSampleDistance(0.01);
volumeMapper2->AutoAdjustSampleDistancesOff();
vtkNew<vtkColorTransferFunction> colorTransferFunction1;
colorTransferFunction1->AddRGBPoint(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
colorTransferFunction1->AddRGBPoint(1.0, 0.7, 0.0, 0.0, 0.0, 0.0);
colorTransferFunction1->AddRGBPoint(255.0, 1.0, 0.0, 0.0, 1.0, 1.0);
vtkNew<vtkPiecewiseFunction> opacityTransferFunction1;
opacityTransferFunction1->AddPoint(0.0, 0.0);
opacityTransferFunction1->AddPoint(1.0, 0.0);
opacityTransferFunction1->AddPoint(255.0, 1.0);
vtkNew<vtkColorTransferFunction> colorTransferFunction2;
colorTransferFunction2->AddRGBPoint(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
colorTransferFunction2->AddRGBPoint(1.0, 0.0, 0.0, 0.7, 0.0, 0.0);
colorTransferFunction2->AddRGBPoint(255.0, 0.0, 0.0, 1.0, 1.0, 1.0);
vtkNew<vtkPiecewiseFunction> opacityTransferFunction2;
opacityTransferFunction2->AddPoint(0.0, 0.0);
opacityTransferFunction2->AddPoint(1.0, 0.0);
opacityTransferFunction2->AddPoint(255.0, 1.0);
vtkNew<vtkVolumeProperty> volumeProperty1;
volumeProperty1->SetColor(colorTransferFunction1);
volumeProperty1->SetScalarOpacity(opacityTransferFunction1);
volumeProperty1->SetInterpolationTypeToLinear();
volumeProperty1->ShadeOn();
volumeProperty1->SetAmbient(0.5);
volumeProperty1->SetDiffuse(0.8);
volumeProperty1->SetSpecular(0.5);
volumeProperty1->SetSpecularPower(20.0);
vtkNew<vtkVolumeProperty> volumeProperty2;
volumeProperty2->SetColor(colorTransferFunction2);
volumeProperty2->SetScalarOpacity(opacityTransferFunction2);
volumeProperty2->SetInterpolationTypeToLinear();
volumeProperty2->ShadeOn();
volumeProperty2->SetAmbient(0.5);
volumeProperty2->SetDiffuse(0.8);
volumeProperty2->SetSpecular(0.5);
volumeProperty2->SetSpecularPower(20.0);
vtkNew<vtkVolume> volume1;
volume1->SetMapper(volumeMapper1);
volume1->SetProperty(volumeProperty1);
vtkNew<vtkVolume> volume2;
volume2->SetMapper(volumeMapper2);
volume2->SetProperty(volumeProperty2);
renderer->AddVolume(volume1);
renderer->AddVolume(volume2);
vtkCamera* camera = renderer->GetActiveCamera();
camera->SetPosition(4, 3, 4);
camera->SetFocalPoint(0.3, 0.05, 0);
camera->SetViewUp(0, 0, 1);
camera->Azimuth(30);
camera->Elevation(20);
renderer->ResetCamera();
renderWindow->Render();
renderWindowInteractor->Start();
return 0;
}
Incorrect occlusion screenshots:
The blue sphere always occludes the red sphere, regardless of viewpoint.
Thank you for any guidance!
