无法显示带有着色器的图片

Cannot display a picture with a shader

我正在为我的项目构建多通道渲染器。问题是,我什至不能显示一张图片。我查看了 WebGL Inspector,但一切似乎都很好(我检查了一个我知道有效的更简单的版本,一切似乎都差不多)。


[edit] 这里有一个minimal codepen。我只提取了 WebGL 调用,因为其余代码超出了范围。我把它留在下面供参考。


这里是codepen, the debug codepen,以及供参考的源代码(尽管它很大,但实际上并没有做很多工作):

class Shader {

    constructor( { fragment, vertex, INPUT_SOURCES = 1 } ) {

        this.INPUT_SOURCES = INPUT_SOURCES;

        this.fragment = fragment;
        this.vertex = vertex;

    }

    getResolution( previous ) {

        return previous;

    }

    buildProgram( context ) {

        let fragmentPrefix = `
            #define uInputSample uInputSample_Prev0
            #define uInputResolution uInputResolution_Prev0
        `;

        let vertexPrefix = `
            #define uInputSample uInputSample_Prev0
            #define uInputResolution uInputResolution_Prev0
        `;

        let fragment = context.createShader( context.gl.FRAGMENT_SHADER, fragmentPrefix + this.fragment );
        let vertex = context.createShader( context.gl.VERTEX_SHADER, vertexPrefix + this.vertex );

        return context.createProgram( fragment, vertex );

    }

}

class RenderOutput {

    constructor( ) {

        this.resolution = { width : 0, height : 0 };

        this.framebuffer = null;
        this.texture = null;

    }

}

class RenderPass {

    constructor( { } ) {

        this.previous = null;
        this.next = null;

    }

    static link( from, to ) {

        if ( to.previous )
            to.remove( );

        if ( from.next )
            from.next.remove( );

        from.next = to;
        to.previous = from;

        from.refreshOutput( );

    }

    static unlink( element ) {

        if ( ! element.previous )
            return ;

        let previous = element.previous;

        element.previous = null;
        previous.next = null;

        element.refreshInputs( );
        previous.refreshOutput( );

    }

    append( element ) {

        RenderPass.link( this, element );

    }

    remove( ) {

        RenderPass.unlink( this );

    }

}

class RenderPassShader extends RenderPass {

    constructor( { context, shader }, previous = null ) {

        super( );

        this.context = context;

        this.shader = shader;
        this.program = shader.buildProgram( this.context );

        this.uInputSampleLocations = [ ];
        this.uInputResolutionLocations = [ ];

        this.inputs = null;
        this.output = new RenderOutput( );

        this.resolution = null;

        // -- switch on uniforms

        this.context.gl.useProgram( this.program );

        // -- locate uniforms

        this.uOutputResolutionLocation = this.context.gl.getUniformLocation( this.program, 'uOutputResolution' );
        this.uScreenResolutionLocation = this.context.gl.getUniformLocation( this.program, 'uScreenResolution' );

        for ( let t = 0, T = this.shader.INPUT_SOURCES; t < T; ++ t ) {
        this.uInputSampleLocations.push( this.context.gl.getUniformLocation( this.program, `uInputSample_Prev${t}` ) );
        this.uInputResolutionLocations.push( this.context.gl.getUniformLocation( this.program, `uInputResolution_Prev${t}` ) );
        }

        // -- locate attributes

        this.aVertexPositionLocation = this.context.gl.getAttribLocation( this.program, 'aVertexPosition' );
        this.aVertexTextureUvLocation = this.context.gl.getAttribLocation( this.program, 'aVertexTextureUv' );

        this.context.gl.bindBuffer( this.context.vertexPositionBuffer.bufferTarget, this.context.vertexPositionBuffer );
        this.context.gl.vertexAttribPointer( this.aVertexPositionLocation, this.context.vertexPositionBuffer.itemSize, this.context.gl.FLOAT, false, 0, 0 );
        this.context.gl.bindBuffer( this.context.vertexPositionBuffer.bufferTarget, null );

        this.context.gl.bindBuffer( this.context.vertexTextureUvBuffer.bufferTarget, this.context.vertexTextureUvBuffer );
        this.context.gl.vertexAttribPointer( this.aVertexTextureUvLocation, this.context.vertexTextureUvBuffer.itemSize, this.context.gl.FLOAT, false, 0, 0 );
        this.context.gl.bindBuffer( this.context.vertexTextureUvBuffer.bufferTarget, null );

        // -- switch off shader

        this.context.gl.useProgram( null );

        // -- initialize

        this.refreshInputs( );

        // -- attach

        previous && previous.append( this );

    }

    refreshOutput( ) {

        if ( this.next && ! this.output.texture ) {

            let { width, height } = this.output.resolution;

            this.output.framebuffer = this.context.createFramebuffer( );
            this.output.texture = this.context.createTexture( );

            this.context.setTextureSize( this.output.texture, width, height );

            this.context.gl.bindFramebuffer( this.context.gl.FRAMEBUFFER, this.output.framebuffer );
            this.context.gl.framebufferTexture2D( this.context.gl.FRAMEBUFFER, this.context.gl.COLOR_ATTACHMENT0, this.context.gl.TEXTURE_2D, this.output.texture, 0 );
            this.context.gl.bindFramebuffer( this.context.gl.FRAMEBUFFER, null );

            this.next.refreshInputs( );

        } else if ( ! this.next && this.output.texture ) {

            this.context.deleteFramebuffer( this.output.framebuffer );
            this.context.deleteTexture( this.output.texture );

            this.output.framebuffer = null;
            this.output.texture = null;

        }

    }

    refreshInputs( ) {

        this.context.useProgram( this.program, ( ) => {

            this.inputs = this.previous ? this.previous.getOutputs( ) : [ ];
            this.valid = this.shader.INPUT_SOURCES <= this.inputs.length;

            let { width, height } = this.getResolution( );
            this.output.resolution = { width, height };

            if ( this.output.texture )
                this.context.setTextureSize( this.output.texture, width, height );

            for ( let t = 0, T = Math.min( this.inputs.length, this.shader.INPUT_SOURCES ); t < T; ++ t ) {
            this.context.gl.uniform1i( this.uInputSampleLocations[ t ], this.inputs[ t ].texture );
            this.context.gl.uniform2f( this.uInputResolutionLocations[ t ], this.inputs[ t ].resolution.width, this.inputs[ t ].resolution.height );
            }

        } );

        if ( this.next ) {
            this.next.refreshInputs( );
        }

    }

    getResolution( ) {

        return this.shader.getResolution( this );

    }

    getOutputs( ) {

        let previous = this.previous ? this.previous.getOutputs( ) : [ ];

        return [ this.output ].concat( previous );

    }

    render( ) {

        if ( ! this.valid )
            throw new Error( 'Invalid pass' );

        this.context.gl.useProgram( this.program );
        this.context.gl.viewport( 0, 0, this.output.resolution.width, this.output.resolution.height );

        this.context.gl.bindFramebuffer( this.context.gl.FRAMEBUFFER, this.output.framebuffer );
        this.context.gl.clear( this.context.gl.COLOR_BUFFER_BIT );

        this.context.gl.bindBuffer( this.context.vertexIndexBuffer.bufferTarget, this.context.vertexIndexBuffer );
        this.context.gl.drawElements( this.context.gl.TRIANGLE_STRIP, this.context.vertexIndexBuffer.itemCount, this.context.gl.UNSIGNED_SHORT, 0 );

        this.next && this.next.render( );

    }

}

RenderPass.EntryPoint = class extends RenderPass {

    constructor( { context } ) {

        super( );

        this.context = context;

        this.output = new RenderOutput( );
        this.output.texture = this.context.createTexture( );

        this.setInputSize( 0, 0 );

    }

    setInputSize( width, height ) {

        this.output.resolution.width = width;
        this.output.resolution.height = height;

        this.context.setTextureSize( this.output.texture, this.output.resolution.width, this.output.resolution.height );

        this.next && this.next.refreshInputs( );

    }

    setInputData( data ) {

        let format = this.context.gl.RGBA;
        let type = this.context.gl.UNSIGNED_BYTE;

        this.context.gl.bindTexture( this.context.gl.TEXTURE_2D, this.output.texture );
        this.context.gl.texImage2D( this.context.gl.TEXTURE_2D, 0, this.context.gl.RGBA, this.output.resolution.width, this.output.resolution.height, 0, format, type, data );
        this.context.gl.bindTexture( this.context.gl.TEXTURE_2D, null );

    }

    refreshOutput( ) {

        this.next && this.next.refreshInputs( );

    }

    getOutputs( ) {

        return [ this.output ];

    }

    render( ) {

        this.next && this.next.render( );

    }

};

RenderPass.StandardOutput = class extends RenderPassShader {

    constructor( { context }, parent ) {

        super( { context, shader : new Shader( {

            fragment : `

                precision mediump float;

                uniform sampler2D uInputSample;

                varying vec2 vTextureCoordinates;

                void main( void ) {
                    gl_FragColor = texture2D( uInputSample, vTextureCoordinates );
                }

            `,

            vertex : `

                precision mediump float;

                uniform mat4 uMatrix;

                attribute vec3 aVertexPosition;
                attribute vec2 aVertexTextureUv;

                varying vec2 vTextureCoordinates;

                void main( void ) {
                    vTextureCoordinates = vec2( aVertexTextureUv.s, 1.0 - aVertexTextureUv.t );
                    gl_Position = uMatrix * vec4( aVertexPosition, 1.0 );
                }

            `

        } ) }, parent );

        this.outputWidth = 0;
        this.outputHeight = 0;

    }

    setOutputSize( width, height ) {

        this.outputWidth = width;
        this.outputHeight = height;

        this.refreshInputs( );

    }

    refreshInputs( ) {

        super.refreshInputs( );

        this.context.useProgram( this.program, ( ) => {

            var inputWidth = this.inputs.length > 0 ? this.inputs[ 0 ].resolution.width : 0;
            var inputHeight = this.inputs.length > 0 ? this.inputs[ 0 ].resolution.height : 0;

            var outputWidth = this.output.resolution.width;
            var outputHeight = this.output.resolution.height;

            var isUndefined = value => value == null || value === '';

            if ( isUndefined( outputWidth ) && isUndefined( outputHeight ) )
                outputWidth = inputWidth, outputHeight = inputHeight;

            if ( isUndefined( outputWidth ) )
                outputWidth = inputWidth * ( outputHeight / inputHeight );

            if ( isUndefined( outputHeight ) )
                outputHeight = inputHeight * ( outputWidth / inputWidth );

            var widthRatio = outputWidth / inputWidth;
            var heightRatio = outputHeight / inputHeight;

            var ratio = Math.min( widthRatio, heightRatio );

            var viewportWidth = widthRatio / ratio;
            var viewportHeight = heightRatio / ratio;

            var matrix = this._createOrthoMatrix( - viewportWidth, viewportWidth, - viewportHeight, viewportHeight, - 100, 100 );
            this.context.gl.uniformMatrix4fv( this.context.gl.getUniformLocation( this.program, 'uMatrix' ), false, matrix );

        } );

    }

    getResolution( ) {

        return { width : this.outputWidth, height : this.outputHeight };

    }

    _createOrthoMatrix( left, right, bottom, top, near, far ) {

        var lr = 1 / ( left - right ), bt = 1 / ( bottom - top ), nf = 1 / ( near - far );

        return [ - 2 * lr, 0, 0, 0, 0, - 2 * bt, 0, 0, 0, 0, 2 * nf, 0, ( left + right ) * lr, ( bottom + top ) * bt, ( near + far ) * nf, 1 ];

    }

};

class RenderContext {

    constructor( { gl } ) {

        this.gl = gl;

        this.vertexPositionBuffer = this.createBuffer( this.gl.ARRAY_BUFFER, 4, new Float32Array( [ -1, -1, 0, /**/ 1, -1, 0, /**/ 1, 1, 0, /**/ -1, 1, 0 ] ) );
        this.vertexTextureUvBuffer = this.createBuffer( this.gl.ARRAY_BUFFER, 4, new Float32Array( [ 0, 0, /**/ 1, 0, /**/ 1, 1, /**/ 0, 1 ] ) );
        this.vertexIndexBuffer = this.createBuffer( this.gl.ELEMENT_ARRAY_BUFFER, 4, new Uint16Array( [ 0, 1, 3, 2 ] ) );

    }

    createBuffer( target, count, content ) {

        var buffer = this.gl.createBuffer( );

        buffer.bufferTarget = target;
        buffer.itemCount = count;
        buffer.itemSize = content.length / count;

        this.gl.bindBuffer( buffer.bufferTarget, buffer );
        this.gl.bufferData( buffer.bufferTarget, content, this.gl.STATIC_DRAW );
        this.gl.bindBuffer( buffer.bufferTarget, null );

        return buffer;

    }

    createShader( type, script ) {

        let shader = this.gl.createShader( type );

        this.gl.shaderSource( shader, script );
        this.gl.compileShader( shader );

        if ( ! this.gl.getShaderParameter( shader, this.gl.COMPILE_STATUS ) )
            throw new Error( `Shader compilation failed: ${this.gl.getShaderInfoLog(shader)}` );

        return shader;

    }

    createProgram( fragment, vertex ) {

        let program = this.gl.createProgram( );

        this.gl.attachShader( program, vertex );
        this.gl.attachShader( program, fragment );

        this.gl.linkProgram( program );

        if ( ! this.gl.getProgramParameter( program, this.gl.LINK_STATUS ) )
            throw new Error( `Shader linking failed: ${this.gl.getError()}` );

        return program;

    }

    createFramebuffer( ) {

        let framebuffer = this.gl.createFramebuffer( );

        return framebuffer;

    }

    createTexture( ) {

        let texture = this.gl.createTexture( );

        this.gl.bindTexture( this.gl.TEXTURE_2D, texture );
        this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE );
        this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE );
        this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST );
        this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST );
        this.gl.bindTexture( this.gl.TEXTURE_2D, null );

        return texture;

    }

    setTextureSize( texture, width, height ) {

        this.gl.bindTexture( this.gl.TEXTURE_2D, texture );
        this.gl.texImage2D( this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null );
        this.gl.bindTexture( this.gl.TEXTURE_2D, null );

    }

    useProgram( program, callback ) {

        this.gl.useProgram( program );
        callback( );
        this.gl.useProgram( null );

    }

}

function getPixelData( image ) {

    let canvas = document.createElement( 'canvas' );
    let context = canvas.getContext( '2d' );

    canvas.width = image.width;
    canvas.height = image.height;
    context.drawImage( image, 0, 0 );

    let canvasData = context.getImageData( 0, 0, canvas.width, canvas.height ).data;
    let pixelData = new Uint8Array( canvasData.length );

    for ( let t = 0; t < pixelData.length; ++ t )
        pixelData[ t ] = canvasData[ t ];

    return pixelData;

}

( function ( ) {

    let image = new Image( );
    image.crossOrigin = 'anonymous';
    image.src = 'http://i.imgur.com/xUm3XMz.png';
    image.addEventListener( 'load', ( ) => {

      image.data = getPixelData( image );

      let canvas = document.createElement( 'canvas' );
      document.body.appendChild( canvas );

      canvas.width = image.width * 1;
      canvas.height = image.height * 1;

      let gl = canvas.getContext( 'webgl' );
      gl.clearColor( 0.0, 0.0, 0.0, 1.0 );

      let context = new RenderContext( { gl } );

      let entry = new RenderPass.EntryPoint( { context } ), pipeline = entry;
      let output = new RenderPass.StandardOutput( { context } );

      pipeline.append( output );

      entry.setInputSize( image.width, image.height );
      entry.setInputData( image.data );

      output.setOutputSize( canvas.width, canvas.height );

      var render = ( ) => {
          requestAnimationFrame( render );
          entry.render( );
      };

      render( );

    } );

} )( );

我忘了启用属性。以下代码修复了它:

this.context.gl.enableVertexAttribArray( this.aVertexPositionLocation )