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_FILTER
和gl.TEXTURE_MIN_FILTER
的常量如下
gl.NEAREST
:使用原紋理上距離映射後像素中心最近的那個像素的顏色值,作爲新像素的值;gl.LINEAR
:使用距離新像素中興最近的四個像素的顏色值的加權平均值,作爲新像素的值,與gl.NEAREST
相比,該方法圖像質量更好,但是會有較大的開銷;
2、可賦值給gl.TEXTURE_WRAP_S
和gl.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 |
L 、L 、L 、1L :流明 |
gl.LUMINANCE_ALPHA |
L 、L 、L 、透明度 |
流明 :表示我們感知到的物體表面的亮度,通常使用物體表面紅、綠、藍顏色值的加權平均來計算流明;
然後,後面就是將紋理單元傳遞給取樣器變量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);
}