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);
}