gl.texImage2D()
是 WebGL 中的一个核心函数,用于将图像数据上传到 2D 纹理对象中。它是最常用的纹理初始化和更新函数之一,支持多种数据源和格式,广泛用于创建纹理、更新纹理内容以及加载图像数据到 GPU。
函数定义
css
gl.texImage2D(
target,
level,
internalFormat,
width,
height,
border,
format,
type,
data
);
-
target
:指定纹理绑定的目标。常见的值包括:gl.TEXTURE_2D
:普通 2D 纹理。gl.TEXTURE_CUBE_MAP_POSITIVE_X
、gl.TEXTURE_CUBE_MAP_NEGATIVE_X
等:立方体贴图的六个面。
-
level
:指定 Mipmap 层级。level = 0
表示基本纹理,level > 0
表示 Mipmap 层级。 -
internalFormat
:指定纹理的内部存储格式。常见的格式包括:gl.RGB
、gl.RGBA
:未压缩的 RGB 或 RGBA 格式。gl.LUMINANCE
、gl.ALPHA
:单通道格式。- 压缩格式(如
gl.COMPRESSED_RGBA_S3TC_DXT5_EXT
)。
-
width
和height
:纹理的宽度和高度,以像素为单位。 -
border
:纹理的边框宽度。在 WebGL 中,这个值必须为 0。 -
format
:指定图像数据的格式,例如gl.RGB
、gl.RGBA
等。 -
type
:指定图像数据的类型,例如gl.UNSIGNED_BYTE
、gl.FLOAT
等。 -
data
:包含图像数据的ArrayBufferView
(如Uint8Array
)、ImageData
、HTMLImageElement
、HTMLCanvasElement
或HTMLVideoElement
。
示例代码
以下是一个使用 gl.texImage2D()
的示例,展示如何将图像数据上传到 2D 纹理中:
ini
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webglContext.texImage3D示例</title>
<style>
canvas {
width: 800px;
height: 600px;
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<img id="textureImage" src="./小狼狗.jpg" crossorigin="anonymous" style="display: none;">
<script>
// 获取 canvas 元素
const canvas = document.getElementById('glCanvas');
canvas.width = 800;
canvas.height = 600;
const gl = canvas.getContext('webgl2');
if (!gl) {
alert('获取渲染上下文失败! ');
throw new Error('获取渲染上下文失败! ');
}
async function main() {
try {
const imgData = await loadImageAndGetData('./小狼狗.jpg');
const texture = create2DTexture(imgData);
renderScene(texture);
} catch (error) {
console.error('加载失败:', error);
}
}
main();
// 渲染场景
function renderScene(texture) {
// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
// 创建着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const vertexSource = `#version 300 es
in vec3 aPosition;
in vec2 aTexCoord;
out vec2 vTexCoord;
void main() {
gl_Position = vec4(aPosition, 1.0);
vTexCoord = aTexCoord;
}
`;
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
//检查编译状态
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log('vertexshader 编译失败! ', gl.getShaderInfoLog(vertexShader));
gl.deleteShader(vertexShader);
return null;
}
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
const fragmentSource = `#version 300 es
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, vTexCoord);
// fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
//检查编译状态
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log('fragmentShader 编译失败! ', gl.getShaderInfoLog(fragmentShader));
gl.deleteShader(fragmentShader);
return null;
}
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// 检查链接状态
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('program 链接失败! ');
gl.deleteProgram(program);
return null;
}
gl.useProgram(program);
// 矩形数据
const vertices = new Float32Array([
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.5, 0.5, 0.0,
-0.5, 0.5, 0.0,
]);
const indices = new Uint16Array([
0, 1, 2,
2, 3, 0
]);
const texCoord = new Float32Array([
0.0, 1.0,
1.0, 1.0,
1.0, 0.0,
0.0, 0.0
]);
// 创建顶点缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
// 索引缓冲区
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(gl.getUniformLocation(program, 'uTexture'), 0);
// 材质坐标
const texBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer);
gl.bufferData(gl.ARRAY_BUFFER, texCoord, gl.STATIC_DRAW);
const texPositionLocation = gl.getAttribLocation(program, 'aTexCoord');
gl.enableVertexAttribArray(texPositionLocation);
gl.vertexAttribPointer(texPositionLocation, 2, gl.FLOAT, false, 0, 0);
// 绘制矩形
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}
// 创建 3D 纹理
function create2DTexture(img) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 设置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
// 上传图像数据到3D纹理
gl.texImage2D(
gl.TEXTURE_2D, // 目标
0, // level, 指定详细级别。0 级是基本图像等级,n 级是第 n 个金字塔简化级。
gl.RGBA, // 内部格式
img.width, // 数据宽(像素个数)
img.height, // 数据高(像素个数 )
0, // 指定纹理的边框宽度。必须为 0。历史接口遗留
gl.RGBA, // 格式
gl.UNSIGNED_BYTE, // 数据类型
img.data, // 数据
);
return texture;
}
// 加载本地的图像
function loadImageAndGetData(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域问题
img.onload = function() {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 获取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixelData = imageData.data; // Uint8ClampedArray
resolve({
width: img.width,
height: img.height,
data: pixelData
});
};
img.onerror = reject;
img.src = url;
});
}
</script>
</body>
</html>
使用 HTML 图像加载纹理
gl.texImage2D()
也可以直接从 HTML 元素(如 <img>
、<canvas>
或 <video>
)加载纹理数据。以下是一个从 <img>
元素加载纹理的示例:
1. 创建 HTML 图像元素
ini
<img id="textureImage" src="texture.jpg" crossorigin="anonymous">
2. 在 JavaScript 中加载纹理
ini
const image = document.getElementById('textureImage');
image.onload = function() {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
gl.TEXTURE_2D, // 目标
0, // Mipmap 层级
gl.RGBA, // 内部格式
gl.RGBA, // 格式
gl.UNSIGNED_BYTE, // 类型
image // 数据(HTMLImageElement)
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
};
关键点解释
-
纹理初始化:
gl.texImage2D()
可以用于初始化纹理,也可以用于更新整个纹理的内容。- 如果需要更新纹理的子区域,可以使用
gl.texSubImage2D()
。
-
内部格式与数据格式:
internalFormat
指定了纹理在 GPU 中的存储格式。format
和type
指定了上传数据的格式和类型。
-
Mipmap:
- 如果需要使用 Mipmap,可以在上传基本纹理后调用
gl.generateMipmap()
生成 Mipmap 层级。
- 如果需要使用 Mipmap,可以在上传基本纹理后调用
-
数据源:
data
参数可以是ArrayBufferView
、ImageData
、HTMLImageElement
、HTMLCanvasElement
或HTMLVideoElement
。
注意事项
-
纹理大小:
- WebGL 要求纹理的宽度和高度必须是 2 的幂(除非启用了
gl.getExtension('EXT_texture_filter_anisotropic')
)。
- WebGL 要求纹理的宽度和高度必须是 2 的幂(除非启用了
-
性能优化:
- 如果需要频繁更新纹理内容,建议使用
gl.texSubImage2D()
更新纹理的子区域,而不是重新上传整个纹理。
- 如果需要频繁更新纹理内容,建议使用