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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM