目录

VTK知识学习46-体绘制一

VTK知识学习(46)- 体绘制(一)

前面所采用的渲染技术都是几何渲染,即通过绘制几何图元(顶点、线段、面片等)来渲染数据,例如,绘制图像需要在空间中建立一个四边形图元,然后以纹理映射的方式将图像贴图到该图元上进行渲染;而三维模型的绘制通常会分解为一系列的多边形面片进行绘制。这种通过生成中间几何图元来进行渲染的方法称为几何渲染。几何渲染方式速度比较快,但是不能显示体数据的内部细节,例如,在渲染人的三维CT体数据时,通过几何渲染只能在切片图像之间进行切换,而不能对体数据内部细节进行立体观察。这时就需要用到体绘制技术了。

体绘制,有时又称作三维重建(区别于投影图像的三维重建),是一种直接利用体数据来生成二维图像的绘制技术。与面绘制不同,体绘制技术不需要提取体数据内部的等值面,它是一个对三维体数据进行采样和合成的过程。体绘制能够通过设置不透明度值来显示体数据内部的不同成分和细节,例如显示人体 CT图像的不同器官和组织。体绘制也是 VTK 中的一个重要内容。

以前面的示例来说明,将渲染窗口 tkRenderWindow 看作一个剧院,剧院中一般需要有灯光(vkLight)、相机(vtkCamera,可以理解为观众)和舞台(vtkRenderer)来为观众呈现精彩的演出:舞台上负责表演的自然是演员(vtkActor),而且演员通常不止一个,可以根据需要为舞台加入更多的演员(vtkActor)。每个演员又各具特色,而用来表示其特色的则是 vkProperty(负责控制颜色、材质以及不透明度等);每个vtkActor 的数据和渲染信息存储在一个 vtkMapper 对象中,负责将原始数据转换为渲染所需的图元数据。

从可视化管线的组成上来讲,体绘制的渲染管线与几何渲染管线基本一致。

private void TestVolumeRendering()
{
    vtkStructuredPointsReader reader = vtkStructuredPointsReader.New();
    reader.SetFileName("F:\\code\\VTK\\TestActiViz\\data\\mummy.128.vtk");
    reader.Update();

    //体积的固定点映射器:这是一个软件光线投射器,用于在 vtkImageData 中渲染体积。它适用于所有输入数据类型和最多四个组件。
    //它执行复合或 MIP 渲染,并且可以与几何数据混合。
    //空间跳跃用于加快渲染过程。此外,计算以 15 位定点精度执行。
    //此映射器是线程化的,并将跨处理器交错扫描行。
    //警告:  当线程数超过 1 时,此 Ray Caster 可能无法产生一致的结果.

    //应用程序应确定性能的权衡是否值得缺乏一致性。
    //此光线投射器的其他限制包括: -它不进行等值面光线投射 - 它仅在分类合成之前进行插值 - 它只执行最大标量值 MIP 此映射器处理从 unsigned char 到 double 的所有数据类型。
    //但是,一些内部计算是在 float 中执行的,因此即使是完整的 float 范围也可能会给这个 Mapper 带来问题(无论是在标量数据值还是在样本之间的间距上)。
    //空间跳跃是通过创建一个 Sub 采样的 Volume 来执行的。原始体积中的 4x4x4 单元格由 Min、Max 以及组合的 Gradient 和 Flag 值表示。
    //最小最大卷从原始卷的每 4x4x4 组单元中有三个无符号短整型 -一个代表最小标量索引(调整标量值以适应 15 位范围),
    //最大标量索引和第三个 unsigned short,它既是邻域中的最大渐变不透明度(unsigned char),也是为当前查找表填充的标志,以指示是否可以跳过此区域。
    vtkGPUVolumeRayCastMapper texMapper = vtkGPUVolumeRayCastMapper.New();
    vtkVolume vol = vtkVolume.New();
    vtkColorTransferFunction ctf = vtkColorTransferFunction.New();
    vtkPiecewiseFunction spwf = vtkPiecewiseFunction.New();
    vtkPiecewiseFunction gpwf = vtkPiecewiseFunction.New();

    //Go through the visulizatin pipeline
    texMapper.SetInputConnection(reader.GetOutputPort());

    texMapper.SetSampleDistance(0.5f);
    //texMapper.SetImageSampleDistance(0.5f);
    //texMapper.SetAutoAdjustSampleDistances(1);
    //Set the color curve for the volume 设置体积的颜色曲线
    ctf.AddRGBPoint(0.000, 0.00, 0.00, 0.00);
    ctf.AddRGBPoint(64.00, 1.00, 0.52, 0.30);
    ctf.AddRGBPoint(190.0, 1.00, 1.00, 1.00);
    ctf.AddRGBPoint(220.0, 0.20, 0.20, 0.20);

    //Set the opacity curve for the volume  设置体积的不透明度曲线
    spwf.AddPoint(70, 0.00);
    spwf.AddPoint(90, 0.40);
    spwf.AddPoint(180, 0.60);

    //Set the gradient curve for the volume 设置体积的梯度曲线
    gpwf.AddPoint(10, 0.0);
    gpwf.AddPoint(90, 0.5);
    gpwf.AddPoint(100, 1.0);

    //vol.GetProperty().SetColor(ctf);
    //vol.GetProperty().SetScalarOpacity(spwf);
    //vol.GetProperty().SetGradientOpacity(gpwf);

    vtkVolumeProperty volumeProperty = vtkVolumeProperty.New();
    volumeProperty.SetInterpolationTypeToLinear();
    //volumeProperty.ShadeOn();
    volumeProperty.SetAmbient(0.4);
    volumeProperty.SetDiffuse(0.6);
    volumeProperty.SetSpecular(0.2);

    volumeProperty.SetColor(ctf);   //设置颜色传输函数
    volumeProperty.SetScalarOpacity(spwf);  //设置灰度不透明度函数
    volumeProperty.SetGradientOpacity(gpwf);

    vol.SetMapper(texMapper);
    vol.SetProperty(volumeProperty);

    //Go through the Graphics Pipeline

    vtkRenderer render = vtkRenderer.New();
    render.AddVolume(vol);

    vtkRenderWindow renWin = renderWindowControl.RenderWindow;
    renWin.AddRenderer(render);
    renWin.Render();
}

https://i-blog.csdnimg.cn/direct/9a2e3fef1a1645638e14ceb467d85ee1.png

①  首先定义 vtkVolumeRayCastMapper 对象。vtkVolumeRayCastMapper 定义了一个光线投射体绘制 Mapper,其主要接受如下两个输入。

Setlnput(vtkImageData*)。该函数用于设置输入图像数据。

SetVolumeRayCastFunction(vtkVolumeRayCastFunction*)。该函数用干设置光线投射函数类型。

vtkVolumeRayCastCompositeFunction 是 vtkVolumeRayCastFunction 的子类,定义光线经过体数据后的颜色计算方式。

②定义 vtkVolumeProperty 体绘制属性对象,并设置标量不透明度传输函数、梯度不透明度函数和颜色传输函数以及阴影。

vtkVolumeProperty用于设置体绘制的属性设置,决定体绘制的渲染效果,其中:

SetScalarOpacity(vtkPiecewiseFunction *fnction)。该函数用于设置灰度不透明度函数

SetColor(vtkColorTransferFunction *function)。该函数用于设置颜色传输函数。

③定义 vtkVolume 对象。vtkVolume 与几何渲染中的 vtkActor 作用一致,需要设置如下两个输入。void SetMapper(vtkAbstractVolumeMapper mapper)。该函数用于设置 Mapper 对象。void SetProperty(vtkVolumePropertyproperty)。该函数用于设置属性对象。

④定义vtkRenderer、vtkRenderWindow 和 vtkRenderWindowInteractor 对象,建立可视化管线。

从以上代码可以看出,体绘制渲染管线和几何渲染管线的组成是比较一致的,都需要vtkRenderWindow、vtkRender、vtkActor/vtkVolume、vtkMapper 等对象。它们的不同之处在于:在几何渲染中,通常使用 vtkActor 来渲染几何图形数据,使用 vtkImageActor 来渲染图像数据:在体绘制中则使用 vtkVolume 渲染数据。在几何渲染中,通常采用 vkPolyDataMapper 实现输入数据向图元数据的转换;在体绘制中,则采用的是vtkVolumeRayCastMapper,这是与体绘制算法有关的,不同的体绘制算法会有不同的Mapper 类,通常在上面代码中直接替换为相应的 Mapper 类即可。下图为几何渲染管线和体绘制渲染管线的对比图,为了便于比较,两个渲染管线的输入都为图像,左侧为一般的几何渲染管线,右侧为体绘制渲染管线,其中虚线部分为两者不同之处。从中可以看出,两者的不同主要在于Mapper和Actor 对象。

https://i-blog.csdnimg.cn/direct/3ace17c974ca4ac596e0d9682f6280aa.png

vtkVolumeMapper 是所有体绘制 Mapper 类的虚基类,提供接口函数,并由其子类实现具体的功能。该类的继承关系如图所示。

https://i-blog.csdnimg.cn/direct/ee4148f3c1c54c158c7ca1fa4da7c37e.png

光线投射法是最常用的体绘制方法。它是一种基于图像序列的直接体绘制方法,其基本原理是从投影图像平面(通常为平面)的每个像素沿着视线方向发射一条穿过体数据的射线然后在射线上按照一定的步长进行等距采样,对每个采样点采用插值技术来计算其体素值,根据颜色传输函数和不透明度传输函数来获取相应的颜色值和不透明度,最后利用光线吸收模型将颜色值进行累加直至光线穿过体数据,即可得到当前平面像素的渲染颜色,生成最终的显示图像。光线投射法的优点是能够比较精确地模拟原始体数据,但计算量比较大,实时体绘制对计算机硬件的要求比较高。

在VTK 中,vtkVolumeRayCastMapper 类可用于实现光线投射体算法,并生成渲染图元数据传递给 vkVolume 对象进行渲染。vtkVolumeRayCastMapper 采用软件方法来实现光线投射算法,精度高但是计算量比较大,因此渲染速度相对比较慢。前面提到了其内部两个最重要的函数。

vtkVolumeRayCastMapper 中计算每条光线在通过体数据后的颜色是通过定义的vtkVolumeRayCastFunction 对象来实现的,因此必须为vtkVolumeRayCastMapper 指定一个vtkVolumeRayCastFunction 对象。投射函数类 vkVolumeRayCastFunction 是一个虚基类,它有三个子类。

该方式通过 Alpha合成技术生成每个像素的颜色值。对于每条管线在穿过体数据时,先根据设置的采样步长进行采样,通过插值技术来计算每个采样点的像素值;然后根据vtkVolumeProperty 中设置的颜色传输函数和不透明度传输函数来计算采样点的颜色和不透明度;最后对所有采样点采用Alpha 合成方法计算最终的颜色。

另外,该方式还可以设置插值优先还是分类优先。插值优先是指对投射光线进行采样计算采样点的颜色值时,先通过插值方式计算该采样点的标量值(灰度值),然后根据颜色传输函数和不透明度传输函数计算该采样点的颜色值和不透明度。分类优先则是指在计算采样点时,先根据颜色传输函数和不透明度传输函数计算包含该采样点的立方体的8个顶点颜色值和不透明度,然后通过插值方法获取当前采样点的颜色值和不透明度。设置插值优先和分类优先的函数如下。

  • void SetCompositeMethodToInterpolateFirst().
  • void SetCompositeMethodToClassifyFirst()。

从显示效果上来说,插值优先具有较好的显示效果,但在一些情况下也会存在问题。例如,CT图像中低于20的值属于空气,20~80的部分是软组织,而大于80的部分则是骨头。如果先进行插值,那么空气与骨头之间永远不会相邻,因为它们之间一定存在着软组织。但是人体中牙齿是和空气直接接触的,当使用插值优先策略并使用比较高的采样率进行体绘制时,渲染结果会使牙齿看起来像是覆盖着一层皮肤。

最大密度投影函数主要用于对体数据中高灰度值的结构进行可视化。当光线穿过体数据时,在光线上进行等距离采样。取采样点中属性最大值为该条光线的输出。光线对应的屏幕像素颜色值即可通过该值进行颜色映射获得。默认情况下,这个属性是指体数据的像素值或者标量值,也可以指定为不透明度,其设置函数如下。

  • void SetMaximizeMethodToScalarValue()。
  • void SetMaximizeMethodToOpacity()。

当采用标量属性时,先经过体数据的光线进行等距离采样,然后通过插值计算每个采样点的标量值,最后取标量值为最大的采样点对应的颜色和不透明度作为输出;当采用不透明度为参考时,投射光线上每次采样都要计算一次不透明度,最后取不透明度为最大值的采样点的颜色和不透明度为输出。通常最大强度投影算法不计算明暗信息和深度信息,成像类似X光图像该方法常用于显示血管的三维结构。

等值面绘制函数能够渲染体数据中的特定等值面,其中SetIsoValue(double)函数用于设置等值面的值。在进行体绘制时,所有小于该值的像素不透明度都设置为0。通过设定等值面的值,可以重建出某一特定的组织,如皮肤、骨骼等,渲染结果类似于面绘制。同样地,使

用该函数进行体绘制时,应将代码:

vtkSmartPointer rayCastFun         =vtkSmartPointer::New();

替换为:

vtkSmartPointer rayCastFun         =vtkSmartPointer::New(;rayCastFun->SetlsoValue(100);

并调用 SetlsoValue(double)函数来设置要进行显示的等值面的值。

光线投射体绘制中,投射光线上的采样点的步长是一个重要参数,决定了体绘制的精度和速度。步长越小,采样点就越多,但体绘制效果提高的同时计算量也会增加。vkVolumeRayCastMapper中设置步长的函数为:

void SetSampleDistance (float)

此外,等值面绘制函数不需要进行采样,而其他两个绘制函数都需要进行采样。在实际应用中,调整采样步长主要考虑的因素有数据的样本间距、标量数据以及由标量数据映射的颜色和不透明度变化率。

vtkVolumeRayCastMapper 中默认的采样步长为1,单位为世界坐标单位。采样步长越小,渲染效果越精细。当体数据变化比较剧烈时,应该适当减小采样步长获取好的显示效果,当然其代价便是渲染速度的降低。

还可以设置图像采样距离,即投射光线的间隔。在默认情况下,图像采样距离为1,即每个像素对应一条投射光线;如果该值设置为0.5,则每个像素对应4条投射光线;而如果设置为2,每4个像素对应一条投射光线。设置图像采样距离的函数为:

void SetlmageSampleDistance (float)

需要特别注意的是,当使用该函数时,必须先关闭自动调节采样距离功能,即调用函数:

void SetAutoAdjustSampleDistances (int)

并将参数设置为0来关闭该功能,否则设置的图像采样距离将无效。需要特别注意的是,默认情况下自动调节图像采样距离是开启的,这样保证了在图像旋转等交互时,能够自动调节采样距离来保证实时性。如果在程序中,关闭该功能,则在旋转体绘制图像时会非常慢。

当设置自动计算图像采样距离时,还可以设置最小图像采样距离和最大图像采样距离,其函数为:

void SetMinimumImageSampleDistance (float)

void SetMaximumImageSampleDistance (float)

当增大图像采样距离时,渲染效果较为粗糙,但是渲染速度会相应地提高。在实际使用时,要对效果和速度进行平衡,因此需要设置合适的图像采样距离。

https://i-blog.csdnimg.cn/direct/3ed358da850f488f98b6dc1eca9bdd83.png

vtkFixedPointVolumeRayCastMapper 是一个较好的 vtkVolumeRayCastMapper 的替代者。该类能够实现基于Alpha合成的体绘制方法和最大密度投影体绘制方法,能够支持任意类的一元或者独立多元数据。例如,当输入数据的二元独立数据时,第一元数据用于颜色映射而第二元用于不透明度映射:对于四元的unsigned char 类型数据,则前三元数据作为颜色值而第四元作为不透明度使用。该类使用了空间跳跃技术来加速体绘制渲染过程,而且在内部计算时统一使用了 foat 数据类型。vtkFixedPointVolumeRayCastMapper 的使用方法与 vtkVolumeRayCastMapper 基本相同,如支持设置投射光线采样步长、设置图像采样距离、设置自动调节图像采样距离等。二者的区别如下。

① vtkFixedPointVolumeRayCastMapper 只能支持基于 Alpha 合成的体绘制方法和最大密度投影体绘制方法,可以通过基类vtkVolumeMapper 的函数来设置。

  • void SetBlendModeToComposite().
  • void SetBlendModeToMaximumIntensity().
  • void SetBlendModeToMinimumIntensity().
  • evoid SetBlendModeToAdditive()。

②对于 Alpha合成体绘制方法,vtkFixedPointVolumeRayCastMapper 只能支持采样点先插值再分类操作。

③vtkFixedPointVolumeRayCastMapper 支持比 vtkVolumeRayCastMapper 更多的数据类型。