向所有 FBO 颜色附件广播片段着色器输出?

Broadcast fragment-shader output to all FBO color attachments?

有什么方法可以让 OpenGL ES 3.0 将单输出片段着色器的值广播到所有活动的(根据 glDrawBuffers())FBO 颜色附件?

如果可能的话,我希望我的着色器或多或少保持原样,并避免多个 layout 输出所需的重写:

layout( location = 0 ) out vec4 out_color0;
layout( location = 1 ) out vec4 out_color1;
layout( location = 2 ) out vec4 out_color2;
layout( location = 3 ) out vec4 out_color3;
void main()
{
    out_color0 = vec4( 1.0, 0.2, 0.0, 1.0 );
    out_color1 = vec4( 1.0, 0.2, 0.0, 1.0 );
    out_color2 = vec4( 1.0, 0.2, 0.0, 1.0 );
    out_color3 = vec4( 1.0, 0.2, 0.0, 1.0 );
}

...或输出数组:

out vec4 out_color[4];
void main()
{
    out_color[0] = vec4( 1.0, 0.2, 0.0, 1.0 );
    out_color[1] = vec4( 1.0, 0.2, 0.0, 1.0 );
    out_color[2] = vec4( 1.0, 0.2, 0.0, 1.0 );
    out_color[3] = vec4( 1.0, 0.2, 0.0, 1.0 );
}

这是我用于测试的程序,它(尝试)为所有四个 FBO 附件绘制一个红色三角形,然后将第三个附件 blits 到默认帧缓冲区:

#include <glad/glad.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include <cstdlib>
#include <cstdarg>
#include <iostream>
#include <vector>

struct Program
{
    static GLuint Load( const char* shader, ... )
    {
        const GLuint prog = glCreateProgram();
        va_list args;
        va_start( args, shader );
        while( shader )
        {
            AttachShader( prog, va_arg( args, GLenum ), shader );
            shader = va_arg( args, const char* );
        }
        va_end( args );
        glLinkProgram( prog );
        CheckStatus( prog );
        return prog;
    }

private:
    static void CheckStatus( GLuint obj )
    {
        GLint status = GL_FALSE;
        if( glIsShader(obj) ) glGetShaderiv( obj, GL_COMPILE_STATUS, &status );
        if( glIsProgram(obj) ) glGetProgramiv( obj, GL_LINK_STATUS, &status );
        if( status == GL_TRUE ) return;
        GLchar log[ 1 << 15 ] = { 0 };
        if( glIsShader(obj) ) glGetShaderInfoLog( obj, sizeof(log), NULL, log );
        if( glIsProgram(obj) ) glGetProgramInfoLog( obj, sizeof(log), NULL, log );
        std::cerr << log << std::endl;
        std::exit( EXIT_FAILURE );
    }

    static void AttachShader( GLuint program, GLenum type, const char* src )
    {
        const GLuint shader = glCreateShader( type );
        glShaderSource( shader, 1, &src, NULL );
        glCompileShader( shader );
        CheckStatus( shader );
        glAttachShader( program, shader );
        glDeleteShader( shader );
    }
};

const char* vert = 1 + R"GLSL(
#version 300 es
void main()
{
    const vec2 verts[3] = vec2[3]
    (
        vec2( -0.5, -0.5 ),
        vec2(  0.5, -0.5 ),
        vec2(  0.0,  0.5 )
    );
    gl_Position = vec4( verts[ gl_VertexID ], 0.0, 1.0 );
}
)GLSL";

const char* frag = 1 + R"GLSL(
#version 300 es
precision mediump float;
out vec4 out_color;
void main()
{
    out_color = vec4( 1.0, 0.2, 0.0, 1.0 );
}
)GLSL";

int main( int argc, char** argv )
{
    glfwSetErrorCallback( []( int err, const char* desc )
    {
        std::cerr << "GLFW error: " << desc << std::endl;
    } );

    if( !glfwInit() )
        return EXIT_FAILURE;

    glfwWindowHint( GLFW_CLIENT_API, GLFW_OPENGL_ES_API );
    glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
    glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 0 );
    glfwWindowHint( GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API );
    GLFWwindow* window = glfwCreateWindow( 640, 480, "GLFW", NULL, NULL );
    if( nullptr == window )
        return EXIT_FAILURE;

    glfwMakeContextCurrent( window );
    glfwSwapInterval( 1 );
    gladLoadGLES2Loader( (GLADloadproc)glfwGetProcAddress );

    const GLuint prog = Program::Load( vert, GL_VERTEX_SHADER, frag, GL_FRAGMENT_SHADER, NULL );
    glUseProgram( prog );

    // init framebuffer attachments
    std::vector< GLuint > textures( 4, 0 );
    glGenTextures( 4, textures.data() );
    for( size_t i = 0; i < textures.size(); ++ i )
    {
        glBindTexture( GL_TEXTURE_2D, textures[i] );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
    }
    GLuint rbDepth = 0;
    glGenRenderbuffers(1, &rbDepth );
    glBindRenderbuffer( GL_RENDERBUFFER, rbDepth );
    glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 32, 32 );

    // init FBO
    GLuint fbo = 0;
    glGenFramebuffers( 1, &fbo );
    glBindFramebuffer( GL_FRAMEBUFFER, fbo );
    for( size_t i = 0; i < textures.size(); ++ i )
    {
        glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, textures[i], 0 );
    }
    glFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbDepth );
    if( GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus( GL_FRAMEBUFFER ) )
    {
        std::cerr << "Incomplete framebuffer" << std::endl;
        std::exit( EXIT_FAILURE );
    }

    while( !glfwWindowShouldClose( window ) )
    {
        glfwPollEvents();

        // render to FBO
        glBindFramebuffer( GL_FRAMEBUFFER, fbo );
        GLenum bufs[] =
        {
            GL_COLOR_ATTACHMENT0 + 0,
            GL_COLOR_ATTACHMENT0 + 1,
            GL_COLOR_ATTACHMENT0 + 2,
            GL_COLOR_ATTACHMENT0 + 3,
        };
        glDrawBuffers( 4, bufs );
        glViewport( 0, 0, 32, 32 );
        glClearColor( 0.0f, 0.6f, 1.0f, 1.f );
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        glDrawArrays( GL_TRIANGLES, 0, 3 );

        // switch back to default framebuffer & clear it with non-black color
        glBindFramebuffer( GL_FRAMEBUFFER, 0 );
        GLenum defaultBuf = GL_BACK;
        glDrawBuffers( 1, &defaultBuf );
        glClearColor( 1.0f, 0.0f, 1.0f, 1.f );
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        // blit a color attachment to the default framebuffer
        glBindFramebuffer( GL_READ_FRAMEBUFFER, fbo );
        glReadBuffer( GL_COLOR_ATTACHMENT0 + 2 );
        glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
        glBlitFramebuffer( 0, 0, 32, 32, 0, 0, 640, 480, GL_COLOR_BUFFER_BIT, GL_LINEAR );

        glfwSwapBuffers( window );
    }

    glfwMakeContextCurrent( NULL );
    glfwDestroyWindow( window );

    glfwTerminate();
    return EXIT_SUCCESS;
}

在这台使用最近 ANGLE 版本的 Windows 10 机器上,我得到了清晰的蓝色,但没有三角形 ("undefined") 用于第三个附件:


第一个附件没问题:


No such functionality exists in the API:

3) Should we support broadcast from gl_FragColor to all gl_FragData[x] or should it be synonymous with gl_FragData[0]?

DISCUSSION: With NV_draw_buffers, writing to gl_FragColor writes to all the enabled draw buffers (ie broadcast). In OpenGL ES 3.0 when using ESSL 1.0, gl_FragColor is equivalent to writing a single output to gl_FragData[0] and multiple outputs are not possible. When using ESSL 3.0, only user-defined out variables may be used.

If broadcast is supported, some implementations may have to replace writes to gl_FragColor with replicated writes to all possible gl_FragData locations when this extension is enabled.

RESOLVED: Writes to gl_FragColor are broadcast to all enabled color buffers. ES 3.0 using ESSL 1.0 doesn't support broadcast because ESSL 1.0 was not extended to have multiple color outputs (but that is what this extension adds). ESSL 3.0 doesn't support the broadcast because it doesn't have the gl_FragColor variable at all, and only has user- defined out variables. This extension extends ESSL 1.0 to have multiple color outputs. Broadcasting from gl_FragColor to all enabled color buffers is the most consistent with existing draw buffer extensions to date (both NV_draw_buffers and desktop GL).