2D程序式火焰

火焰一般包括焰心、内焰、外焰,至少要有内焰、外焰的区分,另外有烟,火花,热扭曲之类的效果。

基本原理很简单,就是使用梯度值(一般是uv.y)截取不断上移的噪声值来形成火焰效果。

截取方法可以定值截取,也可以插值截取
1. 定值

参考博客

使用step区分内外焰,效果上类似卡通火焰(toon fire)。

火焰语义分割 火焰解析_插值

另外使用偏导数也可以实现类似效果,好处是能加一点抗锯齿效果。

参考博客

火焰语义分割 火焰解析_粒子效果_02

效果其实差不多

2. 插值

噪声值减去递增梯度值即可,结果是颜色变化比较平滑。

float gradientValue = 1 - uv.y;
float t = noiseValue + lerp(0, -2, gradientValue);
float3 col = _FireColor.rgb*(1-t);

火焰语义分割 火焰解析_插值_03

Mask

使用Mask来约束火焰外形的时候,定值截取效果并不好。应该是需要特制下噪声,让火焰集中。

火焰语义分割 火焰解析_火焰语义分割_04

插值的火焰效果就好很多。

火焰语义分割 火焰解析_粒子效果_05

Smoke

烟可以单独制作然后叠加上去,也可以从火焰中截取一部分作为烟雾。

1. 单独制作

与火焰原理相同,一个噪声向上移动,然后需要另加一个噪声与之相差让结果有层次感。

火焰语义分割 火焰解析_火焰语义分割_06

相乘结果:

火焰语义分割 火焰解析_粒子效果_07

修改颜色,加上Mask。

火焰语义分割 火焰解析_火焰语义分割_08

最后将结果和合并到火焰上取即可。

2. 截取

既然前面可以截取噪声不同值区分内外焰,那么自然可以截取一部分作为烟。

直接插值截取效果并不好,比较好的实现参考:

shadertoy

火花、热扭曲

火花使用纯粹的shader实现起来比较麻烦,目前没有找到好的方法,还是使用粒子效果或者动画。

热扭曲就是简单的uv扭曲。

其他

在一个贴图上是可以模拟出粒子效果。最简单原理参考:

How to Write a Smoke Shader

粒子模拟需要能获取上一渲染结果,在unity中需要创建2个RenderTexture。

private RenderTexture SimTexture;
private RenderTexture buffer;
private Material ParticleSimulationMat;
public Shader ParticleSimulationShader; // 粒子模拟Shader
public Material ObjMat;  // 目标Sprite的Material
Start {
    ParticleSimulationMat = new Material(ParticleSimulationShader)
    SimTexture = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGBFloat);
    buffer = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGBFloat);
}
Update {
    // Graphics.Blit(SimTexture, SimTexture, ParticleSimulationMat) 无效,2019.4 版本
    // 保存渲染结果,ParticleSimulationMat中_MainTex便是上一渲染结果
    Graphics.Blit(SimTexture, buffer, ParticleSimulationMat);
    Graphics.Blit(buffer, SimTexture);
    ObjMat.SetTexture("_SimTexture", SimTexture);
}

shadertoy上有一些不错的火焰模拟,基本上可以通过这个方法移植过来,比如这个:

Smoke and fire

结论

还是直接使用Unity自带的粒子系统吧。