Tistory View

Texture(FBO) to Texture(FBO) 복사

Texture(또는 FBO)를 다른 Texture(또는 FBO)에 복사하려면 보통 대상을 FBO(Framebuffer Object)로 만들고 Shader를 작성하여 복사를 한다.

이 방식에는 FBO와 Texture를 엮는 과정이 발생하게 되는 데, 이 게 좀 부하가 걸리는 작업이다.

이 부하를 줄이고, Framebuffer를 사용하는 복잡한 과정없이 바로 복사할 수 있는 함수들이 있다. 모든 상황을 지원하는 것이 아니기에 정리를 좀 해 본다.

 

1 : FBO -> FBO glBlitFrameBuffer(3.0), glCopyPixels(2.0)
2 : FBO -> Texture glCopyTexSubImage2D(2.0)
3 : Texture -> FBO 바로 지원하는 함수는 없다.
Shader를 만들어서 Rendering
4 : Texture -> Texture 바로 지원하는 함수는 없다.
1. 한쪽을 FBO로 변경하여 2번이나 3번 방식 사용
( OpenGL ES 3.0에서는 texture의 크기를 알아 낼 수가 없어서 2번으로 대체,
미리 크기를 알고 있다면 3번 방식을 사용하면 좋을 것 같다.)
2. 3.1의 경우 ComputeShader를 사용
   ( 단 target이 immutable texture여야 한다)

glBlitFrameBuffer의 경우 Interpolation도 지원하기 때문에 크기 변경이 가능하고 부드럽게 처리가 가능하다.

 

4번방식의 경우 FBO를 만드는 부하가 있지만 달리 방법은 없다. 또한 ComputeShader는 쓸만한 속도를 내긴하지만, GPU만드는 칭구들이 워낙 Fragment shader를 최적화를 해놔서 현재 ComputeShader의 경우 열이 너무 많이 나고, 속도도 솔직히 맘에 들지는 않는다. 그래서 기냥 이 글에는 포함 시키지 않았다.

속도

3.Shader Draw[겁나 빠름, 따봉!]

2.glCopyTexSubImage2D(3번에 견줄만함)

1. glBlitFramebuffer[2번보다 살짝 느린 정도]

1. glCopyPixels[많이 느림]

 

glColyPixels를 제외하고는 거의 비슷한 속도가 나오기에, 어떤 방식을 사용할지는 케바케라서 필요한 것을 골라 써야 한다.

 

 

Copy FBO to FBO using glBlitFramebuffer

프레임 버퍼에서 프레임 버퍼로

void CopyFboToFbo( GLuint srcFbo,
                           GLint srcOffsetX, GLint srcOffsetY,
                           GLsizei srcW, GLsizei srcH,

                           GLuint dstFbo,
                           GLint dstOffsetX, GLint dstOffsetY,
                           GLsizei dstW, GLsizei dstH,
                           GLbitfield mask, GLenum filter ) {


    glBindFramebuffer( GL_READ_FRAMEBUFFER, srcFbo );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, dstFbo );

    glBlitFramebuffer(
        srcOffsetX, srcOffsetY,
        srcOffsetX + srcW, srcOffsetY + srcH,
        dstOffsetX, dstOffsetY,
        dstOffsetX + dstW, dstOffsetY + dstH, mask, filter );

    glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
}

크게 신경 쓸 필요없이 glBlitFramebuffer함수로 한방에 해결이 가능하다.

mask : GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT 이런 식으로 넘기고

filter : GL_LINEAR or GL_NEAREST 

 

 

Copy FBO to Texture using glCopyTexSubImage2D

프레임버퍼에서 텍스처로

void CopyFboToTex(GLuint srcFbo, GLint srcOffsetX, GLint srcOffsetY,
                          GLuint dstTex, GLint dstOffsetX, GLint dstOffsetY,
                          GLsizei width, GLsizei height ) {

    // srcFbo -> texture
    glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
    glBindTexture(GL_TEXTURE_2D, dstTex );
    
    // readBuffer[GL_READ_BUFFER] is automaticaly changed to GL_BACK on default framebuffer, and
    // GL_COLOR_ATTACHMENT0 on non-zero framebuffer.
    // so glReadBuffer call is not needed
    //glReadBuffer( GL_FRONT );

    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, dstOffsetX, dstOffsetY, srcOffsetX, srcOffsetY, width, height );

    glBindFramebuffer(GL_FRAMEBUFFER, 0 );
    glBindTexture( GL_TEXTURE_2D, 0 );
}

이 것도 문제 없이 glCopyTexSubImage2d 함수로 한 방에 가능하다.

 

필자가 주석을 영어로 다는 나쁜 버릇이 있어서..

glCopyTexSubImage2D함수는 GL_READ_BUFFER값에 해당하는 버퍼의 값을 복사를 한다.
이 것이 기본 Framebuffer[0번 프레임버퍼]의 경우, GL_BACK을 넣어줘야하고, 기본 버퍼가 아니면 GL_COLOR_ATTATCHMENTi값을 넣어 줘야하는 데, 필자가 테스트를 한 결과 BindFramebuffer함수로 bind를 하면 기본 Framebuffer일 경우 GL_BACK으로 아니면 GL_COLOR_ATTATCHMENT0값으로 자동으로 변경되는 것을 확인 했다.
대부분의 상황에서는 그냥 glReadBuffer함수를 사용할 필요가 없다.

 

 

Copy Texture to FBO using Shader

텍스처에서 프레임버퍼로

#ifndef SHADERCODE_COPYTEX2FBO_H
#define SHADERCODE_COPYTEX2FBO_H


#define LOC_VTXPOS  0
#define LOC_TEXPOS  1
#define LOC_TEXTURE 2

static const char* COPYTEX2FBO_VTX_SHADER =
        "#version 300 es                                       \n"
        //"precision highp float;                              \n"
        "                                                      \n"
        "layout( location = 0 ) in vec4 aVtxPos;               \n"
        "layout( location = 1 ) in vec2 aTexPos;               \n"
        "                                                      \n"
        "out vec2 vTexPos;                                     \n"
        "                                                      \n"
        "void main()                                           \n"
        "{                                                     \n"
        "   vTexPos  = aTexPos;                                \n"
        "   gl_Position = aVtxPos;                             \n"
        "}                                                     \n"
;

static const char* COPYTEX2FBO_FRG_SHADER =                    
        "#version 300 es                                       \n"
        "precision lowp float;                                 \n"
        "layout( location = 2 ) uniform sampler2D uTexture;    \n"
        "in vec2 vTexPos;                                      \n"
        "                                                      \n"
        "out vec4 fragmentColor;                               \n"
        "void main()                                           \n"
        "{                                                     \n"
        "   fragmentColor = texture( uTexture, vTexPos );      \n"
        "}                                                     \n"
;


#endif

 

#include "ShaderCode_CopyTex2Fbo.h"
static GLuint m_iProgramCopyTexToFbo = 0;

void CGles2::CopyTexToFbo( GLuint srcTex,
                           GLint srcOffX0, GLint srcOffY0,
                           GLint srcOffX1, GLint srcOffY1,
                           GLsizei srcSizeW, GLsizei srcSizeH,

                           GLuint dstFbo,
                           GLint dstOffX0, GLint dstOffY0,
                           GLint dstOffX1, GLint dstOffY1,
                           GLsizei dstSizeW, GLsizei dstSizeH ) {

    assert( srcTex );

    GLint vp[4];
    bool  bRestoreViewport = false;
    if( !m_iProgramCopyTexToFbo ) {
        m_iProgramCopyTexToFbo = CGles2::CreateProgram(COPYTEX2FBO_VTX_SHADER, COPYTEX2FBO_FRG_SHADER);
        assert( m_iProgramCopyTexToFbo );

        assert( LOC_VTXPOS  == glGetAttribLocation( m_iProgramCopyTexToFbo, "aVtxPos" ) );
        assert( LOC_TEXPOS  == glGetAttribLocation( m_iProgramCopyTexToFbo, "aTexPos" ) );
        assert( LOC_TEXTURE == glGetUniformLocation( m_iProgramCopyTexToFbo, "uTexture" ) );
    }

    if( !m_iProgramCopyTexToFbo ) {
        return;
    }


    // backup viewport
    glGetIntegerv(GL_VIEWPORT, vp);

    const GLfloat fDstX0 = ( (GLfloat)dstOffX0 / dstSizeW ) * 2.0F - 1.0F;
    const GLfloat fDstY0 = ( (GLfloat)dstOffY0 / dstSizeH ) * 2.0F - 1.0F;
    const GLfloat fDstX1 = ( (GLfloat)dstOffX1 / dstSizeW ) * 2.0F - 1.0F;
    const GLfloat fDstY1 = ( (GLfloat)dstOffY1 / dstSizeH ) * 2.0F - 1.0F;

    const GLfloat fSrcX0 = (GLfloat)srcOffX0 / srcSizeW;
    const GLfloat fSrcY0 = (GLfloat)srcOffY0 / srcSizeH;
    const GLfloat fSrcX1 = (GLfloat)srcOffX1 / srcSizeW;
    const GLfloat fSrcY1 = (GLfloat)srcOffY1 / srcSizeH;

    const GLfloat vertex[] = {
            fDstX0, fDstY0, 0.0F, fSrcX0, fSrcY0,
            fDstX1, fDstY0, 0.0F, fSrcX1, fSrcY0,
            fDstX1, fDstY1, 0.0F, fSrcX1, fSrcY1,
            fDstX0, fDstY1, 0.0F, fSrcX0, fSrcY1,
    };

    glBindFramebuffer( GL_FRAMEBUFFER, dstFbo );

    glViewport( 0, 0, dstSizeW, dstSizeH );
    bRestoreViewport = true;

    glUseProgram( m_iProgramCopyTexToFbo );

    glEnableVertexAttribArray( LOC_VTXPOS );
    glEnableVertexAttribArray( LOC_TEXPOS );

    glBindBuffer( GL_ARRAY_BUFFER, 0 ); // use cpu mem
    glVertexAttribPointer( LOC_VTXPOS, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, vertex );
    glVertexAttribPointer( LOC_TEXPOS, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, &vertex[3] );

    glActiveTexture( GL_TEXTURE0 );
    glBindTexture( GL_TEXTURE_2D, srcTex );
    glUniform1i( LOC_TEXTURE, 0 );

    glDrawArrays( GL_TRIANGLE_FAN, 0, 4 );


    glDisableVertexAttribArray( LOC_VTXPOS );
    glDisableVertexAttribArray( LOC_TEXPOS );


    glUseProgram(0);

    glBindFramebuffer( GL_FRAMEBUFFER, 0 );

    // restore viewport
    if( bRestoreViewport ) {
        glViewport(vp[0], vp[1], vp[2], vp[3]);
    }
}

m_iProgramCopyTexToFbo변수는 사용이 끝나면 없애줘야 한다.(이 코드는 이 글에 없다.. 쉬운 거니..)

viewport를 변화시키는 부분에서 혹시나 "속도 저하가 있지않을까"하는 의구심이있어 테스트를 해봤는 데, 문제가 없는 듯하다.

vertex-data를 만드는 것을 cpu-mem. 에서 처리했지만, 필요에 따라 바꿔 사용하기를 권장한다.(쉽지는 않을 듯 하다.)

 

이 방식이 귀찮다면 src를 fbo로 묶어서 CopyFboToFbo 함수를 호출하는 것도 나쁘지 않다.

 

Copy Texture to Texture[Change src to fbo]

텍스처에서 텍스처로

void CopyTexToTex( GLuint srcTex, GLint srcOffsetX, GLint srcOffsetY,
                           GLuint dstTex, GLint dstOffsetX, GLint dstOffsetY,
                           GLsizei width, GLsizei height ) {

    GLuint srcFbo = 0;
    // Wrap destination texture to fbo
    glGenBuffers( 1, &srcFbo );
    glBindFramebuffer(GL_FRAMEBUFFER, srcFbo );
    glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTex, 0 );
    glBindFramebuffer(GL_FRAMEBUFFER, 0 );

    // call pre-created function
    CopyFboToTex( srcFbo, srcOffsetX, srcOffsetY,
            dstTex, dstOffsetX, dstOffsetY,
            width, height );


    if( srcFbo ) {
        glDeleteFramebuffers( 1, &srcFbo );
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0 );
    glBindTexture( GL_TEXTURE_2D, 0 );
}

원본 텍스처를 FBO로 변경한 후 이미 만들어진 함수를 호출해 버린다. 3번방식을 사용하고 싶었지만, 텍스처크기를 자동으로 알아낼 방법이 없어 그냥 2번 방식을 사용했다. Fbo를 만들고 바인딩과정에서 속도가 떨어지긴 하지만, 범용함수로는 쓸 만 할 듯하다.

 

 

마지막으로

위에 함수들은 사용예제일 뿐 실전에서 사용하려면 함수를 좀 고쳐 사용하기를 바란다. 일단 동작은 되게 만들었지만, 느려지는 부분이 있을 수 있으니, 함수내부코드를 복사해서 사용하기를 권장한다.

 

그리고.. 버그 있으면 댓글 달아주시면, 감사하겠습니다.~~

 

 

 

 

만드느라 뒈지는 줄...

 

Reference

https://stackoverflow.com/questions/23981016/best-method-to-copy-texture-to-texture

 

Best method to copy texture to texture

What is the best method to copy pixels from texture to texture? I've found some ways to accomplish this. For instance, there's a method glCopyImageSubData() but my target version is OpenGL 2.1, so I

stackoverflow.com

 

Replies
Reply Write