在上一篇文章中,我们实现卡通火ShaderToy到ShaderLab的移植。在这篇文章中,我们来解析一下其原理。
我们在分析时,需要从整体到局部。
依次从输出结果倒着逐步分解,这样对于复杂的效果就不会迷失目标。
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = i.uv;
uv.x *= 4.0;
float t = _Time.y * 3.0;
float3 col = 0;
float noise = getNoise(uv, t);
//shape _CUTOFF to get higher further up the screen
_CUTOFF = uv.y;
//and at horiz edges
_CUTOFF += pow(abs(uv.x * 0.5 - 1.), 1.0);
//debugview _CUTOFF field
//fragColor = float4(float3(_CUTOFF),1.0);
if (noise < _CUTOFF)
{
//black
col = 0;
}
else
{
//fire
float d = pow(getDepth(noise), 0.7);
float3 hsv = float3(d * 0.17, 0.8 - d / 4., d + 0.8);
col = hsv2rgb(hsv);
}
return float4(col, 1.0);
}
if (noise < _CUTOFF)
{
//black
col = 0;
}
else
{
//fire
float d = pow(getDepth(noise), 0.7);
float3 hsv = float3(d * 0.17, 0.8 - d / 4., d + 0.8);
col = hsv2rgb(hsv);
}
black部分(当在遮罩范围外时,返回黑色)
fire部分(显示 火的形状 和 火的着色)
hsv2rgb : 由 hsv 转化为 RGB 色
hsv : 色相、饱和度、亮度
d :使用pow调节色阶范围
getDepth(noise) : 将黑白灰渐变色分离成几个色阶,以表现卡通效果
fixed4 frag(v2f_img i) : SV_Target
{
float2 uv = i.uv;
uv.x *= 4.0;
float t = _Time.y * 3.0;
float3 col = 0;
float noise = getNoise(uv, t);
//shape : 模拟出火焰大体形态(三角形)
_CUTOFF = uv.y;
//and at horiz edges
_CUTOFF += pow(abs(uv.x * 0.5 - 1.), 1.0);
if (noise < _CUTOFF)
{
//black
col = 0;
}
else
{
//fire
//计算得到 火的轮廓
float d = pow(getDepth(noise), 0.7);
//计算得到 色相、明度、饱和度
loat3 hsv = float3(d * 0.17, 0.8 - d / 4., d + 0.8);
//将 HSV 转化为 RGB
col = hsv2rgb(hsv);
}
return float4(col, 1.0);
}
//获取整屏的噪波效果
float getNoise(float2 uv, float t)
{
//given a uv coord and time - return a noise val in range 0 - 1
//using ashima noise
//add time to y position to make noise field move upwards
float TRAVEL_SPEED = 1.5;
//octave 1
float SCALE = 2.0;
float noise = snoise(float3(uv.x * SCALE, uv.y * SCALE - t * TRAVEL_SPEED, 0));
//octave 2 - more detail
SCALE = 6.0;
noise += snoise(float3(uv.x * SCALE + t, uv.y * SCALE, 0)) * 0.2;
//move noise into 0 - 1 range
noise = (noise / 2. + 0.5);
return noise;
}
使用 TRAVEL_SPEED 、 SCALE 和 t 作为噪波因子,作为影响生成噪波的主要参数
TRAVEL_SPEED : 流速
SCALE :缩放
t :时间
snoise(float3(x,y,z)); : 噪波生成算法(这里使用了 ashima noise 算法)
Perlin噪声与Simplex噪声笔记
这里引用一篇别人的笔记,作为该噪音函数的讲解
_CUTOFF = uv.y;
uv.x * 0.5 - 1 :值域范围改变(0,4)-> (0,2) -> (-1,-1)
abs(uv.x * 0.5 - 1.); : 使值域变成对称的样子 (-1,-1) -> (1,0)、(0,1)
pow(abs(uv.x * 0.5 - 1.), 2.0);可以用指数函数来控制三角形状的边扭曲程度
uv.y + pow(abs(uv.x * 0.5 - 1.), 1.0) :实现三角形状
//计算得到 火的轮廓
float d = pow(getDepth(noise), 0.7);
// 将黑白灰渐变色分离成几个色阶,以表现卡通效果
float getDepth(float n)
{
//given a 0-1 value return a depth,
//remap remaining non-_CUTOFF region to 0 - 1
//实现边缘虚化的作用,把原本尖锐的边缘 ,变得虚化柔和
float d = (n - _CUTOFF) / (1. - _CUTOFF);
return d;
//色调分离
d = floor(d * _Steps) / _Steps;
return d;
}
n - _CUTOFF 的效果
float d = (n - _CUTOFF) / (1. - _CUTOFF); :边缘虚化,归一化后
色调分离 : d = floor(d * _Steps) / _Steps;
把上面计算的结果,进行乘以一个较大的数,使用向下取整后,再除以这个较大的数归一化。以达到卡通效果中,一块一块的色调效果
pow 用来调节亮度
计算得到 色相、饱和度、亮度。这里的特征值是 计算出符合 火的颜色区间范围。
其他效果的颜色,需要自己调整寻找颜色算法
float3 hsv = float3(d * 0.17, 0.8 - d / 4., d + 0.8);
float3 hsv2rgb(float3 c)
{
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * lerp(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
这里给出taecg老师总结的 HSV 2 RGB 算法链接