纹理坐标是 WebGL 中将 2D 图像(纹理)应用到 3D 物体表面的重要概念。在 WebGL 中,纹理坐标通常使用一个二维坐标系,称为 uv
坐标,它们决定了纹理图像如何映射到几何体上。理解纹理坐标的核心就是明白它们如何将二维纹理贴图应用到三维物体的表面。
文章目录
-
-
- 纹理坐标的基本概念
- 纹理坐标的作用
- 代码示例:立方体纹理映射
-
- [1. HTML 文件](#1. HTML 文件)
- [2. JavaScript 部分:WebGL 纹理坐标示例](#2. JavaScript 部分:WebGL 纹理坐标示例)
- [3. 代码解析](#3. 代码解析)
- [4. 纹理坐标的作用](#4. 纹理坐标的作用)
-
纹理坐标的基本概念
纹理坐标(u
, v
)是一个标准的二维坐标系统,用于描述纹理图像中每个像素(即纹理元素,Texel)的位置。u
和 v
是纹理的归一化坐标 ,即它们的值通常位于 [0, 1]
区间:
u
坐标 :控制纹理图像的水平位置,u = 0
是纹理的最左侧,u = 1
是纹理的最右侧。v
坐标 :控制纹理图像的垂直位置,v = 0
是纹理的底部,v = 1
是纹理的顶部。
例如,在纹理坐标 (u=0.5, v=0.5)
处表示纹理图像的中心。
纹理坐标的作用
在 WebGL 中,每个顶点都有一个对应的纹理坐标。顶点着色器负责将这些坐标传递到片元着色器,片元着色器利用纹理坐标来从纹理图像中获取相应的像素颜色,从而将图像"映射"到几何体表面。
代码示例:立方体纹理映射
下面是一个带有纹理坐标的立方体的简单例子,展示了如何将纹理图像映射到立方体的每个面。
1. HTML 文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL 纹理坐标示例</title>
</head>
<body>
<canvas id="webgl-canvas" width="500" height="500"></canvas>
<script src="main.js"></script>
</body>
</html>
2. JavaScript 部分:WebGL 纹理坐标示例
javascript
// 获取 WebGL 上下文
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
// 立方体顶点坐标 (x, y, z)
const vertices = new Float32Array([
-0.5, -0.5, -0.5, // 前面
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, 0.5, // 后面
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
]);
// 立方体的纹理坐标 (u, v)
// 每个顶点的纹理坐标。注意 u/v 范围是 [0, 1]
const texCoords = new Float32Array([
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // 前面
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // 后面
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // 左面
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // 右面
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // 上面
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // 下面
]);
// 立方体的索引,用于绘制每个面
const indices = new Uint16Array([
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
0, 1, 5, 0, 5, 4,
1, 2, 6, 1, 6, 5,
2, 3, 7, 2, 7, 6,
3, 0, 4, 3, 4, 7
]);
// 创建并绑定缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// 创建着色器程序
const vertexShaderSource = `
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = a_position;
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
`;
// 编译着色器并链接程序
function compileShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compilation failed', gl.getShaderInfoLog(shader));
}
return shader;
}
const vertexShader = compileShader(gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('Program linking failed', gl.getProgramInfoLog(shaderProgram));
}
// 使用着色器程序
gl.useProgram(shaderProgram);
// 获取属性和统一变量的位置
const positionLocation = gl.getAttribLocation(shaderProgram, 'a_position');
const texCoordLocation = gl.getAttribLocation(shaderProgram, 'a_texCoord');
const textureLocation = gl.getUniformLocation(shaderProgram, 'u_texture');
// 绑定位置数据
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
// 绑定纹理坐标数据
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texCoordLocation);
// 创建并绑定纹理
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
const image = new Image();
image.onload = () => {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
image.src = 'your-texture-image.jpg'; // 这里使用你自己的纹理图片路径
// 清除画布并绘制
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
// 绘制立方体
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
3. 代码解析
- 纹理坐标(texCoords) :每个顶点都有对应的纹理坐标,
texCoords
数组定义了每个面四个顶点的纹理坐标。在这个例子中,(u, v)
范围是 [0, 1],代表纹理图像的左下角 (0, 0) 到右上角 (1, 1)。
-
着色器:
- 顶点着色器:将顶点位置和纹理坐标传递到片元着色器。
- 片元着色器:根据纹理坐标,从纹理图像中采样颜色。
-
纹理加载 :在
image.onload
中加载图片,并将其绑定为 WebGL 的纹理。在加载完成后,WebGL 会自动生成多级渐远纹理(mipmap),提高纹理渲染的效率和质量。 -
绘制 :使用
gl.drawElements
绘制立方体的每个面。每个面由两个三角形组成,纹理坐标确保了纹理图像正确地映射到每个面。
4. 纹理坐标的作用
纹理坐标使得 WebGL 能够将一个 2D 图像(纹理)正确地映射到 3D 对象的表面。当你需要将图片或其他 2D 图像添加到你的 3D 模型时,理解和使用纹理坐标是非常重要的。纹理坐标的映射关系决定了纹理如何在模型表面分布,例如,是否是平铺、是否有旋转、是否有缩放等。