[GLSL]着色器周记02——火焰特效
这周学了好多。包括伪随机数。柏林噪声。
先说伪随机数。伪随机数我们用的是周期函数而不是那种由前一项乘一个超大的数取余数的方法。使用周期函数的好处就是可以让其随时间均匀变化。不过使用周期函数一定要保证周期非常长,不然就会出现重复的图样。 这是我在网上找到的一个伪随机函数: cos(x * (12.9898) + y * (4.1414)) * 43758.5453 它使用x, y作为参数,刚好对应像素的坐标。(PS:发现好多GLSL的例子都用类似的随机函数,应该里面蕴含了什么数学吧。) 这个函数的周期应该挺长的, 会发现在cos的后面还乘上了一个超大的数,这样可以达到不错的伪随机效果,不过还需要很重要的一步。 最终的随机函数是这样的: fract(cos(x * (12.9898) + y * (4.1414)) * 43758.5453) fract这个glsl预定义的函数的作用是返回小数部分,会达到非常不错的效果。 ↑这是没有加上fract的结果,因为所有大于1的值都变成1了。(GL中像素的颜色值范围是0.0-1.0) ↑这是加上fract的效果,看起来还不错是吧。 PS: 在想随机数的时候,我想人工智能是不是其实是可以实现的呢。因为智能可能就是对一个个小事件的反应,当这些对各种事情的反应多起来的时候,数量趋近于无穷,或者达到一个阀值,就会表现出智能。因为人类的大脑说不定也是这样,是不是也是对一个个小事件的反应呢,我想是的,大脑里面这种反应非常多,多到了一个阀值,所以才会变现出“智能”,而*意志应该就是量子随机性造成的,而至于为什么不觉得这种随机不想是“随机”的呢,而想是可控的呢?因为这些随机性的东西都会经过一个个在大脑中设定好的程序,被这样处理过的信息之后,看起来就变得“有序”了。(稍后你会看到上面这些这样随机的图样,最终会变成一幅不错的图画)所以,人工智能终有一天是会实现的,只要不断地设定对各种事件的反应,让它看起来“好像是智能的样子呢”,当这种“好像是智能的样子呢”多起来的时候,达到了一个阀值,那么这个东西就是真正的智能了,这里可没有双引号。这里也稍微的用一下数学里面的极限思想了呢,0.999...这样无穷下去,数学上的结果就是1。不过目前想要达到这个阀值,需要的是非常高性能的计算机还有聪明的程序员,至少前者还不能实现,在未来20年之后,估计就能实现看起来非常智能的人工智能了。 哎呀,扯多了。 接下来是一个火焰特效,看起來像20年前的技術,哈哈。由浅入深嘛。慢慢地就会学到现代的技术了。 看起来还阔以吧。 不知道gif能不能播放,不过gif画质太渣了,还是不用gif了。你就想想这个火焰是会动的就行啦。 接下来是柏林噪声(Perlin噪声)了,Ken Perlin在1981年为电影创造了这种噪声,它可以模拟自然的云、火焰、水面等,为电影特效做出了功不可没的贡献,为此,他获得了“奥斯卡技术成就奖”。 Perlin噪声就是n个函数的叠加,这n个函数满足,下一个的频率是前一个的两倍,幅度是二分之一。而函数嘛,当然是选择我们上面的伪随机函数啦。 不过首先,我们要对屏幕的像素分组。我的分组是250*250个一组。 也就是这250*250个像素共享同一个随机的值。然后,对于每个小组,每个小组里面的像素,进行一次插值,(可以是线性插值(假如在A,,B两点间插值,t(0<=t<=1)是距离A的距离比例,那么t位置的值是(1 - t) * A + t * B,(1 - t)是插值函数),不过效果差,Perlin本人推荐 3 * t * t - 2 * t * t * t,后来推荐..哎呦 这个好长,就不写出来了)二维的插值是这样的,首先取本小组的值和右边小组的值在x方向插值得到a,然后是下面小组的值和右下小组的值在x方向插值得到b,最后就是a和b在y方向插值得到最终结果。 glsl里面有预定义插值函数,线性的mix和平滑的smoothstep。 所以,代码看起来是这样的: #define r 250 float noise(float x, float y)
{
int base_x = int(floor(x / r)); // 小组的x编号
int base_y = int(floor(y / r)); // 小组的y编号
x = fract(x / r); // 像素在小组内x方向的权值
y = fract(y / r); // 像素在小组内y方向的权值
float f_x = smoothstep(0, 1, x); // 平滑后的值
float f_y = smoothstep(0, 1, y); // 平滑后的值
return
mix(
mix(rand(float(base_x), float(base_y)), rand(float(base_x + 1), float(base_y)), f_x) // 第一次的x方向插值
,
mix(rand(float(base_x), float(base_y + 1)), rand(float(base_x + 1), float(base_y + 1)), f_x) // 第二次的x方向插值
,
f_y) // 最后的y方向插值
;
} 看起来是这样的: 哇..看起来好爽。 接下来就是Perlin噪声了, 请看代码: float fbm(float x, float y)
{
float total = 0.0, amplitude = 1.0;
for (int i = 0; i < 4; i++) // 4个函数叠加
{
total += noise(x, y) * amplitude;
x += x; // 频率乘以2
y += y; // 频率乘以2
amplitude *= 0.5; // 幅度乘以二分之一
}
return total;
} 效果是这样的: 我已经看到了一点云的样子了呢。 接下来,加点颜色,你可以*发挥,这里只是一个例子: t = float(_t);
float z = fbm(gl_FragCoord.x - t * 100, gl_FragCoord.y - t * 100);
float x = fbm(gl_FragCoord.x + t * 60, gl_FragCoord.y - t * 40);
float c = fbm(gl_FragCoord.x - t * 70, gl_FragCoord.y - t * 80);
vec3 color1 = vec3(1.0, 0.9, 0.0);
vec3 color2 = vec3(1.0, 0.0, 0.0);
vec3 color3 = vec3(0.0, 0.0, 0.0);
vec3 color4 = vec3(0.2, 0.2, 0.2);
vec3 color5 = vec3(0.0, 0.0, 0.6);
vec3 color6 = vec3(0.0, 0.6, 0.0);
gl_FragColor = vec4(mix(color2, color1, z) + mix(color3, color4, x) - mix(color5, color6, c), 1.0); 就可以达到火焰特效了,要更加逼真的效果就需要不断的尝试颜色的组合,或者加入数学知识。 对了,漏了最重要的一点,就是怎么向着色器里面传一个值。 用到的是uniform技术。 在glsl代码中,你只要这样: uniform int _t; 就可以声明一个_t的uniform变量。uniform在一次渲染中值不会改变,所有着色器都可以访问这个值。程序也可以在每一帧时任意修改uniform的值。用这样的语句: GLint location;
location = glGetUniformLocation(program, "_t");
glUniform1i(location, _t); **以上参考:http://www.tuicool.com/articles/VFnAFbB。火焰特效是借鉴http://glslsandbox.com/里面的一个特效,不过原文的地址我忘了T T。 好了。周记完毕,在最后送一个小特效: void main()
{
vec3 color = vec3(0.0, 0.0, 0.0);
float angle = t * t * 0.1;
vec2 q;
q.x = p.x + cos(angle * 3.14 / 180) * 10;
q.y = p.y + sin(angle * 3.14 / 180) * 10;
float A = q.y - p.y;
float B = p.x - q.x;
float C = q.x * p.y - p.x * q.y;
float rate;
rate = A * gl_FragCoord.x + B * gl_FragCoord.y + C;
rate = rate * rate;
rate = rate / (A * A + B * B);
rate = 100 / rate;
color.x = rate * 0.0;
color.y += rate * 0.0;
color.z += rate * 0.8;
float x, y;
x = gl_FragCoord.x - 1366.0 / 2;
y = gl_FragCoord.y - 768.0 / 2;
rate = x * x + y * y;
rate = sqrt(rate);
int temp = _t % 20;
rate -= (temp * temp);
if(rate < 0)
rate = -rate;
rate *= rate;
rate = 100 / rate;
color.x = rate * 0.8;
color.y += rate * 0.0;
color.z += rate * 0.0;
gl_FragColor = vec4(color, 1.0);
}
|