数字孪生常见特效Shader低成本实现 pseudo fire火焰

485 阅读4分钟

接上一篇的湍流与上上篇的Tonmap技术,作者利用多层 Wave生成湍流这个思路,叠加一系列的绘图技巧完成了 火焰特效。 当然美丽的效果是被发现的,而不是创造的。 , 首先来看看效果。

weixin.qq.com/sph/AOsMJVH…

当然忧郁的我还是喜欢蓝色的火焰 :)

2505012137

本篇中详细分析了火焰实现的所有技巧。

空间挤压拉伸

如果读者朋友们使用过 blender或者其他的 3d软件, 有对 geometry做挤压和拉伸的操作。 同样我们可以通过函数对坐标系(也就是空间)做同样的操作。 需要强调的这里挤压/拉伸 是针对值。 如果坐一些反向操作,例如 1.0 / value。 拉伸就变成了挤压 y方向拉升函数为

    float ystretch = 1.0 - 0.5 / (1.0+p.x*p.x);
2505012053 2505011816

左边的图为函数的图像, 右边是经过 y轴拉伸之后。 可以看到右图白色更加宽了。 记住我们话白色的依据是他的 y值 < 0.1。 因为y方向乘上了一个小于1.0的值,所以<0.1的范围在原坐标系看起来变多了,也就是被拉伸了。 (我的表达力有限...)

x方向压缩函数为,

float xstretch = 2.0 - 1.5*smoothstep(-2.0,2.0,p.y);
2505012213 2505012256

左边的图为函数的图像,可以看到在y为负值的时候 stretch放大了,y为正值 streach变小。 也就是会形成。 stretch放大意味在 canvas坐标系中是一个压缩的状态,也就会形成右图中的下缩上拉的状态。 就像是一个火焰 flame的时候 由小变大一样。

最后做一个朝上方向的运动,就像火焰往上飘一样。实现起来很简单 坐标系整体往下移动,

float scroll = SCROLL*iTime;
p.y -= scroll;
    

2505013126

空间湍流扭曲

这块的理解可以参考上一篇文章 Pseudo Turbulence 伪湍流。 不过值得一提的是在上一节做了一个往下移动的坐标系, 然后又 reverse了坐标。

p.y -= scroll;
p = turbulence(p);
p.y += scroll ;

可以发现y轴还在移动,但是 x轴的线已经不动了。 这里想了好久为什么 y轴在 reverse之后还能动。 2505021245

应为 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);

为了能够更加清楚,我们在不变形不拉伸的坐标看看

2505021026 2505020718
dist = length(p) - 0.1dist微调
2505020834 2505021449
空间拉伸挤压空间变形

最后已经有点火的感觉的感觉了

颜色梯度

颜色主要是考虑 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对整个空间做了下移,也就是将火源的中心点挪动到圆的最下方。 接下去就是正常径向衰减 2505023401

环境光闪烁

火焰会有忽明忽暗的感觉,焰周围环境游微弱光晕(类似烟雾或背景光)。 这个感觉通过环境光来做。闪烁 Flicker通过构造两明一暗的正弦波来组成

//Flicker animation time
float ft = FLICKER_SPEED * iTime;
//Flicker brightness
float flicker = 1.0+FLICKER*cos(ft+sin(ft*1.618-p.y));

2505023814

这里可以使用非常多的方法,对参数也可以任意调整,看的舒服就行。 对于环境光的强度,通过 dot(screen, screen) 随距离增加而衰减,避免远处完全黑暗。

    vec3 amb = 16.0*flicker/(1.0+dot(screen,screen))*grad;

2505024123

纹理随机

作者在最后使用了一个纹理,以增加火焰一些独特的味道。不过经过我的实验,感觉区别不是很大。 也许在其他创意中可以用到

   //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);

下图为两个不同的结果

2505025610 2505025557