Cropping in vtkOpenGLGPUVolumeRayCastMapper is broken
This issue was created automatically from an original Mantis Issue. Further discussion may take place here.
Cropping in vtkOpenGLGPUVolumeRayCastMapper is not working correctly because the conversion from dataset coordinates to texture space is implemented incorrectly in the shader. This is the relevant shader code (from vtkVolumeShaderComposer::CroppingInit):
return std::string("
\n // Convert cropping region to texture space
\n float cropping_planes_ts[6];
\n mat4 datasetToTextureMat = in_inverseTextureDatasetMatrix;
\n vec4 temp = vec4(cropping_planes[0], cropping_planes[1], 0.0, 1.0);
\n temp = datasetToTextureMat * temp;
\n if (temp[3] != 0.0)
\n {
\n temp[0] /= temp[3]; temp[1] /= temp[3];
\n }
\n cropping_planes_ts[0] = temp[0];
\n cropping_planes_ts[1] = temp[1];
\n
\n temp = vec4(cropping_planes[2], cropping_planes[3], 0.0, 1.0);
\n temp = datasetToTextureMat * temp;
\n if (temp[3] != 0.0)
\n {
\n temp[0] /= temp[3]; temp[1] /= temp[3];
\n }
\n cropping_planes_ts[2] = temp[0];
\n cropping_planes_ts[3] = temp[1];
\n
\n temp = vec4(cropping_planes[4], cropping_planes[5], 0.0, 1.0);
\n temp = datasetToTextureMat * temp;
\n if (temp[3] != 0.0)
\n {
\n temp[0] /= temp[3]; temp[1] /= temp[3];
\n }
\n cropping_planes_ts[4] = temp[0];
\n cropping_planes_ts[5] = temp[1];"
);
Putting the lower and upper boundary for each dimension as x and y coordinates into a vector and multiplying with the matrix is incorrect.
Creating one vector with the lower boundaries and another one with the upper boundaries fixes the issue:
return std::string("
\n // Convert cropping region to texture space
\n float cropping_planes_ts[6];
\n mat4 datasetToTextureMat = in_inverseTextureDatasetMatrix;
\n vec4 temp = vec4(cropping_planes[0], cropping_planes[2], cropping_planes[4], 1.0);
\n temp = datasetToTextureMat * temp;
\n if (temp[3] != 0.0)
\n {
\n temp[0] /= temp[3];temp[1] /= temp[3];temp[2] /= temp[3];
\n }
\n cropping_planes_ts[0] = temp[0];
\n cropping_planes_ts[2] = temp[1];
\n cropping_planes_ts[4] = temp[2];
\n
\n temp = vec4(cropping_planes[1], cropping_planes[3], cropping_planes[5], 1.0);
\n temp = datasetToTextureMat * temp;
\n if (temp[3] != 0.0)
\n {
\n temp[0] /= temp[3];temp[1] /= temp[3];temp[2] /= temp[3];
\n }
\n cropping_planes_ts[1] = temp[0];
\n cropping_planes_ts[3] = temp[1];
\n cropping_planes_ts[5] = temp[2];"
);
This is a small program demonstrating the bug:
#include "vtkColorTransferFunction.h" #include "vtkCommand.h" #include "vtkImageChangeInformation.h" #include "vtkGPUVolumeRayCastMapper.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"
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)
{
changeZSpacing(0.9);
}
else if (strcmp(pressedKey, "9") == 0)
{
changeZSpacing(1.1);
}
}
void changeZSpacing(double factor)
{
double outputSpacing[3] = {1.0, 1.0, 1.0};
imageChangeInfo->GetOutputSpacing(outputSpacing);
outputSpacing[2] = outputSpacing[2] * factor;
imageChangeInfo->SetOutputSpacing(outputSpacing);
// Calculate the cropping planes so that they don't crop anything at all.
double croppingPlanes[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
croppingPlanes[1] = (dataDimensions[0]- 1) * outputSpacing[0];
croppingPlanes[3] = (dataDimensions[1]- 1) * outputSpacing[1];
croppingPlanes[5] = (dataDimensions[2]- 1) * outputSpacing[2];
// Cropping with these planes shouldn't crop anything
rayCastMapper->SetCropping(1);
rayCastMapper->SetCroppingRegionPlanes(croppingPlanes);
renderer->ResetCamera();
renderWindow->Render();
}
vtkSmartPointer<vtkImageChangeInformation> imageChangeInfo;
vtkSmartPointer<vtkGPUVolumeRayCastMapper> rayCastMapper;
vtkSmartPointer<vtkRenderer> renderer;
vtkSmartPointer<vtkRenderWindow> renderWindow;
int dataDimensions[3];
};
int main() { vtkSmartPointer pointsReader = vtkSmartPointer::New(); pointsReader->SetFileName("ironProt.vtk"); pointsReader->Update();
vtkSmartPointer<vtkImageChangeInformation> imageChangeInfo = vtkSmartPointer<vtkImageChangeInformation>::New();
imageChangeInfo->SetInputConnection(pointsReader->GetOutputPort());
int dataDimensions[3];
pointsReader->GetOutput()->GetDimensions(dataDimensions);
double desiredBounds = 500;
double outputSpacing[3];
outputSpacing[0] = desiredBounds / (double)dataDimensions[0];
outputSpacing[1] = desiredBounds / (double)dataDimensions[1];
outputSpacing[2] = desiredBounds / (double)dataDimensions[2];
imageChangeInfo->SetOutputSpacing(outputSpacing);
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);
vtkSmartPointer<vtkGPUVolumeRayCastMapper> rayCastMapper= vtkSmartPointer<vtkGPUVolumeRayCastMapper>::New();
rayCastMapper->SetInputConnection(imageChangeInfo->GetOutputPort());
// Calculate the cropping planes so that they don't crop anything at all.
double croppingPlanes[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
croppingPlanes[1] = (dataDimensions[0]- 1) * outputSpacing[0];
croppingPlanes[3] = (dataDimensions[1]- 1) * outputSpacing[1];
croppingPlanes[5] = (dataDimensions[2]- 1) * outputSpacing[2];
rayCastMapper->SetCropping(1);
rayCastMapper->SetCroppingRegionPlanes(croppingPlanes);
vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(rayCastMapper);
volume->SetProperty(volumeProperty);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
vtkSmartPointer<vtkRenderWindowInteractor> interactor= vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindow->AddRenderer(renderer);
renderWindow->SetSize(500, 300);
renderWindow->SetPosition(100, 100);
interactor->SetRenderWindow(renderWindow);
renderer->AddActor(volume);
renderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<MyCallBack> callback = vtkSmartPointer<MyCallBack>::New();
callback->imageChangeInfo = imageChangeInfo;
callback->rayCastMapper = rayCastMapper;
callback->renderer = renderer;
callback->renderWindow = renderWindow;
callback->dataDimensions[0] = dataDimensions[0];
callback->dataDimensions[1] = dataDimensions[1];
callback->dataDimensions[2] = dataDimensions[2];
interactor->AddObserver("KeyPressEvent", callback);
// Let's go
renderWindow->Render();
interactor->Start();
}
Pressing '8' or '9' decreases/increases the z-spacing of the rendered volume. If you hold '8' for a while you will see that the volume starts being cropped even though the cropping planes are set to crop nothing at all.