一、简介
在Cesium的三维场景中,scene.globe.material 属性用于设置全球(地形)的外观材质样式
目前没有提供类似new Cesium.ColorMaterialProperty这种直接实例化的类。Cesium内置的用于Globe的材质类主要有以下几个:
ElevationContour 等高线
ElevationRamp 高程
SlopeRamp 坡向
AspectRamp 坡度
ElevationBand 多级高程
要创建Globe材质可以通过以下两种方式:
1、new Cesium.Material()的方式
let material = new Cesium.Material({
fabric: {
type: "ElevationContour",
uniforms: {
width: 1,
spacing: 20,
color: Cesium.Color.YELLOW,
},
}
});
this.viewer.scene.globe.material = material;
2、Cesium.Material.fromType(type)
let material = new Cesium.Material.fromType("ElevationContour");
material.uniforms.color=Cesium.Color.RED;
material.uniforms.width=1;
material.uniforms.spacing=20;
viewer.scene.globe.material = material;
这些材质默认将应用于整个地球的范围,在实际的项目中,我们往往只关心项目的研究区域,所以需要对其显示的范围进行限定。限制Globe材质的作用范围,有以下两个思路:
1.将范围传入Globe顶点着色器,在顶点着色器中判断当前顶点是否在范围内,然后在片元着色器中进行处理。
2.覆写Material的source,将范围传入到source中,判断当前片元是否在范围内,然后进行处理。
第一种方式需要修改Cesium的相关源码,不便于更新维护,进阶学习我们介绍第二种方式。
二、应用场景
1、局部等高线
我们在基础篇中介绍过如何覆写Cesium内置Material的source的方法,如果忘了也没关系,您可以先去回顾一下,也可以直接往下看。我们打印一下内置等高线材质的source:
打印语句console.log(Cesium.Material._materialCache._materials.ElevationContour.fabric.source)
uniform vec4 color;
uniform float spacing;
uniform float width;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
float distanceToContour = mod(materialInput.height, spacing);
#if (__VERSION__ == 300 || defined(GL_OES_standard_derivatives))
float dxc = abs(dFdx(materialInput.height));
float dyc = abs(dFdy(materialInput.height));
float dF = max(dxc, dyc) * czm_pixelRatio * width;
float alpha = (distanceToContour < dF) ? 1.0 : 0.0;
#else
// If no derivatives available (IE 10?), use pixel ratio
float alpha = (distanceToContour < (czm_pixelRatio * width)) ? 1.0 : 0.0;
#endif
vec4 outColor = czm_gammaCorrect(vec4(color.rgb, alpha * color.a));
material.diffuse = outColor.rgb;
material.alpha = outColor.a;
return material;
}
可以看到等高线材质的核心是根据materialInput.height参数进行着色计算,这是一个输入参数,代表的就是的当前的高程值。如何验证materialInput.height为高程值?我们可以将着色器修改为下面这样:
Cesium.Material._materialCache._materials.ElevationContour.fabric.source = `
uniform vec4 color;
uniform float spacing;
uniform float width;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
if(materialInput.height>250.){
material.diffuse=vec3(1.,0.,0.);
}
return material;
}
`
let material = new Cesium.Material({
fabric: {
type: "ElevationContour",
uniforms: {
width: 1,
spacing: 20,
color: Cesium.Color.YELLOW,
},
}
});
viewer.scene.globe.material = material;
着色器代码中,我们将高程值大于250的片元设置为红色。(您可以显示一个高度为250的点进行测试)
虽然验证了此处的materialInput.height就是当前片元的高程值,但是此处我们这里暂时用不到该值,我们需要的是将范围数据传递到着色器中来。如何将范围数据传递到着色器中,并判断当前片元是否在范围内,在后处理章节其实已经讲解过了。我们参考后处理章节,将范围数据传递到着色器中。(后处理-限制后处理的作用范围)假设有以下范围数据:
let positions=[
114.48165315948947, 26.88537765441287,
114.49059645844312, 26.886623091932666,
114.49046426464001, 26.894158820558456,
114.48015054916547, 26.89228862770083,
];
(注意不能直接传递矩阵到Material的着色器,解决方法是将矩阵进行拆分)将其传入到着色器的完整代码如下:
Cesium.Material._materialCache._materials.ElevationContour.fabric.source = `
uniform vec4 color;
uniform float spacing;
uniform float width;
uniform vec4 rect;
uniform vec4 m_0;
uniform vec4 m_1;
uniform vec4 m_2;
uniform vec4 m_3;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
mat4 m=mat4(m_0[0],m_0[1],m_0[2],m_0[3],m_1[0],m_1[1],m_1[2],m_1[3],m_2[0],m_2[1],m_2[2],m_2[3],m_3[0],m_3[1],m_3[2],m_3[3]);
return material;
}
`
let material = new Cesium.Material({
fabric: {
type: "ElevationContour",
uniforms: {
width: 1,
spacing: 20,
color: Cesium.Color.YELLOW,
rect: rect,
m_0: new Cesium.Cartesian4(inverse[0], inverse[1], inverse[2], inverse[3]),
m_1: new Cesium.Cartesian4(inverse[4], inverse[5], inverse[6], inverse[7]),
m_2: new Cesium.Cartesian4(inverse[8], inverse[9], inverse[10], inverse[11]),
m_3: new Cesium.Cartesian4(inverse[12] , inverse[13] , inverse[14] , inverse[15])
},
}
});
因为矩阵是4*4的一个数组,此处我们将其拆分为4个vec4类型的值。这样我们就将范围和矩阵传入到了着色器中,接下来我们要判断当前片元是否在范围内。在后处理章节中,我们通过深度还原了片元的世界坐标,然后将世界坐标转到局部坐标系,最后判断点是否在范围内。在Material的source中我们没有深度信息,又如何还原片元的世界坐标呢?我们在输入参数materialInput中可以获取到当前片元的在相机坐标系的坐标materialInput.positionToEyeEC(视空间坐标),获取到视空间坐标后通过 czm_inverseView矩阵还原为世界坐标,然后就可以像后处理章节里面那么进行判断了。
完整代码如下:
let positions = [
114.48165315948947, 26.88537765441287,
114.49059645844312, 26.886623091932666,
114.49046426464001, 26.894158820558456,
114.48015054916547, 26.89228862770083,
];
positions = Cesium.Cartesian3.fromDegreesArray([].concat.apply([], positions));
//建立局部坐标系 将所有点转到该坐标系下
let m = Cesium.Transforms.eastNorthUpToFixedFrame(positions[0]);
let inverse = Cesium.Matrix4.inverse(m, new Cesium.Matrix4());
let localPositions = [];
positions.forEach