前言
Shadertoy是一个神奇的网站,有无数图形学大神分享的用shader写的各种让人匪夷所思的效果,而代码仅仅只有数十行至数百行。虽然很多实现无法直接应用到我们开发的实时渲染上,但分析它们的实现可以让我们理解shader以及带来很多灵感。
看了许多Shadertoy上面效果的实现,大部分模拟大自然物体(如火,云,大海等)都依赖一个秘密武器:噪声。通过对噪声的倍频,叠加,变形等处理来让渲染的物体“看起来像”真实的物体,但这背后似乎没什么理论依据,应该是靠作者丰富的经验和强大的想象力创造的。这也应了图形学中的一句话:看起来对,那就是对的。
本文分析一个个人比较喜欢的波纹效果的实现,当然分析过程纯属个人猜测,不代表原作者思路,原文地址如下:
https://www.shadertoy.com/view/Mds3W7
原效果如下:
正文
第一步,画一条直线
如下图
在像素着色器中画直线的方法很多,这里首先随便确定了直线中心的位置ycenter = 0.5,然后根据像素坐标与直线ycenter = 0.5的垂直距离来决定它的强度c1。公式是c1 = 1.0 - mix(0.0,1.0,diff*20.0)。mix函数为mix(a, b, x) = a * (1 - x) + x * b, 代入得到c1 = 1.0 - diff * 20.0。如下图:
因为c1的结果最终会被裁剪到(0--1.0)范围,当diff<0.0时,c1==1.0,当diff>0.05时,c1==0.0,当diff在0.0到0.05之间时是(0~1.0)的线性插值。这样就可以画出一条直线,直线的位置由ycenter决定,宽度是由diff决定的,如下图
当然也可以用smoothstep函数去画直线,例如:
smoothstep函数画直线会比mix函数的直线更亮,这是由它们的函数形状决定的,不赘述了。
第二步,给直线加上颜色。
我们前面col = vec4(c1, c1, c1,1.0)来构建最终的颜色,因为rgb都是相同的,得到的是白色,也就是只能展示亮度,用什么颜色完全属于个人喜好,原作者使用的是紫色,我们改为col = vec4(c0.6,0.2c2,c,1.0);得到一条紫色的直线:
第三步,添加噪声。
3.1 产生闪电效果的原理
前面已经知道,当ycenter为固定值时,画出来的是一条直线。如果ycenter不是固定,而是随机变化的,那么画出来的就不是一条直线,而是一条“抖动”的线。因为这条“抖动的线”看起来像闪电,于是我们就说这是闪电效果。
3.2 noise函数的特点
本例使用的噪声实现代码是:
float rand(vec2 n) {
return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 n) {
const vec2 d = vec2(0.0, 1.0);
vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y);
}
说实话我也不知道这个噪声实现的背后原理是什么,但可以从实现上看出三个特点:
第一,这是一个二维噪声,输入参数n为一个二维向量。
第二,这是伪随机噪声。所谓伪随机就是,只要函数nosie的输入参数n不变,那么无论调用多少次,noise函数的返回值都是不变的。
第三,这是连续噪声。所谓连续就是,如果noise函数的输入参数n变化很小,那么noise的返回值的变化也不会太大,也就是说变化曲线是相对平滑的。
3.3 倍频,叠加
简单说下倍频的概念。如下图:
每一个周期函数,它的频率f=1/T。比如图中的正弦波,最上面的sin(x)周期为2π,频率为1/2π。第二个sin(2x)频率为1/4π,第三个sin(3x)频率为1/6π。可以看到频率越高的函数,“抖动”得越厉害。
对于随机噪声函数,虽然它不一定是周期函数(也有可能是),我们同样可以改变它的频率。例如一个一维噪声函数,如下图
而我们的闪电效果,实际上是多个频率加权叠加而成的。比如上图中的第4个函数就是有前三个noise函数加权叠加而成的。
shadertoy中的效果如下
可以看到shadertoy中的倍频叠加处理在fbm函数中,其中低频分量的幅值较大,主要表现闪电整体的一个曲线波动,而高频分量的幅值较小,主要用于表现闪电边缘尖锐的毛刺。
第四步,让闪电动起来
让闪电动起来,可以通过不断改变fbm的输入值t来达到目的。如下
网友评论