Coco2d-x Framebuffer笔记

本文深入探讨OpenGL中的Framebuffer对象(FBO),介绍其基本概念、组成部分、创建及使用流程,并结合Coco2d-x引擎实例,展示如何实现自定义帧缓冲。

OpenGL Framebuffer

OpenGL中Framebuffer的特性:

1.Framebuffer由什么组成:color buffer,depth buffer,stencil buffer

2.The default framebuffer is created and configured when you create your window (GLFW does this for us). 

3.For a framebuffer to be complete the following requirements have to be satisfied:

  • We have to attach at least one buffer (color, depth or stencil buffer).
  • There should be at least one color attachment.
  • All attachments should be complete as well (reserved memory).
  • Each buffer should have the same number of samples.

4.两种attachment:texture attachment(可读写,一般用作color buffer)和renderbuffer attachment(可写,一般用作depth stencil buffer)

5.一般使用方法:用texture作为color attachment ,depth attachment 和stencil attachment 共同使用一个buffer(GL_DEPTH24_STENCIL8)作为一个DepthStencil attachment(GL_DEPTH_STENCIL_ATTACHMENT),并且使用renderbuffer object作为attachment。

OpenGL使用Framebuffer的流程:

1.Gen

GLuint fbo;
glGenFramebuffers(1, &fbo);

2.Bind

glBindFramebuffer(GL_FRAMEBUFFER, fbo); 

3.Attachment

(1)Texture Color Attachment

GLuint texture;

glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB,GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,0); 

(2)Texture DepthStencil Attachment

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
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_DEPTH24_STENCIL8,800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture,0); 

(3)Renderbuffer Color Attachments

GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); 
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 800, 600);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); 

(4)Renderbuffer DepthStencil  Attachments

GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); 
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

4.check complete

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
	// log info output

5.Delete

glDeleteFramebuffers(1, &fbo); 

Coco2d-x Framebuffer

1.使用framebuffer需要的所有参数:

1)Framebuffer size:width,height(Pixels Unit)

2)Attachment:color buffer(texture or renderbuffer),depth stencil buffer(renderbuffer)

3)Clear value for glClear*(type value):vec3 clearcolor, GLfloat cleardepth, int8 clearstencil

2.删减版framebuffer.h

class CC_DLL FrameBuffer : public Ref
{
public:
    static FrameBuffer* create(uint8_t fid, unsigned int width, unsigned int height);
    
    bool init(uint8_t fid, unsigned int width, unsigned int height);
public:
    //call glclear to clear frame buffer object : color, depth, stencil
    void clearFBO();
    void applyFBO();
    void restoreFBO();
    void setClearColor(const Color4F& color) { _clearColor = color;}
    void setClearDepth(float depth) { _clearDepth = depth; }
    void setClearStencil(int8_t stencil) { _clearStencil = stencil; }
    
	// set _rt
    void attachRenderTarget(RenderTargetBase* rt);
	// set _rtDepthStencil
    void attachDepthStencilTarget(RenderTargetDepthStencil* rt);
    
    bool isDefaultFBO() const { return _isDefault; }
    unsigned int getWidth() const { return _width; }
    unsigned int getHeight() const { return _height; }

CC_CONSTRUCTOR_ACCESS:
    FrameBuffer();
    virtual ~FrameBuffer();
	// init _defaultFBO with GLView
    bool initWithGLView(GLView* view);
private:
    //openGL content for FrameBuffer
    GLuint _fbo;
    GLuint _previousFBO;
    //dirty flag for fbo binding, if true need apply new attachment
    bool _fboBindingDirty;
    //
    uint8_t _fid;
    //
    Color4F _clearColor;
    float   _clearDepth;
    int8_t  _clearStencil;
    int _width;
    int _height;
    RenderTargetBase* _rt;
    RenderTargetDepthStencil* _rtDepthStencil;
    bool _isDefault;
public:
    static FrameBuffer* getOrCreateDefaultFBO(GLView* glView);
    static void applyDefaultFBO();
    static void clearAllFBOs();
private:
    //static GLuint _defaultFBO;
    static FrameBuffer* _defaultFBO;
    static std::set<FrameBuffer*> _frameBuffers;
};

3.FBO相关操作

1)Create FrameBuffer

FrameBuffer* FrameBuffer::create(uint8_t fid, unsigned int width, unsigned int height)
{
    auto result = new (std::nothrow) FrameBuffer();
    if(result && result->init(fid, width, height))
    {
        result->autorelease();
        return result;
    }
    else
    {
        CC_SAFE_DELETE(result);
        return nullptr;
    }
}
FrameBuffer::FrameBuffer()
: _clearColor(Color4F(0, 0, 0, 1))
, _clearDepth(1.0)
, _clearStencil(0)
, _fbo(0)
, _previousFBO(0)
, _rt(nullptr)
, _rtDepthStencil(nullptr)
, _fboBindingDirty(true)
, _isDefault(false)
{
    _frameBuffers.insert(this);
}
bool FrameBuffer::init(uint8_t fid, unsigned int width, unsigned int height)
{
    _fid = fid;
    _width = width;
    _height = height;
    
    GLint oldfbo;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldfbo);

    glGenFramebuffers(1, &_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, oldfbo);
    
    return true;
}


new FrameBuffer 和init()都没有create RenderTarget,仅仅gen了一个FBO id,所以需要根据使用需求自己生成RenderTarget,并attach到FBO

2)Create RenderTarget

RenderTarget包括:RenderTargetBase(Render Target基类),RenderTarget(colorbuffer attachment为Texture2D的RenderTarget),RenderTargetRenderBuffer(colorbuffer attachment为RenderBuffer的RenderTarget),RenderTargetDepthStencil(DepthStencil attachment为RenderBuffer的RenderTarget),FrameBuffer。

//--------------------------------------------------------------------
//
// RenderTarget
//
//--------------------------------------------------------------------
RenderTarget* RenderTarget::create(unsigned int width, unsigned int height, Texture2D::PixelFormat format/* = Texture2D::PixelFormat::RGBA8888*/)
{
    auto result = new (std::nothrow) RenderTarget();
    if(result && result->init(width, height,format))
    {
        result->autorelease();
        return result;
    }
    else
    {
        CC_SAFE_DELETE(result);
        return nullptr;
    }
}

bool RenderTarget::init(unsigned int width, unsigned int height, Texture2D::PixelFormat format)
{
    if(!RenderTargetBase::init(width, height))
    {
        return false;
    }
    
    _texture = new (std::nothrow) Texture2D();
    if(nullptr == _texture) return false;
    //TODO: FIX me, get the correct bit depth for pixelformat
    auto dataLen = width * height * 4;
    auto data = malloc(dataLen);
    if( nullptr == data) return false;
    
    memset(data, 0, dataLen);
    if(_texture->initWithData(data, dataLen, format, width, height, Size(width, height)))
    {
        _texture->autorelease();
        CC_SAFE_RETAIN(_texture);
        free(data);
    }
    else
    {
        CC_SAFE_DELETE(_texture);
        free(data);
        return false;
    }
    return true;
}


//--------------------------------------------------------------------
//
// RenderTarget
//
//--------------------------------------------------------------------
RenderTargetDepthStencil* RenderTargetDepthStencil::create(unsigned int width, unsigned int height)
{
    auto result = new (std::nothrow) RenderTargetDepthStencil();
    
    if(result && result->init(width, height))
    {
        result->autorelease();
        return result;
    }
    else
    {
        CC_SAFE_DELETE(result);
        return nullptr;
    }
}

bool RenderTargetDepthStencil::init(unsigned int width, unsigned int height)
{
    if(!RenderTargetBase::init(width, height)) return false;
    GLint oldRenderBuffer(0);
    glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRenderBuffer);
    
    //generate depthStencil
    glGenRenderbuffers(1, &_depthStencilBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _depthStencilBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
    glBindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
    
    return true;
}
//--------------------------------------------------------------------
//
// RenderTargetRenderBuffer
//
//--------------------------------------------------------------------
bool RenderTargetRenderBuffer::init(unsigned int width, unsigned int height)
{
    if(!RenderTargetBase::init(width, height)) return false;
    GLint oldRenderBuffer(0);
    glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRenderBuffer);
    
    //generate depthStencil
    glGenRenderbuffers(1, &_colorBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorBuffer);
    //todo: this could have a param
    glRenderbufferStorage(GL_RENDERBUFFER, _format, width, height);
    glBindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
    
    return true;
}

RenderTargetRenderBuffer* RenderTargetRenderBuffer::create(unsigned int width, unsigned int height)
{
    auto result = new (std::nothrow) RenderTargetRenderBuffer();
    
    if(result && result->init(width, height))
    {
        result->autorelease();
        return result;
    }
    else
    {
        CC_SAFE_DELETE(result);
        return nullptr;
    }
}


3)attach RenderTarget

Color Attachment

void FrameBuffer::attachRenderTarget(RenderTargetBase* rt)
{
    if(isDefaultFBO())
    {
        CCLOG("Can not apply render target to default FBO");
        return;
    }
    CC_ASSERT(rt);
    if(rt->getWidth() != _width || rt->getHeight() != _height)
    {
        CCLOG("Error, attach a render target with different size, Skip.");
        return;
    }
    CC_SAFE_RETAIN(rt);
    CC_SAFE_RELEASE(_rt);
    _rt = rt;
    _fboBindingDirty = true;//因为更新_rt,下次applyFBO()需要重新attach color attachment
}


DepthStencil Attachment

void FrameBuffer::attachDepthStencilTarget(RenderTargetDepthStencil* rt)
{
    if(isDefaultFBO())
    {
        CCLOG("Can not apply depth stencil target to default FBO");
        return;
    }
    
    if(nullptr != rt && (rt->getWidth() != _width || rt->getHeight() != _height))
    {
        CCLOG("Error, attach a render target Depth stencil with different size, Skip.");
        return;
    }
    CC_SAFE_RETAIN(rt);
    CC_SAFE_RELEASE(_rtDepthStencil);
    _rtDepthStencil = rt;
    _fboBindingDirty = true;//因为更新了_rtDepthStencil,下次applyFBO()需要重新attach depth stencil attachment
}


4) applyFBO

void FrameBuffer::applyFBO()
{
    CHECK_GL_ERROR_DEBUG();
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_previousFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
//    CCASSERT(_fbo==0 || _fbo != _previousFBO, "calling applyFBO without restoring the previous one");
    CHECK_GL_ERROR_DEBUG();
    if(_fboBindingDirty && !isDefaultFBO())
    {
        CHECK_GL_ERROR_DEBUG();
        if(RenderTargetBase::Type::Texture2D == _rt->getType())
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _rt->getTexture()->getName(), 0);
        else
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _rt->getBuffer());
        CHECK_GL_ERROR_DEBUG();
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nullptr == _rtDepthStencil ? 0 : _rtDepthStencil->getBuffer());
        CHECK_GL_ERROR_DEBUG();
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, nullptr == _rtDepthStencil ? 0 : _rtDepthStencil->getBuffer());
        CHECK_GL_ERROR_DEBUG();
        CCLOG("FBO is %d _fbo %d color, %d ds", _fbo, RenderTargetBase::Type::Texture2D == _rt->getType() ? _rt->getTexture()->getName() : _rt->getBuffer(), nullptr == _rtDepthStencil ? 0 : _rtDepthStencil->getBuffer());
        _fboBindingDirty = false;
    }
    if(GL_FRAMEBUFFER_COMPLETE != glCheckFramebufferStatus(GL_FRAMEBUFFER))
    {
        CCLOG("FrameBuffer Status Error %d", (int)glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
    CHECK_GL_ERROR_DEBUG();
}


在attach时没有进行_rt断言,所以需要注意记得attach

5)clearAllFBO

void FrameBuffer::clearAllFBOs()
{
    for (auto fbo : _frameBuffers)
    {
        fbo->clearFBO();
    }
}
void FrameBuffer::clearFBO()
{
    applyFBO();
    glClearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearColor.a);
    glClearDepth(_clearDepth);
    glClearStencil(_clearStencil);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    restoreFBO();
}
void FrameBuffer::restoreFBO()
{
    glBindFramebuffer(GL_FRAMEBUFFER, _previousFBO);
}



4.Director中FBO使用流程

 1)mainloop

void DisplayLinkDirector::mainLoop()
{
    if (_purgeDirectorInNextLoop)//end()函数会把_purgeDirectorInNextLoop设置为true
    {
        _purgeDirectorInNextLoop = false;//把_purgeDirectorInNextLoop恢复为默认值
        purgeDirector();//清除Director
    }
    else if (_restartDirectorInNextLoop)
    {
        _restartDirectorInNextLoop = false;
        restartDirector();
    }
    else if (! _invalid)
    {
        drawScene();
     
        // release the objects
        PoolManager::getInstance()->getCurrentPool()->clear();
    }
}



2)drawScene

void Director::drawScene()
{
    // calculate "global" dt
    calculateDeltaTime();
    
    if (_openGLView)
    {
        _openGLView->pollEvents();
    }

    //tick before glClear: issue #533
    if (! _paused)
    {
        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);
	_scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }

    _renderer->clear();
    experimental::FrameBuffer::clearAllFBOs();
    /* to avoid flickr, nextScene MUST be here: after tick and before draw.
     * FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9
     */
    if (_nextScene)
    {
        setNextScene();
    }

    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    
    if (_runningScene)
    {
#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)
        _runningScene->stepPhysicsAndNavigation(_deltaTime);
#endif
        //clear draw stats
        _renderer->clearDrawStats();
        
        //render the scene
        _openGLView->renderScene(_runningScene, _renderer);
        
        _eventDispatcher->dispatchEvent(_eventAfterVisit);
    }

    // draw the notifications node
    if (_notificationNode)
    {
        _notificationNode->visit(_renderer, Mat4::IDENTITY, 0);
    }

    if (_displayStats)
    {
        showStats();
    }
    _renderer->render();

    _eventDispatcher->dispatchEvent(_eventAfterDraw);

    popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    _totalFrames++;

    // swap buffers
    if (_openGLView)
    {
        _openGLView->swapBuffers();
    }

    if (_displayStats)
    {
        calculateMPF();
    }
}



3)clearAllFBO

void FrameBuffer::clearAllFBOs()
{
    for (auto fbo : _frameBuffers)
    {
        fbo->clearFBO();
    }
}
void FrameBuffer::clearFBO()
{
    applyFBO();
    glClearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearColor.a);
    glClearDepth(_clearDepth);
    glClearStencil(_clearStencil);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    restoreFBO();
}
void FrameBuffer::restoreFBO()
{
    glBindFramebuffer(GL_FRAMEBUFFER, _previousFBO);
}

4)renderScene

void GLView::renderScene(Scene* scene, Renderer* renderer)
{
    CCASSERT(scene, "Invalid Scene");
    CCASSERT(renderer, "Invalid Renderer");

    if (_vrImpl)
    {
        _vrImpl->render(scene, renderer);
    }
    else
    {
        scene->render(renderer, Mat4::IDENTITY, nullptr);
    }
}



5)scene->render

void Scene::render(Renderer* renderer, const Mat4& eyeTransform, const Mat4* eyeProjection)
{
    auto director = Director::getInstance();
    Camera* defaultCamera = nullptr;
    const auto& transform = getNodeToParentTransform();

    for (const auto& camera : getCameras())
    {
        if (!camera->isVisible())
            continue;

        Camera::_visitingCamera = camera;
        if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT)
        {
            defaultCamera = Camera::_visitingCamera;
        }

        // There are two ways to modify the "default camera" with the eye Transform:
        // a) modify the "nodeToParentTransform" matrix
        // b) modify the "additional transform" matrix
        // both alternatives are correct, if the user manually modifies the camera with a camera->setPosition()
        // then the "nodeToParent transform" will be lost.
        // And it is important that the change is "permanent", because the matrix might be used for calculate
        // culling and other stuff.
        if (eyeProjection)
            camera->setAdditionalProjection(*eyeProjection * camera->getProjectionMatrix().getInversed());
        camera->setAdditionalTransform(eyeTransform.getInversed());

        director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());
		// apply viewport
        camera->apply();
        //clear background with max depth
        camera->clearBackground();
        //visit the scene
        visit(renderer, transform, 0);
#if CC_USE_NAVMESH
        if (_navMesh && _navMeshDebugCamera == camera)
        {
            _navMesh->debugDraw(renderer);
        }
#endif

        renderer->render();
        camera->restore();

        director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

        // we shouldn't restore the transform matrix since it could be used
        // from "update" or other parts of the game to calculate culling or something else.
//        camera->setNodeToParentTransform(eyeCopy);
    }

#if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION
    if (_physics3DWorld && _physics3DWorld->isDebugDrawEnabled())
    {
        director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _physics3dDebugCamera != nullptr ? _physics3dDebugCamera->getViewProjectionMatrix() : defaultCamera->getViewProjectionMatrix());
        _physics3DWorld->debugDraw(renderer);
        renderer->render();
        director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    }
#endif

    Camera::_visitingCamera = nullptr;
//    experimental::FrameBuffer::applyDefaultFBO();
}


6)camera->apply

void Camera::apply()
{
    applyFrameBufferObject();
    applyViewport();
}

void Camera::applyFrameBufferObject()
{
    if(nullptr == _fbo)//如果没有设置_fbo说明使用的是默认FBO,不需要bindFBO
    {
        // inherit from context if it doesn't have a FBO
        // don't call apply the default one
//        experimental::FrameBuffer::applyDefaultFBO();
    }
    else
    {
        _fbo->applyFBO();
    }
}

void Camera::applyViewport()
{
    glGetIntegerv(GL_VIEWPORT, _oldViewport);

    if(nullptr == _fbo)
    {
        glViewport(getDefaultViewport()._left, getDefaultViewport()._bottom, getDefaultViewport()._width, getDefaultViewport()._height);
    }
    else
    {
        glViewport(_viewport._left * _fbo->getWidth(), _viewport._bottom * _fbo->getHeight(),
                   _viewport._width * _fbo->getWidth(), _viewport._height * _fbo->getHeight());
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值