一、简介(Appearance的分类)
Appearance是Primitive的重要组成部分,在Primitive章节中,我们已经简单地使用了Appearance来设置Primitive几何图形的外观。Cesium中有以下几种Appearance,不同的Appearance适应于不同的场景,在使用时需要注意,用错了Appearance可能导致看不见渲染结果。
1、MaterialAppearance:适用于所有Geometry类型的材质外观,支持传入material,关于material见下面第二部分
2、EllipsoidSurfaceAppearance:EllipsoidSurfaceAppearance是MaterialAppearance的一个版本,主要应用于那些于地球表面平行的几何类型,比如PolygonGeometry、RectangleGeometry,不适用于与地表垂直的几何类型,比如WallGeometry。EllipsoidSurfaceAppearance可以在计算大量顶点属性的时候节省内存。
3、PerInstanceColorAppearance:实例化几何的材质外观,我们知道Primitive通过实例化几何可以同时显示大量的几何实体,但是一个Primitive只能有一个Appearance,那当我们在使用实例化几何显示大量几何实体的时候,如果我们想要每个几何具有不同的外观如何实现呢,答案是通过PerInstanceColorAppearance这个类。
4、PolylineMaterialAppearance:专门用于线的材质外观,支持传入material。
5、PolylineColorAppearance:专门用于线顶点颜色的外观,PolylineGometry支持根据顶点颜色进行着色,需要结合该类。
二、Material材质:
Material 我们一般称其为材质,用于表达物体表面外观。在现实世界中,每一种物体对光都会产生不同的反应,这就会使不同物体的表面,看起来不一样的(比如海水和沙漠),所以世界才会如此多姿多彩。Cesium的Material类为我们封装了大量的材质类型,不同的几何应用不同的材质,可以使场景的视觉效果更加的丰富。目前共有20多种内置的材质类型。
1、每一种材质,都需要不同的输入参数,这就像炒菜一样,不同的菜需要不同的原材料。 关于Material的更多信息可以查看 Material。使用Material类,我们有以下两种方式:
a、new Cesium.Material()
let material = new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
});
传入参数为一个fabric,fabric 是对材质描述的一个json对象
b、Cesium.Material.fromType()
let material = Cesium.Material.fromType("Color", {
color: Cesium.Color.BLUE
});
2、Material 内部的的主要构成
a、uniforms:统一值,用于向着色器内传递数据
b、shaderSource: 材质的着色器代码(一般情况下,我们不会修改默认的着色器代码,但是需要知道其执行流程),默认的shaderSource如下:
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
return material;
}
我们以Color材质的着色器为例
uniform vec4 color_0;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = czm_gammaCorrect(color_0.rgb);
material.alpha = color_0.a;
return material;
}
首先会声明变量,用于接收我们通过uniforms传递的数据。注意,这些变量会自动添加一个序号,在appearance中使用时也要添加这个序号。其次定义了一个czm_getMaterial方法(这个方法会在Appearance的片元着色器中被调用),方法的参数materialInput是一个czm_materialInput类型,方法内部通过materialInput作为输入参数又调用了Cesium内置的一些方法进行计算,最后将计算结果存储到czm_material类型的变量material中作为返回值。
czm_materialInput结构体,在源码Shaders/Builtin/Structs/materialInput.js中定义,用于存储输入的纹理坐标,法线等数据。
struct czm_materialInput
{
float s;
vec2 st;
vec3 str;
mat3 tangentToEyeMatrix;
vec3 positionToEyeEC;
vec3 normalEC;
};
czm_material结构体,在源码Shaders/Builtin/Structs/material.js中定义,用于存储Material着色器计算的结果。
struct czm_material{
vec3 diffuse;
float specular;
float shininess;
vec3 normal;
vec3 emission;
float alpha;
}
三、Appearance的构成
以 MaterialAppearance 为例,因为 MaterialAppearance 适用范围最大,最具代表性,后续章节除了特别说明,否则Appearance都是表示MaterialAppearance。MaterialAppearance 构造函数如下:
重要属性介绍:
a、flat 平面着色,不考虑光照,当设置为true,着色器就不使用光照计算。图一flat = true;图二flat = false
b、material 使用Cesium内置的材质进行着色计算,在上面Material中介绍过。
c、vertexShaderSource 顶点着色器
d、fragmentShaderSource 片元着色器
四、Appearance顶点着色器
1、Appearance默认的vertexShaderSource如下:
in vec3 position3DHigh;
in vec3 position3DLow;
in vec3 normal;
in vec2 st;
in float batchId;
out vec3 v_positionEC;
out vec3 v_normalEC;
out vec2 v_st;
void main()
{
vec4 p = czm_computePosition();
v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates
v_normalEC = czm_normal * normal; // normal in eye coordinates
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
首先看到的是一个position3DHigh和一个position3DLow输入变量,这是Cesium为了保证从CPU传递到GUP的数据精度,将模型顶点拆分为position3DHeight和position3DLow两个变量。
in vec3 position3DHigh;
in vec3 position3DLow;
然后是输入的法线和纹理坐标
in vec3 normal;
in vec2 st;
最后是输出参数(输出到片元着色器)
out vec3 v_positionEC;//输出顶点 in eye
out vec3 v_normalEC;//输出法线 in eye
out vec2 v_st; //输出纹理坐标
在顶点着色器代码中,我们可以通过position3DHigh、position3DHigh这两个变量相加获取几何体的模型坐标
in vec3 positionModel=position3DHigh+position3DLow;
可以通过以下代码验证求取的模型坐标是否正确
let geometry = Cesium.BoxGeometry.fromDimensions({
dimensions: new Cesium.Cartesian3(1.0, 1.0, 1.0),
});
let position = Cesium.Cartesian3.fromDegrees(124.21936679679918, 5.85136872098397, 10);
let instance = new Cesium.GeometryInstance({
geometry: geometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(position),
});
let appearance = new Cesium.MaterialAppearance({
fragmentShaderSource: `
in vec3 v_color;
void main()
{
out_FragColor = vec4(v_color,0.5);
}
` ,
vertexShaderSource: `
in vec3 position3DHigh;
in vec3 position3DLow;
in vec3 normal;
in vec2 st;
in float batchId;
out vec3 v_positionEC;
out vec3 v_normalEC;
out vec2 v_st;
out vec3 v_color;
void main()
{
vec3 positionModel=position3DHigh + position3DLow;
if(positionModel.z>0.){
v_color=vec3(1.0,0.,0.);
}else{
v_color=vec3(0.0,1.,0.);
}
gl_Position=czm_modelViewProjection*vec4(positionModel,1.);
}`,
});
let primitive = viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: appearance,
}));
我们创建一个BoxGeometry,长宽高都设置为1,因为BoxGeometry的原点是位于其中心点,所以我们知道此时盒子的z值范围应该为-0.5到0.5,我们设置z值大于0.为红色,小于0.为绿色,此时不出意外我们看到的结果应该是一个上红下绿的几何体。运行代码查看结果,正如我们预想的一样,只不过颜色被插值了,所以看起来是一个渐变的颜色,这是正常的。当构造viewer对象时如果不设置 scene3DOnly: true那么通过position3DHigh + position3DLow获取到的不是几何体的东北上局部坐标。
五、Appearance片元着色器
1、Appearance默认的fragmentShaderSource如下:
in vec3 v_positionEC;
in vec3 v_normalEC;
in vec2 v_st;
void main()
{
vec3 positionToEyeEC = -v_positionEC;
vec3 normalEC = normalize(v_normalEC);
#ifdef FACE_FORWARD
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
#endif
czm_materialInput materialInput;
materialInput.normalEC = normalEC;
materialInput.positionToEyeEC = positionToEyeEC;
materialInput.st = v_st;
czm_material material = czm_getMaterial(materialInput);
#ifdef FLAT
out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
#else
out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
#endif
}
首先是定义变量接收从顶点着色器中传入的数据。然后定义了着色器的入口函数main,在方法中首先通过输入数据组装了一个czm_materialInput类型的结构体materialInput。然后调用czm_getMaterial方法并将materialInput作为参数计算材质的着色结果czm_getMaterial方法是在Material中定义的,我们在Material一节中介绍过)。最后根据是否使用光照计算最后的着色结果并设置给out_FragColor,out_FragColor就是最终的片元颜色了。如果不使用光照直接将物体表面的颜色作为输出结果,否则根据光照采用冯式着色法计算结果,冯式着色法是图形学中常用的一种着色计算模型,关于冯式着色法更多信息可阅读相关书籍。
Appearance最后会将Material的shaderSource和fragmentShaderSource进行合并,我们可以通过Appearance的getFragmentShaderSource方法获取完整的代码。如果我们不需要使用Material材质,那我们在fragmentShaderSource中完全不用调用czm_getMaterial方法。
#define FACE_FORWARD
uniform vec4 color_0;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = czm_gammaCorrect(color_0.rgb);
material.alpha = color_0.a;
return material;
}
in vec3 v_positionEC;
in vec3 v_normalEC;
in vec2 v_st;
void main()
{
vec3 positionToEyeEC = -v_positionEC;
vec3 normalEC = normalize(v_normalEC);
#ifdef FACE_FORWARD
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
#endif
czm_materialInput materialInput;
materialInput.normalEC = normalEC;
materialInput.positionToEyeEC = positionToEyeEC;
materialInput.st = v_st;
czm_material material = czm_getMaterial(materialInput);
#ifdef FLAT
out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
#else
out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
#endif
}
六、向Appearance着色器中传递数据
我们在项目需要传递参数,有两种方式可以向Appearance的着色器中传递数据,即使您不需要Material的着色计算结果,想要完全在fragmentShaderSource中完全自定义自己的着色代码,您也可以通过Material的uniforms传递数据。
1、第一种方式是通过Material的uniforms属性:传递一个颜色值为例我们命名为u_color, 首先创建一个基础的Appearance并将u_color设置到uniforms属性中。
let appearance = new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
uniforms: {
u_color: Cesium.Color.BLUE
}
}
})
})
在片元着色器中可以直接使用,但是必须添加一个索引值,比如现在只有一个uniforms参数,那么索引值为_0,如下:
fragmentShaderSource: `
in vec3 v_positionEC;
in vec3 v_normalEC;
in vec2 v_st;
void main()
{
out_FragColor =u_color_0;
}`
在片元着色器中不需要声明变量接收,因为最后Appearance的片着色器和Material的着色器会合并,这在上一节中我们说过。而在顶点着色器中必须声明接收数据的变量,否则会出现未定义的错误。声明用于接收的变量名称也必须是带索引的,代码如下:
vertexShaderSource: `
in vec3 position3DHigh;
in vec3 position3DLow;
in vec3 normal;
in vec2 st;
in float batchId;
out vec3 v_positionEC;
out vec3 v_normalEC;
out vec2 v_st;
uniform vec4 u_color_0;
void main()
{
vec4 color=u_color_0;
vec4 p = czm_computePosition();
v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates
v_normalEC = czm_normal * normal; // normal in eye coordinates
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
`
2、第二种方式是为Appearance对象声明一个uniforms属性,然后将参数设置到uniforms中,这种方式在Cesium相关的文档中没有说明。
let appearance = new Cesium.MaterialAppearance({
fragmentShaderSource: `
in vec3 v_positionEC;
in vec3 v_normalEC;
in vec2 v_st;
uniform vec4 u_color;
void main()
{
out_FragColor =u_color;
return;
vec3 positionToEyeEC = -v_positionEC;
vec3 normalEC = normalize(v_normalEC);
#ifdef FACE_FORWARD
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
#endif
czm_materialInput materialInput;
materialInput.normalEC = normalEC;
materialInput.positionToEyeEC = positionToEyeEC;
materialInput.st = v_st;
czm_material material = czm_getMaterial(materialInput);
#ifdef FLAT
out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
#else
out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
#endif
}`,
vertexShaderSource: `
in vec3 position3DHigh;
in vec3 position3DLow;
in vec3 normal;
in vec2 st;
in float batchId;
out vec3 v_positionEC;
out vec3 v_normalEC;
out vec2 v_st;
uniform vec4 u_color;
void main()
{
vec4 color=u_color;
vec4 p = czm_computePosition();
v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates
v_normalEC = czm_normal * normal; // normal in eye coordinates
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
`
})
appearance.uniforms = {
u_color: Cesium.Color.BLUE
}
注意:
必须在创建完Appearance对象后再设置uniforms属性,不能在构造函数中设置
顶点着色器和片元着色器中如果要使用,必须声明用于接收的参数,参数不需要添加索引值