『壹』 cocoscreator 2.4.x版本 drawcall优化 第一期(掌握控制drawcall数量的必要知识)
1,测试环境
2,为何drawcall多会影响性能
3, 哪些组件支持渲染
4,影响drawcall的因素
5,一句话介绍如何减少drawcall
6,哪些渲染组件不会被渲染
7,减少drawcall的理论(放在第二期)
8,理论指导实践,实践印证理论,demo实操(放在第三期)
9,总结(放在第三期)
「测试环境」 :
1.Mac 系统
2.cocoscreator 2.4.x版本
「为何drawcall多会影响性能?」
Drawcall: 绘制调用,指cpu调用图形绘制接口命令gpu进行图形绘制
「每一次绘制前,CPU要准备绘制参数(状态)比如色彩通道(color filter),绘图方式(shader)等复杂的数据处理,然后Drawcall,如果有大量drawcall,cpu会很“忙”,而gpu的处理能力很强,这时他可能闲置,不能充分发挥应有的能力,导致性能下降。」
「哪些组件支持渲染:」 因为一个drawcall是一次cpu调用图形绘制接口命令 gpu进行图形绘制渲染的过程,所以需要了解cocoscreator中哪些组件支持渲染,才能更好的控制drawcall
**
「影响drawcall的因素:」
1,层级(zindex)
2,材质(Material)(shander,贴图(纹理),混合模式(blend))。只有拥有相同材质的渲染节点 才可能进行批处理,贴图,shader 决定了材质,而层级则决定了相同的材质 是否能 进行合并处携桥贺理 即合并网格(mesh) 合并drawcall.,
「一句话介绍如何减少drawcall:」 绘制状态的变化 是导致drawcall增多的 主要原因。cocoscreator认为要以深度(zindex)优先的方式对渲染组件进行渲染,并且cocoscreator认为相同的材质可以被批量渲染。所以具有相同材质的并且连续的渲染节点 可以合并渲染 减少drawcall.
「连续:」
1,层级相同添加顺序相邻,
2,层级不同 中间层级没有其他材质的渲染组件。比如 a的层级是1 b的层级是3 在 1-3层级之间没有其他材质的 渲染组件.
「影响drawcall的因素:」
「1,渲染节点(zindex)层级」
zIndex是节点的层级是用来对节点进行排序的关键属性,它决定一个节点 在兄弟节点之间的层级,和谁被优先渲染。
1) zIndex 的取值介于 cc.macro.M IN_ZINDEX 和 cc.macro.MAX_ZINDEX 之间
即 - math.pow(2,15). 和 math.pow(2,15)-1之间。
实际操作中一般是 -1 到 n n一般不会超过1000
2)父节点主要根据节点的 zIndex 和添加次序来排序,拥有更高 zIndex 的节点将被排在后面(后被渲染先被渲染的图在后被渲染的图下面),如果两个节点的 zIndex 一致,先添加的节点会稳定排在另一个节点之前。排在前面的节点先被渲染,也就是说两张图层级相同 先添加的会先被渲染 显示出来的结果是 在后被渲染的图的下面。
3)节点在 children 中的顺序决定了其渲染顺序。父节点永远在所有子节点之前被渲染
4)node节点放在Canvas或者父节点的zindex默认值是0
5)决定节点层级的另一个因素是siblingIndex 他的权重低于 zIndex 当我们在编辑器上编辑借点的时候 兄弟节点之间的zIndex相同,消数为什么会出现一个先被渲染一个后被渲染呢 ,就是因为 siblingIndex 不同,排在前面的siblingIndex要小一些后面的要大一些 最终后面的后选择然 层级就在 前面的上边。 也就是说 zindex 其决定性作用,zIndex相同 就比较siblingIndex来判定最终层级。
「2,材质」
1)纹理(贴图)
2)shander:渲染器,能够读懂的点和颜色的对应关系的程序,简单来说就是绘图的方式)
只有拥有相同材质的物体才可以进行批处理。因此,辩派如果你想要得到良好的批处理效果,你需要在程序中尽可能地复用材质和物体。
如果你的两个材质仅仅是纹理不同,那么你可以通过 纹理拼合 操作来将这两张纹理拼合成一张大的纹理。一旦纹理拼合在一起,你就可以使用这个单一材质来替代之前的两个材质了。
「哪些渲染组件不会被渲染」
cocoscreator 认为 透明度 === 0. 或者 active = false 的渲染组件 不会被渲染。
『贰』 WebGL Shader教程
关于WebGL Shader的视频教程比较少,你开发游戏用的是那种游戏引擎,Layabox,cocos creator,还是白鹭。每种游戏引擎在shader的开发上都有些差别,主要是引擎向GPU提交数据上会有不同的写法,存数据的位置也不太一样。具体到shader内功能的开发,都是差不多的,因为都是用的WebGL那一套。
https://e.csdn.net/course/detail/32454
这个教程主要是用Layabox来开发webGL Shader的教程。讲的很详细,对新手入门很友好,源代码都可以下载,比较方便新手学习。如果你是使用cocos creator 或 白鹭的话,可以参照引擎的官方文档,熟悉引擎怎么提交数据的,shader内部的计算都是一样的,也很有学习的价值。
『叁』 求cocos2d-x教程
cocos教程网络网盘免费资源在线学习
链接: https://pan..com/s/1lYZHKPPVuvBR4rddE1jasA
cocos教程 极客学院Cocos2d-x源码 05_第5阶段 项目实战 04_第4阶段 功能扩展 03_第3阶段 常用功能 02_第2阶段 基础知识 01_第1阶段 环境搭建 5 使用Eclipse在Ubuntu下搭建Cocos2d-x 3集成开发环境 4 Cocos Code IDE使用 3 Windows环境下Visual Studio 2013中搭建Cocos2d-x 3.1集成开发环境 2 Cocos2d-x3.1rc0项目创建及新功能介绍 1 WinMac环境Cocos2d-x开发环境搭建 06. HelloWorld示例详解.webm 05. 在Mac平台编译成Android程序.webm 04. Mac平台开发环境搭建.webm
『肆』 CocosCreator教程(入门篇)
自动释放资源: 切换场景后,上一个场景中的资源,从内存中释放。
延迟加载资源: 意味着不用等待所有资源加载完毕,才显示场景。(快速切换场景,资源陆续在画面显示)
普通图,子层为一张spriteFrame。
创建方式:拖拽场景节点,到资源管理器。
精灵图,子层为多张spriteFrame。(精灵图合成软件:TexturePacker、Zwoptex)
打包时,将所在目录中的所有碎图,合成为图集。
数字为内容的图集。
动态字体:.ttf
位图字体:.fnt + .png(存在于同一目录)
小型动画
模式: web audio、dom audio
操作流程:
(1)导出:文件 => 资源导出,选择 .fire场景文件,输出assets目录的 .zip压缩包。
(2)导入:文件 => 资源导入,选择压缩包源路径、解压路径,输出assets目录内容。
基于size mode,尽量去除spriteFrame无像素的部分,减小图片尺寸。
作用: 用于变换、子节点定位基准。
对摄像机、渲染组件的了解。
对widget、layout等UI组件的了解。
(1)创建动画的基本流程
(2)时间曲线(双击动画线,进入编辑窗口)
(3)事件管理(双击游标、加减按钮控制参数个数)
(4)脚本控制
碰撞组件(普通碰撞)
(1)editing——是否为编辑模式
(2)regenerate points——计算图形边界,自定生成控制点,数值为控制点的生成密度 / 准确度
(3)ctrl + 点击——删除控制点
(4)组件类型:矩形、圆形、多边形
(5)设置碰撞组(项目 => 项目设置 => 分组设置):
制定分组 => 匹配分组 => 碰撞组件所在节点上,设置所属分组
(6)脚本控制
Box2D物理引擎(高级碰撞)
(1)audioSource组件
(2)脚本控制
(1)定义 CCClass
(2)实例化
(3)判断类型
(4)构造函数(ctor)
(5)实例方法
(6)继承(extends)
(7)父构造函数
(8)完整声明属性
properties常用参数
(1)获得组件所在的节点
(2)获得其它组件
(3)获得其它节点及其组件
(4)访问已有变量里的值(通过模块访问)
(1)节点状态和层级操作
(2)更改节点的变换(位置、旋转、缩放、尺寸)
(3)颜色和不透明度
(4)常用组件接口
cc.Component 是所有组件的基类,任何组件都包括如下的常见接口:
(1)创建新节点
(2)克隆已有节点
(3)创建预制节点
(4)销毁节点
(1)加载和切换
(2)通过常驻节点,进行场景资源管理和参数传递
(3)场景加载回调
(4)预加载场景
(1)资源属性的声明
(2)静态加载(在属性检查器里设置资源)
(3)动态加载
(4)加载远程资源和设备资源
(5)资源的依赖和释放
(1)监听事件
(2)关闭监听
(3)发射事件
(4)派送事件
(5)事件对象(回调参数的event对象)
(1)鼠标事件类型和事件对象
(2)触摸事件类型和事件对象
(3)其它事件
(1)动作控制
(2)容器动作
(3)即时动作
(4)时间间隔动作
(5)动作回调
(6)缓动动作
(1)XMLHttpRequest——短连接
(2)WebSocket——长连接
对象池的概念
在同一场景中,需要多次进行节点的生成、消失时,假如直接进行创建、销毁的操作,就会很浪费性能。因此,使用对象池,存储需要消失的节点,释放需要生成的节点,达到节点回收利用的目的。
工作流程
(1)初始化对象池
(2)从对象池请求对象
(3)将对象返回对象池
清除对象池
『伍』 cocos2dx creator canvas 怎么使用shader
满怀期待用了一段时间cocos creator, 比较令人失望,文档不够全, 接口太乱,经常遇到和原cocos2d接口不匹配问题, 导致学习成本增加。作为一个初学者我不知道后面还会有什么坑。所以决定暂时先退出cocos creator 使用。从stio到creator触控总是令人比较失望。
『陆』 cocos creator 2.4.0 渲染流程详解(七:ForwardRender)
全文共5000+字,分为8个章节,由本人历时一周整理而来。由于篇幅问题,将本文分为8个章节分开发布。全文 ( 不 ) 详细描述了cocoscreator 引擎的2.40版本中,web平台的js部分引擎的渲染流程。请将文章配合源码一起食用!
由于我尚在学习引擎源码中,文章可能有不正确的部分,所以我会不断更新内容携让。如有错误或补充,请留言交流!
全部章节链接:
一: 渲染流程中用到的核心类
二 : 渲染流程详解
三: RenderFlow 的运行逻辑
四: Assembler 的作用
五: ModelBatcher 数据合知隐弊批
六: 材质系统
七: ForwardRender
ForwardRender 继承于 Base, 是与底层渲染最靠近的类型,当上面的流程处理完毕后,会在ForwardRender 的 render() 中处理当前场景的渲染状态,材质,光照,通道,着色器,更新着色器的统一变量。并在 _draw() 中调用 device.draw()方法,进行绘制。
部分重要的继承于 Base 的成员变量:
_device:根据运行平台对应的绘制图形对象 gfx.Device 的实例,用于绘制图形到屏幕,类型定义于 cocos2d
enderergfxindex.js。
_programLib : 管理 shader 定义,获取,检查等相关的变量。类型定义于 cocos2d
enderercoreprogram-lib.js。
_stage2fn:保存有不同渲染通道的名称与其对应的不同渲染方法。ForwardRender 中设置有 shadowcast, opaque, transparent 三种渲染通道。
_viewPools:单个相机的描述数据类(View) 的对象池。一个View对应一个相机。
_drawItemsPools:渲染数据类的对象池,保存有每个渲染批次需要的model,effect 等数据。
_stageItemsPools:单个渲染通道需要渲染的数据的对象池,搭族本质是对 _drawItemsPools 中的数据按照不同通道进行了分类。
ForwardRender 中定义的成员变量:
_lights:保存所有灯光数据。
_shadowLights:保存所有阴影灯光数据。
类名 ForwardRender 翻译为前向渲染,泛指传统上只有 Opaque 和 Transparent 两个通道的渲染技术。cocos有三个渲染通道,渲染通道方法定义在 _stage2fn 中。
渲染管线具体详解请参考unity官方文档(对的,真要学cocos还得看unity的文档):
内置渲染管线中的渲染路径
相关链接
『柒』 如何用Cocos引擎打造次世代3D画质‘游戏大观
从Cocos 2d-x 3.0起我们已经可以在游戏中使用3D元素。Cocos引擎推出3D功能的时间不算太迟,我们已经可以看到越来越多的手机上能流畅地渲染3D游戏,而且这些机型正在成为主流。在最近两年我们可以看到,高端手机游戏从2D转到3D的倾向很明显。许多游戏开发商试图在竞争激烈的红海里占有一席之地,那么选择开发3D游戏或许会是一个强有力的竞争手段。
上面的视频是我的下一款游戏作品《Food of the Gods》。这游戏使用了Cocos 2d-x 3.3,视频是从我iPhone上录制的实际运行效果。在这篇文章里我将要介绍我是如何制作它、如何把它跑在cocos引擎上的。对于熟悉cocos官方提供的3D示例游戏 《Fantasy Warrior》的开发者,将会看到以下一些主要不同点:
1. 光照贴图(Light Mapping):你将看到每件物体都有被照亮并且投射阴影。光影效果的质量是由你的3D工具软件决定的,用3D软件能烘焙出复杂的光效,包括直接光照,反射光照,以及阴影。
2. 顶点合并(Vertex Blending):请注意看路、草地和悬崖交接的地方,看不到任何可见的接缝。
3. 透明遮罩(Alpha Masks):灌木如果没有透明遮罩就跟纸片一样。
4. 滤色叠加的公告板(Billboards):增加一些光束和其他环境的效果。
所有的模型都是用一个叫Modo的3D 软件建模制作的,贴图则是使用Photoshop。关于3D模型的制作和贴图的绘制在此就不再赘述,网上已经有很多教程,在此主要介绍下跟Cocos 2d-x有关的部分。
模型网格和贴图(Meshes and Textures)
如下图所示,每个模型的贴图都是由几个256 x 256或者更小的贴图组成的。同时你也会注意到我把所有的小图片都合在了一张贴图上,这是减少GPU绘制次数(draw call)最简单的方法之一。贴图是从http://www.cgtextures.com 或者网上找的。
为了把这些图片拼接起来,我使用的是Photoshop的补偿滤镜(offset filter)然后在接缝的地方用修复画笔来做一些自然的过渡。为了获得一种油画的视觉效果我会先使用cutout滤镜(注意:cutout滤镜也会使得png格式图片的压缩效果更好),然后在需要的地方绘制一些高光和阴影的效果。我发现如果直接拿照片当贴图的话,当你把它尺寸缩小的时候会出现图像噪点。
另一种方案是为每一个模型网格制作一整张独立的贴图。当网格比较小或者摄像机不是很靠近网格的时候这种方法是可行拆橡的。如果你的photoshop技术过硬的话,出来的效果会更好。附带的好处是,因为只使用一张贴图因此只有一次GPU绘制调用。但我不建议采用这种方法来制作第一人称射击游戏(FPS)中的建筑,因为当你走得很靠近建筑物的时候,贴图分辨率过低的问题就会显露出来。我不喜欢用这种整张贴图方法,因为这实在太费时耗力了。这个场景的制作花了我足足四天时间。
光照贴图(Light Maps)
当你做好模型和贴图之后,现在就可以来烘亩御歼焙光照贴图了。Cocos 2d-x目前还不像Unreal或Unity一样在官方编辑器里提供烘焙光照贴图的功能,但是别失望,大部分的制作3D模型的软件都可以烘焙光照贴图,并且效果比市面上任何游戏引擎的效果还好。首先,在你的3D工具软件里,先给场景打好灯光,照亮场景,然后为每份网格制作第二张UV map。每份网格的表面都必须被映射在0到1范围内的UV 平面上。这听起来好像很复杂且耗时,但在Modo里这是非常简单的。我先后使用 “Atlas map”的UV 工具和“Pack UV”工具,这两个工具会自动将网格展开成一个相当不错的排布图。
这些都完成迅冲之后,设置3D工具软件的渲染器为“只渲染烘焙的光照”,然后开始渲染。当然了,如果你想做一些环境光遮罩的效果也是可以的。
你也可以使用一些分辨率较低的光照贴图。有时候这样的效果反而会看起来更好,因为相互混叠的模糊像素会让阴影看起来更柔和。上面的这些建筑都映射到一张512 x 512的光照贴图上。整个场景总共使用了4 张512 x 512的光照贴图。请确保每个小图块之间有一定的空隙,且让你的渲染范围比这些图块的边界多出几个像素。这样可以防止当较低的mip-maps(一种纹理采样)起作用时黑边出现在网格周围的角落里。
最后一点听起来像是3D技术的行话。如果是对Texture Packer熟悉的话,那么其中的“Extrude”值起到的作用就是刚刚我所描述的。对贴图的边缘接缝做一些涂抹处理,这样在精灵之间就不会有那些烦人的缝隙了,那些缝隙在这里会变成多边形边缘的黑边。
如果你想牺牲内存和包大小来提高性能的话,你可以把颜色和光照信息都烘焙到一张贴图上并避免共同使用一张光照贴图。但是这样做的话,同样的像素密度,贴图的大小至少得翻一倍。这完全取决于你个人、以及你游戏的要求。
接下来,添加顶点颜色。我在地形上提供了顶点颜色,这可以让着色器在合成悬崖顶上的草地贴图时,不会有任何可见的接缝。下图中涂成白色的顶点部分可以合成你指定的贴图。在这个例子里实际上我只使用红色通道,当然了根据实际需要你可以使用4个通道(RGBA)去合成不同的贴图。
最后,我把整个场景分成了很多独立的网格(mesh):每个建筑都有自己独立的网格,地形独立一个网格,水也是独立一个。带透明遮罩的贴图也会有一个网格——比如视频中看到的植物叶子和小旗子。我这样做有两个原因,首先,让地形、建筑、水和带透明遮罩的贴图各自使用不同的着色器。其次,我们打算通过不渲染摄像机范围外的对象来减少性能开支。很重要的一点是摄像机会根据网格的包围盒来决定对象是否可见,因此尽量把网格弄成小块,这样包围盒会比较小。
导出
完成了模型和贴图之后,我们需要把每个mesh导出为一个.fbx文件。幸运的是,大多数的3D建模软件都支持这个功能。Autodesk为此格式提供了一个免费SDK。但不幸的是,Modo 701在导出fbx格式时会出现相当多的错误。因此我必须自己写一些脚本来保证第二组贴图坐标和顶点颜色的正确导出。你可以从我个人网站上的“Modo Scripts”部分下载这个导出脚本。搞定fbx之后,你将需要用到Cocos 2d-x自带的fbx-conv.exe命令行工具,它位于Cocos 2d-x根目录的/tools下。
fbx-conv.exe -a your_mesh_name_here.fbx
使用“-a”参数后,工具会同时导出mesh的二进制文件(.c3b)和文本格式文件(.c3t)。文本格式的文件非常的有用,你可以利用它来查看所有的东西是否被正确导出,但千万不要把它放到resource目录下。如果所有的都被正确地导出的话,你将在c3t文件的开头看到以下的内容:
“attributes”: [{
“size”: 3,
“type”: “GL_FLOAT”,
“attribute”: “VERTEX_ATTRIB_POSITION”
}, {
“size”: 3,
“type”: “GL_FLOAT”,
“attribute”: “VERTEX_ATTRIB_NORMAL”
}, {
“size”: 2,
“type”: “GL_FLOAT”,
“attribute”: “VERTEX_ATTRIB_TEX_COORD”
}, {
“size”: 2,
“type”: “GL_FLOAT”,
“attribute”: “VERTEX_ATTRIB_TEX_COORD1″
}]
注意VERTEX_ATTRIB_TEX_COORD1这个属性。如果没有它光照贴图将无法显示。如果你导出了一张带顶点颜色的mesh,你也应该要看到一个类似的属性才行。还有一点很重要,贴图的坐标也必须按正确的顺序才行。我通常采用的是第一个tex_coord是瓦片贴图,最后一个tex_coord是光照贴图。使用Modo的话,uv maps会按照字母顺序排列。
着色器(Shaders)
我花了很长的一段时间来搞懂GLSL和着色器,但正如编程中经常遇到的,有时候一个点通了,其他的就都好理解了。一旦理解了其中的原理,你便会发现着色器真的很简单。如果你不只是想用Cocos 2d-x来把贴图套到模型网格上的话,你需要学会如何写着色器。目前Cocos 2d-x没有Unreal那样好用的着色器可视化编辑器(visual shader editor),所以我们只能自己动手焊代码。
本节我将讲解我为视频中的游戏场景所写的着色器,并说明我做了什么、为什么这样做。如果你对着色器已经非常熟悉了,那么可以快速跳过本节。
首先,先来看一下如何将着色器应用到模型网格上。
这段代码摘自Cocos 2d-x的测试集cpp-tests工程。如果你用不同的着色器来加载大量的meshes,那么最好根据功能来进行,这样可以避免冗余。那么现在我们只关心如下的代码段,来看下这个着色器。
GLProgram* shader =GLProgram::createWithFilenames(“shaders/lightmap1.vert”,”shaders/lightmap2.frag”);
GLProgramState* state = GLProgramState::create(shader);
mesh->setGLProgramState(state);
Texture2D* lightmap =Director::getInstance()->getTextureCache()->addImage(“lightmap.png”);
state->setUniformTexture(“lightmap”,lightmap);
“lightmap1.vert”是顶点着色器(vertex shader)。如果将其应用到网格上,那么每个顶点的每一帧都将执行这个操作。而“lightmap2.frag”是片段着色器(fragment shader),网格上贴图的每个像素的每一帧都将执行这个操作。我不太确定为什么将其命名为“片段着色器”,我一直认为应叫做“像素”着色器(pixel shader)。从这段描述,我们可以很容易理解为什么大量着色器指令会降低帧率,尤其是你用片段着色器的话。
接下来我们详细地分解顶点着色器:
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_texCoord1;
这些属性是由渲染器提供的。“a_position”是顶点的位置。“a_texCoord” 和 “a_texCoord1”对应你那两个UV坐标。还记得在.cbt文本格式文件中开头部分的“VERTEX_ATTRIB_TEX_COORD”么?这些值与属性对应起来了。你可以在渲染器中获取更多其他的属性,包括顶点法线(vertexnormal)和顶点颜色(vertex color)。请在cocos引擎的CCGLProgram.cpp中查看完整属性列表。
varying vec2 v_texture_coord;
varying vec2 v_texture_coord1;
“varying”值将被传到片段着色器中(fragment shader)。片段着色器所需要的任何变量前都需要添加“varying”限定符。这个例子中,我们仅需要知道这两个贴图的坐标。
void main(void)
{
gl_Position = CC_MVPMatrix * a_position;
v_texture_coord.x = a_texCoord.x;
v_texture_coord.y = (1.0 – a_texCoord.y);
v_texture_coord1.x = a_texCoord1.x;
v_texture_coord1.y = (1.0 – a_texCoord1.y);
}
设置顶点位置,拷贝贴图的坐标给varying values,这样片段着色器就可以使用这些值。现在我们一起来分解片段着色器。
#ifdef GL_ES
varying mediump vec2 v_texture_coord;
varying mediump vec2 v_texture_coord1;
#else
varying vec2 v_texture_coord;
varying vec2 v_texture_coord1;
#endif
声明从顶点着色器传递过来的“varying” 值
uniform sampler2D lightmap;
还记得在将着色器应用到网格时所使用的 state->setUniformTexture(“lightmap“,light map); 语句么?这个值就是对应语句中的那个贴图。
void main(void)
{
gl_FragColor = texture2D(CC_Texture0, v_texture_coord) *(texture2D(lightmap, v_texture_coord1) * 2.0);
}
这个语句设置像素颜色。首先你会注意到从未声明过的 CC_Texture0变量。Cocos 2d-x中有大量可在着色器中使用的默认统一变量。再次强调,可在CCGLProgram.cpp中查看完整属性列表。这个例子中,CC_Texture0对应在3D模型中所应用到网格中的贴图。texture2D命令会在给定的贴图坐标中去查找贴图的像素颜色和透明度。它会返回一个包含了那个像素的RGBA值的vec4值 。所以这里我会在UV1中查找到瓦片贴图的颜色值,然后在UV2中查到光照贴图的颜色值,最后把两个值相乘。
你应该注意到了我先是把光照贴图的颜色值两两相乘了。因为贴图颜色值范围为0.0-1.0,所以很显然,如果用白色值vec4(1.0, 1.0, 1.0, 1.0)去乘中间灰值vec4( 0.5, 0.5, 0.5,1.0 ),那么你仍是得到一个中间灰值vec4( 0.5, 0.5, 0.5,1.0 )。将两个值相乘可以使贴图更亮,同时也可以使贴图更暗,这将使你获得一个很好的可变的亮度范围。
『捌』 cocos creator 2.1版本Material材质系统解析
管理与缓存 shader 源码与编译后的 shader 引用
_templates 关联 shader_name 与 shader 源码 (vert, frag, defines) 。显而易见, shader_name 要求全局唯一。
_cache 缓存编译后的 shader 引用。因为支持宏, 不同的宏配置,就对应了单独的 shader 源码,
不同的宏配置编译出不一样的 program ,所以 _cache 的 key 为元组 (shader_name, defines) 计算得到。
Pass 通过 programName 绑定 shader ,并记录一些 webgl 的状态:
详见 enginecocos2dcore enderer ender-engine.js:10322
是不是类比于 Unity 的 Rendering Mode ?
如果是的话, 2d 游戏, stage 基本上都是设为 transparent 就可以了
Technique 管理1到多个 pass . 多个 pass 的意义在于多通道渲染一组模型。 描边或许算是一种应用场景?
Technique 也提供了 pass 中用到的 uniform 变量的名称、卜禅类型、大小和值的管理。
为 Technique 设定 Stages ,可为渲染顺序提供参考,通常设为 transparent 。 stages 为数组类型, passes 也是数组类型,是否存在一一对应的关系?
_layer 不知道作用
关联多个 Technique
配置 uniform 属性值
配置 shader 宏
注意 defines 要求在构造函数中给出,后续 define 的值可以变,但属性没办法直接调用 define 函数动态增减
关联 一个 Effect
管理 _texIds :
维护一个更新哈希值 _hash 。材质数据有变化时,需要调用 updateHash 更新哈希。
上述可知, Material , Effect , Technique , Pass 都只是数据容器而已,具体如何使用,就是渲染函数的责任了。
网上的资料讲, OGRE 的材质系统分成三层抽象: Material , Technique , Pass . Unity 的材质系统也是三层: Shader , SubShader , Pass 。多 Pass 实现同一模型,调用多次渲染。多 Technique 方便作低中高质量切换, Material 存放配置数据。
cocos creator 的材质系统多出个 Effect ,现在还是比较不理解。
渲染相关类:
render-engine.js 中定义了唯一一个 stage : transparent .并在 ForwardRenderer 中注册了 transparent stage的渲染函数 _transparentStage 。
渲染入口函数为 ForwardRenderer.prototype.render ,遍历所有相机,为每个相机调用一型裤尘次 ForwardRenderer.prototype.renderCamera(camera, scene) 。然后跳入 Base._render ,清除设备,从 scene._models 中 extractDrawItem ,遍历每个 drawItem ,从 effect.getTechnique(stage) 中得到 tech 。最纯毕后调用 _transparentStage 。
_transparentStage 设定下矩阵,又回到 Base._draw 函数中,执行真正的渲染。
Base._draw 根据 Effect , Technique , Pass 的数据,得到 shader ,并为 shader 设置好 webgl 状态和各个 Uniform 变量,最后调用 device.draw 完成一个渲染流程。
根据渲染流程,可推测, cocos creator 的材质系统也是三层: Effect , Technique , Pass 。 Material 继承 Asset ,对 Effect 作进一步封闭, 是为了方便编辑器?
详见 enginecocos2dcore enderer ender-engine.js:13303 和 enginecocos2dcore enderer ender-engine.js:13677
『玖』 CocosCreator游戏性能优化(3):GPU优化之降低计算分辨率
本文从降低计算或设计分辨率来分如何提升性能, 仅提供一些可参考的思路。
本文链接 CocosCreator游戏性能优化(3):GPU优化之降低计算分辨率
相关链接 CocosCreator游戏性能优化(1):性能分析工具
CocosCreator游戏性能优化(2):合批渲染之RenderToTarget
GPU的Fragment Shader即片元着色器程序在执行的时候,会并行每个像素(严格定义是光栅化后的每个片元,也即resolution)都执行一次。
此时主要的性能小号取决于两方面:
1、片元着色器程序内部逻辑的复杂度和计算量。
2、需要执行的片元数量。
现在我们从削减片元数量的角度来提升性能
假设我们的图片TextureA。应用了ShaderA。
TextureA的宽高分别是1920和1080。
此时我们的片元着色器程序执行次数calcCount = 1920*1080 = 2,073,600次。即200万次。
这在很多普通机器上是可能存在性能问题的,即便shader逻辑不复杂。
因此我们TextureA的宽高减半, 传入片元着色器程序。计算后的calcCount = 960*540= 518,400次。即50万次。
执行完shader后通过线性拉伸来使用该处理过后地textureA,如下。
我们发现计算量降低为原来的1/4,而图的精度值仅降低了1/2.
因此,我们我们在对图的渲染精度要求不高或不需要的情况下,应尽可能地降低其计算分辨率,来提升基基纯性能。
通过上述分析启发,我们做游戏时,最初在决定设计分辨率的时候,应该预先考虑到这个问题。
锋扰 在使用GPU渲染到纹理时,可以尽量减小目标纹理分辨率。使片元数量降低,从而降低计算量。
离屏渲染,和目标FrameBuffer分辨率有关。如果目标纹理分辨率搏咐能够降低,那么游戏中大部分对象的分辨率都会比较低,因而会带来非常可观的性能效率提升。
最后将RenderTexture传入节点进行scale拉伸,得到我们需要的实际显示效果。
本文链接 CocosCreator游戏性能优化(3):GPU优化之降低计算分辨率
相关链接 CocosCreator游戏性能优化(1):性能分析工具
CocosCreator游戏性能优化(2):合批渲染之RenderToTarget
『拾』 Cocos 3.x 由浅到浅入门批量渲染
参考
由浅到浅入门批量渲染(一)
由浅到浅入门批量渲染(二)
由浅到浅入门批量渲染(三)
由浅到浅入门批量渲染(四)
由浅到浅入门批量渲染(完)
【Unity游戏开发】静态、动态合批与GPU Instancing
本文只摘抄部分原文,达到概念性了解。
批量渲染其实是个老生常谈的话题,它的另一个名字叫做“合批”。在日常开发中,通常说到优化、提高帧率时,总是会提到它。
可以简单的理解为: 批量渲染是通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。 但这是建立在GPU相对空闲,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义。
如果瓶颈在GPU,比如GPU性能偏差,或片段着色器过于复杂等,那么没准适当减少批处理,反而能达到优化的效果。所以要做性能优化,还是应该先定位瓶颈到底在哪儿,然后再考虑优化方案,而不是一股脑的就啪啪啪合批。当然,通常情况下,确实是以CPU出现瓶颈更为常见,所以适当的兆握了解些批量渲染的技法,是有那么一丢丢必要的。
静态合批是一种听起来很常用,但在大多数手游项目里又没那么常用的合批技术。这里,我简单的将静态合批分为预处理阶段的合并,和运行阶段的批处理。
合并时,引擎将符合合批条件的渲染器身上的网格取出,对网格上的顶点进行空间变换,变换到合并根节点的坐标系下后,再合并成一个新的网格;这里需要注意的是,新网格是以若干个子网格的形式组合而成的,因为需要记录每一个合并前网格的索引数量和起始索引(相对于合并后的新网格)。
空间变换的目的,是为了“固化”顶点缓冲区和索引缓冲区内的数据,使其顶点位置等信息都在相同的坐标系下。这样运行时如果需要对合并后的对象进行空间变换(手动静态合批对象的根节点可被空间变换),则无需修改缓冲区内的顶点属性,只提供根节点的变换矩阵即可。
在Unity中,可以通过勾选静态批处理标记,让引擎在打包时自动合并;当然,也可以在运行时闹郑调用合并函数,手动合并。打包时的自动合并会膨胀场景文件,会在一定程度上影响场景的加载时间。此外,不同平台对于合并是有顶点和索引数量限制的,超过此限制则会合并成多个新网格。
运行时是否可以合批(Batch)成功,还取决于渲染器材质的设置。
当然,如果手动替换过场景中所有Material,也会打断批次。
静态合批与直接使用大网格(是指直接制作而成,非静态合并产生的网格)的不同,主要体现液猜颂在两方面。
其一,静态合批可以主动隐藏部分对象。静态合批在运行时,由于每个参与合并的对象可以通过起始索引等彼此区分,因此可以通过上述多次DrawCall的策略,实现隐藏指定的对象;而直接使用大网格,则无法做到这一点。
其二,静态合批可以有效参与CPU的视锥剔除。当有剔除发生时,被送进渲染管线的顶点数量就会减少(通过参数控制),也就意味着被顶点着色器处理的顶点会减少,提升了GPU的效率;而使用大网格渲染时,由于整个网格都会被送进渲染管线,因此每一个顶点都需要被顶点着色器处理,如果摄像机只能照到一点点,那么绝大多数参与计算的顶点最后都会被裁减掉,有一些浪费。
当然,这并不意味着静态合批一定就比使用大网格要更好。如果子网格数量非常多,视锥剔除时CPU的压力也会增加,所以具体情况具体分析吧~
静态合批采用了以空间换时间的策略来提升渲染效率。
其优势在于:网格通常在预处理阶段(打包)时合并,运行时顶点、索引信息也不会发生变化,所以无需CPU消耗算力维护;若采用相同的材质,则以一次渲染命令,便可以同时渲染出多个本来相对独立的物体,减少了DrawCall的次数。
在渲染前,可以先进行视锥体剔除,减少了顶点着色器对不可见顶点的处理次数,提高了GPU的效率。
其弊端在于:合批后的网格会常驻内存,在有些场景下可能并不适用。比如森林中的每一棵树的网格都相同,如果对它采用静态合批策略,合批后的网格基本等同于:单颗树网格 x 树的数量,这对内存的消耗可能就十分巨大了。
总而言之,静态合批在解决场景中材质基本相同、网格不同、且自始至终都保持静止的物体上时,很适用。
试想一个场景:一场激烈的战斗中,双方射出的箭矢飞行在空中,数量很多,材质也相同;但因为都在运动状态,所以无法进行静态合批;倘若一个一个的绘制这些箭矢,则会产生非常多次绘制命令的调用。
对于这些模型简单、材质相同、但处在运动状态下的物体,有没有适合的批处理策略呢?有吧,动态合批就是为了解决这样的问题。
动态合批没有像静态合批打包时的预处理阶段,它只会在程序运行时发生。 动态合批会在每次绘制前,先将可以合批的对象整理在一起(Unity中由引擎自动完成),然后将这些单位的网格信息进行“合并”,接着仅向GPU发送一次绘制命令,就可以完成它们整体的绘制。
动态合批比较简单,但有两点仍然需要注意:
动态合批不会在绘制前创建新的网格,它只是将可以参与合批单位的顶点属性,连续填充到一块顶点和索引缓冲区中,让GPU认为它们是一个整体。
在Unity中,引擎已自动为每种可以动态合批的渲染器分配了其类型公用的顶点和索引缓冲区,所以动态合批不会频繁的创建顶点和索引缓冲区。
在向顶点和索引缓冲区内填充数据前,引擎会处理被合批网格的每个顶点信息,将其空间变换到世界坐标系下。
这是因为这些对象可能都不属于相同的父节点,因此无法对其进行统一的空间转换(本地到世界),需要在送进渲染管线前将每个顶点的坐标转换为世界坐标系下的坐标(所以Unity中,合并后对象的顶点着色器内被传入的M矩阵,都是单位矩阵)。
相对于上述看起来有点厉害但是本质上无用的知识而言,了解动态合批规则其实更为重要。比如:
当我们想要呈现这样的场景:一片茂密的森林、广阔的草原或崎岖的山路时,会发现在这些场景中存在大量重复性元素:树木、草和岩石。它们都使用了相同的模型,或者模型的种类很少,比如:树可能只有几种;但为了做出差异化,它们的颜色略有不同,高低参差不齐,当然位置也各不相同。
使用静态合批来处理它们(假设它们都没有动画),是不合适的。因为数量太多(林子大了,多少树都有), 所以合并后的网格体积可能非常大,这会引起内存的增加 ;而且,这个合并后的网格还是由大量重复网格组成的,不划算。
使用动态合批来处理他们,虽然不会“合并”网格,但是仍然需要在渲染前遍历所有顶点,进行空间变换的操作;虽然单颗树、石头的顶点数量可能不多,但由于 数量很多,所以也会在一定程度上增加CPU性能的开销,没必要 。
那么,对于场景中这些模型重复、数量多的渲染需求,有没有适合的批处理策略呢?有吧,实例化渲染就是为了解决这样的问题。
实例化渲染,是通过调用“特殊”的渲染接口,由GPU完成的“批处理”。
它与传统的渲染方式相比,最大的差别在于:调用渲染命令时需要告知GPU这次渲染的次数(绘制N个)。当GPU接到这个命令时,就会连续绘制N个物体到我们的屏幕上,其效率远高于连续调用N次传统渲染命令的和(一次绘制一个)。
举个例子,假设希望在屏幕上绘制出两个颜色、位置均不同的箱子。如果使用传统的渲染,则需要调用两次渲染命令(DrawCall = 2),分别为:画一个红箱子 和 画一个绿箱子。
如果使用实例化渲染,则只需要调用一次渲染命令(DrawCall = 1),并且附带一个参数2(表示绘制两个)即可。
当然,如果只是这样,那GPU就会把两个箱子画在相同的位置上。所以我们还需要告诉GPU两个箱子各自的位置(其实是转换矩阵)以及颜色。
这个位置和颜色我们会按照数组的方式传递给GPU,大概这个样子吧:
那接下来GPU在进行渲染时,就会在渲染每一个箱子的时候,根据当前箱子的索引(第几个),拿到正确的属性(位置、颜色)来进行绘制了。
静、动态合批实质上是将可以合批的对象真正的合并成一个大物体后,再通知GPU进行渲染,也就是其顶点索引缓冲区中必须包含全部参与合批对象的顶点信息; 因此,可以认为是CPU完成的批处理。
实例化渲染是对网格信息的重复利用,无论最终要渲染出几个单位,其顶点和索引缓冲区内都只有一份数据, 可以认为是GPU完成的批处理 。
其实这么总结也有点问题,本质上讲: 动、静态合批解决的是合批问题,也就是先有大量存在的单位,再通过一些手段合并成为批次;而实例化渲染其实是个复制的事儿,是从少量复制为大量,只是利用了它“可以通过传入属性实现差异化”的特点,在某些条件下达到了与合批相同的效果。
无论是静态合批、动态合批或实例化渲染,本质上并无孰优孰劣,它们都只是提高渲染效率的解决方案,也都有自己适合的场景或擅长解决的问题。个人以为:
Unity下可以通过以下两种方式快速优化骨骼蒙皮动画:
在相同的测试环境下,再次进行测试后可以发现,这种方法确实可以产生一定效果。其原因我认为主要有以下两点:
开启GPU蒙皮,Unity会通过ComputeShader的方式,使用GPU进行蒙皮。GPU上有大量的ALU(算数逻辑单元),可以并行大量的数值计算,效率较高,应该很适合这种针对顶点属性的数值计算。
但是实际情况是:在移动设备上使用GPU蒙皮反而会使主线程的耗时增加。通过Profiler可以发现,CPU把更多的时间放在了执行ComputeShader上,由于骨骼动画的实例很多(500个),所以这个调用时间本身成为了性能热点。所以,以目前的情况来看,这种在移动设备上使用GPU蒙皮的方式,似乎不适合处理大量骨骼蒙皮动画实例(也许是我使用的方式存在问题)。
简单来说,它们基本的思路,都是将骨骼蒙皮动画的“结果”预先保存在一张纹理中;然后在运行时通过GPU从这张纹理中采样,并使用采样结果来更新顶点属性; 再结合实例化技术(GPU instancing) ,达到高效、大批量渲染的目的。
可以简单的将它的工作流程分为两个阶段:
这个阶段其实是为后面的播放阶段准备动画资源,你也可以把这个资源简单的理解为一种特殊类型的动画文件。
首先,在编辑状态下,让携带骨骼蒙皮动画的角色,按照一定帧率播放动画。我们知道:动画播放时,角色网格会被蒙皮网格渲染器(SkinnedMeshRenderer)更新而产生变形(顶点变化);如果在动画播放的同时,记录下每一个顶点在这一时刻相对于角色坐标系(通常是角色脚下)的位置。那么,当动画播放完毕时,我们会得到一张“每一个顶点在每一个关键帧时的位置表”,我们就叫它“帧顶点位置表”吧。可是“帧顶点位置表”名字太长,你可能不太好记,我打字也比较麻烦,所以我们后面就叫它“表表”吧。
除了表表,我们还能得到与它对应的动画信息,比如这个动画的名称、时长、帧率、总帧数、是否需要循环播放等信息。
到这里,第一个阶段我们需要的内容就准备就绪了,可以进入下一个阶段:播放动画阶段。
在动画播放时,通过一个变量(播放时长)来更新播放进度;结合上一阶段我们记录下来的动画总长度、动画总帧数信息,就可以计算出当前动画播放到了第几帧(当前帧数 = 已播放时长 / 动画总时长 x 动画总帧数)。一旦得到当前动画帧,就表示可以通过表表锁定一行顶点数据。
接下来,只要遍历这行顶点数据;然后根据索引找到网格中对应的顶点并更新它的位置,就等于完成了蒙皮工作。
既然通过一个播放进度、表表和一些简单的动画信息,就能直接完成蒙皮(对顶点属性的更新),那么我们就不再需要以下内容了:
使用CPU来读取表表,并遍历更新所有顶点,显然不如GPU来的高效;所以接下来,我们将这一步放到渲染管线中的顶点变换阶段,借着GPU处理顶点的契机,完成使用表表中的数据对顶点属性进行更新。
实例化渲染的特点是使用相同网格,相同材质,通过不同的实例属性完成大批量的带有一定差异性的渲染;而烘焙顶点恰好符合了实例化渲染的使用需求。
所以,我们只需将控制动画播放的关键属性:比如过渡动画播放的V坐标、当前和下一个动画的插值比例等,放入实例化数据数组中进行传递;再在顶点着色器中,对关键属性获取并使用即可。
与蒙皮网格渲染器的比较
烘焙顶点的主要问题
除了烘焙顶点,另一种常用的优化方案是烘焙骨骼矩阵动画。
听名字就知道,烘焙骨骼矩阵与烘焙顶点位置,原理十分相似;最大的差异在于它们在烘焙时所记录的内容不一样:烘焙顶点记录下来的是每个顶点的位置,而烘焙骨骼矩阵记录下来的是每一根骨骼的矩阵,仅此而已。
烘焙骨骼矩阵最大的意义在于它补上了烘焙顶点的短板:受顶点数量限制、烘焙的动画纹理过大 及 纹理数量较多。
烘焙顶点对于纹理(面积)的使用是受顶点数量决定的,可以简单理解为:
所以,当模型的顶点数量过多时(数以千计),这种烘焙方式或者无法烘焙下整个模型(顶点数量>2048),或者需要一张或多张(法线、切线)大尺寸纹理(<2048 && > 1024)。
但是,烘焙骨骼矩阵主要取决于骨骼的数量,可以简单理解为:
在移动平台上,通常20根左右的骨骼就可以取得不错的表现效果,所以相对于烘焙顶点,烘焙骨骼可以记录下更长的动画,同时它也不再受顶点数量的限制,也无需对法线或切线进行特殊处理(因为可以在采样后通过矩阵计算得出)。
针对这两种方案,我个人认为并没有绝对的孰优孰劣;正如你所看到的,烘焙骨骼在处理复杂模型或多动作时更具优势;但如果渲染数量较多、模型顶点数较少、表现需求较弱(无需法向量参与计算)时,烘焙顶点也是值得尝试的,因为它在性能上会更好一些。
目前静态合批方案为运行时静态合批,通过调用 BatchingUtility.batchStaticModel 可进行静态合批。
该函数接收一个节点,然后将该节点下的所有 MeshRenderer 里的 Mesh 合并成一个,并将其挂到另一个节点下。
在合批后,将无法改变原有的 MeshRenderer 的 transform,但可以改变合批后的根节点的 transform。只有满足以下条件的节点才能进行静态合批:
引擎目前提供两套动态合批系统,instancing 合批和合并 VB 方式的合批,两种方式不能共存,instancing 优先级大于合并 VB。
要开启合批,只需在模型所使用的材质中对应勾选 USE_INSTANCING 或 USE_BATCHING 开关即可。
通过 Instancing 的合批适用于绘制大量顶点数据完全相同的动态模型,启用后绘制时会根据材质和顶点数据分组,每组内组织 instanced attributes 信息,然后一次性完成绘制。
合并 VB 合批适用于绘制大量低面数且顶点数据各不相同的非蒙皮动态模型,启用后绘制时会根据材质分组,然后每组内每帧合并顶点和世界变换信息,然后分批完成绘制
通常来说合批系统的使用优先级为:静态合批 > instancing 合批 > 合并 VB 合批。
骨骼动画是一种常见但类型特殊的动画,我们提供了 预烘焙骨骼动画 和 实时计算骨骼动画 两套系统,针对不同方向的需求,分别优化。
这两套系统的唯一开关就是 SkeletalAnimation 上的 useBakedAnimation 开关,启用时会使用预烘焙骨骼动画系统,禁用后会使用实时计算骨骼动画系统,运行时也可以无缝切换。
目前所有模型资源在导入后,prefab 中全部默认使用预烘焙系统,以达到最佳性能。我们建议只在明显感到预烘焙系统的表现力无法达标的情况下,再使用实时计算系统。虽然两套系统可以在运行时无缝切换,但尽量不要高频执行,因为每次切换都涉及底层渲染数据的重建。
基于预烘焙系统的框架设计,蒙皮模型的 instancing 也成为了触手可及的功能,但要保证正确性还需要收集一些比较底层的信息。
这里的根本问题是,同一个 drawcall 内的各个模型使用的骨骼贴图必须是同一张,如果不是同一张,显示效果会完全错乱。所以如何将动画数据分配到每张骨骼贴图上,就成为了一个需要用户自定义的信息,对应在编辑器项目设置的 骨骼贴图布局面板 进行配置。
注意 :
目前底层上传 GPU 的骨骼纹理已做到全局自动合批复用,上层数据目前可以通过使用 批量蒙皮模型组件(BatchedSkinnedMeshRenderer)将同一个骨骼动画组件控制的所有子蒙皮模型合并: