---
# System prepended metadata

title: WebGL 學習手冊
tags: [圖學]

---

---
tags: 圖學
---
# WebGL 學習手冊

> 此手冊為學習 WebGL编程指南 與 WebGL 基礎概念 的學習心得文件.
> [WebGL编程指南](https://www.tenlong.com.tw/products/9787121229428?list_name=srh)
> [WebGL Beginner's Guide](https://www.tenlong.com.tw/products/9781849691727)
> [WebGL 基礎概念](https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-fundamentals.html)
> [The Book Of Shaders](https://thebookofshaders.com/?lan=ch)
> [阿凱Shader入門](https://docs.google.com/presentation/d/1PG7e9a2y9YFi6wy5ljuNge_q1MYnXCe1_LZCRxwnua0/edit#slide=id.g17fd6042b3_0_43)
> [史丹佛VR課](https://stanford.edu/class/ee267/lectures/lecture3.pdf)
> [圖解渲染流程](https://zhuanlan.zhihu.com/p/137780634)
> [3D图形学基础 - 柯灵杰](https://zhuanlan.zhihu.com/p/27846162)
> [20分钟让你了解OpenGL](https://zhuanlan.zhihu.com/p/56693625)
> [color=#3b75c6]


> [Real-Time-Rendering-4th 中譯版](https://www.wolai.com/dpaCHvw9WZRaB3aM2BzF9j)
> [color=#F7A004]
## 著色器 shader

### 初始化著色器 initialize shader

1. getCanvas (Html canvas element)
2. getWebGLContext
3. 初始化著色器
4. setCanvasBGColor
5. gl.clearColor + gl.clear(gl.COLOR_BUFFER_BIT)
6. Render ur shader

### 頂點著色器 Vertex Shader

* vec4 gl_Position      表頂點位置 (必須值)
* float gl_PointSize    表頂點大小 (pixel 數量)

> gl_Position = vec4(0.0, 0.0, 0.0, 1.0)
> 這當中的`1.0` ，也就是第四的參數值被稱為齊次座標，當其為1.0可以視為三維座標中的點
![](https://i.imgur.com/15nvJIL.png)
[color=#42B983]


### 片元著色器 Fragment Shader

* vec4 gl_FragColor      指定片元顏色 (RGBA格式)

## WebGL 座標系統

![](https://i.imgur.com/yTPhcec.png)

## shader 變數
### attribute 變數
 attribute 被稱為`存除限定符` (storage qualifer)，其必須聲明為全域變數。
 
 ```
     attribute   vec4   a_Position;
     存儲限定符   類型    變數名
```
     
> attribute 變數以 a_ 前綴開頭，uniform 變數以 u_ 前綴開頭，本書以此為編碼風格

![](https://i.imgur.com/UaATSon.png)


* `gl.getAttribLocation(program, name)` 取得 name 參數指定的 attribute 記憶體位置
* `gl.vertexAttrib3f(location, v1, v2, v3)` 將(v1,v2,v3)傳給 location 指向的 attribute 變數

![](https://i.imgur.com/6ApL6pO.png)

> **只有頂點著色器能使用 attribute 變數，使用片元著色器就需要用 uniform 變數或是使用 varying 變數**
> [color=#42B983]




### uniform 變數

![](https://i.imgur.com/8KBnoGx.png)
> uniform 變數用來傳輸給 Vertex Shader、Fragment Shader，`一致的`數據
> [color=#42B983]

```
    uniform    vec4   u_FragColor;
    存儲限定符   類型    變數名
```
###  varying 變數

#### 畫彩色三角形
  頂點著色器中的 v_Color 變量在傳入片元著色器之前經過了內插過程。所以片元著色器中的 v_Color 變量和頂點著色器中的 v_Color 變量實際上並不是一回事,這也正是我們將這種變量稱為“varying”(變化的)變量的原因。
    
![](https://i.imgur.com/qyuDONP.png)

WebGL2 後請用 `in`、`out`
> 在GL3.x中，廢棄了attribute關鍵字（以及varying關鍵字）
> 屬性變量統一用in/out作為前置關鍵字 [color=#42B983]


## 緩衝區物件 Buffer Object 

 緩衝區物件(buffer object)，可以一次向著色器傳入多個頂點，它是WebGL中的一塊記憶體區域，可以一次性地向 BO 填充大量的頂點資料，然後將資料傳給著色器
 
### Buffer Object 使用流程

![](https://i.imgur.com/TldcuuY.png)

1. gl.createBuffer()
2. gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
3. gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
4. gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
5. gl.enableVertexAttribArray(a_Position)

![](https://i.imgur.com/2OkoUAh.png)


### 1. 創建多個緩衝區對象

vertex buffer & size buffer
![](https://i.imgur.com/4Ij2Dow.png)

### 2. 透過 gl.vertexAttribPointer 傳入多項資訊

創建 vertexSize buffer ，將 2x3 與 1x3 的 array 合成為 3x3的 Float32Array，在透過  `gl.vertexAttribPointer` 的 `stride` 與 `offset` 去取資料


###  類型化陣列
 需要用`new`來調用構造函數來建構並傳入參數
 
![](https://i.imgur.com/jkub54m.png)

![](https://i.imgur.com/53Tjcxz.png)

##  基本圖形API
`gl.drawarrys`當中的參數 mode，能繪出7種主要基本圖形
| 基本圖形 | 參數 mode | 描述 |
| ------ | ------ | ----------- |
|點 	|gl.POINTS|	一系列點,繪製在v0,v1,v2......處
|線段 	| gl.LINES |一系列`單獨的線段`,繪製在(v0,v1)、(v2,v3)、(v4,v5)... 處,如果點的個數是奇數,最後一個點將被忽略
|線條 	| gl.LINE_STRIP	|一系列`連接的線段`,被繪製在(v0,vl)、(v1 v2)、(v2,v3)... 處,第1個點是第1條線段的起點,第2個點是第1條線段 的終點和第2條線段的起點......第i(i>1)個點是第i-1 條線 段的終點和第i條線段的起點,以此類推。最後一個點是最後 一條線段的終點
|迴路 	| gl.LINE_LOOP	|一系列`連接的線段`。與 g1.LINE_STRIP 繪製的線條相比,增 加了一條從最後一個點到第1個點的線段。因此,線段被繪製 在(v0,vl)、(v1,v2) (vn,v0)處,其中vn是最後一個 點
|三角形	| gl.TRIANGLES	|一系列`單獨的三角形`,繪製在(v0,v1,v2)、(v3,v4,v5)... 處。如果點的個數不是3的整數倍,最後剩下的一或兩個點將 被忽略
|三角帶 	| gl.TRIANGLE_STRIP	|一系列`條帶狀的三角形`,前三個點構成了第1個三角形,從 第2個點開始的三個點構成了第2個三角形(該三角形與前一個三角形共享一條邊),以此類推。這些三角形被繪製在 (v0,v1,v2)、(v2,v1,v3)、(v2,v3,v4)............處(注意點的順序)'
|三角扇    |	gl.TRIANGLE_FAN	|一系列三角形組成的類似於扇形的圖形。前三個點構成了第1 個三角形,接下來的一個點和前一個三角形的最後一條邊組 成接下來的一個三角形。這些三角形被繪製在(v0,v1,v2)、 (v0,v2,v3), (v0,v3,v4). 處

![](https://i.imgur.com/UxvN66O.png)

##  平移、旋轉、縮放、向量與變換矩陣

### 平移 (向量)
```javascript
    ...
    
    const vs = `
        attribute vec4 a_Position;
        uniform vec4 u_Transition;
        void main() {
            gl_Position = a_Position + u_Transition;
        }
    `;

    var u_Transition = gl.getUniformLocation(gl.program, 'u_Transition');
    gl.uniform4f(u_Transition, .5, .5, 0.0, 0.0);

    ...
```

### 旋轉  (向量)
``` javascript
     /** 旋轉公式 
     *  x2 = x1 * cosθ - y1 * sinθ
     *  y2 = x1 * sinθ + y1 * cosθ
     *  z2 = z1
     * 
     * 轉換矩陣 transformation matrix (以旋轉矩陣為例)
     * [x2,y2,z2] = [ a b c     [ x 
     *                d e f   *   y 
     *                g h i ]     z ]
     * 展開後可得
     * x2 = ax + by + cz
     * y2 = dx + ey + fz
     * z2 = gx + hy + iz
     * 
     * 將其與旋轉公式對應可得
     * [x2,y2,z2] = [ cosθ  -sinθ    0     [ x 
     *                sinθ  cosθ     0   *   y 
     *                0      0       1 ]     z ]
     **/

    const vs = `
        attribute vec4 a_Position;
        uniform float u_CosB, u_SinB;
        void main() {
            gl_Position.x = a_Position.x * u_CosB - a_Position.y * u_SinB;
            gl_Position.y = a_Position.x * u_SinB + a_Position.y * u_CosB;
            gl_Position.z = a_Position.z;
            gl_Position.w = 1.0;
        }
    `;

```

### 變換矩陣
``` javascript
    // 因 按列主序(column major order)行列轉置過來，才能讓 WebGL 由上而下的將原矩陣內數據
    // 儲存到陣列中!
    // 旋轉矩陣
    var xformMatrix = new Float32Array([
        cosB, sinB, 0.0, 0.0,
        -sinB, cosB, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 1.0,
    ]);

    // 平移矩陣
    var xformMatrix = new Float32Array([
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        x, y, z, 1.0,
    ]);

    // 縮放矩陣
    var xformMatrix = new Float32Array([
        sx, 0.0, 0.0, 0.0,
        0.0, sy, 0.0, 0.0,
        0.0, 0.0, sz, 0.0,
        0.0, 0.0, 0.0, 1.0,
    ]);
    
    // 上面的 xformMatrix 選其一套入 其它請註釋掉
    var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
    gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

    const vs = `
        attribute vec4 a_Position;
        uniform mat4 u_xformMatrix;
        void main() {
            gl_Position = u_xformMatrix * a_Position;
        }
    `;
```

### 平移後旋轉

>等式4.1
>    <“平移”後的座標 >=<平移矩陣>×<原始座標>


然後對<平移後的座標進行旋轉。
>等式 4.2
>    <“平移後旋轉”後的座標 >=<旋轉矩陣>×<平移後的座標>


當然你也可以分步計算這兩個等式,但更好的方法是,將4.1代入到4.2中, 把兩個等式組合起來:
>等式 4.3
>    <“平移後旋轉”後的座標>=<旋轉矩陣>×(<平移矩陣>×<原始座標>)


這裡
>    <旋轉矩陣>×(<平移矩陣>×<原始坐標 >)


等於
(注意括號的位置)
>    (< 旋轉矩陣>×<平移矩陣>)×<原始坐標>


最後,我們可以在JavaScript 中計算<旋轉矩陣><平移矩陣>,然後將得到的 矩陣傳入頂點著色器。像這樣,我們就可以把多個變換複合起來了。一個模型經過多個轉換，將多個轉換合成一個等效的轉換，就得到了`模型轉換(model transformation)` 或稱之為 `建模轉換(modeling transformation)`，相對應地模型轉換的矩陣稱之為`模型矩陣(model matrix)`

##  圖形裝配與光柵化

* **圖形裝配** : 將孤立的頂點座標裝配成幾何圖案 , 幾何圖形裝配 (geometric shape assembly)階段將 gl_Position 中的點，畫成 mode 參數中指定的圖案, 又稱為 primitives assembly process。 因為被畫出來的基本圖形又稱之為[圖元(primitives)](https://en.wikipedia.org/wiki/Geometric_primitive)
* **光柵化** : 將裝配的幾何圖案轉化為片元/點陣。EX.[字形光柵化](https://zh.wikipedia.org/zh-tw/%E5%AD%97%E4%BD%93%E5%85%89%E6%A0%85%E5%8C%96)

![](https://i.imgur.com/xnt7fjR.png)


##  紋理 Texture

### 紋理座標

![](https://i.imgur.com/1aAI5Zo.png)

> 紋理座標很通用，因為座標值與圖片本身的尺寸無關。
> EX. 128x128 或 128x256 的右上角紋理座標都是 (1.0,1.0)
> [color=#42B983]


### 紋理映射

![](https://i.imgur.com/GNo8ZkM.png)

> 另一種常用的命名習慣是用 `uv` 當作紋理座標名稱。這邊用 `st` 是因為GLSL ES 也用 st 分量名取得紋理
> [color=#42B983]

### 設定紋理座標

``` javascript
    
    function initVertexBuffers(gl) {
        const verticesTexCoords = new Float32Array([
            -.5, .5, /**/ 0.0, 1.0,
            -.5, -.5, /**/ 0.0, 0.0,
            .5, .5, /**/ 1.0, 1.0,
            .5, -.5, /**/ 1.0, 0.0
        ]);
        const len = verticesTexCoords.length / 4;
        // 1. 創建 buffer
        const vertexTexCoordBuffer = gl.createBuffer();
        if (!vertexTexCoordBuffer) return -1;
        // 2. Binfing buffer
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
        // 3. Setting buffer obj data
        gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

        const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
        // 4. Assign attribute form buffer & enable vertex attribute
        const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
        gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
        gl.enableVertexAttribArray(a_Position)

        const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
        gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
        gl.enableVertexAttribArray(a_TexCoord)

        return len;
    }

```

### 配置和加載紋理

``` javascript
    
    function initTexture(gl, n) {
            // 1. 創建紋理 Buffer
            const texture = gl.createTexture();
            // 取得Shader 的紋理變數
            const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
            // 創建圖片
            const image = new Image();

            image.onload = () => {
                // Y軸反轉
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
                // 2. 開啟 Texture Unit (WebGL 至少可用 8個 Texture Unit)
                gl.activeTexture(gl.TEXTURE0);
                // 3. 綁定紋理 (申請2D 紋理空間)
                gl.bindTexture(gl.TEXTURE_2D, texture);
                // 4. 紋理縮放參數
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                // 5. 寫入紋理資料 到 紋理 Buffer 
                // (image 如果是png，圖像格式&紋理格式 需設定 RGBA)
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
                // 6. 將紋理 Buffer 傳至 shader 變數
                // 將0號紋理傳遞給著色器
                gl.uniform1i(u_Sampler, 0); 

                gl.clearColor(0.0, 0.0, 0.0, 1.0);
                gl.clear(gl.COLOR_BUFFER_BIT);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
            }
            image.src = 'imgs/sky.jpg';
        }

```

#### 反轉 y 軸
![](https://i.imgur.com/PGWUPgb.png)

#### 開啟紋理單元 (gl.activeTexture())
![](https://i.imgur.com/lNGAwG1.png)

![](https://i.imgur.com/smRZ2Mz.png)

#### 綁定紋理 (gl.bindTexture())
![](https://i.imgur.com/0Xm3hx7.png)


#### 配置紋理對象參數 (gl.texParameteri())

``` javascript
    /** gl.texParameteri
     * 將 param 的值賦予給目標紋理物件(target)的 pname 參數上
     * target: gl.TEXTURW_2D / gl.TEXTURE_CUBE_MAP
     * pname: gl.TEXTURE_MAG_FILTER|gl.TEXTURE_MIN_FILTER|gl.TEXTURE_WRAP_S|gl.TEXTURE_WRAP_T
     * param: GLint (OpenGL ES 定義的 number)
     **/
    texParameteri(target: GLenum, pname: GLenum, param: GLint): void;
```

4種效果與預設對應的默認值
![](https://i.imgur.com/BeDB75U.png)

在 gl.TEXTURE_MAG_FILTER / gl.TEXTURE_MIN_FILTER 使用的param常數
![](https://i.imgur.com/iIdmEOf.png)

在 gl.TEXTURE_WRAP_S / gl.TEXTURE_WRAP_T 使用的param常數

![](https://i.imgur.com/XsYrzEp.png)

> ![](https://i.imgur.com/pzNUsiE.png)
> [color=#42B983]

#### 將紋理貼圖分配給紋理物件 (gl.texImage2D())
[MDN - WebGLRenderingContext.texImage2D()](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D)

``` javascript
    gl.texParameteri(target, level, internalformat, format, type, source);
```

* **target** 目標的紋理物件，這邊主要是 `gl.TEXTURE_2D`, `gl.TEXTURE_CUBE_MAP` 三維紋理暫不討論
* **level** 直接用 0 就好了,因為我們沒用到金字塔紋理。
* **internalformat** 表示 the color components in the texture. 在 WebGL 中, internalformat 和 format 必須一致。In WebGL 2, the combinations are listed in [this table](https://registry.khronos.org/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE)
* **format** 表示  the format of the texel data , 具體取值如表5.6所示,你必需根據紋理圖像的格式來選擇這個參數。範例中使用的紋理圖片是JPG格式的,該格式將每個像素用RGB三個分量來表示,所以我們將參數指定為g1.RGB。對其他格式的圖像,如 PNG ,通常使用gl.RGBA, BMP 通常使用g1.RGB,而 g1.LUMINANCE 和g1.LUMINANCE_ALPHA 通常用在灰階圖上等等。


![](https://i.imgur.com/GwGR9kI.png)
> 這裡的流明(luminance)表示我們感知到的物體表面的亮度。通常使用物體表面紅、 綠、藍顏色分量值的加權平均來計算流明。
> [color=#42B983]

* **type** 指定紋理數據類型,見表5.7。通常我們使用 `g1.UNSIGNED BYTE` 數據類型。當然也可以使用其他數據類型,如 `gl.UNSIGNED_SHORT_5_6_5` (將RGB三分量壓縮入16bits 中)。後面的幾種數據格式通常被用來壓縮數據,以**減少瀏覽器加載圖像的時間**。

![](https://i.imgur.com/sxwvR6F.png)

#### 將紋理單元傳遞給 fragment shader (gl.uniform1i())
 一但將紋理圖像傳入了 WebGL,就必須將其傳入片元著色器,映射到圖形的表面上去。
 另外我們使用 uniform 變數來表示紋理,因為紋理圖像不會隨著片元變化。
 
 ![](https://i.imgur.com/u9c5Vgd.png)


#### 從 vs 向 fs 傳輸紋理座標

``` javascript
    const vs = `
        attribute vec4 a_Position;
        attribute vec2 a_TexCoord;
        varying vec2 v_TexCoord;
        void main() {
            gl_Position = a_Position;
            v_TexCoord = a_TexCoord;
        }
    `;
```

#### 在 fs 取 紋理像素顏色

``` javascript
    const fs = `
        precision mediump float; 
        uniform sampler2D u_Sampler;
        varying vec2 v_TexCoord;
        void main() {
            gl_FragColor = texture2D(u_Sampler, v_TexCoord);
        }
    `;
```
![](https://i.imgur.com/YZqTkGO.png)

### 使用多張紋理貼圖

![](https://i.imgur.com/MbQOfVX.png)

### 進階
[Real-Time Rendering 3rd - Texturing](https://github.com/QianMo/Game-Programmer-Study-Notes/tree/master/Content/%E3%80%8AReal-Time%20Rendering%203rd%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/Content/BlogPost05)
## 3D 世界

### 視點 Eye point、 視線 viewing direction、觀察點 look-at point、上方向 up direction

* **視點**: 觀察者所處的位置點
* **視線**: 從視點沿觀察方向的射線
* **觀察方向**: 觀察者自己在的位置相對於場景的方向
* **可視距離**: 視點 到 視線上最遠的點 之間的距離
* **觀察目標點**: 即 注視點
* **上方向**: 視點與觀察點之間連線的向上法線方向

### 視圖矩陣

![](https://i.imgur.com/xPMRlZi.png)

### 可視空間

由水平、垂直、深度視角去定義 **可視空間(view volume)**

#### 2D: 正交投影 3D: 透視投影
![](https://i.imgur.com/lQJgKA2.png)

#### 盒狀可視空間

![](https://i.imgur.com/KhGGEhq.png)

#### 透視投影可視空間

![](https://i.imgur.com/5bpBl3C.png)

可視空間這塊區域又稱之為 [frustum 錐體](https://zh.wikipedia.org/zh-tw/%E9%94%A5%E5%8F%B0)

### 投影矩陣

![](https://i.imgur.com/K1atm8S.png)

### 深度測試 / 隱藏面消除

1. 開啟隱藏面消除功能 `gl.enable(gl.DEPTH_TEST)`
2. 在繪製之前，清除深度緩衝區 `gl.clear(gl.DEPTH_BUFFER_BIT)`

![](https://i.imgur.com/U5kZo4L.png)

![](https://i.imgur.com/3xoAjcG.png)

### 裁剪（Clipping）

在經過投影過程把頂點坐標轉換到裁剪空間後，GPU就可以進行 Clipping。
Clipping 的目的就是把 Camera 看不到的頂點剔除出去，使他們不被渲染到。
判斷頂點是否可以免受裁剪也十分簡單，只需要滿足

![](https://i.imgur.com/9AnLhPP.png)

在 Clipping 後，GPU 需要把頂點映射到屏幕空間，這是一個從三維空間轉換到二維空間的操作，更符合大家對“投影”的理解。

對透視裁剪空間來說，GPU需要對裁剪空間中的頂點執行齊次除法（其實就是將齊次坐標系中的w分量除x、y、z分量），得到頂點的歸一化的設備坐標（Normalized Device Coordinates, NDC），經過齊次除法後，透視裁剪空間會變成一個x、y、z三個坐標都在[-1,1]區間內的立方體。

![](https://i.imgur.com/8vlrTrl.png)

對於正交裁剪空間就要簡單得多，只需要把w分量去掉即可。

此時頂點的x、y坐標就已經很接近於它們在屏幕上所處的位置了，不過還有一個多出來的z分量，不過它也不會被白白丟棄，而是被寫入了深度緩衝（z-buffer）中,可以做一些有關於頂點到Camera距離的計算。

### 深度衝突 (z-fighting)

``` javascript
  function main() {
      ...
    // Enable the polygon offset function
    gl.enable(gl.POLYGON_OFFSET_FILL);
    
    // Draw the triangles
    gl.drawArrays(gl.TRIANGLES, 0, n/2);   // The green triangle
    
    // Set the polygon offset
    gl.polygonOffset(1.0, 1.0);
    
    gl.drawArrays(gl.TRIANGLES, n/2, n/2); // The yellow triangle
  }
```
[詳解](https://juejin.cn/post/7049621665751728142#heading-13)

在 camera 的 near 和 far 的相差非常大的時候，更加容易引起 z-fighting問題。

![](https://i.imgur.com/vHRASmU.png)

## 光照

### 光照原理

所有物體都是由物體反射光線進到瞳孔，進而產生的成像，才能夠看見物體並辨別顏色。
當光照到物體時，有兩個重要現象:

* 根據光源和光線方向，物體表面的明暗程度不一樣
* 根據光源和光線方向﹑物體產生陰影

![](https://i.imgur.com/aA4IH6w.png)

![](https://i.imgur.com/tSvur3U.png)

### 光源類型

**平行光光**: 平行光的光線是相互平行的,平行光具有方向。平行光可以看 作是無限遠處的光源(比如太陽)發出的光。因為太陽距離地球很遠,所以陽光到達地 球時可以認為是平行的。平行光很簡單,可以用一個方向和一個顏色來定義。

**點光源**: 點光源光是從一個點向周圍的所有方向發出的光。點光源光可以用來表 示現實中的燈泡、火焰等。我們需要指定點光源的位置和顏色。光線的方向將根據點光 源的位置和被照射之處的位置計算出來,因為點光源的光線的方向在場景內的不同位置 是不同的。

**環境光**: 環境光(間接光)是指那些經光源(點光源或平行光源)發出後,被牆壁 等物體多次反射,然後照到物體表面上的光。環境光從各個角度照射物體,其強度都是一致的。比如說,在夜間打開冰箱的門,整個廚房都會有些微微亮,這就是環境光的作用。環境光不用指定位置和方向,只需要指定顏色即可。
![](https://i.imgur.com/HZChA7l.png)

### 反射類型

#### 漫反射

#### 環境反射

### 逆轉置矩陣

[轉置矩陣](https://zh.wikipedia.org/zh-tw/%E8%BD%AC%E7%BD%AE%E7%9F%A9%E9%98%B5)
[逆矩陣](https://zh.wikipedia.org/zh-tw/%E9%80%86%E7%9F%A9%E9%98%B5)

## Advanced Techniques


### 用滑鼠使物體旋轉


### 選中一個表面


### 霧化


### Alpha 混和 (Alpha Blending)


### 偵緩衝物件 & 渲染緩衝物件 (Framebuffer Object and Renderbuffer Object)

By default, the WebGL system draws using a color buffer and, when using the hidden
surface removal function, a depth buffer. ***The final image is kept in the color buffer***.

The `framebuffer object` is an alternative mechanism you can use instead of a `color buffer` or a `depth buffer` ( Figure 10.18 ). Unlike a color buffer, the content drawn in a framebuffer object is not directly displayed on the ``<canvas>``. Therefore, you can use it if you want to perform different types of processing before displaying the drawn content. Or you can use it as a texture image. Such a technique is often referred to as `offscreen drawing` .

![](https://i.imgur.com/XPOceNi.png)
![](https://i.imgur.com/tBchu0v.png)

The ***framebuffer object*** has the structure shown in Figure 10.19 and ***supports substitutes for the color buffer and the depth buffer***. As you can see, drawing is not carried out in the framebuffer itself, but in the drawing areas of ***the objects that the framebuffer points to***.

These objects are attached to the framebuffer using its **attachment** function. A **color attachment** specifies the destination for drawing to be a replacement for the color buffer. A **depth attachment** and a **stencil attachment** specify the replacements for the depth buffer and stencil buffer. 

![](https://i.imgur.com/boiAd1X.png)

![](https://i.imgur.com/r7ipfYv.png)


``` javascript
    function initFramebufferObject(gl) {
        var framebuffer, texture, depthBuffer;

        // Define the error handling function
        var error = function() {
            if (framebuffer) gl.deleteFramebuffer(framebuffer);
            if (texture) gl.deleteTexture(texture);
            if (depthBuffer) gl.deleteRenderbuffer(depthBuffer);
            return null;
        }

        // 1. Create a frame buffer object (FBO)
        framebuffer = gl.createFramebuffer();
        if (!framebuffer) {
            console.log('Failed to create frame buffer object');
            return error();
        }

        // 2. Create a texture object and set its size and parameters
        texture = gl.createTexture();
        if (!texture) {
            console.log('Failed to create texture object');
            return error();
        }
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        // Store the texture object into FBO
        framebuffer.texture = texture; 

        // 3. Create a renderbuffer object
        depthBuffer = gl.createRenderbuffer();
        // 4. Bind the renderbuffer object to the target and set its size (must equal to texture size)
        gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);

        // 5. Attach the texture object to the color attachment of the framebuffer object
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        // WebGL COLOR_ATTACHMENT only cab be COLOR_ATTACHMENT0, WebGL2 can be more
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0 /* level (MIPMAP tex level)*/);
        // 6. Attach the renderbuffer object to the depth attachment of the framebuffer object
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

        // 7. Check if FBO is configured correctly
        var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
        if (gl.FRAMEBUFFER_COMPLETE !== e) {
            console.log('Frame buffer object is incomplete: ' + e.toString());
            return error();
        }

        // Unbind the buffer object
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.bindTexture(gl.TEXTURE_2D, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);

        return framebuffer;
    }
```
    
1. createFramebuffer
2. createTexture
3. [gl.createRenderbuffer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/createRenderbuffer)
4. [gl.bindRenderbuffer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindRenderbuffer) / [gl.renderbufferStorage()](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/renderbufferStorage)
5. [gl.bindFramebuffer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindFramebuffer) / [gl.framebufferTexture2D()](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/framebufferTexture2D)
6. [gl.framebufferRenderbuffer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/framebufferRenderbuffer)
7. [gl.checkFramebufferStatus](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/checkFramebufferStatus)
8. draw(webgl, canvas, fbo, plane, cube, angle, texture, viewProjMatrix, viewProjMatrixFBO)

``` javascript
    function draw(gl, canvas, fbo, plane, cube, angle, texture, viewProjMatrix, viewProjMatrixFBO) {
        // Change the drawing destination to FBO
        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
        // Set a viewport for FBO
        gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
        // Clear FBO
        gl.clearColor(0.2, 0.2, 0.4, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        drawTexturedCube(gl, gl.program, cube, angle, texture, viewProjMatrixFBO);   

        // Change the drawing destination to color buffer
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);        
        // Set the size of viewport back to that of <canvas>
        gl.viewport(0, 0, canvas.width, canvas.height);
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        drawTexturedPlane(gl, gl.program, plane, angle, fbo.texture, viewProjMatrix);
    }
```

### 陰影

我們需要兩個著色器來實現陰影
1. 用來計算光源到物體的距離，並用一張 texture 將深度結果記錄起來
2. 根據(1)中計算的距離繪製場景，並將(1)的 texture 結果傳到下一個 shader 中，來繪製畫面中的陰影

這張紋理貼圖就被稱作為 `陰影貼圖 (shadow/depth map) `


而通過陰影貼圖實現陰影的方法就被稱為 `陰影映射 (shadow mapping)`
[陰影映射 (shadow mapping)](https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping) 包括以下兩步:
1. 將視點(eye-point)移到光源處，並運行(1)的 shader 。此時那些"將被繪出"的片元就是被光照射到的，即此格像素上位於最前面的片元。我們不會畫出其片元的顏色，而是傳遞片元中的 z值(深度值)到 "陰影貼圖" (FBO.texture <- the depth buffer.)
2. 將視點(eye-point)移回原位置，運行(2)的 shader 。此時每個片元在光源坐標系下的座標，與陰影貼圖中的Z值比較，前者大於後者，即代表片元處在陰影之中

![](https://i.imgur.com/UEufoA9.png)



![](https://i.imgur.com/eN6xXOx.png)


## 番外 COCOS 渲染流程

director.mainLoop -> director.tick -> root.frameMove(dt)
-> pipeline.render(cameraList) -> (預設) render-pipeline.render(camera)
-> render-flow.render(camera) -> render-stage.render(camera)

<style>
    .markdown-body b {
        font-weight: 700;
    }
    .markdown-body code,
    code {
        color: #99999 !important;
        background-color: #FFD080;
    }
</style>