更新于 

纹理

在矩阵表面贴上图像

几个名词
  • 纹理映射(texture mapping): 根据纹理图像,为光栅化后的每个片元填充适当的颜色
  • 纹理(texture)/纹理图像(texture image)
  • 纹素(texels,texture elements): 组成纹理图像的像素
  • 纹理坐标(texture coordinates): 决定纹理图像哪个部分用于覆盖
纹理坐标

表示图像上的坐标,同样进行了归一化处理,
为了和xy坐标区分,WebGL使用 st坐标系统

纹理映射步骤
将纹理坐标映射到顶点上
将纹理坐标映射到顶点上

step1-提供纹理

准备好映射到几何图形上的纹理图像。

step2-配置映射

为几何图形配置纹理映射方式。

step3-加载纹理

加载纹理图像并对其进行一些配置。

step4-抽取纹素

在片元着色器中将相应纹素从纹理中抽取出来,赋值给片元。

TexturedQuad

TexturedQuad
TexturedQuad运行效果
TexturedQuad运行效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/cuon-matrix.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="./TexturedQuad.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="300" height="300"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

// 第1部分:设置顶点着色器
let VSHADER_SOURCE =
`
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main(){
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
`;

// 第2部分:设置片元着色器
let FSHADER_SOURCE =
`
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main(){
gl_FragColor = texture2D(u_Sampler,v_TexCoord);
}
`;

// 第3部分:创建坐标缓冲区
function main() {
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
let n = initVertexBuffers(gl);
if (n < 0) {
console.log('变量获取失败');
return;
}
if (!initTextures(gl, n)) {
console.log('纹理映射失败');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
}

function initVertexBuffers(gl) {
let 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
]);
let n = 4;
let FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
let vertexTexCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position);

let a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord);
return n;
}
// 第4部分:准备纹理
function initTextures(gl, n) {
// 创建纹理对象
let texture = gl.createTexture();

// 获取u_Sampler
let u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');

// 创建image
let image = new Image();

// 注册图像加载事件的响应函数
image.onload = function () {
loadTexture(gl, n, texture, u_Sampler, image);
};
// 浏览器加载图像
image.src = '../img/tobydog.jpg';
return true
}

// 第5部分:使用纹理
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);
}
步骤解析
设置纹理坐标:initVertexBuffers()

将顶点坐标分配给a_Position并开启:

1
2
3
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position);

将纹理坐标分配给a_TexCoord并开启:

1
2
3
let a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord);
配置和加载纹理:initTexture()
gl.createTexture()

用于创建纹理对象。

纹理图像管理单元

一共有8个管理纹理图像的纹理单元:gl.TEXTURE[0-7]
每一个都与 gl.TEXTURE_2D 相关,
gl.TEXTURE_2D是 绑定纹理时的纹理目标

使用 gl.deleteTexture() 删除纹理对象:

异步加载纹理图像

处于安全考虑,WebGL不运行跨域请求纹理图像。

OpenGL程序WebGL网页 读取纹理图片的区别:

OpenGL
直接从存储纹理图像的磁盘上读取纹理图像。

WebGL
一般情况下需要向浏览器请求图像资源。

为WebGL配置纹理:loadTexture()

``

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}
纹理配置详细步骤
STEP1:图像Y轴反转
1
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);
为什么要进行Y轴反转?

答: WebGL与一般格式图片的y轴方向 相反,需要将图片y轴反转才能进行正确映射,或者手动反转映射坐标。

  • WebGL Y(t)轴方向:
  • 图片系统 Y轴方向:
STEP2:激活纹理单元
1
gl.activeTexture(gl.TEXTURE0);
纹理单元(texture unit)是用来干嘛的?

答: 是WebGL用于 同时使用多个纹理 的一种机制。
每个纹理单元有一个 单元编号 来管理一张纹理图像,
系统支持的纹理单元的个数取决于:

  • 硬件
  • 浏览器的WebGL实现 (通常是8个)
STEP3:绑定纹理对象
1
gl.bindTexture(gl.TEXTURE_2D,texture);
WebGL的两种纹理类型

可以看到,纹理对象的绑定实际上有2步:

  1. 纹理对象的开启
  2. 纹理单元纹理对象 的绑定
无法直接操作纹理对象

必须将纹理对象绑定到纹理单元上,通过操作纹理单元来操作纹理对象。

STEP4:配置纹理对象的参数
1
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
配置涉及的内容都包括哪些?
  • 纹素获取方式
  • 纹理填充方式

纹理参数可以指定4个值:

纹理获取方式 纹理填充方式
方法名 纹理参数 描述 默认值 图解 参数值 参数描述
放大方法 gl.TEXTURE_MAG_FILTER 小纹理绘制大范围,
填充空隙
gl.LINEAR
gl.NEAREST 以中心像素为锚点,
按照点位与中心点的曼哈顿距离大小映射
gl.LINEAR 距像素中心最近的四个像素颜色值加权平分求出的,质量更好,开销更大
缩小方法 gl.TEXTURE_MIN_FILTER 大纹理绘制小范围,
剔除像素
gl.NEAREST_MIPMAP_LINEAR
gl.NEAREST -
gl.LINEAR -
水平填充 gl.TEXTURE_WRAP_S 左右侧区域的填充 gl.REPEAT
gl.REPEAT 平铺式的重复纹理
gl.MIRRORED_REPEAT 镜像对称式的重复纹理
gl.CLAMP_TO_EDGE 使用纹理图像边缘值
垂直填充 gl.TEXTURE_WRAP_T 上下区域的填充 gl.REPEAT -
gl.MIRRORED_REPEAT -
gl.CLAMP_TO_EDGE -
MIPMAP(金字塔)纹理类型

实际上是原始纹理图像的一系列不同分辨率的版本。

曼哈顿距离

即直角距离,棋盘距离,(x1,y1)与(x2,y2)的曼哈顿距离为|x1-x2|+|y1-y2|。

STEP5:将纹理图像分配给纹理对象
1
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image);
纹理数据格式(internalformat)

纹理数据格式的选择要根据 纹理图像的格式

  • PNG 通常使用 gl.RGBA
  • JPG、BMP 通常使用 gl.RGB
  • 灰度图 通常使用 gl.LUMINANCE/gl.LUMINANCE_ALPHA
流明(luminance)

表示物体表面亮度,通常使用物体表面红绿蓝分量值加权平均计算流明。

WebGL中,internalformat必须和format一样。

纹理数据类型(type)

除了UNSIGNED_BYTE数据类型,其他格式基本都遵循16比特规则:
将RGB三分量压缩入16比特中
设置不同格式目的是为了 压缩数据,减少加载时间

STEP6:将纹理单元传递给片元着色器
sampler2D
1
uniform sampler2D u_Sampler;
  • uniform 表示纹理图像不会随片元变化
  • sampler2D 是一种专用于纹理对象的数据类型
uniform1i
1
gl.uniform1i(u_Sampler,0); // 0 表示 gl.TEXTURE0

对于sampler类型,必须将其指定的 纹理单元编号(texture unit number) 传给着色器。

STEP7:从顶点着色器向片元着色器传输纹理坐标
1
2
3
4
5
6
7
8
9
10
let VSHADER_SOURCE =
`
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main(){
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
`

传递

Vertex_Shader获取到纹理坐标a_TexCoord后,
通过v_TexCoord传递到Fragment_Shader。

内插

虽然只明确传入了绘制范围的顶点纹理坐标,
但是片元的纹理坐标会在光栅化的过程中内插出来。

抽取后填色

根据计算出的片元纹理坐标,从纹理图像上抽取出纹素的颜色,然后涂抹到片元上。

STEP8:在片元着色器中获取纹理像素颜色
1
gl_FragColor = texture2D( u_Sampler, v_TexCoord);

texture2D是GLSL ES的内置函数,
它只需要知道两个参数: 纹理单元编号纹理坐标,就能够取得纹理上的像素颜色。

OS:基本原理就和查字典一样,知道大体的模块,再知道具体坐标,就能提取到一切你需要查询的信息。

texture2D的返回值

就必须要提到之前对WebGL纹理进行配置的 texParameteritexImage2D
texParameteri纹理参数(pname) 直接决定WebGL求取片元纹理坐标内插值的方式。
texImage2D纹理数据格式(internalformat) 直接决定 texture2D返回值类型

小实验
TexturedQuad_Repeat
1
2
3
4
5
6
let verticesTexCoords = new Float32Array([
-0.5, 0.5, -0.3, 1.7,
-0.5, -0.5, -0.3, -0.2,
0.5, 0.5, 1.7, 1.7,
0.5, -0.5, 1.7, -0.2
]);
TexturedQuad_Repeat运行结果
TexturedQuad_Repeat运行结果

之所以会重复,是因为 gl.TEXTURE_WRAP_Sgl.TEXTURE_WRAP_T 都设置为了 gl.REPEAT

纹理坐标示意图
纹理坐标示意图
TexturedQuad_Mirror
1
2
3
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
TexturedQuad_Mirror运行结果
TexturedQuad_Mirror运行结果

MultiTexture

本案例主要介绍如何同时处理多幅纹理

MultiTexture Code
MultiTexture运行结果
MultiTexture运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/cuon-matrix.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="./MultiTexture.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="300" height="300"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
let VSHADER_SOURCE =
`
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main(){
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
`;
let FSHADER_SOURCE =
`
precision mediump float;
uniform sampler2D u_Sampler0;
uniform sampler2D u_Sampler1;
varying vec2 v_TexCoord;
void main(){
vec4 color0 = texture2D(u_Sampler0,v_TexCoord);
vec4 color1 = texture2D(u_Sampler1,v_TexCoord);
gl_FragColor = color0 * color1;
}
`;
function main() {
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
let n = initVertexBuffer(gl);
if (!initTexture(gl, n)) {
console.log('纹理映射失败');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
}
function initVertexBuffer(gl) {
let 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
]);
let n = 4;
let FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
let vertexTexCoordBuffer = gl.createBuffer();
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
let a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');

gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position);
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord);
return n;
}
function initTexture(gl, n) {
let texture0 = gl.createTexture();
let texture1 = gl.createTexture();
let u_Sampler0 = gl.getUniformLocation(gl.program, 'u_Sampler0');
let u_Sampler1 = gl.getUniformLocation(gl.program, 'u_Sampler1');

let image0 = new Image();
let image1 = new Image();

image0.onload = function () {
loadTexture(gl, n, texture0, u_Sampler0, image0, 0);
}
image1.onload = function () {
loadTexture(gl, n, texture1, u_Sampler1, image1, 1);
}
image0.src = '../img/tobydog.jpg';
image1.src = '../img/blueflower.jpg';
return true;
}
let g_texUnit0 = false;
let g_texUnit1 = false;
function loadTexture(gl, n, texture, u_Sampler, image, texUnit) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
if (texUnit === 0) {
gl.activeTexture(gl.TEXTURE0);
g_texUnit0 = true;
} else {
gl.activeTexture(gl.TEXTURE1);
g_texUnit1 = true;
}
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(u_Sampler, texUnit);
gl.clear(gl.COLOR_BUFFER_BIT);
if (g_texUnit0 && g_texUnit1) {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}
}
GLSL ES中的vec4矢量的分量乘法