JavaScript WebGL 繪製一個面

XXHolic發表於2021-12-20

引子

JavaScript WebGL 基礎疑惑點之後進行了一些優化,然後嘗試繪製常見二維的面。

WebGL 中幾何體最終都是由三角形構成,由三角形切入比較合適。

繪製三角形

這是示例,基於繪製一條直線主要的變化有:

  • 頂點
  • 繪製圖元

頂點

三角形有三個頂點,在基礎疑惑點中知道座標系是右手座標系,個人習慣描述頂點的順序以圖形中心為原點,從第一象限到第四象限。

  let vertices = [
    0.5, 0.5, 0.0, // 第一象限
    -0.5, 0.5, 0.0, // 第二象限
    -0.5, -0.5, 0.0, // 第三象限
  ]; // 三角形

繪製圖元

這次繪製的是一個面,drawArrays 中繪製模式變為 gl.TRIANGLES 。順便看看圖元的幾種模式。

  • gl.POINTS : 繪製一系列點。
  • gl.LINES :繪製一系列單獨線段,每兩個點作為端點,線段之間不連線。例如有頂點 A、B、C、D、E、F,就會得到了三條線段。

94-lines

  • gl.LINE_STRIP : 繪製一系列線段,上一點連線下一點。

94-line-strip

  • gl.LINE_LOOP : 繪製一系列線段,上一點連線下一點,並且最後一點與第一個點相連。

94-line-loop

  • gl.TRIANGLES : 繪製一系列三角形,每三個點作為頂點。例如有 6 個頂點 A、B、C、D、E、F,就會繪製 2 個三角形: ABC 和 DEF。

94-triangles

  • gl.TRIANGLE_STRIP : 用來繪製有共享邊的三角形。從第二個三角形開始,每次讀取一個頂點,並利用前面的末尾兩個頂點構成一個三角形,以此類推。例如有 6 個頂點 A、B、C、D、E、F,就會繪製 4 個三角形: ABC 和 BCD 和 CDE 和 DEF。

94-triangle-strip

  • gl.TRIANGLE_FAN : 繪製有共享邊的三角形。從第二個三角形開始,每次讀取一個頂點,並利用首個頂點和之前最後一個頂點來構成一個三角形,以此類推。例如有 6 個頂點 A、B、C、D、E、F,就會繪製 4 個三角形: ABC 和 ACD 和 ADE 和 AEF。

94-triangle-fan

執行過程

這裡有一個繪製三角形執行過程視覺化,結合看看有助於加深理解。

高清處理

上面的示例,在高清螢幕中會出現明顯的模糊和鋸齒,但跟處理 2d 上下文的模糊又有些不一樣。最主要的一個區別是 WebGL 中需要用 viewport 方法指定從標準裝置到視窗座標的對映變換。詳細可以見這篇文章裡面的解釋。

這是高清示例

  function WebGLHD(w = 300, h = 150) {
    const ratio = window.devicePixelRatio || 1;
    const canvas = document.createElement("canvas");
    const context = canvas.getContext("webgl");
    // 高清螢幕模糊問題處理
    canvas.width = w * ratio; // 實際渲染畫素
    canvas.height = h * ratio; // 實際渲染畫素
    canvas.style.width = `${w}px`; // 控制顯示大小
    canvas.style.height = `${h}px`; // 控制顯示大小
    context.viewport(0, 0, context.canvas.width, context.canvas.height);
  }

繪製矩形

前面有說 WebGL 中幾何體最終都是由三角形構成,在繪製多邊形的時候需要分解為多個三角形。

這是示例,一個矩形可以分為兩個三角形:

  let vertices = [
    0.5, 0.5, 0.0,
    -0.5, 0.5, 0.0,
    -0.5, -0.5, 0.0, // 第一個三角形
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.5, 0.5, 0.0, // 第二個三角形
  ]; // 矩形

可以發現有一條邊是公共,這個時候可以索引緩衝區物件減少冗餘的資料。

索引緩衝物件

索引緩衝物件全稱是 Index Buffer Object(IBO),通過索引的方式複用已有的資料。

基於上面正方形的示例,主要的變化有以下幾方面:

  • 資料
  • 緩衝
  • 繪製

資料

頂點位置資料只需要 4 個就足夠了,公共資料使用索引代替。

  const vertices = [
    0.5, 0.5, 0.0, // 第 1 個頂點
    -0.5, 0.5, 0.0, // 第 2 個頂點
    -0.5, -0.5, 0.0, // 第 3 個頂點
    0.5, -0.5, 0.0, // 第 4 個頂點
  ]; // 矩形

索引資料跟上面提示到的圖元繪製模式有關。

繪製模式為 gl.TRIANGLES 時,兩個三角形是獨立的,索引資料如下:

const indexData = [
  0, 1, 2, // 對應頂點位置資料中 1、2、3 頂點的索引
  0, 2, 3, // 對應頂點位置資料中 1、3、4 頂點的索引
]

繪製模式為 gl.TRIANGLE_STRIP 時,利用前一個三角形末尾的兩個頂點構建三角形:

const indexData = [
  1, 0, 2, 3 // 繪製時,先取索引 1、0、2 的位置資料繪製第一個三角形,然後再取索引 0、2、3 的位置資料 繪製第二個三角形
]

繪製模式為 gl.TRIANGLE_FAN 時,利用第一個頂點,和前一個三角形末尾的一個頂點,加上新讀取的頂點構建三角形:

const indexData = [
  0, 1, 2, 3 // 繪製時,先取索引 0、1、2 的位置資料繪製第一個三角形,然後再取索引 0、2、3 的位置資料 繪製第二個三角形
]

緩衝

索引的資料需要緩衝到對應的變數才能使用。

/**
 * 緩衝索引資料
 * @param {*} gl WebGL 上下文
 * @param {*} data 索引資料
 */
function setIndexBuffers(gl, data) {
  // 建立空白的緩衝物件
  const buffer = gl.createBuffer();
  // 繫結目標
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
  // WebGL 不支援直接使用 JavaScript 原始陣列型別,需要轉換
  const dataFormat = new Uint16Array(data);
  // 初始化資料儲存
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, dataFormat, gl.STATIC_DRAW);
}

繪製

使用了索引緩衝物件,需要用 drawElements 方法來替代 drawArrays。該方法多了一個 type 引數,指的是索引緩衝資料的型別,有下面的值可取:

  • gl.UNSIGNED_BYTE
  • gl.UNSIGNED_SHORT

前面緩衝索引資料型別轉換為了 Uint16Array ,這裡應該使用 gl.UNSIGNED_SHORT

三種方式示例如下:

參考資料

相關文章