【GISER&&Painter】Chapter01:WebGL渲染初體驗

基於上一篇OpenGL的渲染原理,這兩週又陸續接觸了一些關於WebGL繪圖的一些內容,因爲剛入門,很多東西又很晦澀,所以特意花了小半天的時間整理了一下,特此記錄。

零  畫一個多邊形吧!

  把一個多邊形畫上屏幕分幾步?

  答:分三步,第一,打開屏幕(1-1.從HTML中獲取Canvas對象;1-2.從Canvas拿到WebGL的Context);第二,把數據畫好(2-1.編譯着色器;2-2.準備數據模型;2-3.頂點緩存VBO的生成和通知);第三,畫上去(3-1.發出繪圖命令,更新Canvas並渲染)。

   畫布和畫筆:創建Canvas && 獲取WebGL的Context

  在開始WebGL的繪製故事之前,我們得先來認識一下Canvas,因爲這玩意是我們之後繪圖的基礎底板:“Canvas元素創造了一個固定大小畫布,並提供了一個或多個渲染上下文,用來繪製和處理要展示的內容”(摘自MDN)。按照定義,我們可以將其理解爲渲染任務的中轉站,因爲最終繪圖的輸出是要將數據交給屏幕展示的,Canvas只是作爲中間暫存待渲染數據的中轉站,也可以看作是一個具有仿屏幕像素矩陣數據結構的容器,大概類似於中間緩存一類的概念。那爲啥不直接在屏幕上輸出畫出來呢?之前看到過這樣一個解釋,緩存的作用在於下一幀沒有及時渲染出來的時候(渲染時間超出了人眼的最低感知幀率,最低幀率爲24幀),當前幀的數據就能夠替代下一幀,以此來保證過程的完整性。

  WebGL的API提供了能夠在支持的瀏覽器中無插件地繪製交互式的2D/3D圖像,而WebGL中最重要的對象:渲染上下文WebGLRenderingContext是基於OpenGL ES 2.0的繪圖上下文所實現,一般用在HTML5的<canvas>元素內繪圖等任務上。而WebGLRenderingContext可以看作是渲染任務的“核心CPU“,所有的操作:從視口剪裁、狀態信息、數據緩衝區、着色器的創建和調用、緩衝區繪製等等任務,都由WebGLRenderingContext調配和使用,所以,在任何Web程序開始繪製之前,我們所需要做的第一件事情就是創建一個畫布作爲數據容器,並將其與WebGL的上下文進行綁定,這樣一來,我們既有了畫布,又有了畫筆,就可以開始繪製我們想要的圖形了。

二  調色盤:着色器

  在傳統的OpenGL的固定管線中,我們對於渲染過程的控制力度是有限的。從上一篇OpenGL基礎裏來看,在頂點操作和圖元裝配、紋理化、片元着色等過程中,我們能控制的只有調用底層硬件廠商提供的接口參數,使用固定的程序去進行上述過程的處理。這種級別的控制非常弱,記得不久前在知乎上看到了一個關於固定管線控制的比喻:“扳開關”,我覺得十分貼切,這個概念有點類似於鐵道上的扳道工,火車的前進方向只能在鋪設好的軌道上選擇,如果沒有軌道的地方,火車自然就沒法開到。渲染同理,如果對於渲染效果有更高更靈活的要求(或者你無法接受硬件廠商提供的笨重不堪的渲染參數配置,想用更能讓人接受的方式去定製屬於自己的渲染結果),那麼固定管線的處理方式就基本可以不考慮了。

圖1 扳開關 

  那懶人就不能拿起畫筆在屏幕上作畫了嗎?幸運的是,我們遇到了可編程渲染管線。它的出現給上面遇到的難題帶來的答案,從圖2可以看出:可編程渲染管線中出現了兩個處理器:頂點着色器vertex shader和片元着色器fragment shader。這兩個處理器繞過了傳統的頂點操作和圖元裝配、片元着色等過程,通過自身的可編程特性,分別接手控制了頂點座標轉換、像素顏色計算的工作:

1)在頂點着色器Vertex Shader的處理階段,頂點數據從GPU顯存中讀取出來,頂點着色器Vertex Shader可以對每個頂點進行模型視圖變換、投影變換等頂點處理工作,替代了固定管線中的頂點處理管線;

<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
    attribute vec2 a_position;
    attribute vec4 a_color;

    uniform mat3 u_matrix;

    varying vec4 v_color;

    void main() {
        gl_Position = vec4((vec3(a_position, 1)).xy, 0, 1);

        v_color = a_color;
    }
</script>

2)頂點着色器處理完之後,管線會對各個頂點進行光柵化處理。由於頂點着色器的計算次數由頂點的數量決定,即n個頂點對應着n個頂點像素數據,但在一個由若干個像素組成的圖元中,非頂點像素的顏色該如何確定呢?此時就需要給大家介紹一個新對象:數據類型Varyings,從下面一段簡短的頂點着色器DEMO中可以看出來,我們在頂點着色器中定義了好幾種數據類型,有attribute,uniform以及varying。但來得早不如來得巧,我們先來認識一下Varying。

  Varying是一個變量,作爲一個信使連接着頂點着色器Vertex Shader和片元着色器Fragment Shader。在一般情況下,頂點着色器Vertex Shader會計算出每個頂點的顏色、座標等值,並用Varying變量來存儲這些值。回到剛纔提出的問題,非頂點的像素如何確定自己的值呢?這就需要片元着色器來理解這個信使了,好在片元着色器和頂點着色器之間有個光柵器牽線搭橋,當頂點着色器傳來Varying類型的頂點值時,光柵器會指定一種插值模式,指導片元着色器按像素逐個進行渲染繪製。

3)片元着色器Fragment Shader在光柵化工具(這個工具我也沒有仔細研究,暫時當作一個黑盒吧)的指導下進行工作(片元在上一篇OpenGL基礎中已經提到過了,所謂片元即指光柵化後的圖元)。片元着色器fs的主要工作是爲當前光柵化的像素提供顏色值,屏幕中的每一個像素都需要調用一次片元着色器Fragment Shader,每次調用都會從一個特殊的全局變量gl_FragColor中獲取顏色信息。

<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">

    precision mediump float;

    varying vec4 v_color;

    void main() {
        gl_FragColor = v_color;
    }
</script>

 

 

圖2 可編程渲染管線

三 輪廓骨架線

  在前面的步驟大概能夠初探一二之後,下一步就是在顯存中創建頂點對象VBO了:所謂VBO,頂點緩衝區對象( Vertex buffer object )這個概念來自於OpenGL,其概念定義爲一個將頂點Vertex的各類屬性信息(如vertex座標,vertex法向量以及顏色等)存儲在顯存的一塊專用的緩存區中,在執行渲染命令時,可直接從顯存中取出VBO。由於整個過程都是在GPU中進行,不同於之前傳統的繪製方式(CPU命令GPU執行繪製動作,反覆傳輸大量的頂點數據到GPU中,渲染速度較慢),所以一般將VBO視爲一個能夠改善數據傳輸效率的對象。

  那麼VBO是如何在WebGL中應用的呢?我們通常第一步通過createBuffer方法創建一個緩存對象VBO,通過圖3的MDN中的定義可以看出,返回值VBO可以是顏色或者頂點座標值的緩衝對象。此時只需調用gl.bufferData向gl_ARRAY_BUFFER中寫入數據,再使用gl. bindBuffer方法就可以把buffer數據與gl上下文中的ARRAY_BUFFER關聯起來,也就是把頂點數據成功地寫入了GPU顯存中。

圖3 WebGLBuffer

    function main(){
          ......
            //Create a buffer & bind buffer
            //創建buffer數據,並綁定到gl的上下文中
            var positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            //set Geometry
            //填充buffer
            setGeometry(gl);
          ......
    }
function setGeometry(gl) { gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ -150, -100, 150, -100, -150, 100, 150, -100, -150, 100, 150, 100]), gl.STATIC_DRAW); }

  進行到上述階段爲止,渲染之前的初始化工作基本完成:畫布空間已創建  -->  將畫布Canvas與WebGL環境綁定 ---> 創建並指定了相關自定義着色器填充頂點數據 ---> 創建頂點緩存VBO。一切準備就緒,千軍萬馬一般的屏幕像素們都在等着一個明確渲染繪圖指令,只待gl令旗一揮,在GPU的指揮下,千萬個屏幕像素將會按照規定的位置和顏色,以迅雷不及掩耳之勢一蹴而就,以你所規定的樣式完美地呈現在你的屏幕上。

四 畫!

  一切準備都已完成,所有任務執行資源就位後,我們再來一起看看最後一步的繪圖渲染命令是如何發出的。

  簡單來看,我們可以把繪圖渲染部分分爲以下三部分:

    1)畫布清潔

    2)指定環境

    3)執行着色程序

//Rendering code: 渲染代碼
        function drawScene() {

       //-----------------------畫布清潔--------------------- webglUtils.resizeCanvasToDisplaySize(gl.canvas);
//covert from clip space to pixels gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); //clear canvas gl.clear(gl.COLOR_BUFFER_BIT); //direct to our program gl.useProgram(program); //------------------------指定環境------------------------------- //打開屬性attribute開關 gl.enableVertexAttribArray(positionAttributeLocation); //對當前狀態進行綁定 : 綁定已經完成填充點數據的buffer數據塊positionBuffer gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) var size = 2; // 2 components per iteration var type = gl.FLOAT; // the data is 32bit floats var normalize = false; // don't normalize the data var stride = 0; // 步長(byte),每個頂點數據所佔的字節數:0 = move forward size * sizeof(type) each iteration to get the next position var offset = 0; // start at the beginning of the buffer //vertexAttribPointer:頂點屬性指路牌 //告訴顯卡從當前綁定的緩衝區(drawScene方法中的bindBuffer)中讀取頂點數據vertex data gl.vertexAttribPointer( positionAttributeLocation, size, type, normalize, stride, offset); //------------------------------執行着色程序-------------------------------------// Draw the geometry. var primitiveType = gl.TRIANGLES; //繪製圖元模式 var offset = 0;//從緩衝區開始讀取數據的首地址偏移first下標 var count = 6;//繪製頂點數據的個數,即Shader代碼的運行次數 gl.drawArrays(primitiveType, offset, count); } //==================================================================================

 

  具體的WebGLRenderingContext提供的接口我在這裏就不贅述了,這篇僅僅只是爲了帶給大家一個如何在瀏覽器中繪製渲染圖形的概念,之後應該會有針對各個環節的專題,畢竟纔剛剛入坑,來日方長!

 

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