绘制3D云图(模型加载和着色)
<p>加载3D体数据并将其渲染到指定的 renderWindow 中。以下是详细的步骤解释:</p>
<h3>数据解析:</h3>
<pre><code>const renderData = getArrayBufferData(souces, data);
const binary = renderData;</code></pre>
<p>这部分从数据源中获取二进制数组(binary),准备传递给 VTK 读取器。</p>
<h3>VTK 对象初始化:</h3>
<pre><code>const vtkVolumeMapper = window.vtk.Rendering.Core.vtkVolumeMapper;
const vtkVolume = window.vtk.Rendering.Core.vtkVolume;
const vtkXMLImageDataReader = window.vtk.IO.XML.vtkXMLImageDataReader;
const vtkBoundingBox = window.vtk.Common.DataModel.vtkBoundingBox;
const vtkColorTransferFunction = window.vtk.Rendering.Core.vtkColorTransferFunction;
const vtkPiecewiseFunction = window.vtk.Common.DataModel.vtkPiecewiseFunction;</code></pre>
<p>引入 VTK 库中的各种对象,用于处理和渲染数据。</p>
<h3>设置交互器更新率:</h3>
<pre><code>renderWindow.getInteractor().setDesiredUpdateRate(30);</code></pre>
<p>设置渲染窗口的交互器更新率为每秒 30 帧,以提高渲染性能。</p>
<h3>数据读取:</h3>
<pre><code>const vtiReader = vtkXMLImageDataReader.newInstance();
vtiReader.parseAsArrayBuffer(binary);
const source = vtiReader.getOutputData(0);</code></pre>
<p>使用 vtkXMLImageDataReader 读取 VTI 格式的体数据,得到 source 数据对象。</p>
<h3>映射器和演员创建:</h3>
<pre><code>const mapper = vtkVolumeMapper.newInstance();
const actor = vtkVolume.newInstance();</code></pre>
<p>创建 mapper 和 actor 对象。mapper 将数据映射到 3D 空间,actor 用于显示该数据。</p>
<h3>设置数据范围和颜色条:</h3>
<pre><code>const dataArray = source.getPointData().getScalars() || source.getPointData().getArrays()[0];
const dataRange = dataArray.getRange();
global.RenderVtk.dataRange = dataRange;
const dataExtent = source.getExtent();
global.RenderVtk.dataExtent = dataExtent;</code></pre>
<p>获取数据的范围(dataRange)和数据扩展(dataExtent),并存储到 global.RenderVtk 对象中。</p>
<h3>配置颜色传输函数:</h3>
<pre><code>const lookupTable = vtkColorTransferFunction.newInstance();
const piecewiseFunction = vtkPiecewiseFunction.newInstance();</code></pre>
<p>创建颜色传输函数(lookupTable)和分段函数(piecewiseFunction)。</p>
<pre><code>const rangeBase = (dataRange[1] - dataRange[0]) / (config.color.length - 1);
const colorMin = config.showRangeStart * (config.scaleBase || 1);
const colorMax = config.showRangeEnd * (config.scaleBase || 1);</code></pre>
<p>计算颜色范围基础、颜色最小值和最大值。</p>
<h3>配置颜色条:</h3>
<pre><code>for (let index = 0; index &lt; config.color.length; index++) {
let color = config.color[index];
const value = dataRange[0] + rangeBase * index;
if (value &gt; colorMax) { color = config.color[config.color.length - 1]; }
if (value &lt; colorMin) { color = config.color[0]; }
const { r, g, b } = d3.rgb(color);
colorsBar.push(color);
lookupTable.addRGBPoint(dataRange[0] + rangeBase * index, r / 255, g / 255, b / 255);
}</code></pre>
<p>为颜色传输函数添加颜色点,根据数据范围和配置中的颜色数组进行设置。</p>
<h3>映射器和演员设置:</h3>
<pre><code>actor.setMapper(mapper);
mapper.setInputData(source);
const sampleDistance = 0.7 * Math.sqrt(source.getSpacing().map(v =&gt; v * v).reduce((a, b) =&gt; a + b, 0));
mapper.setSampleDistance(sampleDistance);</code></pre>
<p>设置 mapper 输入数据并调整采样距离以提高渲染效果。</p>
<h3>数据组件设置:</h3>
<pre><code>let ComponentsNum = source.getPointData().getScalars().getNumberOfComponents();
if (ComponentsNum &gt; 4) { ComponentsNum = 4; }
source.getPointData().getScalars().setNumberOfComponents(ComponentsNum);</code></pre>
<p>限制数据组件的数量,最大为 4。</p>
<h3>配置演员属性:</h3>
<pre><code>actor.getProperty().setRGBTransferFunction(0, lookupTable);
for (let index = 1; index &lt; ComponentsNum; index++) {
actor.getProperty().setScalarOpacity(index, piecewiseFunction);
}
actor.getProperty().setInterpolationTypeToLinear();
actor.getProperty().setScalarOpacityUnitDistance(
0,
vtkBoundingBox.getDiagonalLength(source.getBounds()) / Math.max(...source.getDimensions())
);
actor.getProperty().setUseGradientOpacity(0, true);
actor.getProperty().setGradientOpacityMinimumOpacity(0, config.colorOpacity / 100);
actor.getProperty().setGradientOpacityMaximumOpacity(0, config.colorOpacity / 100);
actor.getProperty().setAmbient(0.2);
actor.getProperty().setDiffuse(0.7);
actor.getProperty().setSpecular(0.3);
actor.getProperty().setSpecularPower(8.0);</code></pre>
<p>配置演员的颜色传输函数、透明度、插值类型、渐变透明度和光照属性,以确保渲染效果符合要求。</p>
<p>完整代码</p>
<pre><code class="language-javascript">/**
*
* @param {*} data
* @param {*} renderWindow
* @param {*} container
*/
async vtkLoad3D3C(data, renderWindow, renderer, camera, options = {}) {
const renderData = getArrayBufferData(souces, data);
const binary = renderData;
const vtkVolumeMapper = window.vtk.Rendering.Core.vtkVolumeMapper;
const vtkVolume = window.vtk.Rendering.Core.vtkVolume;
const vtkXMLImageDataReader = window.vtk.IO.XML.vtkXMLImageDataReader;
const vtkBoundingBox = window.vtk.Common.DataModel.vtkBoundingBox;
const vtkColorTransferFunction = window.vtk.Rendering.Core.vtkColorTransferFunction;
const vtkPiecewiseFunction = window.vtk.Common.DataModel.vtkPiecewiseFunction;
renderWindow.getInteractor().setDesiredUpdateRate(30);
const vtiReader = vtkXMLImageDataReader.newInstance();
vtiReader.parseAsArrayBuffer(binary);
const source = vtiReader.getOutputData(0);
const mapper = vtkVolumeMapper.newInstance();
const actor = vtkVolume.newInstance();
const dataArray =
source.getPointData().getScalars() || source.getPointData().getArrays()[0];
const dataRange = dataArray.getRange();
global.RenderVtk.dataRange = dataRange;
const dataExtent = source.getExtent();
const colorsBar = [];
global.RenderVtk.dataExtent = dataExtent;
const lookupTable = vtkColorTransferFunction.newInstance();
const piecewiseFunction = vtkPiecewiseFunction.newInstance();
const rangeBase = (dataRange[1] - dataRange[0]) / (config.color.length - 1);
const colorMin = config.showRangeStart * (config.scaleBase || 1);
const colorMax = config.showRangeEnd * (config.scaleBase || 1);
for (let index = 0; index &lt; config.color.length; index++) {
let color = config.color[index];
const value = dataRange[0] + rangeBase * index;
if (value &gt; colorMax) {
color = config.color[config.color.length - 1];
}
if (value &lt; colorMin) {
color = config.color[0];
}
const { r, g, b } = d3.rgb(color);
colorsBar.push(color);
lookupTable.addRGBPoint(dataRange[0] + rangeBase * index, r / 255, g / 255, b / 255);
}
actor.setMapper(mapper);
mapper.setInputData(source);
const sampleDistance =
0.7 *
Math.sqrt(
source
.getSpacing()
.map(v =&gt; v * v)
.reduce((a, b) =&gt; a + b, 0)
);
mapper.setSampleDistance(sampleDistance);
let ComponentsNum = source.getPointData().getScalars().getNumberOfComponents();
if (ComponentsNum &gt; 4) {
ComponentsNum = 4;
}
source.getPointData().getScalars().setNumberOfComponents(ComponentsNum);
global.RenderVtk.source = source;
actor.getProperty().setRGBTransferFunction(0, lookupTable);
for (let index = 1; index &lt; ComponentsNum; index++) {
actor.getProperty().setScalarOpacity(index, piecewiseFunction);
}
actor.getProperty().setInterpolationTypeToLinear();
actor
.getProperty()
.setScalarOpacityUnitDistance(
0,
vtkBoundingBox.getDiagonalLength(source.getBounds()) /
Math.max(...source.getDimensions())
);
actor.getProperty().setUseGradientOpacity(0, true);
actor.getProperty().setGradientOpacityMinimumOpacity(0, config.colorOpacity / 100);
actor.getProperty().setGradientOpacityMaximumOpacity(0, config.colorOpacity / 100);
actor.getProperty().setAmbient(0.2);
actor.getProperty().setDiffuse(0.7);
actor.getProperty().setSpecular(0.3);
actor.getProperty().setSpecularPower(8.0);
}</code></pre>