cocos源碼分析--RenderTexture


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繪制過程的分析,因為自己也是初學,難免有錯誤之處,敬請見諒。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM