vtkGPUVolumeRayCastMapper offsets the near plane too much
This issue was created automatically from an original Mantis Issue. Further discussion may take place here.
In vtkOpenGLGPUVolumeRayCastMapper::ClipBoundingBox the near plane is offset by a small value. The comment says this is done in order to avoid hardware clipping of the near plane due to floating-point precision.
This offset is 0.001 times the camera plane normal. 0.001 was arbitrarily chosen (according to the comment). There is a check afterwards to make sure 0.001 is not larger than the distance between near and far plane. That is not sufficient however:
The datasets we are rendering are all very small in real world dimensions, mostly around 500 micrometers in all dimensions. This results in a very small spacing, which leads to a small distance between near and far plane. 0.001 times the camera plane normal is a very large chunk of that distance, meaning that a large part of the dataset is clipped away.
Ironically, rendering even smaller datasets (100 micrometers for example) works fine, because then 0.001 is larger than the distance between near and far plane, and the check sets the offset to (far - near) / 1000.0.
Rendering datasets larger than 1000 micrometers works fine, because 0.001 is a small enough value. So our datasets just happen to be in the "bad" region :)
I propose changing the code from: double offset=0.001; // some arbitrary small value. if(offset>=distNearFar) { offset=distNearFar/1000.0; }
to:
offset = distNearFar/1000.0; if (0.001 < offset) { // 0.001 is enough to avoid floating point precision problems. offset = 0.001; }
or something better.
I wrote a small program to demonstrate the effect. IronProt.vtk is rendered, but with a modified spacing, so that its real world dimensions are 500 micro meters. At startup the program uses vtkVolumeRayCastMapper, showing that CPU raycasting works fine. Pressing "9" switches to vtkGPUVolumeRayCastMapper, demonstrating the bug. Pressing "8" switches back to the CPU raycaster.
#include "vtkCamera.h" #include "vtkColorTransferFunction.h" #include "vtkCommand.h" #include "vtkGPUVolumeRayCastMapper.h" #include "vtkImageChangeInformation.h" #include "vtkPiecewiseFunction.h" #include "vtkRenderer.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkSmartPointer.h" #include "vtkStructuredPoints.h" #include "vtkStructuredPointsReader.h" #include "vtkVolume.h" #include "vtkVolumeProperty.h" #include "vtkVolumeRayCastCompositeFunction.h" #include "vtkVolumeRayCastMapper.h"
class MyCallBack: public vtkCommand { public:
static MyCallBack *New()
{
return new MyCallBack;
}
virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData)
{
if (eventId != vtkCommand::KeyPressEvent) return;
vtkRenderWindowInteractor* interactor = static_cast<vtkRenderWindowInteractor*>(caller);
if (interactor == NULL) return;
char* pressedKey = interactor->GetKeySym();
if (strcmp(pressedKey, "8") == 0)
{
volume->SetMapper(cpuRayCaster);
renderWindow->Render();
std::cout << "Changed to CPU RayCasting" << std::endl;
}
else if (strcmp(pressedKey, "9") == 0)
{
volume->SetMapper(gpuRayCaster);
renderWindow->Render();
std::cout << "Changed to GPU RayCasting" << std::endl;
}
}
vtkSmartPointer<vtkVolume> volume;
vtkSmartPointer<vtkVolumeRayCastMapper> cpuRayCaster;
vtkSmartPointer<vtkGPUVolumeRayCastMapper> gpuRayCaster;
vtkSmartPointer<vtkRenderWindow> renderWindow;
};
int main() { vtkSmartPointer pointsReader = vtkSmartPointer::New(); pointsReader->SetFileName("ironProt.vtk"); pointsReader->Update();
int dims[3];
pointsReader->GetOutput()->GetDimensions(dims);
double desiredBounds = 0.0005;
double desiredSpacing[3];
desiredSpacing[0] = desiredBounds / (double)dims[0];
desiredSpacing[1] = desiredBounds / (double)dims[1];
desiredSpacing[2] = desiredBounds / (double)dims[2];
vtkSmartPointer<vtkImageChangeInformation> imageChangeInfo = vtkSmartPointer<vtkImageChangeInformation>::New();
imageChangeInfo->SetInputConnection(pointsReader->GetOutputPort() );
imageChangeInfo->SetOutputSpacing(desiredSpacing);
double sampleDistance = 1e-6;
double scalarOpacityUnitDistance = 1e-6;
vtkSmartPointer<vtkVolumeRayCastMapper> cpuRayCaster = vtkSmartPointer<vtkVolumeRayCastMapper>::New();
cpuRayCaster->SetInputConnection(imageChangeInfo->GetOutputPort() );
cpuRayCaster->SetSampleDistance(sampleDistance);
cpuRayCaster->SetAutoAdjustSampleDistances(1);
vtkSmartPointer<vtkVolumeRayCastCompositeFunction> compFunction = vtkSmartPointer<vtkVolumeRayCastCompositeFunction>::New();
cpuRayCaster->SetVolumeRayCastFunction(compFunction);
vtkSmartPointer<vtkGPUVolumeRayCastMapper> gpuRayCaster = vtkSmartPointer<vtkGPUVolumeRayCastMapper>::New();
gpuRayCaster->SetInputConnection(imageChangeInfo->GetOutputPort() );
gpuRayCaster->SetSampleDistance(sampleDistance);
gpuRayCaster->SetAutoAdjustSampleDistances(1);
vtkSmartPointer<vtkColorTransferFunction> colorTransferFunction = vtkSmartPointer<vtkColorTransferFunction>::New();
colorTransferFunction->AddRGBPoint( 0.0, 0.0, 0.0, 0.0);
colorTransferFunction->AddRGBPoint( 64.0, 1.0, 0.0, 0.0);
colorTransferFunction->AddRGBPoint(128.0, 0.0, 0.0, 1.0);
colorTransferFunction->AddRGBPoint(192.0, 0.0, 1.0, 0.0);
colorTransferFunction->AddRGBPoint(255.0, 0.0, 0.2, 0.0);
vtkSmartPointer<vtkPiecewiseFunction> opacityTransferFunction = vtkSmartPointer<vtkPiecewiseFunction>::New();
opacityTransferFunction->AddPoint(0, 0.0);
opacityTransferFunction->AddPoint(255, 1.0);
vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
volumeProperty->SetColor(colorTransferFunction);
volumeProperty->SetScalarOpacity(opacityTransferFunction);
volumeProperty->SetScalarOpacityUnitDistance(scalarOpacityUnitDistance);
vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(cpuRayCaster);
volume->SetProperty(volumeProperty);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(volume);
renderer->SetBackground(1.0, 1.0, 1.0);
// Set the camera to a position demonstrating the effect.
vtkSmartPointer<vtkCamera> camera = renderer->GetActiveCamera();
double viewUp[3] = {-1.0, -1.0, -1.0};
double position[3] = {1000.0, 1000.0, -1000.0};
double focalPoint[3] = {0.0, 0.0, 0.0};
camera->SetPosition(position);
camera->SetViewUp(viewUp);
camera->SetFocalPoint(focalPoint);
renderer->ResetCamera();
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->SetSize(800, 600);
renderWindow->AddRenderer(renderer);
vtkSmartPointer<vtkRenderWindowInteractor> interactor= vtkSmartPointer<vtkRenderWindowInteractor>::New();
interactor->SetRenderWindow(renderWindow);
vtkSmartPointer<MyCallBack> callback = vtkSmartPointer<MyCallBack>::New();
callback->cpuRayCaster = cpuRayCaster;
callback->gpuRayCaster = gpuRayCaster;
callback->renderWindow = renderWindow;
callback->volume = volume;
interactor->AddObserver("KeyPressEvent", callback);
renderWindow->Render();
interactor->Start();
}
I have attached the result of the program.