跳到主要内容

渲染引擎

RenderingEngine 让使用者能够创建视口、把这些视口与页面上的 HTML 元素关联起来, 并借助一块离屏 WebGL 画布把数据渲染到这些元素上。

需要说明的是,RenderingEngine 本身就能渲染多个视口,你并不需要创建多个引擎。 不过确实可以创建多个 RenderingEngine 实例——例如你想做多显示器方案、 并用各自独立的 WebGL 上下文渲染每台显示器上的视口时。

在 Cornerstone3D 中,RenderingEngine 是我们从零构建的, 并以 vtk.js 作为渲染的底层支撑。 vtk.js 是一个三维渲染库,能够利用 WebGL 做 GPU 加速渲染。

屏上渲染与离屏渲染​

在早前的 Cornerstone(旧版)中,我们用一块 WebGL 画布处理每个视口的数据。 这种做法扩展性不好:随着视口数量增加,以及面对复杂的影像使用场景 (例如相互同步的视口),最终会产生大量对屏上画布的更新, 性能随视口数量增加而下降。

在 Cornerstone3D 中,我们在离屏画布上处理数据。也就是说, 我们有一块很大的、不可见的画布(离屏画布),它把所有屏上画布都包含在自己内部。 当用户操作数据时,离屏画布上相应的像素会被更新;到渲染时, 我们再为每个视口把内容从离屏拷贝到屏上。由于拷贝过程比每次操作都重新渲染 各个视口快得多,性能下降的问题就此解决。

共享体数据 Mapper​

vtk.js 提供了我们用于渲染的标准渲染能力。在此之外,Cornerstone3D 还引入了 共享体数据 Mapper(Shared Volume Mappers), 使任何需要某份纹理的视口都能复用它,而不必复制数据。

举例来说,PET-CT 融合使用 3×3 布局,包含 CT(轴位、矢状位、冠状位)、 PET(轴位、矢状位、冠状位)和融合(轴位、矢状位、冠状位)。 我们分别为 CT 和 PET 各创建一个体数据 mapper, 而融合视口则复用这两份已创建的纹理,而不是重新创建新的。

渲染引擎的两种实现​

Cornerstone3D 提供两种渲染引擎实现,以应对不同的使用场景、并克服技术限制:

TiledRenderingEngine​

TiledRenderingEngine 是最初的实现,它为所有视口使用同一块大型离屏画布。 这种方案:

  • 创建一块巨大的离屏画布,并随着视口的添加在水平方向扩展
  • 把所有视口渲染到这块单一离屏画布上的特定坐标处
  • 再把像素数据从离屏画布拷贝到各个屏上视口

TiledRenderingEngine 的限制:

  • 画布尺寸上限:浏览器对画布尺寸设有上限(例如 Chrome 中为 16,384px)。 当所有视口的总宽度超过这个上限时,离屏画布会被静默裁切, 造成严重的视觉错乱、视口错位和空白视口。
  • 性能下降:当离屏画布接近尺寸上限时,性能会显著下降, 在高分辨率显示器或视口数量很多的布局下尤其明显。
  • 多显示器问题:由于画布尺寸限制,几乎无法用于多台高分辨率显示器。
  • 内存占用:无论实际用到多少视口,都会分配一块巨大且耗内存的离屏画布。

TiledRenderingEngine 的优点:

  • 简单:实现直白,在视口数量较少时工作良好。
  • 久经检验:五年来可靠性得到验证,对大多数基础场景表现足够好。

ContextPoolRenderingEngine(SequentialRenderingEngine)​

ContextPoolRenderingEngine(内部名为 SequentialRenderingEngine) 通过一种不同的渲染策略,从根本上解决了平铺方案的那些限制:

  • 把每个视口分别渲染到一块与该视口等大的离屏画布上
  • 再把结果拷贝到对应的屏上画布
  • 然后顺序处理下一个视口,复用同一块离屏画布
  • 利用 WebGL 上下文池做批量渲染(例如 8 个 WebGL 上下文就以 8 个为一批)

ContextPoolRenderingEngine 的优点:

  • 没有画布尺寸上限问题:浏览器的最大画布尺寸现在只作用于单个视口, 而不是所有视口的总宽度。
  • 性能提升:无论视口数量或显示分辨率如何,性能都保持稳定。
  • 更好的内存使用:避免分配巨大的离屏画布。
  • 支持多显示器:能在多台高分辨率显示器上流畅工作。
  • 稳定性更好:减少了因超大画布表面而导致的 WebGL 上下文丢失。

配置渲染引擎​

ContextPoolRenderingEngine 现在是 Cornerstone3D 的默认实现。 如果你需要使用旧的 TiledRenderingEngine,可以在初始化时配置:

import { init } from '@cornerstonejs/core';

// 使用旧的 TiledRenderingEngine
init({
rendering: {
renderingEngineMode: 'standard',
},
});

// ContextPoolRenderingEngine 是默认值,你也可以显式指定它
init({
rendering: {
renderingEngineMode: 'next',
},
});

对 ContextPoolRenderingEngine,你还可以配置批量渲染所用的 WebGL 上下文数量:

import { init } from '@cornerstonejs/core';

// 使用 ContextPoolRenderingEngine 并指定 WebGL 上下文数量
init({
rendering: {
renderingEngineMode: 'next',
webGLContextCount: 7, // 默认为 7,可根据需要调整
},
});

一般用法​

创建好 renderingEngine 之后,就可以给它分配视口用于渲染。 创建 Stack 或 Volume 视口主要有两种方式,下面分别介绍。

实例化一个 RenderingEngine​

调用 new RenderingEngine() 即可实例化一个 RenderingEngine。

import { RenderingEngine } from '@cornerstonejs/core';

const renderingEngineId = 'myEngine';
const renderingEngine = new RenderingEngine(renderingEngineId);

创建视口​

接下来可以用两种方式创建视口:setViewports,或者 enable/disable 这组 API。 两种方式都需要传入一个 ViewportInput 对象作为参数。

PublicViewportInput = {
/** DOM 中的 HTML 元素 */
element: HTMLDivElement
/** 该视口在 renderingEngine 内的唯一 id */
viewportId: string
/** 视口类型,VolumeViewport 或 StackViewport */
type: ViewportType
/** 视口的选项 */
defaultOptions: ViewportInputOptions
}

setViewports API​

setViewports 方法适合一次性创建一组视口。传入视口数组之后, renderingEngine 会把它的离屏画布尺寸调整为所提供画布的尺寸, 并触发相应的事件。

const viewportInput = [
// CT 体数据视口 —— 轴位
{
viewportId: 'ctAxial',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
},
// CT 体数据视口 —— 矢状位
{
viewportId: 'ctSagittal',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement2,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL,
},
},
// CT 轴位堆栈视口
{
viewportId: 'ctStack',
type: ViewportType.STACK,
element: htmlElement3,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
},
];

renderingEngine.setViewports(viewportInput);

Enable / Disable API​

如果需要对每个视口的启用 / 禁用做完全控制,可以使用 enableElement 和 disableElement 这组 API。启用某个元素之后, renderingEngine 会依据这个新元素调整自身的尺寸和状态。

const viewport = {
viewportId: 'ctAxial',
type: ViewportType.ORTHOGRAPHIC,
element: element1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
};

renderingEngine.enableElement(viewport);

你可以用视口的 viewportId 来禁用任意视口;禁用之后, renderingEngine 会重新调整其离屏画布的尺寸。

renderingEngine.disableElement(viewportId: string)