iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 3
3

0. 今日工事

  • Three.js:三的黑魔法暗藏科學
  • WebGL:加速的平行世界
  • Shader:GPU 的異國語言
    • Vertex Shader
      • Buffer
      • Attributes
      • Uniforms
      • 放射系宇宙解析
    • Fragment Shader
      • Uniforms
      • Varying
      • 放射系宇宙解析

1. Three.js:三的黑魔法暗藏科學

https://ithelp.ithome.com.tw/upload/images/20171222/20107828GOhEs4fNOl.jpg

前兩天,算圖用的核心技術是 Three.js。神奇的要命喔,隨便就畫個山啊畫個水的。我給你翻譯翻譯,什麼叫做 Three.js(後面簡稱 Three 或是「三」)。首先,對自己默念兩遍:讀冊日,爬文章像爬山,見山不是山,見山又是山,一山還有中山高。讀冊日,爬文章像爬三,見三不是三,見三又是三,一三還有一三高。

它啊,是個 JavaScript Library,主要用來在網頁上顯示 3D 的畫面(這也是名字的來由)。底下用到的技術包括 HTML5 中的 canvasWebGL(可以透過 canvas 取得 WebGL 的 context)、SVG 等等,實作 3D 場景、模型載入、3D 動畫、鏡頭、燈光等物件。而神說過,雖然不理解 WebGL 也能用三,因為三 abstracting 很多實際上非常繁瑣的 WebGL 的 API。

目前三還是處於 alpha 階段,從 2010 released 到現在,到了 0.89.0。很多版,但始終還是 alpha。可以想像三體社群(我亂講的,但是這樣滿好笑的)的野心非常龐大。所以最好還是了解一下 WebGL 比較穩妥,畢竟到時候 Three 的 API 改了之後哇哇叫找不到人哭訴啊。慘。但是說回來,其實也不用到很懂,只要到你可以大概想像三每個物件後面掛鉤的 WebGL 相應 API 是什麼就好!

2. WebGL:加速的平行世界

被科技餵養的人類,為了要宅在房間打遊戲的時候畫面不要當機,CPU 序列執行已經撐不住我們的爆炸性幻想,只好委託偉大的效能領領導 NVIDIA 製作出可以平行運算畫面中每個 pixel 的 GPU 來解救不斷滑落山坡的 framerate。畫面顯示每個 pixel 的運算量其實並不大,但是 1920 * 1080 的螢幕,跑著 60 fps 的影集的時候,總共有一億兩千多萬次要算,CPU 就可以生火取暖。GPU 就像是超超超超多核心計算核心,可以用特殊的架構同時跑畫面中的每個 pixel放射性宇宙每顆星星都是 parallel 執行運算,我的媽啊,真的很快。

https://ithelp.ithome.com.tw/upload/images/20171222/20107828JvmzATD6gu.png

初學 WebGL 是有恐懼的。覺得很奇怪啊,明明就是 graphic library,連個簡單的三角形都要寫三十行才畫出來,耍帥?但是你若能一層一層剝開她的心,就會流淚的發現她的美好。我得承認我可能無法在兩三篇文章裡面把自己讀了好多大神寫的東西綜合起來打包好,就算打包好了,本來不懂的人可能還是看不懂。所以練神功,必須得秘笈之間互相參照,文後會附上本人爬三的 roadmap 和血淚。初階密集新法大推 WebGL Fundamentals 這個網站,口吻細心呵護,demo 嬌柔貼身。

https://ithelp.ithome.com.tw/upload/images/20171222/20107828K01Udd4Ep4.png

WebGL 簡單來說是一個基於 OpenGL 標準所設計的 API,做的事情就是讓 JavaScript 跟 GPU 溝通(如上圖所表示)。學習新事物,我習慣先嗑一張圖,也就是俗稱的燒符水。搞懂 key words 之間的互動邏輯,再來了解她們個別的意涵與內心戲。上面的圖是從另一個 JavaScript 3D Framework “philogb” 的簡介投影片借來的,解釋得少一分太少、多一分太多。

3. Shader:GPU 的異國語言

而 GPU 裡面執行的 Compiled Program 也不是無中生有,是可以由 WebGL 的介面把你的設計丟進去。內部有很多複雜的步驟,但是最基本可以客製是 Vertex Shader 與 Fragment Shader 這兩個步驟裡面的程序。這兩個 Shader Program 用 GLSL (openGL Shading Language) 撰寫。

https://ithelp.ithome.com.tw/upload/images/20171222/20107828PDlGVtbk04.png

3.1 Vertex Shader 點點點點線面

Vertex Shader 之所以叫做 Vertex Shader 是因為每一筆 data 就會執行一次 Vertex Shader。那你問我 vertex 從哪裡來,就是 JavaScript 端透過 Buffer 傳進 GPU。Buffer 可以簡單想像為 GPU 裡面的記憶體。而 Attribute 就是單指從 Buffer 裡面拿出拉的資料。也就是說,剛剛前面說的 data 都會以 Attribute 的形式出現在 Vertex Shader 裡面。而 Vertex Shader 主要的功能就是回傳這個點的位置!

解析放射系宇宙

以前放射系宇宙為例,每個 particle 都是一筆 data,js 程式碼將每個 particle 的出生時間、顏色、大小等資訊存到 Buffer,就變成一個序列的資料,長度就是 particles 的數量(因為每 spawn 一個 particle 就會增加一單位長度)。

./js/GPUParticleSystem.js裡面GPUParticleContainer.spawnParticle

// 把新的 position 加到相應的 attribute 裡面
positionStartAttribute.array[ i * 3 + 0 ] = position.x + ( particleSystem.random() * positionRandomness );
positionStartAttribute.array[ i * 3 + 1 ] = position.y + ( particleSystem.random() * positionRandomness );
positionStartAttribute.array[ i * 3 + 2 ] = position.z + ( particleSystem.random() * positionRandomness );

...

// 把新的 velocity 加到相應的 attribute 裡面
velocityAttribute.array[ i * 3 + 0 ] = velX;
velocityAttribute.array[ i * 3 + 1 ] = velY;
velocityAttribute.array[ i * 3 + 2 ] = velZ;

而 Vertex Shader 就根據現在的時間減掉出生時間在加上颳風的強度(turbulance) 來計算每次的位置。講到這裡聰明認真的孩子就會問,那現在時間的資料是哪裡傳進來的勒?
因為這些資料對於每個 pixel 都是一樣的,就像是 global variable 一樣,所以我們就他做 uniform!一樣可以透過 WebGL 的 API 用 js 傳進來。

vertexShader: [
    
    // uTime 的 u 代表他是 uniform,time 就是 global 的現在時間
    'uniform float uTime;',
    'uniform float uScale;',
    'uniform sampler2D tNoise;',
    
    ...
    
    // 經過時間 = 現在的時間 - 出生時間
    '	float timeElapsed = uTime - startTime;',
    
    ...
];

https://ithelp.ithome.com.tw/upload/images/20171222/201078286X2XoXzKdD.png

3.2 Fragment Shader 色色色即是空

Fragment Shader 執行的邏輯很不一樣,GPU 會每個 pixel 都需要執行一 Fragment Shader 裡面的程序。原因很簡單,畫面中每個點都需要顏色嘛。沒星星的地方就黑的這樣對嗎?假如我想要渲染星星的效果,那連帶那個位置附近的 pixel 都需要處理呢!那你要使用的 data 哪裡來勒,其中一個就是 uniform,原理跟 Vertex Shader 當中的相同,也就是同一組變數。而這裡沒有 Attribute,只有 Varying。可以把 Varying 想像成 Vertex Shader 和 Fragment Shader 溝通的管道,每次 Vertex 計算完位置之後,也要給所有存在的 Varying 一個數值,剛好那個位置 pixel 的相應 Varying 值就會是 Vertex Shader 裡面設定的樣子,而其他所有的點就會利用這些已經存在的 Varying 數值進行內差來得到。簡單的例子就是三角形上三個點在 Vertex Shader 分別透過 Varying 設定一個藍紅綠三色,那在 Fragment Shader 直接使用那個 Varying 來上色就會出現下面的圖!

https://ithelp.ithome.com.tw/upload/images/20171222/20107828vckvhN9UcD.png

解析放射系宇宙

而在放射系宇宙中,Fragment Shader 並沒有使用到內差的計算,因為每個點之間都互相沒有關聯。他做的事情就是讓每個 particle 的顏色根據已經出身的時間(由 Vertex Shader 計算出來透過 varying float lifeLeft 傳進 Fragment Shader 的)來將顏色慢慢淡入和淡出!

./js/GPUParticleSystem.js裡面GPUParticleContainer.spawnParticle

fragmentShader: [
    
    ... 
    
    // 由 vertex shader 計算完傳過來的
    'varying float lifeLeft;',
    
    ...
    'if( lifeLeft > 0.995 ) {',
        // 淡入
    '	alpha = scaleLinear( lifeLeft, vec2( 1.0, 0.995 ), vec2( 0.0, 1.0 ) );',
    '} else {',
        // 淡出
    '	alpha = lifeLeft * 0.75;',
    '}',
    ...
];

7. 請愛CYBERの audio / VISUAL

偷偷說,不要告訴別人,你假如想要知道為什麼可以畫點、畫線、又畫三角形,去查查 Primitve Assembly。口水用光。
來點 mad science:工程師如我,modulelize 絕對是正義的。2017 的前端奴,看到 React 哪有不叫一聲大哥的。還有那個那個,上面那個 glsl 居然直接用 array 去打,吼,真夠他媽腦殘。沒錯,來了!Webpack + glslify + Three.js + React 邁進吧!三體模組化!三體萬歲!三體萬歲!奈米切割打完收工。

https://ithelp.ithome.com.tw/upload/images/20171222/201078283N1f1Lngwz.png

8. 血淚書房

關於作者

Vibert Thio

致力於將對於技術的深度研究轉化為新型態藝術創作的能量,並思考技術的拓展/侷限與其對於藝術論述/呈現的影響。專長為數位藝術創作、音像程式設計、互動設計,喜愛即時運算的臨場感與不可預測。


上一篇
§d02§ 錐細胞衝擊!時空扭曲!數學模擬黑洞造型。
下一篇
§d04§ 荒野探勘部!山也模組,地也模組,Shader 也模組!Three.js + Webpack 專案架構。
系列文
aesthEtic,CYBERの audio / VISUAL,網頁中的聲音與影像研究30

2 則留言

1
牙膏大師
iT邦新手 5 級 ‧ 2017-12-22 20:21:12

哈哈哈雖然風格很特別但內容挺不錯的

請繼續支持,拜託了!

1
angelliya00
iT邦新手 5 級 ‧ 2017-12-22 23:29:08

好有趣,期待後續的文章!

落空就不好了

我要留言

立即登入留言