最近看到国内一位cesium大牛的博客,讲材质material(材质)的,于是对照源码看了下,发现很有研究的意义,首先,源码的项目搭建用的是dojo,所有的样式业务绘制前端都是良好分离,关键是不存在多份拷贝,方便调试,相较于傻瓜式且过度模块化封装的vue而言,这点好很多,不过这些不是本文的重点,本文涉及到的水特效其实只是Cesium原生提供的22种材质中的一种而已,cesium除了定义了22中常用材质,每种都可以自定义渲染属性,且能互相组合使用,还允许用户自定义任意材质,确实很方便。而本文提到的火特效跟材质没啥关系,只是因为水和火总是容易让人联系到一起,既然研究了水渲染,顺便理解下火渲染也无妨。
Cesium官方给出的22中材质及其uniform属性说明官网链接
以下是从官方Material中删除其余不在本文中关心的材质示例代码后的最简代码:
var worldRectangle;
function applyWaterMaterial(primitive, scene) {
//Sandcastle.declare(applyWaterMaterial); // For highlighting in Sandcastle.
primitive.appearance.material = new Cesium.Material({
fabric: {
type: 'Water',
uniforms: {
// baseWaterColor:Cesium.Color.RED,
// blendColor:Cesium.Color.DARKBLUE,
specularMap: '../images/earthspec1k.jpg',
normalMap: Cesium.buildModuleUrl('Assets/Textures/waterNormals.jpg'),
frequency: 10000.0,
animationSpeed: 0.01,
amplitude: 1
}
}
});
}
function toggleWorldRectangleVisibility() {
worldRectangle.show = true;
}
function createPrimitives(scene) {
worldRectangle = scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromDegrees(-180.0, -90.0, 180.0, 90.0),
vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
})
}),
appearance: new Cesium.EllipsoidSurfaceAppearance({
aboveGround: false
}),
show: false
}));
}
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
createPrimitives(scene);
toggleWorldRectangleVisibility();
applyWaterMaterial(worldRectangle, scene);
cesium水材质的uniform中定义包括如下属性:
以上两个颜色如果不设置,默认为:
如果设置为:
baseWaterColor:Cesium.Color.RED,
blendColor:Cesium.Color.DARKBLUE,
则显示效果如下:
在实际应用中,我们可能多数情况下只是为了展示一个指定polygon区域的水面特效,对吧,所以在理解了每个属性的含义之后,我们只要在此基础上稍加修改即可满足需要。
于是我们这里将primitive的坐标修改成自定义的:
worldRectangle = scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy : new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArray([
120.0, 40.0,
120.0, 35.0,
125.0, 30.0,
120.0, 30.0,
118.0, 40.0
])
)
})
}),
appearance: new Cesium.EllipsoidSurfaceAppearance({
aboveGround: false
}),
show: false
}));
然后将specularMap设置去除,或者设置成一张全白色的图即可。效果如下所示:
放大就是水面特效:
当然我们还可以自定义其他属性
火焰特效和喷泉、喷火等等特效都是粒子特效,cesium对这块有很好的支持,其实就是使用各种属性,只要看看api文档说明即可,其实我个人更愿意花时间多看看材质方面,毕竟内容比较多使用比较复杂,但是无奈这种特效很多客户都喜欢,老是要做,所以只好单独看看熟悉下了。
无论是喷火也好、喷水也好、喷烟也好,随便你要喷什么东西,反正实现的原理都一样,使用粒子效果。
阅读Cesium官方示例,甚至发现了一些意外的惊喜。不过下面还是言归正传,分析官网粒子效果的实现代码。
首先,粒子效果是在一定时间范围内生成的,所以需要开启cesium的时间动画,即需要设置:viewer.clock.shouldAnimate = true;
//Sandcastle_Begin
var viewer = new Cesium.Viewer('cesiumContainer');
//Set the random number seed for consistent results.
//Cesium.Math.setRandomNumberSeed(3);
//Set bounds of our simulation time
var start = Cesium.JulianDate.fromDate(new Date(2015, 2, 25, 16));
var stop = Cesium.JulianDate.addSeconds(start, 120, new Cesium.JulianDate());
//Make sure viewer is at the desired time.
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; //Loop at the end
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;
//Set timeline to simulation bounds
viewer.timeline.zoomTo(start, stop);
示例第一步是初始化一个view,然后设置randomNumberSeed,这个主要是用于生成随机数的,本示例中注释掉也没关系,因为没有用到nextRandomNumber函数:
接下来是设置动画开始和结束时间start和end,这里应该是下面还需要用到所以单独定义了相应变量,并且赋值clock,其中clockRange设置为LOOP_STOP代表这段动画完成以后时间序列重头开始重新来过,一直循环下去,除了这个枚举值以外,ClockRange还可以定义UNBOUNDED(没有边界一直往后),CLAMPED(到达end后停止),cesium默认clock是UNBOUNDED,所以这里需要单独设置,multiplier属性用来定义每次加多少时间,最后将时间范围定位到开始和结束范围。
示例第二步是进行数据绑定,设定监听订阅分发,类似vue中的watch和数据绑定,由于Cesium原生用的不是vue,所以这里使用的轻量级的数据绑定Knockout,knockout已经集成到Cesium中,引用了cesium自动就能使用,如果不用cesium也可以在其他应用中直接使用knockout,直接应用原生html原生,不依赖任何插件,以前没遇到过所以单独实验了一把,实践证明,在vue中也绝地可以使用,只是要用原生的input,而不是用其他类似element这样的插件。
var viewModel = {
emissionRate : 5.0,
gravity : 0.0,
minimumParticleLife : 1.2,
maximumParticleLife : 1.2,
minimumSpeed : 1.0,
maximumSpeed : 4.0,
startScale : 1.0,
endScale : 5.0,
particleSize : 25.0
};
Cesium.knockout.track(viewModel);
var toolbar = document.getElementById('toolbar');
Cesium.knockout.applyBindings(viewModel, toolbar);
数据绑定部分代码如下:
Cesium.knockout.getObservable(viewModel, 'emissionRate').subscribe(
function(newValue) {
particleSystem.emissionRate = parseFloat(newValue);
}
);
Cesium.knockout.getObservable(viewModel, 'particleSize').subscribe(
function(newValue) {
var particleSize = parseFloat(newValue);
particleSystem.minimumImageSize.x = particleSize;
particleSystem.minimumImageSize.y = particleSize;
particleSystem.maximumImageSize.x = particleSize;
particleSystem.maximumImageSize.y = particleSize;
}
);
Cesium.knockout.getObservable(viewModel, 'minimumParticleLife').subscribe(
function(newValue) {
particleSystem.minimumParticleLife = parseFloat(newValue);
}
);
Cesium.knockout.getObservable(viewModel, 'maximumParticleLife').subscribe(
function(newValue) {
particleSystem.maximumParticleLife = parseFloat(newValue);
}
);
Cesium.knockout.getObservable(viewModel, 'minimumSpeed').subscribe(
function(newValue) {
particleSystem.minimumSpeed = parseFloat(newValue);
}
);
Cesium.knockout.getObservable(viewModel, 'maximumSpeed').subscribe(
function(newValue) {
particleSystem.maximumSpeed = parseFloat(newValue);
}
);
Cesium.knockout.getObservable(viewModel, 'startScale').subscribe(
function(newValue) {
particleSystem.startScale = parseFloat(newValue);
}
);
Cesium.knockout.getObservable(viewModel, 'endScale').subscribe(
function(newValue) {
particleSystem.endScale = parseFloat(newValue);
}
);
对应html绑定数据方法: