20 WebGL使用紋理貼圖
案例檢視地址:點選這裡
WebGL中紋理的限制
WebGL中的紋理需要注意一點,所使用的圖片資料的大小必須是2的階乘,橫豎的畫素長度大小必須是32x32,128x128等2的階乘的形式。當然,做一些處理的話,不是2的階乘的圖片資料也是可以用的,但是基本上作為紋理使用的影象資料的大小必須是2的階乘。
另外,看一下普通的網頁就能感覺到,網頁上的圖片資料的讀取是要花一點時間的,在進行紋理轉換的話,必須是在圖片讀取完之後才行,這裡需要做一些特殊的處理,如果對JavaScript不太熟悉的話可能會無從下手,這個後面會說。
為什麼需要紋理?
因為不可能所有的影象都靠程式碼生成,那是在浪費生命。
這一部分由於WebGL的安全機制,必須開啟服務獲取,或者允許瀏覽器訪問本地檔案,要不然WebGL無法獲取檔案。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Title</title> <style> body { margin: 0; text-align: center; } #canvas { margin: 0; } </style> </head> <body onload="main()"> <canvas id="canvas" height="800" width="800"></canvas> </body> <script src="lib/webgl-utils.js"></script> <script src="lib/webgl-debug.js"></script> <script src="lib/cuon-utils.js"></script> <script src="lib/cuon-matrix.js"></script> <script> /*第一部分頂點著色器接收頂點的紋理座標,傳遞給片元著色器*/ var VSHADER_SOURCE = "" + "attribute vec4 a_Position;\n" +// "attribute vec2 a_TexCoord;\n" +// "varying vec2 v_TexCoord;\n" +// "void main(){\n" + " gl_Position = a_Position;\n" + " v_TexCoord = a_TexCoord;\n" +// "}\n"; var FSHADER_SOURCE = "" + "precision mediump float;\n" +// "uniform sampler2D u_Sampler;\n" +// "varying vec2 v_TexCoord;\n" +// "void main(){\n" + " gl_FragColor = texture2D(u_Sampler,v_TexCoord);\n" +// "}\n"; /*第二部分 main()方法 初始化著色器,設定頂點資訊,呼叫配置紋理方法*/ function main() { var canvas = document.getElementById("canvas"); var gl = getWebGLContext(canvas); if(!gl){ console.log("你的電腦不支援WebGL!"); return; } if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){ console.log("初始化著色器失敗!"); return; } //設定頂點的相關資訊 var n = initVertexBuffers(gl); if(n < 0){ console.log("無法獲取到點的資料"); return; } //配置紋理 if(!initTextures(gl,n)){ console.log("無法配置紋理"); return; } } /*第三部分 initVertexBuffers() 設定頂點座標和紋理座標 呼叫initTextures()進行下一步處理*/ function initVertexBuffers(gl) { var verticesSizes = new Float32Array([ //四個頂點的位置和紋理資料 -0.5,0.5,0.0,1.0, -0.5,-0.5,0.0,0.0, 0.5,0.5,1.0,1.0, 0.5,-0.5,1.0,0.0 ]); var n = 4; var vertexSizeBuffer = gl.createBuffer(); if(!vertexSizeBuffer){ console.log("無法建立緩衝區"); return -1; } gl.bindBuffer(gl.ARRAY_BUFFER,vertexSizeBuffer); gl.bufferData(gl.ARRAY_BUFFER,verticesSizes,gl.STATIC_DRAW); var a_Position = gl.getAttribLocation(gl.program,"a_Position"); if(a_Position < 0){ console.log("無法獲取到儲存位置"); return; } //獲取陣列一個值所佔的位元組數 var fsize = verticesSizes.BYTES_PER_ELEMENT; //將頂點座標的位置賦值 gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,fsize*4,0); gl.enableVertexAttribArray(a_Position); //將頂點的紋理座標分配給a_TexCoord並開啟它 var a_TexCoord = gl.getAttribLocation(gl.program,"a_TexCoord"); if(a_TexCoord < 0){ console.log("無法獲取到儲存位置"); return; } //將紋理座標賦值 gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,fsize*4,fsize*2); gl.enableVertexAttribArray(a_TexCoord); return n; } /*第四部分 initTextures() 建立紋理物件 並呼叫紋理繪製方法*/ function initTextures(gl,n) { var texture = gl.createTexture();//建立紋理物件 if(!texture){ console.log("無法建立紋理物件"); return; } //獲取u_Sampler的儲存位置 var u_Sampler = gl.getUniformLocation(gl.program,"u_Sampler"); if(u_Sampler < 0){ console.log("無法獲取變數的儲存位置"); return; } //建立Image物件,並繫結載入完成事件 var image = new Image(); image.onload = function () { loadTexture(gl,n,texture,u_Sampler,image); }; image.src = "./resources/sky.jpg"; return true; } /*第五部分 設定紋理相關資訊供WebGL使用,並進行繪製*/ function loadTexture(gl,n,texture,u_Sampler,image) { //對紋理影象進行y軸反轉 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1); //開啟0號紋理單元 gl.activeTexture(gl.TEXTURE0); //向target繫結紋理物件 gl.bindTexture(gl.TEXTURE_2D,texture); //配置紋理引數 gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR); //配置紋理影象 gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image); //將0號紋理傳遞給著色器 gl.uniform1i(u_Sampler,0); //繪製 gl.clearColor(0.0,0.0,0.0,1.0); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP,0,n); } </script> </html>
這次程式碼共分成了五大部分:
第一部分增加上了varying變數。
第二部分初始化WebGL。
第三部分繪製頂點資訊。
第四部分主要是建立了紋理物件,和通過瀏覽器載入圖片並且觸發設定紋理的相關資訊的方法。
重要的就是第五部分,在之前沒有接觸過的紋理相關的資訊設定api:
首先要明白紋理的座標問題:
為了將紋理座標和廣泛的使用的xy座標區分開來,WebGL使用s和t命名紋理座標,如圖。
我們的任務是什麼呢?
就是將圖片對映到WebGL上面
我們在initVertexBuffers()裡面的資料內添加了紋理的座標,來達到上圖的匹配。
首先需要建立gl.createTexture()了紋理物件:
對應的還有gl.deleteTexture()來刪除紋理物件
如果刪除一個已經被刪除的紋理物件時,不會報錯也不會產生任何影響。
在等待圖片被瀏覽器載入成功後,就會呼叫loadTexture()方法進行設定WebGL的相關紋理資訊。
第一個方法gl.pixelStorei()是對影象進行Y軸反轉,才能正確的將影象對映到圖形上。
然後啟用紋理單元gl.activeTexture(),每個紋理單元有一個單元編號來管理紋理影象。即使你的程式只需要一張紋理影象,也得為其指定一個紋理單元。
然後需要繫結紋理物件gl.bindTexture(),這一點與緩衝區很像,寫入資料之前需要繫結。
WebGL支援兩種型別的紋理:
案例使用的二維影象,所以傳入了gl.TEXTURE_2D
紋理物件繫結成功後,就可以進行紋理物件的引數進行配置gl.texParameteri():
引數設定完成以後,就需要將紋理影象分配給紋理物件gl.texImage2D():
這裡的L(流明)表示我們感知到的物體表面的亮度。通常使用物體表面紅、綠、藍顏色分量值得加權平均來計算流明。
一旦將紋理影象傳入WebGL系統,就必須將其傳入片元著色器並對映到圖形的表面上去。
在39行,我們使用uniform聲明瞭一個texture2D型別的u_Sampler變數來表示紋理,因為紋理影象不會隨著片元變化。
而表示紋理物件的unifrom變數必須宣告為一種特殊的、專用於紋理物件的資料型別。如圖:
在程式碼的156行,我們使用了gl.uniform1i(),指定紋理單元編號將紋理物件傳遞給u_Sampler,由於案例的紋理物件被繫結到了gl.TEXTURE0上面,第二個引數為0。
由於我們是使用的varying變數從頂點著色器內將變數傳到片元著色器裡面,片元著色器和頂點著色器內的同名、同類型的varying變數可以用來在兩者之間傳輸資料。頂點之間的片元的紋理座標會在光柵化的過程中內插出來,所以在片元著色器中,我們使用的是內插後的紋理座標。
在片元著色器中,第四十二行的程式碼使用了GLSL ES內建函式texture2D()來抽取紋素顏色,然後把從紋理上獲取到的顏色,逐片元的賦值到圖形上面。
這個函式時內建的,留意一下其引數和返回值