前言
Shadertoy是一个神奇的网站,有无数图形学大神分享的用shader写的各种让人匪夷所思的效果,而代码仅仅只有数十行至数百行。虽然很多实现无法直接应用到我们开发的实时渲染上,但分析它们的实现可以让我们理解shader以及带来很多灵感。
看了许多Shadertoy上面效果的实现,大部分模拟大自然物体(如火,云,大海等)都依赖一个秘密武器:噪声。通过对噪声的倍频,叠加,变形等处理来让渲染的物体“看起来像”真实的物体,但这背后似乎没什么理论依据,应该是靠作者丰富的经验和强大的想象力创造的。这也应了图形学中的一句话:看起来对,那就是对的。
本文分析一个个人比较喜欢的波纹效果的实现,当然分析过程纯属个人猜测,不代表原作者思路,原文地址如下:
https://www.shadertoy.com/view/ldfyzl
原效果如下:

正文
第1步:调整uv坐标

第一步什么也不渲染,只调整了uv坐标,其中resolution是决定后面的波纹的大小,我们先设为2。因渲染屏幕的分辨率是640x360,这样计算后得出的u的范围是0~3.555(640 / 360 * 2),v的范围是0~2.0。p0是uv取整的结果。如上图,我们相当于把渲染屏幕划分成了一个个正方形格子,后面会在这些格子之间画波纹。
第2步:画四分之一圆环。

画圆环的方法用了两个smoothstep函数相乘实现,看代码会有点抽象,可以工具来看下函数的形状:

第3步:增加圆环数量
可以用一个正弦函数和上面的smoothstep相乘来增加圆环的数量,如下图:

对应的函数图形为:

可以通过调节sin函数的系数(sin(31*x)中的31)来改变圆环的数量,目前可以看到两个“波纹了”。
第4步,为圆环添加具有方向性的反光效果
我们知道真实的水中波纹是反光的,也正是反光才让我们能更容易注意到波纹。为了模拟反光效果,我们首先要让圆环具有方向性,因为真实的反光在每个方向的强度是不一样的。上面的步骤使用一个浮点数p1代表圆环的所有方向的强度,现在我们用一个二维向量circles来替换它,circles方向和v相同,并且定义一个二维向量direction来指定某个方向,当v和direction之间角度越小,强度越大,这样就可以模拟具有方向性的反光了。如下图:

第5步,画出完整的圆环
上面我们只画了四分之一的圆环,通过循环方式把另外的四分之三补齐。

我们看到我们画出的圆环并不是连续的,图中的蓝色虚线两侧的圆环并没有接在一起。原因是,第一,p1 = sin(31.d) * smoothstep(0., -0.3, d) * smoothstep(-0.6, -0.3, d)等式中,p1其实在不同坐标下是正负交替的(因为有sin函数的存在),所以其实画面中我们看到的黑色并不一定是0,而是负值,只不过到最后被clamp掉了。第二,由于dot(circles, direction)等式的作用,brightness会以circles向量与direction向量等于90度的地方为交界,做一次正负号的跳变。sin(31.d) * smoothstep(0., -0.3, d) * smoothstep(-0.6, -0.3, d)的函数图如下:

第6步,增加波纹的细节
tip:这一步纯粹个人瞎猜
经过上面的步骤,我们有了一个渐变,并在某方向反光的“波纹”,但它和真实的波纹效果还相差甚远。下面为通过几个方法来增加它的真实感。(至于作者如何想到的我也不知道....)
6.1 将direction由二维改为三维
如下图

改为三维后,圆环的明暗渐变细节更加丰富,让圆环有了立体的效果。
6.2 用p2-p1替代p1.
这个无法用语言描述,直接看代码吧...

对比上一步,感觉画面差不多,只是修改后视觉上好像更自然一些。
p2 - p1的函数图形如下:

图中紫色的曲线代表的就是p2 - p1,而青色的曲线其实是两条曲线(因为太接近了看起来像一条线),分别是p1和p2。
6.3 调整亮度

用来pow指数函数来校正亮度,由pow曲线特性,可以简单理解为增强了对比度,即暗的更暗,亮的更亮。
第7步,加入纹理背景。
通过上一步的一系列动作,可以看到我们的波纹已经非常逼真了。现加入一张纹理还模拟水下的场景。

intensity为波纹和纹理混合的强度。
第8步,加入随机性。
现在我们让画面动起来。通过随机的放置波纹的位置,随机亮度,随机混合强度等,可以得到一幅生动的雨滴波纹画面。

最终得到的动图效果:

之所以和开头的波纹大小不一样,是因为我把resolution设置为2,而原文是10。
网友评论