Cesium深入浅出之shadertoy篇
引言
要学好 Cesium,先学好 shader,但如果你还不知道 shadertoy,那你可能要错失一个亿。
大家好,懒人回归!我看了一下,距离上一次更新居然有一年半之久!好吧,像我这么懒的估计运营个啥都不会长久,所以第N次下定决心,勤奋起来,加油!
初入 shadertoy,看着上面那眼花缭乱的效果,着实让我震惊,没想到看着不起眼的着色器语言居然能那么强大,做个材质是最小儿科的,做个酷炫的视频都不在话下,更夸张的是居然有人能用它做个游戏。当时就萌生一个想法,这么牛的效果咱能不能抄~啊不~是借鉴到 Cesium 当中呢?
预期效果
在 Cesium 中,使用着色器语言的主要有后期处理、材质样式等地方。着色器主要分为顶点着色器和片元着色器,其中后期处理主要是写片元着色器,材质样式可以写顶点着色器和片元着色器,而材质要写材质着色器,其实也是一种片元着色器,只是主函数名称有所差异。上一篇文章我们刚好讲了 Cesium 自定义材质的入门,那么今天我们就使用材质的方式来移植 shadertoy 的代码吧。
实现原理
Cesium 的材质类是 Cesium.Material,上一篇文章已经详细讲过了,这里不赘述了,有需要的请移步《Cesium深入浅出之自定义材质》。另外也可以参考官网沙盒中的 Material with Custom GLSL。
其实移植的过程并不复杂,因为毕竟 glsl 是相通的,核心代码基本复用,无非是将两个平台的不同标准修改一下即可。下面我们以一个具体示例来做一下试试吧。
具体实现
先从 shadertoy 上找一个案例,我觉得这个流动炫彩的还不错,简单几行代码就出来很棒的效果。试想一下把它做成一个立体的光幕或者是电子围墙,逼格一定很高吧。
案例代码主要分为两个部分,Common 和 Image,顾名思义 Common 中会写一些通用的函数之类的,而 Image 是主要的实现。这里我们把原始代码也贴一下。
着色器输入
uniform vec3 iResolution; // viewport resolution (in pixels)
uniform float iTime; // shader playback time (in seconds)
uniform float iTimeDelta; // render time (in seconds)
uniform float iFrameRate; // shader frame rate
uniform int iFrame; // shader playback frame
uniform float iChannelTime[4]; // channel playback time (in seconds)
uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
uniform samplerXX iChannel0..3; // input channel. XX = 2D/Cube
uniform vec4 iDate; // (year, month, day, time in seconds)
Common
vec2 stanh(vec2 a) {return tanh(clamp(a, -40., 40.));
}
Image
// -13 thanks to Nguyen2007 ⚡void mainImage( out vec4 o, vec2 u )
{vec2 v = iResolution.xy;u = .2*(u+u-v)/v.y; vec4 z = o = vec4(1,2,3,0);for (float a = .5, t = iTime, i; ++i < 19.; o += (1. + cos(z+t)) / length((1.+i*dot(v,v)) * sin(1.5*u/(.5-dot(u,u)) - 9.*u.yx + t))) v = cos(++t - 7.*u*pow(a += .03, i)) - 5.*u, // use stanh here if shader has black artifacts// vvvvu += tanh(40. * dot(u *= mat2(cos(i + .02*t - vec4(0,11,33,0))),u)* cos(1e2*u.yx + t)) / 2e2+ .2 * a * u+ cos(4./exp(dot(o,o)/1e2) + t) / 3e2;o = 25.6 / (min(o, 13.) + 164. / o) - dot(u, u) / 250.;
}
可以看到核心的代码实现非常简单,反倒是着色器输入那块看着挺复杂,其实这些参数大部分是关于 shadertoy 场景设置的,无须太过关心,我们只做必要的转换。我们先来看看这些参数主要是做什么的吧。
- iResolusion:视口分辨率,以像素为单位。不过这个参数是 vec3 类型的,长和宽,还有高?本案例的分辨率是 640 x 360。
- iTime:着色器播放的时间,以秒为单位。
- iTimeDelta:渲染时间,以秒为单位。
- iFrameRate:着色器帧率。每秒播放多少帧,也就是 fps。
- iFrame:着色器播放帧。当前播放到第几帧,这个可以用作动态材质。
- iChannelTime:通道播放时长,以秒为单位。
- iChannelResolusion:通道分辨率,以像素为单位。
- iMouse:鼠标像素坐标。xy 标示鼠标坐标,zw 标示鼠标按键状态。上面提到过的着色器做游戏就要用到它,毕竟只有能交互的才叫游戏,不能交互的只能叫视频。
- iChanel:输入通道。
- iDate:日期(年、月、日、时间)。
通常,我们都要用到 iResolusion 这个参数,因为图像计算是依赖于分辨率的,还有就是 iFrame,是通过帧让图像动起来。OK,现在开启改造之旅!
主体结构
的主函数是 mainImage,通过上一篇文章我们知道 Cesium 的材质主函数是 czm_getMaterial。
czm_material czm_getMaterial(czm_materialInput materialInput)
{czm_material material = czm_getDefaultMaterial(materialInput);// Write code here.return material;
}
内部实现
这个案例中 shadertoy 的参数用到了 iResolusion 和 iTime,因此要对其进行处理。输出参数 o 是 vec4 类型的,其中 xyz 对应 material.diffuse,w 对应 material.alpha。
uniform float time;czm_material czm_getMaterial(czm_materialInput materialInput) {czm_material material = czm_getDefaultMaterial(materialInput);vec2 st = materialInput.st;// iTime 通过 uniform 传值进来。float iTime = time;// iResolution 主要是做分辨率修正的,我们可以忽略它。vec2 v; // vec2 v = iResolution.xy;vec2 u = st * .2; // vec2 u = .2 * (st + st - v) / v.y; vec4 o;vec4 z = o = vec4(1, 2, 3, 0);for (float a = .5, t = iTime, i; ++i < 19.; o += (1. + cos(z + t))/ length((1. + i * dot(v, v)) * sin(1.5 * u / (.5 - dot(u, u)) - 9. * u.yx + t))){v = cos(++t - 7.*u*pow(a += .03, i)) - 5.*u, u += tanh(40. * dot(u *= mat2(cos(i + .02*t - vec4(0,11,33,0))), u) * cos(1e2*u.yx + t)) / 2e2+ .2 * a * u+ cos(4./exp(dot(o,o)/1e2) + t) / 3e2;}o = 25.6 / (min(o, 13.) + 164. / o) - dot(u, u) / 250.;material.diffuse = o.xyz;material.alpha = o.w * 2.;return material;
}
运行测试,效果如下:
怎么说呢,效果差异就挺大的吧,毕竟是不同的平台吧,不能要求那么高,如果想要达到和 shadertoy 平台一样的效果估计还得做些优化吧,不过那不在本次探讨范围之内。
小结
本篇暂且写到这,但并不是到此为止,随便找的例子,效果并不是太好,后续可能会找些更好的例子更新上来。最后,欢迎到 854943530 部落来。