Grafika 3D
 
  Zarejestruj się
::  Newsy  ::  Pliki  ::  Twoje Konto  ::  Forum  ::
Menu
· Strona główna
· Forum
· Linki
· Lista u?ytkowników
· O nas...
· Pliki
· Statystyki
· Twoje Konto
Tutoriale
· API
· Matematyka
· Teoria
· Direct3D
· OpenGL
· Techniki
Kto Jest Online
Aktualnie jest 37 gość(ci) i 0 użytkownik(ów) online.

Jesteś anonimowym użytkownikiem. Możesz się zarejestrować za darmo klikając tutaj
Tutoriale - Techniki - glXPorter

Otóż i spotykamy się po raz kolejny omawiając eksportowanie z 3D Studio MAX. Tak jak obiecałem ostatnio dzisiaj już koniec teorii i będzie cos bardziej realnego (gotowego do używania), czyli eksporter. W zasadzie napisanie tego tutorialu jest dość problematyczne (nawet nie wiecie jak - podchodziłem do niego ze trzy razy :), jako że eksporter ma trochę kodu i ciężko by było wszystko omówić jakoś bardzo dokładnie (najchętniej zamknąłbym kod w dll'u i omówił tylko wczytywanie :P). Dlatego oczywiście kod jest opisany (znaczy w części, która tego wymaga ;). Tutaj skupię się na tym jak go używać w Max'ie (no i oczywiście pokażę jeszcze parę sztuczek związanych z pracą z Max'em :)).

Po pierwsze chciałem zwrócić uwagę na ograniczenia jakie narzuca eksporter (a właściwie na rzeczy, których nie obsługuje :). No więc po pierwsze nie można eksportować animacji. To jest dość oczywiste, jako że celem tego eksportera było zapewnienie możliwości eksportu obiektów statycznych do późniejszego wykorzystania w dziale OpenGL i samodzielnej ich animacji w 3D. No i tutaj oczywiście od razu widać drugą sprawę - otóż format jest przystosowany do używania w OpenGL'u i bez odpowiedniej konwersji nie będzie można używać go w Direct3D (bardzo prostej konwersji :). Eksporter wspiera tylko (aż?? :P) materiały typu Standard i Multi/Sub-Object. Ponieważ w OpenGL używamy dotychczas tylko plików *.bmp i *tga, toteż aby widzieć jakąś teksturę na ekranie (po wczytaniu już do naszego demka) należy oryginalną teksturę (np. jpg) przekonwertować na *.bmp (poza Max'em oczywiście - tam można używać dowolnych). Eksporter automatycznie zmienia rozszerzenia wszystkich tekstur (poza tga - te zostaną) na bmp (tak je zapisuje w pliku). Kolejna rzecz dotycząca tekstur to oczywiście to, że wyeksportowane zostaną tylko tekstury nałożone na kanały diffuse i bump (już niedługo będzie nam to potrzebne :D). Oczywiście nie oznacza to, że obiekt w Max'ie nie może mieć tektury na innych kanałach - oczywiście że może, ale eksporter nie uwzględni tych tekstur. Dlaczego? Ponieważ i tak nie potrafilibyśmy ich obsługiwać (na razie :). Jeśli z kolei chodzi o geometrię, to nie ma wsparcia dla obiektów zgrupowanych - eksporter nie rozdziela ich w żaden sposób. To wszystkie znane mi ograniczenia, ale oczywiście może ich być więcej - łącznie z jakimiś drobnymi błędami (proszę oczywiście o kontakt w takich wypadkach - postaram się naprawić :), jako że ja nie jestem grafikiem i w Max'ie to potrafię bardzo niewiele, więc siłą rzeczy nie miałem złożonych obiektów do testowania. Oczywiście jeszcze nic straconego. To jest wersja 1.0 eksportera i jeśli zajdzie taka potrzeba to eksporter zostanie poszerzony o nowe możliwości, ale bez przesady znowu - nie zrobię z tego formatu X z Direct3D (chociaż kto wie... :).

No ale dobrze. Skoro już wiemy czego glXPorter nie potrafi, to czas się zająć tym co potrafi :). A cóż to takiego jest? W zasadzie nie jest tego dużo, a mimo to efekty są wspaniałe dla naszych oczu ;)). Można bowiem eksportować geometrię statyczną (znaczy na scenie może być animacja, ale i tak wszystko zostanie pobrane z klatki 0). Oczywiście nie ma wymagań, aby materiał był nałożony. Wszystkie obiekty są eksportowane z normalnymi i współrzędnymi tekstury (jeśli włączone zostało generowanie tychże współrzędnych w Max'ie ;). Eksportowane są również materiały i tekstury (o czym już wspomniałem wyżej). Również nie ma przymusu używania tekstur (albo można użyć jednej na przykład). I to w zasadzie wszystko. Zresztą takie było założenie, więc nic dziwnego :).

Zacznę od omówienia formatu glX (który de facto jest bardzo prosty), bo pomoże nam to zrozumieć wszystkie klasy, z jakich korzystam. Klasy te znajdują się w pliku Objects.h (a implementacja ich metod w Objects.cpp). W całym projekcie używam klas biblioteki STL (Standard Template Library) C++, a właściwie klasy vector. Nie będę oczywiście jej omawiał, bo to nie jest kurs C++. To zresztą oznacza, że osoby znające C, ale nie C++ mogą mieć problemy ze zrozumieniem kodu.

Plik glX zawiera oczywiście nagłówek definiujący rodzaj pliku (czyli znaczki GLX), następnie w pliku znajduje się wersja (nie będę tutaj podawał, że jest to powiedzmy 32 bitowa liczba, bo przecież cały kod eksportera, odczytu formatu i jego wyświetlenia znajdziecie w Plikach). Następnie znajdują się dane geometrii. Najpierw umieszczana jest w pliku ilość obiektów, które zostają wyeksportowane, a następnie same obiekty. Obiekt geometryczny (czyli siatka) jest reprezentowany przez klasę glXMesh. Klasa ta zawiera nazwę obiektu, index do tablicy materiałów (materiał używany na tym obiekcie), oraz wektor tzw. pod-obiektów. Czym jest pod-obiekt? No cóż jest w zasadzie jakąś częścią geometrii obiektu, która używa innego materiału niż reszta tego obiektu. Jest to powiązane z użyciem materiału Mulit-Sub-Object w Max'ie. Jeśli użyjemy takiego materiału, to możemy każdemu trójkątowi, bądź grupie trójkątów, siatki przypisać osobny materiał. My musimy jakoś to później narysować, więc dzielimy sobie obiekt na części używające takiego samego materiału. Tutaj należy jeszcze zauważyć, że obiekt używający jednego materiału typu Standard też będzie zawierał pod-obiekt. Ponieważ siatka jest zbiorem pod-obiektów, więc może wystąpić szczególny przypadek, że składa się z jednego pod-obiektu. To jest właśnie taki przypadek - wymuszam niejako istnienie tego pod-obiektu (tak jak wymuszam istnienie pod-matriału, ale o tym za chwilę). Zapis tej klasy do pliku polega na zapisie nazwy i indexu materiału, a następnie kolejno wszystkich tablic w sposób - rozmiar tablicy, dane tablicy (kolejność zapisu całej klasy jest inna, ale to zobaczycie sobie w metodzie WriteToFile). W pliku (Objects.h/cpp) znajduje się jeszcze jedna klasa związana z geometrią - CSmoothingGroup, ale nie jest ona wykorzystywana w żaden sposób w pliku glX. Ona służy jedynie do konwersji obiektów z Max'a na format glX. Do czego więc dokładnie się w tym procesie przydaje i co definiuje? Otóż większość obiektów geometrycznych (np. kostka) składa się z kilku części, które możemy odróżnić gołym okiem. W kostce te części to poszczególne jej ściany. Normalnie nie zastanawiamy się dlaczego wyróżniamy sześć części kostki (gdy tym czasem kula ma jedną część), ale ponieważ w grafice nic nie jest normalne, więc musimy przeprowadzić to jakże skomplikowane rozumowanie :PPP. Otóż to że wyróżniamy sześć części spowodowane jest tym, że widzimy dokładnie krawędzie sześcianu. Jak? Poszczególne ściany mają nieco inne kolory. No i to jest właśnie sedno. Wiemy doskonale (jak nie wiemy, to proponuję się udać do lekcji o światłach :), że efekt ten jest spowodowany różnymi kątami pod jakimi padają promienie światła na ściany kostki, a co za tym idzie rożnymi kątami odbicia tych promieni. W grafice efekt taki uzyskujemy poprzez przypisanie innych normalnych dla każdej ze ścian. No i właśnie do tego służy klasa CSmoothingGroup. Zawiera ona te trójkąty, które należą do tej samej grupy cieniowania (które jak wiemy są zdefiniowane w Max'ie). Czyli nasza kostka będzie się składała z sześciu grup - po jednej dla każdej ściany. Po co takie rozróżnienie? Ano po to, że Max przechowuje dany wierzchołek w siatce tylko raz - niezależnie od tego ile ma on normalnych. My jednak nie możemy tak postąpić - przecież nie możemy wyrenderować jednego wierzchołka z więcej niż jedną normalną. Dlatego też musimy powielić te wierzchołki, które mają więcej niż jedna normalną (nie powielając pozostałych). A jakie to są wierzchołki? Te, które należą do różnych grup, czyli tworzą trójkąty należące do innych grup (najczęściej wierzchołki narożne i tworzące wszelkie ostre krawędzie). Mam nadzieję, że nie namieszałem za bardzo i jest to przynajmniej trochę zrozumiałe, bo bez zrozumienia tej kwestii można mieć poważne problemy ze zrozumieniem zasady działania tego eksportera :).

Kolejną rzeczą w pliku glX jest ilość materiałów. Materiały są zapisane po prostu jako kolejne elementy wektora materiałów - klasy glXMaterial. Tutaj mamy analogię do siatki - tam był pod-obiekt tutaj pod-materiał. Dlaczego? Ano po to, aby wszystko działało jak należy i było proste w obsłudze. Klasa ta (glXMaterial) definiuje niejako ojcowski materiał dla pod-materiałów. No i znowu, tak jak w przypadku siatki, wymuszam istnienie pod-materiału. Czyli każdy materiał jest u mnie reprezentowany jako Multi/Sub-Object - standardowy materiał to po prostu jeden wpis w wektorze. Wszystkie dane materiału są zapisane w klasie glXSubMaterial. Klasa ta zawiera standardowe dane dla materiału, czyli kolory ambient, diffuse, specular, nazwę, a także wartość dla kanału alpha, siłę odbłysku i skupienie tegoż. Zawiera również wektor ze składowymi typu glXMapInfo. Definiuje on kanały diffuse i bump z Max'a (a w zasadzie wszystkie możliwe tekstury nałożone na obiekt). Klasa glXMapInfo zawiera identyfikator tekstury (jej indeks w tablicy tekstur), macierz przekształcenia dla tekstury (na danym kanale - nie zapisuję tej macierzy w pliku), oraz typ tekstury. Jak tego wszystkiego używam powiem za chwilkę.

Jak to się dzieje, że wszystkie te dane znajdują się w składowych odpowiednich klas? Ano oczywiście stosuję konwersję. Algorytm używany do tego celu jest sercem całego eksportera. Ale niestety jest to serce przyprawiające cały eksporter o zawał ;)). Mimo iż nie jest bardzo skomplikowany, to zajmuje ponad 200 linii kodu, a w jego ciele znajduje się trochę pętli for i warunków if (kod konwersji nie jest zbytnio zoptymalizowany - znaczy nie jest praktycznie wcale zoptymalizowany :)). Powoduje to, że algorytm nieco przytyka proces eksportu. Można by się zastanawiać po co jakiś algorytm. Przecież na przykład plik 3DS jest eksportowany bez żadnej konwersji. I owszem - my też moglibyśmy tak zrobić, ale to zmusiłoby nas albo do używania funkcji glVertex/Color/Normal/itd. (użytkownicy OpenGL oczywiście :)) czyli wolnych jak cholera, albo do napisania jakiegoś konwertera, który wykonywałby się przy wczytywaniu pliku. Mnie się wydaje jednak, że lepiej poświęcić chwilkę na konwertowanie w czasie eksportu (które robimy powiedzmy raz) niż później kazać użytkownikowi czekać godzinę przed uruchomieniem programu wyświetlając mu tabliczkę w stylu Loading... A tak w pliku mamy ułożone dane w taki sposób, że wystarczy tylko odczytać wszystkie tablice (co wykonuje się bardzo szybko) i załadować je poprzez glVertexPointer i pokrewne (mówię oczywiście o użytkownikach OpenGL'a :). Jeśli idzie o prędkość eksportu, to jest ona zbliżona do eksportowania pliku ASE więc nie jest tak znowu źle (choć źle zaczyna się robić przy obiektach zawierających bardzo dużo trójkątów - np. kostka 200x200x200 - wtedy wszystko trwa przerażająco długo i może się wydawać, że się wszystko zawiesiło :). No tyle że ja to sprawdzałem na swoim kompie, a ja mam procesor 1.7Ghz (wiem, że nie za dużo :). Tak więc oczywiście prędkość eksportu będzie zależna głównie od mocy obliczeniowej kompa. Nie polecam eksportować dużych scen komuś kto ma słaby sprzęt, bo mu się może wszystko przywiesić (zresztą na słabym sprzęcie to i tak by tego nie wyświetlił w czasie rzeczywistym :P). Ponieważ jak już wspomniałem kod do konwersji ma ok. 200 linii (tak na oko ;), toteż raczej ciężko będzie go tutaj wrzucić w całości. Wydaje mi się, że najlepiej będzie jak opiszę co on robi i ewentualnie posłużę się wstawkami z oryginalnego kodu (sam kod jest oczywiście opisany). Należy pamiętać, że funkcja Convert (metoda klasy CSceneEnumProc) konwertuje pojedynczy obiekt, a nie całą scenę.

Zasada konwersji jest mniej więcej taka. Najpierw pobieramy sobie z Max'a macierz przekształcenia i numer materiału dla obiektu oraz nazwę obiektu. Następnie, jeżeli nałożony jest materiał typu Standard (lub nie ma materiału) traktujemy cały obiekt jak jeden pod-obiekt i tworzymy tenże w wektorze. Sam materiał konwertujemy również, aby móc używać go później w programie. Jeśli na obiekt jest nałożony materiał typu Multi\Sub-Object to poszczególne pod-obiekty zostaną dodane do siatki podczas konwersji (wewnątrz pętli). Kolejną rzeczą jest stwierdzenie, czy trójkąty w siatce są ułożone w stronę zgodną z ruchem wskazówek zegara, czy przeciwnie i ustawienie odpowiedniej kolejności dla wierzchołków. W tym momencie jesteśmy już gotowi do konwersji. Robimy sobie więc pętlę przez wszystkie trójkąty w siatce i... Najpierw pobieramy sobie grupę cieniowania do której należy wybrany właśnie trójkąt. Pamiętamy oczywiście, że jest to potrzebne do stwierdzenia, które wierzchołki musimy w siatce powielić. Mając już grupę naszego trójkąta musimy się dowiedzieć, czy przypadkiem na obiekt nie jest nałożony materiał typu Multi/Sub-Object. Jeśli jest, to to może oznaczać, że na nasz trójkąt jest nałożony inny materiał niż na resztę obiektu. Jak to sprawdzić? Nic prostszego - przechodzimy przez wszystkie pod-obiekty naszej siatki (glXMesh) i sprawdzamy, czy przypadkiem nie mamy już pod-obiektu o takim materiale jak trójkąt. Jeśli taki pod-obiekt już występuje to zapamiętujemy jego indeks w zmiennej SubObjectIndex i kończymy pętlę. Jeśli jednak nie znaleźliśmy takiego pod-obiektu (zmienna SubObjectIndex ma wartość -1) to musimy utworzyć nowy pod-obiekt. W tym momencie wiemy już do jakiego pod-obiektu należy nasz trójkąt. Teraz musimy jeszcze znaleźć grupę do której należy. Przechodzimy więc przez wszystkie grupy naszego pod-obiektu i szukamy, czy nie wystąpiła już taka, która miałaby taki sam numer jak numer grupy trójkąta. Jeśli takiej grupy nie znajdziemy (czyli nie przechodzimy przez poniższe pętle) to tworzymy nową i dodajemy tam wszystkie dane trójkąta (wierzchołki, normalne, indeksy, współrzędne uv). Jeśli jednak znaleźliśmy grupę dla trójkąta, to nie możemy tak po prostu dodać jego danych do tablic. Dlaczego? Bo chcemy, żeby wierzchołki należące do jednej grupy nie powtarzały się w tablicy wierzchołków - chcemy powielać tylko należące do różnych grup (oszczędzamy miejsce w pamięci i czas renderowania, bo przez magistrale AGP nie trzeba przerzucać takiej ilości danych). Oczywiście są odstępstwa od tej reguły, do których za chwilkę dojdziemy :)). Jak sprawdzić, czy dany wierzchołek wystąpił już w tablicy? No tak jak zawsze, czyli przechodzimy przez całą tablicę numerów wierzchołków. Jeśli natkniemy się na wierzchołek o takim samym numerze jak obecny, to znaczy że jest on już w tablicy i teoretycznie nie musimy go tam dodawać (zapamiętujemy tylko jego indeks). Piszę "teoretycznie" ponieważ musimy jeszcze wykonać jeden test. Trzeba bowiem sprawdzić, czy współrzędne teksturowania obu wierzchołków są takie same. Jeśli są, to nie dodajemy wierzchołka. Jeśli jednak nie są to musimy go powielić, aby wszystko się nam ładnie rysowało. Tą procedurę przeprowadzamy dla wszystkich trzech wierzchołków trójkąta. No i to w zasadzie jest sedno algorytmu konwersji. Na końcu już tylko kopiujemy niektóre dane do odpowiednich tablic. Po co? Ano po to, że wcześniej zapisywaliśmy numery wierzchołków, a nie same wierzchołki (po co porównywać trzy liczby float, jeśli można porównać jedną liczbę całkowitą??).

W zasadzie mógłbym skończyć już teraz, ale jednak zostało jeszcze kilka rzeczy do omówienia. Po pierwsze pewnie zauważyliście już klasę glXMtlList w SceneEnumProc.h/cpp? Ta klasa służy do przechowywania materiałów Max'a i konwertowania ich na materiały których możemy używać (zapisane w klasie glXMaterial). Przechowuje jeszcze spis tektur, które są używane przez obiekty mogące zostać wyeksportowane. Sama klasa zawiera niewiele funkcji. AddMaterial() służy do dodania nowego materiału (z Max'a). Jednocześnie sprawdza, czy taki materiał już nie wystąpił przypadkiem i czy jest to materiał, który potrafimy odwzorować - czyli sprawdza, czy jest to materiał typu Standard albo Multi/Sub-Object. Jeśli tak (nie wystąpił i potrafimy go odwzorować), to dodaje go do tablicy i jednocześnie alokuje pamięć (tworzy nowe wpisy w wektorze) dla pod-materiałów, które zostaną zapełnione poprawnymi danymi podczas konwersji. Metoda GetMtlID zwraca index materiału przekazanego jako parametr. Jeżeli materiał nie występuje w tablicy zostanie zwrócone -1 (co dla obiektu oznacza brak materiału). Czyli jeśli materiał nie mógł być dodany do spisu bo nie spełniał jakich kryteriów (np. był innego typu niż obsługiwane) to eksporter potraktuje obiekt na który jest nałożony taki materiał, jakby nie był nałożony żaden :). Metoda AddTexture() dodaje nową teksturę do spisu tekstur (oczywiście tylko jeśli taka tekstura już nie wystąpiła) jednocześnie zwracając indeks pod którym w tablicy znajduje się ta tekstura (bądź -1 jeśli nazwa była ciągiem pustym). Metoda ConvertMaxMtlToGlxMtl() konwertuje oczywiście materiały z Max'a na nasz glXMaterial - oczywiście pod warunkiem, że nie został już wcześniej skonwertowany (składowa glXMaterial::m_bConverted). Najpierw zapisujemy standardowe dane o kolorach materiału, a następnie dane o teksturach na kanałach diffuse i bump. Dane tekstury możemy zapisać tylko wtedy, jeśli na danym kanale coś jest (texmapa pobrana z tego kanału jest != NULL) i jeśli teksmapę możemy skonwertować na bitmapę. Jeśli te warunki są spełnione to dodajemy nowy element typu glXMapInfo (definiuje on poszczególne jednostki teksturujące), a następnie zapisujemy typ tekstury, indeks do tablicy tekstur dla użytej tekstury (przy okazji dodajemy nazwę tekstury ;) i na końcu pobieramy sobie macierz przekształcenia tekstury. Przy odczycie nazwy tekstury używana jest funkcja GetTextureFilename(), która zwraca tylko nazwę tekstury (usuwa ścieżkę) i zamienia każde rozszerzenie na bmp i tga. Znaczy jeśli tekstura miała rozszerzenia tga, to ono zostaje, a wszystkie inne rozszerzenia są zamieniane na bmp. Podczas konwersji wykorzystywana jest również funkcja GetUVTransform(), która pobiera oczywiście macierz przekształcenia dla tekstury. Co robimy z tą macierzą?? Otóż tuż przed zapisaniem każdego obiektu (geometrycznego) w pliku zostaje wywołana metoda SetUVCoords() (z CSceneEnumProc). Jej zadaniem jest utworzenie osobnych zestawów współrzędnych UV dla każdej jednostki teksturującej (czyli u nas dla kanałów diffuse i bump) i wymnożenie ich przez macierz przekształcenia tekstury z odpowiedniego kanału. Dlaczego robię osobne zestawy współrzędnych? Ano dlatego, że nie potrafię odwzorować macierzy tekstury w czasie rzeczywistym, tak aby wszystko wyglądało jak w Max'ie. Jeśli ktoś wie jak utworzyć tą macierz (pobierając inne dane z Max'a) w czasie rzeczywistym, tak aby wszystko wyglądało jak należy to proszę o kontakt!!!!! :)))).

I to prawie wszystko. Pozostała jeszcze tylko jedna metoda - metoda PreProcess() z klasy CSceneEnumProc. Po zaglądnięciu do niej okaże się, że jest to prosta metoda wywoływana rekurencyjnie, której zadaniem jest zliczenie ilości obiektów na scenie Max'a i przygotowanie materiałów. Materiały są dodawane tylko wtedy, kiedy obiekt może zostać wyeksportowany, to znaczy można go zamienić na TriObj.

I to wszystko. Eksporter możecie znaleźć w plikach - jest on pisany dla wersji 4.2 Max'a, ale działa również z wyższymi (sprawdzałem w 5.0). Nie wiem czy działa z niższymi - np. wersją 3.0 (sądzę że nie:). Ale nie ma się co przejmować - wystarczy tylko podmienić kilka funkcji Max'a, które nie były dostępne w wersji 3.0, przekompilować i koniec. Nie wiem które to są dokładnie funkcje, więc nie piszę, ale jak spróbujecie skompilować projekt, to zaraz dostaniecie odpowiednie info. Później już tylko pozostaje odpalić MaxSDK i poszukać podobnych funkcji...

Co nowego w wersji 1.01

Jak wiadomo nikt nie jest doskonały i nie sposób ustrzec się błędów, szczególnie w przypadku programowania. Ponadto czasem z niewiadomych przyczyn nie implementujemy rzeczy banalnie wręcz prostych, a jednak znacznie ułatwiających życie ;). Nie inaczej jest z w przypadku glXPortera. Co więc zostało dodane, a co poprawione? Heh. Przede wszystkim znalazłem dość sporego bug'a. Okazało się bowiem, że eksporter często powielał niepotrzebnie wierzchołki. Była to jednakże kwestia dopisania dwóch linijek kodu dla każdego z trzech wierzchołków więc teraz już działa ok. Ważniejsze są w sumie nowo dodane opcje (chciałem tu zauważyć, że tak jak wcześniej ten dokument służy raczej celom informacyjnym i wytłumaczeniu wszystkiego z grubsza - dokładniejszy opis znajdziecie w kodzie). Po pierwsze dodałem nową klasę - glXCopiedVerticlesInfo. Zawiera ona dane o wierzchołkach, których powielenie zostało niejako wymuszone przez eksporter. Chodzi oczywiście o te wierzchołki, które znajdują się na łączeniach obiektów, czyli tak zwanych szwach. Wszystko bowiem działa bez zarzutu do czasu, aż odpalimy liczenie wektorów przestrzeni stycznej (o tym za chwilę). Wtedy nagle się okaże, że szew, którego pozbyliśmy się za pomocą kopiowania wierzchołków pojawia się nagle powodując bardzo nieprzyjemny efekt wizualny. Chodzi mianowicie o to, że wektory przestrzeni stycznej (tangent i binormal) muszą zostać uśrednione dla sąsiadujących trójkątów jeśli cała operacja dot3 ma wyglądać jak należy. Dla większości wierzchołków nie ma problemu ponieważ po prostu sobie przechodzimy przez nie kolejno. Skopiowane wierzchołki powodują jednakże przerwę w geometrii bryły i w zwykły sposób nie możemy uśrednić tangentów dla trójkątów znajdujących się po obu stronach szwu. Potrzebujemy więc jakiejś informacji na temat tych wierzchołków, które zostały powielone. Do tego właśnie służy wyżej wspomniana klasa. Zawiera indeksy tych wierzchołków, które w danej grupie cieniowania zostały skopiowane, jak również indeks wierzchołka bazowego z 3D Studio. Ten ostatni pozwala nam oczywiście stwierdzić, czy dany wierzchołek z maxa był już powielany czy nie. Całość działa oczywiście dość prosto, z jednym wyjątkiem. Otóż w momencie, kiedy stwierdzimy, że dany wierzchołek ma być powielony po raz pierwszy (nie istnieje jeszcze dla niego kopia klasy glXCopiedVerticlesInfo) to oznacza, że gdzieś (właśnie - 'gdzieś' ;) w tablicy indeksów znajduje się ten pierwszy. Tutaj pojawia się taki problem, że nie mamy w zasadzie zielonego pojęcia jakiż to też indeks może mieć ten pierwotny wierzchołek (jego indeks nie jest tym z maxa). Dlatego, aby dostarczyć dodatkowe informacje o kopiowanych wierzchołkach, trzeba najpierw dostarczyć dodatkowe informacje do wygenerowania tych poprzednich ;). Te dodatkowe informacje są bardzo proste. Jest to bowiem wektor (w klasie CSmoothingGroup) elementów struktury SIndexInfo, która ma dwie składowe - numer indeksu wierzchołka w maxie i numer indeksu tego samego wierzchołka w exporterze. Info takie dodajemy tylko wtedy, kiedy wierzchołek ma być powielony, ale jest to jego pierwsze powielenie, czyli kod wygląda następująco (dla wierzchołka 0):
if (!cv0)
{
    SIndexInfo II;
    II.m_glXIndex   = mesh.m_SubObjects[SubObjectIndex].m_ConvertGroups[j].m_IndexList.back();
    II.m_3DMaxIndex = pMesh->faces[i].v[vx0];
    mesh.m_SubObjects[SubObjectIndex].m_ConvertGroups[j].m_IndicesInfo.push_back(II);
};
Zmienna cv0 informuje o tym, czy wierzchołek jest powielany przez 'wymuszenie'. Jak już wspomniałem wcześniej, z taka sytuacją mamy do czynienia wtedy, jeśli powielamy wierzchołek w tej samej grupie cieniowania dlatego, że jest na szwie (ten sam wierzchołek ma różne zestawy współrzędnych tekstur dla tej samej jednostki). Powyższy kod jest więc wykonywany tylko jeśli nie wymuszamy takiego kopiowania (czyli dla pierwszego wierzchołka, bo skąd mamy wiedzieć, że ma różne zestawy tekstur :). Domyślnie w pętli zmienna cv0 jest ustawiana na false (tak jak zmienne cv1 i cv2, które decydują o kopiowaniu wierzchołków 1 i 2). Wartość tej zmiennej zmienia się na true tylko w przypadku wymuszenia kopiowania. Proces zbierania informacji o indeksach na potrzeby klasy glXCopiedVerticlesInfo praktycznie nie obciąża procesu eksportowania - wszystkie warunki sprawdzane były bowiem już wcześniej.

Omówione powyżej zmiany są największymi w nowej wersji. Kolejnym ulepszeniem jakie wprowadziłem jest podmiana współrzędnych. Chodzi o to, że 3D Studio MAX używa innego układu (oś Y jest skierowana w głąb, a nie do góry). Nie wiem czemu wcześniej nie dopisałem tego fragmentu, ale znalazł się w wersji obecnej ;). Cała zabawa zabiera 4 linijki dla wierzchołka i normalnej, oraz jedna dla tekstury. O ile pozycja i normalna są zrozumiałe, o tyle kwestia tekstury wymaga wyjaśnienia. Chodzi mianowicie o to, że po zamianie układu współrzędnych współrzędne u tekstury (os x) będą odwrócone (jak po zastosowaniu jednokładności względem osi x). Dlatego też musimy je ponownie przywrócić do stanu poprzedniego. Robimy to za pomocą banalnej wręcz operacji, która wygląda tak:

nowe_x = 1 - stare_x;


I koniec. Tekstura jest gotowa do używania w stanie takim jak wcześniej.

Ostatnim dodatkiem do klas glX jest kod liczący wektory przestrzeni stycznej. Robią to dwie metody (obie nazywają się identycznie - CalculateTangents). Ich kod znajdziecie w klasach glXMesh i glXSubObject. Ta w pierwszej klasie wywołuje tą z drugiej :) i na końcu robi globalne uśrednienie wektorów przestrzeni stycznej. Wszystko to po to, aby pozbyć się niechcianych efektów typu właśnie szwy itd. Obie metody przyjmują dwa parametry. Pierwszy oznacza numer jednostki teksturującej, z której mają być pobrane współrzędne teksturowania dla obliczeń wektorów przestrzeni stycznej. Drugi parametr decyduje, czy metoda ma wymuszać użycie podanej jednostki. Jeśli tak, to dla obiektów, które nie posiadają tekstury na danej jednostce nie zostanie nic policzone. Jeśli natomiast drugi parametr ma wartość false, to metoda użyje (o ile są dostępne) współrzędnych z jednostki 0. Co to są wektory przestrzeni stycznej możecie się dowiedzieć z artykułu Robala w dziale Techniki: dot3 - teoria. Ponieważ jednak ja używam innej metody do generowania tych wektorów, toteż jej omówienie znajdziecie w dziale OpenGL: dot3 w OpenGL

Jeśli natomiast chodzi o sam eksporter to dodałem okno dialogowe eksportowania. Wygląda ono tak:


Opcje są oczywiście banalne ;). Zaznaczenie pierwszego pola powoduje, że eksporter nie będzie zapisywał do pliku informacji o skopiowanych wierzchołkach. Informacje takie jednak zbierze i wykorzysta przy liczeniu tangentów (oczywiście jeśli chcemy je liczyć). Opcja ta jest przydatna dla obiektów, które nie wymagają późniejszego liczenia tangentów (pozwala zmniejszyć rozmiar pliku). Następne trzy opcje są oczywiście ze sobą powiązane. Zaznaczenie pierwszego radio-buttona spowoduje, że wektory przestrzeni stycznej zostaną policzone i zapisane w pliku dla wszystkich siatek posiadających co najmniej jedną teksturę. Przy czym obojętne jest, czy jest to tekstura diffuse, czy bump. Domyślnie eksporter stara się policzyć wektory dla tekstury na kanale bump, ale jeśli takiej nie ma to policzy je dla tekstury diffuse. Druga opcja zadziała tylko dla siatek posiadających teksturę na kanale bump. Ostatnia spowoduje oczywiście, że wektory przestrzeni stycznej nie będą ani liczone, ani zapisywane w pliku.

To w sumie tyle nowości w wersji 1.01. Sądzę, że w miarę pisania kolejnych tutoriali przyjdzie potrzeba zmiany lub dodania czegoś w eksporterze i powstanie wersja 1.02, ale na to jest jeszcze czas ;).


Kod źródłowy

©Copyright by Domino   



Tutoriale - Techniki
Nasze newsy s� w RSS: backend.php
PHP-Nuke Copyright © 2005 by Francisco Burzi. This is free software, and you may redistribute it under the GPL. PHP-Nuke comes with absolutely no warranty, for details, see the license.
Tworzenie strony: 0.05 sekund

:: Layout strony został stworzony przez www.nukemods.com w oparciu o styl phpbb2 Helius, którego autorem jest Cyberalien ::