--- 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>