WebGL學習筆記(二):利用緩衝區動態繪製多個點

上一個程序中,只實現了靜態的單點繪製的,點的位置和顏色都是寫死在着色器中的。如果想要動態的顯示點,將交互添加進去,我們就需要實現使用 JavaScript 向着色器中傳值,這就需要使用到緩衝區。在上一個程序基礎上,實現點擊時生成隨機顏色點的效果。

一、修改着色器代碼

我們需要將着色器中創建幾個變量。着色器中一共有三種變量類型

  1. attribute: 只能在頂點着色器中聲明和使用,常用來存放頂點座標,頂點顏色,法線等。
  2. uniform: 既可以在頂點着色器中使用,也可以在片元着色器中使用,常用來存放變化矩陣,光照參數等。
  3. varying: 用於從頂點着色器向片元着色器中傳遞參數。
//頂點着色器源碼
var vertexShaderSource = `
    //設置浮點數精度爲中等精度
    precision mediump float;
    //接收點在 canvas 座標系上的座標 (x, y)
    attribute vec2 a_Position;
    //接收 canvas 的寬高尺寸
    attribute vec2 a_Screen_Size;
    //接收 JavaScript 顏色值
    attribute vec4 a_Color;
    //將顏色值轉遞給片元着色器
    varying vec4 v_Color;
    void main(){
        //將 canvas 座標轉換至裁剪座標系
        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,
        v_Color = a_Color;
        //聲明要繪製的點的大小。
        gl_PointSize = 5.0;
    }
`

//片元着色器源碼
var fragmentShaderSource = `
    //設置浮點數精度爲中等精度
    precision mediump float;
    //接收頂點着色器中傳來的顏色值
    varying vec4 v_Color;
    void main(){
        gl_FragColor = v_Color;
    }
`

二、JavaScript 操作着色器中的變量

我們需要將 canvas 的長寬信息傳入着色器中,用於將 canvas 座標轉換爲 WebGL 的裁剪座標,在着色器中,我們定義了 a_Screen_Size 用與存放這個信息。在 JavaScript 中,我們首先獲取到這個變量,再通過 vertexAttrib2f 爲它賦值。

//從着色器程序中獲取 a_Screen_Size 的索引
var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size');
//將畫布的寬高傳入 a_Screen_Size
gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height);

三、使用緩衝區爲變量賦值

使用上面一種方法爲變量賦值存在一個問題,不能同時傳入多個值,比如畫三角形時,需要多個點同時參與,這種方法就行不通了。我們這個程序只需要畫點,不使用緩衝區也可以實現,但是每次畫點都需要遍歷一遍存放信息的數組,每次循環都需要在 JavaScript 中爲着色器變量賦一次值,效率比較低。爲了同時傳入多個頂點值和提高效率,我們需要使用緩衝區。
使用緩衝區需要以下步驟:

  1. 創建緩衝區
    創建緩衝區的函數爲 createBuffer(), 我們爲位置信息和顏色信息都創建一個緩衝區。

    var postionBuffer = gl.createBuffer();
    var colorBuffer = gl.createBuffer();
    
  2. 獲取並啓用變量

    var a_Position = gl.getAttribLocation(program, 'a_Position');
    var a_Color = gl.getAttribLocation(program,'a_Color');
    gl.enableVertexAttribArray(a_Position);
    gl.enableVertexAttribArray(a_Color);
    
  3. 綁定緩衝區並設置變量從緩衝區獲取數據的規則
    這一步主要涉及到兩個函數,bindBuffer 和 vertexAttribPointer。bindBuffer 用於綁定緩衝區,即指定當前使用哪一個緩衝區。vertexAttribPointer 則用於設置從緩衝區讀取數據的規則,同時,它將變量與當前緩衝區綁定起來了,這個方法有 6 個參數, 更詳細的內容可以參考 MDN

    1. index: 指定要修改的頂點屬性的索引
    2. size: 指定每個頂點屬性的組成數量, 如 a_Postion 需要兩個屬性(橫縱座標),a_Color 需要四個屬性(RGBA)
    3. type: 指定每個頂點屬性的數據類型, 如 a_Postion 和 a_Color 都是浮點數類型的,所以使用 gl.FLOAT
    4. normalized: 當轉換爲浮點數時是否應該將整數歸一化到特定的範圍
    5. stride: 以字節爲單位指定臉書頂點屬性開始之間的偏移量,我們數據是連續的,所以可以設置爲 0
    6. offset: 指定頂點屬性數組中第一部分的字節偏移量
    gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer);
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, 0, 0);
    
  4. 向緩衝區中填入數據並重新渲染
    之前幾步我們已經準備好了緩衝區,並將變量與緩衝區相關聯,設置好了讀取規則了,現在就需要準備緩衝區裏的數據。
    首先,定義兩個空的數組用於保存位置和顏色的信息

    //創建保存點位置的數組
    var position = [];
    //創建保存顏色信息的數組
    var color = [];
    

    我們再給 canvas 綁定一個點擊事件

    canvas.onclick = function(e){
        //將座標存入數組
        position.push(e.pageX, e.pageY);
        //將隨機生成的顏色存入數組
        color.push(Math.random(), Math.random(), Math.random(), 0);
    
        //向 postionBuffer 緩衝區中寫入數據
        gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);
    
        //向 colorBuffer 緩衝區中寫入數據
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW);
    
        //清空畫布
        gl.clear(gl.COLOR_BUFFER_BIT);
        //重繪畫布上的點
        gl.drawArrays(gl.POINTS, 0, position.length/2);
    }
    

    當我們點擊時,就會將點擊位置加入 position 數組,同時生成一個隨機的顏色存入 color 數組。之後,我們就將數組中的數組傳入緩衝區中。通過 bindBuffer 選擇一個緩衝區,再通過 bufferData 將數組傳入當前緩衝區。緩衝區有了數據之後,我們再進行繪圖,調用 drawArrays 方法,其中第三個參數爲需要畫的點的個數,開始繪圖時,就會按照我們設置的規則依次從緩衝區中取點了。

四. 完整代碼

```
<canvas id="canvas"></canvas>

<script>
    var canvas = document.getElementById('canvas');
    var gl = canvas.getContext('webgl') || canvas.getContext("experimental-webgl");

    //頂點着色器源碼
    var vertexShaderSource = `
        //設置浮點數精度爲中等精度
        precision mediump float;
        //接收點在 canvas 座標系上的座標 (x, y)
        attribute vec2 a_Position;
        //接收 canvas 的寬高尺寸
        attribute vec2 a_Screen_Size;
        //接收 JavaScript 顏色值
        attribute vec4 a_Color;
        //將顏色值轉遞給片元着色器
        varying vec4 v_Color;
        void main(){
            //將 canvas 座標轉換至裁剪座標系
            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,
            v_Color = a_Color;
            //聲明要繪製的點的大小。
            gl_PointSize = 5.0;
        }
    `
    
    //片元着色器源碼
    var fragmentShaderSource = `
        //設置浮點數精度爲中等精度
        precision mediump float;
        //接收頂點着色器中傳來的顏色值
        varying vec4 v_Color;
        void main(){
            gl_FragColor = v_Color;
        }
    `


    //創建着色器
    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    //將源碼分配給着色器
    gl.shaderSource(vertexShader, vertexShaderSource);
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    //編譯着色器程序
    gl.compileShader(fragmentShader);
    gl.compileShader(vertexShader);

    //創建着色器程序
    var program = gl.createProgram();
    
    //將頂點着色器掛載在着色器程序上
    gl.attachShader(program, vertexShader); 
    //將片元着色器掛載在着色器程序上
    gl.attachShader(program, fragmentShader);
    //鏈接着色器程序
    gl.linkProgram(program);
    //使用剛創建好的着色器程序
    gl.useProgram(program);
    
    //設置清屏顏色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    //清空畫布
    gl.clear(gl.COLOR_BUFFER_BIT);


    //傳入畫布長寬信息
    var a_Screen_Size = gl.getAttribLocation(program, 'a_Screen_Size');
    gl.vertexAttrib2f(a_Screen_Size, canvas.width, canvas.height);
    var a_Position = gl.getAttribLocation(program, 'a_Position');
    var a_Color = gl.getAttribLocation(program,'a_Color');
    gl.enableVertexAttribArray(a_Position);
    gl.enableVertexAttribArray(a_Color);

    //爲點位置創建緩衝區,並啓用 a_Postion 變量
    var postionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer);
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
    
    //爲顏色創建緩衝區,並啓用 a_Color 變量
    var colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, 0, 0);

    
    //創建保存點位置的數組
    var position = [];
    //創建保存顏色信息的數組
    var color = [];

    canvas.onclick = function(e){
        //將座標存入數組
        position.push(e.pageX, e.pageY);
        //將隨機生成的顏色存入數組
        color.push(Math.random(), Math.random(), Math.random(), 0);

        //向 postionBuffer 緩衝區中寫入數據
        gl.bindBuffer(gl.ARRAY_BUFFER, postionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(position), gl.STATIC_DRAW);

        //向 colorBuffer 緩衝區中寫入數據
        gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW);

        //清空畫布
        gl.clear(gl.COLOR_BUFFER_BIT);
        //重繪畫布上的點
        gl.drawArrays(gl.POINTS, 0, position.length/2);
    }
</script>
```
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章