WebGL 3D 入門系列:繪製漸變三角形 --- 深刻理解緩衝區

WebGL 3D 入門系列 --- 繪製漸變三角形:深刻理解緩衝區

本節內容來自於小冊 WebGL 入門與實踐javascript

上節帶領你們學習了基本三角形圖元的繪製過程,以及如何使用緩衝區向着色器傳遞多個數據,但上節只演示了往着色器傳遞座標這一種數據,本節經過繪製漸變三角形,講解一下如何經過緩衝區向着色器傳遞多種數據。html

目標

本節經過一個鼠標每點擊三次便會繪製一個漸變三角形的示例,帶你們深刻理解緩衝區的用法,最終效果以下圖所示: 前端

經過本節學習,你將會掌握以下內容:java

  • 頂點數據在 buffer 中的排布方式。
  • 切換 buffer 時,bindBuffer 的重要性。
  • 使用多個 buffer 讀取多種頂點數據。
  • 使用單個 buffer 讀取多種頂點數據。
  • 如何實現漸變效果。

漸變三角形

上節咱們實現的是單色三角形,經過在片元着色器中定義一個 uniform 變量,接收 JavaScript 傳遞過去的顏色值來實現。那漸變三角形的處理與單色三角形有何不一樣呢?git

漸變三角形顏色不單一,在頂點與頂點之間進行顏色的漸變過渡,這就要求咱們的頂點信息除了包含座標,還要包含顏色。這樣在頂點着色器以後,GPU 根據每一個頂點的顏色對頂點與頂點之間的顏色進行插值,自動填補頂點之間像素的顏色,因而造成了漸變三角形。github

那既然咱們須要爲每一個頂點傳遞座標信息和顏色信息,所以須要在頂點着色器中額外增長一個 attribute 變量a_Color,用來接收頂點的顏色,同時還須要在頂點着色器和片元着色器中定義一個 varying 類型的變量v_Color,用來傳遞頂點顏色信息。web

着色器

  • 依然從頂點着色器開始,頂點着色器新增一個 attribute 變量,用來接收頂點顏色。
//設置浮點數精度爲中等精度。
    precision mediump float;
    //接收頂點座標 (x, y)
    attribute vec2 a_Position;
    //接收瀏覽器窗口尺寸(width, height)
    attribute vec2 a_Screen_Size;
    //接收 JavaScript 傳遞的頂點顏色
    attribute vec4 a_Color;
    //傳往片元着色器的顏色。
    varying vec4 v_Color;
    void main(){
      vec2 position = (a_Position / a_Screen_Size) * 2.0 - 1.0;
      position = position * vec2(1.0,-1.0);
      gl_Position = vec4(position, 0, 1);
      v_Color = a_Color;
    }

複製代碼
  • 片元着色器

片元着色器新增一個 varying 變量 v_Color,用來接收插值後的顏色。編程

//設置浮點數精度爲中等。
    precision mediump float;
    //接收 JavaScript 傳過來的顏色值(rgba)。
    varying vec4 v_Color;
    void main(){
      vec4 color = v_Color / vec4(255, 255, 255, 1);
      gl_FragColor = color;
   }
複製代碼

咱們的着色器部分仍是和以前同樣簡單,只是在頂點着色器中增長了頂點顏色這一變量。canvas

接下來咱們用 JavaScript 向着色器傳遞數據。數組

JavaScript 部分

用緩衝區向着色器傳遞數據有兩種方式:

  • 利用一個緩衝區傳遞多種數據。
  • 另外一種是利用多個緩衝區傳遞多個數據。

上節繪製三角形的時候咱們給頂點着色器傳遞的只是座標信息,而且只用了一個 buffer,本節示例,咱們除了傳遞頂點的座標數據,還要傳遞頂點顏色。 按照正常思路,咱們能夠建立兩個 buffer,其中一個 buffer 傳遞座標,另一個 buffer 傳遞顏色。

建立兩個 buffer,將 a_PositionpositionBuffer 綁定,a_ColorcolorBuffer 綁定,而後設置各自讀取 buffer 的方式。

請謹記:程序中若是有多個 buffer 的時候,在切換 buffer 進行操做時,必定要經過調用 gl.bindBuffer 將要操做的 buffer 綁定到 gl.ARRAY_BUFFER 上,這樣才能正確地操做 buffer 。您能夠將 bindBuffer 理解爲一個狀態機,bindBuffer 以後的對 buffer 的一些操做,都是基於最近一次綁定的 buffer 來進行的。

如下 buffer 的操做須要在綁定 buffer 以後進行:

  • gl.bufferData:傳遞數據。
  • gl.vertexAttribPointer:設置屬性讀取 buffer 的方式。
方式一:多個 buffer 傳遞

咱們使用一個 buffer 傳遞座標信息,另外一個 buffer 傳遞顏色信息。

// 建立 座標信息 buffer
var positionBuffer = gl.createBuffer();
// 將當前 buffer 設置爲 postionBuffer,接下來對 buffer 的操做都是針對 positionBuffer 了。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 設置 a_Position 變量讀取 positionBuffer 緩衝區的方式。
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(
      a_Position, size, type, normalize, stride, offset);
      
// 建立 顏色信息 buffer
var colorBuffer = gl.createBuffer();
// 將當前 buffer 設置爲 postionBuffer,接下來對 buffer 的操做都是針對 positionBuffer 了。
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
// 設置 a_Position 變量讀取 positionBuffer 緩衝區的方式。
var size = 4;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(
      a_Color, size, type, normalize, stride, offset);

複製代碼

gl.vertexAttribPointer( a_Color, size, type, normalize, stride, offset)。這個方法比較重要,上節已經向你們詳細介紹了,若是還不太明白的,能夠再次回顧下上節內容。

咱們發現,上面代碼對 buffer 的操做有些冗餘,咱們仍是提取出一個方法 createBuffer 放到 webgl-helper.js,減小重複編碼,以後咱們對 buffer 的一系列調用只須要以下兩句就能夠了:

var positionBuffer = createBuffer(gl, a_Position, { size: 2});
var colorBuffer = createBuffer(gl, a_Color, { size: 4});

複製代碼

假如咱們頂點座標數組中有四個頂點 8 個元素【30, 30, 30, 40, 40, 30, 20, 0】,頂點着色器中的 a_Position 屬性在讀取頂點座標信息時,以 2 個元素爲一組進行讀取:

又假如咱們頂點顏色數組中有兩個頂點 8 個元素 【244, 230, 100, 1, 125, 30, 206, 1】,那麼頂點着色器中的 a_Color 屬性在讀取頂點顏色信息時,以 4 個元素(r, g, b, a)爲一組進行讀取,以下圖所示。

以多少元素做爲一個頂點信息進行讀取的設置,是在調用 gl.vertexAttribPointer 時設置的 size 參數值。

言歸正傳,接下來咱們爲 canvas 添加點擊事件:

canvas.addEventListener('click', e => {
    var x = e.pageX;
    var y = e.pageY;
    positions.push(x, y);
    //隨機一種顏色
    var color = randomColor();
    //將隨機顏色的 rgba 值添加到頂點的顏色數組中。
    colors.push(color.r, color.g, color.b, color.a);
    //頂點的數量是 3 的整數倍時,執行繪製操做。
    if (positions.length % 6 == 0) {
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.DYNAMIC_DRAW);
        render(gl);
    }
})
複製代碼

萬事俱備,只欠繪製:

function render(gl) {
      //用設置的清空畫布顏色清空畫布。
      gl.clear(gl.COLOR_BUFFER_BIT);
      if (positions.length <= 0) {
        return;
      }
      //繪製圖元設置爲三角形。
      var primitiveType = gl.TRIANGLES;
      //由於咱們要繪製三個點,因此執行三次頂點繪製操做。
      gl.drawArrays(primitiveType, 0, positions.length / 2);
    }
複製代碼

至此,三角形的漸變效果就實現啦。

另外一種思路:使用 1 個 buffer 同時傳遞座標和顏色信息

常規思路使用多個 buffer 傳遞多種數據(座標和顏色),咱們再演示另一種思路:使用 1 個 buffer 同時傳遞多種數據。

着色器部分的代碼和上面的同樣,無需改動,改動的主要部分是 JavaScript 程序。

首先,咱們依然是建立 buffer,只不過此次是建立一個 buffer

var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
複製代碼

建立完 buffer,接下來設置讀取 buffer 的方式,咱們有兩個屬性 a_Positiona_Color,因爲咱們只有一個 buffer,該 buffer 中既存儲座標信息,又存儲顏色信息,因此兩個屬性須要讀取同一個 buffer

咱們能夠看到,一個頂點信息佔用 6 個元素,前兩個元素表明座標信息,後四個元素表明顏色信息,因此在下面設置屬性讀取 buffer 方式時,a_Colora_Position 的設置會有不一樣:

  • a_Position:座標信息佔用 2 個元素,故 size 設置爲 2。 座標信息是從第一個元素開始讀取,偏移值爲 0 ,因此 offset 設置爲 0.

  • a_Color:因爲 color 信息佔用 4 個元素,因此 size 設置爲 4 。 color 信息是在座標信息以後,偏移兩個元素所佔的字節(2 * 4 = 8)。因此,offset 設置爲 8。

  • stride:表明一個頂點信息所佔用的字節數,咱們的示例,一個頂點佔用 6 個元素,每一個元素佔用 4 字節,因此,stride = 4 * 6 = 24 個字節。

gl.vertexAttribPointer(
      a_Color, 4, gl.FLOAT, false, 24, 8);
      
gl.vertexAttribPointer(
      a_Position, 2, gl.FLOAT, false, 24, 0);

複製代碼

canvas 的點擊事件也有所不一樣,一個頂點佔用 6 個元素,三個頂點組成一個三角形,因此咱們的 positions 的元素數量必須是 18 的整數倍,才能組成一個三角形:

canvas.addEventListener('click', e => {
      var x = e.pageX;
      var y = e.pageY;
      positions.push(x);
      positions.push(y);
      //隨機出一種顏色
      var color = randomColor();
      //將隨機顏色的 rgba 值添加到頂點的顏色數組中。
      positions.push(color.r, color.g, color.b, color.a);
      //頂點的數量是 18 的整數倍時,執行繪製操做。
      if (positions.length % 18 == 0) {
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
        render(gl);
      }
    })
複製代碼

實現效果和上面操做多緩衝區的方式同樣,可是單緩衝區不只減小了緩衝區的數量,並且減小了傳遞數據的次數以及複雜度。

回顧

至此,咱們對緩衝區的講解就結束了,本節所講知識點和上節基本相似,不一樣點在於用單個緩衝區傳遞多類數據時,gl.vertexAttribPointer 各個參數如何設置,理解這點對咱們之後編程十分有用,但願你們課下多多練習,深入理解它的用法。

到目前爲止,咱們掌握了三角形的繪製方法,接下來學習怎樣用三角形構建其餘圖形。

下一節咱們將從簡單平面開始:先用三角形構建一個矩形。

小冊

這一系列的內容來自於小冊 WebGL 3D 入門與實踐,若是你們對進階知識感興趣,能夠到小冊中去學習:小冊:WebGL 3D 入門與實踐

小冊內容除了包含 WebGL 相關的基礎練習,還包括 3D 圖形概念與相關數學的原理與推導,旨在幫助你們創建圖形學的技術輪廓。這部分圖形學知識獨立於 WebGL,除了能夠適用於 WebGL,還適用於 OpenGL 等。

固然,本小冊主要目的仍是幫助 Web 前端同窗學習 3D 技術,除了介紹適用 WebGL 實現 3D 效果之外,還對 CSS3 中的 3D 技術相關屬性進行了深刻剖析,並演示了與數學庫的結合使用,但願可以讓前端同窗不只僅侷限於通常的二維平面開發,也可以在 3D 開發上更近一步~

相關文章
相關標籤/搜索