最近在研究cesium.js,第一次接触这个引擎,以前用Babylon.js和Three.js比较多,对于这个新的引擎感觉还是比较新鲜,也扩充一下自己的知识面。学习了一段时间感觉还是挺有意思的。于是乎就想实现一个简单的自定义shader,看一下Cesium.js是如何实现的。接触后发现Cesium.js的Primitive的加载方式挺有意思的,这种加载方式更接近与底层的OpenGL和WebGL的方式,庆幸的是OpenGL和WebGL这两个都有过学习,看起这个还是比较友好的,不瞎扯了,下面简单说一下实现过程。
首先对代码的整体框架作以介绍。
let viewer = new Cesium.Viewer('cesiumContainer',{});
//平面的顶点数据
let points=[
110.2, 20.0,
112.2, 20.0,
110.2, 22.0,
112.2, 22.0
];
let positions = Cesium.Cartesian3.fromDegreesArray(points);
//顶点对应的纹理坐标数据
let sts=[
0,0,
0,1,
1,0,
1,1
];
//顶点卷绕的方式
let positionIndex=[
0,1,2,
1,2,3
];
var plane = new primitivePlaneShaer({
viewer:viewer,
positions:positions,
sts:sts,
positionIndex:positionIndex
});
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3.fromDegrees(110.2, 20.0,500000)
});
上面给出的是整体的框架,创建三维场景、设置相机位置、给出顶点数据、顶点索引数据、纹理坐标数据传入primitivePlaneShader中构建几何体,这个方法下面进行详细的说明。
function _(options) {
viewer = options.viewer;
vertexShader = v_shader();
fragShader = f_shader();
sts = new Uint8Array(options.sts);//纹理数据
positionIndex = new Uint16Array(options.positionIndex);//顶点索引数据
let tempPosition =[];
for (var i = 0; i < options.positions.length; i++) {
tempPosition.push(options.positions[i].x);
tempPosition.push(options.positions[i].y);
tempPosition.push(options.positions[i].z);
}
positions = new Float64Array(tempPosition);//顶点数据
let geometry=createGeometry(positions,sts,positionIndex);//几何体
let appearance = createAppearance(vertexShader , fragShader);//外观
//primitive方式加载
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({//渲染的几何体
geometry: geometry
}),
appearance: appearance,//外观
asynchronous: false
}));
primitivePlaneShader中的_方法主要是将顶点数据、顶点索引数据、纹理坐标数据存入到对应的Array中,获取顶点着色器、片元着色器,创建几何体、创建外观,给到Primitive对象中,加载到场景。下面介绍创建几何体、创建外观的方法。
function createAppearance(vertexShader, fragShader) {
return new Cesium.Appearance({
material: new Cesium.Material({
fabric: {
uniforms: {
speed:0.1
},
source:getMS()
}
}),
translucent: false,//显示不为半透明
renderState: {
blending: Cesium.BlendingState.PRE_MULTIPLIED_ALPHA_BLEND,//使用Alpha混合功能启用混合
depthTest: { enabled: true },//深度检测
depthMask: true,//启用深度检测
},
fragmentShaderSource: fragShader,//片段着色器
vertexShaderSource: vertexShader//顶点着色器
});
}
//构建几何体
function createGeometry(positions,sts,positionIndex) {
return new Cesium.Geometry({
attributes:{//几何顶点属性
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.DOUBLE,//数据类型
componentsPerAttribute:3,//定义几个为一组
values:positions//坐标值
}),
st: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,//数据类型
componentsPerAttribute:2,//定义几个为一组
values: sts//坐标值
})
},
indices:positionIndex,//顶点索引
primitiveType: Cesium.PrimitiveType.TRIANGLES,//图元类型
boundingSphere:Cesium.BoundingSphere.fromVertices(positions)//包围球
});
}
上面的创建外观和几何体比较的固定,注释给得比较的清楚,需要注意的根据不同的需求选择自己的基本图元:点、线、三角形。下面给出顶点、片段着色器。
function v_shader() {
return'attribute vec3 position3DHigh;\n' +
'attribute vec3 position3DLow;\n'+
'attribute float batchId;\n'+
'attribute vec2 st;\n'+
'varying vec2 v_st;\n'+
'void main() {\n'+
' vec4 position = czm_modelViewProjectionRelativeToEye *czm_computePosition();\n'+
' v_st = st;\n'+
' gl_Position = position;\n'+
'}';
}
function f_shader() {
return'varying vec2 v_st;\n'+
'//uniform float speed;\n'+
'void main() {\n'+
' vec2 position = -1.0 + 2.0 *v_st;\n'+
' float speed = getSpeed();\n'+
' float time= czm_frameNumber *speed;\n'+
' float r = abs( cos( position.x * position.y + time / 5.0 ));\n'+
' float g = abs( sin( position.x * position.y + time / 4.0 ) );\n'+
' float b = abs( cos( position.x * position.y + time / 3.0 ) );\n'+
' gl_FragColor = vec4( r, g, b, 1.0 );\n'+
'}\n';
}
function getMS() {
return 'uniform float speed;\n'+
'float getSpeed(){\n'+
' return speed;\n'+
'}';
}
着色器的代码比较的简单就是将平面每一个像素点的颜色进行动态的计算,实现一个动态效果,就是再传uniform值的时候与Three.js不同,Three.js是直接在着色器中收这个值,而Cesium是通过在Source中收在调用到着色器中来获取这个值。因为刚开始研究可能还有更好的方式来获取这个值。效果如下。
刚开始学习Cesium.js还有太多的不足,有什么错误小白虚心接受,希望和大家一起学习成长。