• Cocos2d-x与OpenGL底层的感想


    1.为什么会卡顿

    这篇文章想写一些工作经常碰到的一些问题,为什么我做一个2D游戏,渲染100多个精灵就会卡。

    他们同样是做2D游戏,为什么渲染那么多东西帧数非常高,一点卡顿的样子都没有?

    这里我们排除一些逻辑因素,在相同游戏逻辑复杂度下。我每帧也没什么逻辑运算也还是卡,这里我们首先排除掉CPU对于游戏帧数瓶颈的限制。我们来谈下,为什么都是2D游戏,我渲染那么少的东西就会卡。其他游戏2D大作渲染那么多东西还是很流畅!

    2.OpenGL问题

    OpenGL是个非常老旧的渲染API,API层非常单薄,僵硬,程序员能控制的东西非常少,无非就是资源的生成,控制一些渲染缓存的状态,调用Draw接口。其实在现在硬件变革好几代之后API还是那样子,已经跟不上硬件的速度。而且手机游戏中为了兼容性问题,一般游戏都是用OpenGLES2.0这个版本,造成对于硬件性能的浪费。

    打个比方如果一台手机可以本来的渲染性能可以渲染基准性能是100分,但是图像接口的问题和驱动层优化问题只能发挥到70分,渲染引擎设计问题继续砍半只能发挥40分,如果程序员对于引擎不够了解只能发挥到30。这样就造成貌似机器性能在飞速发展,但是有些游戏的画面提升不是很快。

    当我们无法改变图形接口和驱动的时候,我们能否把可以控制的70分给压榨出来,就是考验一个游戏厂商的实力(大厂商跟硬件公司合作非常密切,而且单一平台上对于优化更好,所以PS4和XBOX的游戏画面一直比单机好点。很多硬件厂商会为了某个游戏还会去优化显卡驱动,他们示可以拿到80分甚至90分的性能,小公司是没有那个待遇的)。

    其他设计硬件和渲染管线问题以后提。这里简单说明单纯的渲染API的调用,这个也是最好理解的。OpenGL是状态机设计模式,每次调用一个API都会去改变渲染管线中某个功能开关。我们每次调用一个API,就会生成一个指令。这个指令告诉显卡怎么去做,这边是有性能损失的。(API调用OpenGL渲染是异步的过程,在缓冲区中会维护一个渲染命令队列,在某个时机去发送给驱动层怎么做,驱动做的事情以后再说。这个时机可以我们手动去触发glFlush强制发送,但是我们一般没有需求不推荐。我们这里可以理解调用OpenglAPI成同步的。)所以尽量的少调用OpenGL API渲染性能就上去了。说了也是白说,我要画那么多东西,当然要多调接口了,不调接口我怎么画。

    这边就引出一个问题,我怎么少调用API,但是可以多画东西呢?批次渲染是最通用也是最简答的做法!

    3.批次渲染概念

    如果我们把一次渲染比作一群人出游,在一个停车场中。大家准备排队上车。每辆顺序出口出去。但是停车场有个奇怪的规定:相邻穿相同颜色衣服的人可以坐同一辆车出去,如果后面一个如果穿不同颜色衣服人,那么就得新配一辆车。假如100个人穿100种颜色的衣服,那么就得100辆车,如果100穿相同颜色的衣服话只要一辆车。如果排队顺序是A.B.C.D  AB如果都穿红色站一起一辆车,C绿一辆车,D红色一辆车。这样就三辆车。如果调换下ABCD顺序变成 A.B.D.C, 这样就变成两辆车。车越少,那么全部离开停车场越短,更快到目的地旅行了。批次渲染就是劲量让穿相同颜色人,塞进同一部车中。更快的出去

    在一般的游戏引擎中,我们都会有一个渲染的队列,每个渲染单元抽样成一个渲染命令。每个命令都会去调用OpenGL API。

    如果两个渲染命令一起的画,我们是否可以把两个命令合成一个命令呢?因为OpenGL是状态机啊,第一个命令状态设置后,第二个命令无需重新去设置渲染状态。这样不就少调用API 但是画一样的多东西!这个就是批次渲染的概念。

    我们今天只谈下2D游戏,在2D游戏每个渲染命令涉及到OpenGL API其实比较少了,设置Shader  绑定TextureDrawElements 差不多就这三样,

    一般精灵的Shader是一样的,这个我们是可以合并的。这个比较简单。

    Texture纹理,每个精灵都有自己的纹理,没办法合并啊!其实有办法,把两张小图拼成一张大图,设置顶点数据的UV坐标!搞定这个也可以合并。

    DrawElement 这里我们渲染图元基本都是三角形,如果把三角形的顶点数据放在一个VBO中。那么也是可以合并。

    4.coco2d-x中的批次渲染

    cocos2d 最早以前有个类型叫BatchNode,这个类型地下绑定精灵如果是同一张纹理的就会生成一个批次渲染,但是这个不是很好用,如果美术改了资源的,有个子节点不是用大图里面纹理会出问题。后面3.X版本,cocos2d-x推出一个技术叫(Auto-batching)、在深度优先的子节点遍历情况,相邻节点如果材质相同就自动合并批次。所以贴图相同的精灵我们是可以合并批次的。

    5.实际测试

    第一个基准测试,我们上面都不渲染,只是单纯控制Cocos2d-x不到满帧,等下好对比。

    const int kDrawNodeNum = 6000;
    
    void TestScene::DrawEmptyNode()
    {
        for (int nIndex = 0; nIndex < kDrawNodeNum; ++nIndex){
            for (int nIndexColor = 0; nIndexColor < 3; ++nIndexColor){        
                Node* pNode = Node::create();
                this->addChild(pNode);
            }
        }
    }

    基准测试

    第二个我们是使用三张分开图的测试。

    void TestScene::DrawPartSprite()
    {
        Size size = Director::getInstance()->getVisibleSize();
        char* spritename[] = { "green.png", "red.png", "blue.png" };
        for (int nIndex = 0; nIndex < kDrawNodeNum; ++nIndex){
            for (int nIndexColor = 0; nIndexColor < 3; ++nIndexColor){
                Sprite* pIcon = Sprite::create(spritename[nIndexColor]);
                pIcon->setAnchorPoint(Vec2::ZERO);
                pIcon->setPosition(Vec2(200 + (nIndex * 20 + nIndexColor*10  ) % int(size.width), 200 + (nIndex * 20 + nIndexColor) % int(size.height)));
                this->addChild(pIcon);
            }
        }
    }

    分图测试

    第三个我们使用一张合并图,其实里面东西都一样的。

    void TestScene::DrawTogetherSprite()
    {
        SpriteFrameCache::getInstance()->addSpriteFramesWithFile("color.plist");
    
        Size size = Director::getInstance()->getVisibleSize();
        char* spritename[] = { "green.png", "red.png", "blue.png" };
    
        for (int nIndex = 0; nIndex < kDrawNodeNum; ++nIndex){
            for (int nIndexColor = 0; nIndexColor < 3; ++nIndexColor){
                Sprite* pIcon = Sprite::createWithSpriteFrameName(spritename[nIndexColor]);
                pIcon->setAnchorPoint(Vec2::ZERO);
                pIcon->setPosition(Vec2(200 + (nIndex * 20 + nIndexColor*10) % int(size.width), 200 + (nIndex * 20 + nIndexColor) % int(size.height)));
                this->addChild(pIcon);
            }
        }
    }

    合图测试

    资源就这样,非常简单

    image

    非常简单的三个图片,分开渲染是三张分开的图片,合并渲染是用一个合图。

    其实代码基本相同。为什么性能竟然有那么大的差距。如果除去CPU性能就是基本性能的消耗。在极端的情况下,我们基本可以达到三倍的提升,三倍其实是个很夸张的数据了。

    如果游戏中,如果游戏中精灵批次命中稍微高点,即使对于性能提高10%到20% 对于性能的优化也是有好处的,而你所需要做的可能就是选择好图片去合并成一张大图。

  • 相关阅读:
    protocol buffer
    一个数组中只有0,1,2三种元素,要求对这样的数组进行排序
    初见-TensorRT简介<转>
    如何制作python安装模块(setup.py)
    Reservoir Sampling
    Tensorflow 之 name/variable_scope 变量管理
    Tensorflow之调试(Debug) && tf.py_func()
    python with和上下文管理工具
    hello--GAN
    metronic后台模板学习 -- 所用外部插件列表
  • 原文地址:https://www.cnblogs.com/wbaoqing/p/5528956.html
Copyright © 2020-2023  润新知