如何从webGL上下文readPixels()API中仅读取单个通道(R组件)?

问题描述 投票:1回答:1

我已经使用webgl将RGBA图像转换为灰度图像。

[当使用gl.readPixels()格式的gl.RGBA读取像素时,由于RGBA像素被转换为YYYA并分配给gl_FragColor,因此将每个像素的值获取为YYYA。我只希望每个像素1个字节的Y分量,而不是4个字节。

尝试读取gl.RED格式(而不是gl.RGBA的像素]

gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RED, gl.UNSIGNED_BYTE, pixels);

但是在Chrome上出现以下错误,并且只有零。

WebGL: INVALID_ENUM: readPixels: invalid format
  1. 是否可以使gl_FragColor在LUMINANCE模式下每像素输出1个字节,而不是RGBA,但是输入纹理必须是RGBA?
  2. 如果无法更改gl渲染的格式,则在调用gl.readPixels()时是否可以仅读取每个4字节像素的第一个字节?

注意:3.我已经通过每4个字节跳转将gl.readPixels()输出复制到另一个数组中。但我要避免使用此副本,因为它需要更多时间。4.另外,我需要解决方案与移动浏览器(ios Safari和android chrome)兼容。

function webGL() {
    var gTexture;
    var gFramebuffer;
    var srcCanvas = null;
    var programs = {};
    var program;
    var pixels;

    this.convertRGBA2Gray = function(inCanvas, inArray) {
        // Y component from YCbCr
        const shaderSourceRGB2GRAY = `
                precision mediump float;

                uniform sampler2D u_image;
                uniform vec2 u_textureSize;
                vec4 scale = vec4(0.299,  0.587,  0.114, 0.0);
                void main() {
                    vec4 color = texture2D(u_image, gl_FragCoord.xy / u_textureSize);
                    gl_FragColor = vec4(vec3(dot(color,scale)), color.a);
                }`;

        if (srcCanvas === null) {
            console.log('Setting up webgl');
            srcCanvas = inCanvas;
            _initialize(srcCanvas.width, srcCanvas.height);
            program = _createProgram("rgb2grey", shaderSourceRGB2GRAY);
        }
        pixels = inArray;
        _run(program);
    }

    ///////////////////////////////////////
    // Private functions

    var _getWebGLContext = function(canvas) {
        try {
            return (canvas.getContext("webgl", {premultipliedAlpha: false, preserveDrawingBuffer: true}) || canvas.getContext("experimental-webgl", {premultipliedAlpha: false, preserveDrawingBuffer: true}));
        }
        catch(e) {
            console.log("ERROR: %o", e);
        }
        return null;
    }

    var gl = _getWebGLContext(document.createElement('canvas'));

    var _initialize = function(width, height) {
        var canvas = gl.canvas;
        canvas.width = width;
        canvas.height = height;

        if (this.originalImageTexture) {
            return;
        }

        this.originalImageTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);

        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        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);

        gTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, gTexture);

        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        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.RGBA, canvas.width, canvas.height, 0,
            gl.RGBA, gl.UNSIGNED_BYTE, null);

        gFramebuffer = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, gFramebuffer);

        var positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            -1.0,  1.0,
            1.0,  1.0,
            1.0, -1.0,

            -1.0,  1.0,
            1.0, -1.0,
            -1.0, -1.0
            ]), gl.STATIC_DRAW);

        gl.framebufferTexture2D(
            gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, gTexture, 0);

        gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, srcCanvas);
    }

    var _createProgram = function(name, fragmentSource, vertexSource) {
        shaderProgram = programs[name];

        if (shaderProgram){
            console.log('Reusing program');
            gl.useProgram(shaderProgram);
            return shaderProgram;
        }

        function createShader(type, source){
            var shader = gl.createShader(type);

            gl.shaderSource(shader, source);

            gl.compileShader(shader);  

            return shader;
        }

        var vertexShader, fragmentShader;

        if (!vertexSource){
            vertexShader = createShader(gl.VERTEX_SHADER,   `attribute vec2 a_position;
                                                            void main() { gl_Position = vec4(a_position, 0.0, 1.0); }`
                                                            );
        } else {
            vertexShader = createShader(gl.VERTEX_SHADER, vertexSource);
        }
        fragmentShader = createShader(gl.FRAGMENT_SHADER, fragmentSource);

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        gl.useProgram(shaderProgram);
        return shaderProgram;
    }

    var _render = function(gl, program){
		var positionLocation = gl.getAttribLocation(program, "a_position"); 

		var u_imageLoc = gl.getUniformLocation(program, "u_image");
		var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");

		gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
		gl.enableVertexAttribArray(positionLocation);

		var width = gl.canvas.width,
			height = gl.canvas.height;

		gl.bindFramebuffer(gl.FRAMEBUFFER, gFramebuffer);

		gl.uniform2f(textureSizeLocation, width, height);
		
		gl.uniform1i(u_imageLoc, 0);

		gl.viewport(0, 0, width, height);

		gl.drawArrays(gl.TRIANGLES, 0, 6);

	}

    var _run = function(program){
        let t0 = performance.now();
        _render(gl, program);
        gl.bindTexture(gl.TEXTURE_2D, gTexture);
        let t1 = performance.now();

        // gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
        gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RED, gl.UNSIGNED_BYTE, pixels);

        let t2 = performance.now();
        console.log('_render dur = ' + Number((t1-t0).toFixed(3)) + ' ms');
        console.log('_run dur = ' + Number((t2-t0).toFixed(3)) + ' ms');
    }

};
<div>
    <canvas id="source"></canvas>
</div>

<script src="webgl.js" type="text/javascript"></script>

<script>
    window.addEventListener('load', function(e) {
        var srcImg = new Image();
        srcImg.crossOrigin = "anonymous";
        srcImg.src = "https://i.picsum.photos/id/17/480/480.jpg";
        srcImg.width = 480;
        srcImg.height = 480;

        srcImg.onload = function(){
            // image  has been loaded
            let srcCanvas = document.getElementById("source");
            srcCanvas.width = srcImg.width;
            srcCanvas.height = srcImg.height;

            let ctx = srcCanvas.getContext('2d');
            ctx.drawImage(srcImg, 0, 0, srcImg.width, srcImg.height);

            var webgl = new webGL();
            let pixels = new Uint8Array(srcCanvas.width * srcCanvas.height * 4);
            webgl.convertRGBA2Gray(srcCanvas, pixels);

            var outData = ctx.createImageData(srcCanvas.width, srcCanvas.height);

            console.log('\n');
            for (let k = 0; k < 12; ++k) {
                console.log(pixels[k] + ', ');
            }
            console.log('\n');

            // Luminance plot
            for (let i = 0, j = 0; i < (srcCanvas.width * srcCanvas.height * 4); i+=4, ++j ) {
                outData.data[i] = outData.data[i+1] = outData.data[i+2] = pixels[j];
                outData.data[i+3] = 255;
            }

            // RGB plot
            // for ( let i = 0; i < (srcCanvas.width * srcCanvas.height * 4); ++i ) {
            //     outData.data[i] = pixels[i];
            // }

            srcCanvas.getContext('2d').putImageData(outData, 0, 0);

        };

    }, true);

</script>
javascript webgl webgl2
1个回答
0
投票

是否可以使gl_FragColor在LUMINANCE模式下每像素输出1个字节,而不是RGBA,但输入纹理必须是RGBA?

不可移植。 WebGL1的规范说gl.RGBA / gl.UNSIGNED_BYTE仅支持渲染到纹理。所有其他格式都是可选的。

如果无法更改gl渲染的格式,则在调用gl.readPixels()时是否可以仅读取每个4字节像素的第一个字节?

否,The spec第4.3.1节说仅支持gl.RGBAgl.UNSIGNED_BYTE。所有其他格式都是可选的,取决于实现方式。在WebGL2上也是如此。即使您制作R8纹理(仅红色,8位),也可以由gl.RED / gl.UNSIGNED_BYTE读取来实现。

请参见Webgl1Webgl2

© www.soinside.com 2019 - 2024. All rights reserved.