Cesium自定义模型和着色器

自定义模型

要让WebGL渲染出一个正确的模型,通常需要提供以下信息:

  • 顶点: 定义模型的各个顶点
  • 顶点颜色: 定义每个顶点的颜色(顶点颜色会在传入片元着色器之前进行插值,如果片元颜色使用的是从顶点着色器中传入的顶点颜色,则最终会呈现一种渐变效果)
  • 法线: 定义每个顶点的法线方向
  • 顶点索引: 定义顶点的连接顺序(哪些点连成一个三角形)
  • UV: 定义每个顶点的颜色应该从贴图上的哪个位置获取(UV坐标也会在传入片元着色器之前进行插值,从而在片元着色器中通过UV坐标查询贴图,获取对应的颜色)
  • 贴图: 模型的纹理

创建一个Geometry

在Cesium中定义自己的模型最重要的是实例化一个Geometry对象。

定义一个Geometry所需的各种属性通常采用读取模型或者公式计算的方式获得,这里为了方便演示,采用直接定义的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 定义顶点
const positions = [0, 0, 250000, 50000, 0, 0, -250000, 50000, 0, 0, 0, 250000, -250000, 50000, 0, -25000, -50000, 0, 0, 0, 250000, -25000, -50000, 0, 50000, 0, 0, 0, 0, 250000, 50000, 0, 0, -250000, 50000, 0 ];

// 定义索引
const indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ];

// 定义UV
const uvs = [0, 1, 0, 0, 0.3333333333333333, 0, 0.3333333333333333, 1, 0.3333333333333333, 0, 0.6666666666666666, 0, 0.6666666666666666, 1, 0.6666666666666666, 0,1, 0, 1, 1, 1, 0, 1.3333333333333333, 0 ];

// 定义法向量
const normals = [ 0.16431020200252533, 0.9858611822128296, 0.03286203742027283, 0.16431020200252533, 0.9858611822128296, 0.03286203742027283, 0.16431020200252533, 0.9858611822128296, 0.03286203742027283, -0.3963700234889984, -0.8918325304985046, 0.218003511428833, -0.3963700234889984, -0.8918325304985046, 0.218003511428833, -0.3963700234889984, -0.8918325304985046, 0.218003511428833, 0.5513178706169128, -0.8269767761230469, 0.11026357114315033, 0.5513178706169128, -0.8269767761230469, 0.11026357114315033, 0.5513178706169128, -0.8269767761230469, 0.11026357114315033, 0.16431020200252533, 0.9858611822128296, 0.03286203742027283, 0.16431020200252533, 0.9858611822128296, 0.03286203742027283, 0.16431020200252533, 0.9858611822128296, 0.03286203742027283 ];

// 创建自定义Geometry
const geometry = new Cesium.Geometry({
attributes: {
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.DOUBLE, // 顶点必须使用DOUBLE类型
componentsPerAttribute: 3, // 每连续的三个值组成一个顶点
values: new Float64Array(positions),
}),
st: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 2, // 每连续的两个值组成一个UV坐标
values: new Float32Array(uvs),
}),
normal: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 3, // 每连续的三个值组成一个向量
values: new Float32Array(normals),
}),
},
// 顶点索引
indices: new Uint16Array(indices),
// 图元类型
primitiveType: Cesium.PrimitiveType.TRIANGLES,
// 包围盒,用于视椎体剔除
boundingSphere: Cesium.BoundingSphere.fromVertices(new Float64Array(positions)),
});

创建一个GeometryInstance

GeometryInstance可用于存放并统一管理多个Geometry,如:

  • 乘以同一个矩阵: 设置modelMatrix
  • 赋予相同的attributes属性: 如赋予相同颜色 attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)}

而它最重要的作用是合并图形减少DrawCall,提高渲染效率。

1
const geometryInstances = new Cesium.GeometryInstance({ geometry: geometry });

创建Appearance

Appearance定义了primitive应该如何进行着色,后面会提到如何自定义着色器

这里简单创建一个MaterialAppearance

1
const appearance = new Cesium.MaterialAppearance({ material: Cesium.Material.fromType("Stripe")});

创建Primitive

1
2
3
4
5
6
7
8
9
10
11
// 由于模型的顶点全部是在标准位置下定义的
// 因此如果需要将其放在正确的位置,就需要计算一个模型矩阵
const position = Cesium.Cartesian3.fromDegrees(113, 23, 0);
const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position);

const primitive = new Cesium.Primitive({
geometryInstances: geometryInstances,
appearance: appearance,
asynchronous: false, // 创建异步加载的primitive需要指定_workerName,这里关闭异步加载
modelMatrix: modelMatrix,
});

最终创建出来的效果如下:

自定义模型

定义着色器

前面提到Appearance定义了primitive应该如何进行着色。

Cesium的Appearance只是一个抽象类,可以通过继承它实现自己的Appearance类,也可以使用Cesium定义好的其他Appearance类。

它们大致都可以通过以下两种方式自定义着色器。

这里以MaterialAppearance为例。

两种方式中定义顶点着色器的方式都是通过传入vertexShaderSource字段,区别在于自定义的片元着色器代码写在什么地方。

第一种方式:直接在fragmentShaderSource中写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const myMaterialAppearance = new Cesium.MaterialAppearance({
translucent: false,
fragmentShaderSource: `
varying vec2 v_st;

void main()
{
vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
if(abs(v_st.y - 0.5) < 0.1){
color.a = 0.0;
}
gl_FragColor = color;
}

`,
vertexShaderSource: `
attribute vec3 position3DHigh;
attribute vec3 position3DLow;
attribute vec2 st;
attribute float batchId;

varying vec2 v_st;

void main()
{
vec4 p = czm_computePosition();
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
`,
});

第二种方式:创建Material对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

// 创建Material对象可以给着色器传入uniform变量
const material = new Cesium.Material({
fabric: {
type: 'MyMaterial',
uniforms: {
u_color: new Cesium.Cartesian3(0, 1, 0),
},
source: `
czm_material czm_getMaterial(czm_materialInput materialInput){
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = u_color.rgb;
material.alpha = 1.0;
vec2 st = materialInput.st;
if(abs(st.y - 0.5) < 0.1){
material.alpha = 0.0;
}
return material;
}
`
}
});

const myMaterialAppearance = new Cesium.MaterialAppearance({
translucent: false,
material: material,
vertexShaderSource: `
attribute vec3 position3DHigh;
attribute vec3 position3DLow;
attribute vec3 normal;
attribute vec2 st;
attribute float batchId;

varying vec3 v_positionEC;
varying vec3 v_normalEC;
varying vec2 v_st;

void main()
{
vec4 p = czm_computePosition();
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;
v_normalEC = czm_normal * normal;
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
`,
});

两段着色器的渲染效果都一致:挖空中间一段。

自定义着色器效果

动起来

前面创建的着色器都是静止的,如果我们想做出流动的效果,建议采用上面的第二种自定义方式。

将会改变的参数作为uniform传入着色器,并在手动为uniform进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const material = new Cesium.Material({
fabric: {
type: 'MyMaterial',
uniforms: {
u_time: ((performance.now()) / 1000) % 1,
u_color: new Cesium.Cartesian3(0, 1, 0),
},
source: `
czm_material czm_getMaterial(czm_materialInput materialInput){
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = u_color.rgb;
material.alpha = 1.0;
vec2 st = materialInput.st;
if(abs(st.y - u_time) < 0.1){
material.alpha = 0.0;
}
return material;
}
`
}
});

const myMaterialAppearance = new Cesium.MaterialAppearance({
translucent: false,
material: material,
vertexShaderSource: `
attribute vec3 position3DHigh;
attribute vec3 position3DLow;
attribute vec3 normal;
attribute vec2 st;
attribute float batchId;

varying vec3 v_positionEC;
varying vec3 v_normalEC;
varying vec2 v_st;

void main()
{
vec4 p = czm_computePosition();
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;
v_normalEC = czm_normal * normal;
v_st = st;
gl_Position = czm_modelViewProjectionRelativeToEye * p;
}
`,
});

// 每一帧后更新 u_time uniform
viewer.scene.postRender.addEventListener(() => {
material.uniforms.u_time = ((performance.now()) / 1000) % 1;
});

流动效果

自定义流动着色器


Cesium自定义模型和着色器
https://www.liaomz.top/2022/02/25/cesium-zi-ding-yi-mo-xing-he-zhao-se-qi/
作者
发布于
2022年2月25日
许可协议