您的位置:首页 > 移动开发 > Cocos引擎

Cocos2d-x 3.x 图形学渲染系列九

2017-01-08 20:09 615 查看
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

在利用Cocos2d-x引擎开发游戏时,它提供了模型加载的功能,现在给读者分析一下其使用情况。

游戏开发设计的都是利用数据驱动的,这样设计的目的是方便策划随时调整游戏数值,换句话说就是游戏开发中的数值是通过程序加载读取的文本文件数据进行调整的。对于3D模型文件的加载其原理也是一样的,模型文件也是需要程序加载读取,3D模型文件的格式本身可以是二进制文件或者是XML文本文件、Json文本文件等。Cocos2d-x引擎的Bundle3D类提供了加载3D模型的接口,对于3D模型来说它有自己的模型Mesh顶点信息、纹理的UV信息、顶点索引信息等。这些信息在程序加载中将它们一一加载读取到程序中最终绘制显示出来。Cocos2D-x引擎目前支持加载三种模型格式:c3t模型格式、c3b模型格式、obj模型格式。c3b和c3t模型格式都是在同一个函数中加载的,区别是前者是二进制,后者是Json文件。Cocos2d-x引擎提供加载支持的c3t和c3b模型文件格式不是通过max工具直接导出的,而是通过fbx-conv工具转换的。这个转换工具会在后面章节给读者介绍,下面开始在实际开发中加载c3t和c3b模型文件,函数如下所示:

bool Bundle3D::load(const std::string& path)
{
if (path.empty())
return false;

if (_path == path)
return true;

getModelRelativePath(path);

bool ret = false;
std::string ext = FileUtils::getInstance()->getFileExtension(path);
if (ext == ".c3t")
{
_isBinary = false;
ret = loadJson(path);
}
else if (ext == ".c3b")
{
_isBinary = true;
ret = loadBinary(path);
}
else
{
CCLOG("warning: %s is invalid file formate", path.c_str());
}

ret?(_path = path):(_path = "");

return ret;
}


load函数的参数是模型的加载路径,函数的具体实现是根据模型的扩展名确定不同的模型文件格式。它们的加载函数分别是loadJson函数和loadBinary函数,具体实现内容会在后面的模型加载中具体讲到,Bundle3D类还提供了加载obj模型,函数实现如下所示:

bool Bundle3D::loadObj(MeshDatas& meshdatas, MaterialDatas& materialdatas, NodeDatas& nodedatas, const std::string& fullPath, const char* mtl_basepath)
{
meshdatas.resetData();
materialdatas.resetData();
nodedatas.resetData();

std::string mtlPath = "";
if (mtl_basepath)
mtlPath = mtl_basepath;
else
mtlPath = fullPath.substr(0, fullPath.find_last_of("\\/") + 1);

std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
auto ret = tinyobj::LoadObj(shapes, materials, fullPath.c_str(), mtlPath.c_str());
if (ret.empty())
{
//填充数据
//转换material材质文件
int i = 0;
charstr[20];
std::string dir = "";
auto last = fullPath.rfind("/");
if (last != -1)
dir = fullPath.substr(0, last + 1);
for (auto &material : materials) {
NMaterialData materialdata;

NTextureData tex;
tex.filename = material.diffuse_texname.empty() ? material.diffuse_texname : dir + material.diffuse_texname;
tex.type = NTextureData::Usage::Diffuse;
tex.wrapS = GL_CLAMP_TO_EDGE;
tex.wrapT = GL_CLAMP_TO_EDGE;

sprintf(str, "%d", i++);
materialdata.textures.push_back(tex);
materialdata.id = str;
material.name = str;
materialdatas.materials.push_back(materialdata);
}

//转换mesh
i = 0;
for (auto& shape : shapes) {
auto mesh = shape.mesh;
MeshData* meshdata = new (std::nothrow) MeshData();
MeshVertexAttrib attrib;
attrib.size = 3;
attrib.type = GL_FLOAT;

if (mesh.positions.size())
{
attrib.vertexAttrib = GLProgram::VERTEX_ATTRIB_POSITION;
attrib.attribSizeBytes = attrib.size * sizeof(float);
meshdata->attribs.push_back(attrib);

}
bool hasnormal = false, hastex = false;
if (mesh.normals.size())
{
hasnormal = true;
attrib.vertexAttrib = GLProgram::VERTEX_ATTRIB_NORMAL;
attrib.attribSizeBytes = attrib.size * sizeof(float);;
meshdata->attribs.push_back(attrib);
}
if (mesh.texcoords.size())
{
hastex = true;
attrib.size = 2;
attrib.vertexAttrib = GLProgram::VERTEX_ATTRIB_TEX_COORD;
attrib.attribSizeBytes = attrib.size * sizeof(float);
meshdata->attribs.push_back(attrib);
}

auto vertexNum = mesh.positions.size() / 3;
for(unsigned int k = 0; k < vertexNum; k++)
{
meshdata->vertex.push_back(mesh.positions[k * 3]);
meshdata->vertex.push_back(mesh.positions[k * 3 + 1]);
meshdata->vertex.push_back(mesh.positions[k * 3 + 2]);

if (hasnormal)
{
meshdata->vertex.push_back(mesh.normals[k * 3]);
meshdata->vertex.push_back(mesh.normals[k * 3 + 1]);
meshdata->vertex.push_back(mesh.normals[k * 3 + 2]);
}

if (hastex)
{
meshdata->vertex.push_back(mesh.texcoords[k * 2]);
meshdata->vertex.push_back(mesh.texcoords[k * 2 + 1]);
}
}

//根据材质分成子网格
std::map<int, std::vector<unsigned short>> subMeshMap;
for (size_t k = 0; k <mesh.material_ids.size(); k++) {
int id = mesh.material_ids[k];
size_t idx = k * 3;
subMeshMap[id].push_back(mesh.indices[idx]);
subMeshMap[id].push_back(mesh.indices[idx + 1]);
subMeshMap[id].push_back(mesh.indices[idx + 2]);
}

auto node = new (std::nothrow) NodeData();
node->id = shape.name;
for (auto& submesh : subMeshMap) {
meshdata->subMeshIndices.push_back(submesh.second);
meshdata->subMeshAABB.push_back(calculateAABB(meshdata->vertex, 		meshdata->getPerVertexSize(), submesh.second));
sprintf(str, "%d", i++);
meshdata->subMeshIds.push_back(str);

auto modelnode = new (std::nothrow) ModelData();
modelnode->matrialId = submesh.first == -1 ? "" : 					materials[submesh.first].name;
modelnode->subMeshId = str;
node->modelNodeDatas.push_back(modelnode);
}
nodedatas.nodes.push_back(node);
meshdatas.meshDatas.push_back(meshdata);
}

return true;
}
CCLOG("warning: load %s file error: %s", fullPath.c_str(), ret.c_str());
return false;
}


读者可能看到加载obj的文件格式感觉头都大了,其实如果你了解了obj的文件内容就知道其实还是很简单的,obj模型的文件内容给读者截取一部分展示效果如图



obj文件中的第三行表示的是模型材质的名字scene.mtl。它也是一个文本文件,内容如下图:



文件中显示的是材质的一些参数,读者了解就可以了,毕竟对于obj模型格式,因为它无法实现模型动作,在实际项目开发中应用非常少,在最后两行显示的是obj模型的图片存放路径。下面继续obj模型文件的介绍,文件显示中,v表示的是模型的顶点,当然obj文件格式不只是只有v的信息,它还有如下图:



接下来就是纹理信息如下图:



最后是模型面的信息如下图:



以上是整个obj模型文件内容,引擎实现的加载函数也是解释这些信息并将模型加载显示出来的。

obj模型文件格式网上也有很多这方面的介绍,但是obj文件有一个致命的弱点就是不能带有骨骼动画,只能是静态的。现在的游戏也好,虚拟现实也罢都需要有骨骼动画的模型,所以在实际项目中加载obj模型文件的函数用的比较少。只可以用来测试模型信息的,下面介绍在3D游戏中都会使用拾取功能。








[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: