webgl 纹理贴图

webgl 纹理贴图

在三维图形学中,有一项很重要的技术就是:就是将一张图像(就像一张贴纸)映射(贴)到一个几何图形的表面上去。这就是纹理映射(texture mapping)。此时,这张图片又可以称为纹理图像(texture image)或纹理(texture)。

纹理坐标
纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色。WebGL 系统中的纹理坐标系统是二维的。为了将纹理坐标和广泛使用的x坐标和y坐标区分开来,WebGL 使用s和t命名纹理坐标(st坐标系统)

image-20210926142214518

纹理图像四个角的坐标为左下角(0.0,0.0),右下角(1.0,0.0),右上角(1.0,1.0)和左上角(0.0,1.0)。纹理坐标与图像自身的尺寸无关,不管是128×128还是128×256的图像,其纹理坐标始终是左下角(0.0,0.0),右下角(1.0,0.0),右上角(1.0,1.0)和左上角(0.0,1.0)

纹理贴图的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var texture = gl.createTexture(); //创建一个纹理 texture

gl.bindTexture(gl.TEXTURE_2D, texture); //表示下面的所有操作都是针对 texture 对象的
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); // 把加载好的图像放到 texture 中
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); //配置纹理参数

gl.bindTexture(gl.TEXTURE_2D, null); // texture的操作完成,要解除webgl状态机的绑定,不然下面操作的都一直会是 texture 这个纹理


// 把纹理传递到shader 中
gl.activeTexture(gl.TEXTURE0); // 激活 0 号纹理单元 一共有32个纹理单元
gl.bindTexture(gl.TEXTURE_2D, texture); //表示下面的所有操作都是针对 texture 对象的 , 这时候 texture 会和 0号纹理单元绑定
var u_Sampler = gl.getUniformLocation(shaderProgram, 'u_Sampler');
gl.uniform1i(u_Sampler, 0); //告诉shader 去 0 号纹理单元 取纹理



完整代码:

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
109
110
111
112
113
114
115
116
<html>
<head>
<meta charset="utf-8">
<title></title>

<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
</script>

<script id="fragmentShader" type="x-shader/x-fragment">

#ifdef GL_ES
precision mediump float;
#endif

uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
</script>
</head>
<body>

<canvas id='glcanvas' width='600' height='600'></canvas>

<script>
var canvas = document.getElementById('glcanvas');
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');


//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;

//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;

//创建顶点着色器对象
var vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, vertexShaderSource); //引入顶点着色器源代码
gl.compileShader(vShader); ////编译顶点着色器

//创建片元着色器对象
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, fragShaderSource); //引入片元着色器源代码
gl.compileShader(fShader); //编译片元着色器


var shaderProgram = gl.createProgram(); //创建程序对象program

//附着顶点着色器和片元着色器到program
gl.attachShader(shaderProgram, vShader);
gl.attachShader(shaderProgram, fShader);

gl.linkProgram(shaderProgram); // 链接program
gl.useProgram(shaderProgram); // 使用program

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 FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;

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

//将顶点坐标分配给 a_Position
var a_Position = gl.getAttribLocation(shaderProgram, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position);

//将纹理坐标分配给 a_TexCoord并开启之
var a_TexCoord = gl.getAttribLocation(shaderProgram, 'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord);

//清空画板
gl.clearColor(0.0, 0.0, 0.0, 1.0); //设置清空颜色缓冲时的颜色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

//创建纹理对象
var texture = gl.createTexture(); // Create a texture object
var u_Sampler = gl.getUniformLocation(shaderProgram, 'u_Sampler');
var image = new Image(); // Create the image object
if (!image) {
console.log('Failed to create the image object');
}
image.onload = function() {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 对纹理图像进行y轴反转
gl.activeTexture(gl.TEXTURE0); // 开启 0 号纹理单元
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);
var n = 4;
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
};
image.src = './resources/sky.jpg';
</script>
</body>
</html>

Y轴反转

纹理也有一套自己的坐标系统,为了和顶点坐标加以区分,通常把纹理坐标称为 UVU 代表横轴坐标,V 代表纵轴坐标。


在使用图像之前,必须对它进行Y轴反转。

image-20210926152510582

1
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 对纹理图像进行y轴反转

纹理参数

1
2
3
void gl.texImage2D(target, level, internalformat, format, type, ImageData );

void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData);

target

  • gl.TEXTURE_2D 二维纹理贴图
  • gl.TEXTURE_CUBE_MAP_POSITIVE_X:立方体映射纹理的正X面。
  • gl.TEXTURE_CUBE_MAP_NEGATIVE_X: 立方体映射纹理的负X面。
  • gl.TEXTURE_CUBE_MAP_POSITIVE_Y: 立方体映射纹理的正Y面。
  • gl.TEXTURE_CUBE_MAP_NEGATIVE_Y: 立方体映射纹理的负Y面
  • gl.TEXTURE_CUBE_MAP_POSITIVE_Z: 立方体映射纹理的正Z面。
  • gl.TEXTURE_CUBE_MAP_NEGATIVE_Z: 立方体映射纹理的负Z面。

level

人眼距离这个纹理越近,图片的精度就越高,使用级别高的纹理。反之精度越小,使用级别低的纹理。

0级是基本图像等级,

n级是第n个金字塔简化级。LOD 层次结构,值越小,人眼距离这个纹理越近,看到的图片越精细,使用精度高的纹理。值越大,人眼距离这个纹理越远,看到的图片越模糊,使用精度低的纹理。

image-20211218105635128

width、height

图片的宽高,如果不指定,就使用 ImageData 自带的宽高

border
纹理的边框宽度。必须为 0。

internalformat

这张纹理存储的格式

format

这张图片文件(也就是image)是什么格式

type

每个分量的精度

WebGLRenderingContext.texImage2D() - Web API 接口参考 | MDN (mozilla.org)

1
gl.texParameterf(GLenum target, GLenum pname, GLfloat param);

target gl.TEXTURE_2D 二维纹理 gl.TEXTURE_CUBE_MAP 立方体纹理.

pname 纹理参数

  • 放大方法(gl.TEXTURE_MAG_FILTER)∶这个参数表示,当纹理的绘制范围比纹理本
身更大时,如何获取纹素颜色。比如说,你将 16×16的纹理图像映射到32×32 像素的空间里时,纹理的尺寸就变成了原始的两倍。WebGL 需要填充由于放大而造成的像素间的空隙,该参数就表示填充这些空隙的具体方法。

  • 缩小方法(gl.TEXTURE_MIN_FILTER)∶这个参数表示,当纹理的绘制范围比纹理本
身更小时,如何获取纹素颜色。比如说,你将32×32的纹理图像映射到16×16 像素的空间里,纹理的尺寸就只有原始的一半。为了将纹理缩小,WebGL 需要剔除纹理图像中的部分像素,该参数就表示具体的剔除像素的方法。
  • 水平填充方法(g1.TEXTURE_WRAP_S )∶这个参数表示,如何对纹理图像左侧或右侧的区域进行填充。
  • 垂直填充方法(g1.TEXTURE_WRAP_T)∶这个参数表示,如何对纹理图像上方和下
方的区域进行填充。

param 纹理参数的值

默认值

| gl.TEXTURE_MAG_FILTER | 纹理放大时的算法法 | gl.LINEAR (默认值),
gl.NEAREST. | 这个参数表示,当纹理的绘制范围比纹理本
身更大时,如何获取纹素颜色。比如说,你将 16×16的纹理图像映射到32×32 像素的空间里时,纹理的尺寸就变成了原始的两倍。WebGL 需要填充由于放大而造成的像素间的空隙,该参数就表示填充这些空隙的具体方法。
|
| gl.TEXTURE_MIN_FILTER | 缩小方法 | gl.LINEAR,
gl.NEAREST,
gl.NEAREST_MIPMAP_NEAREST,
gl.LINEAR_MIPMAP_NEAREST,
gl.NEAREST_MIPMAP_LINEAR (默认值),
gl.LINEAR_MIPMAP_LINEAR. | 这个参数表示,当纹理的绘制范围比纹理本
身更小时,如何获取纹素颜色。比如说,你将32×32的纹理图像映射到16×16 像素的空间里,纹理的尺寸就只有原始的一半。为了将纹理缩小,WebGL 需要剔除纹理图像中的部分像素,该参数就表示具体的剔除像素的方法。 |
| gl.TEXTURE_WRAP_S | 水平填充方法 | gl.REPEAT (默认值),
gl.CLAMP_TO_EDGE,
gl.MIRRORED_REPEAT. | 这个参数表示,如何对纹理图像左侧或右侧的区域进行填充。 |
| gl.TEXTURE_WRAP_T | 垂直填充方法 | gl.REPEAT (默认值),
gl.CLAMP_TO_EDGE,
gl.MIRRORED_REPEAT. | 这个参数表示,如何对纹理图像上方和下
方的区域进行填充。 |

image-20210926154133475

g1.NEAREST 使用原纹理上距离映射后像素(新像素)中心最近的那个像素的颜色值,作为新像素的值(使用曼哈顿距离’。)
gl.LINEAR 使用距离新像素中心最近的四个像素的颜色值的加权平均,作为新像素的值(与 g1.NEAREST 相比.该方法图像质量更好,但是会有较大的开销。)
gl.REPEAT 平铺式的重复纹理
gl.MIRRORED_REPEAT 镜像对称式的重复纹理
gl.CLAMP_TO_EDGE 使用纹理图像边缘值

例子

1
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); //配置纹理参数

纹理过渡的方法

在使用纹理贴图的过程中,纹理的大小和WebGL绘制的图形的大小并不能完全一致,,可能纹理对象大,也可能纹理对象小。

  • 当纹理图像大于WebGL图形时,WebGL图形的一个片元与许多纹理像素映射。
  • 当纹理图像小于WebGL图形时,WebGL图形的许多个片元会和同一纹理的像素映射。

当以上情况发生时,纹理的像素点并不能和图形的像素点一一对应。对于缺失或者多余的像素点,WebGL提供了几种处理方法。

gl.NEAREST (最近点采样)

当纹理被放大或者缩小时,WebGL会在原始图像上根据片元着色器中的纹理坐标来寻找最近的像素点来决定片元的颜色。

优点

  • 算法简单,计算量也小

缺点

  • 当纹理被放大多倍时,容易产生明显的锯齿。
  • 纹理缩小很多时,图像会变得模糊不清。
img

gl.LINEAR (线性采样)

线性采样时,WebGL会使用原始图像上距离纹理坐标最近的多个像素点的颜色的加权平均值作为片元的颜色。比如,在一个白的和一个黑的像素之间的像素会被输出为灰色。

优点:

  • 不会出现锯齿,而是平滑过渡
img

**Mipmaps 多重细节纹理 **(缩小时的纹理过滤方法)

  • gl.NEAREST_MIPMAP_NEAREST。 从一系列纹理中找到尺寸最接近的一个,然后采用gl.NEAREST方式从该纹理中获取像素点
  • gl.NEAREST_MIPMAP_LINEAR。 从一系列纹理中找到尺寸最接近的一个,然后采用gl.LINEAR方式从该纹理中获取像素点
  • gl.LINEAR_MIPMAP_NEAREST。 从一系列纹理中找到尺寸最接近的两个,然后采用gl.NEAREST方式从两个纹理中获取像素点颜色,然后通过加权平均获取两个像素点颜色的平均值。
  • gl.LINEAR_MIPMAP_LINEAR。 从一系列纹理中找到尺寸最接近的两个,然后采用gl.LINEAR方式从两个纹理中获取像素点颜色,然后通过加权平均获取两个像素点颜色的平均值。

填充方法

gl.REPEAT

超出纹理范围的坐标整数部分被忽略,形成重复效果。这是默认的填充方式

image-20211218113532540

gl.CLAMP_TO_EDGE(边缘值填充)

超出纹理范围的坐标被截取成0(昨面)和1(右面),形成纹理边缘延伸的效果。

image-20211218113325522

gl.MIRRORED_REPEAT

超出纹理范围的坐标整数部分被忽略,但当整数部分为奇数时进行取反,形成镜像效果

image-20211218113745048

webgl 纹理贴图
http://example.com/2024/05/23/webgl/webgl 纹理贴图/
Author
John Doe
Posted on
May 23, 2024
Licensed under