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.

2 komentarze:

  1. Wydaje mi sie, ze zamiast tworzyc dwa zestawy coordow, wystarczy w shaderze przemnozyc te standardowe - mniej kodu i elegantsze rozwiazanie:
    vec2 detail_texcoord = gl_TexCoord[0].st * 16.0f;
    i potem zamiast 'gl_TexCoord[0].st' dac 'detail_texcoord' :)

    OdpowiedzUsuń
  2. owszem, można też tak nie ma problemu, nawet nie pomyślałem o tym, dzięki za słuszną uwagę :)

    OdpowiedzUsuń