Cesium 中的Shader解析1
一、简介
Cesium中在创建点、线、面、球等Geometry的时候,可以动态指定材质。材质用于设置物体的外观。通常修改材质的片元着色器来改变外观。
在Cesium源码中有许多内置材质,比如最常用的Color和Image等。
材质的创建有两种方式,快捷方式和原始方式。下面的例子为多边形设置颜色材质,两种写法具有相同效果。
polygon.material = Material.fromType('Image');
polygon.material.uniforms.image = 'image.png';
或者:
polygon.material = new Cesium.Material({ fabric : { type : 'Image', uniforms : { image : 'image.png' } } });
fabric是Cesium自己定义的一种写材质的一种语法,就是个json,不具体说这个,这个可以去查文档。
二、GLSL
最灵活的一中定义材质的方式是写GLSL,为Fabric指定source属性,在cesium源码中会读取source并加工合并到最终的着色器中进行图形绘制。
struct czm_materialInput { float s; vec2 st; vec3 str; mat3 tangentToEyeMatrix; vec3 positionToEyeEC; vec3 normalEC; }; struct czm_material { vec3 diffuse; float specular; float shininess; vec3 normal; vec3 emission; float alpha; }; czm_material czm_getMaterial(czm_materialInput materialInput);
最简单的定义如下,这是直接返回默认的材质属性,这也是我们写shader的切入点,可以传入uniforms变量,可以从纹理中获取原色,也结合时间变化来动态修改材质等等。
在source中我们可以使用webgl的内置函数,比如step,fract等等,也可直接使用cesium内置函数、变量、结构体等,比如czm_xxxx等。
{ source : 'czm_material czm_getMaterial(czm_materialInput materialInput) { return czm_getDefaultMaterial(materialInput); }' }
三、一个面
下面的代码是大家都熟悉的内容,就是定义个primitive,在三维球上添加一个矩形rectangle。
function createPrimitives(scene) { rectangle = scene.primitives.add( new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: new Cesium.RectangleGeometry({rectangle: Cesium.Rectangle.fromDegrees(116, 39, 117, 39.7),
// EllipsoidSurfaceAppearance.VERTEX_FORMAT = Cesium.VertexFormat.POSITION_AND_ST; vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT, }) }), appearance: new Cesium.EllipsoidSurfaceAppearance({ aboveGround: false }) }) ) }
尝试修改其材质,为这个矩形贴上一个图片纹理。
其中一句关键代码如下,texture2D函数是webgl内置函数,传入两个参数,一个是图片,一个是纹理坐标。
图片是在uniforms中定义的变量【image: './sky.JPG'】。纹理坐标是二维的平面坐标,s轴和t轴垂直大小在0到1之间,(0,0)在左下角。
material.diffuse=texture2D(image,materialInput.st).rgb;
这句代码就是获取图片纹素颜色,然后把颜色值给材质的漫反射分量,漫反射代表了材质的颜色,类型是vec3。除了漫反射,还有镜面反射、自发光、高光系数等等,
这些都属于光照系统,用于最终确定材质颜色,具体可以了解一下phong模型,是一种局部光照模型。
rectangle.appearance.material = new Cesium.Material({ fabric: { uniforms: { image: './sky.JPG', alpha: 1.0, }, source: /* glsl */ ` czm_material czm_getMaterial(czm_materialInput materialInput){ czm_material material=czm_getDefaultMaterial(materialInput); material.diffuse=texture2D(image,materialInput.st).rgb; return material; } ` } })
四、再修改面
把中间的和外面的挖掉,就成了下面的样子。关键代码
float dis=distance(center,materialInput.st);
distance是webgl内置函数,用于计算距离。知道这一点,代码就很容易理解了。
rectangle.appearance.material = new Cesium.Material({ fabric: { uniforms: { image: './sky.JPG', alpha: 1.0, }, source: /* glsl */ ` czm_material czm_getMaterial(czm_materialInput materialInput){ czm_material material=czm_getDefaultMaterial(materialInput); material.diffuse=texture2D(image,materialInput.st).rgb; material.alpha=alpha; vec2 center=vec2(0.5,0.5); float dis=distance(center,materialInput.st); if(dis>0.25){ material.diffuse=vec3(1.0,0.0,0.0); } if(dis<0.1){ material.diffuse=vec3(1.0); } return material; } ` } })
五、动态墙
没有什么新知识,主要是给uniforms传入了时间t,用于生成动态效果,这不是最优雅的一种方式,后续再说。
wall = new Cesium.Primitive({ geometryInstances: [ greenWallInstance, ], appearance: new Cesium.MaterialAppearance({ material: Cesium.Material.fromType('Color'), faceForward: true }) })
wall.appearance.material = new Cesium.Material({ fabric: { uniforms: { color: 'vec3(1.0,1.0,0.0)', alpha: 1.0, image: './abc.png', image2: './sky.JPG', t: 0.0 }, source: /* glsl */ ` czm_material czm_getMaterial(czm_materialInput materialInput){ czm_material material=czm_getDefaultMaterial(materialInput); material.diffuse=vec3(1.0,1.0,0.0); // material.alpha=fract(1.0-materialInput.st.s+t); material.alpha=mod(1.0-materialInput.st.s+t,1.0); return material; } ` } })setInterval(() => { wall.appearance.material.uniforms.t += 0.1; if (wall.appearance.material.uniforms.t > 1.0) { wall.appearance.material.uniforms.t = 0.0; } }, 200);