如何在浏览器里展示三维体积数据:CesiumJS 体素渲染实战
【免费下载链接】cesiumAn open-source JavaScript library for world-class 3D globes and maps :earth_americas:项目地址: https://gitcode.com/GitHub_Trending/ce/cesium
如果你需要在浏览器里渲染医学 CT、地质勘探网格这类三维体积数据,CesiumJS 体素渲染提供了一整套现成的 API。本文从最小可运行 Demo 讲起,串起光线步进参数调优、真实数据接入与拾取交互,所有示例都指向仓库里的真实文件。
表面模型与体素网格:为什么旧方法不够用
传统 3D 模型只描述物体的"皮"——几何以顶点和面存储,内部是空的:
体积数据则相反,它像一张三维表格,空间里每个格子都带一个数值——密度、温度或组织概率。信息藏在网格内部,而不是表面。所以 WebGL 体积可视化普遍采用光线步进:GPU 从相机向网格发射视线,沿路采样并累加颜色与透明度,累积不透明度足够高或视线撞上边界时就提前终止。最终像素就是沿途数值的合成结果。
这段着色器你不需要自己写,VoxelPrimitive已经封装好了。
三步跑通最小体素渲染 Demo
第一步:选形状与数据源。体素形状由VoxelShapeType决定,共三种:
| 形状 | 典型场景 | 采样特性 |
|---|---|---|
| BOX | 规则网格数据 | 全量利用空间,计算最简单 |
| ELLIPSOID | 类球体对象 | 球面边界,格外空间自动剔除 |
| CYLINDER | 管道等柱状结构 | 轴向对称 |
数据源分两类:演示可用程序化数据(实现requestData,返回VoxelContent.fromMetadataArray);真实数据则接 3D Tiles 体素扩展的瓦片集。
第二步:创建 primitive。这是整个体素渲染实现的核心,代码只有几行:
const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl( "VoxelBox3DTiles/tileset.json" ); const primitive = viewer.scene.primitives.add( new Cesium.VoxelPrimitive({ provider }) ); primitive.stepSize = 0.7; // 光线步进步长,越小采样越密 primitive.nearestSampling = true; // 最近邻采样,避免数值涂抹第三步:把相机飞过去。调viewer.camera.flyToBoundingSphere(primitive.boundingSphere, { duration: 0.0 }),镜头直接对准包围球。
体素示例源码里可以切换三种形状,3D Tiles 体素示例数据则附带了每种形状对应的现成瓦片集。
光线步进的 3 个关键参数怎么调 🎛️
Demo 跑起来后,画质与性能问题大多集中在这三个参数上:
stepSize(采样步长):1.0 表示每个格子恰好采一次,是最高精度;示例用 0.7 换取帧率。画面出现阶梯状锯齿就调小,掉帧就调大。nearestSampling(最近邻采样):分类标签这类离散数据建议开启,否则相邻格子的插值会造出数据里不存在的"中间值"。customShader(自定义着色器):传输函数——数值如何映射为颜色与透明度——就写在片元着色器里。你可以用fsInput.metadata.color读属性,也可以顺手加一层简单光照。
⚠️ 别一上来就把所有参数拉到"最高画质"。先用
stepSize: 0.7渲染,开着scene.debugShowFramesPerSecond观察帧率再逐步调整。
用 3D Tiles 体素扩展接入真实数据
tileset.json 的职责是告诉引擎"这块体积数据该怎么切"。仓库里真实的描述文件长这样:
"extensions": { "3DTILES_content_voxels": { "dimensions": [2, 4, 3], // x/y/z 轴上的体素网格数量 "class": "voxel" // 对应 schema 中的元数据类 } }还有几个字段你会经常遇到:
extensionsRequired:声明必须支持 3DTILES_content_voxels 扩展,引擎不认会直接报错,避免静默降级。implicitTiling:OCTREE 细分加 subtrees,引擎只请求视锥内可见的瓦片——这是体素数据性能优化的核心,网格再大也不会一次性塞满显存。schema与statistics:声明属性类型(如 VEC4 颜色)及 min/max 统计,为样式系统和空区域跳过提供依据。
如果你的数据存在明显阈值(CT 里的肿瘤边界、地质模型中的矿体),可以用自定义着色器写一个二值传输函数:阈值之上不透明,阈值之下透明。这样就能得到等值面提取的视觉效果,不必自己跑移动立方体算法生成网格,也不用担心网格面数爆炸。
完整字段对照可打开 Voxel 瓦片集描述文件,着色器写法参考 自定义着色器指南。
拾取与性能调优:点中格子并读出数值 🔍
三维体积数据可视化"看得见"之后,下一个问题通常是"我点的这一格是什么"。pickVoxel直接告诉你视线命中的是哪个格子:
const cell = viewer.scene.pickVoxel(mousePosition); if (cell) { const rgba = cell.getProperty("color"); // 读取体素属性 cell.primitive.customShader.setUniform( // 高亮该格子 "u_selectedTile", cell.tileIndex); }返回对象里带tileIndex、sampleIndex和格子的orientedBoundingBox,配合属性值就能做测量、切片标注这类交互——体素拾取示例就是这样高亮选中格的。
性能上记住三件事:靠隐式瓦片做空间切分而不是手动切数据;利用statistics让引擎跳过空区域;用 性能测试指南 里的方法观察相机视角下瓦片加载与帧率的实际表现:
下一步行动
- 运行 体素渲染示例,切换 Box / Ellipsoid / Cylinder 观察不同形状
- 对照 Voxel 瓦片集描述文件 逐字段读懂 3D Tiles 体素扩展
- 在 体素拾取示例 里练习读取
sampleIndex与属性值 - 想写自定义传输函数时,翻一遍 自定义着色器指南 的 GLSL 片段
【免费下载链接】cesiumAn open-source JavaScript library for world-class 3D globes and maps :earth_americas:项目地址: https://gitcode.com/GitHub_Trending/ce/cesium
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考