11. Multiteksturowanie i macierze tekstur

Transkrypt

11. Multiteksturowanie i macierze tekstur
Multitexturowanie
Pod pojęciem multitexturowania (inaczej tekstur wielokrotnych) kryje się technika
nakładania kilku tekstur jednocześnie na bryłę. Oczywiście liczba takich tekstur jest ograniczona,
maksymalnie może być ich osiem. Oczywiście jest to liczba teoretyczna, gdyż faktycznie ich ilość
zależy od możliwości karty graficznej. Multitexturowania używa się np. Przy tworzeniu lightmap.
Wówczas jedna z tekstur pełni swoją tradydyjną rolę, zaś druga imituje oświetlenie. Zanim
przejdziemy do sedna sprawy należy omówić parę funkcji i ustawień związanych z teksturami.
Definiowanie zachowania się koloru wierzchołków i tekstury
Najczęściej tworzyliśmy sobie jakąś bryłę, ustawialiśmy kolory wierzchołków, nakładaliśmy
nań teksturę. W efekcie widzieliśmy kolory wierzchołków „prześwitujące” przez naszą teksturę. W
rzeczywistości Direct3D dokonał pewnej operacji między kolorami wierzchołków a kolorem
tekstury – ich wzjamne przemnożenie przez siebie. Poniżej pokazano taki przykład.
Wierzchołki po prawej stronie są białe, żeby lepiej można było
zaobserwować rzeczywiste barwy tekstury.
Możemy jedna zmienić to tradycyjne zachowanie się wierzchołków i tekstury przy jej nakładaniu.
Służą do tego odpowiednie funkcje - do modyfikacji argumentów oraz do modyfikacji operacji.
Domyslnymi argumentami są: kolor wierzchołków (D3DTA_DIFFUSE) oraz kolor tekstury
(D3DTA_TEXTURE), zaś domyślną operacją jest przermnożenie przez siebie pierwszego i
drugiego argumentu (D3DTOP_MODULATE).
Do zmiany operacji używamy funkcji:
device.SetTextureStageState 0, D3DTSS_COLOROP, wartość.
Pierwszy argument na razie przemilczę, drugi natomiast (D3DTSS_COLOROP) informuje
Direct3D, że będziemy modyfikowali operację na kolorach. Jako wartość wpisujemy zwykle:
D3DTOP_DISABLE - tekstura nie jest brana pod uwagę.
D3DTOP_MODULATE - czyli przemnożenie przez siebie pierwszego i drugiego argumentu. Efekt
widoczny na powyższym obrazku (ustawienie domyślne)
D3DTOP_MODULATE2X - podobnie jak wyżej z tą różnicą, że wynik mnożymy jeszcze przez
dwa. Dzięki takiej operacji zwiększamy jasność:
D3DTOP_MODULATE4X - podobnie jak wyżej z tą różnicą, że wynik mnożymy teraz przez
cztery. Dzięki temu jasność jest jeszcze większa.
D3DTOP_ADD - dodanie do siebie pierwszego i drugiego argumentu. Jeśli wynik wykracza poza
zakres (tzn jest większy niż kolor biały 1.0, 1.0, 1.0) to jest ograniczany do maksymalnej wartości,
czyli do koloru białego. Efekt taki obserwujemy na prawej ścianie bryły:
D3DTOP_SUBTRACT - odjęcie od pierwszego argumentu argumentu drugiego. Jeśli wynik jest
ujemny, to jest ograniczany do najmniejszej wartości, czyli do koloru czrnego (0.0, 0.0, 0.0). Efekt
taki powstał na prawej ścianie bryły:
Istnieje jeszcze cała masa flag D3DTOP - zapraszam do lektury DirectX7 SDK.
Pozostało jeszcze omówić ustawianie argumentów. Pierwszy argument modyfikujemy:
device.SetTextureStageState 0, D3DTSS_COLORARG1, wartość
Zaś drugi bardzo podobnie - jako drugi parametr ustawiamy D3DTSS_COLORARG2.
Jako wartość podajemy zwykle:
D3DTA_TEXTURE - argumentem jest kolor tekstury (wartość domyślna dla argumentu
pierwszego)
D3DTA_DIFFUSE - argumentem jest kolor wierzchołków
D3DTA_CURRENT - argumentem jest wynik z mieszania z poziomu niższego. Jeśli ustawienia
takiego dokonaliśmy na poziomie 0, to jest ono równoznaczne z D3DTA_DIFFUSE (szerzej
omówię to później).
D3DTA_SPECULAR - argumentem jest kolor odblasków wierzchołków.
D3DTOP_SELECTARG1 - na wyjście przepisywany jest pierwszy argument bez zmian.
Przykładowo - mamy tylko teksturę poziomu 0, jako pierwszy argument ustawiliśmy klor
wierzchołków, zaś jako drugi - kolor tekstuy. Wówczas na ekranie zobaczymy wyłącznie bryłę
pokrytą kolorem wierzchołków, bez tekstury. Gdybyśmy jako pierwszy argument ustawili kolor
tekstury, zaś jako drugi - kolor wierzchołków, to działanie będzie odwrotne - zobaczymy bryłą
pokrytą wyłącznie teksturą, bez wmieszania koloru wierzchołków.
D3DTOP_SELECTARG2 - działa podobnie jak D3DTOP_SELECTARG1, ale wybiera drugi
argument.
Zatem - jeśli w naszym programie nie dokonaliśmy żadnych modyfikacji argumentów ani operacji,
to ustawienia są następujące:
device.SetTextureStageState 0, D3DTSS_COLOROP, D3DTOP_SUBTRACT
device.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_TEXTURE
device.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_CURRENT
lub:
device.SetTextureStageState 0, D3DTSS_COLOROP, D3DTOP_SUBTRACT
device.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_TEXTURE
device.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE
Poziomy tekstur
Wpomniałem na wstępie, że istnieje możliwość nałożenia kilku tekstur na jedną bryłę, w nte
sposób, że np. pierwsza będzie prześwitywała przez drugą. Takich tekstur może być maksymalnie
do ośmiu, choć zwykle aż tylu nie potrzebujemy. Każda w ten sposób użyta tekstura jest
przechowywana w tzw. poziomie tekstury. Na ogół używaliśmy poziomu 0 (texture stage). Aby
dostać się do danej tekstury musimy się odwołać do odpowiedniego poziomu. Załóżmy, że w
naszym programie mamy stworzone dwie tekstury:
Texture1
Texture2
Chcemy je nałożyć na przedmiot tak, aby pierwsza tekstura była widoczna przez drugą, czyli
doprowadzić do czegoś takiego:
Będziemy używać dwóch poziomów tekstur: 0 i 1. Najpierw ustawiamy żądaną teksturę na danym
poziomie:
Device.SetTexture 0, Texture1
Pierwszy argument funkcji SetTexture oznacza poziom tekstury, drugi - teksturę.
Musimy jeszcze ustawić odpowiednią operację na kolorach. Wynik tej operacji jest może być
odebrany na poziomie pierwszym i przekazany jako jeden z argumentów:
Device.SetTextureStageState 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1
D3DTOP_SELECTARG1 - oznacza, że pierwszy z argumentów jest przepisywany na wyjście.
Gdybysmy mieli tylko jedną teksturę, to na ekranie zobaczylibysmy albo bryłę pokryta tylko
teksturą (pierwszym argumentem byłby wtedy D3DTA_TEXTURE) lub pokrytą tylko kolorem
wierzchołków (wtedy pierwszym argumentem byłby D3DTA_DIFFUSE.
Ustawiamy jeszcze argumenty:
Device.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_TEXTURE
Device.SetTextureStageState 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE
W zasadzie ustawienie argumentów na poziomie 0 nie jest konieczne, gdyż są to wartosci
domyślne.
Można ewentualnie włączyć filtrowanie tekstur dla danego poziomu tekstury.
Teraz zajmiemy się poziomem wyższym, czyli 1.
Device.SetTexture 1, Texture2 - na poziomie 1 ustawiamy drugą teksturę
Device.SetTextureStageState 1, D3DTSS_COLOROP, D3DTOP_MODULATE
Device.SetTextureStageState 1, D3DTSS_COLORARG1, D3DTA_TEXTURE
Device.SetTextureStageState 1, D3DTSS_COLORARG2, D3DTA_CURRENT
Działanie powyższych trzech linii jest następujące - jako pierwszy argument ustawiamy kolor
tekstury na poziomie 1. Jako drugi argument ustawiamy wynik mieszania z poziomu zero
(ustawiliśmy tam Device.SetTextureStageState 0, D3DTSS_COLOROP,
D3DTOP_SELECTARG1). Jednym słowem mamy takie argumenty - pierwszy - kolor tekstury z
poziomu 1, drugi - kolor tekstury z poziomu 0. Te dwa argumenty mnozymy przez siebie (pierwsza
z trzech powyższych linii kodu).
Na pierwszy rzut oka może się to wydawać nieco skomplikowane…dla ułatwienia podają graf tego,
co zrobiliśmy:
Argument 1
Kolor tekstury
(Tekstura1)
Argument 2
Kolor wierzchołków
bryły
Poziom 0
(Tekstura1)
Argument 1
Kolor wierzchołków
bryły
Wychodzi:
Kolor tesktury (Tekstura1)
Argument 2
Kolor tesktury (Tekstura1)
Poziom 1
(Tesktura2)
Wyjście na ekran:
Kolor Tekstura1 x Kolor Tekstura2
Imitacja dna morskiego stworzona za pomocą techniki multitexturowania i mgły. W celu
zwiększenia jasności użyto flagi D3DTOP_MODULATE2X przy operaci mnożenia przez siebie
kolorów tekstur dna oraz odblasków.
Macierze tekstur
Jak wiadomo macierze w DirectX służą do przeprowadzania transformacji - świata (world),
projekcji (projection) bądź widoku (view). Istnieje jeszcze jeden typ transformacji - transformacje
na teksturach. Działa to podobnie jak transforamacja świata - tu robiliśmy różne operacje
geomentryczne na naszych bryłach (przesunięcia, obroty, translacje itp.). Podobne rzeczy możemy
zrobić ze współrzędnymi tekstur, także przy pomocy macierzy. Dotychczas używaliśmy macierzy
4x4. W tym przypadku użyjemy tego samego rozmiaru. Jednak będziemy się posługiwali teksturami
dwuwymiarowymi (posiadają one dwie współrzędne u oraz v), więc w macierzach nieistotna będzie
cała czwarta kolumna oraz cały czwarty wiersz.
Zanim wszystko zaczniemy, należy poinformować Direct3D, że będziemy korzystali z macierzy
tekstur:
device.SetTextureStageState 0, D3DTSS_TEXTURETRANSFORMFLAGS, wartość
Pierwszy parametr funkcji (0) oznacza poziom tekstury - to poznaliśmy przy okazji
multiteksturowania.
D3DTSS_TEXTURETRANSFORMFLAGS - będziemy modyfikowali zachowanie się macierzy
tekstur.
Jako wartość podajemy najczęściej:
D3DTTFF_DISABLE - transformacje tekstur są wyłączone (domyślne ustawienie)
D3DTTFF_COUNT1 - przeprowadzamy operacje tylko na 1-wymiarowych współrzędnych tekstur
(czyli na u)
D3DTTFF_COUNT2 - przeprowadzamy operacje na 2-wymiarowych współrzędnych tekstur (czyli
na u oraz v).
D3DTTFF_COUNT3 - współrzędne są trójwymiarowe (dwie współrzędne tekstury i współczynnik
skalowania)
Jeśli mamy zamiar korzystać z dwuwymiarowych współrzędnych i jednej tekstury to ustawimy:
device.SetTextureStageState 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2
Pozostało jeszcze odpowiednio przygtować macierz transformacji. Jak w przypadku transformacji
świata korzystamy z typu D3DMATRIX.
Transformację można opisać zależnościami:
u = U*M11+U*M21+w*M31
v' = V*M12+V*M22 +w*M32
w' = x*M13+y*M23+w*M33
(w = 1)
Współrzędne transformowane maja postać (u, v), zaś przetransformowane (u', v'). Można też
korzystać z trójwymiarowych współrzędnych tekstury. Wówczas trzecia współrzędna to tzw.
współczynnik skalowania. Po przetransformowaniu u oraz v na u' i v' nowe współrzędne zostaną
jeszcze podzielone przez współczynnik skalowania, czyli ostatecznie będą miały postać:
(u'/w', v'/w'). W tym przypadku zamiast D3DTTFF_COUNT2 trzeba zastosować flagę
D3DTTFF_COUNT3.
Przykładowe macierze transformacji 2D:
Macierz translacji o wektor (Tx, Ty) będzie miała postać:
1
0
0
0
0
1
0
0
Tx
Ty
1
0
0
0
0
1
Macierz skalowania o wektor (Sx, Sy):
Sx
0
0
0
0
Sy
0
0
0
0
1
0
0
0
0
1
Macierz obrotu (sensowny jest obrót wokół osi Z) o kąt 
Cos  sin  0
0
-sin  cos  0
0
0
0
1
0
0
0
0
1
Oczywiście obrót będzie wykonywany względem wierzchołka, który miał przypisane współrzędne
tekstury równe (0, 0).
Na koniec pozostało ustawienie transformacji na podstawie wcześniej przygtowowanej macierzy.
Dokonujemy tego za pomocą funkcji:
device.SetTransform D3DTRANSFORMSTATE_TEXTUREn, matTexture
D3DTRANSFORMSTATE_TEXTUREn, gdzie n = 0….7 to poziom tekstury. Spotka nas drobna
niespodzianka. Powyższe stałe nie są zdefiniowane w bibliotece dx7.dll i musimy to zrobić sami
(nie mam pojęcia dlaczego). Mają następujące wartości
D3DTRANSFORMSTATE_TEXTURE0 = 16
D3DTRANSFORMSTATE_TEXTURE1 = 17
D3DTRANSFORMSTATE_TEXTURE2 = 18
D3DTRANSFORMSTATE_TEXTURE3 = 19
D3DTRANSFORMSTATE_TEXTURE4 = 20
D3DTRANSFORMSTATE_TEXTURE5 = 21
D3DTRANSFORMSTATE_TEXTURE6 = 22
D3DTRANSFORMSTATE_TEXTURE7 = 23
Zatem np. gdzieś w deklaracjach umieszczamy definicję odpowiedniej stałej:
Public Const D3DTRANSFORMSTATE_TEXTURE0 = 16
Public Const D3DTRANSFORMSTATE_TEXTURE1 = 17
Lub od razu wpisujemy wartość liczbową. Jednak taki zapis jest chyba mniej czytelny niż podanie
stałej.
matTexture - macierz przekształcenia współrzędnych tekstury.