DrawCommand 是 Cesium 渲染器的核心类,常用的接口 Entity、Primitive、Cesium3DTileSet,以及地形和影像的渲染等等,底层都是一个个 DrawCommand 完成的。在进行扩展开发、视觉特效提升、性能优化、渲染到纹理(RTT),甚至基于 Cesium 封装自己的开发框架,定义独家数据格式等等,都需要开发人员对 DrawCommand 熟练掌握。而这部分接口,Cesium 官方文档没有公开,网上的相关资料也比较少,学习起来比较困难,所以接下来我们用几期文章,由浅入深,实用为主,力求全面地介绍 DrawCommand 及相关类的运用。
上一期(《Cesium 高性能扩展之DrawCommand(一):入门》)介绍 DrawCommand 基本属性,相信大家已经能够轻松创建一个可用的DrawCommand和自定义Primitive了。那么自定义Primitive有什么用呢?还有没有必要深入学习呢?
这一期我们先不增加知识点,综合运用上一期介绍的基本属性,以及前几期介绍的Osgb解析技术(参考文章《如何在Web上直接浏览大规模OSGB格式倾斜模型(一):解析OSGB,附源码
》、《如何在Web上直接浏览大规模OSGB格式倾斜模型(二):转换OSGB》),实现在不依赖Three.js的情况下,直接创建DrawCommand来显示Osgb倾斜摄影模型。
很容易总结出关键步骤:
- 将
Osgb几何体转成Cesium.Geometry; - 将
Osgb纹理贴图转为Cesium.Texture; - 在
Shader中读取纹理贴图。
我们直接基于转换OSGB这篇文章的代码,稍微修改一下就可以实现。
- 用
Cesium.GeometryAttribute替代THREE.BufferAttribute; - 用
Cesium.Geometry替代THREE.BufferGeometry; uv属性我们按照Cesium的命名规则,改成st;- 索引数组,根据最大值来选择相应的数组类型,可以节省内存。
function parseGeometry(osgGeometry) {
//顶点属性
var positions = new Float32Array(osgGeometry.VertexArray.flat());
var uvs = new Float32Array(osgGeometry.TexCoordArray[0].flat());
//索引
var primitiveSet = osgGeometry.PrimitiveSetList[0]
var indices = primitiveSet.data;
if (primitiveSet.value == 7) {
let newIndices = [];
for (let i = 0; i < indices.length; i += 4) {
let i0 = indices[i], i1 = indices[i + 1], i2 = indices[i + 2], i3 = indices[i + 3];
newIndices.push(i0, i1, i3, i1, i2, i3);
}
indices = newIndices;
}
if (indices) {//根据最大值来选择相应的数组类型,可以节省内存
var max = Math.max.apply(null, indices)
if (max > 65535) {
indices = new Uint32Array(indices)
} else {
indices = new Uint16Array(indices)
}
}
//构造Cesium几何体
var geometry = new Cesium.Geometry({
attributes: {
position: {
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 3,
values: positions
},
st: {
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 2,
values: uvs
}
},
indices: indices
})
return geometry
}我们还是基于转换OSGB这篇文章的代码进行修改,不过我们只要提取纹理贴图。
这里需要注意,Cesium创建纹理贴图对象时会立即将贴图数据绑定到绘图上下文,所以不能必须在获取context之后,也就是渲染循环开始之后才能创建。这点和Three.js不同,THREE.Texture只封装贴图数据和参数,绑定是渲染器层负责的(Three.js的渲染器可有多个实现,比如WebGLRenderer、WebGPURenderer、CSSRenderer、SVGRenderer等等),所以可以在渲染循环开始之前创建。
function parseTexture(osgStateSet, context) {
var osgImage = osgStateSet.TextureAttributeList[0].value.StateAttribute.Image
var fileName = osgImage.Name;
const isJPEG = fileName.search(/\.jpe?g($|\?)/i) > 0
const isPNG = fileName.search(/\.png($|\?)/i) > 0
if (!isPNG && !isJPEG) return;
var mimeType = isPNG ? 'image/png' : 'image/jpeg';
var imageUri = new Blob([osgImage.Data], { type: mimeType });
imageUri = URL.createObjectURL(imageUri)
//加载图片
return Cesium.Resource.fetchImage(imageUri).then(img => {
//创建纹理贴图对象
var texture = new Cesium.Texture({
context: context,
source: img,
width: img.width,
height: img.height
});
return texture
});
}我们改动一下上一期的shader代码,增加st属性,并将st从vertexShader传到fragmentShader,当然,这对于老手来说是基本操作了。
vertexShader:
attribute vec3 position;
attribute vec2 st;
//向fragmentShader传递
varying vec2 v_st;
void main(){
//接收js传来的值
v_st=st;
gl_Position = czm_projection * czm_modelView * vec4( position , 1. );
}fragmentShader:
uniform sampler2D map;
//接收vertexShader传来的值
varying vec2 v_st;
void main(){
gl_FragColor=texture2D( map , v_st);
}可以看出,纹理贴图map需要通过uniform传递,所以uniformMap也需要调整。
//处理纹理贴图,先生成一个默认的空白贴图,等模型纹理贴图转换完成,再替换掉
var map = new Cesium.Texture({
context: context,
width: 2, height: 2
})
parseTexture(this.osgGeometry.StateSet, context).then(texture => {
//释放默认纹理贴图
map.destroy();
//使用转换好的模型纹理贴图
map = texture;
})
var uniformMap = {
map() {
return map
}
}构造自定义Primitive时需要传入osg::Geometry类型节点对象,在创建DrawCommand的时调用转换几何体和纹理贴图的函数。
function parseGeometry(osgGeometry) {
var positions = new Float32Array(osgGeometry.VertexArray.flat());
var uvs = new Float32Array(osgGeometry.TexCoordArray[0].flat());
var primitiveSet = osgGeometry.PrimitiveSetList[0]
var indices = primitiveSet.data;
if (primitiveSet.value == 7) {
let newIndices = [];
for (let i = 0; i < indices.length; i += 4) {
let i0 = indices[i], i1 = indices[i + 1], i2 = indices[i + 2], i3 = indices[i + 3];
newIndices.push(i0, i1, i3, i1, i2, i3);
}
indices = newIndices;
}
if (indices) {
var max = Math.max.apply(null, indices)
if (max > 65535) {
indices = new Uint32Array(indices)
} else {
indices = new Uint16Array(indices)
}
}
var geometry = new Cesium.Geometry({
attributes: {
position: {
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 3,
values: positions
},
st: {
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 2,
values: uvs
}
},
indices: indices
})
return geometry
}
function parseTexture(osgStateSet, context) {
var osgImage = osgStateSet.TextureAttributeList[0].value.StateAttribute.Image
var fileName = osgImage.Name;
const isJPEG = fileName.search(/\.jpe?g($|\?)/i) > 0
const isPNG = fileName.search(/\.png($|\?)/i) > 0
if (!isPNG && !isJPEG) return;
var mimeType = isPNG ? 'image/png' : 'image/jpeg';
var imageUri = new Blob([osgImage.Data], { type: mimeType });
imageUri = URL.createObjectURL(imageUri)
return Cesium.Resource.fetchImage(imageUri).then(img => {
var texture = new Cesium.Texture({
context: context,
source: img,
width: img.width,
height: img.height
});
return texture
});
}
class MyPrimitive {
constructor(modelMatrix, osgGeometry) {
this.modelMatrix = modelMatrix || Cesium.Matrix4.IDENTITY.clone()
this.drawCommand = null;
this.osgGeometry = osgGeometry
}
createCommand(context) {
var modelMatrix = this.modelMatrix;
//转换几何体
var geometry = parseGeometry(this.osgGeometry)
var attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry)
var va = Cesium.VertexArray.fromGeometry({
context: context,
geometry: geometry,
attributeLocations: attributeLocations
});
var vs = `
attribute vec3 position;
attribute vec2 st;
varying vec2 v_st;
void main(){
v_st=st;
gl_Position = czm_projection * czm_modelView * vec4( position , 1. );
}
`;
var fs = `
uniform sampler2D map;
varying vec2 v_st;
void main(){
gl_FragColor=texture2D( map , v_st);
}
`;
var shaderProgram = Cesium.ShaderProgram.fromCache({
context: context,
vertexShaderSource: vs,
fragmentShaderSource: fs,
attributeLocations: attributeLocations
})
//处理纹理贴图,先生成一个默认的空白贴图,等模型纹理贴图转换完成,再替换掉
var map = new Cesium.Texture({
context: context,
width: 2, height: 2
})
parseTexture(this.osgGeometry.StateSet, context).then(texture => {
//释放默认纹理贴图
map.destroy();
//使用转换好的模型纹理贴图
map = texture;
})
var uniformMap = {
map() {
return map
}
}
var renderState = Cesium.RenderState.fromCache({
cull: {
enabled: true,
face:Cesium.CullFace.BACK
},
depthTest: {
enabled: true
}
})
this.drawCommand = new Cesium.DrawCommand({
modelMatrix: modelMatrix,
vertexArray: va,
shaderProgram: shaderProgram,
uniformMap: uniformMap,
renderState: renderState,
pass: Cesium.Pass.OPAQUE
})
}
/**
*
* @param {Cesium.FrameState} frameState
*/
update(frameState) {
if (!this.drawCommand) {
this.createCommand(frameState.context)
}
frameState.commandList.push(this.drawCommand)
}
}解析,遍历节点,处理类型为osg::Geometry的节点,创建一个自定义Primitive来展示该节点。
import * as osg from 'osg-serializer-browser';
var origin = Cesium.Cartesian3.fromDegrees(106, 26, 0)
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin)
var url = '../../assets/sampleData/models/tile_0_0_0_tex_children.osgb';
Cesium.Resource.fetchArrayBuffer(url).then(buf => {
var osgObj = osg.readBuffer(buf)
osg.TraverseNodes(osgObj, node => {
if (node.Type == 'osg::Geometry') {
var primitive = new MyPrimitive(modelMatrix, node);
viewer.scene.primitives.add(primitive)
}
})
})至此,大家对于文章开头的两个问题应该都有自己的答案了,我们下期继续!

