RenderTexture的作用是捕捉画面并输出到Texture或者文件。其底层实现是OpenGL自建帧缓冲(FrameBuffer)

一、帧缓冲知识

语雀内容
帧缓冲包含以下部分:

  • 颜色缓冲(Color Buffer)
    • 存放最终渲染数据,视口(ViewPort)显示的画面就来自于此。
  • 深度缓冲(Depth Buffer)
    • 保存每个像素的深度值
  • 模板缓冲(Stencil Buffer)
    • 保存每个像素的模板值

OpenGL渲染管线生成的像素默认是输出到默认帧缓冲中(default framebuffer),ViewPort视口显示的画面(像素数据)一直是从默认帧缓冲中获取。当我们绑定一个自建的帧缓冲到GL_FRAMEBUFFER时,渲染数据将输出到自建帧缓冲的颜色缓冲中,然后我们就可以从中提取渲染数据,注意此时当前绑定的帧缓冲并不是默认帧缓冲,因此渲染数据并不会显示到视口上。

二、使用示例

  1. auto winSize = Director::getInstance()->getWinSize();
  2. auto renderTexture = RenderTexture::create(winSize.width,
  3. winSize.height,
  4. Texture2D::PixelFormat::RGBA8888);
  5. auto sp = Sprite::create(......);
  6. addChild(renderTexture);
  7. renderTexture.begin();
  8. sp.visit(); // 将 begin、end之间的绘制输出到自建帧缓冲
  9. renderTexture.end();
  10. // 清空颜色缓冲,相当于调用:glClearColor and glClear(GL_COLOR_BUFFER_BIT);
  11. renderTexture->clear(float r, float g, float b, float a);
  12. // 将颜色缓冲区数据输出到图片文件
  13. bool sucess = renderTexture->saveToFile(filename, // 文件名
  14. Image::Format::PNG, // 保存的图片文件格式
  15. [](RenderTexture* rt, // renderTexture
  16. const std::string& filePath) { // 保存到文件路径
  17. });
  18. // Add this function to avoid crash if we switch to a new scene.
  19. Director::getInstance()->getRenderer()->render();
  20. if(sucess){
  21. // 保存成功。
  22. }
  23. // 获取输出的纹理数据,可用于再创建
  24. auto sprite = Sprite::createWithTexture( renderTexture->getSprite()->getTexture() );

三、源码分析

1、initWithWidthAndHeight

初始化函数。主要的工作是构建自定义帧缓冲,包括:

  • 颜色附件:纹理类型,由一个Texture2D*指向
  • 深度附件
  • 模板附件 ```cpp

bool RenderTexture::initWithWidthAndHeight( int w, int h, // renderTexture的宽高 Texture2D::PixelFormat format, // 帧缓冲颜色附件格式,也即是纹理格式,只支持RGB、RGBA GLuint depthStencilFormat) // 帧缓冲深度/模板附件 { CCASSERT(format != Texture2D::PixelFormat::A8, “only RGB and RGBA formats are valid for a render texture”);

  1. bool ret = false;
  2. void *data = nullptr;
  3. do
  4. {
  5. _fullRect = _rtTextureRect = Rect(0,0,w,h);
  6. //Size size = Director::getInstance()->getWinSizeInPixels();
  7. //_fullviewPort = Rect(0,0,size.width,size.height);
  8. w = (int)(w * CC_CONTENT_SCALE_FACTOR());
  9. h = (int)(h * CC_CONTENT_SCALE_FACTOR());
  10. _fullviewPort = Rect(0,0,w,h);
  11. glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO); // 保存在当前已绑定的帧缓冲,方便后续恢复
  12. // 默认帧缓冲 = 0
  13. // *****************************************************
  14. // 下面的工作主要构件一个合适大小的纹理,作为帧缓冲的颜色附件(纹理类型)
  15. // *****************************************************
  16. // textures must be power of two squared
  17. int powW = 0;
  18. int powH = 0;
  19. if (Configuration::getInstance()->supportsNPOT())
  20. {
  21. powW = w;
  22. powH = h;
  23. }
  24. else
  25. {
  26. powW = ccNextPOT(w);
  27. powH = ccNextPOT(h);
  28. }
  29. auto dataLen = powW * powH * 4; // 最大的纹理格式就是RGBA888,4个byte
  30. data = malloc(dataLen);
  31. CC_BREAK_IF(! data);
  32. memset(data, 0, dataLen); // 内存区域初始化为0
  33. _pixelFormat = format;
  34. if (_texture = new (std::nothrow) Texture2D())
  35. {
  36. // 通过内存中的图像数据(imagedata)data,在显存中创建一个纹理
  37. _texture->initWithData(data, dataLen,
  38. (Texture2D::PixelFormat)_pixelFormat, // 纹理格式
  39. powW, powH, // 纹理的像素狂傲
  40. Size((float)w, (float)h));
  41. }
  42. ......
  43. GLint oldRBO;
  44. glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRBO); // 保存当前已绑定的渲染缓冲,相关知识见:https://www.yuque.com/tvvhealth/cs/kos5bz
  45. // _textureCopy主要是用于解决某些高通GPU的会出现的问题
  46. /* Certain Qualcomm Adreno GPU's will retain data in memory after a frame buffer
  47. switch which corrupts the render to the texture.
  48. The solution is to clear the frame buffer before rendering to the texture. However,
  49. calling glClear has the unintended result of clearing the current texture.
  50. Create a temporary texture to overcome this.
  51. At the end of RenderTexture::begin(),
  52. switch the attached texture to the second one, call glClear, and then switch back to
  53. the original texture. This solution is unnecessary for other devices as they don't have
  54. the same issue with switching frame buffers.
  55. */
  56. if (Configuration::getInstance()->checkForGLExtension("GL_QCOM"))
  57. {
  58. _textureCopy = new (std::nothrow) Texture2D();
  59. if (_textureCopy)
  60. {
  61. _textureCopy->initWithData(data, dataLen,
  62. (Texture2D::PixelFormat)_pixelFormat,
  63. powW, powH,
  64. Size((float)w, (float)h));
  65. }
  66. else
  67. {
  68. break;
  69. }
  70. }
  71. glGenFramebuffers(1, &_FBO); // 创建帧缓冲
  72. glBindFramebuffer(GL_FRAMEBUFFER, _FBO); // 绑定到当前帧缓冲
  73. // associate texture with FBO
  74. // 将帧缓冲的颜色附件设置为_texture纹理,也即是设置为纹理类型附件。
  75. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0);
  76. // 帧缓冲还必须构建深度、模板附件,否则framebuffer is not complete
  77. if (depthStencilFormat != 0) {
  78. _depthAndStencilFormat = depthStencilFormat;
  79. setupDepthAndStencil(powW, powH); // 见下面解释
  80. }
  81. // 检查帧缓冲的完整性,非常有必要
  82. CCASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Could not attach texture to framebuffer");
  83. // 设置抗锯齿效果,其实做的就是设置的纹理的Filtering为Linear
  84. // 这更消耗性能,不知道为什么一定要这么干。
  85. _texture->setAntiAliasTexParameters();
  86. if (_textureCopy) {
  87. _textureCopy->setAntiAliasTexParameters();
  88. }
  89. // 这里主要是让RenderTexture能显示出来,自建的帧缓冲的颜色附件是不会被输出到屏幕上的
  90. setSprite(Sprite::createWithTexture(_texture));
  91. _texture->release();
  92. _sprite->setFlippedY(true);
  93. // alpha预乘:GL_ONE, GL_ONE_MINUS_SRC_ALPHA
  94. _sprite->setBlendFunc( BlendFunc::ALPHA_PREMULTIPLIED );
  95. _sprite->setOpacityModifyRGB(true);
  96. // 切换会默认帧缓冲,_FBO保存了我们创建的帧缓冲
  97. glBindRenderbuffer(GL_RENDERBUFFER, oldRBO);
  98. glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
  99. // Disabled by default.
  100. _autoDraw = false;
  101. // add sprite for backward compatibility
  102. addChild(_sprite);
  103. ret = true;
  104. } while (0);
  105. CC_SAFE_FREE(data);
  106. return ret;

}

  1. ```cpp
  2. // 构建帧缓冲的深度、模板附件
  3. void RenderTexture::setupDepthAndStencil(int powW, int powH)
  4. {
  5. ......; // 这里是android平台的构建逻辑,我们只看windows平台的,原理都一样
  6. // 附件类型是渲染缓冲类型RenderBuffer,这种类型的好处在于写入速度非常快,但前提是不会被读取
  7. // 深度、模板值,应用端很少需要去读取,所以非常适合。
  8. glGenRenderbuffers(1, &_depthRenderBuffer); // 创建渲染缓冲对象
  9. glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer); // 绑定到当前渲染缓冲类型
  10. glRenderbufferStorage(GL_RENDERBUFFER, // 给当前绑定的renderbuffer分配数据存储内存并指定格式
  11. _depthAndStencilFormat, // 格式
  12. (GLsizei)powW, (GLsizei)powH); // 大小
  13. // 将当前的帧缓冲对象的深度附件设置为将当前绑定的渲染缓冲对象(即_depthRenderBuffer)
  14. glFramebufferRenderbuffer(GL_FRAMEBUFFER,
  15. GL_DEPTH_ATTACHMENT,
  16. GL_RENDERBUFFER,
  17. _depthRenderBuffer);
  18. // if depth format is the one with stencil part,
  19. // bind same render buffer as stencil attachment
  20. // GL_DEPTH24_STENCIL8表示帧缓冲_depthRenderBuffer管理的数据存储区的每个像素的前24bit为深度值,然后是8bit的模板值
  21. // 所以我们还要设置帧缓冲的模板附件同样为当前绑定的渲染缓冲对象(即_depthRenderBuffer)
  22. if (_depthAndStencilFormat == GL_DEPTH24_STENCIL8) {
  23. glFramebufferRenderbuffer(GL_FRAMEBUFFER,
  24. GL_STENCIL_ATTACHMENT,
  25. GL_RENDERBUFFER,
  26. _depthRenderBuffer);
  27. }
  28. }

2、visit

主要工作是绘制自建帧缓冲的颜色缓冲内容,以及调用下面的draw,将子节点渲染数据输出到自建帧缓冲。

  1. void RenderTexture::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
  2. {
  3. // override visit.
  4. // Don't call visit on its children
  5. if (!_visible)
  6. {
  7. return;
  8. }
  9. uint32_t flags = processParentFlags(parentTransform, parentFlags);
  10. Director* director = Director::getInstance();
  11. // IMPORTANT:
  12. // To ease the migration to v3.0, we still support the Mat4 stack,
  13. // but it is deprecated and your code should not rely on it
  14. director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  15. director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);
  16. // 将自建帧缓冲当前的颜色缓冲输出到屏幕。
  17. // 这就是为什么“重定向了”帧缓冲,但是依然能在屏幕上绘制自建缓冲内容的原因。
  18. // 不明白我说的,请看OpenGL帧缓冲的知识:https://www.yuque.com/tvvhealth/cs/kos5bz#y7my3
  19. _sprite->visit(renderer, _modelViewTransform, flags);
  20. if (isVisitableByVisitingCamera())
  21. {
  22. draw(renderer, _modelViewTransform, flags);
  23. }
  24. director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  25. // FIX ME: Why need to set _orderOfArrival to 0??
  26. // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
  27. // setOrderOfArrival(0);
  28. }

3、draw

将RenderTexture的子节点渲染数据输出到自定义的帧缓冲中。

  1. void RenderTexture::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
  2. {
  3. if (_autoDraw)
  4. {
  5. // Begin will create a render group using new render target
  6. // 创建一个单独RenderQueue,并将RenderQueueID放置绘制栈栈顶,
  7. // 这样后续的renderCommand都将插入这个RenderQueue。
  8. // 紧接着在RenderQueue中插入了一个onBegin的CustomCommand,用于在
  9. // 绘制字节之前,将当前帧缓冲绑定为自定义帧缓存
  10. begin();
  11. // 清理默认帧缓冲(screen buffer)的颜色、深度、模板附件
  12. _clearCommand.init(_globalZOrder);
  13. _clearCommand.func = CC_CALLBACK_0(RenderTexture::onClear, this);
  14. renderer->addCommand(&_clearCommand);
  15. // 子节点排序,localzOrder的升序排序
  16. sortAllChildren();
  17. // 绘制子节点,绘制指令都插入的单独RenderQueue,且子节点的渲染数据都将
  18. // 输出到自定义的帧缓冲中
  19. for(const auto &child: _children)
  20. {
  21. if (child != _sprite)
  22. child->visit(renderer, transform, flags);
  23. }
  24. // 向RenderQueue插入一个onEnd CustomCommand,用于将帧缓冲回复成默认缓冲
  25. // 然后RenderQueueID从栈顶弹栈,这样后续的RenderCommand将不会再插入这个RenderQueue
  26. end();
  27. }
  28. }

4、begin

主要工作

  • 新建RenderQueue
    • 用于接收接下来的绘制指令,这样就可以灵活选择需要输出的绘制内容。
  • onBegin回调
    • 在执行指令绘制前的初始化工作
    • 这个函数是作为RenderQueue的第一个RenderCommand,保证最前执行 ```cpp

void RenderTexture::begin() { Director* director = Director::getInstance(); CCASSERT(nullptr != director, “Director is null when setting matrix stack”);

  1. director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  2. _projectionMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  3. director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  4. _transformMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  5. if(!_keepMatrix)
  6. {
  7. director->setProjection(director->getProjection());
  8. const Size& texSize = _texture->getContentSizeInPixels();
  9. // Calculate the adjustment ratios based on the old and new projections
  10. Size size = director->getWinSizeInPixels();
  11. float widthRatio = size.width / texSize.width;
  12. float heightRatio = size.height / texSize.height;
  13. Mat4 orthoMatrix;
  14. Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix);
  15. director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
  16. }
  17. // groupCommand在构造的时候已经指向了一个闲置的空的RenderQueue
  18. // RenderQueue一直都在绘制栈中,必须要有一个groupCommand指向,它里面的renderCommand才会被执行
  19. _groupCommand.init(_globalZOrder);
  20. Renderer *renderer = Director::getInstance()->getRenderer();
  21. // 执行groupcommand时就是执行RenderQueue的指令。
  22. renderer->addCommand(&_groupCommand);
  23. // RenderQueue设置在绘制栈顶,后续的绘制指令都将插入这个RenderQueue
  24. renderer->pushGroup(_groupCommand.getRenderQueueID());
  25. // RenderQueue的第一条Command,执行初始化。
  26. _beginCommand.init(_globalZOrder);
  27. _beginCommand.func = CC_CALLBACK_0(RenderTexture::onBegin, this);
  28. Director::getInstance()->getRenderer()->addCommand(&_beginCommand);

}

void RenderTexture::onBegin() { // Director *director = Director::getInstance();

  1. _oldProjMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  2. director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _projectionMatrix);
  3. _oldTransMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  4. director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _transformMatrix);
  5. if(!_keepMatrix)
  6. {
  7. director->setProjection(director->getProjection());
  8. const Size& texSize = _texture->getContentSizeInPixels();
  9. // Calculate the adjustment ratios based on the old and new projections
  10. Size size = director->getWinSizeInPixels();
  11. float widthRatio = size.width / texSize.width;
  12. float heightRatio = size.height / texSize.height;
  13. Mat4 orthoMatrix;
  14. Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix);
  15. director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
  16. }
  17. //calculate viewport
  18. {
  19. Rect viewport;
  20. viewport.size.width = _fullviewPort.size.width;
  21. viewport.size.height = _fullviewPort.size.height;
  22. float viewPortRectWidthRatio = float(viewport.size.width)/_fullRect.size.width;
  23. float viewPortRectHeightRatio = float(viewport.size.height)/_fullRect.size.height;
  24. viewport.origin.x = (_fullRect.origin.x - _rtTextureRect.origin.x) * viewPortRectWidthRatio;
  25. viewport.origin.y = (_fullRect.origin.y - _rtTextureRect.origin.y) * viewPortRectHeightRatio;
  26. //glViewport(_fullviewPort.origin.x, _fullviewPort.origin.y, (GLsizei)_fullviewPort.size.width, (GLsizei)_fullviewPort.size.height);
  27. glViewport(viewport.origin.x, viewport.origin.y, (GLsizei)viewport.size.width, (GLsizei)viewport.size.height);
  28. }
  29. glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO); // 保存当前绑定帧缓存
  30. glBindFramebuffer(GL_FRAMEBUFFER, _FBO); // 设置当前帧缓存为自建的帧缓存
  31. ......

}

  1. <a name="1dRSR"></a>
  2. ## 5、end
  3. RenderTexure绘制指令接收完毕,RenderQueue不再接收绘制指令,帧缓存恢复成绑定前的帧缓存。
  4. ```cpp
  5. void RenderTexture::end()
  6. {
  7. // 恢复先前绑定的帧缓存,一般就是默认帧缓存。
  8. _endCommand.init(_globalZOrder);
  9. _endCommand.func = CC_CALLBACK_0(RenderTexture::onEnd, this);
  10. Director* director = Director::getInstance();
  11. CCASSERT(nullptr != director, "Director is null when setting matrix stack");
  12. // 插入到RenderQueue中的最后一条
  13. Renderer *renderer = director->getRenderer();
  14. renderer->addCommand(&_endCommand);
  15. renderer->popGroup(); //将RenderQueueID弹栈,不再由这个RenderQueue接收绘制指令。
  16. director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  17. director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  18. }
  19. void RenderTexture::onEnd()
  20. {
  21. Director *director = Director::getInstance();
  22. glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
  23. // restore viewport
  24. director->setViewport();
  25. const auto& vp = Camera::getDefaultViewport();
  26. glViewport(vp._left, vp._bottom, vp._width, vp._height);
  27. //
  28. director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _oldProjMatrix);
  29. director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _oldTransMatrix);
  30. }