webgl2 方法解析: readPixels()

206 阅读3分钟

readPixels() 是 WebGL 2 中的一个非常重要的函数,用于从帧缓冲区(Frame Buffer Object,FBO)或默认的绘图缓冲区中读取像素数据。它通常用于实现一些高级图形效果,如屏幕截图、像素级操作、后处理等。

1. readPixels() 的基本语法

void gl.readPixels(
    GLint x,         // 指定读取像素的左下角的 x 坐标
    GLint y,         // 指定读取像素的左下角的 y 坐标
    GLsizei width,   // 指定读取像素的宽度
    GLsizei height,  // 指定读取像素的高度
    GLenum format,   // 指定读取像素的格式
    GLenum type,     // 指定读取像素的数据类型
    void* pixels     // 存储读取像素数据的缓冲区
);

2. 参数说明

  • xy:指定读取像素的左下角的坐标。坐标系的原点在视口的左下角,xy 的值可以是负数,表示从视口外读取像素。

  • widthheight:指定读取像素的区域的宽度和高度。

  • format:指定读取像素的格式。常见的格式包括:

    • gl.RGB:读取红、绿、蓝三个通道的数据。
    • gl.RGBA:读取红、绿、蓝、透明度四个通道的数据。
    • gl.RED:只读取红色通道的数据。
    • gl.LUMINANCE:读取亮度值。
  • type:指定读取像素的数据类型。常见的类型包括:

    • gl.UNSIGNED_BYTE:无符号字节类型,范围是 [0, 255]。
    • gl.FLOAT:浮点类型,范围是 [0, 1]。
  • pixels:一个缓冲区,用于存储读取的像素数据。通常是一个 Uint8ArrayFloat32Array

3. 使用示例

以下是一个完整的示例, 读取屏幕上的像素数据:

<!DOCTYPE html>
<html=" langen">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL2 readPixels Example</title>
    <style>
        canvas {
            display: block;
            margin: 0 auto;
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <canvas id="glCanvas" width="512" height="512"></canvas>
    <script>
        // 获取 canvas 元素
        const canvas = document.getElementById('glCanvas');
        const gl = canvas.getContext('webgl2');
​
        if (!gl) {
            console.error('WebGL2 is not supported by your browser.');
            alert('WebGL2 is not supported by your browser.');
            throw new Error('WebGL2 is not supported by your browser.');
        }
​
        // 顶点着色器
        const vertexShaderSource = `#version 300 es
          in vec4 a_position;
          void main() {
            gl_Position = a_position;
          }
        `;
​
        // 片段着色器
        const fragmentShaderSource = `#version 300 es
          precision highp float;
          out vec4 fragColor;
          void main() {
            fragColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色
          }
        `;
​
        // 创建着色器
        function createShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('Shader compile failed with:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        }
​
        function createProgram(vertexShader, fragmentShader) {
​
          // 创建程序
          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 link failed with:', gl.getProgramInfoLog(program));
              gl.deleteProgram(program);
              throw new Error('Failed to link program');
          }
​
          return program;
​
        }
​
        // 创建程序
        const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
        const program = createProgram(vertexShader, fragmentShader);
        
        gl.useProgram(program);
​
        // 创建一个简单的三角形
        const positions = new Float32Array([
            -0.5, -0.5, 0.0,
             0.5, -0.5, 0.0,
             0.0,  0.5, 0.0
        ]);
​
        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
​
        const positionLocation = gl.getAttribLocation(program, 'a_position');
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
​
        // 清空画布并绘制三角形
        gl.clearColor(0.0, 0.0, 0.0, 1.0); // 设置背景为黑色
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLES, 0, 3);
​
        // 使用 readPixels 读取屏幕像素
        const width = gl.drawingBufferWidth;
        const height = gl.drawingBufferHeight;
        const pixels = new Uint8Array(width * height * 4); // RGBA 格式,每个像素 4 个字节
​
        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
​
        // 输出像素数据到控制台
        console.log('Pixel data:', pixels);
    </script>
</body>
</html>

4. 注意事项

  • 性能问题readPixels() 是一个阻塞操作,它会暂停 JavaScript 执行,直到像素数据被读取完成。因此,不要在频繁的渲染循环中使用它,否则会导致性能下降。
  • 帧缓冲区绑定:默认绑定绘图缓冲区。 如果要从帧缓冲区读取像素,必须先绑定目标帧缓冲区。
  • 数据格式匹配formattype 参数必须与帧缓冲区的颜色缓冲区格式匹配。否则,可能会导致读取的数据不正确。
  • 像素数据的存储pixels 参数是一个缓冲区,它的大小必须足够大,以存储指定区域的所有像素数据。如果缓冲区大小不足,可能会导致数据被截断。

5. 应用场景

  • 屏幕截图:通过读取整个屏幕的像素数据,可以实现屏幕截图功能。
  • 像素级操作:可以对读取的像素数据进行处理,例如颜色调整、滤镜效果等。
  • 后处理:在后处理阶段,可以读取渲染结果并进行进一步的处理,例如模糊、边缘检测等。