[WebGL入門]十一,著色器的編譯和連線

lufy發表於2014-08-06

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


反覆重複的東西

這已經是第11篇了,因為只說了一些基本的東西,到現在為止連個多邊形也沒繪製出來。哎呀呀呀......
不管怎麼說吧,基礎是很重要的。那就在這些基礎上,來繪製一個多邊形吧。需要準備的知識都已經介紹過了,剩下的就是按照步驟開始繪製多邊形。首先,來確認一下繪製的步驟。
・從HTML中獲取canvas物件
・從canvas中獲取WebGL的context
・編譯著色器
・準備模型資料
・頂點快取(VBO)的生成和通知
・座標變換矩陣的生成和通知
・發出繪圖命令
・更新canvas並渲染
上面所列舉的步驟中,到現在為止完全沒有介紹的是最後兩個,關於繪圖命令的部分和canvas更新部分。稍後,我會說一說它們基本的概念。雖然這些步驟看起來挺複雜的,但是這就是使用WebGL進行渲染的基本步驟。

這次的文章,就從上到下來依次看一下前三個步驟,先說到編譯著色器。

*其中有幾個步驟順序可以有變化,現在先按照這個順序來看一下。


HTML和canvas的處理

關於HTML和canvas的處理,在之前的文章中(七,context的初始化)也已經講過了。基本上也不會有什麼變化,在這裡再說一下。

>HTML程式碼

<html>
    <head>
        <title>WebGL TEST</title>
        <script src="script.js" type="text/javascript"></script>
        <script src="minMatrix.js" type="text/javascript"></script>
        
        <script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
uniform   mat4 mvpMatrix;

void main(void){
    gl_Position = mvpMatrix * vec4(position, 1.0);
}
        </script>
        
        <script id="fs" type="x-shader/x-fragment">
void main(void){
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
        </script>
    </head>
    <body>
        <canvas id="canvas"></canvas>
    </body>
</html>
HTML程式碼就是上面這樣,head標籤中引用了兩個javascript檔案,一個是WebGL的處理檔案script.js,另一個是本網站自己寫的矩陣計算的庫minMatrix.js。

頂點著色器的處理

接著,該出現頂點著色器和片段著色器的程式碼了。首先,先從type是x-shader/x-vertex的頂點著色器開始。下面是頂點著色器部分的程式碼。

>頂點著色器的程式碼

attribute vec3 position;
uniform   mat4 mvpMatrix;

void main(void){
    gl_Position = mvpMatrix * vec4(position, 1.0);
}
這裡用到了一個attribute變數和一個uniform變數。
變數position的型別是vec3,是一個3維向量。裡面是頂點的位置,向量的三個元素分別是X,Y,Z座標。

另一個uniform宣告的變數mvpMatrix,型別是mat4,所以是一個4x4的方陣。是模型,檢視,投影的各個變換矩陣結合後的座標變換矩陣。

這次的頂點著色器,只是利用座標變換矩陣來變換頂點的座標位置,使用乘法運算。這時候,為了讓position和矩陣相乘,使用vec4函式,先將其變成一個4維的向量,然後相乘,最後將計算結果代入到gl_Position,頂點著色器的處理結束。


片段著色器的處理

接著說片段著色器。

這次,繪製的模型是一個簡單的三角形,先不進行著色,只是使用白色來填充。所以,片段著色器中的處理,就只是將白色資訊傳給gl_FragColor中。下面是片段著色器的程式碼。

>片段著色器的程式碼

void main(void){
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
關於顏色,基本上使用vec3或者vec4的情況比較多。因為一般就是使用RGB或者RGBA,需要3~4個元素。這一次使用的vec4是所有的引數都是1.0的向量,顏色是白色[紅,綠,藍,不透明度的各元素都是最大=白色]。

編譯程式碼,生成著色器

接著來看頂點著色器和片段著色器的編譯。

編譯也不需要什麼特別的編譯器,只需要呼叫WebGL內部的函式就可以進行編譯了。準備一個函式,從著色器的編譯,到實際著色器的生成這一連串的流程,都在這一個函式中來完成。下面是這個函式的程式碼。

*下面的函式中的gl,是提前從WebGL的context中獲取的。

>生成和編譯著色器的函式

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));
    }
}
使用這個函式的時候,需要傳入script標籤的id作為引數,函式中根據這個id來獲取指定的標籤。
生成著色器的時候,使用WebGL中的函式createShader。這個函式在生成著色器的時候,根據頂點著色器和片段著色器的不同來傳入不同的引數。引數指定為gl.VERTEX_SHADER的時候會生成頂點著色器,引數指定為gl.FRAGMENT_SHADER的時候會生成片段著色器。

首先,將程式碼分配給生成的著色器的時候,使用的是shaderSource函式,引數有兩個,第一個引數是著色器物件,第二個引數是著色器的程式碼。這時候,只是把著色器的程式碼分配給了著色器,還沒有進行編譯。編譯的時候,使用的是compileShader函式,將著色器物件作為引數傳給這個函式,就可以將著色器進行編譯。

著色器是否編譯成功,通過著色器中的引數可以判斷,獲取這個引數的時候,使用getShaderParameter函式,並使用WebGL中存在的常量COMPILE_STATUS作為引數。如果這時候的返回值是false,則表示因為什麼原因導致編譯失敗了,要想檢視原因的話,將著色器傳入getShaderInfoLog函式中,就可以確認log了。

這個自定義函式,無論是頂點著色器還是片段著色器,都可以進行編譯。實際上,頂點著色器和片段著色器的處理不同的地方就是createShader函式,其他地方是完全一樣的。


程式物件的生成和連線

著色器生成之後,接著來生成程式物件。這裡突然出現的程式物件,到底是個什麼物件呢?

以前的文章中(八,著色器的說明和基礎)也稍微接觸了一些,使用varying修飾符定義的變數,可以從頂點著色器向片段著色器中傳遞資料。其實,實現從一個著色器向另一個著色器傳遞資料的,不是別的,就是程式物件。程式物件是管理頂點著色器和片段著色器,或者WebGL程式和各個著色器之間進行資料的互相通訊的重要的物件。

那麼,生成程式物件,並把著色器傳給程式物件,然後連線著色器,將這些處理函式化。

>程式物件的生成和著色器連線的函式

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));
    }
}
這個函式接收頂點著色器和片段著色器兩個引數。然後,首先生成程式物件,分配著色器。生成著色器的時候,使用WebGL中的函式createProgram,將著色器分配給程式物件的時候使用函式attachShader,attachShader函式的第一個引數是程式物件,第二個引數是著色器。
著色器分配結束後,根據程式物件,要連線兩個著色器,這時候使用linkProgram函式,引數就是程式物件。

和生成著色器一樣,要判斷著色器的連線是否成功,這時候使用getProgramParameter函式,並傳入常量LINK_STATUS。如果沒有問題的話,為了將程式物件設定為有效,使用useProgram函式。注意,如果不執行這個函式的話,在WebGL中是無法識別這個程式物件的。如果連線失敗,則彈出錯誤資訊,使用getProgramInfoLog函式來得到log。


總結

來簡單總結一下本次的內容。

HTML程式碼中引入必要的javascript檔案,以及描述著色器的程式碼。

準備了著色器的編譯函式和連線著色器的程式物件相關的函式。每個函式中都有是否進行了正確處理的判斷


下次,準備好頂點資料,也就是說準備好模型資料,然後變換為VBO。按照前面說的步驟,一步步全都理解的話,就應該沒問題了,加油吧。


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

相關文章