CesiumJS PrimitiveAPI 高级着色入门-底层知识
CesiumJS 是一个非常强大的开源 JavaScript 库,用于在 web 浏览器中创建 3D 地球仪和 2D 地图。它提供了丰富的API,允许开发者构建复杂的地理空间可视化应用。在 CesiumJS 中,PrimitiveAPI 允许你以较低级别的方式渲染几何体,如点、线和多边形等,这为自定义着色和渲染效果提供了可能。
3D模型在线预览提供多种低代码平台3D模型在线预览解决方案,实现了将多种3D模型格式无缝集成到低代码业务表单中。这意味着用户可以在不离开低代码平台的情况下,直接查看和操作3D模型,极大地提升了数据可视化的效果和用户交互体验。
底层知识
渲染状态对象
注意到一个东西:appearance.renderState
,在创建外观对象时可以传入一个对象字面量:
也可以不传递,默认会生成这样一个对象:
这个对象会记录在外观对象上,伴随着 Primitive 的更新过程,还会增增减减、修改状态值,在 Primitive 的 createRenderStates
函数中,用这个对象的即时值创建或取得缓存的 RenderState
实例,等待着在 createCommands
函数中传递给 DrawCommand
。
RenderState
的状态值和 WebGL 最终渲染有关,在 Context
模块的 beginDraw
函数、applyRenderState
函数中,就有大量使用渲染状态的代码(还要往里进去两三层),举例:
这两个函数就是在修改 WebGL 全局状态的值,值来自 RenderState
实例的 depthMask
和 stencilMask
字段。
CesiumJS 漫长的一帧的更新过程中,有两个状态对象可以关注一下,一个是挂载在 Scene
上的 帧状态 对象(FrameState 实例),另一个就是身处于各个实际三维对象上的 渲染状态 对象(RenderState 实例)。前者记录一些整装待发的资源,例如 DrawCommand
清单等,后者则为三维对象标记在实际渲染时要更改 WebGL 全局状态的状态值。两大状态的链接桥梁是 DrawCommand
。
似 Primitive 对象与创建似 Primitive 对象
这一节介绍的内容将有助于理解 CesiumJS 单帧更新的核心思路。别看 CesiumJS 拥有这么多加载数据、模型的 API 类,实际上是可以根据它们在场景结构中的层级,做个简单的分类:
- Entity 与 DataSource,高层级的数据 API,是高级的人类友好的数据格式加载封装,还能与时间关联
- Globe 与 ImageryLayer,负责地球本身的渲染,含皮肤(影像 Provider)和肌肉(地形 Provider)
- Primitive 家族,含本篇介绍的
Primitive
,以及glTF
、3DTiles
等数据
Entity 和 DataSource 实际上底层也是在调用 Primitive 家族,只不过这两个属于 Viewer;中间的 Globe 与 ImageryLayer 和最后的 Primitive 家族,属于 Scene 容器。
既然这篇是介绍的 Primitive,那么就重点介绍 Primitive 家族。
你一定注意过可以向 scene.primitives
这个 PrimitiveCollection
中添加好几种对象:Model
、Cesium3DTileset
、PrimitiveCollection
(是的,可以嵌套添加)、PointPrimitive
、GroundPrimitive
、ClassificationPrimitive
以及本篇介绍的 Primitive
均可以,在 1.101 版本的更新中还添加了一个体素:VoxelPrimitive
(仍在测试)。
我将这类 Primitive 家族类,称为 PrimitiveLike
类,即“似 Primitive”。
这些似 Primitive 有一个共同点,才能添加到 PrimitiveCollection 中,伴随着场景的单帧更新过程进入 WebGL 渲染。它们的共同点:
- 有
update
实例方法 - 有
destroy
实例方法
在 update
方法中,它接受 FrameState
对象传入,然后经过自己的渲染逻辑,创建出一系列的指令对象(主要是 DrawCommand
),并送入帧状态对象的指令数组中,待更新完毕最终进入 WebGL 的渲染。
所以知道这些有什么用呢?
Cesium 团队是一个求稳的团队,2012 年还在内测的时候,ES5 标准才落地没多久,哪怕现在的代码也仍然是使用函数来创建类,而不是用 ES6 的 Class(尽管现在切换过去已经没什么技术难点了)。ES6 实现类继承是很简单的,但是在那个时候就比较困难了。像这种似 Primitive 的情况,ES6 来写实际上就是有一个共同的父类罢了,如果是 TypeScript,那更是可以抽象为更轻量的 interface
:
这构成了编写自定义 Primitive 的基础,CesiumJS 团队和 CesiumLab 核心成员 vtxf 均有一些古早的资料,告诉你如何编写自定义的 Primitive 类。
Primitive 在 Scene 中的大致图示
这样就能大致看到在什么时候更新的 PrimitiveCollection 了。
文末小结
整篇文章分为4个章节,主要介绍这几个内容:
- 一般性的
Primitive API
用法,包括:Geometry API
的自定义几何、参数内置几何;Appearance + Material API
所表达的 CesiumJS Fabric 材质规范 - 提出似 Primitive 的概念,为之后自定义 Primitive 学习挡在 WebGL 原生接口之前的最底层 API 打下基础
- 简单思考了 CesiumJS 的着色器设计和应用
CesiumJS PrimitiveAPI 高级着色入门-索引
CesiumJS PrimitiveAPI 高级着色入门-参数化几何
CesiumJS PrimitiveAPI 高级着色入门-外观材质