[WebGL入門]十四,繪製多邊形

lufy發表於2014-08-08

注:文章譯自http://wgld.org/,原作者杉本雅広(doxas),文章中如果有我的額外說明,我會加上[lufy:],另外,鄙人webgl研究還不夠深入,一些專業詞語,如果翻譯有誤,歡迎大家指正。



這是本次的demo的執行結果

繪製流程

這次終於該繪製多邊形了,之前的文章(十一,著色器的編譯和連線)中介紹了HTML,頂點著色器和片段著色器,這次看一下javascript從開始到最終的全部處理。
如果前兩篇文章介紹的內容完全理解的話,這次的內容也應該不難了。或許會有不容易理解的地方,不要著急。
首先,我先貼出所有程式碼,然後在慢慢說明。

>script.js的全部程式碼

onload = function(){
    // canvas物件獲取
    var c = document.getElementById('canvas');
    c.width = 300;
    c.height = 300;

    // webgl的context獲取
    var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
    
    // 設定canvas初始化的顏色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    
    // 設定canvas初始化時候的深度
    gl.clearDepth(1.0);
    
    // canvas的初始化
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    // 頂點著色器和片段著色器的生成
    var v_shader = create_shader('vs');
    var f_shader = create_shader('fs');
    
    // 程式物件的生成和連線
    var prg = create_program(v_shader, f_shader);
    
    // attributeLocation的獲取
    var attLocation = gl.getAttribLocation(prg, 'position');
    
    // attribute的元素數量(這次只使用 xyz ,所以是3)
    var attStride = 3;
    
    // 模型(頂點)資料
    var vertex_position = [
         0.0, 1.0, 0.0,
         1.0, 0.0, 0.0,
        -1.0, 0.0, 0.0
    ];
    
    // 生成VBO
    var vbo = create_vbo(vertex_position);
    
    // 繫結VBO
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    
    // 設定attribute屬性有効
    gl.enableVertexAttribArray(attLocation);
    
    // 新增attribute屬性
    gl.vertexAttribPointer(attLocation, attStride, gl.FLOAT, false, 0, 0);
    
    // 使用minMatrix.js對矩陣的相關處理
    // matIV物件生成
    var m = new matIV();
    
    // 各種矩陣的生成和初始化
    var mMatrix = m.identity(m.create());
    var vMatrix = m.identity(m.create());
    var pMatrix = m.identity(m.create());
    var mvpMatrix = m.identity(m.create());
    
    // 檢視變換座標矩陣
    m.lookAt([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0], vMatrix);
    
    // 投影座標變換矩陣
    m.perspective(90, c.width / c.height, 0.1, 100, pMatrix);
    
    // 各矩陣想成,得到最終的座標變換矩陣
    m.multiply(pMatrix, vMatrix, mvpMatrix);
    m.multiply(mvpMatrix, mMatrix, mvpMatrix);
    
    // uniformLocation的獲取
    var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix');
    
    // 向uniformLocation中傳入座標變換矩陣
    gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
    
    // 繪製模型
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    
    // context的重新整理
    gl.flush();
    
    // 生成著色器的函式
function create_shader(id){
    // 用來儲存著色器的變數
    var shader;
    
    // 根據id從HTML中獲取指定的script標籤
    var scriptElement = document.getElementById(id);
    
    // 如果指定的script標籤不存在,則返回
    if(!scriptElement){return;}
    
    // 判斷script標籤的type屬性
    switch(scriptElement.type){
        
        // 頂點著色器的時候
        case 'x-shader/x-vertex':
            shader = gl.createShader(gl.VERTEX_SHADER);
            break;
            
        // 片段著色器的時候
        case 'x-shader/x-fragment':
            shader = gl.createShader(gl.FRAGMENT_SHADER);
            break;
        default :
            return;
    }
    
    // 將標籤中的程式碼分配給生成的著色器
    gl.shaderSource(shader, scriptElement.text);
    
    // 編譯著色器
    gl.compileShader(shader);
    
    // 判斷一下著色器是否編譯成功
    if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
        
        // 編譯成功,則返回著色器
        return shader;
    }else{
        
        // 編譯失敗,彈出錯誤訊息
        alert(gl.getShaderInfoLog(shader));
    }
}
    
    // 程式物件的生成和著色器連線的函式
function create_program(vs, fs){
    // 程式物件的生成
    var program = gl.createProgram();
    
    // 向程式物件裡分配著色器
    gl.attachShader(program, vs);
    gl.attachShader(program, fs);
    
    // 將著色器連線
    gl.linkProgram(program);
    
    // 判斷著色器的連線是否成功
    if(gl.getProgramParameter(program, gl.LINK_STATUS)){
    
        // 成功的話,將程式物件設定為有效
        gl.useProgram(program);
        
        // 返回程式物件
        return program;
    }else{
        
        // 如果失敗,彈出錯誤資訊
        alert(gl.getProgramInfoLog(program));
    }
}
    
    // 生成VBO的函式
function create_vbo(data){
    // 生成快取物件
    var vbo = gl.createBuffer();
    
    // 繫結快取
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    
    // 向快取中寫入資料
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
    
    // 將繫結的快取設為無效
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    
    // 返回生成的VBO
    return vbo;
}

};

初始化處理

那麼,從上到下按順序來看一下吧。
首先,前提是在頁面載入的同時執行script.js中的所有程式碼,所以,將程式碼全都寫進了onload函式中。之後,獲取canvas物件開始處理。

>canvas的獲取和初始化如下

    // canvas物件獲取
    var c = document.getElementById('canvas');
    c.width = 300;
    c.height = 300;

    // webgl的context獲取
    var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
    
    // 設定canvas初始化的顏色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    
    // 設定canvas初始化時候的深度
    gl.clearDepth(1.0);
    
    // canvas的初始化
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
最先開始做的是獲取canvas物件,並設定canvas的大小為寬300px,高300px。然後獲取WebGL的context,以及設定清除畫面所使用的顏色。
接著是設定清除的深度。使用clearDepth函式可以設定清除畫面的時候的深度,以前的例子中只是對顏色進行了初始化,所以只使用了clearColor函式,其實處理三維空間的時候,深度相關的情報也需要清除,所以就用到了clearDepth函式。
同樣,clear函式中的引數也做了相應的變化,不光是顏色,還包含深度,所以增加了gl.DEPTH_BUFFER_BIT常量。


著色器和程式物件的生成

生成頂點著色器和片段著色器,並使用程式物件進行連線。在以前的文章(著色器的編譯和連線)中進行過詳細的說明,可以參考一下。

>著色器和程式物件相關的處理

    // 頂點著色器和片段著色器的生成
    var v_shader = create_shader('vs');
    var f_shader = create_shader('fs');
    
    // 程式物件的生成和連線
    var prg = create_program(v_shader, f_shader);
    
    // attributeLocation的獲取
    var attLocation = gl.getAttribLocation(prg, 'position');
    
    // attribute的元素數量(這次只使用 xyz ,所以是3)
    var attStride = 3;
注意這裡出現的函式,並不是WebGL中內建的函式,而是自己寫的。具體點就是說create_shader和create_program都是自己寫的函式。
create_shader函式的引數是HTML中的id字串,從script標籤中獲取到著色器程式碼,生成著色器物件並返回。上面的例子中,是根據id是vs和fs的這兩個script標籤中的內容,來生成頂點著色器和片段著色器。
著色器生成後,作為引數傳給create_program函式,返回值就是程式物件,然後用變數儲存起來。在create_program函式中,生成程式物件,並將著色器進行連線。
接著,定義了兩個變數,attLocation和attStride,用來儲存在後面向頂點著色器中傳入資料時的必要的內容。
話說回來,看一下以前的文章中的程式碼中是如何使用生成的著色器的,然後就明白為什麼要定義這兩個變數了吧。

>使用頂點著色器的程式碼

attribute vec3 position;
uniform   mat4 mvpMatrix;
void main(void){
    gl_Position = mvpMatrix * vec4(position, 1.0);
}
這次使用著色器的時候,只使用了一個attribute變數,當然就是position了,這個position變數定義成了vec3,說明是擁有3各元素的向量。
這裡的重點就是[利用一個叫做position的attribute變數]和[這個變數是vec3型別]這兩點內容。其實利用attribute變數向著色器中傳遞資料的時候,必要的兩個情報是這個資料是第幾個attribute變數,以及這個變數由幾個元素組成的。
也就是說,變數attLocation是為了儲存這個資料是第幾個,變數attStride是為了儲存這個資料是由幾個元素組成的。
WebGL的context中的getAttribLocation函式的兩個引數,第一個引數是程式物件,第二個引數是想要獲取的attribute變數的變數名。返回值是數值型,就是向頂點著色器傳遞資料時的序號。就是告訴我們是第幾個變數。這個數值後面會使用到。
變數attStride表示頂點著色器中的attribute變數position是一個有三個元素的vec3型別的變數。這個後面也會用到。


頂點快取的生成和通知

繼續吧,接著是定義模型資料,並生成VBO,然後為了將VBO傳給頂點著色器,進行繫結並傳入資料。

>VBO生成的相關處理

    // 模型(頂點)資料
    var vertex_position = [
         0.0, 1.0, 0.0,
         1.0, 0.0, 0.0,
        -1.0, 0.0, 0.0
    ];
    
    // 生成VBO
    var vbo = create_vbo(vertex_position);
    
    // 繫結VBO
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    
    // 設定attribute屬性有効
    gl.enableVertexAttribArray(attLocation);
    
    // 新增attribute屬性
    gl.vertexAttribPointer(attLocation, attStride, gl.FLOAT, false, 0, 0);
以前的文章(十二,模型資料和頂點屬性)中也有過詳細說明,將頂點資料定義為簡單的陣列,然後根據這個陣列資料,用自定義函式create_vbo來生成頂點快取(VBO)。
為了將頂點快取和頂點著色器中的attribute變數聯絡起來,首先要向WebGL中繫結VBO。然後使用剛才獲取到的attribute屬性的序號,將這個attribute屬性設定為有效。使用WebGL的函式enableVertexAttribArray可以讓指定屬性變為有效。
接著,使用WebGL函式vertexAttribPointer向著色器中寫入資料。剛才定義的兩個變數attLocation和attStride,在這裡也用到了。vertexAttribPointer函式的第一個引數是attribute變數的序號,第二個引數是元素數,第三個引數是指定了資料型別的內建常量。gl.FLOAT是一個表示浮點型的常量。第四~第六各引數基本上是不怎麼變的,根據記憶體有時候會傳入其他的內容。
需要注意的是,執行vertexAttribPointer的時候,VBO物件必須先進行繫結,哪個VBO以及和它關聯的attribute屬性是必須的,所以不要忘了先將VBO跟WebGL進行繫結。


座標變換矩陣的生成和通知

接著,是準備渲染用的座標變換矩陣。這裡使用了本網站製作的矩陣計算的庫minMatrix.js,minMatrix.js的基本的使用方法在上一篇文章中(minMatrix.js和座標變換矩陣)已經介紹過了。
>座標變換矩陣的生成以及相關的處理

    // 使用minMatrix.js對矩陣的相關處理
    // matIV物件生成
    var m = new matIV();
    
    // 各種矩陣的生成和初始化
    var mMatrix = m.identity(m.create());
    var vMatrix = m.identity(m.create());
    var pMatrix = m.identity(m.create());
    var mvpMatrix = m.identity(m.create());
    
    // 檢視變換座標矩陣
    m.lookAt([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0], vMatrix);
    
    // 投影座標變換矩陣
    m.perspective(90, c.width / c.height, 0.1, 100, pMatrix);
    
    // 各矩陣想成,得到最終的座標變換矩陣
    m.multiply(pMatrix, vMatrix, mvpMatrix);
    m.multiply(mvpMatrix, mMatrix, mvpMatrix);
    
    // uniformLocation的獲取
    var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix');
    
    // 向uniformLocation中傳入座標變換矩陣
    gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
這次,模型變換矩陣初始化之後沒有做任何處理就直接使用了,當然,並不是說不能操作模型變換矩陣,現在就先這麼使用了。
檢視變換矩陣使用minMatrix.js中定義的matIV.lookAt函式可以生成,向上面這樣,是將三維空間中的鏡頭放在了從原點開始向上移動1.0,向後移動3.0的地方,原點是參考點,鏡頭的方向指向Y軸方向。
投影變換矩陣是使用matIV.perspective生成的,視角是90度,螢幕比例為canvas的大小比例,然後分別指定了近截面和遠截面。
然後將模型,檢視,投影的各個變換矩陣相乘,得到最終的座標變換矩陣mvpMatrix,然後通知WebGL就可以了。
向WebGL中傳入uniform變數的時候,和attribute變數一樣,首先獲取變數的序號,使用WebGL的getUniformLocation函式,傳入程式物件和變數的名字,就可以得到uniform變數的序號了。
得到了序號之後,向頂點著色器中傳遞資料,這時候使用WebGL的uniformMatrix4fv函式,第一個引數是uniform變數的序號,第二個引數是矩陣是否進行轉置(true的話,有時候程式會崩潰),第三個引數是實際的座標變換矩陣。

>>uniform系列函式相關
這次出現的uniformMatrix4fv只是uniform系列函式的一個,uniform系列函式有很多種,主要分為以下幾大類。


uniform 系列
uniform系列有uniform1 ~ uniform4,分別在向頂點著色器中傳入一個到四各元素時使用。根據傳給頂點著色器的資料的型別為整型或者浮點型,數字的後面會加上i(int)或者f(float)等小寫字母。比如,傳給著色器的資料是有兩個元素的浮點型資料的時候,使用uniform2f。
示例 : gl.uniform2f(uniformLocation, date1, data2);


uniform v 系列
這一系列,基本上和上面的uniform系列沒有太大差別,傳入的資料是陣列的時候使用。和uniform系列一樣,有1 ~ 4、資料型別也是在後面新增i或者f。
示例 : gl.uniform3iv(uniformLocation, Array);


uniformMatrix 系列
看名字就應該知道了,這個系列是在矩陣的時候使用。當然,矩陣不可能出現1,所以只有2 ~ 4。而且,矩陣中基本上只使用浮點型小數、所以,資料型別也不存在i,資料就是以使用陣列為前提的,所以後面加不加v都是沒有區別的。
示例 : gl.uniformMatrix4fv(uniformLocation, false, Matrix);


模型的繪製和context的重新整理

著色器,頂點資料,座標變換矩陣等,各種準備工作都完工了,終於該寫繪製命令了。

>繪製命令和重新整理

// 繪製模型
gl.drawArrays(gl.TRIANGLES, 0, 3);

// context的重新整理
gl.flush();
執行WebGL的drawArrays函式的話,模型就被繪製到了快取中了,這裡之所以說是[快取中],是因為當執行drawArrays函式的時候,還沒有把多邊形繪製到畫面上。
要想把模型繪製到畫面上,必須執行WebGL的flush函式,這樣才能把結果反映到畫面上。

這裡出現的drawArrays函式,第一個引數是指定如何使用頂點進行繪圖的一個常量,第二個引數是從第幾個頂點開始使用,第三個引數是繪製幾個頂點。

這一次,使用的是gl.TRAIANGLES,所以頂點被當成了純粹的三角形多邊形,利用三個頂點進行了繪製


總結

這次的文章有點太長了,貼出的程式碼量有點多,可能有人會吃了一驚吧。

其實這次的程式碼繪製的只是一個簡單的三角形。只是這樣,卻寫了這麼長的程式碼,所以才說3D開發是比較難的。

但是,個人認為,即使這樣,和DirectX相比較的話,已經相當簡單,簡練了。

單從開發環境上來說,不需要特別的開發環境,WebGL這一點已經很輕鬆了吧。理解了本次的內容的話,只需要稍微慢慢的調整一下,就可以實現很多效果。這以後的內容,都是以這次的內容為基礎的,所以必須要好好的理解一下。

本文章最後,給出本文demo的連結,如果瀏覽器支援的話,直接看demo比較直觀吧。


那麼,下次給多邊形進行著色,敬請期待。

渲染三角形的demo

http://wgld.org/s/sample_002/


轉載請註明:轉自lufy_legend的部落格http://blog.csdn.net/lufy_legend

相關文章