【筆記】《WebGL編程指南》學習-第3章繪製和變換三角形(1-繪製多個點)

目標:繪製多個點組成的圖形。本節的例子將會在屏幕上繪製三個紅色小點。
結果:
這裏寫圖片描述


前一章有一個示例程序 ClickedPoints,它在鼠標點擊的位置會致點。ClickedPoints 將所以後的點的座標數據存儲在一個JS數組 g_points[]中,然後使用了一個循環遍歷該數組,每次遍歷就向着着色器傳入一個點,並調用gl.drawArrays()將這個點繪製出來。

for(var i = 0; i < len; i+=2){
    //將點的位置傳遞到變量中
    gl.vertexAttrib3f(a_Position, g_points[i], g_points[i+1], 0.0);

    //繪製點
    gl.drawArrays(gl.POINTS, 0, 1);
}

顯然,這種方法只能繪製一個點。對那些由多個頂點組成的圖形,比如三角形、矩形和立方體來說,你需要一次性地將圖形的頂點全部傳入頂點着色器,然後才能把圖形畫出來。

WebGL 提供了一種很方便的機制,即緩衝區對象,它可以一次性地向着色器傳入多個頂點的數據。緩衝區對象是 WebGL 系統中的一塊內存區域,我們可以一次性地向緩衝區對象中填充大量的頂點數據,然後將這些數據保存在其中,供頂點着色器使用。


示例程序

本例子的流程與第2章中的 ClickPoints.js 與 ColoredPoints.js 的流程基本一致,唯一的不同就是加入了一個新的步驟:設置點的座標信息。

這裏寫圖片描述

MultiPoints.js

//頂點着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'void main(){'+
    'gl_Position=a_Position;'+
    'gl_PointSize=10.0;'+
    '}';

//片元着色器程序
var FSHADER_SOURCE=
    'void main(){'+
    'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+
    '}';

function main() {
    //獲取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //獲取WebGL繪圖上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化着色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //設置頂點位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }


    //指定清空<canvas>顏色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    //清空<canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);

    //繪製三個點
    gl.drawArrays(gl.POINTS, 0, n);
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0, 0.5, -0.5, -0.5, 0.5, -0.5
    ]);
    var n=3; //點的個數

    //創建緩衝區對象
    var vertexBuffer = gl.createBuffer();
    if(!vertexBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //將緩衝區對象保存到目標上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

    //向緩存對象寫入數據
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    //將緩衝區對象分配給a_Postion變量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

    //連接a_Postion變量與分配給它的緩衝區對象
    gl.enableVertexAttribArray(a_Position);

    return n;
}

新加入的函數 initVertexBuffers()的任務是創建定點緩衝區對象,並將多個頂點的數據保存在緩衝區中,然後將緩衝區傳給頂點着色器。

函數的返回值是待繪製頂點的數量,保存在變量n中。注意,如果函數內發生錯誤,返回的負值。

示例程序僅調用了一次 gl.drawArrays()函數就完成了繪圖操作,這與之前的不同。調用這個函數時,本例傳入的第3個參數是 n,而不是1。

//繪製三個點
gl.drawArrays(gl.POINTS, 0, n);

因爲我們在 initVertexBuffers()函數中利用緩衝區對象向頂點着色器傳輸了多個頂點的數據,所以還需要通過第3個參數告訴 gl.drawArrays()函數需要繪製多少個頂點。


使用緩衝區對象

先創建一個緩衝區,然後向其寫入頂點數據,你就能一次性地向頂點着色器中傳入多個頂點的 attribute 變量的數據。

這裏寫圖片描述

在示例程序中,向緩衝區對象寫入數據是一種特殊的JS數組(Float32Array):

 var vertices = new Float32Array([
    0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);

使用緩衝區對象向點點着色器傳入多個頂點的數據,需要遵循以下五個步驟:

  1. 創建緩衝區對象(gl.creatBuffer())。
  2. 綁定緩衝區對象(gl.bindBuffer())。
  3. 將數據寫入緩衝區對象(gl.bufferData())。
  4. 將緩衝區對象分配給一個 attribute 變量(gl.vertexAttribPointer())。
  5. 開啓attribute 變量(gl.enableVertexAttribArray)。

這裏寫圖片描述

創建緩衝區對象

在使用緩衝區對象之前,你必須創建它。

 //創建緩衝區對象
var vertexBuffer = gl.createBuffer();

下面的圖示意了該方法執行前後 WebGL 系統的中間狀態,上面一張圖是執行前的狀態,下面一張圖是執行後的狀態。執行該方法的結果就是,WebGL 系統中多了一個新創建出來的緩衝區對象。

這裏寫圖片描述

下面是 gl.creatBuffer()的函數規範

這裏寫圖片描述

綁定緩衝區

創建緩衝區之後的第2個步驟就是將緩衝區對象綁定到 WebGL 系統中已經存在的“目標”上。這個“目標”表示緩衝區對象的用途(在這裏,就是向頂點着色器提供傳給 attribute 變量的數據),這樣 WebGL 才能正確處理其中的內容:

//將緩衝區對象保存到目標上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

下面是gl.bindBuffer( )的函數規範

這裏寫圖片描述

在示例程序中,我們將緩衝區對象綁定到了 gl.ARRAY_BUFFER 目標上,緩衝區對象中存儲着的關於頂點的數據。在這個函數執行完畢後,WebGL 系統內部狀態發生了改變:

這裏寫圖片描述

接下來,我們就可以向緩衝區對象中寫入數據了。

向緩衝區對象寫入數據

第3步是,開闢空間並向緩衝區中寫入數據。

//向緩存對象寫入數據
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

該方法的效果是,將第2個參數 vertices 中的數據寫入了綁定到第1個參數 gl.ARRAY_BUFFER 上的緩衝區對象。我們不能直接向緩衝區寫入數據,而只能向“目標”寫入數據,所以要向緩衝區寫數據,必須先綁定。該方法執行後,WebGL系統的內部狀態如圖:

這裏寫圖片描述

從上圖你可以看到,定義在JS程序中的數據被寫入了綁定在 gl.ARRAY_BUFFER 上的緩衝區對象。下面是對 gl.bufferData()的範圍。

這裏寫圖片描述

現在我們來看看 gl,bufferData()方法向緩衝區中傳入了什麼數據。該方法使用了一個特殊的數組 vertices 將數據傳給頂點着色器。我們是用 new 運算符,並以<第一個頂點的x座標和y座標><第二個頂點的x座標和y座標>,等等的形式創建這個數組:

如你所見,我們使用了 Float32Array 對象,而不是JS中更常見的 Array 對象。這是因爲JS中通用的數組 Array 是一種通用的類型,即可以在裏面存儲數字也可以存儲字符串,而並沒有對“大量元素都是同一種類型”這種情況進行優化。爲了解決這個問題,WebGL 引入了類型化數組,Float32Array 就是其中之一。

類型化數組

爲了繪製三維圖形,WebGL 通常需要同時處理大量相同類型的數據,例如頂點的座標和顏色數據,爲了優化性能,WebGL 爲每種基本數據類型引入了一種特殊的數組(類型化數組)。瀏覽器事先知道數組中的數據類型,所以處理起來也更加有效率。

例子中的Float32Array 就是一種類型化數組,通常用來存儲頂點的座標或顏色數據。應當牢記,WebGL 中的很多操作都要用到類型化數組,比如 gl.bufferData()中的第2個參數 data。

下表列舉了各種可用的類型化數組。

這裏寫圖片描述

與JS中的 Array 數組相似,類型化數組也有一系列方法和屬性,如下表所示。注意,與普通的 Array 數組不同,類型化數組不支持 push()和 pop()方法。

這裏寫圖片描述

和普通的數組一樣,類型化數組可以通過 new 運算符調用構造函數並傳入數據而被創造出來。注意,創建類型化數組的唯一方法就是使用 new 運算符,不能使用 []運算符。

此外,你也可以通過指定數組元素的個數來創建一個空的類型化數組,例如:

var vertices = new Float32Array(4);

到目前爲止,你就完成了建立和使用穿衝去的前三個步驟(創建緩衝區,綁定緩衝區對象到目標,向緩衝區對象中寫入數據)。我們們來看看在接下倆的兩步中,WebGL 是如何真正使用緩衝區來進行繪圖的。

向緩衝區對象分配給 attribute 變量

如第2章所述,你可以使用 gl.vertexAttrib[1234]f系列函數爲 attribute 變量分配值。但是,這些方法一次只能向 attribute 變量分配一個值。而現在,你需要將整個數組中的所有值一次性地分配給一個 attribute 變量。

gl.vertexAttribPointer()方法解決了這個問題,它可以將整個緩衝區對象(實際上是緩衝區對象的引用或指針)分配給 attribute 變量。示例程序將緩衝區對象分配給 attribute 變量 a_Position。

//將緩衝區對象分配給a_Postion變量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

gl.vertexAttribPointer()的規範如下所示:
這裏寫圖片描述

執行完第4步後,我們就將整個緩衝區對象分配給了 attribute 變量,爲 WebGl 繪圖進行的準備工作(即向 location 處的 attribute 變量傳入緩衝區)就差最後一步了:進行最後的“開啓”,使這次分配真正生效,如下圖所示:

這裏寫圖片描述

開啓 attribute 變量

爲了使頂點着色器能夠訪問緩衝區內的數據,我們需要使用 gl.enableVertexAttribArray()方法來開啓 attribute 變量。

//連接a_Postion變量與分配給它的緩衝區對象
gl.enableVertexAttribArray(a_Position);

注意,雖然函數的名稱似乎表示該函數是用來處理“頂點函數”的,但實際上它處理的對象是緩衝區。

這裏寫圖片描述

當你執行 gl.enableVertexAttribArray()並傳入一個已經分配好緩衝區的 attribute 變量後,我們就開啓了該變量,也就是說,緩衝區對象和 attribute 變量之間的連線就真正建立起來了。

這裏寫圖片描述

現在你只需要orange頂點着色器運行起來,它會自動將緩衝區中的頂點畫出來。如第2章,你使用 gl.drawArrays()方法繪製了一個點,現在你要畫多個點,所用的方法仍是 gl.drawArrays()方法,但使用的是方法中的第2個和第3個參數。

注意,開啓 atrribute 變量後,你就不能再用gl.vertexAtrrib[1234]f 向它傳數據了,除非你顯式地關閉該 attribute 變量。實際上,你無法同時使用這兩個函數。

gl.drawArrays()的第2個和第3個參數

在對 gl.drawArray()作進一步詳細說明之前,看一下他的規範:

這裏寫圖片描述

這個示例程序如下調用這個方法:

gl.drawArrays(gl.POINTS, 0, n);

當程序運行到這個方法時,實際上頂點着色器執行了count次,我們通過存儲在緩衝區中的頂點座標數據被依次傳給 attribute 變量,如下圖所示

這裏寫圖片描述

注意,每次執行頂點着色器,a_Postion 的 z 和 w 分量值都會被自動設爲0.0或1.0,因爲 a_Position 需要4個分量,而你只提供了兩個。


運行出結果:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章