WebGL - 紋理映射

1、紋理映射

紋理映射:紋理映射就是將一張圖像映射大盤幾何體表面上去,此時這樣圖片就稱爲紋理貼圖;

紋理映射的作用,就是根據紋理圖像,爲之前光柵化後的每一個片元塗上合適的顏色,組成紋理圖像的像素又被稱爲 紋素,每一個紋素的顏色都使用 RGB或者 RGBA格式編碼

紋素可以簡單的理解爲一個像素

2、紋理映射四步

  • 1、準備紋理圖像
  • 2、配置集合圖形紋理映射方式
  • 3、加載和配置紋理圖像
  • 4、從紋理中抽出紋素賦給片元

3、紋理座標

紋理座標:就是紋理圖像上的座標,通過紋理座標可以在紋理圖像上獲取紋素顏色,webgl系統中的紋理座標是 二維的;

紋理座標用來確定紋理圖像那部分覆蓋到集合圖形上,紋理座標值與圖像自身的尺寸無關

在這裏插入圖片描述

4、映射過程

紋理映射得過程需要頂點着色器和片元着色器二者得配合;

1、初始化着色器

如下着色器代碼

頂點着色器

attribute vec4 a_Position;
attribute vec4 a_TexCoord;
varying vec2 v_TexCoord;
void main(){
    gl_Position = a_Position;
    v_TexCoord = a_TexCoord;
}

片元着色器

precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main(){
    gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}

2、設置紋理座標

將紋理座標傳入頂點着色器,與將其他頂點數據(如顏色),傳入頂點着色器得方法是一樣的;

// 創建頂點座標和紋理座標
var verticesTexCoords = 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
]);

3、配置和加載紋理

/* 四、創建紋理對象並調用紋理繪製方法 */
function initTextures(gl, n) {

    // 創建紋理對象
    var texture = gl.createTexture();
    if (!texture) {
        console.log('創建紋理對象失敗!');
        return false;
    }

    // 獲取 u_Sampler 的存儲位置
    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    if (!u_Sampler) {
        console.log('獲取 uniform 變量 u_Sampler 失敗!');
        return false;
    }

    // 創建 Image 對象
    var image = new Image();
    image.onload = function (ev) {
        loadTexture(gl, n, texture, u_Sampler, image);
    };

    image.src = '../resources/sky.JPG';

    return true;

}

上面的代碼負責配置和加載紋理,首先 創建紋理gl.createTexture(),然後通過 gl.getUniformLocation()從片元着色器中獲取 uniform變量 u_Sampler(取樣器)的存儲位置,用來接收紋理圖像;

可以通過 gl.deleteTexture()刪除一個紋理對象,注意:如果視圖刪除一個已經被刪除的紋理對象,不會報錯也不會產生影響

4、配置紋理參數

/* 五、設置紋理相關信息 */
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.clear(gl.COLOR_BUFFER_BIT);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);

}

以上代碼對紋理進行了配置,下面是具體過程

1、圖像Y軸反轉

圖像Y軸反轉是比較重要的一步,gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1),因爲webgl紋理座標系統的u軸,的方向和一些png、jpg等格式圖片的座標系統的y軸方向式相反的,如果不進行圖像y軸反轉則映射之後看到的式向下倒着的圖像;

如果你瞭解three.js這個webgl引擎的話,它的內部在你加載紋理貼圖的時候就已經進行y軸反轉了,可以在紋理貼圖部分看下這個Texture對象;

.flipY : boolean,默認爲true,翻轉圖像的Y軸以匹配WebGL紋理座標

2、激活紋理單元

webgl通過一種稱作 紋理單元的機制來同時使用多個紋理(在同一個幾何體平面),每個紋理單元有一個單元編號,來管理一張紋理圖像,即使只是用一張紋理貼圖,也要爲其指定一個紋理單元,默認情況下至少支持8個紋理單元;

gl.activeTexture(gl.TEXTURE0);//開啓0號紋理單元
3、綁定紋理對象

與綁定緩衝區很類似,在對緩衝區進行操作之前也需要綁定緩衝區對象

webgl支持兩種類型的紋理

  • gl.TEXTURE_2D 二維紋理
  • gl.TEXTURE_CUBE_MAP 立方體紋理
gl.bindTexture(gl.TEXTURE_2D,texture);//綁定紋理對象

實際上,在webgl中,你沒法直接操作紋理對象,必須通過將紋理對象綁定到紋理單元上,然後通過操作紋理單元來操作紋理對象;

4、紋理對象參數

綁定之後還需要對紋理對象做更具體的配置;

// 配置紋理參數
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  • 第一個參數是紋理的類型;
  • 第二個是獲取紋素顏色的方式;
  • 第三個是賦值給第二個參數的值;

參數二具體如下

  • 放大方法:gl.TEXTURE_MAG_FILTER,表示當紋理的繪製範圍比紋理本身更大時,獲取紋素顏色的方式;
  • 縮小方法:gl.TEXTURE_MIN_FILTER,表示當紋理的繪製範圍比紋理本身小的時,獲取紋素顏色的方式;
  • 水平填充方法:gl.TEXTURE_WRAP_S, 表示對紋理圖像左側或者右側的區域進行填充;
  • 垂直填充方法:gl.TEXTURE_WRAP_T,表示如何對紋理圖像上方和下方的區域進行填充;

設置方式

通過gl.texParameteri()來設置,上面參數的默認值如下,通常可以不需要調用gl.texParameteri()就可以使用默認值;

紋理參數 描述 默認值
gl.TEXTURE_MAG_FILTER 紋理放大 gl.LINEAR
gl.TEXTURE_MIN_FILTER 紋理縮小 gl.NEAREST_MIPMAP_LINEAR
gl.TEXTURE_WRAP_S 紋理水平填充 gl.REPEAT
gl.TEXTURE_WRAP_T 紋理垂直填充 gl.REPEAT

可賦值的常量值描述

1、可賦值給gl.TEXTURE_MAG_FILTERgl.TEXTURE_MIN_FILTER的常量如下

  • gl.NEAREST:使用原紋理上距離映射後像素中心最近的那個像素的顏色值,作爲新像素的值;
  • gl.LINEAR:使用距離新像素中興最近的四個像素的顏色值的加權平均值,作爲新像素的值,與gl.NEAREST相比,該方法圖像質量更好,但是會有較大的開銷;

2、可賦值給gl.TEXTURE_WRAP_Sgl.TEXTURE_WRAP_T的常量如下

  • gl.REPEAT :平鋪式重複紋理;
  • gl.MIRRORED_REPEAT :鏡像對稱式的重複紋理;
  • gl.CLAMP_TO_EDGE :使用紋理圖像邊緣值;
5、分配給紋理對象
// 分配紋理對象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
  • 參數一:紋理類型;
  • 參數二:默認0
  • 參數三:圖像內部格式;
  • 參數四:紋理數據的格式;
  • 參數五:紋理數據的類型;
  • 參數六:包含紋理圖像的Image對象;

紋素數據的格式

格式 描述
gl.RGB 紅、綠、藍
gl.RGBA 紅、綠、藍、透明度
gl.ALPHA (0.0, 0.0, 0.0, 透明度)
gl.LUMINACE LLL1L:流明
gl.LUMINANCE_ALPHA LLL、透明度

流明 :表示我們感知到的物體表面的亮度,通常使用物體表面紅、綠、藍顏色值的加權平均來計算流明;

然後,後面就是將紋理單元傳遞給取樣器變量u_Sampler

// 將0號紋理單元傳遞給取樣器變量
gl.uniform1i(u_Sampler, 0);
6、使用多福紋理

webgl可以同時處理多福紋理,因爲可以同時使用多個紋理單元,兩個紋理就需要兩個紋理單元;

頂點着色器不用修改,因爲兩幅紋理圖像所用的是一樣的紋理座標,只需要在片元着色器中再添加一個取樣器變量即可uniform sampler2D u_Sampler;

着色器代碼

precision mediump float;
uniform sampler2D u_Sampler;
uniform sampler2D u_Sampler1;//新增取樣器變量
varying vec2 v_TexCoord;
void main(){
    vec4 color = texture2D(u_Sampler,v_TexCoord);
    vec4 color1 = texture2D(u_Sampler,v_TexCoord);
    gl_FragColor = color * color1;//這是顏色矢量的相乘得到新的矢量
}

下面的操作和加載一張時一樣,只是需要等到兩張紋理全部加載進來之後,在開始繪製;

紋理加載基礎代碼

/* 一、着色器部分 */
// 頂點着色器
var vertex_shader_source = '' +
    'attribute vec4 a_Position;' +
    'attribute vec2 a_TexCoord;' +
    'varying vec2 v_TexCoord;' +
    'void main() {' +
    '   gl_Position = a_Position;' +
    '   v_TexCoord = a_TexCoord;' +
    '}';

// 片元着色器
var fragment_shader_source = '' +
    'precision mediump float;' +
    'uniform sampler2D u_Sampler;' +
    'varying vec2 v_TexCoord;' +
    'void main(){' +
    '   gl_FragColor = texture2D(u_Sampler, v_TexCoord);' +
    '}';

/* 二、 初始化着色器,設置頂點信息*/
(function () {

    // 獲取canvas對象
    var canvas = document.getElementById('webgl');

    // 獲取webgl 上下文對象
    var gl = getWebGLContext(canvas);

    // 初始化着色器
    if (!initShaders(gl, vertex_shader_source, fragment_shader_source)) {
        console.log('初始化着色器失敗!');
        return false;
    }

    // 設置頂點位置
    var n = initVertexBuffer(gl);
    if (n < 0) {
        console.log('頂點寫入緩存失敗!');
        return false;
    }

    // 指定清除顏色
    gl.clearColor(0.0, 0.5, 0.5, 1.0);

    // 配置紋理
    if (!initTextures(gl, n)){
        console.log('無法配置紋理!');
        return false;
    }

}());

/* 三、 設置頂點座標和紋理座標*/
function initVertexBuffer(gl) {

    // 創建頂點座標和紋理座標
    var verticesTexCoords = 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 vertexTexCoordBuffer = gl.createBuffer();
    if (!vertexTexCoordBuffer) {
        console.log('創建緩衝區失敗!');
        return -1;
    }

    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

    var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if (a_Position < 0) {
        console.log('獲取 attribute 變量 a_Position 失敗!');
        return -1;
    }

    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
    if (a_TexCoord < 0) {
        console.log('獲取 attribute 變量 a_TexCoord 失敗!');
        return -1;
    }

    gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
    gl.enableVertexAttribArray(a_TexCoord);

    return n;

}

/* 四、創建紋理對象並調用紋理繪製方法 */
function initTextures(gl, n) {

    // 創建紋理對象
    var texture = gl.createTexture();
    if (!texture) {
        console.log('創建紋理對象失敗!');
        return false;
    }

    // 獲取 u_Sampler 的存儲位置
    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    if (!u_Sampler) {
        console.log('獲取 uniform 變量 u_Sampler 失敗!');
        return false;
    }

    // 創建 Image 對象
    var image = new Image();
    image.onload = function (ev) {
        loadTexture(gl, n, texture, u_Sampler, image);
    };

    image.src = '../resources/sky.JPG';

    return true;

}

/* 五、設置紋理相關信息 */
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.clear(gl.COLOR_BUFFER_BIT);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);

}

示例鏈接:
1、紋理加載基礎示例
2、平鋪式紋理映射
3、鏡像紋理映射
4、使用多張紋理貼圖

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