1.2.02_COCOS2D-X渲染之繪圖原理

精靈的繪制

打開CCSprite的代碼文件,其中draw方法負責(zé)繪制精靈榆俺,其實現(xiàn)代碼如下:

void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    if (_texture == nullptr)
    {
        return;
    }

#if CC_USE_CULLING
    // Don't calculate the culling if the transform was not updated
    auto visitingCamera = Camera::getVisitingCamera();
    auto defaultCamera = Camera::getDefaultCamera();
    if (visitingCamera == defaultCamera) {
        _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) 
          || visitingCamera->isViewProjectionUpdated()) 
          ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
    }
    else
    {
        // XXX: this always return true since
        _insideBounds = renderer->checkVisibility(transform, _contentSize);
    }

    if(_insideBounds)
#endif
    {
        _trianglesCommand.init(_globalZOrder,
                               _texture,
                               getGLProgramState(),
                               _blendFunc,
                               _polyInfo.triangles,
                               transform,
                               flags);

        renderer->addCommand(&_trianglesCommand);

#if CC_SPRITE_DEBUG_DRAW
        _debugDrawNode->clear();
        auto count = _polyInfo.triangles.indexCount/3;
        auto indices = _polyInfo.triangles.indices;
        auto verts = _polyInfo.triangles.verts;
        for(ssize_t i = 0; i < count; i++)
        {
            //draw 3 lines
            Vec3 from =verts[indices[i*3]].vertices;
            Vec3 to = verts[indices[i*3+1]].vertices;
            _debugDrawNode->drawLine(Vec2(from.x, from.y), 
              Vec2(to.x,to.y), Color4F::WHITE);

            from =verts[indices[i*3+1]].vertices;
            to = verts[indices[i*3+2]].vertices;
            _debugDrawNode->drawLine(Vec2(from.x, from.y), 
              Vec2(to.x,to.y), Color4F::WHITE);

            from =verts[indices[i*3+2]].vertices;
            to = verts[indices[i*3]].vertices;
            _debugDrawNode->drawLine(Vec2(from.x, from.y), 
              Vec2(to.x,to.y), Color4F::WHITE);
        }
#endif //CC_SPRITE_DEBUG_DRAW
    }
}
void TrianglesCommand::init(float globalOrder, GLuint textureID, 
GLProgramState* glProgramState, BlendFunc blendType,
const Triangles& triangles,const Mat4& mv, uint32_t flags)
{
    CCASSERT(glProgramState, "Invalid GLProgramState");
    CCASSERT(glProgramState->getVertexAttribsFlags() == 0, \
      "No custom attributes are supported in QuadCommand");

    RenderCommand::init(globalOrder, mv, flags);

    _triangles = triangles;
    if(_triangles.indexCount % 3 != 0)
    {
        int count = _triangles.indexCount;
        _triangles.indexCount = count / 3 * 3;
        CCLOGERROR("Resize indexCount from %zd to %zd, \
          size must be multiple times of 3", count, _triangles.indexCount);
    }
    _mv = mv;
    
    if( _textureID != textureID || _blendType.src != blendType.src
    || _blendType.dst != blendType.dst ||
       _glProgramState != glProgramState)
    {
        _textureID = textureID;
        _blendType = blendType;
        _glProgramState = glProgramState;

        generateMaterialID();
    }
}
  • 采用"服務(wù)器端"(負責(zé)具體的繪制渲染)+"客戶端"(負責(zé)向服務(wù)器端發(fā)送繪圖指令)的方式進行渲染。

  • 初始化 渲染指令TrianglesCommand-_trianglesCommand坞淮,設(shè)置ZOrder茴晋、紋理、OpenGL狀態(tài)回窘、顏色混合模式诺擅、貼圖渲染的方式、頂點坐標(biāo)啡直、紋理坐標(biāo)以及頂點顏色等烁涌。

  • renderer->addCommand(&_trianglesCommand);將渲染指令加入render渲染列表,等待下一幀主循環(huán)mainLoop調(diào)用繪制Director::drawScene()來渲染_renderer->render();酒觅。

</br>

渲染樹的繪制

無論如何復(fù)雜的游戲場景也都是精靈通過不同的層次撮执、位置組合構(gòu)成的,因此只要可以把精靈按照前后層次阐滩,在不同的位置繪制出來就完成了游戲場景的繪制二打。

Cocos2d-x的渲染樹結(jié)構(gòu),渲染樹是由各種游戲元素按照層次關(guān)系構(gòu)成的樹結(jié)構(gòu)掂榔,它展示了Cocos2d-x游戲的繪制層次继效,因此游戲的渲染順序就是由渲染樹決定的。

回顧Cocos2d-x游戲的層次:導(dǎo)演類CCDirector直接控制渲染樹的根節(jié)點--場景(CCScene)装获,場景包含多個層(CCLayer)瑞信,層中包含多個精靈(CCSprite)。實際上穴豫,每一個上述的游戲元素都在渲染樹中表示為節(jié)點(CCNode)凡简,游戲元素的歸屬關(guān)系就轉(zhuǎn)換為了節(jié)點間的歸屬關(guān)系,進而形成樹結(jié)構(gòu)精肃。

CCNode的visit方法實現(xiàn)了對一棵渲染樹的繪制秤涩。為了繪制樹中的一個節(jié)點,就需要繪制自己的子節(jié)點司抱,直到?jīng)]有子節(jié)點可以繪制時再結(jié)束這個過程筐眷。因此,為了每一幀都繪制一次渲染樹习柠,就需要調(diào)用渲染樹的根節(jié)點匀谣。換句話說照棋,當(dāng)前場景的visit方法在每一幀都會被調(diào)用一次。這個調(diào)用是由游戲主循環(huán)完成的武翎。

繪制父節(jié)點時會引起子節(jié)點的繪制烈炭,同時,子節(jié)點的繪制方式與父節(jié)點的屬性也有關(guān)宝恶。例如符隙,父節(jié)點設(shè)置了放大比例,則子節(jié)點也會隨之放大卑惜;父節(jié)點移動一段距離膏执,則子節(jié)點會隨之移動并保持相對位置不變。顯而易見露久,繪制渲染樹是一個遞歸的過程更米,下面我們來詳細探討visit的實現(xiàn),相關(guān)代碼如下:

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, 
                 uint32_t parentFlags)
{
    // quick return if not visible. children won't be drawn.
    if (!_visible)
    {
        return;
    }

    uint32_t flags = processParentFlags(parentTransform, parentFlags);

    // 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);
    
    bool visibleByCamera = isVisitableByVisitingCamera();

    int i = 0;

    if(!_children.empty())
    {
        sortAllChildren();
        // draw children zOrder < 0
        for(auto size = _children.size(); i < size; ++i)
        {
            auto node = _children.at(i);

            if (node && node->_localZOrder < 0)
                node->visit(renderer, _modelViewTransform, flags);
            else
                break;
        }
        // self draw
        if (visibleByCamera)
            this->draw(renderer, _modelViewTransform, flags);

        for(auto it=_children.cbegin()+i, itCend = _children.cend(); 
            it != itCend; ++it)
            (*it)->visit(renderer, _modelViewTransform, flags);
    }
    else if (visibleByCamera)
    {
        this->draw(renderer, _modelViewTransform, flags);
    }

    _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    
    // FIX ME: Why need to set _orderOfArrival to 0??
    // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
    // reset for next frame
    // _orderOfArrival = 0;
}

</br>

坐標(biāo)變換

在繪制渲染樹中毫痕,最關(guān)鍵的步驟之一就是進行坐標(biāo)系的變換征峦。沒有坐標(biāo)系的變換,則無法在正確的位置繪制出紋理消请。同時栏笆,坐標(biāo)系的變換在其他的場合(例如碰撞檢測中)也起著十分重要的作用。因此在這一節(jié)中臊泰,我們將介紹Cocos2d-x中的坐標(biāo)變換功能蛉加。
首先,我們來看一下transform方法缸逃,其代碼如下所示:

Mat4 Node::transform(const Mat4& parentTransform)
{
    return parentTransform * this->getNodeToParentTransform();
}
const Mat4& Node::getNodeToParentTransform() const
{
    if (_transformDirty)
    {
        // Translate values
        float x = _position.x;
        float y = _position.y;
        float z = _positionZ;
        
        if (_ignoreAnchorPointForPosition)
        {
            x += _anchorPointInPoints.x;
            y += _anchorPointInPoints.y;
        }
        
        bool needsSkewMatrix = ( _skewX || _skewY );

        // Build Transform Matrix = translation * rotation * scale
        Mat4 translation;
        //move to anchor point first, then rotate
        Mat4::createTranslation(x, y, z, &translation);
        
        Mat4::createRotation(_rotationQuat, &_transform);
        
        if (_rotationZ_X != _rotationZ_Y)
        {
            // Rotation values
            // Change rotation code to handle X and Y
            // If we skew with the exact same value for both x and y then we're simply just rotating
            float radiansX = -CC_DEGREES_TO_RADIANS(_rotationZ_X);
            float radiansY = -CC_DEGREES_TO_RADIANS(_rotationZ_Y);
            float cx = cosf(radiansX);
            float sx = sinf(radiansX);
            float cy = cosf(radiansY);
            float sy = sinf(radiansY);
            
            float m0 = _transform.m[0], m1 = _transform.m[1], m4 = _transform.m[4], m5 = _transform.m[5], m8 = _transform.m[8], m9 = _transform.m[9];
            _transform.m[0] = cy * m0 - sx * m1, _transform.m[4] = cy * m4 - sx * m5, _transform.m[8] = cy * m8 - sx * m9;
            _transform.m[1] = sy * m0 + cx * m1, _transform.m[5] = sy * m4 + cx * m5, _transform.m[9] = sy * m8 + cx * m9;
        }
        _transform = translation * _transform;

        if (_scaleX != 1.f)
        {
            _transform.m[0] *= _scaleX, _transform.m[1] *= _scaleX, _transform.m[2] *= _scaleX;
        }
        if (_scaleY != 1.f)
        {
            _transform.m[4] *= _scaleY, _transform.m[5] *= _scaleY, _transform.m[6] *= _scaleY;
        }
        if (_scaleZ != 1.f)
        {
            _transform.m[8] *= _scaleZ, _transform.m[9] *= _scaleZ, _transform.m[10] *= _scaleZ;
        }
        
        // FIXME:: Try to inline skew
        // If skew is needed, apply skew and then anchor point
        if (needsSkewMatrix)
        {
            float skewMatArray[16] =
            {
                1, (float)tanf(CC_DEGREES_TO_RADIANS(_skewY)), 0, 0,
                (float)tanf(CC_DEGREES_TO_RADIANS(_skewX)), 1, 0, 0,
                0,  0,  1, 0,
                0,  0,  0, 1
            };
            Mat4 skewMatrix(skewMatArray);
            
            _transform = _transform * skewMatrix;
        }

        // adjust anchor point
        if (!_anchorPointInPoints.isZero())
        {
            // FIXME:: Argh, Mat4 needs a "translate" method.
            // FIXME:: Although this is faster than multiplying a vec4 * mat4
            _transform.m[12] += _transform.m[0] * -_anchorPointInPoints.x + _transform.m[4] * -_anchorPointInPoints.y;
            _transform.m[13] += _transform.m[1] * -_anchorPointInPoints.x + _transform.m[5] * -_anchorPointInPoints.y;
            _transform.m[14] += _transform.m[2] * -_anchorPointInPoints.x + _transform.m[6] * -_anchorPointInPoints.y;
        }
    }

    if (_additionalTransform)
    {
        // This is needed to support both Node::setNodeToParentTransform() and Node::setAdditionalTransform()
        // at the same time. The scenario is this:
        // at some point setNodeToParentTransform() is called.
        // and later setAdditionalTransform() is called every time. And since _transform
        // is being overwritten everyframe, _additionalTransform[1] is used to have a copy
        // of the last "_transform without _additionalTransform"
        if (_transformDirty)
            _additionalTransform[1] = _transform;

        if (_transformUpdated)
            _transform = _additionalTransform[1] * _additionalTransform[0];
    }

    _transformDirty = _additionalTransformDirty = false;

    return _transform;
}

首先通過getNodeToParentTransform()方法獲取此節(jié)點相對于父節(jié)點的變換矩陣针饥,然后把它轉(zhuǎn)換為OpenGL格式的矩陣并右乘在當(dāng)前繪圖矩陣之上,最后進行了一些攝像機與Gird特效相關(guān)的操作需频。

把此節(jié)點相對于父節(jié)點的變換矩陣與當(dāng)前節(jié)點相連丁眼,也就意味著在當(dāng)前坐標(biāo)系的基礎(chǔ)上進行坐標(biāo)系變換,得到新的合適的坐標(biāo)系昭殉。

形象地講苞七,transform方法的任務(wù)就是根據(jù)當(dāng)前節(jié)點的屬性計算出如何把繪圖坐標(biāo)系變換為新坐標(biāo)系的矩陣。圖10-8形象地描述了這一操作挪丢。

變換相關(guān)的方法

方法 | 描述
-- |
CCAffineTransform nodeToParentTransform() | 獲取節(jié)點相對于父節(jié)點的變換矩陣
CCAffineTransform parentToNodeTransform() | 獲取父節(jié)點相對于節(jié)點的變換矩陣
CCAffineTransform nodeToWorldTransform() | 獲取節(jié)點相對于世界坐標(biāo)系的變換矩陣
CCAffineTransform worldToNodeTransform() | 獲取世界坐標(biāo)系相對于節(jié)點的變換矩陣
CCPoint convertToNodeSpace(const CCPoint& worldPoint) | 把世界坐標(biāo)系中的點坐標(biāo)轉(zhuǎn)換到節(jié)點坐標(biāo)系
CCPoint convertToWorldSpace(const CCPoint& nodePoint) | 把節(jié)點坐標(biāo)系中的點坐標(biāo)轉(zhuǎn)換到世界坐標(biāo)系
CCPoint convertToNodeSpaceAR(const CCPoint& worldPoint) | 把世界坐標(biāo)系中的點坐標(biāo)轉(zhuǎn)換到節(jié)點坐標(biāo)系(相對于錨點)
CCPoint convertToWorldSpaceAR(const CCPoint& nodePoint) | 把節(jié)點坐標(biāo)系中的點坐標(biāo)(相對于錨點)轉(zhuǎn)換到世界坐標(biāo)系
CCPoint convertTouchToNodeSpace(CCTouch * touch) | 獲取觸摸點在節(jié)點坐標(biāo)系中的坐標(biāo)
CCPoint convertTouchToNodeSpaceAR(CCTouch * touch) | 獲取觸摸點在節(jié)點坐標(biāo)系中的坐標(biāo)(相對于錨點)

"節(jié)點坐標(biāo)系"指的是以一個節(jié)點作為參考而產(chǎn)生的坐標(biāo)系蹂风,換句話說,它的任何一個子節(jié)點的坐標(biāo)值都是由這個坐標(biāo)系確定的乾蓬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惠啄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌礁阁,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件族奢,死亡現(xiàn)場離奇詭異姥闭,居然都是意外死亡,警方通過查閱死者的電腦和手機越走,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門棚品,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人廊敌,你說我怎么就攤上這事铜跑。” “怎么了骡澈?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵锅纺,是天一觀的道長。 經(jīng)常有香客問我肋殴,道長囤锉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任护锤,我火速辦了婚禮官地,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烙懦。我一直安慰自己驱入,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布氯析。 她就那樣靜靜地躺著亏较,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魄鸦。 梳的紋絲不亂的頭發(fā)上宴杀,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音拾因,去河邊找鬼旺罢。 笑死,一個胖子當(dāng)著我的面吹牛绢记,可吹牛的內(nèi)容都是我干的扁达。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蠢熄,長吁一口氣:“原來是場噩夢啊……” “哼跪解!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起签孔,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤叉讥,失蹤者是張志新(化名)和其女友劉穎窘行,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體图仓,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡罐盔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了救崔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惶看。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖六孵,靈堂內(nèi)的尸體忽然破棺而出纬黎,到底是詐尸還是另有隱情,我是刑警寧澤劫窒,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布本今,位于F島的核電站,受9級特大地震影響主巍,放射性物質(zhì)發(fā)生泄漏诈泼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一煤禽、第九天 我趴在偏房一處隱蔽的房頂上張望铐达。 院中可真熱鬧,春花似錦檬果、人聲如沸瓮孙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杭抠。三九已至,卻和暖如春恳啥,著一層夾襖步出監(jiān)牢的瞬間偏灿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工钝的, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翁垂,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓硝桩,卻偏偏與公主長得像沿猜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碗脊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 摘自:Cocos2d-x 高級開發(fā)教程-第10章OpenGL基礎(chǔ) OpenGL簡介(1) OpenGL是一個基于C...
    資深小夏閱讀 1,584評論 0 0
  • 渲染是一個游戲的重要組成部分,給與玩家最直接的體驗祈坠。渲染做的好害碾,游戲不僅要華麗,還要運行流暢赦拘。接下來蛮原,我們來看看c...
    魚鳥fly閱讀 1,009評論 0 2
  • 前言 我選擇開發(fā)一個游戲有很多原因。我覺得自己是“核心”玩家另绩,過去的大部分時間我都花在玩游戲,自己制作花嘶、閱讀和游戲...
    月影檀香閱讀 11,833評論 1 27
  • 在一個風(fēng)和日麗的上午笋籽,案主悠閑地靠在樹下看著書,爸爸媽媽在落地窗前吹口琴椭员、飲茶车海,樹上的小鳥在歡快地歌唱,身邊的小兔...
    皮皮爸爸時代閱讀 152評論 0 2
  • 上班沒多久隘击,就被告知侍芝,工作室的助理A月底就要離開這里,開始下一個冒險挑戰(zhàn)埋同,化妝助理B因意外情況也突然離開州叠,助...
    小伊__er閱讀 448評論 3 0