用Houdini的grid作畫板學習GLSL的fragment shader原理

最近一直在學習使用GLSL,國外有兩個很是好的資源網站 shadertoyglslsandbox。裏面有不少關於glsl的案例,網站維護也都是一些圈裏的大神在作着,若是有想法學習比較底層的shader能夠看看這兩個網站。html

這裏主要講一講經過在Houdini裏面用grid作畫板,經過編寫VEX代碼來實現GLSL中fragment shader的原理。所謂的fragment其實能夠直接理解爲像素化。從vertex shader出來後全部的頂點數據(位置,顏色等等)都經過fragment shader來進行插值一個像素一個像素點的畫滿整個屏幕。爲了理解這一過程,我直接在最熟悉的vex環境裏面作了一些練習。其實vex從某種程度上來說也算houdini的shader語言,一方面他原本就是point或者primitive、vertex遍歷的一種語言,除開pcopen帶來的與自身鄰居點產生互動外,其每個point或primitive都是相互獨立進行運算的,另外一方面你要是牛逼點的,mantra的shader也能夠直接使用vex開寫。並且vex的處理過程像極了GPU的並行運算方法。在下面的三個練習過程當中,也發現了H14與以前的版本相比,在VOP上有一個比較好的改進的改進,這個我留在後面講。多線程

 

練習一 - 在grid上畫一個帶陰影的圓球,其中光源能跟着攝像機走性能

用fragment shader的思路來闡述這個效果怎麼實現,首先咱們把grid上面的每個點當作屏幕上的一個像素點。在shader進行着色的過程當中,是並行的對每個像素點進行着色。也就是說我隨機找grid上的一個點,那麼我計算這個點的顏色時,我是假設這個世界上就只有我這一個點了,個人鄰居點在哪我不知道,我也不用知道。就比如我要作一千個餃子,每一個餃子都是用一種方法作出來的,就是拿起餃子皮,用筷子戳一塊餡放在餃子皮中間而後捲起餃子皮包起來。GPU牛逼的地方就是你來一千個餃子的訂單他就出一千個工人同時每人包一個餃子而後出貨,一萬個就一萬個工人同時每人包一個,每次一次性能出多少餃子只取決於GPU有多少工人(管線)。雖然上面寫的有點囉嗦,可是這也是最直白解釋並行運算的原理了。學習

回到點上,咱們開始包這一個餃子了。首先是圓球,那麼就有球心。定義好球心和半徑,這個球基本上就出來了。雖然grid上只有x和y這兩個軸,可是咱們經過sqrt(radius*radius - x*x - y*y)這個方法直接模擬計算出z軸的正方向值。這樣咱們在這個點上以x,y和計算出來的z三個數肯定了它在模擬的三維空間中的位置,與圓心相減那就是這個位置在模擬的三維空間中應有的面法線向量surfaceNormal。這樣空間有了面法相也有了,光源light在攝像機位置也算是已知的。經過dot(light,surfaceNormal)可以簡單的計算出光影明暗關係來。若是把這個值乘以一個指數,高光和陰影的過分能過更加的平滑。下面是代碼:測試

 1 vector light = point(1, "P", 0);
 2 light = normalize(light);
 3 
 4 vector bmax, bmin;
 5 getbbox(geoself(), bmin, bmax);
 6 
 7 float posX = (@P.x - bmin.x) / (bmax.x - bmin.x);
 8 float posY = (@P.y - bmin.y) / (bmax.y - bmin.y);
 9 
10 float ratio = (bmax.y - bmin.y) / (bmax.x - bmin.x);
11 
12 posY *= ratio;
13 vector pos = set(posX, posY, 0);
14 vector center = set(0.5, 0.5 * ratio, 0);
15 
16 float radius = 0.2;
17 float posZ = 0;
18 
19 float distance = length(pos - center);
20 if(distance < radius){
21         posZ = sqrt(radius * radius - pow((center.x - posX),2) - pow((center.y - posY),2));
22         vector relPos = normalize(set(posX - center.x, posY - center.y, posZ));
23         float bright = pow(dot(light, relPos),2);
24         @Cd = set(bright, bright, bright);
25 }else{
26         @Cd = set(1,1,1);
27 }

這裏把點的x,y的位置壓縮到了一x軸總長爲1的矩形,ratio是高寬比。這裏主要是模擬glsl裏面的:網站

vec3 pos = (gl_FragCoord.xy / resolution.xy , 0);spa

float ratio = resolution.x / resolution.y;線程

 

練習2 - 生成幾個發光的小亮點,並讓小亮點畫出三葉線。code

這個比上一個練習有稍微上升了一個級別,由於咱們在這用到了for循環。orm

一樣是每次只看一個點,for中循環每一次至關於我又要增長一個小亮點了,可是for主體裏面不是在建立小亮點,在計算這個點的顏色值以前,咱們已經假設全部小亮點在屏幕上什麼位置和大小關係都是知道的,咱們只是要求出全部亮點的顏色對這個點產生了什麼影響,有點像倒推的理解。

 1 #define PI 3.14159265359
 2 
 3 float time = chf("time");
 4 
 5 vector light = point(1, "P", 0);
 6 light = normalize(light);
 7 
 8 vector bmax, bmin;
 9 getbbox(geoself(), bmin, bmax);
10 
11 float posX = (@P.x - bmin.x) / (bmax.x - bmin.x);
12 float posY = (@P.y - bmin.y) / (bmax.y - bmin.y);
13 
14 float ratio = (bmax.y - bmin.y) / (bmax.x - bmin.x);
15 
16 posY *= ratio;
17 vector pos = set(posX, posY, 0);
18 vector center = set(0.5, 0.5 * ratio, 0);
19 
20 int count = 10;
21 float radius = 4;
22 float size = 0.1;
23 vector color = set(0,0,0);
24 
25 for(int i = 0; i < count; i++){
26         vector starPos = set(sin(float(i)/float(count) * 2 * PI + time) * sin(3 * float(i)/float(count) * 2 * PI  + time) *radius,
27                              cos(float(i)/float(count) * 2 * PI + time) * sin(3 * float(i)/float(count) * 2 * PI  + time) *radius,
28                              0);
29         float distance = size / max(length(@P - starPos), 0.001);
30         color += pow(min(distance, set(1,1,1)), 3);
31 }
32 
33 @Cd = min(color, set(1,1,1));

這裏講一講hou14的一個默不做聲的升級,也就是以前提到的VOP(wrangle)比較好的改進。就是從以前可選單線程到八線程改成了有多少線程就多少線程。按理說vop在cpu中進行多線程並行運算應該是不難也合情合理的事,正如我在最上面講的vex每次是針對一個point或primitive進行計算的,同理vop也是這樣的。在模擬過程我明顯的感受到了多線程在vop上的優點。

測試過程我是用公司的機器作的,H14可以同時使用最多16個線程一塊兒運算,grid樣本一共有400*640=256,000個點。下面的這個表格是個人練習二在H13和H14中wrangle部分的計算表現:

循環次數  H14 H13
10 52.57ms 239.30ms
100 184.81ms 9.170s
1000 1.378s 1m30.06s

能夠看到但循環次數成指數增加時,H14的vop中並行運算速度發揮了驚人的做用。其實這個數據也是我爲何這麼想要學glsl的一個緣由之一,本人很是但願可以把feature movie中的一些特效可以拿到實時交互中去和人產生互動,形式我先很少說,單單計算能力就已經很是吃力,而glsl給了咱們一個直接可以把GPU浮點並行運算的能力運用起來的一個途徑。以前不少對於成千上萬個點同時進行計算操做的任務,若是用傳統的方法一一排隊扔給CPU的話,效果是很難達到實時的要求的,以前關於彈力和斥力的博文就談到了這一點。

 

練習3 - 畫出一個太空穿梭效果

這裏爲了看看fragment shader到底有多神奇,本人直接拿着glslsandbox中的例子轉譯過來試了一下,沒想到效果確實很是好。簡單改了改一些參數直接就轉起來了,真是棒棒噠。

值得一提的是這個例子由於我的緣由斷斷續續搞的,中間分別用h14和h13實現了一次,發現vex裏面的數據類型在h14裏面也有了一些小更新。新增了vector2 和 matrix2這兩個新數據類型,通常人可能用不上他,不過在處理平面數據上這兩個類型起始仍是蠻有用的。 :P

說實話,整段代碼本人也沒搞太明白,只是隨便調了一點參數,而後把matrix2須要的計算直接寫成了手算的方法。正所謂前人栽樹後人乘涼,有時間再正經琢磨一下代碼。

 1 #define PI 3.14159265359
 2 
 3 float time = chf("time");
 4 
 5 vector bmax, bmin;
 6 getbbox(geoself(), bmin, bmax);
 7 
 8 float posX = (@P.x - bmin.x) / (bmax.x - bmin.x) - 0.5;
 9 float posY = (@P.y - bmin.y) / (bmax.y - bmin.y);
10 
11 float ratio = (bmax.y - bmin.y) / (bmax.x - bmin.x);
12 
13 posY = posY -  0.8*ratio;
14 
15 posY *= ratio;
16 vector pos = set(posX, posY, 0);
17 
18 time = time * 0.5 + ((.25+.05*sin(time*.1))/(length(pos)+.07))* 2.2;
19 float si = cos(time);
20 float co = tan(time);
21 //matrix2 (a b) (c d) -> (co, si) (-si, co)
22 
23 float c = 1.0;
24 float v1 = 0.0;
25 float v2 = 0.0;
26 
27 for(int i = 0; i < 100; i++){
28         float s = float(i) * 0.035;
29         vector PP = s * pos;
30         PP = set(PP.x * co + PP.y * si, PP.x * (-si) + PP.y * co, 0);
31         PP += set(0.22, 0.9, s - 1.9 - sin(time * 1.13) * 0.1);
32         for(int j = 0; j < 8; j++){
33                 PP = abs(PP) / dot(PP, PP) - 0.659;
34         }
35         v1 += dot(PP,PP) *.0015 * (1.8+sin(length(pos*13.0)+.5-time*.2));
36         v2 += dot(PP,PP) *.0015 * (1.5+sin(length(pos*13.5)+2.2-time*.3));
37         c = length(PP * 0.5) * 0.35;
38 }
39 
40 float len = length(pos);
41 v1 += smooth(01, 0.0, len);
42 v2 += smooth(1, 0.0, len);
43 
44 float re = clamp(c, 0.0, 1.0);
45 float gr = clamp((v1 + c) * 0.35, 0.0, 1.0);
46 float bl = clamp(v2, 0.0, 1.0);
47 vector co1 = set(re, gr, bl) + smooth(0.6, 0.0, len) * 0.9;
48 
49 float gray = dot(co1, set(0.499, 0.487, 0.014));
50 @Cd = set(gray, gray, gray);
相關文章
相關標籤/搜索