Cocos2d-x 3D模型渲染

Cocos2d-x 3D模型渲染

声明:本文使用的是cocos2d-x-3.17的代码

文章中的提到的测试代码下载地址https://gitee.com/Kyle12/Cocos2dRenderStudy

3D模型

Cocos2d支持渲染*.fbx和*.obj两种3D模型,*.obj模型可以直接渲染,*.fbx模型需要先使用官方的转换程序fbx-conv.exe转换成*.c3t或者*.c3b类型才能渲染。*.c3t是Json格式的文本文件可以使用记事本打开,*.c3b是二进制文件更省空间,处理速度也更快。这几种文件中只有*.c3t文件是文本文件,这里结合*.c3t文件分析一下3D模型。

 

以下是一个*.c3t文件,这里下载,可以先把文件的扩展名改成json,用notepad++打开:

Cocos2d-x 3D模型渲染_第1张图片

文件由version、id、meshes、materials、nodes、animations组成。Meshes包含了顶点数据索引数据,materials包含了绘制使用的纹理,nodes包含了模型各个模块和骨架 ,animations是骨骼动画。Animations另外有文章讨论,先看meshes、materials、nodes。

对于一个3D模型可以包含很多模块,如下图是win10上“画图3D”程序制作3D模型,(注意“画图3D”程序生成的*.fbx模型,使用fbx-conv.exe程序转换时有bug,转换后的模型有时显示不正常

Cocos2d-x 3D模型渲染_第2张图片

这里面有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另外有文章讨论。另外三个对象数据结构如下:

 

Cocos2d-x 3D模型渲染_第3张图片

其他类型的3D模型加载后也对应的是这三个对象,加载函数为Sprite3D::loadFromFile。

 

Mesh初始化

对于3D模型的绘制使用的是Sprite3D对象,Sprite3D对象包含了多个Mesh对象,最终模型的绘制由Mesh对象完成。加载后的模型数据meshdatas、MaterialDatas、NodeDatas可以用来创建Mesh对象,Mesh对象实现了3D模型的绘制。模型中每个模块需要创建一个Mesh,即NodeData中的每个modelNodeData都会创建一个Mesh

 

顶点缓存和索引缓存

首先需要根据meshdatas创建顶点缓存MeshVertexData和索引缓存MeshIndexData,顶点缓存MeshVertexData中会包含多个索引缓存MeshIndexData,结构如下:

Cocos2d-x 3D模型渲染_第4张图片

 

Mesh结构

 

Cocos2d-x 3D模型渲染_第5张图片

Mesh中有一个对象Mesh:: _material对象中这个对象并不是MaterialDatas初始而来。MaterialDatas中只存储了纹理,也就是Mesh:: _texturesMesh:: _material是一个Material类包含了所有mesh绘制的数据。

Material类内部包含了Technique数组,Technique中包含Pass数组,Pass里面有VertexAttribBinding,真正存储绘制数据的是VertexAttribBindingVertexAttribBinding包含了绘制用的着色器程序GLProgramState,顶点索引缓存MeshVertexData。

Material类设计的很乱,很难从Technique类和Pass类看出类设计的目的,原本以为这两者是为了实现3D模型“换装”用的,但官方给的“换装”例子并没有使用这两个类实现。

 

模型绘制

3D模型绘制使用的是MeshCommand命令,MeshCommand命令可以直接使用顶点缓存索引缓存绘制,也可以使用Material类绘制。直接使用缓存绘制的方法请参考“Cocos2d-x 渲染器Renderer——MeshCommand”,这里主要是使用Material类绘制。

 

Material绘制3D模型

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中对这一块的代码设计的很糟糕,原因有以下几点:

  1. pass绘制的顶点缓存和索引缓存来至VertexAttribBinding,所以绘制原本应该使用VertexAttribBinding:: _glProgramState,但实际上使用的是Pass::_glProgramState
  2. Pass::_glProgramStateVertexAttribBinding:: _glProgramState都是指针,他们指向的是同一个对象,这个一致是因为Mesh::setMaterial中的以下代码:
    auto vertexAttribBinding = VertexAttribBinding::create(_meshIndexData, pass->getGLProgramState());
    pass->setVertexAttribBinding(vertexAttribBinding);

    这种方式并不能完全保证Pass::_glProgramStateVertexAttribBinding:: _glProgramState完全都是指向同一个对象。
  3. Pass::_glProgramState是可以通过函数Pass::setGLProgramState随意设置的,而设置的_glProgramState并不能保证没有被共享。毕竟程序中还专门设计了GLProgramStateCache类共享GLProgramState

 

系统预创建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)可以获取Materialtype为Material类型,skinned为是否使用骨骼。

 

Material绘制步骤

  1. 创建顶点缓存和索引缓存,可使用模型加载的MeshDatas数据创建
  2. 创建Material对象,可以使用Sprite3DMaterial::createBuiltInMaterial函数创建
  3. 使用索引缓存创建VertexAttribBinding,并设置Material:: _techniques:: _passes_vertexAttribBinding
  4. 使用Material对象初始MeshCommand绘制命令
  5. 设置Material:: _techniques:: _passes_glProgramStateUniform值。
  6. 绘制出图形。

Material绘制完整例子:DrawModelScene.cpp/DrawModelScene.h,对应程序菜单“Test Draw 3D->Draw Model

 

光照

以下是有光照和无光照对比

Cocos2d-x 3D模型渲染_第6张图片

光照作用是使得3D物体看上去更加立体,光照也可以通过光的颜色改变物体颜色。

 

Cocos2d-x支持四种光源,四种光源以及对应的类是,环境光AmbientLight、方向光DirectionLight、点光源PointLight、聚光源SpotLight。

使用光照也很简单,光源类继承了Node类,只需要创建相应的光源对象,然后用AddChild加入到场景树的任意节点,绘制Sprite3D精灵时会自动使用场景树的节点。

执行的顺序如下图:

Cocos2d-x 3D模型渲染_第7张图片

完整例子: SpriteLightScene.cpp/SpriteLightScene.h,对应程序菜单“Test Draw 3D->Sprite3D with Light


动画部分的讲解:Cocos2d骨骼动画

你可能感兴趣的:(Cocos2d-x,渲染,Cocos2d-x)