środa, 13 października 2010

The PIMPL Pattern

The PIMPL this is a "private implementation". This pattern is useful to cut a time of compilation.

Advantages:
We don't need attach a lot of headers file to another header file.
We reduce a time of compilation.

Disadvantages:
For each object we must create one dynamic object.

How can we implement it ?

Look at this simple header file :

//Height map header
#include "Camera.h"
#include "Hero.h"
//etc.

class HeightMap{
   private:
   Camera camera;
   Hero hero;
   //etc.
};

We see that we must attach a few headers.
We can create a promising declaration, but we can create by this only a dynamic object.

We can solve this problem by private implementation. There is example of this solution:

//Height map header

class HeightMap_pimpl;

class HeightMap{
private:
   HeightMap_pimpl* pimpl;
};

//cpp file
#include "Camera.h"
#include "Hero.h"

class HeightMap_pimpl{
private:
   Camera camera;
   Hero hero;
}

HeightMap::HeightMap(){ pimpl = new HeightMap_pimpl; }

And that' s it. This is really simple and useful. We can also use a smart pointer from my general library to avoid responsibility to delete an allocated object :

class HeightMap{
private:
   scoped_ptr pimpl;
};

See you  later

niedziela, 10 października 2010

Simple resource manager

Hi!

Today I want to show you my simple resource manager. I never earlier have been writing similar mechanisms.

What is it?

A resource manager let us use frequently the same resource. But what can be a resource ? A lot of things can be a resource. The best example is file (model file, texture file, etc.). We can have a lot of object which refer to the same texture/model but we really have only one resource of texture/model. Below drawing show it :


My resource class look like below:

class IResource{
private:
    uint4 countLock;
    float lastUseTime;
    STATE_RES state;
    string name;
    string group;
protected:
    //Wywoływane podczas ładowania
    virtual void OnLoad() {}
    //Wywoływane podczas odładowywania
    virtual void OnUnload() {}
public:
    IResource(){}
    IResource(const string &Name, const string &Group = "");
    virtual ~IResource();
    void Load();
    void Unload();
    void Lock();
    void Unlock();
    const string& GetName() { return name; }
    const string& GetGroup() { return group; }
    STATE_RES GetState();
};

Each resource have own name. By this name we get a pointer to resource. Optionally each resource may have a group name. For example all files of texture may have name group : "texture" etc.

My resource manager is writing as Singleton. He provides functions to get a resource with the given name. This function is template, so we can get ever resource any type. And that's it. Manager haves a few function more, but I won' t describe it, you can download all source code below.

ATTENTION: You should create resource objects  in some function, but no as global objects because probably the program will return a mistake.

Use of my manager

Below example shows use of my manager :

class Texture : public library::IResource{
//some data like pixels
public: Texture(const string& name, const string& group = "") : library:IResource(name, group) {}
}

void LoadAllResources(){
   Texture texture1("myTexture.bmp, "Texture");
 }

int main(){
   BindTexture( ResManager::GetResource("myTexture.bmp);
}
library:: this is my namespace where we can find all class of resource manager.
I strongly believe that this isn't hard.

Here you can download a source code.

Greetings.

niedziela, 3 października 2010

Biblioteka generalna

Opiszę tutaj pokrótce najniższą warstwę mianowicie bibliotekę generalną.

Kod tej biblioteki odpowiedzialny jest za uzupełnienie jakby, niektórych braków w c++.

Większość kodu mieści się w przestrzeni nazw general.

Moduł Base
Moduł ten zawiera nazwy podstawowych zmiennych (np unit8 to liczba bez znaku 64 bitowa).

Posiada niektóre funkcje i stałe matematyczne, których brak w C++. Są to m.in przeliczanie radianów na stopnie i odwrotnie, zaokrąglanie liczb, szybkie potęgowanie dla potęg całkowitych (w czasie O(lg n)), częśc całkowitą i ułamkową liczby itp. Posiada funkcję timera.

Zostały napisane (a raczej skopiowane) funkcje przekształcające z jednego typu do stringa i odwrotnie. Jest również funkcja szablonowa SthToStr i StrToSth konwertująca z jakiegoś typu do stringa i ze stringa do jakieś typu.(Autor: Adam Sawicki)

Zawiera 2 implementacje inteligentnych wskaźników (jedna nie pozwala kopiowania obiektu, a druga umożliwia kopiowanie i prowadzi politykę zliczania (wskaźnik jest usuwany gdy nie wskazuje na niego żaden inny obiekt)Autor: Adam Sawicki).

Ostatnim element to klasa Format, która zwraca tekst w stawionymi liczbami np Format("a = " % 5) zwróci nam tekst ("a = 5") (Autor: Adam Sawicki).

Moduł Error Umożliwia nam hierarchię błędów i zawiera funkcję ErrorMessage, która powoduje wyświetlenie komunikatu z błędem przesłanym jako string.

Moduł Event zawiera zdarzenia (narazie jest zaimplementowane zdarzenie, które jest uruchamiane po upływie określonego czasu.

Moduł File - udostępnia klasę, która operuje plikiem. Działa podobnie jak klasa fstream, lecz jest sporo prostsza i około 2 razy szybsza.

Moduł Math - moduł matematyczny (udostępniany wcześniej)

Moduł Tokenizer - jest to klasa, która w znaczny sposób upraszcza wydobywanie informacji z ciągu znaków. Jej działanie sprowadza się do zwracania zmiennych odpowiedniego typu z podanego ciągu wejściowego.
Działa to tak :

Tokenizer t("to jest liczba 123");
cout << t.GetStringNext() << endl;
cout << t.GetStringNext() << endl;
cout << t.GetStringNext() << endl;
cout << t.GetIntNext() << endl;

To powyższe linie kodu wypiszą nam w osobnych liniach każdy wyraz ciągu podanego w konstruktorze obiektu. Jest to jakby strumień stringstream z biblioteki standardowej c++, przy czym działa szybciej.

Na razie to tyle co oferuje nam biblioteka generalna.

Jej kod znajduje się do ściągnięcia TUTAJ.

Pozdrawiam Norbert G.

piątek, 1 października 2010

Reorganizacja projektu

Dłuższy czas nie pisałem, gdyż tak naprawdę całą organizację projektu zmieniłem. Myślałem, że dam to robić  na bieżąco, podczas rozwijania projektu ale to był zły pomysł. Już na samym początku miałem sporo problemów, a co dopiero działo by się potem. Dlatego uważam, że taka organizacja przyniesie wiele dobrego.

Pomocnym przy tym była książka "C++ dla programistów gier" oraz praca magisterska Pana Adama Sawickiego (do ściągnięcia z internetu w pdf).
Przede wszystkim wzorowałem się właśnie na powyższej pracy magisterskiej, jak i również zapożyczyłem kilka funkcji z pracy autora.

Na dzień dzisiejszy architektura silnika wygląda następująco :



Widać trzy główne osobne biblioteki. Na samym dole jest Biblioteka generalna, poźniej Obsługa OpenGL (w tym okien i zdarzeń) no i na górnej warstwie jest cały silnik grafiki 3D. Widzimy, że przy tym układzie nie powstają cykle (tzn że jakieś dwie warstwy muszę zależą od siebie), co myślę, że jest dobrą organizacją.
Najmniej dopracowana jest warstwa samego silniku ale to tylko taki prototyp. Jest prawie pewne, że to się zmieni. Na razie chciałbym zająć się warstwami, które składają się na cały engine. W pierwszej kolejności będzie to biblioteka generalna a potem obsługa okna OpenGL no i wreszcie cały engine. Trochę żałuję, że tak późno wziąłem się do zmiany całej architektury. Oczywiście tamten poprzedni kod co napisałem (wczytywanie terenu) na pewno wykorzystam w przyszłości, bo jest to już prawie pełny, kompletny działający kod.

Moduł matematyczny jest zaimplementowany, nic nie zmieniło się w nim od ostatniego update' u.

Pozdrawiam Norbert G.

poniedziałek, 6 września 2010

Geomipmapping part II - usuwanie dziur w w geometrii terenu

Jak poradzić sobie z powstałymi w poprzednim artykule "dziurami geometrii" ?? Cóż sposobów jest kilka ja znam 2, które w mniejszym w lub mniejszym stopniu postaram się przedstawić.

Rozwiązanie za pomocą "Skirt"

Skirt (z ang. spódniczka), służy dodaniu dodatkowej "spódniczki" do każdego z sektorów. Jest to rozwiązanie o tyle dobre, bo nie przy stwarza dużych problemów przy implementacji i w miarę dobrze spełnia swoje zadanie.

Dobra to jak taka spódniczka wygląda ? Na rysunku zaznaczone to jest na czerwono :

Co musimy zrobić by dodać tę dodatkową warstwę sektorów ?
Dla skrajnych wierzchołków sektora (leżących na bokach sektora) można dodać dodatkową warstwę wierzchołków o współrzędnych takich samych jak wierzchołki podstawowe, lecz opuszczone o jakąś konkretną wartość (np o długość boku sektora, chodź w praktyce potrzeba dużo mniejszych wartości).
Gdy dodamy te dodatkowe wierzchołki wystarczy obliczyć jeszcze texCoord' y i odpowiednio połączyć w trójkąty. Tego pomysłu nie mam zaimplementowanego u siebie.

Rozwiązanie za pomocą dodatkowego indeksowania

W tym rozwiązaniu zakłada się, że poziom szczegółowości nie może różnić się więcej niż jeden. Tutaj zamiast jednego zestawu IB tworzy się 16 zestawów index bufferów (tak naprawdę można chyba 8 ale nie będę się zagłębiać dalej). Po co ? Przygotowuje się wszystkie podzbiory krawędzi tak by pasowały do poziomu szczegółowości sąsiada. Lecz by można było zastosować to rozwiązanie musimy lekko zmodyfikować indexowanie trójkątów. Teraz sektor mający wszystkich sąsiadów o takim poziomie szczegółowości będzie wyglądać tak :

(Indeksowanie w kwadracie czerwonym jest identyczne jak było)

Widać jak zmieniło się indeksowanie trójkątów skrajnych (takich, które łączą się z innymi sektorami)

A jak będzie to wyglądać dla sektorów gdzie jakiś sąsiad ma inny poziom szczegółowości ?(pamiętajmy o założeniu, że nie będzie to poziom większy niż jeden). Rysunek poniżej (pokazuje nam sytuację, gdzie każdy sąsiad ma inny poziom szczegółowości) :


Widzimy w tej sytuacji, że indeksowanie tego sektora wpasuje się nam do innego sektora, rozwiązując tym samym nasz problem, mianowicie usunie dziury w geometrii.

Jak to zaimplementować ? Cóż, moja implementacji do najładniejszych nie należy. Nie będę też przedstawiać tu kodu bo aktualny stan silnika będzie można pobrać w linku na samym dole. Chciałbym jedynie tutaj przedstawić zarys mojego rozwiązania. Obliczam sobie krawędź "normalną" tzn taką, która ma sąsiada o tym samym poziomie i krawędź "dodatkową', która ma sąsiada o innym poziomie szczegółowości. Nie muszę obliczać 4 takich krawędzi "normalnych" i "dodatkowych". Wystarczy 2, jedna obliczająca krawędź w poziomie a druga w pionie, a kolejne 2 oblicze z niedużych przekształceń tych 2 obliczonych wcześniej(tak naprawdę wystarczy jedna krawędź, ale wtedy jest więcej kombinowania, jakie wartości dodać, by otrzymać wszystkie cztery wartości). Każda z czterech krawędzie jest tak ponumerowana :

  
Teraz wystarczy przejść przez wszystkie podzbiory krawędzi. Jak efektywnie wygenerować podzbiory ?
Przedstawię pomysł, który pokazał mi  Pan Marcin Andrychowicz na kółku informatycznym : 

for(int i = 0; i < (1 << n) - 1; ++i){
   for(int j = 0; j < n; ++j)
      if(i & j) cout << j << " ";
   cout << j;
}
Jak to działa ? najpierw idzie pętla przez wszystkie podzbiory (lekkie przypomnienie z prawdopodobieństwa jak to policzyć). Ilość podzbiorów równa się 2 ^ n - 1. Potem idziemy przez wszystkie elementy, by sprawdzić, jakie elementy należą do aktualnie liczonego podzbioru. Warunek if(i & j) sprawdza czy element j należy do podzbioru i. Ale dlaczego to działa ? Operator & to zwykłe mnożenie bitowe. W naszym przykładzie warunek jest prawdziwy jeżeli i - ty bit jest równy jeden. Jeżeli nie bardzo rozumiesz rozpisz na kartce to powinieneś zrozumieć o co chodzi. Teraz wiedząc już jak wygenerować podzbiory przechodzimy do obliczania wszystkich IB.

Jak pokazałem wyżej puszczamy pętle przez wszystkie podzbiory (trzeba tylko delikatnie pozmieniać wartości w pętli bo trzeba zauważyć, że może występować taki index buffer, który nie będzie miał żadnej krawędzi "dodatkowej", dzieje się to przy sąsiadach o takim samym poziomie szczegółowości, a podzbiór nie może być przecież zerowy). U nas moc podzbioru oczywiście wynosi 4 bo mamy 4 krawędzie, numerowane :0, 1, 2, 3 jak pokazałem wyżej na rysunku. Wystarczy teraz dla i - tego podzbioru sprawdzić, które krawędzie w sobie zawiera, właśnie ten warunek to robi if(i & j) i dodać odpowiednie index' y i na koniec wygenerować wszystkie IB. No i na koniec renderując dany sektor musimy obliczyć, który zestaw index buffera wybrać. Robię to tak :

    int EvoHeightMap::GetGeoDetail(int i, int j){
        int result = 0;
        if(i > 0 && geoMap[i][j] < geoMap[i - 1][j]) result |= 4;
        if(j > 0 && geoMap[i][j] < geoMap[i][j - 1]) result |= 2;
        if(i < (numOfSectors - 1) && geoMap[i][j] < geoMap[i + 1][j]) result |= 1;
        if(j < (numOfSectors - 1) && geoMap[i][j] < geoMap[i][j + 1]) result |= 8;
        return result;
    }
geoMap - to tablica 2 - wymiarowa zawierająca jaki jest poziom geomipmappingu dla sektora [i][j].

Teraz nie ma już żadnych dziur :



Aktualny kod silnika do pobrania TUTAJ.

Pozdrawiam Norbert G.

piątek, 3 września 2010

Geomipmapping przy terenie

Dziś chciałbym przedstawić na czym polega metoda geomimappingu i jak ją zastosować do naszego terenu.

Podział terenu na sektory


Pierwszą rzecz jaką chciałbym przedstawić w tym artykule to podział całej naszej heightmpay na sektory. Z założenia teren jest kwadratem o boku będącą wielokrotnością 2. Drugim założeniem silnika jest to, że liczba sektorów również musi być wielokrotnością 2. Spójrzmy na poniższy rysunek, który wszystko to przedstawia :
Kwadratowa heightmapa o boku 256 została podzielona na na 16 sektorów, który każdy będzie mieć wymiary 64 x 64.... no właśnie nie dokońca. Tak naprawdę musimy dodać 1 wiersz i jedną kolumnę bo inaczej renderując sektory uzyskamy dziru pomiędzy sektorami, których chcemy uniknąć (rysunek poniżej) :



 Na powyższym rysunku widać pęknięcia terenu. Rozwiązanie jest takie, że wymiary sektora powinny być
mieć wymiary 2 ^ n + 1(czyli tak naprawdę i heightmapa powinna mieć wymiary 2^n + 1 ale przyjąłem, że będzie to 2^n a jeden dodatkowy wiersz i kolumnę dodaję ręcznie i ustawiam ich wierzchołki na ostatnią kolumnę/wiersz). Czyli sektory powinny wyglądać mniej więcej tak(czerwona linia to nasze sektory) :

w funkcji void EvoHeightMap::Load(EvoTexture* heightMap, int amountOfSectors, float xzScale = 1.0f, float yScale = 1.0f);

amountOfSectors to właśnie ilość sektorów na jeden bok. Na powyższym rysunku dalibyśmy tam wartość 2.

Geomipmapping w teorii

Na czym polega geomipmapping ? Jest to jeden z algorytmów LOD (Level of Detail), który ma za zadanie uprościć geometrię odległą od kamery w celu znacznego przyspieszenia renderingu. Jest to coś w rodzaju mipmappingu tylko, że dla geometrii. Założenie jest takie by teren był jakoś podzielony, za pomocą np. drzewa czwórkowego, lecz u siebie implementuję sektory jako tablica dwuwymiarowa po prostu (opisałem to w poprzednim akapicie). Każdy następny level geomipmapping' u zawiera 2 razy mniej wierzchołków (wszystkie wierzchołki brane są co drugi). Poniższy rysunek powinien wszystko wytłumaczyć.

Dany sektor będzie miał log_2(n - 1) + 1 leveli geomipmappingu, gdzie log_2 to logarytm o podstawie 2,
n - ilość wierzchołków w boku sektora i dodajemy 1 bo mamy jeszcze poziom zerowy.

Geomipmapping w praktyce

No dobra ale jak to naprawdę wygląda w praktyce ?? W praktyce jest tak że przygotowujemy po prostu odpowiednie zestawy index bufferów, zawierające odpowiednie wierzchołki. Tak naprawdę będzie nam potrzebny tylko jeden zestaw dla 1 poziomu geomipmappingu, i będziemy po prostu odpowiednio się przesuwać od którego wierzchołka mamy rysować w VBO (ale o tym zachwilkę).

Jak wyznaczyć te zestawy IB dla każdego levelu geomipmappingu....ano już o tym praktycznie pisałem w artykule "Terrain Rendering part I". Wystarczy lekko zmodyfikować ten kod opierając się o ostatni rysunek i tylko trzeba uwzględnić, że każdy następy level będzie tworzył dwa razy większe kwadraty (wzdłuż i wszerz) niż poprzedni poziom.

kod przedstawia się mniej więcej tak :

REP(k, geoLevel){
    int power = 1 << k;
    vector face;
    REP(i, (sectorSize - 1) / power){
            REP(j, (sectorSize - 1) / power){
                //Pierwszy trójkąt
                face.PB(j * power + sectorSize * i);
                face.PB(j * power + sectorSize * i + power);
                face.PB(j * power + sectorSize * i + sectorSize * power);
                //Drugi trójkąt
                face.PB(j * power + sectorSize * i + sectorSize * power);
                face.PB(j * power + sectorSize * i + power);
                face.PB(j * power + sectorSize * i + sectorSize * power + power);
         }
    }
    //Tutaj musimy wygenerować VBO dla kazdego levelu geoMipMapping'u
}

gdzie power -  to 2 ^ k, geoLevel - to ilość poziomów geoMipMappingu, sectorSize - to ilość wierzchołków boku w jednym sektorze.

Wiem, że kod wygląda trochę zagmatwanie ale, trzeba wziąć kartkę do ręki i spróbować zrozumieć to (chodź wszystko zostało wytłumaczone wcześniej, więc myślę, że bez problemu zrobisz własną wersją towrzenia IB).

Teraz tylko wystarczy dla każdego środkowego weirzchołka sektora w zależność od odległości od kamery wybrać odpowiedni poziom. Jak to zrobisz to już twoja sprawa, ja narazei nie mam żadnej dobrej aproksymacji wybierania levelu. Pewnie uzależnię odpowiedni poziom za pomocą funkcji liniowej.

Teraz znając level danego sektora wystarczy tylko to wyrenderować.

glDrawElements(GL_TRIANGLES, countElement, GL_UNSIGNED_INT, (int*)offset);

countElement to liczba elementów danego geoMipMappingu (musimy sobie gdzieś to pozapamiętywać w jakiejś tablicy dla każdego levelu), a offset to przesunięcie od, którego wierzchołka mamy renderować. Dla wierzchołka w wierszu 'i' i kolumnie 'j', offset będzei wynosił 

countElement * (i * numOfSectors + j) * sizeof(unsigned int)

numOfSectors to ilość sektorów na jeden bok dla całej heightmapy. To już wszystko poniżej screen przykładowy.


Widzimy jednak przeszkadzające cięcia w geometrii. Niestety jest to efekt nieunikniony gdy 2 sąsiadujące ze sobą sektory będą posiadać inny poziom geomipmappingu. Są na to sposoby by tego uniknąć lecz to już temat na całkiem nowy artykuł...

piątek, 27 sierpnia 2010

Terrain Rendering part II

Witam!

Dziś chciałbym ożywić nasz teren dodając teksturowanie. Ograniczenie jest takie, że możemy wykorzystać do 3 tekstur na teren jak pisałem we wcześniejszej części.

Detail Map z programu EarthSculptor
W ogóle co to jest ta detail mapa ? Krótko mówiąc detail mapa dodaje do terenu.... detale (najprostsze rozwiązanie wy renderowania terenu to mieć 1 teksturę ze wszystkimi kolorami nałożoną na cały teren a drugą z detalami /rysunek poniżej/ nałożoną kilkakrotnie na całą mapę dodając jej poziom szczegółowości)


W programie EarthSculptor jest trochę inaczej tam detail mapa określa poziom mieszania z każdej z 3 tekstur (na co przeznaczony jest 1 zestaw texCoordów)

Popatrzmy na przykładową detail mapę z programu EarthSculptor


Jak określony jest ten poziom mieszania dla każdego wierzchołka ? To proste. Każdy piksel zawiera składowe RGB w zakresie 0 - 255 (lub 0.0 - 1.0 jak w w OpenGL) i właśnie każda z tych składowych określa stopień użycia każdej tekstury (będziemy jeszcze mówić o tym później jak policzyć kolor wynikowy dla każdego wierzchołka)

Obliczanie 2 zestawów TexCoordów
Pierwszy zestaw TexCoordów jak wspomniałem wyżej służy do nałożenia całej detail mapy na cały teren.
No dobra ale potrzebujemy jeszcze jednego zestawu aby tekstury nie były rozciągnięte. Właśnie za pomocą tych TexCoordów będziemy wyświetlać tekstury (tamten zestaw służy tylko do nadania kolorów). W programie tekstury nakładane mają wymiary 16 x 16 jednostek (liczyłem ręcznie) i tak samo ja postanowiłem zrobić u siebie.

Obliczenie tych 2 zestawów nie jest ciężkie. Spójrzmy na kod poniżej znajdujący się umnie w tej samej funkcji Load co w pierwszym rozdziale.

vector texCoord0, texCoord1;
//texCoord0 to zestaw odpowiedzialny za nakładanie kwadratowych tekstur 16 x 16 tak by mapa była szeczółowa
//texCoord1 to zestaw odpowiedzialny za nałożenie całej detail mapy na teren

for(int i = 0; i < height; ++i) //height - wysokość heightmapy
   for(int j = 0; j < width; ++j){ //width szerokość heightmapy
      texCoord0.PB(Vec2((float)i / 16, (float)j / 16));
      texCoord1.PB(Vec2((float)i / height, (float)j / width));
   }

Jeżeli ktoś nie wie jak nakładane są tekstury to zapraszam to krótkiego wytłumaczenia pod spodem, jeżeli ktoś wie to nie musi tego czytać.
Nakładanie tekstur w OpenGL' u
Spójrzmy na poniższy rysunek

Współrzędne pokazane na rogach należy czytać w ten sposób, że : (x', y') oznacza że wybrana tekstura zostanie nałożona na wierzchołek wzdłuż osi x w x' * 100% i w y' * 100% wzdłuż osi y, czyli na powyższym rysunku tekstura zostanie nałożona na kwadrat w całości. No dobrze a jakie współrzędne powinny mieć te wierzchołki pozostałe ? Cóż... wartości pośrednie. Jeżeli znamy wysokośc i szerokość obrazka w pikselach to każdy piksel odpowiada
jednemu wierzchołkowi więc współrzedne powinny wygladać tak : (i / height, j / width) gdzie i to odpowiedni wiersz a 'j' odpowiednia kolumna na heightmapie.

A co będzie oznaczać taki zapis : texCoord0.PB(Vec2((float)i / 16, (float)j / 16)); ? Co 16 wierzchołków wzdłuż każdej z osi wartość będzie osiągać wartość 1 więc w całości tekstura będzie nakładana co 16 wierzchołków wzdłuż każdej z osi. No dobrze a co się dzieje z wartościami większymi od 1 ? Cóż, standardowo OpenGL powiela teksturę więc tak naprawdę tekstura będzie powtarzana co 16 wierzchołków w pionie i w poziomie.

Więcej informacji o teksturowaniu znajdziemy tutaj(również o tym jak je ładować do pamięci i jak bindować, w ogóle Nehe to ogromna skarbnica wiedzy o OpenGLu) : Nehe pl

LightMap & ColorMap
Light mapa to statyczna mapa światła, bardzo łatwo dodaje efekt światła statycznego do naszego terenu praktycznie bez żadnych obliczeń, po prostu modyfikuje wynikowy kolor.

Przykład light mapy :



Color mapa robi to samo co light mapa tzn modyfikuje wynikowy kolor ale dodaje kolory do otoczenia by wyglądał różnorodnie.

Przykład color mapy :

Teoretycznie można połączyć lightmape z colormapą odrobinę upraszczając obliczenia przesyłając tylko wynikową teksturę light i color mapy, ale ja potraktowałem je jako osobne tekstury na razie.

Wyświetlanie naszego terenu z teksturami

Czas nadszedł by wszystko wyrenderować. Na początku oczywiście trzeba 2 zestawy TexCoordów wrzucić do VBO:

glGenBuffersARB(1, &vboTexCoord0);
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vboTexCoord0)
;glBufferDataARB(GL_ARRAY_BUFFER_ARB, (GLsizei)(texCoord0.size() * sizeof(Vec2)), &texCoord0.front(), GL_STATIC_DRAW_ARB);
glGenBuffersARB(1, &vboTexCoord1);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboTexCoord1);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, (GLsizei)(texCoord1.size() * sizeof(Vec2)), &texCoord1.front(), GL_STATIC_DRAW_ARB);

Teraz wystarczy zbindować odpowiednie 3 tekstury dla terenu (takie jakie sobie życzymy), detail mape, light mapę, color mapę, napisać shader do mieszania(poniżej się znajduje) i... wszystko wyrenderować.

Mieszanie kolorów w Pixel Shaderze

Kod pixel shadera do mieszania wygląda u mnie tak:

#version 120

uniform sampler2D tex0; //detail map1
uniform sampler2D tex1; //texture1 detail map1
uniform sampler2D tex2;    //texture2 detail map1
uniform sampler2D tex3;    //texture3 detail map1
uniform sampler2D tex4; //color map
uniform sampler2D tex5; //light map

void main(){

    //kolor danego piksela, który określa intensywność danej tekstury dla danego wierzchołka
    vec4 intensity = texture2D(tex0, gl_TexCoord[1].st);

    vec4 color1 = texture2D(tex1, gl_TexCoord[0].st); //tekstura pierwsza terenu
    vec4 color2 = texture2D(tex2, gl_TexCoord[0].st); //tekstura druga terenu
    vec4 color3 = texture2D(tex3, gl_TexCoord[0].st); //tekstura trzecia terenu

    vec4 colorMap = texture2D(tex4, gl_TexCoord[1].st); //color map
    vec4 lightMap = texture2D(tex5, gl_TexCoord[1].st); //light map

     vec4 result = mix(color1, color2, intensity.g );
     result = mix(result, color3, intensity.b);

    gl_FragColor = result * colorMap * lightMap;
}

vec4 result;
ta zmienna jest rezultatem naszego mieszania
result = mix(color1, color2, intensity.g ); //mieszanie pierwszej tekstury z drugą za pomocą funkcji mix
result = mix(result, color3, intensity.b); //mieszanie poprzedniego rezultatu z teksturą trzecią

Funkcja mix(value1, value2, factor) to tak zwana interpolacja liniowa. Zwraca to wartość pośrednią pomiędzy value1 a value2. Factor określa poziom tego mieszania i przymuje wartości od 0 - 1. Zero oznacza całkowitą wartość value1,a jeden całkowitą wartość value2 (albo na odwrót nie pamiętam:)). Wartości pomiędzy tym zakresem określają odpowiedni poziom wykorzystania dwóch wartości, dzięki czemu powstaje bardzo płynne i ładne mieszanie.

DEMO DO ŚCIĄGNIĘCIA TUTAJ

To już jest koniec na koniec jako główny smaczek tego artykułu chciałbym pokazać krótki filmik z renderingu terenu :)


Pozdrawiam Norbert G.

poniedziałek, 23 sierpnia 2010

Makra i typedefy używane dotąd w silniku

Tutaj chciałbym przedstawić listę makr używanych w silniku. Niby nie dużo ale ułatwiają życie ;)(na samym dole przykład użycia makra.

#define ALL(c) (c).begin(), (c).end()
#define FOR(a, b, c) for(int a = b; a <= c; ++a)
#define FORD(a, b, c) for(int a = b; a >= c; --a)
#define FOREACH(i, c) for(VAR(i, (c).begin()); i != (c).end(); ++i)
#define INS insert
#define INF 1000000000
#define MP make_pair
#define PB push_back
#define PR 1339992061
#define REP(x, p) for(int x = 0; x < p; ++x)
#define REPD(x, p) for(int x = p - 1; x >= 0; --x)
#define REPD_1(x, p) for(int x = p; x >= 1; --x)
#define REP_1(x, p) for(int x = 1; x <= p; ++x)
#define VAR(v, n) __typeof(n) v = (n)
#define ST first
#define ND second

typedef long long LL;
typedef pair PII;
typedef pair PDD;
typedef vector VI;
typedef vector > VII;
typedef vector > VDD;


Pętle rodzaju for(int i = 0; i < 123213; ++i),
możemy zamienić używając makra REP na :
REP(i, 123213), prawda że ułatwia pisanie ?

Terrain Rendering part I

Praca oczywiście powolutku posuwa się do przodu. W tym pości wreszczie wyrenderujemy coś pożytecznego a mianowicie postaram się opisać jak wyrenderować teren.

Teren oczywiście będzie składać się z tak zwanej mapy wysokości (heightmapy).

W swoim silniku mam już gotowy kod importujacy kod z niezwykle prostego programu EarthSculptor.
Ograniczenie jakie nakładam narazie jest takie, że możemy dodać narazie 3 różne tekstury na teren (chodź myślę, że tyle wystarczy.

Przykładowy screen z programu : 

Dobra zacznijmy od początku.
Program dostarcza nam takie pliki jak : zwykłą heightmapę z których liczymy wysokości, lightmapę - tzw statyczną mapę cieni, detail map - zawiera informację jak mieszać tekstury dla każdego wierzchołka.


Wczytywanie heightmapy : w silniku mamy możliwość odczytu tekstur narazie w formacie BMP oraz TGA, lecz ich działanie opiszę w innym poście i pokażę jak mamy to wczytywać (z gotowym kodem).


Klasa wczytująca bitmapę zawiera sporo metod i pół a narazie nie chcę dawać kodu źródłowego ponieważ pracuję jeszcze nad geomipmappingiem, kiedy to skończę to dopiero dam cały kod.

Narazie przyjrzyjmy się jednej metodzie :

void EvoHeightMap::Load(EvoTexture* heightMap, int amountOfSectors, float xzScale = 1.0f, float yScale = 1.0f);

Pierwszy parametr to obiekt tekstury  z której będą wyznaczane wysokości wierzchołków.
Drugi parametr to liczba sektorów, które są kwadratami i MUSZĄ być potęgą liczby 2.
Trzeci parametr skaluje wierzchołki na osiach x i z.
Czwarty parametr skaluje wysokość.

Samo wyznaczanie wysokości nie jest niczym skompilowanym, na początku tworzymy odpowiednie tablice dynamicznie:

void EvoHeightMap::Load(video::EvoTexture *heightMap, int amountOfSectors, float xzScale, float yScale){
int height = heightMap->GetHeight(), width =heightMap->GetWidth();

int halfHeight = height / 2, halfWidth = width / 2, counter = -1;

Vec3** terrain, **normal;


        terrain = new Vec3*[height + 1];
        normal = new Vec3*[height + 1];
        REP(i, height + 1){
            terrain[i] = new Vec3[width + 1];
            normal[i] = new Vec3[width + 1];
        }


tutaj następuje ustawianie położenia wierzchołka w przestrzeni 3D


REP(i, width)
            REP(j, height){
                terrain[i][j] = Vec3((-halfWidth + j) * xzScale,
                                     (data[++counter] + data[++counter] + data[++counter]) / 3.0f * yScale - halfHeight * yScale,
                                     -(-halfHeight + i) * xzScale);
            }


haflWidth i halfHeight to zmienne pomocnicze by środek modelu znajdował się w punkcie (0,0,0)
REP to zwykłe makro pętli, a wygląda tak :


#define REP(x, p) for(int x = 0; x < p; ++x) czyli zwykła pętla:)

Pozostało nam jeszcze obliczyć normalne, ale żeby to zrobić musimy znać wszystkie trójkąty na mapie, by wiedzieć do których trójkątów należy dany wierzchołek. Spójrzmy na poniższy rysunek(nie dokońca podpisałem wierzchołki):



 można zauważyć pewną regularność tworzenia kolejnych trójkątów. Mianowicie trójkąty składane są z następujących wierzchołków : (0, 1, 4); (4, 1, 5); (1, 2, 5); (5, 2, 6) itd.
Mianowicie znając szerokośći wysokość naszej mapy (która z założenia jest kwadratowa i jest potęgą liczby 2).

Otrzymujemy następujący kod na otrzymanie wszystkich indeksów potrzebnych do ułożenia całej naszej siatki.

vector

REP(i, width - 1){
            REP(j, height - 1){
                //Pierwszy trójkąt
                face.PB(j + width * i);
                face.PB(j + width * i + 1);
                face.PB(j + width * i + height);
                //Drugi trójkąt
                face.PB(j + width * i + height);
                face.PB(j + width * i + 1);
                face.PB(j + width * i + 1 + height);
         }
}

Skoro mamy już wsyzstkie indeksy wierzchołków, możemy przystąpić do liczenia normalnych.
 Jak policzyć wektor normalny dla pojedyńczego trójkąta ?








Potrzebujemy teraz 2 wektorów. Musimy wykorzystać wsyzstkie 3 wierzchołki i wyznaczyć dwa różne wektory niech to będzie np:

w1 = v1 - v2;
w2 = v1 - v3;

i teraz wystarczy policzyć iloczyn wektorowych tych 2 wektorów, znormalizować.. i mamy wektor normalny powierzchni. Prawda że proste ?

Co zrobić jeżeli wierzchołek należy do kilku trójkątów na raz (tak jak u nas na heightmapie wierzchołek należy do kilkut trójkątów naraz)? Ja rozwiązałem to tak, że otrzymane wektory poprostu do siebie dodaję a następnie wszystko normalizuję i w ten sposób otrzymuję wektor normalny dla każdego wierzchołka.

Kod, który to robi przedstawia się tak (wykorzystuję zadeklarowaną wcześniej tablicę normal) :

        for(int i = 0; i < face.size(); i += 3){
            Vec3 v1 = terrain[face[i] / width][face[i] % width] - terrain[face[i + 1] / width][face[i + 1] % width];
            Vec3 v2 = terrain[face[i] / width][face[i] % width] - terrain[face[i + 2] / width][face[i + 2] % width];
            Vec3 n = Dot(v1, v2); //Wektor normalny powierzchni
            Normalize(&n);
            normal[face[i] / width][face[i] % width] += n;
            normal[face[i + 1] / width][face[i + 1] % width] += n;
            normal[face[i + 2] / width][face[i + 2] % width] += n;
        }
        REP(i, width)
            REP(j, height)
                Normalize(&normal[i][j]);

Na początku idę przez wszystkie trójkąty (każdy trójkąt składa się z 3 wierzchołków dlatego jest tam i += 3).







Vec3 v1 = terrain[face[i] / width][face[i] % width] - terrain[face[i + 1] / width][face[i + 1] % width];
Vec3 v2 = terrain[face[i] / width][face[i] % width] - terrain[face[i + 2] / width][face[i + 2] % width];

Te 2 linijki obliczają 2 wektory tak jak pisałem wyżej z tym trójkątem. Zapis może wydaje się dziwny ale zauważmy że tablica face jest 1 wymiarowa a ja mam tablicę terrain dwuwymiarową. Jeżeli zastanowisz się to napewno zrozumiesz jak to działa.
Vec3 n = Dot(v1, v2); //Wektor normalny powierzchni
Normalize(&n);

Potem obliczam  wektor normalny powierzchni funkcją Dot (jest w biblioteczce matematycznej jak również Normal i wiele funkcji, tylko wystarczy tam zajrzeć), a następnie normalizuję ten wektor.

Teraz tylko dodaję do każdego wierzchołka nowo otrzymany wektor(ta sama uwaga z tablicami co wcześniej):

normal[face[i] / width][face[i] % width] += n;
normal[face[i + 1] / width][face[i + 1] % width] += n;
normal[face[i + 2] / width][face[i + 2] % width] += n;

A na koniec wszystkie wektory normalizuję i tak mamy wektory normalne dla każdego wierzchołka:

REP(i, width)
      REP(j, height)
            Normalize(&normal[i][j]);

Teraz wystarczy tylko utworzyć VBO i wyrenderować :)
Ja utowrzyłem sobie 2 tablice pomocnicze dla VBO, bo kod jest bardziej czytelny dla mnie

vector vertex, normalCoord;

REP(i, height + 1)
      REP(j, width + 1){
            vertex.PB(terrain[i][j]);
            normalCoord.PB(normal[i][j]);
     }
a tutaj wspomniane vbo.

glGenBuffersARB(1, &vboVertex);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboVertex);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, (GLsizei)(vertex.size() * sizeof(Vec3)), &vertex.front(), GL_STATIC_DRAW_ARB);
        vertex.erase(ALL(vertex));


glGenBuffersARB(1, &vboNormal);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboNormal);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, (GLsizei)(normalCoord.size() * sizeof(Vec3)), &normalCoord.front(), GL_STATIC_DRAW_ARB);
        normalCoord.erase(ALL(normalCoord));

na koniec tylko wystarczy wygenerować Index Buffer (wspomniana wyżej tablica face) i możemy renderować:

glGenBuffersARB(1, &vboFace);
                glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vboFace);
                glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, (GLsizei)(face.size() * sizeof(unsigned int)), &face.front(), GL_STATIC_DRAW_ARB);

Teraz bindujemy nasze bufery i wykonujemy rendering:

//VBO z wierzchołkami
        glEnableClientState(GL_VERTEX_ARRAY);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboVertex);
        glVertexPointer(3, GL_FLOAT, 0, (char*)NULL);

//VBO z normalnymi
        glEnableClientState(GL_NORMAL_ARRAY);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboNormal);
        glNormalPointer(GL_FLOAT, 0, (char*)NULL);

//Index Buffer
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vboFace

No i rendering :

glDrawElements(GL_TRIANGLES, face.size(), GL_UNSIGNED_INT, 0);

A o to ujrzymy taki obraz mniej więcej obraz :


Co prawda jeszcze bez żadnych tekstur ale i tak efekt nie jest zły.
To tyle w części pierwszej.

środa, 18 sierpnia 2010

EvoEngine - biblioteczka matematyczna

Dziś chciałbym przedstawić prostą biblioteczkę matematyczną.

Główne typy danych to :
Vec2 - wektor/punkt 2D
Vec3 - wektor/punkt 3D
Vec4 - wektor/punkt 4D

Matrix - macierz 4x4

Quaternion / Quat - kwaternion

Biblioteczka zawiera kilka podstawowe przęciąrzone operatory ułatwiające operowanie typami danych.

Zawiera również szeroki wachlarz podstawowych funkcji :

-Normalizacja (sprowadzanie wektora do długości jeden);
-Interpolacja liniowa oraz kwadratowa;
-Różne przekształcania -
   -macierz - kwaternion i odwrotnie 
   - odwrotność macierzy
   - przekształcanie wektorów przez macierze
itp.

Nie będę przedstawiać tutaj kodu źródłowego ponieważ zajmuje grubo ponad 1000 lini. Można go ściągnąć z źródeł mojego projektu (TUTAJ DO POBRANIA - EvoMath.zip), przez co bardzo zachęcam do testowania funkcji i podawanie czego jeszcze tu brakuje (a wiem, że i tak będzie to jeszcze z pewnością uzupełniane).

czwartek, 12 sierpnia 2010

Evolution Engine - krótko co i jak

Silnik postanawiam pisać w C++ + jakaś biblioteka (narazie jest to SDL, ale bardzo możliwe, że to ulegnie zmianie), do inicjalizacji podstawowych czynności (tworzenie okna, dodanie kontekstu OpenGL, podwójne buforowanie, etc.) + OpenGL v 2.1 + shadery w GLSL v 1.2.
Nie posiadam zbyt dobrego sprzętu (karta ATI Mobility HD 2300), więc powinien chodzić on każdemu na kartach podobnych do moich i wzwyż.

Krótko o silniku :
Evolution Engine ma być silnikiem do gier komputerowych, który ma w dużym stopniu ułatwić ten proces.

Chodź istnieje wiele darmowych i profesjonalnych silników takich jak Irrlicht, Ogre, to od zawsze chciałem stworzyć coś samemu. Teraz mam okazję się wykazać.

Silnik ma służyć (jak narazie w założeniach) do renderowania scen outdoors.

Główne moduły :
 - grafika (rendering terenu, modelów, efektów, itp)
 - fizyka (kolizje, zjawiska)
 - dźwięk
 - przyjazne GUI

Jako, że jest to silnik do gier więc na początku skupię się tylko i wyłącznie na module grafiki.

Pozdrawiam Norbert G.

Witam :)

Nie będę się rozpisywał bo i tak mało osób czyta takie posty...

Blog powstał w celu edukacyjnym bym dzielił się swoimi doświadczeniami wraz z innymi oraz co ważniejsze dla mnie poznawał nowych technik.

Na tym blogu będę starać się umieszczać najnowsze informacje na temat moich prac silnika graficznego "Evolution Engine". Chciałbym zaznaczayć, iż jest to pierwszy mój silnik graficzny, który postanowiłem napisać.

Dlaczego wybrałem język polski a nie angielski ? Cóż tak naprawdę nie ma tak naprawdę dużej ilości informacji na temat różnych zagadnień związanych z grami a ja chcialbym aby wszyscy rozumieli posty bez znajomości języka ang (chodź pewnie duża część z was zna go w znacznie lepszym od mojego), chodź czasem może będę i pisac po angielsku, by go doszlifowywać.

Zachęcam do przeglądania bloga co jakiś czas komentowania, dzielenia się moją wiedzą i oczywiście promować własne blogi, na które na pewno zajrzę:)

Pozdrawiam Norbert G.