與Ogre共舞:第四步,乳酪方塊的協奏曲
在前三步中,從安裝Ogre開始,到建立視窗並在Ogre中放了一個彩色三角片,我們已經接觸了幾個Ogre中重要的元件:Ogre根本的Root,系統繪圖API介面的RenderSystem,掌控物件配置的SceneManager,隸屬於ResourceManager、產生材質的MaterialManager,以及建立自訂幾何構成物件的ManualObject。在這步中,我們要用SceneManager在場景中擺設12個乳酪方塊,認識SceneManager如何控制元件的位置與姿態。當然,還是只用程式碼來完成一切唷。
首先,我們需要12個乳酪方塊,前面學會用ManualObject來產生一個三角片,當然也可以產生一個方塊囉!嗯~沒錯,產生立方體的程式不難,不過今天的重點不是ManualObject,所以暫時把它忘了吧!今天要登場的主角是Entity,
中文意思是「實體」,表示在場景中的物件其幾何構成的實體…真難解釋,還是先叫Entity吧。在Ogre中,多邊形物件(mesh)都由ResourceManager管理與載入。載入以後,Ogre還不能描繪它,必須由SceneManager產生該Mesh物件的Entity,才能上場。
// create a prefab cube Entity *ent = m_scene_mgr->createEntity( "cube0", SceneManager::PT_CUBE ); ent->setMaterialName( "CheeseCube" ); m_scene_mgr->getRootSceneNode()->attachObject( ent );
上段程式碼的第1行,Entity是由SceneManager產生的,如同ManualObject一樣。”cube0”是產生Entity時給定的名字,在整個場景中必須是獨一無二的,不然Ogre會不爽給你看。在大多數的場合,createEntity()的第二個參數是Mesh物件的名稱,用來產生Mesh物件的Entity。不過SceneManager提供另外一種產生Entity的方式:使用預先製好的幾何模型,平面(PT_PLANE)、立方體(PT_CUBE)與球體(PT_SPHERE)。所以第1行就是產生一個名叫”cube0”的立方體(的Entity,你知道的)。第2行,我們透過ent這個指標來設定剛剛被產生Entity的材質。這個材質的名稱叫做”CheeseCube”,後面會說明它的產生方式。最後,和先前一樣,把Entity加入RootSceneNode,乳酪方塊就可以被Ogre畫出來囉。

Entity屬於MovableObject,看字面就知道他是個可以亂動的東西。SceneManager提供了七種可以亂動的物件:Camera, Light, Entity, ManualObject, BillboardChain (RibbonTrail), ParticleSystem。那麼,物件想放在場景中,要怎樣「擺」呢?SceneManager利用了一個樹狀節點的結構,來描述物件彼此間的關係,以及紀錄在場景中的位置與姿態。每一個節點,可以裝上(attach)一或多個「可以亂動的物件」,而物件的位置與姿態,就由該節點表示。換句話說,物件本身並不知道自己在哪裡,只有查詢節點才知道它的位置。舉例來說,你(SceneManager)把一個杯子(Mesh)放在房間(RootSceneNode)的桌上(SceneNode),如果我想知道杯子在哪裡,問杯子一定沒有答案。杯子會知道自己在哪裡嗎?不會吧!只有你會記得它在哪裡。進一步來說,我問杯子在哪裡,你的回答會是:在房間(RootSceneNode)裡的桌子(SceneNode)上。如果桌子被搬動,那麼杯子在房間裡的位置就會跟著改變。這就是SceneNode簡單的概念。
我們先來看個改變位置的小例子。SceneNode提供兩種方法設定在場景中的位置:translate()跟setPosition()。translate()是設定相對的x,y,z位移量,預設是根據其父節點的座標系來平移。而setPosition()則是直接設定節點位置,只能根據其父節點的座標系。這個例子中,兩行敘述會讓RootSceneNode移動到(200, 200, 0)的位置:
SceneNode *root_node = m_scene_mgr->getRootSceneNode(); root_node->translate( Vector3( 200, 0, 0 ) ); root_node->translate( Vector3( 0, 200, 0 ) );
這等同於下面這行敘述,直接把RootSceneNode的位置設成(200, 200, 0):
root_node->setPosition( Vector3( 200, 200, 0 ) );
如果上述三行敘述一起按照順序執行,最後RootSceneNode的位置會在哪?答案是(200, 200, 0),答對了嗎?又,如果先執行setPosition()的敘述,再執行上述的兩個translate()敘述,RootSceneNode的位置會在哪?答案是(400, 400, 0),不意外吧!
接著,我們考慮旋轉的情形。在3D場景中,旋轉的操作往往會帶來許多意外的混亂。讓我們先弄清楚幾個重點:
1. 不論怎麼旋轉某節點,它的local space永遠不會改變。
2. 對某節點一起做旋轉與平移的操作,具有不可交換性。也就是旋轉與平移操作的順序會影響結果。
有點抽象,不過還好,看看小例子吧!(暗紅線標示世界座標系的x水平軸與y鉛直軸,水藍色箭頭表示平移操作,橘色箭頭表示旋轉操作)
root_node->translate( Vector3( 200, 0, 0 ), SceneNode::TS_LOCAL ); root_node->rotate( Vector3::UNIT_Z, Degree( 30 ), SceneNode::TS_LOCAL ); root_node->translate( Vector3( 200, 0, 0 ), SceneNode::TS_LOCAL );

root_node->rotate( Vector3::UNIT_Z, Degree( 30 ), SceneNode::TS_LOCAL ); root_node->translate( Vector3( 200, 0, 0 ), SceneNode::TS_LOCAL ); root_node->translate( Vector3( 200, 0, 0 ), SceneNode::TS_LOCAL );

上面兩組程式中,旋轉是以+z為轉軸,轉動30度。要注意的是,所有的操作都參考local space。如果換成SceneNode::TS_PARENT,表示平移或旋轉的操作是參考父節點座標系(parent space)。這就當成作業吧!在嘗試之前,不妨先推測看看會得到什麼樣的結果。
講了一大堆,其他的乳酪方塊呢?沒問題!我們打算把乳酪方塊繞著原點排成一圈,用一個for迴圈來完成。在節點的架構上,先由一個節點產生12個子節點,依序設定好位置後,再一一把Entity掛上去。另一個問題則是,每個Entity都需要一個不會重複的名字,所以就用索引值分別產生"cube0″, “cube1″, “cube2″,…這樣的名字。
// create a child scene node for handling 12 scene nodes
SceneNode *center_node = m_scene_mgr->getRootSceneNode()->createChildSceneNode();
for ( int idx = 0; idx < 12; ++idx )
{
// generate the unique entity name
ostringstream entity_name;
entity_name << "cube" << idx;
// create an entity for the cheese cube
Entity *ent = m_scene_mgr->createEntity( entity_name.str(), SceneManager::PT_CUBE );
ent->setMaterialName( "CheeseCube" );
// set the appropriate position and pose for each node of the cheese cube
SceneNode *scene_node = center_node->createChildSceneNode();
scene_node->rotate( Vector3::UNIT_Z, Degree( 30 * idx ), SceneNode::TS_LOCAL );
scene_node->translate( Vector3( 300, 0, 0 ), SceneNode::TS_LOCAL );
// finally, attach the entity to the node
scene_node->attachObject( ent );
}
在第2行先產生一個新的子節點,因為如果要移動這12個乳酪方塊,只要移動該節點就好囉!什麼?前面不是都用RootSceneNode嗎?也是可以啦,不過等物件一多,你就會知道手忙腳亂在哪裡了。排出這12個乳酪方塊的重點是第15,16行,請參考上面的小例子就可以明白其結果。而從前面使用到現在的"CheeseCube"材質,它的身份是:
MaterialPtr mtl; mtl = MaterialManager::getSingleton().create( "CheeseCube", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); mtl->setLightingEnabled( true ); mtl->setAmbient( ColourValue( 0.87, 0.77, 0.46 ) ); mtl->setDiffuse( ColourValue( 0.87, 0.77, 0.46 ) );
ColorValue裡填的分別是代表乳酪色的R,G,B值(範圍在0~1之間)。為了讓乳酪方塊看起來有「方塊」的感覺(因為我們現在還沒有去改變視角),所以範例中偷偷加了一個平行光源,讓乳酪方塊的不同面有亮暗之分。燈光與材質間的關係,以及他們錯綜複雜的參數設定,往後有機會碰到的。

在這個協奏曲中,主要的重點在Entity的使用,以及SceneNode的意義與操作。而旋轉與平移操作所參考的座標系統,會影響最後的結果,這部份也是需要多花點心思去理解的。最後,來個挑戰題吧!如同上面12個乳酪方塊的排列方式,但希望節點的設計是「串列」的方式,也就是每個節點(除了最後一個)都只有一個子節點跟在屁股後面,該怎麼做呢?不要先偷看答案喔!
[下載] 範例程式 (VC2008 專案)
ogre_tutorial_4.zip 4.9 Kb
從這個範例開始,程式架構有一些變動。範例採用Ogre的sample裡的"ExampleApplication.h"的方式,將基本的部份定義為一個基礎類別,產生場景的部份宣告為純虛函數(pure virtual function),由範例的需求來繼承與實做。如果class, pure virtual function看起來有點吃力,趁現在快去翻翻書吧!不然Ogre可是不會手下留情的喔!
[解答] 數值是大約值,沒有經過計算
ogre_tutorial_4_ans.zip 0.6kb
稍微挑個筆誤XD
>這等同於下面這行敘述,直接把RootSceneNode的位置設成(200, 200, 0):
>root_node->setPosition( Vector3( 0, 200, 0 ) );
這裡應該是
root_node->setPosition( Vector3( 200, 200, 0 ) );
這樣吧
Ogre的Entity雖然很方便
可是如果不懂他那個是從哪邊load的話會很亂
Resource manager那些鬼東西讓我看了兩三天才大概搞懂..
之前都不知道mesh跟texture那些是從邊生出來的Orz
話說最近好不容易把Ogre稍微弄懂一點
結果又卡死在OIS上面..
一個不小心就把滑鼠跟鍵盤的控制權丟掉了Orz
第一次覺得寫程式這麼讓人心驚膽顫(炸
迴響 由 艾薩克 | 三月 26, 2009
謝謝提醒,已經修正了!
關於ResourceManager的部份,像在遊戲或是場景編輯的應用,應該是非常吃重的。目前不提,是因為重點擺在如何使用Ogre取代直接使用OpenGL API上。往後有機會再來介紹ResourceManager囉~個人是希望能推廣Ogre的使用,讓初接處電腦圖學得使用者,能有一個快速穩定的環境來學習理論基礎,而不需要去拼一堆API。
迴響 由 chia0418 | 三月 26, 2009
Ogre的場景設置方法挺直觀的
而且最方便的就是他那個Node的設定
一次可以改動一堆物件好棒阿~~~~
物件transform的方法也很直觀
不像opengl因為matrix的關係還要顛倒順序
對初學者來說應該是挺容易上手的吧
(就算不是初學者還是覺得好棒XD)
總之教學辛苦了,期待下集(?)
迴響 由 艾薩克 | 三月 27, 2009
沒錯,有用API玩過座標轉換的就知道難過在哪裡,而Ogre的SceneNode設計真的是讓物件的操作非常直觀,至少不用再去擔心動了腿而腳卻沒跟上的問題XD
迴響 由 chia0418 | 三月 28, 2009
谢谢 辛苦了 很期待你后面的作品 呵呵
迴響 由 yang | 五月 6, 2009
請問一下 Entity 所加入的物體是 OGRE SDK 內建的
還是要用額外的程式去製作 ?
還有 OGRE 有沒有支援製作 3D 圖形 ?
他主要的功能是 ?
本然看了許多文章還是不太清楚他的主要用途…
突然之間問了這麼多問題,造成您的困擾十分抱歉…
如果方便的話,希望能夠回答一下,解決小的疑惑,謝謝!!
迴響 由 路人甲 | 四月 11, 2010
OGRE是一個繪圖引擎,最主要是提供場景管理、動畫操作、繪圖系統(DirectX, OpenGL)的包裝等等,並沒有提供製作3D模型的功能。當然啦~你可以利用OGRE開發一個像是3D Max或是Maya之類的建模軟體或動畫製作軟體。所以,Entity能處理的模型,大致有兩種方法加入:1. 由其他軟體製作後,轉換為OGRE的3D模型格式後讀入;2. 由程式指令一行一行建立。第一種方法是比較正式的作法,透過OGRE的資源管理系統載入模型。第二種方法則是類似用OpenGL指令那樣,一步一步的建立自行定義的模型,在OGRE裡是由ManualObject物件來處理(請參考教學第三步)。本來是有預計要寫一個比較完整的ManualObject物件範例的,但礙於時間,只能之後再來寫囉~
迴響 由 chia0418 | 四月 12, 2010
請問一下 繪圖系統(DirectX, OpenGL)的包裝 指的是?
前面兩種我都有用過但沒看過最後一種XD
方便的話可以解釋一下嗎? 謝謝!
迴響 由 路人甲 | 五月 1, 2010