接上一篇的湍流与上上篇的Tonmap技术,作者利用多层 Wave生成湍流这个思路,叠加一系列的绘图技巧完成了 火焰特效。 当然美丽的效果是被发现的,而不是创造的。 , 首先来看看效果。
当然忧郁的我还是喜欢蓝色的火焰 :)
本篇中详细分析了火焰实现的所有技巧。
空间挤压拉伸
如果读者朋友们使用过 blender或者其他的 3d软件, 有对 geometry做挤压和拉伸的操作。 同样我们可以通过函数对坐标系(也就是空间)做同样的操作。 需要强调的这里挤压/拉伸 是针对值。 如果坐一些反向操作,例如 1.0 / value。 拉伸就变成了挤压 y方向拉升函数为
float ystretch = 1.0 - 0.5 / (1.0+p.x*p.x);
左边的图为函数的图像, 右边是经过 y轴拉伸之后。 可以看到右图白色更加宽了。 记住我们话白色的依据是他的 y值 < 0.1。 因为y方向乘上了一个小于1.0的值,所以<0.1的范围在原坐标系看起来变多了,也就是被拉伸了。 (我的表达力有限...)
x方向压缩函数为,
float xstretch = 2.0 - 1.5*smoothstep(-2.0,2.0,p.y);
左边的图为函数的图像,可以看到在y为负值的时候 stretch放大了,y为正值 streach变小。 也就是会形成。 stretch放大意味在 canvas坐标系中是一个压缩的状态,也就会形成右图中的下缩上拉的状态。 就像是一个火焰 flame的时候 由小变大一样。
最后做一个朝上方向的运动,就像火焰往上飘一样。实现起来很简单 坐标系整体往下移动,
float scroll = SCROLL*iTime;
p.y -= scroll;
空间湍流扭曲
这块的理解可以参考上一篇文章 Pseudo Turbulence 伪湍流。 不过值得一提的是在上一节做了一个往下移动的坐标系, 然后又 reverse了坐标。
p.y -= scroll;
p = turbulence(p);
p.y += scroll ;
可以发现y轴还在移动,但是 x轴的线已经不动了。 这里想了好久为什么 y轴在 reverse之后还能动。
应为 y轴的线是有 p.xx决定的, 当做了turbulence ,再去修改p.y已经无意义了。
光
光就是一个距离的倒数,不过作者在这里做了非常多微调,具体代码如下
float dist = length(min(p,p/vec2(1,stretch.y))) - RADIUS;
float light = 1.0 / pow(dist*dist + GRADIENT * max(p.y + .5, 0.0), 3.0);
为了能够更加清楚,我们在不变形不拉伸的坐标看看
dist = length(p) - 0.1 | dist微调 |
| 空间拉伸挤压 | 空间变形 |
最后已经有点火的感觉的感觉了
颜色梯度
颜色主要是考虑 RBG通道的径向衰减, 对不同的颜色做分层颜色分层:
- 红通道(分母为9)衰减最慢,表现为火焰外层的橙红色。
- 绿通道(分母为2)衰减较快,模拟火焰中间的黄色。
- 蓝通道(分母为1)衰减最快,仅在火焰边缘或近源区域出现(如火芯的蓝焰)。
vec2 source = p + 2.0 * vec2(0, RADIUS) * stretch;
vec3 grad = 0.1 / (1.0 + 8.0 * length(source) / vec3(9, 2, 1));
source对整个空间做了下移,也就是将火源的中心点挪动到圆的最下方。 接下去就是正常径向衰减
环境光闪烁
火焰会有忽明忽暗的感觉,焰周围环境游微弱光晕(类似烟雾或背景光)。 这个感觉通过环境光来做。闪烁 Flicker通过构造两明一暗的正弦波来组成
//Flicker animation time
float ft = FLICKER_SPEED * iTime;
//Flicker brightness
float flicker = 1.0+FLICKER*cos(ft+sin(ft*1.618-p.y));
这里可以使用非常多的方法,对参数也可以任意调整,看的舒服就行。 对于环境光的强度,通过 dot(screen, screen) 随距离增加而衰减,避免远处完全黑暗。
vec3 amb = 16.0*flicker/(1.0+dot(screen,screen))*grad;
纹理随机
作者在最后使用了一个纹理,以增加火焰一些独特的味道。不过经过我的实验,感觉区别不是很大。 也许在其他创意中可以用到
//Scrolling texture uvs
vec2 uv = (p - SCROLL*vec2(0,iTime)) * 510.;
//Sample texture for fire
vec3 tex = texture(iChannel0,uv).rgb;
//Combine ambient light and fire
vec3 col = amb + light*grad*tex;
而我换成
vec3 col = amb + light*grad*vec3(0.9,0.1, 0.1);
下图为两个不同的结果