1. 纹理坐标(st坐标)简介
ST纹理坐标(也称为UV坐标)是一种二维坐标系统,用于在三维模型的表面上精确地定位二维纹理图像。这种坐标系统通常将纹理的左下角映射到(0,0),而右上角映射到(1,1)。
- S坐标(U坐标):通常对应纹理图像的水平方向,即纹理的宽度。
- T坐标(V坐标):通常对应纹理图像的垂直方向,即纹理的高度。
1.1 纹理坐标的工作原理
- 纹理坐标的范围通常是从0.0到1.0,其中(0.0, 0.0)代表纹理的左下角,而(1.0, 1.0)代表右上角。
- 在WebGL中,纹理坐标系统的t轴(垂直轴)与传统图像文件的y轴方向相反,这意味着当你在WebGL中使用纹理时,通常需要翻转图像的Y轴以确保正确的映射。
- 这可以通过设置gl.UNPACK_FLIP_Y_WEBGL为1来实现。
2. 基本步骤
2.1 创建纹理对象
- 使用gl.createTexture()创建一个新的纹理对象。
- 可以通过 gl.deleteTexture(textrue)来删除纹理对象。
const texture = gl.createTexture();
2.2 翻转图片Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
2.3 开启纹理单元
gl.activeTexture(gl.TEXTURE0)
2.4绑定纹理对象
使用gl.bindTexture(type, texture)将纹理对象绑定到纹理单元上。type 参数有以下两种:
- gI.TEXTURE_2D:二维纹理
- gI.TEXTURE_CUBE_MAP:立方体纹理
gl.bindTexture(gl.TEXTURE_2D, texture);
2.5 配置纹理参数
设置纹理的过滤方式(gl.TEXTURE_MIN_FILTER和gl.TEXTURE_MAG_FILTER)、包裹方式(gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T)等参数。
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2.6 上传纹理图像数据
使用gl.texImage2D(type, level,internalformat, format,dataType, image)将图像数据上传到纹理对象中。
2.6.1 type参数:
- gI.TEXTURE_2D:二维纹理
- gI.TEXTURE_CUBE_MAP:立方体纹理
2.6.2 level参数:
- 默认为0
2.6.3 internalformat参数
- gI.RGB
- gI.RGBA
- gI.ALPHA
- gI.LUMINANCE 使用物体表面的红绿蓝 分量的加权平均值来计算
- gI.LUMINANCE ALPHA
2.6.4 format参数
- format 纹理的内部格式,必须和internalformat 相同
2.6.5 dataType参数
- 9I.UNSIGNED_BYTE
- gI.UNSIGNED_SHORT_5_6_5
- gI.UNSIGNED_SHORT_4_4_4_4
- gI.UNSIGNED_SHORT_5_5_5_1
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
3. 实例代码
以下是一个简单的WebGL示例,展示了如何给一个简单的四边形添加背景图:
/** @type {HTMLCanvasElement} */
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
attribute vec4 aPosition;
attribute vec4 aTex;
varying vec2 vTex;
void main() {
gl_Position = aPosition;
vTex = vec2(aTex.x, aTex.y);
}`
// 片源着色器源码
const fragmentShaderSource = `
precision lowp float;
uniform sampler2D uSampler;
varying vec2 vTex;
void main() {
gl_FragColor = texture2D(uSampler, vTex);
}`
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aTex = gl.getAttribLocation(program, 'aTex');
const uSampler = gl.getUniformLocation(program, 'uSampler');//赋值
const points = 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,
])
const buffer = gl.createBuffer();
const BYTES = points.BYTES_PER_ELEMENT; // 偏移字节
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0);
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aTex, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2);
gl.enableVertexAttribArray(aTex);
const img = new Image();
img.onload = function () {
// 创建纹理对象并加载图片
const texture = gl.createTexture();
// 翻转图片Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
// 开启一个单元纹理
gl.activeTexture(gl.TEXTURE0)
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 传纹理图像数据
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
gl.uniform1i(uSampler, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
img.src = './images/img2.png';
4.效果如下
5. 添加多张背景图
- 添加几张图,就定义几个sampler2D
// 片源着色器源码
const fragmentShaderSource = `
precision lowp float;
uniform sampler2D uSampler;
uniform sampler2D uSampler1;
varying vec2 vTex;
void main() {
vec4 c1 = texture2D(uSampler, vTex);
vec4 c2 = texture2D(uSampler1, vTex);
texture2D(uSampler1, vTex);
gl_FragColor = c1 * c2;
}`
- 将创建纹理的过程封装方法
// 封装函数
function getImage(location, url, index) {
// Promise
return new Promise(resolve => {
const img = new Image();
img.onload = function () {
// 创建纹理对象并加载图片
const texture = gl.createTexture();
// 翻转图片Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
// 开启一个单元纹理
gl.activeTexture(gl[`TEXTURE${index}`])
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 传纹理图像数据
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
gl.uniform1i(location, index);
resolve();
}
img.src = url;
});
}
Promise.all([getImage(uSampler, "./images/img.png", 0), getImage(uSampler1, "./images/img2.png", 1)]).then(() => {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
})
效果如下