Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

Zawód: murarz

Tekst został importowany z Warsztatowych artykułów. Jego oryginalnym autorem jest Rafał Kozik. Jeżeli został importowany poprawnie, usuń ten szablon!

Zawód: murarz - Rafał 'revo' Kozik

Wstęp

Artykuł przedstawia prostą alternatywę dla użycia powtarzalnych tekstur do rysowania kafelków, cegieł, paneli itp. Materiał w nim przedstawiony jest średnio trudny, jednak wymaga podstawowej znajomości pixel shadera.

Typowe rozwiązanie

Jeżeli chcesz narysować mur z teksturą z cegieł, to pewnie użyjesz tekstury podobnej do tej poniżej i użyjesz koordynatów spoza zakresu [0,1], aby ją powielić:


Rys 1. Tekstura dla muru

Niestety ma ono jedną bardzo dużą wadę. Bardzo łatwo zauważyć powtarzający się wzorzec, a przez to wygląda to mało realistycznie:


Rys 2. Tekstura powtórzona czterokrotnie na wysokości i szerokości
Pomysł

Zamiast przedstawionej na początku tekstury użyjemy takiej tekstury i będziemy starali się wybrać cegłówkę losowo:


Rys 3. Nasza nowa tekstura

Jeżeli wzór nie będzie się za bardzo powtarzał, to w teorii powinno wszystko wyglądać znacznie lepiej niż w pierwotnym rozwiązaniu. Prezentowany tu kod był pisany w programie RenderMonkey, z niego też pochodzą zrzuty ekranu.

Generowanie liczb losowych

Chwilowo będziemy bawić się sześcianem, którego ścianki mają koordynaty tekstury od [0,0] do [32,32]. Na początku wypadałoby obliczyć wiersz i kolumnę:

float row = floor(Input.Texcoord.y) + 1;
float col = floor(Input.Texcoord.x) + 1;

Teraz będziemy chcieli dla każdego wiersza i kolumny dostać jakąś w miarę losową, całkowitą, wartość od 0 do 3. Wyniki wyświetlimy używając tej wartości podzielonej przez 4. Na początku próbowałem mnożyć wiersz i kolumnę przez liczby pierwsze:

float n = fmod(7 * col + 11 * row, 4);

Rezultat nie jest jednak zbyt ciekawy:


Rys 4. Pseudolosowość, podejście pierwsze

Metodą prób i błędów udało mi się jednak dojść do całkiem ciekawych rezultatów, które zdecydowanie wystarczają do prezentowanego tu zagadnienia:

float a = 1.37 * row * sin(col);
float b = 0.42 * col * sin(row);
float r = frac(a + b);
float n = floor(r * 4);

Rys 5. Wynik funkcji otrzymanej metodą prób i błędów

Może komuś uda się dojść do jakiejś prostszej funkcji, która tu wystarczy. Można też użyć dodatkowej tekstury z szumem i z niej próbkować odpowiednie wartości.

Budujemy mur

Teraz będziemy bawić się sześcianem, który ma koordynaty tekstury z zakresu od [0,0] do [4,8]. Mamy już obliczone, które cegły będą miały zostać użyte w którym miejscu, teraz wystarczy odpowiednio wybrać koordynaty:

Input.Texcoord.x = frac(Input.Texcoord.x);
Input.Texcoord.y = 0.25f * n + frac(Input.Texcoord.y) / 4;

Rys 6. Cegły po raz pierwszy

Nie jest źle, udało nam się już wyświetlić cegły, jednak nie jest to do końca to co byśmy chcieli:

  • Cegły są zdecydowanie za równo.
  • Ponieważ koordynaty tekstury gwałtownie zmieniają się na łączeniach, powstają duże artefakty przez błędne użycie mip-map.
Rozsunięcie cegieł

Ta część jest prosta - po prostu chcemy, aby nieparzyste wiersze były przesunięte o pół cegły. Wystarczy zatem przed obliczeniem kolumny dodać:

Input.Texcoord.x += fmod(row, 2) / 2;

Rys 7. Cegły po raz drugi
Naprawa mip-map

Jak wspomniałem wcześniej, błędy użycia mip-map powstały przez to, że koordynaty próbkowanej tekstury zmieniały się gwałtowanie na łączeniach. Przez to ich zmiana względem współrzędnych ekranowych była na tyle duża, że próbkowana była inna (mniejsza) mip-mapa niż byśmy tego chcieli. Musimy zatem sprawić, żeby próbkowanie odbywało się w inny sposób. W poziomie tekstura jest próbkowana z taką częstością jak podano w parametrze shadera. W pionie natomiast cztery razy wolniej, ponieważ używamy tylko jednej z czterech cegieł. Zatem możemy udać taką zmianę koordynatów tekstury i podać obliczone ddx i ddy do samplera:

float2 wallSpaceTexcoord = Input.Texcoord;
wallSpaceTexcoord.y /= 4;
return tex2D(baseMap, Input.Texcoord, ddx(wallSpaceTexcoord), ddy(wallSpaceTexcoord));

Rys 8. Cegły po raz trzeci, sprzedane!

Aby upewnić się, że pobierane są takie mip-mapy jak chcemy i że jedynymi miejscami gdzie nastąpiła znaczna zmiana są łączenia cegieł, możemy zerknąć na różnicę:


Rys 9. Różnica obrazów bez i z korekcją mip-map
Po jasnej stronie kodu

Poniżej znajduje się cały kod pixel shadera z komentarzami:

sampler2D baseMap;

struct PS_INPUT 
{
   float2 Texcoord : TEXCOORD0;
};

float4 ps_main( PS_INPUT Input ) : COLOR0
{
   // tak zmieniają się koordynaty w przestrzenii naszej ściany
   float2 wallSpaceTexcoord = Input.Texcoord;
   // tylko 1/4 wysokości tekstury cegieł jest próbkowana na jedną cegłę

   wallSpaceTexcoord.y /= 4;
   
   float row = floor(Input.Texcoord.y) + 1;
   
   // w wierszach parzystych przesuwamy się o pół cegły
   Input.Texcoord.x += fmod(row, 2) / 2;
   
   float col = floor(Input.Texcoord.x) + 1;
   
   // próbujemy wygenerować pseudolosową liczbę
   float a = 1.37 * row * sin(col);
   float b = 0.42 * col * sin(row);
   float r = frac(a + b);
   
   // wybieramy numer cegły
   float n = floor(r * 4);
   
   Input.Texcoord.x = frac(Input.Texcoord.x);
   Input.Texcoord.y = 0.25f * n + frac(Input.Texcoord.y) / 4;
   
   return tex2D(baseMap, Input.Texcoord, ddx(wallSpaceTexcoord), ddy(wallSpaceTexcoord));
}
Podsumowanie

Okazało się, że w ten sposób możemy osiągnąć bardzo dobre rezultaty, a wizualne artefakty są pomijalne. Należy pamiętać jednak, żeby wszystkie składane elementy do siebie idealnie pasowały - w przeciwnym wypadku na łączeniach (zwłaszcza przy przybliżeniu) widać będzie color bleeding. Możemy zastosować tę technikę dla cegieł, kafli, paneli, parkietu, kostki brukowej itp. Dzięki temu nie powinno to być aż tak powtarzalne i monotonne. W ten sposób można też próbkować mapy normalnych itp., aby dodać trochę detali. Niestety przez użycie ddx i ddy wymagany jest pixel shader w wersji conajmniej 2.0a.


Rys 10. The Wall

http://revo.pl/stuff/gdpl/murarz/

Tekst dodał:
revo
12.11.2008 23:34

Ostatnia edycja:
revo
29.03.2012 20:06

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#2 edytuj (poprz.) 29.03.2012 20:06 revo 7.8 KB (-489)
#1 edytuj (bież.) 12.11.2008 23:34 revo 8.28 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • ~hmmm ... 13 listopada 2008 02:15
    Jak przeczytałem tytuł artykułu to pomyślałem, że "znoooowu coś o niekonwencjonalnych klasach postaci w grach (c)RPG" ;p
  • Tomasz Dąbrowski (@Dab) 13 listopada 2008 11:24
    Bardzo fajny art. Można by jeszcze w sumie dodać wagi (prawdopodobieństwa) dla każdego "wzorku".
  • revo (@revo) 13 listopada 2008 15:37
    Można też dodać np. rotację o 180 stopni, odbicia w pionie/poziomie, rozjaśnianie/ściemnianie. Pomysłów jest jeszcze trochę ;)
  • Rafał Gaweł (@raver) 13 listopada 2008 16:09
    Proponuje zmienić nazwę tego artykułu na jakąś bardziej opisową, bo trudno zgadnąć o co z tym murarzem chodzi :/.
  • Piotr Karkut (@Kiro) 13 listopada 2008 16:12
    I to daje większą popularność :D Ja zasugerowany nazwą chciałem pojechać po temacie ale go przeczytałem arta;D
  • Kamil Szatkowski (@Netrix) 13 listopada 2008 18:02
    Świetny artykuł, pisz ich więcej, przydadzą się.
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)