Chia´s Small Shop

Chia小鋪,陳列隨手的文字

與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畫出來囉。

乳酪方塊x1

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 );

TRT範例

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 );

RTT範例

上面兩組程式中,旋轉是以+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之間)。為了讓乳酪方塊看起來有「方塊」的感覺(因為我們現在還沒有去改變視角),所以範例中偷偷加了一個平行光源,讓乳酪方塊的不同面有亮暗之分。燈光與材質間的關係,以及他們錯綜複雜的參數設定,往後有機會碰到的。

乳酪方塊x12

在這個協奏曲中,主要的重點在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

三月 23, 2009 - 發文者為 chia0418 | Ogre | , | 5 則留言

5 則留言 »

  1. 稍微挑個筆誤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
    第一次覺得寫程式這麼讓人心驚膽顫(炸

    Comment 由 艾薩克 | 三月 26, 2009

  2. 謝謝提醒,已經修正了!

    關於ResourceManager的部份,像在遊戲或是場景編輯的應用,應該是非常吃重的。目前不提,是因為重點擺在如何使用Ogre取代直接使用OpenGL API上。往後有機會再來介紹ResourceManager囉~個人是希望能推廣Ogre的使用,讓初接處電腦圖學得使用者,能有一個快速穩定的環境來學習理論基礎,而不需要去拼一堆API。

    Comment 由 chia0418 | 三月 26, 2009

  3. Ogre的場景設置方法挺直觀的
    而且最方便的就是他那個Node的設定
    一次可以改動一堆物件好棒阿~~~~
    物件transform的方法也很直觀
    不像opengl因為matrix的關係還要顛倒順序
    對初學者來說應該是挺容易上手的吧
    (就算不是初學者還是覺得好棒XD)
    總之教學辛苦了,期待下集(?)

    Comment 由 艾薩克 | 三月 27, 2009

  4. 沒錯,有用API玩過座標轉換的就知道難過在哪裡,而Ogre的SceneNode設計真的是讓物件的操作非常直觀,至少不用再去擔心動了腿而腳卻沒跟上的問題XD

    Comment 由 chia0418 | 三月 28, 2009

  5. 谢谢 辛苦了 很期待你后面的作品 呵呵

    Comment 由 yang | 五月 6, 2009


留言