cocos中RenderTexture主要用来实现截屏,然后把截取出来的图片保存到磁盘中,除了保存图片和渲染纹理,它还可以得到一些预渲染结果,并将这些结果作为一种纹理数据。
例如我们可以用RGB5_A1的纹理格式来保存一些Alpha值,这些Alpha值表示一种类型的蒙版,它们可以作为多重纹理数据被用在其他元素中进行过滤处理 。再比如我们可以通过
深度和模版测试产生一些颜色值来决定其他元素的绘制行为。本文主要分析最常用的用途,截屏并保存。
比如代码如下:
auto layer2 = LayerColor::create(Color4B(25, 100, 155,255), 568, 320); // scene->addChild(layer2); // layer2->setPosition(100,100); auto background=Sprite::create("bbb.png"); background->setAnchorPoint(Vec2(0,0)); auto rend=RenderTexture::create(designSize.width,designSize.height); // auto rect=Rect(0,0,designSize.width,designSize.height); //rend->setVirtualViewport(Vec2(0,0),rect,rect); auto rect=Rect(0,0,designSize.width/2,designSize.height/2); rend->setVirtualViewport(Vec2(0,0),rect,rect); rend->setKeepMatrix(true); rend->begin(); layer2->visit(); background->visit();//他的command就会放到新的renderqueue里面了
//不起作用,因为他主要是基于UI树的遍历顺序,将后续直到End之前的一些元素的绘制命令封装到一起,ZOrder包括local和global不再起作用
(*layer2).setGlobalZOrder(2);
(*background).setLocalZOrder(1);
rend->end(); rend->saveToFile("bbbbbb.png",true); rend->getSprite()->setAnchorPoint(Vec2::ZERO); // // rend->setPosition(Vec2(designSize.width/2,designSize.height/2)); rend->setPosition(Vec2(0,0)); scene->addChild(rend);
在 rend之间,绘制了一个sprite和一个layerColor,然后绘制完毕把纹理保存到了bbbbbb.png中
绘制的结果如图:
绘制了个四分之一大小的layerColor和一个sprite,下面分析下是如何做到的:
在分析之前,需要先了解 帧缓冲 的概念,网上的资料比较多,比如参考一下几个地址:
https://www.cnblogs.com/George1994/p/6361442.html
https://blog.csdn.net/cauchyweierstrass/article/details/53166940#%E6%B8%B2%E6%9F%93%E7%BC%93%E5%86%B2%E5%AF%B9%E8%B1%A1%E9%99%84%E7%9D%80
下面开始分析RenderTexture的绘制原理:
1 首先要创建RenderTexture,进入create方法中
RenderTexture * RenderTexture::create(int w, int h) { RenderTexture *ret = new RenderTexture(); if(ret && ret->initWithWidthAndHeight(w, h, Texture2D::PixelFormat::RGBA8888, 0)) { ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; } /** * 初始化RenderTexture * * @param w 宽度 * @param h 高度 * @param format 客户端像素格式 * * @return true: 初始化成功 false: 初始化失败 */ bool RenderTexture::initWithWidthAndHeight(int w, int h, Texture2D::PixelFormat eFormat) { return initWithWidthAndHeight(w, h, eFormat, 0); } /** * 初始化RenderTexture * * @param w 宽度 * @param h 高度 * @param format 客户端像素格式 * @param depthStencilFormat 深度模板缓冲格式 * * @return true: 初始化成功 false: 初始化失败 */ bool RenderTexture::initWithWidthAndHeight(int w, int h, Texture2D::PixelFormat format, GLuint depthStencilFormat) { CCASSERT(format != Texture2D::PixelFormat::A8, "only RGB and RGBA formats are valid for a render texture"); bool ret = false; void *data = nullptr; do { _fullRect = _rtTextureRect = Rect(0,0,w,h); //Size size = Director::getInstance()->getWinSizeInPixels(); //_fullviewPort = Rect(0,0,size.width,size.height); //宽度和高度乘以缩放比 w = (int)(w * CC_CONTENT_SCALE_FACTOR()); h = (int)(h * CC_CONTENT_SCALE_FACTOR()); _fullviewPort = Rect(0,0,w,h); //查看帧缓冲绑定的状态,返回到oldFBO中 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO); // textures must be power of two squared //保存纹理的宽度和高度 int powW = 0; int powH = 0; //检查设备是否支持纹理为非2的幂次方 if (Configuration::getInstance()->supportsNPOT()) { //支持就用RenderTexture的大小作为纹理的大小 powW = w; powH = h; } else { //不支持,则转换为2的幂次方 powW = ccNextPOT(w); powH = ccNextPOT(h); } //根据纹理的大小申请的字节数,每个像素4字节 auto dataLen = powW * powH * 4; //申请内存 data = malloc(dataLen); //申请失败,跳出 CC_BREAK_IF(! data); //使用内存 memset(data, 0, dataLen); //客户端像素格式 _pixelFormat = format; /* 使用(w,h)对应的实际分辨率的大小创建了一个Texture2D对象 */ //创建一个纹理对象 _texture = new Texture2D(); if (_texture) { //初始化纹理 _texture->initWithData(data, dataLen, (Texture2D::PixelFormat)_pixelFormat, powW, powH, Size((float)w, (float)h)); } else { break; } GLint oldRBO; glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRBO); if (Configuration::getInstance()->checkForGLExtension("GL_QCOM")) { _textureCopy = new Texture2D(); if (_textureCopy) { _textureCopy->initWithData(data, dataLen, (Texture2D::PixelFormat)_pixelFormat, powW, powH, Size((float)w, (float)h)); } else { break; } } // generate FBO // generate FBO //生成帧缓冲对象(数量为1个) glGenFramebuffers(1, &_FBO); //绑定帧缓冲对象 glBindFramebuffer(GL_FRAMEBUFFER, _FBO); // associate texture with FBO //设置将帧缓冲区颜色数据输出到纹理 //通过FramebufferTexture2D将texture附加到一个新创建的Framebuffer的 //GL_COLOR_ATTACHMENT0上,用来存储镇缓冲的颜色数据 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0); /*如果该镇缓冲支持深度和模版,则创建一个额外的RenderBuffer用来存储深度和模版数据 这里通常使用GL_DEPTH24_STENCIL8存储深度和模版值。注意,这些数据并不能在RenderTexutre之外被使用。他们仅用来支持镇缓冲渲染功能,例如在该RenderTexture 渲染期间使用了深度和模版测试,他可以保证结果被正确绘制,如果需要使用深度和模版值,则需要自定义镇缓冲对象 */ //使用了深度缓冲 if (depthStencilFormat != 0) { //create and attach depth buffer //创建1个渲染深度缓冲对象并绑定 glGenRenderbuffers(1, &_depthRenderBufffer); glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBufffer); //设置渲染缓冲对象的像素格式,尺寸 glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, (GLsizei)powW, (GLsizei)powH); //将渲染缓冲对象绑定到当前的帧缓冲中名为GL_DEPTH_ATTACHMENT的逻辑缓冲区中,帧缓冲将修改该附加点的状态 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBufffer); // if depth format is the one with stencil part, bind same render buffer as stencil attachment //深度缓冲格式是24位深度,8位模版缓冲,则将渲染深度缓冲对象绑定到当前帧缓冲名为GL_STENCIL_ATTACHMENT的逻辑缓冲区中,修改附加点的状态。 if (depthStencilFormat == GL_DEPTH24_STENCIL8) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBufffer); } } //最后通过CheckFrameBufferStatus来检查镇缓冲的完成状态,例如设置了不被支持的格式, //则在Debug状态下会报错,但是在实际发布后这个检测并不会执行,这也是使用GL命令的最佳实践 //帧缓冲状态必须是完成状态 // check if it worked (probably worth doing :) ) CCASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Could not attach texture to framebuffer"); //设置纹理不使用抗锯齿模糊 _texture->setAliasTexParameters(); // retained //将纹理绑定到精灵,将精灵绑定到RederTexture setSprite(Sprite::createWithTexture(_texture)); //释放纹理 _texture->release(); //设置精灵Y翻转 _sprite->setFlippedY(true); //设置alpha的混合模式 _sprite->setBlendFunc( BlendFunc::ALPHA_PREMULTIPLIED ); //还原渲染缓冲对象和帧缓冲对象 glBindRenderbuffer(GL_RENDERBUFFER, oldRBO); glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO); // Diabled by default. _autoDraw = false; // add sprite for backward compatibility //添加精灵以实现向后兼容 addChild(_sprite); ret = true; } while (0); CC_SAFE_FREE(data); return ret; }
代码差不多都加了注释
主要做了以下几点:
1 把我们传进来的以width和height的设计分辨率大小,开辟空间传给纹理对象
2 创建了一个纹理对象,之后把这个纹理绑定到帧缓冲中,然后之后绘制的内容就会保存到这个纹理中了
3 生成了一个帧缓冲对象,并将帧缓冲颜色数据输出到1中创建的纹理,如果使用了深度缓冲,在创建一个深度渲染缓冲对象
4 把创建的纹理对象 的指针指向 RenderTexture中,因为RenderTexture也继承于Node,最后他会把自己的sprite绘制到屏幕帧缓冲中,这里主要是为了展示我们绘制到帧缓冲中的结果
和我们自己创建的帧缓冲对象用的是同一个纹理数据
5 设置完毕还原回以前的帧缓冲对象和渲染缓冲对象
方法完毕
2 绘制过程,主要分三步,
renderTexture->begin()
元素绘制
renderTexture->end().
在这里要说一下这个begin和end,相当于一个出栈和入栈的操作,主要用了GroupCommand命令。
cocos中的Renderer维护着一个RenderQueue数组,每隔RenderQuque记录了一组RenderCommand,每个RenderCommand通常由其globalOrder属性决定绘制顺序。
Renderer同时维护着一个RenderQueue的Id组成的栈,每个元素的绘制命令通过AddCommand发送到Renderer,Renderer会将其放置到Id栈中最后一个元素对应的RenderQueue上。
一个GroupCommand在初始化的时候会创建一个新的RenderQueue并添加到Renderer的Id栈上,这样后续元素的RenderCommand将会被添加到新的RenderQueue,从而实现分组绘制,直到该GroupCommand绘制完毕将其从Id栈移除,从此不会影响后续的绘制命令。
当所有的UI元素被遍历之后,Renderer会从RenderQueue数组的第一个RenderQueue开始绘制,如果某个RenderCommand的类型是GroupCommand,则找到该GroupCommand记录的RenderQueue开始绘制,从而实现了分组绘制。
进入代码:
/** * 开始绘制 */ void RenderTexture::begin() { Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when seting matrix stack"); //在矩阵变换之前把当前矩阵保存在矩阵栈中 director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); //获取相机和裁剪矩阵的乘矩阵 _projectionMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); //最新的模型视图矩阵,对当前Node来说,乘以这个矩阵,得到世界坐标,RenderTexture也是继承于Node,和普通Node一样对待 //同样在RenderTexture begin和end绘制以内的sprite等,也用到了最顶层的transformMatrix,和 RenderTexure的坐标系是一致的,它们是平级关系 _transformMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); //不使用矩阵保持,进行矩阵变换 if(!_keepMatrix) { director->setProjection(director->getProjection()); //纹理尺寸 const Size& texSize = _texture->getContentSizeInPixels(); // Calculate the adjustment ratios based on the old and new projections Size size = director->getWinSizeInPixels(); float widthRatio = size.width / texSize.width; float heightRatio = size.height / texSize.height; Mat4 orthoMatrix; Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix); director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix); } //初始化组渲染指令 _groupCommand.init(_globalZOrder); /* */ //将组渲染指令加入当前组渲染队列之中 //RenderTexure会新创建一个RenderQueue Renderer *renderer = Director::getInstance()->getRenderer(); renderer->addCommand(&_groupCommand); //将渲染队列Id加入到保存渲染队列Id的栈中 renderer->pushGroup(_groupCommand.getRenderQueueID()); //初始化开始渲染指令 //加入了最新创建的RenderQueue中 _beginCommand.init(_globalZOrder); //设置回调 _beginCommand.func = CC_CALLBACK_0(RenderTexture::onBegin, this); //将开始渲染指令加入到组渲染队列之中 Director::getInstance()->getRenderer()->addCommand(&_beginCommand); }
看到和之前的分析是一致的。把groupCommand加入最顶层的Renderqueue中,然后beginCommand作为groupCommand的中的一个自定义绘制命令,绑定方法
RenderTexture::onBegin,作为groupCommand中的第一个执行命令的方法,具体执行代码后面说。这里需要说明的一点就是projectMatrix和modelMatrix的处理。
以为RenderTexture也是作为一个普通Node处理的,通过入栈得到最顶层的矩阵得到transformMatrix和prjectMatrix,这两个矩阵是遍历到的visit方法中最新的矩阵,
RenderTexture的本地坐标乘以 modelMatrix就可以得到世界坐标,具体原理请看Node::visit. 不过一般来说RenderTexutre获取的这两个矩阵就是最底层的矩阵,因为
begin方法是在第一个Node::Visit方法之前执行的,此时,栈里面就有一个最底层的矩阵。
/** * 绘制结束 */ void RenderTexture::end() { //初始化结束渲染指令 _endCommand.init(_globalZOrder); //设置回调 _endCommand.func = CC_CALLBACK_0(RenderTexture::onEnd, this); Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when seting matrix stack"); Renderer *renderer = director->getRenderer(); //将指令加入渲染队列 renderer->addCommand(&_endCommand); //将组渲染指令生成的渲染队列弹出渲染队列 renderer->popGroup(); //还原矩阵 director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); }
当所有需要在该组一起绘制的元素都被遍历之后,需要从Renderer的Id栈移除该GroupCommand对应的RenderQueue,这样后续任何绘制命令都不会再受该GroupCommand的影响。
从Id栈移除当前的RenderQueue的时候,哈可以还原一些客户端或者GL的状态设置,以免影响后续的绘制。
Visit方法是指 如Sprite::Visit或者Layer::Visit的方法,这些方法在之前我们已经分析过,和之前绘制到屏幕缓冲区基本是一样的,唯一不同的地方是,此时它们的绘制命令加入到的是当前GroupCommand对应的RenderQueue了,具体实现逻辑在代码Renderder::visitRenderQueue中,关键代码如下:
void Renderer::visitRenderQueue(const RenderQueue& queue) { ssize_t size = queue.size(); for (ssize_t index = 0; index < size; ++index) { auto command = queue[index]; auto commandType = command->getType(); if(RenderCommand::Type::GROUP_COMMAND == commandType) { flush(); int renderQueueID = ((GroupCommand*) command)->getRenderQueueID(); visitRenderQueue(_renderGroups[renderQueueID]); } } }
如果是GroupCommand命令,拿到command对应的id,然后遍历renderqueue。之后layer和sprite绘制的内容会存储到自己创建的帧缓冲中的纹理中。
3 这里还有2个地方需要说明,第一是 RendeTexuture有自己的sprite,那么这个sprite的绘制在什么地方和时机。看方法RenderTexture::visit
//RenderTexture重写了Node 的 visit方法 void RenderTexture::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) { //重写父类的visit方法,不再遍历所有子元素 // override visit. // Don't call visit on its children if (!_visible) { return; } //处理父节点的标识 uint32_t flags = processParentFlags(parentTransform, parentFlags); //为了让开发者可以将2dx2.0的项目移植到2dx3.0的版本,扔支持矩阵堆栈 //但该方法已经废弃了,不推荐使用 Director* director = Director::getInstance(); // IMPORTANT: // To ease the migration to v3.0, we still support the Mat4 stack, // but it is deprecated and your code should not rely on it director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); //绘制自己的sprite _sprite->visit(renderer, _modelViewTransform, flags); draw(renderer, _modelViewTransform, flags); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); _orderOfArrival = 0; }
RenderTexture自己实现了visit,而其余比如sprite,layer都是继承的Node,RenderTexture通过重写visit的方法,把自己当成一个普通Node,绘制到了屏幕缓冲区中(不是创建的GroupCommand对应的帧缓冲)。
第二是 RenderTexture的onBegin和onEnd,这两个作为customCommand的回调方法,进行一些初始化设置,代码如下:
/** * 开始渲染指令的回调 */ void RenderTexture::onBegin() { // Director *director = Director::getInstance(); //保存变换前矩阵 _oldProjMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _projectionMatrix); _oldTransMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _transformMatrix); //不保持矩阵,进行矩阵变换 if(!_keepMatrix) { director->setProjection(director->getProjection()); #if CC_TARGET_PLATFORM == CC_PLATFORM_WP8 Mat4 modifiedProjection = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); modifiedProjection = CCEGLView::sharedOpenGLView()->getReverseOrientationMatrix() * modifiedProjection; director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,modifiedProjection); #endif const Size& texSize = _texture->getContentSizeInPixels(); // Calculate the adjustment ratios based on the old and new projections Size size = director->getWinSizeInPixels(); float widthRatio = size.width / texSize.width; float heightRatio = size.height / texSize.height; Mat4 orthoMatrix; Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix); director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix); } else { #if CC_TARGET_PLATFORM == CC_PLATFORM_WP8 Mat4 modifiedProjection = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); modifiedProjection = CCEGLView::sharedOpenGLView()->getReverseOrientationMatrix() * modifiedProjection; director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, modifiedProjection); #endif } //计算视口逻辑 //calculate viewport { Rect viewport; viewport.size.width = _fullviewPort.size.width; viewport.size.height = _fullviewPort.size.height; float viewPortRectWidthRatio = float(viewport.size.width)/_fullRect.size.width; float viewPortRectHeightRatio = float(viewport.size.height)/_fullRect.size.height; viewport.origin.x = (_fullRect.origin.x - _rtTextureRect.origin.x) * viewPortRectWidthRatio; viewport.origin.y = (_fullRect.origin.y - _rtTextureRect.origin.y) * viewPortRectHeightRatio; //glViewport(_fullviewPort.origin.x, _fullviewPort.origin.y, (GLsizei)_fullviewPort.size.width, (GLsizei)_fullviewPort.size.height); glViewport(viewport.origin.x, viewport.origin.y, (GLsizei)viewport.size.width, (GLsizei)viewport.size.height); } // Adjust the orthographic projection and viewport //检查帧缓冲绑定状态,返回到_oldFBO中 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO); //绑定帧缓冲对象_FBO glBindFramebuffer(GL_FRAMEBUFFER, _FBO); //TODO move this to configration, so we don't check it every time /* Certain Qualcomm Andreno gpu's will retain data in memory after a frame buffer switch which corrupts the render to the texture. The solution is to clear the frame buffer before rendering to the texture. However, calling glClear has the unintended result of clearing the current texture. Create a temporary texture to overcome this. At the end of RenderTexture::begin(), switch the attached texture to the second one, call glClear, and then switch back to the original texture. This solution is unnecessary for other devices as they don't have the same issue with switching frame buffers. */ if (Configuration::getInstance()->checkForGLExtension("GL_QCOM")) { // -- bind a temporary texture so we can clear the render buffer without losing our texture glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _textureCopy->getName(), 0); CHECK_GL_ERROR_DEBUG(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0); } } /** * 结束渲染指令回调 */ void RenderTexture::onEnd() { Director *director = Director::getInstance(); //检查帧缓冲状态返回到_oldFBO对象 glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO); //还原视口 // restore viewport director->setViewport(); //还原回老的 裁剪照相矩阵和模型适口矩阵 director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _oldProjMatrix); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _oldTransMatrix); }
看标红的代码glviewport,我们知道在cocos中的计算几乎全是用的设计分辨率,之后在设置屏幕视口的时候才用了实际分辨率,具体位置在
//设置视口大小,和屏幕分辨率一样 void Director::setViewport() { if (_openGLView) { //设置视口大小 ,单位为像素 //在内部将基于设计分辨率的坐标信息转换为基于屏幕实际像素大小的坐标信息,然后 //使用glViewPort进行设置 _openGLView->setViewPortInPoints(0, 0, _winSizeInPoints.width, _winSizeInPoints.height); } }
这是因为在屏幕渲染之前,我们通过照相机,裁剪之后,最后会得到归一化的坐标,范围是(-1,1),之后再通过实际分辨率计算出顶点在屏幕中的最终位置,所以计算过程中只要求出归一化坐标即可,因为多数情况下 设计分辨率和实际分辨率比例是一样的,所以会得到相同的归一化坐标。
但是看这里在我们自己创建的帧缓冲绘制过程中,设置glviewport是用的设计分辨率,这是为什么呢。因为 比如我们需要把sprite绘制到帧缓冲中,sprite通过模型,裁剪矩阵等计算之后得到了归一化坐标,然后此时不是绘制到屏幕视口中,而是绘制到我们自己设置的视口中,但是计算方法是一样的,只不过是宽高不一样,只要比例不变,是不会变形的。并且在Sprite中的texture的width和height也是以设计分辨率为标准的,这样把viewport同样以设计分辨率计算,更容易理解。
比如我们做个测试,还是之前那块代码,我们改一下sprite的坐标,让他居中显示,如果是绘制到屏幕中,很好理解,但如果把他绘制到自己创建的帧缓冲中,看是否会居中展示,答案是 会的。如下:
auto layer2 = LayerColor::create(Color4B(25, 100, 155,255), 568, 320); // scene->addChild(layer2); // layer2->setPosition(100,100); auto background=Sprite::create("bbb.png"); // background->setAnchorPoint(Vec2(0,0)); //让精灵居中展示 background->setPosition(568/2,320/2); auto rend=RenderTexture::create(designSize.width,designSize.height); // auto rect=Rect(0,0,designSize.width,designSize.height); //rend->setVirtualViewport(Vec2(0,0),rect,rect); auto rect=Rect(0,0,designSize.width/2,designSize.height/2); rend->setVirtualViewport(Vec2(0,0),rect,rect); rend->setKeepMatrix(true); rend->begin(); layer2->visit(); background->visit();//他的command就会放到新的renderqueue里面了 rend->end(); rend->saveToFile("bbbbbb.png",true); rend->getSprite()->setAnchorPoint(Vec2::ZERO); // // rend->setPosition(Vec2(designSize.width/2,designSize.height/2)); rend->setPosition(Vec2(0,0)); scene->addChild(rend);
绘制结果如下:
以上就是RenderTexture绘制过程的分析,因为自己也是初学,难免有错误之处,敬请见谅。