基於上一篇OpenGL的渲染原理,這兩週又陸續接觸了一些關於WebGL繪圖的一些內容,由於剛入門,不少東西又很晦澀,因此特地花了小半天的時間整理了一下,特此記錄。web
把一個多邊形畫上屏幕分幾步?編程
答:分三步,第一,打開屏幕(1-1.從HTML中獲取Canvas對象;1-2.從Canvas拿到WebGL的Context);第二,把數據畫好(2-1.編譯着色器;2-2.準備數據模型;2-3.頂點緩存VBO的生成和通知);第三,畫上去(3-1.發出繪圖命令,更新Canvas並渲染)。canvas
在開始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 扳開關 ide
那懶人就不能拿起畫筆在屏幕上做畫了嗎?幸運的是,咱們遇到了可編程渲染管線。它的出現給上面遇到的難題帶來的答案,從圖2能夠看出:可編程渲染管線中出現了兩個處理器:頂點着色器vertex shader和片元着色器fragment shader。這兩個處理器繞過了傳統的頂點操做和圖元裝配、片元着色等過程,經過自身的可編程特性,分別接手控制了頂點座標轉換、像素顏色計算的工做:工具
1)在頂點着色器Vertex Shader的處理階段,頂點數據從GPU顯存中讀取出來,頂點着色器Vertex Shader能夠對每一個頂點進行模型視圖變換、投影變換等頂點處理工做,替代了固定管線中的頂點處理管線;webgl
<!-- 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。spa
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提供的接口我在這裏就不贅述了,這篇僅僅只是爲了帶給你們一個如何在瀏覽器中繪製渲染圖形的概念,以後應該會有針對各個環節的專題,畢竟纔剛剛入坑,來日方長!