一、简介
shadertoy是一个基于webgl的分享Shader的开放平台,用户可以在上面根据既定规则分享自己编写的shader。在Cesium中要想实现一些酷炫的效果,唯一的一条路就是写shader,而shader的编写应该算是图形学中的难度天花板了。好在有很多shader大佬分享了自己编写的glsl效果,比如shadertoy网站上就是这些大牛们的作品,我们可以借鉴。
二、shadertoy着色器基本结构
shadertoy上的shader是纯2d绘图,没有几何顶点这些概念,它的绘图方式和canvas绘图方式很像,它将整个canvas作为绘图的画布,所以输入参数fragCoord的x值范围是(0,画布的宽度),fragCoord的y值范围是(0,画布的高度),画布的宽高在定义的输入参数中是iResolution,所以fragCoord.x范围就是(0,iResolution.x),fragCoord.y范围就是(0,iResolution.y)。
如图所示,shadertoy上的shader示例最基本的着色器结构主要包括两个部分:
a、输入参数的定义
b、着色器的入口函数
1、输入参数,通过uniform来定义外部的输入值。
uniform vec3 iResolution; // 视口分辨率,即画布的宽高
uniform float iTime; // shader 的运行时间 秒
uniform float iTimeDelta; // 渲染时间 秒
uniform float iFrameRate; // shader 帧率
uniform int iFrame; // shader 帧率
uniform float iChannelTime[4]; // 频道运行时间 不管
uniform vec3 iChannelResolution[4]; // 频道分辨率 不管
uniform vec4 iMouse; // 鼠标坐标
uniform samplerXX iChannel0..3; // 输入的纹理 比如我们从一张图片上采用颜色
uniform vec4 iDate; // 日期 年月日 不管
uniform float iSampleRate; // 声音采样 不管
2、入口函数mainImage
void mainImage(out vec4 fragColor, in vec2 fragCoord )
{
fragColor = vec4(1.);
}
方法的第一个参数fragColor,是一个vec4类型的变量,表示最后输出的颜色值。
方法的第二个参数fragCoord,是一个vec2类型的变量,表示输入的像素坐标。
三、在cesium中如何使用
shadertoy上的着色器是在一个canvas画布上进行工作的,要移植到Cesium中,我们需要找一个载体来替代canvas。我们知道,Cesium绘制几何图形可以通过Entity和Primitive两种方式,那么只有Primitive+Appearance比较合适了,关于Primitive的使用及介绍,可以观看前面的章节。要将着色器移植到Cesium中,我们先来重点看看shadertoy上的shader着色器需要用到的参数。以下面这个例子讲解https://www.shadertoy.com/view/XdlSDs
glsl代码:
void mainImage(out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
float tau = 3.1415926535*2.0;
float a = atan(p.x,p.y);
float r = length(p)*0.75;
vec2 uv = vec2(a/tau,r);
//get the color
float xCol = (uv.x - (iTime / 3.0)) * 3.0;
xCol = mod(xCol, 3.0);
vec3 horColour = vec3(0.25, 0.25, 0.25);
if (xCol < 1.0) {
horColour.r += 1.0 - xCol;
horColour.g += xCol;
}
else if (xCol < 2.0) {
xCol -= 1.0;
horColour.g += 1.0 - xCol;
horColour.b += xCol;
}
else {
xCol -= 2.0;
horColour.b += 1.0 - xCol;
horColour.r += xCol;
}
// draw color beam
uv = (2.0 * uv) - 1.0;
float beamWidth = (0.7+0.5*cos(uv.x*10.0*tau*0.15*clamp(floor(5.0 + 10.0*cos(iTime)), 0.0, 10.0))) * abs(1.0 / (30.0 * uv.y));
vec3 horBeam = vec3(beamWidth);
fragColor = vec4((( horBeam) * horColour), 1.0);
}
a、首先是fragCoord,前面介绍过,fragCoord表示当前处理的像素坐标,是一个vec2类型,fragCoord.x范围为(0,画布宽度),fragCoord.y范围为(0,画布高度)。
b、其次是iResolution,iResolution代表的是当前画布的宽高,即绘图区域的尺寸,所以fragCoord.x范围就是(0,iResolution.x),fragCoord.y范围就是(0,iResolution.y)。
c、然后我们还在代码中看到有个iTime,该参数代表当前运行的时间,一般用来实现动画,因为您会发现大多数shader的效果都是动态的。
d、最后是输出结果fragColor,代表最后计算的颜色输出值,在Cesium中为out_FragColor。
接下来介绍在Cesium如何获取对应的参数:
a、fragCoord在Cesium有个gl_FragCoord与之对应,这是一个WebGL内置的变量。
b、iResolution在Cesium有个czm_viewport与之对应,不过使用时采用zw分量即 czm_viewport.zw
c、iTime在Cesium中没有对应的变量,我们可以通过变量的方式传递一个参数,然后在渲染时不断修改该值,不过这种方式略显麻烦,在Cesium中我们可以用float iTime=czm_frameNumber/100.来模拟,czm_frameNumber代表当前帧,是一个自增长的数值,所以可以用来模拟时间不断地增长。
接下来我们实操一下,在Cesium中实现该shader的效果:
1、首先我们创建一个Primitive并添加到sene中
let xMin = 115.894604, yMin = 39.516896, xMax = 117.431959, yMax = 40.630521;
let rect = new Cesium.Rectangle(Cesium.Math.toRadians(xMin), Cesium.Math.toRadians(yMin), Cesium.Math.toRadians(xMax), Cesium.Math.toRadians(yMax));
const rectangle = new Cesium.RectangleGeometry({
rectangle: rect,
height: 10000.0,
});
const geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: geometry
}),
}));
2、此时会发现什么也看不见,这是因为没有设置外观,我们创建一个默认的外观
let appearance = new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.RED
}
}
}),
})
primitive.appearance = appearance;
3、接下来我们为外观添加片元着色器,并将shadertoy上的shader赋值给外观的片元着色器属性。
shadertoy上glsl代码:
fragmentShaderSource: `
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
float tau = 3.1415926535*2.0;
float a = atan(p.x,p.y);
float r = length(p)*0.75;
vec2 uv = vec2(a/tau,r);
//get the color
float xCol = (uv.x - (iTime / 3.0)) * 3.0;
xCol = mod(xCol, 3.0);
vec3 horColour = vec3(0.25, 0.25, 0.25);
if (xCol < 1.0) {
horColour.r += 1.0 - xCol;
horColour.g += xCol;
}
else if (xCol < 2.0) {
xCol -= 1.0;
horColour.g += 1.0 - xCol;
horColour.b += xCol;
}
else {
xCol -= 2.0;
horColour.b += 1.0 - xCol;
horColour