1. 程式人生 > >20 WebGL使用紋理貼圖

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()來抽取紋素顏色,然後把從紋理上獲取到的顏色,逐片元的賦值到圖形上面。


這個函式時內建的,留意一下其引數和返回值