來做一個 WebGL 練習場

Q: 什麼是 WebGL?
A: 讓你的 JavaScript 可以和顯示卡溝通的 API。

Q: 所以是拿來畫圖用的?
A: 主要是拿來畫圖,但是也有強者拿來算 CUDA 或是挖礦。

Q: 如果使用者的機器沒有顯卡呢?
A: 看瀏覽器實作,通常是用 CPU 暴力模擬,有時就直接沒反應了。

好了我們直接進入主題。

  1. 首先來建立環境,為了方便維護,直接使用 github pages 來放我們的練習場,首先開一個 github 專案 webgl-playground,並且新增兩個基本檔案 package.json.gitignore

2. 再來在 docs/ 裡建立一個基本的網頁架構。內容請參考這裏

docs/
├── README.md
├── css
│ └── index.css
├── html
│ └── index.html
└── js
└── index.js

3. 切換去 github 的設定頁:

在底下 GitHub Pages 的地方選擇 master branch /docs folder

這樣 README.md 就會變成 github pages 的首頁,並且在裡面用 iframe 的語法內嵌我們真正的練習場網頁

<iframe
width="100%"
height="600px"
frameborder="0"
scrolling="no"
src="https://bill42362.github.io/webgl-playground/html/index.html"
></iframe>

太好了建立了一個免費的網頁!

4. 建立開發用的 local server。
 a. 安裝一些必要的 package: yarn add -D express helmet nodemon
 b. 在 package.json 中增加

"scripts": {
"start": "nodemon --exec 'node src/server' -e js,css,html --watch docs --watch src"
},

c. 新增 src/server/index.js

d. 執行 yarn start 我們的開發 server 就會跑在 http://localhost:3000 拉!

5. 建立讓使用者自訂變數的欄位,預設值,還有加上我們的主角 <canvas>。比較值得注意的是讀取使用者輸入的程式碼時,用 Function() 會比用 eval() 快很多。commit 在此

6. 讓 WebGL 真正跑起來!這一步驟主要參考 MDNcommit 在此
 a. 首先要取得 WebGL 的進入點,並且編譯我們的 shader。

b. 接著是連接兩個 shader 形成我們的 program。這裡同時要把 shader 裡面變數 aPosition, aColor, uModalView, uProjection的進入點提取出來。

c. 再來是在 GPU 中創建 buffer 將我們的變數 aPosition, aColor 傳入。

d. 然後是計算我們的變數 uModelView, uProjection,這邊需要用到一些線性代數

e. 最後就用這個 program 畫出我們預設的正方形!

7. 將 uModelView 拆成 uModel, uView。直接看 commit 吧。

8. 終於完成流水線式的繪製了,接下來歸納一下改動各個 input 時需要處理的行為:

a. Positions (這裡把 Vertexes 改為 Position) 或 Colors:
讀取輸入 -> 建立 buffer -> 綁定 buffer 給 program -> 繪圖
b. Shaders:
讀取輸入 -> 編譯 shader -> 編譯 program
-> 綁定 buffers 給 program -> 傳入 uniforms -> 繪圖
c. Uniforms (要做成動態效果):
傳入 uniforms -> 繪圖

繪成流程圖:

Input
|
v
Vertex shader
|
v
Program <- Fragment shader <- Input
|
v
Locations
|
| <- Position Buffer <- Input
| <- Color Buffer <- Input
| <- Uniforms <- Time, Mouse, Keyboard, etc.
|
v
Render

寫成函式的格式:

createVertexShader(gl, Input)
|
v
createProgram(gl, shaders) <- createFragmentShader(gl, Input)
|
v
getLocations(gl, program)
|
dockBuffer(gl, buffer) <- createPositionBuffer(gl, Input)
|
dockBuffer(gl, buffer) <- createColorBuffer(gl, Input)
|
dockUniform(gl, uniform) <- Uniforms <- Time, Mouse, Keyboard, etc.
|
v
render()

另外我們還需要這個物件來存 render() 需要的參數:

const glState = {
sources: {
attributes: { position, color },
shaders: { vertex, fragment },
},
program,
shaders: { vertex, fragment },
locations: {
attributes: { aPosition, aColor },
uniforms: { uProjection, uView, uModel },
},
buffers: { aPosition, aColor },
uniforms: { uProjection, uView, uModel },
};

我們每個 frame 需要做的事情會變成這樣:

根據以上思維重構的 commit

9. 增加繪製座標軸的 program,並且擴充 nextFrame() 成可以繪製兩組 glState。commit

10. 最後加上 animation 的功能,並且根據時間旋轉 uModel。這樣就更有 3D 的感覺拉!commit

這邊附上做出來的成果!

https://bill42362.github.io/webgl-playground/html/index.html

** 你還可以更進一步:如果想要幫 <canvas> 增加用滑鼠轉動的功能,應該要怎麼做?
** 這裡提供幾個我覺得很值得參考的 WebGL Demo:
 1. http://madebyevan.com/webgl-water/
 2. https://experiments.withgoogle.com/body-browser
 3. https://playcanv.as/e/p/44MRmJRU/ <- 這是 webgl2