ThemeFestival na wybiegu, czyli modele

Wszystko, co chcemy w grze zobaczyć, z czym ma następować interakcja trzeba jakoś narysować. Natomiast wszystko, co chcielibyśmy narysować składa się z trójkątów – to dla tego prymitywu graficznego dostępne jest od lat sprzętowe wsparcie rysowania, wypełniania kolorem i teksturowania.

Bez obaw, nie porywam się na ręczne tworzenie całej graficznej zawartości gry i potem ręczne przekazywanie każdego trójkąta do rysowania. Skorzystamy z dobrodziejstw biblioteki XNA, która daje możliwość ładowania, wstępnej obróbki oraz wyświetlania siatek ładowanych z plików. Te natomiast można zrobić samemu w programach typu Maya lub Blender, lub poszukać w sieci darmowych wersji.

Model

Zajmijmy się ideą samego modelu. W pliku ładowanym z dysku muszą znaleźć się informacje o położeniu wierzchołków każdego trójkąta, informacje o indeksach (czyli kolejności, w jakiej wierzchołki muszą być rysowane, aby model wyglądał sensownie), koordynatach tekstur, aby dobrze nałożyć na siatkę obraz imitujący szczegóły. Formatów modeli jest mnóstwo, od bardzo ogólnego OBJ, przez .x (specyficzny dla DirectX i właśnie XNA) aż do FBX stworzonego przez Adobe. Wszystkie różnią się ilością i formatem przechowywanych informacji, ale wszystkie zawierają informacje o wierzchołkach, indeksach, teksturach, kościach itd.

Content

Po stworzeniu projektu gry (Windows Game) w XNA tworzone są tak naprawdę dwa projekty, drugim z nich jest Content. Jest to zbiór tzw. assetów – modeli, tekstur, dźwięków, ikon, czcionek, używanych przez grę.Po dodaniu do tego projektu plików powinny (z automatu) zostać do nich dobrane odpowiednie loader’y (klasy odpowiedzialne za poprawne sparsowanie pliku i dostosowanie go do wewnętrznego formatu XNA).

Z kodu programu dostęp do tych danych mamy od ręki przez wywołanie metody

Content.Load<asset_type>(asset_name);

gdzie, oczywiście, asset_type to typ pobieranych danych (Model, Texture2D, SpriteFont – czcionka), a asset_name to nazwa zasobu.

Budowa modelu

Model składa się z siatek. Każda z siatek ma przypisaną tzw. „kość”, czyli macierz transformacji względem rodzica. Najłatwiej wyobrazić to sobie na dosłownym przykładzie – ludzkiego szkieletu. Niech główną „kością” będzie kręgosłup, wtedy ramię jest odpowiednio obrócone względem kręgosłupa. Dalej, przedramię jest przesunięte i obrócone względem ramienia itd. Gdy zmodyfikujemy przekształcenie dla ramienia (ruszymy ręką), wszystkie dalsze elementy (przedramię, nadgarstek i dłoń) obrócą się i przesuną odpowiednio do zaaplikowanej transformacji.

XNA dostarcza narzędzi do przechowywania modeli, klasa za to odpowiedzialna to (o dziwo) Model. Opakujmy ją aby uzyskać łatwiejszy dostęp do pewnych właściwości.

class MyModel
{
    private Matrix[] bones;
    private Model model;

    public Model Model
    {
        get { return model; }
        set
        {
            model = value;
            bones = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(bones);
        }
    }

    public MyModel(Model m)
    {
        Model = m;
    }

    public void Draw(Matrix world, Matrix view, Matrix projection)
    {
        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();

                effect.View = view;
                effect.Projection = projection;
                effect.World = bones[mesh.ParentBone.Index] * world;
            }
            mesh.Draw();
        }
    }
}

Powyższy wrapper daje dwie możliwości – opakowuje metodę Draw() (w reszcie kodu wystarczy wywołać MyModel.Draw() z odpowiednimi macierzami) oraz robi porządek z kośćmi. Przeanalizujmy setter z linii 9-14

bones = new Matrix[model.Bones.Count];

zostaje utworzona tablica na kości (macierze transformacji dla odpowiedniej części modelu)

model.CopyAbsoluteBoneTransformTo(bones);

Tablica zostaje wypełniona absolutnymi transformacjami dla każdej części. Co w tym przypadku oznacza „absolutna transformacja”? Jest to transformacja uwzględniająca transformację rodzica. Czyli, odwołując się do przykładu z kośćmi ręki, macierz nadgarstka będzie uwzględniała obrót kości ramienia oraz przedramienia. W ten sposób w metodzie Draw() macierz świata dla każdej części modelu (czyli przekształcenie z jej układu lokalnego do globalnego układu świata) to iloczyn odpowiedniej „absolutnej transformacji” oraz globalnej macierzy świata dla sceny. Może się to na początku wydawać skomplikowane, ale jest w tym jakaś logika 😉

Dodatkowe właściwości

Model jest bardzo użyteczną klasą (zwłaszcza, że nie musimy się martwić o teksturowanie itp.), jednak na powyższej użyteczności się nie kończy. W następnym wpisie dotyczącym modeli zajmiemy się integracją Gracza z nimi – zaznaczaniem. Póki co – screen z przykładowym statkiem kosmicznym znalezionym gdzieś w Sieci.

Model

Be Sociable, Share!
czoper opublikowano dnia 2010-9-25 Kategoria: Daj się poznać, Programowanie, Theme Festival | Tagi:, ,

Jedna odpowiedź Zostaw komentarz

    Zostaw odpowiedź

    (Ctrl + Enter)