webgl2 方法解析: texImage2D()

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_Xgl.TEXTURE_CUBE_MAP_NEGATIVE_X 等:立方体贴图的六个面。
  • level :指定 Mipmap 层级。level = 0 表示基本纹理,level > 0 表示 Mipmap 层级。

  • internalFormat:指定纹理的内部存储格式。常见的格式包括:

    • gl.RGBgl.RGBA:未压缩的 RGB 或 RGBA 格式。
    • gl.LUMINANCEgl.ALPHA:单通道格式。
    • 压缩格式(如 gl.COMPRESSED_RGBA_S3TC_DXT5_EXT)。
  • widthheight:纹理的宽度和高度,以像素为单位。

  • border:纹理的边框宽度。在 WebGL 中,这个值必须为 0。

  • format :指定图像数据的格式,例如 gl.RGBgl.RGBA 等。

  • type :指定图像数据的类型,例如 gl.UNSIGNED_BYTEgl.FLOAT 等。

  • data :包含图像数据的 ArrayBufferView(如 Uint8Array)、ImageDataHTMLImageElementHTMLCanvasElementHTMLVideoElement

示例代码

以下是一个使用 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);
};

关键点解释

  1. 纹理初始化

    • gl.texImage2D() 可以用于初始化纹理,也可以用于更新整个纹理的内容。
    • 如果需要更新纹理的子区域,可以使用 gl.texSubImage2D()
  2. 内部格式与数据格式

    • internalFormat 指定了纹理在 GPU 中的存储格式。
    • formattype 指定了上传数据的格式和类型。
  3. Mipmap

    • 如果需要使用 Mipmap,可以在上传基本纹理后调用 gl.generateMipmap() 生成 Mipmap 层级。
  4. 数据源

    • data 参数可以是 ArrayBufferViewImageDataHTMLImageElementHTMLCanvasElementHTMLVideoElement

注意事项

  1. 纹理大小

    • WebGL 要求纹理的宽度和高度必须是 2 的幂(除非启用了 gl.getExtension('EXT_texture_filter_anisotropic'))。
  2. 性能优化

    • 如果需要频繁更新纹理内容,建议使用 gl.texSubImage2D() 更新纹理的子区域,而不是重新上传整个纹理。
相关推荐
康康的幸福生活3 小时前
webgl2 方法解析: colorMask()
webgl
Mapmost3 小时前
全新升级!3DTiles加载速度Mapmost完胜Cesium
性能优化·webgl·cesium
点量云实时渲染-小芹2 天前
UE/Unity/Webgl云渲染推流网址,如何与外部网页嵌套和交互?
unity·webgl·webgl云渲染网页交互·点量云流
小old弟3 天前
🚀🚀🚀WebGL 加载 glTF 模型
前端·webgl
放逐者-保持本心,方可放逐7 天前
webgl(three.js 与 cesium 等实例应用)之浏览器渲染应用及内存释放的关联与应用
开发语言·javascript·webgl·顶点着色器·three.js 释放·cesium 释放·片元着色器
新中地GIS开发老师9 天前
三维GIS开发cesium智慧地铁教程(4)城市白模加载与样式控制
学习·arcgis·智慧城市·webgl·gis开发·webgis·地理信息科学
ak啊9 天前
WebGL入门教程:实现场景中相机的视角与位置移动
前端·webgl
魂断蓝桥66613 天前
如何基于three.js(webgl)引擎架构,实现3D密集架库房,3D档案室智能巡检
webgl·threejs·3d建筑·3d档案室·3d定位、三维室内定位、3d建筑·3d库房·3d密集架
ak啊14 天前
WebGL魔法:从立方体到逼真阴影的奇妙之旅
前端·webgl