体渲染
体渲染是一个用于描述3D数据渲染过程的术语,这里的3D数据是指其属性信息遍及3D空间,而不是一个在3D空间中的2D曲面;
面渲染是对数据的表面或者一个抽取的轮廓进行渲染,是通过对面上的标量属性进行显示的;面渲染能显示其表面或者一个抽取的轮廓,但不能反映出3D数据信息的空间特性;
体渲染最为常用的技术是
光线投射(ray cast)
。
光线投射
(ray cast)是假设有一道射线照射在体数据上,并从体数据中直穿过去。如果把这一射线与体数据表面交点的标量数据映射为颜色,并当作该位置上最终显示的颜色,这就是面渲染,该点背后大量的数据信息没有表示出来。
而体渲染技术要求最终的渲染结果能在一定程度上反映出背后的信息,也就是对体数据中位于同一射线上的点的标量数据,通过共同作用产生最终该位置上的颜色;
对这些标量数据的计算方式有两种:
1.对射线上的一系列离散点颜色数据进行加权叠加。其中颜色数据是通过对这些离散点的标量数据映射得来的;加权系数指的是不透明度,其取值范围为0-1;不透明度也可以通过标量数据映射得到;
2.通过梯度映射得到;
下图是面渲染结果:
下图是体渲染结果:
从上面两张图可以明显看到面渲染和体渲染的不同效果;体渲染有三维空间上的深度的延伸;
总之:面绘制提取的是感兴趣物体的表面信息,所重建出的三维图像不能反映物体内部的细节,应用于切割具有局限性。体绘制的处理是直接对所有体数据进行光照、纹理、计算处理,从而合成三维图像,更加适合与三维切割,且可以清晰的表达图像的内部信息与三维图像的关系。相比面绘制,体绘制在三维图像切割及三维图像切片中具有一定的优势。体绘制由于体绘制需要对所有体数据进行处理,切割效果好,但是计算量大、耗时长。
VTK中和体渲染相关类
vtkFixedPointVolumeRayCastMapper
在早期VTK版本中,使用vtkVolumeRayCastMapper进行体数据映射,由于在最新VTK版本中不再支持vtkVolumeRayCastMapper,则使用了vtkFixedPointVolumeRayCastMapper或者vtkGPUVolumeRayCastMapper来替代;
vtkFixedPointVolumeRayCastMapper能够实现基于Alpha合成的体渲染方法和最大密度投影体渲染方法,能够支持任意类型的医院或者多元数据。数据中由颜色和不透明度表示;或者配合vtkPiecewiseFunction和 vtkColorTransferFunction将标量数据映射为不同的颜色和不透明度;
默认采用的是基于Alpha合成的体渲染方法;
volumeMapper->SetBlendModeToComposite();
vtkColorTransferFunction可以设置使用最大密度投影体渲染:
volumeMapper->SetBlendModeToMaximumIntensity();
关于vtkFixedPointVolumeRayCastMapper的使用,以后找时间单独做些笔记;
vtkVolume
vtkVolume与几何渲染中的vtkActor作用一致,vtkVolume适用于体渲染场景,用于渲染场景中的对象;
需要设置如下两个输入:
void SetMapper(vtkAbstractVolumeMapper* mapper):该函数用于设置Mapper对象。
void SetProperty(vtkVolumeProperty* property):该函数用于设置属性vtkVolumeProperty对象。
vtkVolumeProperty
vtkVolumeProperty表达了用于体渲染的一些公共属性,包括颜色、标量不透明度、梯度不透明度等;
不透明度可以由标量或梯度转变来。可分别用方法SetScalarOpacity()和SetGradientOpacity()设置转移函数。如果这两个函数都设置了,那么最终的不透明度就是两者的乘积;如果这两个函数都没设置,则不透明度为常数1.0;
void SetScalarOpacity(int index, vtkPiecewiseFunction* function);
void SetScalarOpacity(vtkPiecewiseFunction* function) { this->SetScalarOpacity(0, function); }
vtkPiecewiseFunction* GetScalarOpacity(int index);
vtkPiecewiseFunction* GetScalarOpacity() { return this->GetScalarOpacity(0); }
void SetGradientOpacity(int index, vtkPiecewiseFunction* function);
void SetGradientOpacity(vtkPiecewiseFunction* function) { this->SetGradientOpacity(0, function); }
vtkPiecewiseFunction* GetGradientOpacity(int index);
vtkPiecewiseFunction* GetGradientOpacity() { return this->GetGradientOpacity(0); }
颜色由标量转变来。可用方法SetColor()设置转移函数。如果要将标量转变为灰度值,那么该方法也可接受vtkPiecewiseFunction实例作为参数;如果是将标量转变为彩色值,则接受vtkColorTransferFunction实例;
void SetColor(int index, vtkPiecewiseFunction *function);
void SetColor(vtkPiecewiseFunction *function){ this->SetColor(0, function); }
void SetColor(int index, vtkColorTransferFunction *function);
void SetColor(vtkColorTransferFunction *function){ this->SetColor(0, function); }
注意
:对于有多个分量的标量或梯度,在设置转移函数时,还可以指定一个索引,以表明是对哪个分量进行转变;如果省略该参数,则使用默认的索引0;
光照属性:vtkVolumeProperty也可以对光照属性进行设置。默认这些属性是不起作用,需要显示时应调用ShadeOn()方法。另外,光照属性的应用还依赖于法线的方向计算法线属于映射器的任务;光照属性主要包括环境光系数、散射光系数、反射光系数和高光强度四个参数影响;一般情况下环境光系数、散射光系数、反射光系数之和为1.0;
但是有时需要提高亮度,就会将之和设置超过1.0;
高光强度用来控制外观平滑程度;
vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
volumeProperty->SetInterpolationTypeToLinear();
volumeProperty->ShadeOn(); // 打开光照设置
volumeProperty->SetAmbient(0.4); // 环境光系数
volumeProperty->SetDiffuse(0.6); // 散射光系数
volumeProperty->SetSpecular(0.2); // 反射光系数
volumeProperty->SetSpecularPower(1);// 高光强度
开启阴影效果,光照属性生效;下图为渲染效果图:
默认关闭阴影效果,光照属性不生效;下图为渲染图:
vtkPiecewiseFunction
vtkPiecewiseFunction定义分段函数映射,相当是分段的线程函数,是标量数据值到不透明度的映射关系。
其中X轴方向表示标量数据值;Y轴方向表示不透明度数值;不透明度Y=f(X)上的任一点都是对应标量X在f()上的映射;
使用vtkPiecewiseFunction时,只要给定几个点或几条线段,它就会用直线将这些点连起来,以形成最后的映射函数f();
vtkPiecewiseFunction的功能是,将某些值或者某些范围的值对应的透明度调高或者降低,以突出其他数值的显示;
配合vtkVolumeProperty的函数SetScalarOpacity和SetGradientOpacity;可以使用不用不透明度映射关系,呈现不同的渲染效果;例如:在CT图像中,表示皮肤、肌肉、骨骼的标量值各不相同,其窗宽和窗位处于不同的范围,那么就可以使用SetScalarOpacity来设置标量对应不透明度的映射关系,用来突出骨骼或者皮肤,其他组织就可以设定透明忽略;再比如:在不同材料的临界区域,如空气到软组织,或者软组织到骨头的临界区,梯度值会比较大,而材料的内部梯度值则会相对比较小;就可以使用SetGradientOpacity来设定梯度值与不透明度的映射关系,来突出高梯度的变化情况;
接口
增加点/片段/移除点
int AddPoint(double x, double y);
int AddPoint(double x, double y, double midpoint, double sharpness);
bool RemovePointByIndex(size_t id);
int RemovePoint(double x);
int RemovePoint(double x, double y);
void RemoveAllPoints();
void AddSegment(double x1, double y1, double x2, double y2);
获取X/Y/中点/锐度值
int GetNodeValue(int index, double val[4]);
int SetNodeValue(int index, double val[4]);
示例
vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
volumeProperty->SetInterpolationTypeToLinear();
volumeProperty->ShadeOn(); //打开或者关闭阴影测试
volumeProperty->SetAmbient(0.4);
volumeProperty->SetDiffuse(0.6); //漫反射
volumeProperty->SetSpecular(0.2); //镜面反射
//设置不透明度
vtkSmartPointer<vtkPiecewiseFunction> compositeOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
compositeOpacity->AddPoint(70, 0.00);
compositeOpacity->AddPoint(90, 0.40);
compositeOpacity->AddPoint(180, 0.60);
volumeProperty->SetScalarOpacity(compositeOpacity);
//设置梯度不透明属性
vtkSmartPointer<vtkPiecewiseFunction> volumeGradientOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
volumeGradientOpacity->AddPoint(10, 0.0);
volumeGradientOpacity->AddPoint(90, 0.5);
volumeGradientOpacity->AddPoint(100, 1.0);
volumeProperty->SetGradientOpacity(volumeGradientOpacity);
vtkColorTransferFunction
vtkColorTransferFunction是将标量数值映射到RGB或HSV空间中的颜色,它使用分段hermite函数来允许插值,插值可以是分段常数、分段线性或介于两者之间的某个位置(一种修改的分段hermite函数,它根据锐度参数压扁函数)。该函数还允许将中点(函数达到两个边界节点平均值的位置)指定为节点之间的规格化距离。
通俗来讲就是,vtkColorTransferFunction和vtkPiecewiseFunction类似,也是将标量范围分成几段,每段标量波形对其中一个颜色分量进行映射;
接口
设置点/线段/移除
设置点的颜色,两点之间的颜色根据设置进行插值;
int AddRGBPoint(double x, double r, double g, double b);
int AddRGBPoint(double x, double r, double g, double b, double midpoint, double sharpness);
int AddHSVPoint(double x, double h, double s, double v);
int AddHSVPoint(double x, double h, double s, double v, double midpoint, double sharpness);
int RemovePoint(double x);
void AddRGBSegment(double x1, double r1, double g1, double b1, double x2, double r2, double g2, double b2);
void AddHSVSegment(double x1, double h1, double s1, double v1, double x2, double h2, double s2, double v2);
void RemoveAllPoints();
获取指定标量对应颜色
double* GetColor(double x) VTK_SIZEHINT(3) { return vtkScalarsToColors::GetColor(x); }
void GetColor(double x, double rgb[3]) override;
double GetRedValue(double x);
double GetGreenValue(double x);
double GetBlueValue(double x);
设置颜色空间
vtkSetClampMacro(ColorSpace, int, VTK_CTF_RGB, VTK_CTF_STEP);
void SetColorSpaceToRGB() { this->SetColorSpace(VTK_CTF_RGB); }
void SetColorSpaceToHSV() { this->SetColorSpace(VTK_CTF_HSV); }
void SetColorSpaceToLab() { this->SetColorSpace(VTK_CTF_LAB); }
void SetColorSpaceToLabCIEDE2000() { this->SetColorSpace(VTK_CTF_LAB_CIEDE2000); }
void SetColorSpaceToDiverging() { this->SetColorSpace(VTK_CTF_DIVERGING); }
void SetColorSpaceToStep() { this->SetColorSpace(VTK_CTF_STEP); }
vtkGetMacro(ColorSpace, int);
vtkSetMacro(HSVWrap, vtkTypeBool);
vtkGetMacro(HSVWrap, vtkTypeBool);
vtkBooleanMacro(HSVWrap, vtkTypeBool);
示例
vtkSmartPointer<vtkColorTransferFunction> color = vtkSmartPointer<vtkColorTransferFunction>::New();
color->AddRGBPoint(0.000, 0.00, 0.00, 0.00);
color->AddRGBPoint(64.00, 1.00, 0.52, 0.30);
color->AddRGBPoint(190.0, 1.00, 1.00, 1.00);
color->AddRGBPoint(220.0, 0.20, 0.20, 0.20);
简单示例
只是简单举一个例子,例子中使用到的具体类说明和介绍,在之后笔记中记录;
张晓东, 罗火灵的《VTK图形图像开发进阶》中关于体绘制的代码如下:
/**********************************************************************
文件名: 7.1_VolumeRenderingApp.cpp
Copyright (c) 张晓东, 罗火灵. All rights reserved.
更多信息请访问:
http://www.vtkchina.org (VTK中国)
http://blog.csdn.net/www_doling_net (东灵工作室)
**********************************************************************/
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkStructuredPoints.h>
#include <vtkStructuredPointsReader.h>
#include <vtkVolumeRayCastCompositeFunction.h>
#include <vtkGPUVolumeRayCastMapper.h>
#include <vtkVolumeRayCastMapper.h>
#include <vtkColorTransferFunction.h>
#include <vtkPiecewiseFunction.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkVolumeProperty.h>
#include <vtkAxesActor.h>
#include <vtkImageShiftScale.h>
#include <vtkImageCast.h>
#include <vtkFixedPointVolumeRayCastMapper.h>
//测试:../data/mummy.128.vtk
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout<<argv[0]<<" "<<"StructuredPointsFile(*.vtk)"<<std::endl;
return EXIT_FAILURE;
}
vtkSmartPointer<vtkStructuredPointsReader> reader = vtkSmartPointer<vtkStructuredPointsReader>::New();
reader->SetFileName(argv[1]);
reader->Update();
vtkSmartPointer<vtkVolumeRayCastCompositeFunction> rayCastFun = vtkSmartPointer<vtkVolumeRayCastCompositeFunction>::New();
vtkSmartPointer<vtkVolumeRayCastMapper> volumeMapper = vtkSmartPointer<vtkVolumeRayCastMapper>::New();
volumeMapper->SetInput(reader->GetOutput());
volumeMapper->SetVolumeRayCastFunction(rayCastFun);
vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
volumeProperty->SetInterpolationTypeToLinear();
//volumeProperty->ShadeOn(); //打开或者关闭阴影测试
volumeProperty->SetAmbient(0.4);
volumeProperty->SetDiffuse(0.6);
volumeProperty->SetSpecular(0.2);
vtkSmartPointer<vtkPiecewiseFunction> compositeOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
compositeOpacity->AddPoint(70, 0.00);
compositeOpacity->AddPoint(90, 0.40);
compositeOpacity->AddPoint(180, 0.60);
volumeProperty->SetScalarOpacity(compositeOpacity); //设置不透明度传输函数
vtkSmartPointer<vtkPiecewiseFunction> volumeGradientOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
volumeGradientOpacity->AddPoint(10, 0.0);
volumeGradientOpacity->AddPoint(90, 0.5);
volumeGradientOpacity->AddPoint(100, 1.0);
vtkSmartPointer<vtkColorTransferFunction> color = vtkSmartPointer<vtkColorTransferFunction>::New();
color->AddRGBPoint(0.000, 0.00, 0.00, 0.00);
color->AddRGBPoint(64.00, 1.00, 0.52, 0.30);
color->AddRGBPoint(190.0, 1.00, 1.00, 1.00);
color->AddRGBPoint(220.0, 0.20, 0.20, 0.20);
volumeProperty->SetColor(color);
vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(volumeMapper);
volume->SetProperty(volumeProperty);
vtkSmartPointer<vtkRenderer> ren = vtkSmartPointer<vtkRenderer>::New();
ren->SetBackground(1.0, 1.0, 1.0);
ren->AddVolume( volume );
vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();
renWin->AddRenderer(ren);
renWin->SetSize(640, 480);
renWin->Render();
renWin->SetWindowName("VolumeRenderingApp");
vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New();
iren->SetRenderWindow(renWin);
ren->ResetCamera();
renWin->Render();
iren->Start();
return EXIT_SUCCESS;
}
遇到问题1
编译时提示:
#include <vtkVolumeRayCastCompositeFunction.h>
#include <vtkVolumeRayCastMapper.h>
找不到头文件;由于我使用的是VTK8.2版本,VTK8.2版本不再支持光线投射算法的库
vtkVolumeRayCastCompositeFunction
和
vtkVolumeRayCastMapper
;可以使用
vtkFixedPointVolumeRayCastMapper
或者
vtkGPUVolumeRayCastMapper
代替;
#include "pch.h"
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkInteractionStyle);
#include <vtkSmartPointer.h>
#include <vtkStructuredPoints.h>
#include <vtkStructuredPointsReader.h>
#include <vtkFixedPointVolumeRayCastMapper.h>
#include <vtkColorTransferFunction.h>
#include <vtkPiecewiseFunction.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkVolumeProperty.h>
#include <vtkAxesActor.h>
#include <vtkOrientationMarkerWidget.h>
int main(int argc, char* argv[])
{
vtkSmartPointer<vtkStructuredPointsReader> reader = vtkSmartPointer<vtkStructuredPointsReader>::New();
reader->SetFileName("G:\\Data\\VTK\\mummy.128.vtk");
reader->Update();
vtkSmartPointer<vtkFixedPointVolumeRayCastMapper> volumeMapper = vtkSmartPointer<vtkFixedPointVolumeRayCastMapper>::New();
volumeMapper->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
volumeProperty->SetInterpolationTypeToLinear();
volumeProperty->ShadeOn(); //打开或者关闭阴影测试
volumeProperty->SetAmbient(0.4);
volumeProperty->SetDiffuse(0.6); //漫反射
volumeProperty->SetSpecular(0.2); //镜面反射
//设置不透明度
vtkSmartPointer<vtkPiecewiseFunction> compositeOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
compositeOpacity->AddPoint(70, 0.00);
compositeOpacity->AddPoint(90, 0.40);
compositeOpacity->AddPoint(180, 0.60);
volumeProperty->SetScalarOpacity(compositeOpacity); //设置不透明度传输函数
//设置梯度不透明属性
vtkSmartPointer<vtkPiecewiseFunction> volumeGradientOpacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
volumeGradientOpacity->AddPoint(10, 0.0);
volumeGradientOpacity->AddPoint(90, 0.5);
volumeGradientOpacity->AddPoint(100, 1.0);
volumeProperty->SetGradientOpacity(volumeGradientOpacity);//设置梯度不透明度效果对比
//设置颜色属性
vtkSmartPointer<vtkColorTransferFunction> color = vtkSmartPointer<vtkColorTransferFunction>::New();
color->AddRGBPoint(0.000, 0.00, 0.00, 0.00);
color->AddRGBPoint(64.00, 1.00, 0.52, 0.30);
color->AddRGBPoint(190.0, 1.00, 1.00, 1.00);
color->AddRGBPoint(220.0, 0.20, 0.20, 0.20);
volumeProperty->SetColor(color);
/********************************************************************************/
vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
volume->SetMapper(volumeMapper);
volume->SetProperty(volumeProperty);
vtkSmartPointer<vtkRenderer> ren = vtkSmartPointer<vtkRenderer>::New();
ren->SetBackground(0, 1, 0);
ren->AddVolume(volume);
vtkSmartPointer<vtkRenderWindow> rw = vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(ren);
rw->SetSize(640, 480);
rw->Render();
rw->SetWindowName("VolumeRendering PipeLine");
vtkSmartPointer<vtkRenderWindowInteractor> rwi = vtkSmartPointer<vtkRenderWindowInteractor>::New();
rwi->SetRenderWindow(rw);
ren->ResetCamera();
rw->Render();
rwi->Start();
return 0;
}
遇到问题2
之前代码使用的下面的配置;
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2); // VTK was built with vtkRenderingOpenGL2
VTK_MODULE_INIT(vtkInteractionStyle);
在例子代码运行时报:
使用以下配置,问题解决;
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkInteractionStyle);
运行结果如下图:
本文中使用的vtk文件:链接:https://pan.baidu.com/s/1OPIw3jkqEnPbxe2YiihUYQ 提取码:uk37
参考资料
1.《VTK图形图像开发进阶》;
2.
初次学习VTK遇到的一些坑(适用于初学者)
3.
VTK系列64_VTK体绘制管线(光线投影法体绘制)
4.《医学图像编程技术》