声明:本文使用的是cocos2d-x-3.17的代码
文章中的提到的测试代码下载地址https://gitee.com/Kyle12/Cocos2dRenderStudy
Cocos2d支持渲染*.fbx和*.obj两种3D模型,*.obj模型可以直接渲染,*.fbx模型需要先使用官方的转换程序fbx-conv.exe转换成*.c3t或者*.c3b类型才能渲染。*.c3t是Json格式的文本文件可以使用记事本打开,*.c3b是二进制文件更省空间,处理速度也更快。这几种文件中只有*.c3t文件是文本文件,这里结合*.c3t文件分析一下3D模型。
以下是一个*.c3t文件,这里下载,可以先把文件的扩展名改成json,用notepad++打开:
文件由version、id、meshes、materials、nodes、animations组成。Meshes包含了顶点数据索引数据,materials包含了绘制使用的纹理,nodes包含了模型各个模块和骨架 ,animations是骨骼动画。Animations另外有文章讨论,先看meshes、materials、nodes。
对于一个3D模型可以包含很多模块,如下图是win10上“画图3D”程序制作3D模型,(注意“画图3D”程序生成的*.fbx模型,使用fbx-conv.exe程序转换时有bug,转换后的模型有时显示不正常)
这里面有3个模块,这3个模块构成了一个大的3D模型,每个模块我们可以调整位置和大小,为了更加方便的控制,每个模块都可以有自己的局部坐标系,模型会有一个大的全局坐标系。
Meshes
Meshes包含了模型的所有顶点数据vertices,顶点数据的属性(attributes)和各个模块(parts)的索引。以上面的3D绘图为例,vertices中会包含3个模块所有的点,parts中包含3组索引,代表三个模块。
materials
materials包含了模型中用到的纹理,只记录了纹理的文件名。这里面也会包含物体的光照材质、反光率、透明度等等,但Cocos2d-x 3.17中并没有使用这些参数。
nodes
nodes记录了整个模型的结构,如果使用了骨骼也会包含骨骼信息。以上面的3D绘图为例,首先有一个node节点,代表整个大的模型,它会有3个子node,代表3个模块。每个node里面都会有一个4*4矩阵用于坐标系变换
模型的加载需要用到Bundle3D类,*.c3t文件加载后meshes对应meshdatas对象,materials对应MaterialDatas对象、nodes对应NodeDatas对象、animations对应Animation3Ddata对象,Animation3Ddata另外有文章讨论。另外三个对象数据结构如下:
其他类型的3D模型加载后也对应的是这三个对象,加载函数为Sprite3D::loadFromFile。
对于3D模型的绘制使用的是Sprite3D对象,Sprite3D对象包含了多个Mesh对象,最终模型的绘制由Mesh对象完成。加载后的模型数据meshdatas、MaterialDatas、NodeDatas可以用来创建Mesh对象,Mesh对象实现了3D模型的绘制。模型中每个模块需要创建一个Mesh,即NodeData中的每个modelNodeData都会创建一个Mesh。
顶点缓存和索引缓存
首先需要根据meshdatas创建顶点缓存MeshVertexData和索引缓存MeshIndexData,顶点缓存MeshVertexData中会包含多个索引缓存MeshIndexData,结构如下:
Mesh结构
Mesh中有一个对象Mesh:: _material对象中这个对象并不是MaterialDatas初始而来。MaterialDatas中只存储了纹理,也就是Mesh:: _textures。Mesh:: _material是一个Material类包含了所有mesh绘制的数据。
Material类内部包含了Technique数组,Technique中包含Pass数组,Pass里面有VertexAttribBinding,真正存储绘制数据的是VertexAttribBinding。VertexAttribBinding包含了绘制用的着色器程序GLProgramState,顶点索引缓存MeshVertexData。
Material类设计的很乱,很难从Technique类和Pass类看出类设计的目的,原本以为这两者是为了实现3D模型“换装”用的,但官方给的“换装”例子并没有使用这两个类实现。
3D模型绘制使用的是MeshCommand命令,MeshCommand命令可以直接使用顶点缓存索引缓存绘制,也可以使用Material类绘制。直接使用缓存绘制的方法请参考“Cocos2d-x 渲染器Renderer——MeshCommand”,这里主要是使用Material类绘制。
Material类包含了所有mesh绘制要使用的数据,绘制的时候只需一个Material对象就可以绘制,以下是MeshCommand中使用Material绘制的代码:
for(const auto& pass: _material->_currentTechnique->_passes) { pass->bind(_mv);
glDrawElements(_primitive, (GLsizei)_indexCount, _indexFormat, 0); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, _indexCount);
pass->unbind(); } |
Material类中有多个Technique,绘制时只会使用_currentTechnique,会对_currentTechnique中所有的pass类都执行一次绘制。
从代码中可以看出Cocos2d希望每个3D模块绘制都由一个Material对象控制,因为获取Material对象的函数Sprite3DMaterial::createBuiltInMaterial返回值是缓存Material对象的克隆值。Material对象克隆时会克隆所有的Technique对象,pass对象,pass对象中的的_glProgramState也会被克隆,这也可以说明pass对象中的的_glProgramState不是共享的,是对象单独所有的。pass->bind(_mv)使用的Pass::_glProgramState,而不是VertexAttribBinding::_glProgramState。
因为pass对象中的的_glProgramState是单独所有的,所以我们可以在生成MeshCommand命令时设置pass的_glProgramState中的uniform值,不会影响其他对象的绘制。
实际代码中Cocos2d-x 3.17中对这一块的代码设计的很糟糕,原因有以下几点:
系统预创建Material类型
Cocos2d-x定义了以下几种Material类型,由枚举MaterialType表示:
NLIT:不使用光照,使用纹理
UNLIT_NOTEX:不使用光照,也不使用纹理
DIFFUS:使用光照和纹理
DIFFUSE_NOTEX:使用光照,不使用纹理
BUMPED_DIFFUSE:使用光照,纹理,和凹凸纹理
CUSTOM:自定义类型
其中UNLIT、DIFFUSE、BUMPED_DIFFUSE还分是否使用蒙皮骨骼。
函数Sprite3DMaterial* Sprite3DMaterial::createBuiltInMaterial(MaterialType type, bool skinned)可以获取Material,type为Material类型,skinned为是否使用骨骼。
Material绘制步骤
Material绘制完整例子:DrawModelScene.cpp/DrawModelScene.h,对应程序菜单“Test Draw 3D”->“Draw Model”
以下是有光照和无光照对比
光照作用是使得3D物体看上去更加立体,光照也可以通过光的颜色改变物体颜色。
Cocos2d-x支持四种光源,四种光源以及对应的类是,环境光AmbientLight、方向光DirectionLight、点光源PointLight、聚光源SpotLight。
使用光照也很简单,光源类继承了Node类,只需要创建相应的光源对象,然后用AddChild加入到场景树的任意节点,绘制Sprite3D精灵时会自动使用场景树的节点。
执行的顺序如下图:
完整例子: SpriteLightScene.cpp/SpriteLightScene.h,对应程序菜单“Test Draw 3D”->“Sprite3D with Light”
动画部分的讲解:Cocos2d骨骼动画