本篇我们来对接OpenGEX。不过上一篇我们留了一个尾巴没写,就是SceneNode的结构。这里补上。
在Framework/Common/SceneNode.hpp当中,首先我们定义一个SceneNode的基类,它拥有一个名字,一个存储子节点的链表,和一个存储矩阵的链表:
class BaseSceneNode { protected: std::string m_strName; std::list<std::unique_ptr<BaseSceneNode>> m_Children; std::list<std::unique_ptr<SceneObjectTransform>> m_Transforms; public: BaseSceneNode() {}; BaseSceneNode(const char* name) { m_strName = name; }; BaseSceneNode(const std::string& name) { m_strName = name; }; BaseSceneNode(const std::string&& name) { m_strName = std::move(name); }; virtual ~BaseSceneNode() {}; void AppendChild(std::unique_ptr<BaseSceneNode>&& sub_node) { m_Children.push_back(std::move(sub_node)); } void AppendChild(std::unique_ptr<SceneObjectTransform>&& transform) { m_Transforms.push_back(std::move(transform)); } };
这个基类其实就是一个标准的树(Tree)结构的节点。
然后,我们定义一个模板,从上面的基类进行派生,并加入一个名为m_pSceneObject的成员变量,代表场景对象在节点图上的挂载点:
template <typename T> class SceneNode : public BaseSceneNode { protected: std::shared_ptr<T> m_pSceneObject; public: using BaseSceneNode::BaseSceneNode; SceneNode() = default; SceneNode(const std::shared_ptr<T>& object) { m_pSceneObject = object; }; SceneNode(const std::shared_ptr<T>&& object) { m_pSceneObject = std::move(object); }; void AddSceneObjectRef(const std::shared_ptr<T>& object) { m_pSceneObject = object; }; };
接下来就是对这个模板进行各种场景对象类型的特化,比如场景几何体:
typedef BaseSceneNode SceneEmptyNode; class SceneGeometryNode : public SceneNode<SceneObjectGeometry> { protected: bool m_bVisible; bool m_bShadow; bool m_bMotionBlur; public: using SceneNode::SceneNode; void SetVisibility(bool visible) { m_bVisible = visible; }; const bool Visible() { return m_bVisible; }; void SetIfCastShadow(bool shadow) { m_bShadow = shadow; }; const bool CastShadow() { return m_bShadow; }; void SetIfMotionBlur(bool motion_blur) { m_bMotionBlur = motion_blur; }; const bool MotionBlur() { return m_bMotionBlur; }; };
光照:
class SceneLightNode : public SceneNode<SceneObjectLight> { protected: Vector3f m_Target; public: using SceneNode::SceneNode; void SetTarget(Vector3f& target) { m_Target = target; }; const Vector3f& GetTarget() { return m_Target; }; };
都是类似的结构,我就不在这里一一赘述了。
————————————————————————————————-
好了,现在我们开始导入OpenGEX。
首先我们需要导入OpenGEX官方网站提供的OpenGEX导入模板。这个模板可以在这里下载:
http://www.opengex.org/OpenGex-Import.zip
将这个模板展开到项目的External/src目录下,然后进行适当的修改。主要需要修改的地方有这么几个:
- 去掉OpenGEX.cpp当中的WinMain函数,因为我们是将其作为一个库使用
- 在OpenGEX.h当中把我们感兴趣的一些数据结构通过Public方法暴露出来
另外代码需要做一些修正才能在多平台上面编译。修改后的代码我也放在了GitHub上面,有需要的可以参考。
也可以直接在本项目的根目录下(branch article_30)直接运行build_opengex.bat(linux环境的话运行build_opengex.sh)这个我事先写好的脚本,直接以submodule方式将修改后的代码下载到External/src/opengex之下,并进行编译。
上一篇文章我们导入了crossguid这个外部库,这篇文章我们导入了OpenGEX。加上之前我们导入的ispc,目前我们的项目已经有了不少的外部倚赖关系。为了方便部分初级读者学习,我也把编译好的这些库加入到了Git Repo里面(在External/Windows或者External/Linux之下),因此如果仅仅是想编译我们开发的引擎本体,可以直接运行(build.bat (Windows) 或者 build.sh (Linux))进行编译。根目录下有各种外部库的编译脚本,想要自己重新创建外部依赖库的,也可以用这些脚本重新编译,包括LLVM/Clang。
在编译好OpenGEX库之后,我们就可以开始将其接入到我们的引擎当中。因为OpenGEX对于我们的引擎来说只是一种外部的场景文件格式,就类似于我们之前所写的BMP文件解析器,因此首先是在Framework/Interface之下创建一个名为SceneParser.hpp的头文件,定义一个通用的场景文件解析器入口给我们的引擎(场景管理模块)调用:
#pragma once #include <memory> #include <string> #include "Interface.hpp" #include "SceneNode.hpp" namespace My { Interface SceneParser { public: virtual std::unique_ptr<BaseSceneNode> Parse(const std::string& buf) = 0; }; }
然后在Framework/Parser之下(注意我这里将之前的Framework/Codec目录改名为了Framework/Parser,以便更好地识别这个目录里代码的功用)创建OGEX.hpp,具体实现OpenGEX场景文件的解析器(篇幅原因,代码有大量删节,具体请上GitHub查看):
#include <unordered_map> #include "OpenGEX.h" #include "portable.hpp" #include "SceneParser.hpp" namespace My { class OgexParser : implements SceneParser { private: std::unordered_map<std::string, std::shared_ptr<BaseSceneObject>> m_SceneObjects; private: void ConvertOddlStructureToSceneNode(const ODDL::Structure& structure, std::unique_ptr<BaseSceneNode>& base_node) { std::unique_ptr<BaseSceneNode> node; switch(structure.GetStructureType()) { case OGEX::kStructureNode: { ... break; case OGEX::kStructureGeometryNode: { ... break; case OGEX::kStructureLightNode: { ... } break; case OGEX::kStructureCameraNode: { ... } break; case OGEX::kStructureGeometryObject: { ... } break; case OGEX::kStructureTransform: { ... } return; default: default: // just ignore it and finish return; }; const ODDL::Structure* sub_structure = structure.GetFirstSubnode(); while (sub_structure) { ConvertOddlStructureToSceneNode(*sub_structure, node); sub_structure = sub_structure->Next(); } base_node->AppendChild(std::move(node)); } public: virtual std::unique_ptr<BaseSceneNode> Parse(const std::string& buf) { std::unique_ptr<BaseSceneNode> root_node (new BaseSceneNode("scene_root")); OGEX::OpenGexDataDescription openGexDataDescription; ODDL::DataResult result = openGexDataDescription.ProcessText(buf.c_str()); if (result == ODDL::kDataOkay) { const ODDL::Structure* structure = openGexDataDescription.GetRootStructure()->GetFirstSubnode(); while (structure) { ConvertOddlStructureToSceneNode(*structure, root_node); structure = structure->Next(); } } return std::move(root_node); } }; }
上面这段代码总的来说就是递归迭代来遍历OpenGEX导入模板所创建的场景图(树),然后将我们感兴趣的数据结构复制到我们之前所定义的场景结构与节点图当中。
接下来我们开始准备测试我们所写的OpenGEX场景文件解析代码,同时也是测试我们在从零开始手敲次世代游戏引擎(二十八)以及从零开始手敲次世代游戏引擎(二十九)所写的SceneObject.hpp和SceneNode.hpp这两个核心的场景数据结构文件。OpenGEX官方网站提供的导入包里面有一个Example.ogex文件,我们将其拷贝到项目的Asset/Scene/ 目录下,然后在项目的Test目录(这也是一个新目录,我将之前写的所有与平台无关的测试用代码都转移到了这里)当中,创建OgexParserTest.cpp,在其中通过我们在从零开始手敲次世代游戏引擎(二十五)写的AssetLoader加载这个文本文件到内存,然后通过我们上面刚刚写出来的OgexParser对其进行解析,生成我们所设计编写的SceneObject和SceneNode场景结构,然后将其以文本方式打印出来:
#include <iostream> #include <string> #include "AssetLoader.hpp" #include "MemoryManager.hpp" #include "OGEX.hpp" using namespace My; using namespace std; namespace My { MemoryManager* g_pMemoryManager = new MemoryManager(); } int main(int , char** ) { g_pMemoryManager->Initialize(); AssetLoader asset_loader; string ogex_text = asset_loader.SyncOpenAndReadTextFileToString("Scene/Example.ogex"); OgexParser* ogex_parser = new OgexParser (); unique_ptr<BaseSceneNode> root_node = ogex_parser->Parse(ogex_text); delete ogex_parser; cout << *root_node << endl; g_pMemoryManager->Finalize(); delete g_pMemoryManager; return 0; }
使用build.bat (windows) 或者 build.sh (linux)编译之后,运行结果如下:
F:sourcereposGameEngineFromScratch>buildTestDebugOgexParserTest.exe Scene Node ---------- Name: scene_root Scene Node ---------- Name: node1 Mesh: SceneObject ----------- GUID: 4da5a87f-3c1f-4287-afb0-cc674e49efa5 Type: MESH Primitive Type: TLST This mesh contains 0x3 vertex properties. Attribute: position Morph Target Index: 0x0 Data Type: FLT3 Data Size: 0x48 Data: -52.019 -51.0689 0 -52.019 51.0689 0 52.019 51.0689 0 52.019 -51.0689 0 -52.019 -51.0689 93.1116 52.019 -51.0689 93.1116 52.019 51.0689 93.1116 -52.019 51.0689 93.1116 -52.019 -51.0689 0 52.019 -51.0689 0 52.019 -51.0689 93.1116 -52.019 -51.0689 93.1116 52.019 -51.0689 0 52.019 51.0689 0 52.019 51.0689 93.1116 52.019 -51.0689 93.1116 52.019 51.0689 0 -52.019 51.0689 0 -52.019 51.0689 93.1116 52.019 51.0689 93.1116 -52.019 51.0689 0 -52.019 -51.0689 0 -52.019 -51.0689 93.1116 -52.019 51.0689 93.1116 Attribute: normal Morph Target Index: 0x0 Data Type: FLT3 Data Size: 0x48 Data: 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 1 0 0 1 0 0 1 0 0 1 0 -1 0 0 -1 0 0 -1 0 -0 -1 0 1 0 0 1 0 0 1 0 0 1 0 0 0 1 0 0 1 0 0 1 0 -0 1 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 Attribute: texcoord Morph Target Index: 0x0 Data Type: FLT2 Data Size: 0x30 Data: 1 0 1 1 0 1 0 0 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 This mesh contains 0x1 index arrays. Material Index: 0x0 Restart Index: 0x0 Data Type: I32 Data Size: 0x24 Data: 0x0 0x1 0x2 0x2 0x3 0x0 0x4 0x5 0x6 0x6 0x7 0x4 0x8 0x9 0xa 0xa 0xb 0x8 0xc 0xd 0xe 0xe 0xf 0xc 0x10 0x11 0x12 0x12 0x13 0x10 0x14 0x15 0x16 0x16 0x17 0x14 Visible: 1 Shadow: 1 Motion Blur: 1 Visible: 1 Shadow: 1 Motion Blur: 1 Transform Matrix: 1,0,0,0 0,1,0,0 0,0,1,0 -0.47506,9.50119,0,1 Is Object Local: 0 Scene Node ---------- Name: node2 Mesh: SceneObject ----------- GUID: 4da5a87f-3c1f-4287-afb0-cc674e49efa5 Type: MESH Primitive Type: TLST This mesh contains 0x3 vertex properties. Attribute: position Morph Target Index: 0x0 Data Type: FLT3 Data Size: 0x48 Data: -52.019 -51.0689 0 -52.019 51.0689 0 52.019 51.0689 0 52.019 -51.0689 0 -52.019 -51.0689 93.1116 52.019 -51.0689 93.1116 52.019 51.0689 93.1116 -52.019 51.0689 93.1116 -52.019 -51.0689 0 52.019 -51.0689 0 52.019 -51.0689 93.1116 -52.019 -51.0689 93.1116 52.019 -51.0689 0 52.019 51.0689 0 52.019 51.0689 93.1116 52.019 -51.0689 93.1116 52.019 51.0689 0 -52.019 51.0689 0 -52.019 51.0689 93.1116 52.019 51.0689 93.1116 -52.019 51.0689 0 -52.019 -51.0689 0 -52.019 -51.0689 93.1116 -52.019 51.0689 93.1116 Attribute: normal Morph Target Index: 0x0 Data Type: FLT3 Data Size: 0x48 Data: 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 1 0 0 1 0 0 1 0 0 1 0 -1 0 0 -1 0 0 -1 0 -0 -1 0 1 0 0 1 0 0 1 0 0 1 0 0 0 1 0 0 1 0 0 1 0 -0 1 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 Attribute: texcoord Morph Target Index: 0x0 Data Type: FLT2 Data Size: 0x30 Data: 1 0 1 1 0 1 0 0 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 This mesh contains 0x1 index arrays. Material Index: 0x0 Restart Index: 0x0 Data Type: I32 Data Size: 0x24 Data: 0x0 0x1 0x2 0x2 0x3 0x0 0x4 0x5 0x6 0x6 0x7 0x4 0x8 0x9 0xa 0xa 0xb 0x8 0xc 0xd 0xe 0xe 0xf 0xc 0x10 0x11 0x12 0x12 0x13 0x10 0x14 0x15 0x16 0x16 0x17 0x14 Visible: 1 Shadow: 1 Motion Blur: 1 Visible: 1 Shadow: 1 Motion Blur: 1 Transform Matrix: 1,0,0,0 0,1,0,0 0,0,1,0 132.079,9.50119,0,1 Is Object Local: 0
将此输出与Example.ogex的内容进行对比,我们可以看到导入是成功的。
参考引用