Ogre源代碼淺析——腳本及其解析(一)


Ogre的許多外部資源數據都有着相應的腳本格式,現例舉如下:

  • Material(材質):Ogre使用的是“大材質”的概念。狹義的“材質”概念往往是與“貼圖”等概念區分開的,比如在Lambert光照模型中,它一般用來指物體表面對模擬光的環境分量、漫反射分量和鏡面反射分量的作用的響應屬性。而在Ogre中,“材質”既包括了上述狹義的材質含義,又包括對要使用的貼圖的描述,還可以包括要使用的shader的相關信息。這些都是用Ogre的材質腳本來描述的。其實仔細思考一下就會發現,Ogre對材質概念的定義是恰當的,因為貼圖實際上是圖形學為了模擬光照效果的一種方法;而后期引入的shader其原意也是為了讓程序員能更自由、更精確地表達各種光照效果;再結合狹義的材質概念就可以明白,在Ogre中,所謂的“材質”實際上就是物體表面對光照的響應最終結果的整體描述,它綜合了現有圖形學的各種技術手段。材質腳本是以.material為后綴的文本文件。

 

  • Program:各種shader語言都有自身的定義和表達方式,但不論用哪種語言寫的shader,從調用者的角度看來,都需要知道shader的源文件在哪兒?是什么類型的?用的是什么版本?入口函數是什么?等方面的內容。Program腳本就負責回答這些問題,實際上program腳本是為Material腳本服務的,它們之間是“被引用者”和“引用者”之間的關系。Program腳本是以.program為后綴的文本文件。program腳本中定義的內容,有時也直接寫在Material文本文件里。

 

  • Particle:Ogre中的粒子系統的實例化即是以此腳本文件為基礎的。Particle腳本是以.particle為后綴的文本文件。

 

  • Compositor:游戲場景中的一些特殊光照效果,有時需要以不同的方式對場景進行多次渲染然后綜合處理;有時要在上次渲染的結果上作進一步的圖像處理,並把處理結果作為下一次處理的輸入數據;或者用多次渲染與重復處理組合起來行成一個渲染鏈,渲染鏈的輸出就是最后要的光照效果(比如常見的"Bloom"、"Flur"等)。這大概也是Ogre為什么把這種處理方法定義為Compositor的原因吧。底層的圖形渲染引擎如DirectX,OpenGL等只提供對場景數據的直接渲染的支持,對這種特殊光效的處理過程鮮有現成的接口可以調用,而Ogre為了實現這種功能所引入的Compositor的概念,就是用來實現以上的組合渲染過程的。Compositor腳本是以.compositor為后綴的文本文件。

 

  • Overlay:作為覆蓋層的Overlay的用途是多方面的。在渲染過程中,Overlay渲染隊列是被放在最后進行處理的,所以Overlay對象總是會覆蓋整個場景而顯示在最前面。Overlay常用作系統中二維對象或特殊對象的顯示,比如UI界面、游戲場景中的HUD(Head Up Display)等。Overlay中的contain對象可以相互疊加或嵌套。對場景中要顯示的Overlay對象內容及其相互關系,Ogre用overlay腳本對其進行描述。overlay腳本是以.overlay為后綴的文本文件。

 

  • Font:在Ogre中顯示的文字,都是以紋理的方式來處理的。其處理方法大致有兩種,第一種是把要顯示的文字內容處理成圖片,然后作為貼圖資源進行加載和處理;第二種方法是使用操作系統提供的或第三方提供的字庫,Ogre會根據字庫和用戶要顯示的內容,自動在內部渲染為紋理。如果要使用第二種方法,就要用Ogre提供的Font腳本。Font腳本是以.fontdef為后綴的文本文件。

 

  • 其他。Ogre還為腳本的擴展提供了可能。Ogre的某些插件使用的資源可以有自已的腳本格式,對這些腳本文件的解析同樣可以依靠Ogre原有的腳本解析機制。

    

      Ogre的資源管理器都是從ResourceManager派生出來的,而ResourceManager又以ScriptLoader為基類。有相應腳本的的資源管理器,在其構造函數中會調用ResourceGroupManager::_registerScriptLoader()函數,將資源管理器對象指針保存到ResourceGroupManager的mScriptLoaderOrderMap容器中。在之后的資源初始化過程中(見前面相關文章的討論),ResourceGroupManager::initialiseResourceGroup()會調用ResourceGroupManager::parseResourceGroupScripts()函數,對相應ResourceGroup中的腳本文件進行解析。新版本的Ogre引入了ScriptCompilerManager類后,以“.material”、“.program”、“.particle”和“.compositor”為后綴的文件都統一由ScriptCompilerManager類型對象統一進行管理和解析(而原來各自對應的資源管理器不再注冊到mScriptLoaderOrderMap中)。ScriptCompilerManager自身也是以ScriptLoader為基類的,Oger在初始化Root對象時,會創建一個ScriptCompilerManager對象,並通過ResourceGroupManager::_registerScriptLoader()函數,將它的指針保存到ResourceGroupManager的mScriptLoaderOrderMap容器中。因此,新版本的ResourceGroupManager的mScriptLoaderOrderMap中保存的第一個ScriptLoader對象,實際上就是ScriptCompilerManager的對象指針。

      ResourceGroupManager::parseResourceGroupScripts()函數的代碼如下:

 1     void ResourceGroupManager::parseResourceGroupScripts(ResourceGroup* grp)
 2     {
 3 
 4         LogManager::getSingleton().logMessage(
 5             "Parsing scripts for resource group " + grp->name);
 6 
 7         // Count up the number of scripts we have to parse
 8         typedef list<FileInfoListPtr>::type FileListList;
 9         typedef SharedPtr<FileListList> FileListListPtr;
10         typedef std::pair<ScriptLoader*, FileListListPtr> LoaderFileListPair;
11         typedef list<LoaderFileListPair>::type ScriptLoaderFileList;
12         ScriptLoaderFileList scriptLoaderFileList;
13         size_t scriptCount = 0;
14         // Iterate over script users in loading order and get streams
15         ScriptLoaderOrderMap::iterator oi;
16         for (oi = mScriptLoaderOrderMap.begin();
17             oi != mScriptLoaderOrderMap.end(); ++oi)
18         {
19             ScriptLoader* su = oi->second;
20             // MEMCATEGORY_GENERAL is the only category supported for SharedPtr
21             FileListListPtr fileListList(OGRE_NEW_T(FileListList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);
22 
23             // Get all the patterns and search them
24             const StringVector& patterns = su->getScriptPatterns();
25             for (StringVector::const_iterator p = patterns.begin(); p != patterns.end(); ++p)
26             {
27                 FileInfoListPtr fileList = findResourceFileInfo(grp->name, *p);
28                 scriptCount += fileList->size();
29                 fileListList->push_back(fileList);
30             }
31             scriptLoaderFileList.push_back(
32                 LoaderFileListPair(su, fileListList));
33         }
34         // Fire scripting event
35         fireResourceGroupScriptingStarted(grp->name, scriptCount);
36 
37         // Iterate over scripts and parse
38         // Note we respect original ordering
39         for (ScriptLoaderFileList::iterator slfli = scriptLoaderFileList.begin();
40             slfli != scriptLoaderFileList.end(); ++slfli)
41         {
42             ScriptLoader* su = slfli->first;
43             // Iterate over each list
44             for (FileListList::iterator flli = slfli->second->begin(); flli != slfli->second->end(); ++flli)
45             {
46                 // Iterate over each item in the list
47                 for (FileInfoList::iterator fii = (*flli)->begin(); fii != (*flli)->end(); ++fii)
48                 {
49                     bool skipScript = false;
50                     fireScriptStarted(fii->filename, skipScript);
51                     if(skipScript)
52                     {
53                         LogManager::getSingleton().logMessage(
54                             "Skipping script " + fii->filename);
55                     }
56                     else
57                     {
58                         LogManager::getSingleton().logMessage(
59                             "Parsing script " + fii->filename);
60                         DataStreamPtr stream = fii->archive->open(fii->filename);
61                         if (!stream.isNull())
62                         {
63                             if (mLoadingListener)
64                                 mLoadingListener->resourceStreamOpened(fii->filename, grp->name, 0, stream);
65 
66                             if(fii->archive->getType() == "FileSystem" && stream->size() <= 1024 * 1024)
67                             {
68                                 DataStreamPtr cachedCopy;
69                                 cachedCopy.bind(OGRE_NEW MemoryDataStream(stream->getName(), stream));
70                                 su->parseScript(cachedCopy, grp->name);
71                             }
72                             else
73                                 su->parseScript(stream, grp->name);
74                         }
75                     }
76                     fireScriptEnded(fii->filename, skipScript);
77                 }
78             }
79         }
80 
81         fireResourceGroupScriptingEnded(grp->name);
82         LogManager::getSingleton().logMessage(
83             "Finished parsing scripts for resource group " + grp->name);
84     }

        ScriptCompilerManager對象有一個StringVector mScriptPatterns成員,它里面主要保存着待解析的腳本文件類型信息,在ScriptCompilerManager被創建時,“.material”、“.program”、“.particle”、“.compositor”四個字符串會被保存在內。第27行的findResourceFileInfo()函數的代碼展開如下:

 1     FileInfoListPtr ResourceGroupManager::findResourceFileInfo(const String& groupName, 
 2         const String& pattern, bool dirs)
 3     {
 4         OGRE_LOCK_AUTO_MUTEX
 5         // MEMCATEGORY_GENERAL is the only category supported for SharedPtr
 6         FileInfoListPtr vec(OGRE_NEW_T(FileInfoList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);
 7 
 8         // Try to find in resource index first
 9         ResourceGroup* grp = getResourceGroup(groupName);
10         if (!grp)
11         {
12             OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, 
13                 "Cannot locate a resource group called '" + groupName + "'", 
14                 "ResourceGroupManager::findResourceFileInfo");
15         }
16 
17         OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME) // lock group mutex
18 
19             // Iterate over the archives
20             LocationList::iterator i, iend;
21         iend = grp->locationList.end();
22         for (i = grp->locationList.begin(); i != iend; ++i)
23         {
24             FileInfoListPtr lst = (*i)->archive->findFileInfo(pattern, (*i)->recursive, dirs);
25             vec->insert(vec->end(), lst->begin(), lst->end());
26         }
27 
28         return vec;
29     }

       以FileSystemArchive為例,在findResourceFileInfo()函數中的第24行將會形成如下調用鏈:

1. FileSystemArchive::findFileInfo

                  ||
                  \/

2. FileSystemArchive::findFiles(const String& pattern, bool recursive, 
        bool dirs, StringVector* simpleList, FileInfoList* detailList)

       其中的調用“結點2”——findFiles()函數展開后為:

 1     void FileSystemArchive::findFiles(const String& pattern, bool recursive, 
 2         bool dirs, StringVector* simpleList, FileInfoList* detailList) const
 3     {
 4         intptr_t lHandle, res;
 5         struct _finddata_t tagData;
 6 
 7         // pattern can contain a directory name, separate it from mask
 8         size_t pos1 = pattern.rfind ('/');
 9         size_t pos2 = pattern.rfind ('\\');
10         if (pos1 == pattern.npos || ((pos2 != pattern.npos) && (pos1 < pos2)))
11             pos1 = pos2;
12         String directory;
13         if (pos1 != pattern.npos)
14             directory = pattern.substr (0, pos1 + 1);
15 
16         String full_pattern = concatenate_path(mName, pattern);
17 
18         lHandle = _findfirst(full_pattern.c_str(), &tagData);
19         res = 0;
20         while (lHandle != -1 && res != -1)
21         {
22             if ((dirs == ((tagData.attrib & _A_SUBDIR) != 0)) &&
23                 ( !msIgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0 ) &&
24                 (!dirs || !is_reserved_dir (tagData.name)))
25             {
26                 if (simpleList)
27                 {
28                     simpleList->push_back(directory + tagData.name);
29                 }
30                 else if (detailList)
31                 {
32                     FileInfo fi;
33                     fi.archive = this;
34                     fi.filename = directory + tagData.name;
35                     fi.basename = tagData.name;
36                     fi.path = directory;
37                     fi.compressedSize = tagData.size;
38                     fi.uncompressedSize = tagData.size;
39                     detailList->push_back(fi);
40                 }
41             }
42             res = _findnext( lHandle, &tagData );
43         }
44         // Close if we found any files
45         if(lHandle != -1)
46             _findclose(lHandle);
47 
48         // Now find directories
49         if (recursive)
50         {
51             String base_dir = mName;
52             if (!directory.empty ())
53             {
54                 base_dir = concatenate_path(mName, directory);
55                 // Remove the last '/'
56                 base_dir.erase (base_dir.length () - 1);
57             }
58             base_dir.append ("/*");
59 
60             // Remove directory name from pattern
61             String mask ("/");
62             if (pos1 != pattern.npos)
63                 mask.append (pattern.substr (pos1 + 1));
64             else
65                 mask.append (pattern);
66 
67             lHandle = _findfirst(base_dir.c_str (), &tagData);
68             res = 0;
69             while (lHandle != -1 && res != -1)
70             {
71                 if ((tagData.attrib & _A_SUBDIR) &&
72                     ( !msIgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0 ) &&
73                     !is_reserved_dir (tagData.name))
74                 {
75                     // recurse
76                     base_dir = directory;
77                     base_dir.append (tagData.name).append (mask);
78                     findFiles(base_dir, recursive, dirs, simpleList, detailList);
79                 }
80                 res = _findnext( lHandle, &tagData );
81             }
82             // Close if we found any files
83             if(lHandle != -1)
84                 _findclose(lHandle);
85         }
86     }

       其作用是在本Archive指定的目錄下搜索以pattern(findFiles的第一個參數)為后綴名的文件,並將滿足條件的所有文件的相關信息(包括文件名、相應Archive對象指針、路徑名及是否為壓縮文件等)添加到名為detailList的FileInfoList中去並返回給調用函數。而ResourceGroupManager::findResourceFileInfo()函數會對保存在本group的locationList中的所有Archive對象進行如上操作(findResourceFileInfo()函數22-26行)。

       因此,回到最開始的ResourceGroupManager::parseResourceGroupScripts()函數,可以知道第16-33行的作用是:對指定的resourceGroup進行搜索,並將保存在locationList中的所有Archive對象對應目錄下,后綴名為資源管理器指定的文件類型(由mScriptPatterns來指定,對ScriptCompilerManager來說mScriptPattern中的值為:“.material”、“.program”、“.particle”、“.compositor”)的文件的相關信息,以文件為單位保存到scriptLoaderFileList(在第12行中定義)列表中。

  ResourceGroupManager::parseResourceGroupScripts()函數的第39-79行,則實現了對保存在scriptLoaderFileList列表中對應的腳本文件的解析。其中最核心的邏輯是DataStreamPtr stream = fii->archive->open(fii->filename)(60行)和su->parseScript()(70、73行)。第60行用於打開待解析的腳本文件,而su->parseScript()函數則會調用相應資源管理器的parseScript(DataStreamPtr& stream, const String& groupName)函數。以ScriptCompilerManager為例,其代碼為:
 1     void ScriptCompilerManager::parseScript(DataStreamPtr& stream, const String& groupName)
 2     {
 3 #if OGRE_THREAD_SUPPORT
 4         // check we have an instance for this thread (should always have one for main thread)
 5         if (!OGRE_THREAD_POINTER_GET(mScriptCompiler))
 6         {
 7             // create a new instance for this thread - will get deleted when
 8             // the thread dies
 9             OGRE_THREAD_POINTER_SET(mScriptCompiler, OGRE_NEW ScriptCompiler());
10         }
11 #endif
12         // Set the listener on the compiler before we continue
13         {
14             OGRE_LOCK_AUTO_MUTEX
15             OGRE_THREAD_POINTER_GET(mScriptCompiler)->setListener(mListener);
16         }
17         OGRE_THREAD_POINTER_GET(mScriptCompiler)->compile(stream->getAsString(), stream->getName(), groupName);
18     }
 
        

       其中,stream->getAsString()用來將腳本文件中的內容以字符串的形式加載到內存;stream->getName()用來返回待解析腳本文件的文件名。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM