内容简介:cocos2d-x 3.x 图形学渲染系列十九
笔者介绍: 姜雪伟 , IT 公司技术合伙人, IT 高级讲师, CSDN 社区专家,特邀编辑,畅销书作者,国家专利发明人 ; 已出版书籍:《手把手教你 架构 3D 游戏引擎》电子工业出版社 和《 Unity3D 实战核心技术详解》电子工业出版社等。
任何一款3D 引擎必须要有模型的加载处理,Unity3D引擎和UE4引擎使用的是FBX模型文件格式,而开源Ogre引擎使用的是扩展名为mesh的文件格式,另外,市面上其它各个引擎也都有自己的文件格式,在此就不一一列举了。Cocos2d-x引擎使用的是FBX文件的转换格式,转换格式会导致模型的一些信息丢失掉,特别是骨骼信息中的Morph骨骼,在这方面Cocos2d-x引擎并不支持,就不做过多讨论了,本节给读者讲述模型的加载流程,首先需将模型创建成Sprite3D精灵,便于在场景中直接挂接,在场景中创建精灵的调用函数接口如下:
Sprite3D *sprite = Sprite3D::create(_modelFile);
调用了Sprite3D
类的接口create函数,下面再具体查看create函数做了哪些工作:
Sprite3D* Sprite3D::create(const std::string& modelPath) { CCASSERT(modelPath.length() >= 4, "invalid filename for Sprite3D"); auto sprite = new (std::nothrow) Sprite3D(); if (sprite && sprite->initWithFile(modelPath)) { sprite->_contentSize = sprite->getBoundingBox().size; sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return nullptr; } Sprite3D* Sprite3D::create(const std::string& modelPath, const std::string& texturePath) { auto sprite = create(modelPath); if (sprite) { sprite->setTexture(texturePath); } return sprite; }
Cocos2d-x 引擎提供了两个接口函数,也是函数的重载。第一个函数create 是逻辑编写经常使用的函数,它内部调用了接口initWithFile函数,通过函数名字知道它只是做了初始化工作。当然它也做了一下获取包围盒大小工作后面会详细介绍。再看第二个create函数,相比第一个create函数参数多了一个设置纹理路径的参数。在Cocos2d-x引擎中,默认情况下,加载模型可以直接加载材质,这是因为对于默认的材质会在模型文件中引用。另外Cocos2d-x引擎也提供了可自己手动更换材质的接口。在create函数中看不到任何加载模型的语句,接下来介绍调用的唯一接口函数initWithFile是如何实现的:
bool Sprite3D::initWithFile(const std::string& path) { _aabbDirty = true; _meshes.clear(); _meshVertexDatas.clear(); CC_SAFE_RELEASE_NULL(_skeleton); removeAllAttachNode(); if (loadFromCache(path)) return true; //定义的模型数据信息 MeshDatas* meshdatas = new (std::nothrow) MeshDatas(); MaterialDatas* materialdatas = new (std::nothrow) MaterialDatas(); NodeDatas* nodeDatas = new (std::nothrow) NodeDatas(); //模型文件加载 if (loadFromFile(path, nodeDatas, meshdatas, materialdatas)) { if (initFrom(*nodeDatas, *meshdatas, *materialdatas)) { //增加到缓存里面 auto data = new (std::nothrow) Sprite3DCache::Sprite3DData(); data->materialdatas = materialdatas; data->nodedatas = nodeDatas; data->meshVertexDatas = _meshVertexDatas; for (const auto mesh : _meshes) { data->glProgramStates.pushBack(mesh->getGLProgramState()); } Sprite3DCache::getInstance()->addSprite3DData(path, data); CC_SAFE_DELETE(meshdatas); _contentSize = getBoundingBox().size; return true; } } CC_SAFE_DELETE(meshdatas); CC_SAFE_DELETE(materialdatas); CC_SAFE_DELETE(nodeDatas); return false; }
在 initWithFile 函数中声明了几个用于存储模型点信息的指针:MeshDatas ,MaterialDatas,NodeDatas,同时调用函数loadFromFile加载模型信息,loadFromFile函数的实现如下所示:
bool Sprite3D::loadFromFile(const std::string& path, NodeDatas* nodedatas, MeshDatas* meshdatas, MaterialDatas* materialdatas) { std::string fullPath = FileUtils::getInstance()->fullPathForFilename(path); std::string ext = FileUtils::getInstance()->getFileExtension(path); //加载obj模型文件 if (ext == ".obj") { return Bundle3D::loadObj(*meshdatas, *materialdatas, *nodedatas, fullPath); } else if (ext == ".c3b" || ext == ".c3t") { //从c3t和c3b文件中加载模型 auto bundle = Bundle3D::createBundle(); if (!bundle->load(fullPath)) { Bundle3D::destroyBundle(bundle); return false; } auto ret = bundle->loadMeshDatas(*meshdatas) && bundle->loadMaterials(*materialdatas) && bundle->loadNodes(*nodedatas); Bundle3D::destroyBundle(bundle); return ret; } return false; }
在 loadFromFile 函数中,终于看到了加载模型的具体语句,抛开obj 文件模型,在loadFromFile 函数中还调用了Bundle3D 的函数接口load,Bundle3D在前面章节中讲过,它主要是处理模型信息的类,接下来进入load函数内部看一下:
bool Bundle3D::load(const std::string& path) { if (path.empty()) return false; if (_path == path) return true; getModelRelativePath(path); boolret = false; std::string ext = FileUtils::getInstance()->getFileExtension(path); if (ext == ".c3t") { _isBinary = false; //加载json文件 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。终于快看到曙光了,模型它相对来说也是一种文本文件,研究一款引擎必须要知道它底层的处理方式。再继续深入下去看loadJson函数的实现内容如下:
bool Bundle3D::loadJson(const std::string& path) { clear(); _jsonBuffer = FileUtils::getInstance()->getStringFromFile(path); if(_jsonReader.ParseInsitu<0>((char*)_jsonBuffer.c_str()).HasParseError()) { clear(); CCLOG("Parse json failed in Bundle3D::loadJson function"); return false; } const rapidjson::Value& mash_data_array = _jsonReader[VERSION]; if (mash_data_array.IsArray()) // 适配于老板本 _version = "1.2"; else _version = mash_data_array.GetString(); return true; }
它是通过_jsonReader.ParseInsitu 把json文件解释出来,这样真相终于大白了,接下来再看看c3b文件的读取,在游戏中这个格式是使用最多的,它的加载函数loadBinary如下所示:
bool Bundle3D::loadBinary(const std::string& path) { clear(); // 获取文件数据 _binaryBuffer.clear(); _binaryBuffer = FileUtils::getInstance()->getDataFromFile(path); if (_binaryBuffer.isNull()) { clear(); CCLOG("warning: Failed to read file: %s", path.c_str()); return false; } // 初始化bundle数据 _binaryReader.init( (char*)_binaryBuffer.getBytes(), _binaryBuffer.getSize() ); // 读取标示信息 char identifier[] = { 'C', '3', 'B', '\0'}; char sig[4]; if (_binaryReader.read(sig, 1, 4) != 4 || memcmp(sig, identifier, 4) != 0) { clear(); CCLOG("warning: Invalid identifier: %s", path.c_str()); return false; } // 读取版本号 unsigned char ver[2]; if (_binaryReader.read(ver, 1, 2)!= 2){ CCLOG("warning: Failed to read version:"); return false; } char version[20] = {0}; sprintf(version, "%d.%d", ver[0], ver[1]); _version = version; // 读取ref表大小 if (_binaryReader.read(&_referenceCount, 4, 1) != 1) { clear(); CCLOG("warning: Failed to read ref table size '%s'.", path.c_str()); return false; } // 读取所有的refs CC_SAFE_DELETE_ARRAY(_references); _references = new (std::nothrow) Reference[_referenceCount]; for (unsigned int i = 0; i <_referenceCount; ++i) { if ((_references[i].id = _binaryReader.readString()).empty()||_binaryReader.read(&_references[i].type, 4, 1) != 1 ||_binaryReader.read(&_references[i].offset, 4, 1) != 1) { clear(); CCLOG("warning: Failed to read ref number %u for bundle '%s'.", i, path.c_str()); CC_SAFE_DELETE_ARRAY(_references); return false; } } return true; }
loadBinary
函数是真正的解释二进制文件,而且其加密方式也是采用的二进制,加密算法会在后面给读者进行讲解,到这里模型的加载就全部完成了。再返回到前面的函数initWithFile
,在函数内部还有一个函数initFrom没有讲解,加载完模型后,最终要将其显示出来,而且还要把材质附加到模型上面以及关于模型的骨骼动画等这些操作都是通过如下函数实现的,函数内容如下所示:
bool Sprite3D::initFrom(const NodeDatas& nodeDatas, const MeshDatas& meshdatas, const MaterialDatas& materialdatas) { for(const auto& it : meshdatas.meshDatas) { if(it) { auto meshvertex = MeshVertexData::create(*it); _meshVertexDatas.pushBack(meshvertex); } } _skeleton = Skeleton3D::create(nodeDatas.skeleton); CC_SAFE_RETAIN(_skeleton); for(const auto& it : nodeDatas.nodes) { if(it) { createNode(it, this, materialdatas, nodeDatas.nodes.size() == 1); } } for(const auto& it : nodeDatas.skeleton) { if(it) { createAttachSprite3DNode(it,materialdatas); } } genMaterial(); return true; }
在函数中实现了meshVertex
网格顶点以及skeleton骨骼动画信息读取,另外还调用了createNode函数用于处理模型相关的所有信息,函数如下所示:
void Sprite3D::createNode(NodeData* nodedata, Node* root, const MaterialDatas& materialdatas, bool singleSprite) { Node* node=nullptr; for(const auto& it : nodedata->modelNodeDatas) { if(it) { if(it->bones.size() >0 || singleSprite) { if(singleSprite && root!=nullptr) root->setName(nodedata->id); auto mesh = Mesh::create(nodedata->id, getMeshIndexData(it->subMeshId)); if(mesh) { _meshes.pushBack(mesh); if (_skeleton&& it->bones.size()) { auto skin = MeshSkin::create(_skeleton, it->bones, it->invBindPose); mesh->setSkin(skin); } mesh->_visibleChanged = std::bind(&Sprite3D::onAABBDirty, this); if (it->matrialId == ""&& materialdatas.materials.size()) { const NTextureData* textureData = materialdatas.materials[0].getTextureData(NTextureData::Usage::Diffuse); mesh->setTexture(textureData->filename); } else { const NMaterialData* materialData=materialdatas.getMaterialData(it->matrialId); if(materialData) { const NTextureData* textureData = materialData->getTextureData(NTextureData::Usage::Diffuse); if(textureData) { mesh->setTexture(textureData->filename); auto tex = mesh->getTexture(); if(tex) { Texture2D::TexParams texParams; texParams.minFilter = GL_LINEAR; texParams.magFilter = GL_LINEAR; texParams.wrapS = textureData->wrapS; texParams.wrapT = textureData->wrapT; tex->setTexParameters(texParams); mesh->_isTransparent = (materialData->getTextureData(NTextureData::Usage::Transparency) != nullptr); } } textureData = materialData->getTextureData(NTextureData::Usage::Normal); if (textureData) { auto tex = Director::getInstance()->getTextureCache()->addImage(textureData->filename); if (tex) { Texture2D::TexParams texParams; texParams.minFilter = GL_LINEAR; texParams.magFilter = GL_LINEAR; texParams.wrapS = textureData->wrapS; texParams.wrapT = textureData->wrapT; tex->setTexParameters(texParams); } mesh->setTexture(tex, NTextureData::Usage::Normal); } } } Vec3 pos; Quaternion qua; Vec3 scale; nodedata->transform.decompose(&scale, &qua, &pos); setPosition3D(pos); setRotationQuat(qua); setScaleX(scale.x); setScaleY(scale.y); setScaleZ(scale.z); node = this; } } else { auto sprite = createSprite3DNode(nodedata,it,materialdatas); if (sprite) { if(root) { root->addChild(sprite); } } node=sprite; } } } if(nodedata->modelNodeDatas.size() ==0 ) { node= Node::create(); if(node) { node->setName(nodedata->id); // 设置局部转换 Vec3 pos; Quaternion qua; Vec3 scale; nodedata->transform.decompose(&scale, &qua, &pos); node->setPosition3D(pos); node->setRotationQuat(qua); node->setScaleX(scale.x); node->setScaleY(scale.y); node->setScaleZ(scale.z); if(root) { root->addChild(node); } } } for(const auto& it : nodedata->children) { createNode(it,node, materialdatas, nodedata->children.size() == 1); } }
createNode 函数完成了3D 模型的整个加载流程,接下来开始讲解模型的材质渲染,从下节开始材质的渲染操作。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CubeEngine v0.3B 更新,图形渲染引擎
- TensorFlow也可以做图形渲染了:当神经网络遇上计算机图形学
- Basemark推出Rocksolid图形渲染解决方案
- Ogre 1.12.3 发布,三维图形渲染引擎
- Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录)
- Cocos2d-x 3.x 图形学渲染系列总结
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Script深度剖析
卢云鹏、沈维伦、Don Gosselin、李筱青 / 卢云鹏、沈维伦、李筱青 / 北京大学出版社 / 2004-10-1 / 49.0
本书适合于大中专院计算机相关专业作为教材,也是JavaScript初学者以及JavaScript爱好者的理想参考用书。书中详细介绍了基本的JavaScript程序设计原理以及实现它们的语法,内容包括JavaScript简介,变理、函数、对角和事件,数据类型、运算符,结构化逻辑控制结构和语句,窗口和框架,表单,动态HTML和动画,cookie和安全性,服务器端 JavaScript,数据库连接,使用......一起来看看 《Java Script深度剖析》 这本书的介绍吧!