一篇文章理清WebGL繪製流程

轉自:https://www.jianshu.com/p/e3d8a244f3d9編程

目錄

  1. 初始化WebGL環境
  2. 頂點着色器(Vertex Shader)與片元着色器(Fragment Shader)
  3. 頂點數組對象(VBO)、索引數值對象(IBO)
  4. 繪製流程
  5. 總結

初始化WebGL環境

關於HTML五、<canvas>標籤、WebGL的一些相關知識能夠去MDN中查看,裏面還有一些相關的學習乾貨,初始化WebGL環境能夠參考初識WebGL,咱們這裏按下不表。canvas

頂點着色器與片元着色器

WebGL圖形渲染管線

介紹着色器以前,咱們先過一下WebGL的圖形管線:數組

 
WebGL Rendering Pipeline Overview

 

咱們能夠把WebGL的渲染管線當作一條車間裏的流水線,這個車間的原材料是一些圖形相關的數據(包括頂點座標,頂點顏色等),這個車間生產的產品是咱們屏幕上看到的各類圖形。ide

圖中綠色的兩個方塊就是咱們要說的頂點着色器(Vertex Shader)片元着色器(Fragment Shader)函數

咱們來一步一步簡單介紹下這個條流水線:
圖中最上面的藍色方塊Attribute能夠看作是一條水管,一端連接頂點數據(Vertex Buffer Objects),另外一端鏈接頂點着色器,這條水管的做用是把頂點數據輸送給頂點着色器處理。緊接着,頂點着色器把處理過的頂點數據交給片元着色器處理,最後通過片元着色器處理過的數據將被輸送到Framebuffer中去,爲了便於理解,咱們暫且、姑且能夠把這個Framebuffer當作屏幕,而這最後的步驟,也就是把這些處理過的數據以圖形的方式顯示到屏幕上,至此,渲染管線也就完成了他的使命。學習

在這條流水線上,寫代碼的咱們,扮演着的是流水線上的工人,因此咱們作的事情是拿來數據,而後確保數據在這條流水線上,按照既定的流程,最終能夠變成咱們想要的圖形。ui

頂點着色器與片元着色器

由上所述,WebGL編程中,咱們須要爲渲染流水線構建好頂點着色器和片元着色器。spa

頂點着色器的功能是對傳進來的頂點數據經過矩陣進行換換位置、計算照明方程式以生成逐頂點的顏色以及生成或者變換紋理座標。
片元着色器則是對即將送到屏幕上的像素內容進行更進一步的處理,包括一些特殊效果的定製等。3d

這二者的內容會在以後的學習過程當中加以說明,本文只需對它們的做用有個大體的瞭解便可,更多的內容能夠參考《OpenGL編程指南》或者是《OpenGL ES 3.0編程指南》,咱們這裏重點只在於簡單整理下WebGL的渲染流程。code

建立及使用着色器(Shader)

簡要梳理下shader的建立過程。shader的做用前文已經介紹過了,咱們能夠把shader當作一個程序,頂點數據輸入shader,輸出通過shader處理過的數據,用於以後的渲染流程。

建立和使用shader的過程分爲如下步驟:

  1. 首先建立並編譯好着色器對象
  2. 將這些着色器對象連接爲一個着色器程序。

對於着色器對象

  1. 建立一個着色器對象
  2. 將着色器源代碼編譯爲對象
  3. 驗證着色器的編譯是否成功

建立着色器代碼以下:

// 建立一個着色器對象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);

// 指定着色器源代碼
gl.shaderSource(vertexShader , vertexshaderSourceCode);

// 將着色器源代碼編譯爲對象
gl.compileShader(vertexShader );

// 驗證着色器的編譯是否成功
if (!gl.getShaderParameter(vertexShader , gl.COMPILE_STATUS)) {
    alert(gl.getShaderInfoLog(vertexShader ));
    return;
}

// 以上的代碼建立了一個頂點着色器,對於片元着色器
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 如下代碼相似
// code gose here ...

建立完着色器對象後,咱們獲得了兩個着色器 vertexShader 和 fragmentShader

對於着色器程序:

  1. 建立一個着色器程序
  2. 將着色器對象關聯到着色器程序
  3. 鏈接着色器程序
  4. 判斷着色器的連接過程是否成功完成
  5. 使用着色器來處理頂點和片元

建立着色器程序代碼以下:

// 建立一個着色器程序
var program = gl.createProgram();

// 將着色器對象關聯到着色器程序
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

// 鏈接着色器程序
gl.linkProgram(program);

// 判斷着色器的連接過程是否成功完成
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
    alert("Could not initialise shaders");
}

//  使用着色器來處理頂點和片元
gl.useProgram(program);

在大體瞭解過這條渲染流水線後,咱們接着將要介紹下頂點數組對象和索引數組對象。

頂點數組對象(VBO)、索引數值對象(IBO)

頂點數組對象(VBO)

頂點數組對象(VBO)對應着圖中最上方的紅色的方塊。頂點數組對象包含着WebGL要渲染的圖形的數據。能夠當作是流水線上的原材料。在WebGL的渲染過程當中,這些圖形的數據每每經過頂點進行儲存和輸送,頂點數組對象包含的數據通常包括頂點位置信息、頂點位置上的法線向量、紋理座標等。

索引數組對象(IBO)

在圖形的繪製過程當中,咱們把每3個頂點繪製成一個三角形,即圖形學中「面」(surface)的含義,然後經過成千上萬的面組成了咱們空間中的三維模型。索引數組對象(IBO)的做用是告訴WebGL要經過什麼樣的順序來將咱們傳入的頂點數據鏈接成面。爲了便於理解,咱們舉個栗子:


 
繪製一個梯形

當咱們按照如圖所示的方式繪製一個梯形時,咱們弄來了5個頂點(0, 0)、(10, 10)、(20, 0)、(30, 10)、(40, 0)分別對應從0-4的五個索引。而當咱們繪製圖形時,定義的索引數組[0, 2, 1, 1, 2, 3, 2, 4, 3]的意思就是告訴WebGL,把索引爲0、二、1的三個頂點組成一個三角形,索引爲一、二、3的三個頂點組成另外一個三角形,索引爲二、四、3的頂點也組成一個三角形(可參照圖示)。
索引數組對象的做用就是保存這些索引數組的數據,用以傳輸給WebGL渲染管線。

建立頂點數組對象&索引數組對象

WebGL狀態機

在介紹如何建立這兩個對象前,咱們要先知道,WebGL是個狀態機。咱們能夠這麼理解,假設WebGL中的屬性P的值爲1,你在某一次操做中,把P的值設置成了2,那麼在你下一次設置P的值以前,P的值永遠是2。

更直觀一點的表述,你能夠想象WebGL像KFC套餐,有個套餐A,裏面包含一個香辣雞腿堡,一杯百事可樂。你每次點A套餐,都會獲得一個香辣雞腿堡,一杯百事可樂,而你在每次用餐的過程當中,吃的漢堡都是香辣雞腿堡,喝的可樂都是百事可樂。直到某一天,KFC把A套餐的內容改爲了一個奧爾良烤雞腿堡,一杯可口可樂。在那天以後,一樣是A套餐,可是你吃的漢堡就變成了奧爾良烤雞腿堡,喝的可樂變成了可口可樂,除非KFC對A套餐的內容再進行調整,否則點A套餐的你只能喝上一生的可口可樂(本人比較喜歡百事可樂)。

因此你能夠把WebGL上下文想象成是A套餐,WebGL中的一些屬性選項的對應的是漢堡、可樂,而香辣雞腿堡、奧爾良烤雞腿堡、百事可樂、可口可樂對應的是屬性當前的值。類比一下。

頂點數組對象的建立

接下來插播一段代碼:

var vertices = [
-50.0, 50.0, 0.0,
-50.0,-50.0, 0.0,
50.0,-50.0, 0.0,
50.0, 50.0, 0.0
];

// 調用gl.createBuffer()建立了一個塊內存空間,並讓 vertexBufferObject  變量指向這塊空間
var vertexBufferObject = gl.createBuffer();

// 把WebGL中用於繪製的數組數據的屬性(ARRAY_BUFFER)的值的地址指向上文咱們建立的 vertexBuffer 內存空間。
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject );

// bufferData 函數對 ARRAY_BUFFER 屬性對應的空間填充值
// WebGL 狀態機概念出場!
// 因爲咱們在上一個語句中,修改了 ARRAY_BUFFER 的值,因爲狀態機的性質,因此咱們調用該函數進行傳值時,
// 傳入的值是會給此時gl中的 ARRAY_BUFFER 屬性指向的空間,也就是
 vertexBufferObject 的內存空間。
// 此處調用把 vertices 中的頂點數據傳入了 vertexBufferObject 對象
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

// 傳值完畢,恢復 ARRAY_BUFFER 的值爲空
gl.bindBuffer(gl.ARRAY_BUFFER, null);

因此這段代碼就建立了一個名爲 vertexBufferObject 的頂點數組對象。代碼中具體的API能夠查閱相關的資料。

索引數組對象的建立

索引數組對象的建立與頂點數組對象的建立大同小異

var indices = [
0, 2, 1, 
1, 2, 3
];
var indicesBufferObject = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBufferObject );
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices),
gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

這裏須要注意的是,索引數組對象對應的屬性爲ELEMENT_ARRAY_BUFFER。而傳入的數組類型是Uint16Array。

繪製流程

最後,咱們要把前面提到的內容按照渲染管線的圖示結合起來。再看一眼咱們的地圖:


 
WebGL Rendering Pipeline Overview

好,上代碼:

// 按照圖示來看,要繪製一個圖形,首先咱們須要原材料:
// 1. 一個頂點數組對象(VBO),用於存儲相關的頂點數據, 
// 同時須要一個表示頂點組合順序的索引數組對象(IBO)。

// 2. 須要兩個着色器(vertex shader 及 fragment shader)和一個着色器程序,用來保證管線的順利進行。
// 好了,開工!

// 先來VBO和IBO
var vertices =  [
    -0.5,0.5,0.0, 
    -0.5,-0.5,0.0,
    0.5,-0.5,0.0, 
    0.5,0.5,0.0
];  

var indices = [3,2,1,3,1,0];

// 建立VBO
var vertexBufferObj = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObj );
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);

// 建立IBO
var indexBufferObj = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObj );
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

// 建立頂點着色器
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader , vertexshaderSourceCode);
gl.compileShader(vertexShader );
// 驗證着色器的編譯是否成功
if (!gl.getShaderParameter(vertexShader , gl.COMPILE_STATUS)) {
    alert(gl.getShaderInfoLog(vertexShader ));
}

// 同理建立片元着色器
var fragmentShader= gl.createShader(gl.FRAGMENT_SHADER);
/* so many balabalabala... */

// 建立着色器程序
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// 判斷着色器的連接過程是否成功完成
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
    alert("Could not initialise shaders");
}

目前爲止,咱們已經準備好了繪製圖形的全部原料了,接下里就是如何經過WebGL繪製出圖形了。

// 先獲取頂點數據進入着色器的入口(後文會講解)
program.vertexPosition = gl.getAttribLocation(program, "aVertexPosition");

// 繪製場景的函數
function drawScene(){
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);
    
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.viewport(0, 0, c_width, c_height);

    // 指定繪製時使用的頂點數據
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObj );
    // 一下這兩行代碼對應的做用是將頂點數據讀入着色器中,後文會加以解釋
    gl.vertexAttribPointer(program.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(program.vertexPosition);

    // 指定繪製時使用的索引數組
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObj);
    // 以給定的形式繪製圖形
    gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT,0);
}

這樣咱們就實現了圖形的繪製。
因爲本文重點在於梳理繪製流程,並無深刻介紹着色器相關的內容,因此對於gl.vertexAttribPointer函數和gl.enableVertexAttribArray會有疑惑。
爲了解釋一下這個問題,咱們先看一段頂點着色器的代碼

<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;

    void main(void) {
        gl_Position = vec4(aVertexPosition,1.0); 
    }
</script>

其中代碼中的aVertexPosition表示的是頂點數據從這個變量進入着色器。

就像咱們前文比喻的那樣,這個aVertexPosition就像一端鏈接頂點數據(Vertex Buffer Objects),另外一端鏈接頂點着色器的水管的開關。

咱們執行program.vertexPosition = gl.getAttribLocation(program, "aVertexPosition");時,至關於把這個開關的位置記錄在program.vertexPosition上面。

如下代碼:

gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObj );
gl.vertexAttribPointer(prg.vertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(program.vertexPosition);

bindBuffer指定當前WebGL繪製的數據爲vertexBufferObj中的頂點數據。

vertexAttribPointer函數則把頂點數據經過水管接到着色器"aVertexPosition"的位置上,也就是咱們記錄下來的program.vertexPosition的位置。

gl.enableVertexAttribArray(program.vertexPosition)則是最後一步把水管的閥門打開,讓頂點數據流入着色器。

總結

本文主要目的是簡單梳理了下WebGL繪製圖形的大體編程流程,並無作很深刻的講解。WebGL基於OpenGL ES 2.0,而早先的OpenGL用的都是固定的渲染管線,以前學OpenGL一開始都是glBegin,glEnd,glVertex2d啥的一堆東西,精簡版的OpenGL ES一上來就拋棄了以前固定渲染管線的東西,使用了可編程的渲染管線,一上來就要求要編寫Shader,因此繪製一個圖形就變得比較繁瑣一點。因而有了這篇筆記,以供剛開始學習WebGL的玩家們參考。
對於文中的一些API並無進入詳細的講解,須要的同窗能夠查閱相關的資料書籍。


相關資料

《OpenGL編程指南》
《OpenGL ES 3.0編程指南》
《WebGL Beginner's Guide》

相關文章
相關標籤/搜索