Использование Multiple Render Targets (MRT) в OpenGL

Под MRT(Multiple Render Targets) понимается возможность одновременного рендеринга сразу в несколько цветовых буферов, а не в один, как обычно происходит.

Именно для поддержки этого в фрагментных программах на GLSL существует возможность записи данных не в gl_FragColor, а в массив gl_FragData [].

При этом каждый элемент массива gl_FragData [] соответствует пикселу в одном из выходных цветовых буферов. Таким образом результатом работы фрагментной программы для фрагмента будет не одно цветовое значение, а сразу несколько, записываемых в разные буфера.

Обратите внимание, что буфера должны быть одного размера и не допускается одновременная работа с переменными gl_FragColor и gl_FragData.

Для того, чтобы из программы, использующей OpenGL, включить запись сразу в несколько буферов можно воспользоваться тремя, практически одинаковыми путями.

Для этого можно воспользоваться функцией glDrawBuffersATI (вводимой расширением ATI_draw_buffers), функцией glDrawBuffersARB (вводимой расширением ARB_draw_buffers) или же воспользоваться функцией glDrawBuffers (вводимой в OpenGL 2.0).

Далее мы будем рассматривать только последний способ (т.к. они фактически отличаются только окончанием имени функции).

Для задания вывода сразу в несколько буферов (в OpenGL 2.0) служит следующая функция:

void glDrawBuffers ( GLsizei n, const GLenum * buffers );

Параметр n задает количество элементов (идентификаторов буферов), передаваемых в параметре buffers.

Каждый элемент массива buffers задает один из используемых буферов вывода, его положение в списке соответствует индексу в массиве gl_FragData. В качестве таких буферов могут выступать как стандартные буфера OpenGL (GL_AUX0, GL_AUX1, ..., GL_NONE, GL_FRONT_LEFT, GL_FRONT_RIGHT и т.д.), так и связанные с FBO текстуры.

Именно последний случай представляет собой наибольший интерес, т.к. при этом фактически происходит рендеринг сразу в несколько текстур.

Чтобы получить количество доступных целей для рендинга, вызовите glGetIntegerv с первым параметром GL_MAX_DRAW_BUFFERS_ARB:

int	maxDrawBuffers;

glGetIntegerv ( GL_MAX_DRAW_BUFFERS_ARB, &maxDrawBuffers );

Пусть у нас есть FBO, заданный идентификатором fbo и две текстуры, также заданные своими идентификаторами texA, texB.

Тогда для направления вывода в обе этих текстуры можно использовать следующий фрагмент кода:

glBindFramebufferEXT ( GL_FRMABUFFER_EXT, fbo );
glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texA, 0 );
glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, texB, 0 );

GLenum  buffers [] = { GL_COLOR_ATTACHMENT0_EXT,GL_COLOR_ATTACHMENT1_EXT };

glDrawBuffers ( 2, buffers );

Для удобства работы модифицируем класс FrameBuffer, чтобы он поддерживал работу сразу с несколькими цветовыми буферами.

Для этого добавим к нему массив colorBuffer, в котором мы будем хранить идентификаторы подключенных к нему текстур. Также нам понадобится изменить метод getColorBuffer, чтобы он принимал в качестве параметра номер требуемой текстуры.

Ниже приводится описание измененного класса FrameBuffer.

class FrameBuffer
{
    int         flags;
    int         width;
    int         height;
    unsigned    frameBuffer;                    // id of framebuffer object
    unsigned    colorBuffer [8];                // texture id or buffer id
    unsigned    depthBuffer;
    unsigned    stencilBuffer;
    int         saveViewport [4];               // saved viewport setting during bind

public:
    FrameBuffer  ( int theWidth, int theHeight, int theFlags = 0 );
    ~FrameBuffer ();

    int getWidth () const
    {
        return width;
    }

    int getHeight () const
	{
        return height;
    }

    bool hasStencil () const
    {
        return stencilBuffer != 0;
    }

    bool hasDepth () const
    {
        return depthBuffer != 0;
    }

    unsigned getColorBuffer ( int no = 0 ) const
    {
        return colorBuffer [no];
    }

    unsigned getDepthBuffer () const
	{
        return depthBuffer;
    }

    bool isOk   () const;
    bool create ();
    bool bind   ();
    bool unbind ();

    bool attachColorTexture ( GLenum target, unsigned texId, int no = 0 );
    bool attachDepthTexture ( GLenum target, unsigned texId );

    bool detachColorTexture ( GLenum target )
    {
        return attachColorTexture ( target, 0 );
    }

    bool detachDepthTexture ( GLenum target )
    {
        return attachDepthTexture ( target, 0 );
    }

                                        // create texture for attaching 
    unsigned createColorTexture ( GLenum format = GL_RGBA, GLenum internalFormat = GL_RGBA8, 
                                  GLenum clamp = GL_REPEAT, int filter = filterMipmap );

    unsigned createColorRectTexture ( GLenum format = GL_RGBA, GLenum internalFormat = GL_RGBA8 );

                                        // mipmapping support
    void buildMipmaps ( GLenum target = GL_TEXTURE_2D ) const
	{
        glGenerateMipmapEXT ( target );
    }

    enum                                // flags for depth and stencil buffers
    {
        depth16 = 1,                    // 16-bit depth buffer
        depth24 = 2,                    // 24-bit depth buffer
        depth32 = 4,                    // 32-bit depth buffer

        stencil1  = 16,                 // 1-bit stencil buffer
        stencil4  = 32,                 // 4-bit stencil buffer
        stencil8  = 64,                 // 8-bit stencil buffer
        stencil16 = 128                 // 16-bit stencil buffer
    };

    enum                                // filter modes
    {
        filterNearest = 0,
        filterLinear  = 1,
        filterMipmap  = 2
    };

    static bool isSupported         ();
    static int  maxColorAttachemnts ();
    static int  maxSize             ();
};

Также изменения потребуют конструктор класса и метод attachColorTexture, приводимые ниже. Полостью исходные файлы для данного класса можно скачать по ссылке в конце статьи.

FrameBuffer :: FrameBuffer  ( int theWidth, int theHeight, int theFlags )
{
    width         = theWidth;
    height        = theHeight;
    flags         = theFlags;
    frameBuffer   = 0;
    depthBuffer   = 0;
    stencilBuffer = 0;

    for ( int i = 0; i < 8; i++ )
        colorBuffer [i] = 0;
}

bool FrameBuffer :: attachColorTexture ( GLenum target, unsigned texId, int no )
{
    if ( frameBuffer == 0 )
        return false;

    if ( target != GL_TEXTURE_2D && target != GL_TEXTURE_RECTANGLE_ARB && 
        (target < GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB || target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB) )
        return false;

    glBindTexture             ( target, colorBuffer [no] = texId );
    glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + no, target, texId, 0 );

    return true;
}

В качестве примера рассмотрим программу, выводящую в одну текстуру нормальное изображение объектов, освещенных по Блинну, а в другую - представленный в виде цвета единичный вектор направления на источник света, и показывающую одну из полученных текстур.

Ниже приводится только код функции display, весь исходный код можно скачать по ссылке в конце статьи.

void display ()
{
    GLenum buffers [] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };

    buffer.bind ();

    glDrawBuffers ( 2, buffers );

    reshape ( buffer.getWidth (), buffer.getHeight () );

    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

                                               // draw the light
    glMatrixMode ( GL_MODELVIEW );
    glPushMatrix ();

    glDisable          ( GL_TEXTURE_2D );
    glTranslatef       ( light.x, light.y, light.z );
    glColor4f          ( 1, 1, 1, 1 );
    glutSolidSphere    ( 0.1f, 15, 15 );
    glPopMatrix        ();

    glMatrixMode   ( GL_MODELVIEW );
    glPushMatrix   ();

    glRotatef    ( rot.x, 1, 0, 0 );
    glRotatef    ( rot.y, 0, 1, 0 );
    glRotatef    ( rot.z, 0, 0, 1 );

    glBindTexture ( GL_TEXTURE_2D, diffMap );

    blinnProgram.bind ();
	
    drawBox ( Vector3D ( -1, -1, -2 ), Vector3D ( 1.7, 1.7, 1.7 ), decalMap );

    glTranslatef ( 2.5, 2.5, 1 );
    glRotatef    ( 15*angle, 0, 1, 0 );

    glutSolidTorus ( 0.7, 1.6, 20, 20 );

    glRotatef    ( 10*angle, 0, 0, 1 );

    drawBox ( Vector3D ( -2, -2, 1 ), Vector3D ( 1.5, 1.5, 1.5 ), diffMap );

    glEnable ( GL_TEXTURE_2D );

    blinnProgram.unbind ();
    buffer.unbind       ();

    glPopMatrix         ();
	
    startOrtho ();
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    drawQuad ( buffer.getColorBuffer ( activeMap ) );
    endOrtho ();

    glutSwapBuffers ();
}

Также ниже приводится используемый фрагментный шейдер.

varying	vec3 l;
varying	vec3 h;
varying vec3 v;
varying vec3 n;

uniform sampler2D	diffMap;

vec3 dirToColor ( in vec3 v )
{
    return 0.5 * ( vec3 ( 1.0 ) + v );
}

void main (void)
{
    const vec4  diffColor = vec4 ( 0.5, 0.0, 0.0, 1.0 );
    const vec4  specColor = vec4 ( 0.1, 0.1, 0.0, 1.0 );
    const float specPower = 20.0;

    vec3 n2   = normalize ( n );
    vec3 l2   = normalize ( l );
    vec3 h2   = normalize ( h );
    vec4 diff = 0.4 + diffColor * max ( dot ( n2, l2 ), 0.0 );
    vec4 spec = 10.0 * specColor * pow ( max ( dot ( n2, h2 ), 0.0 ), specPower );
    vec4 clr  = diff*texture2D ( diffMap, gl_TexCoord [0].xy ) + spec;

    gl_FragData [0] = clr;
    gl_FragData [1] = vec4 ( dirToColor ( l ), 1.0 );
}

Исходный код класса FrameBuffer можно скачать по этой ссылке.

По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.

Используются технологии uCoz