Mapowanie sześcienne otoczenia (cubic environment mapping)

Transkrypt

Mapowanie sześcienne otoczenia (cubic environment mapping)
Mapowanie sześcienne otoczenia (cubic
environment mapping)
Mapowanie środowiska jest techniką pozwalającą na odwzorowanie otoczenia na
powierzchni przedmiotu przy użyciu specjalnie spreparowanej tekstury.
Mamy dwa rodzaje mapowania środowiska:
- sferyczne - tekstura zostaje przygotowana wcześniej w specjalnym programie graficznym
(najlepszy rezultat uzyskuje się dla tekstur, na których zastosowano filtr "rybiego oka").
Współrzędne tekstury zmieniają się dynamicznie w trakcie działania programu w zależności
od stanu macierzy widoku i przekształcenia świata.
- sześcienne - otoczenie jest renderowane do specjalnych tekstur (nazywanych mapami
sześciennymi) i nakładane na przedmiot.
Renderowanie do tekstury
Przed poruszeniem właściwego tematu należy wyjaśnić sposób renderowania sceny do
tekstury, gdyż technika ta będzie później używana.
Zwykle scena był renderowana do tylnego bufora a potem przerzucana na przedni w pętli
wyświetlania. Analogicznie możemy zrobić z teksturą, najpierw jednak należy ją oczywiście
utworzyć. Robimy to podobnie jak w przypadku zwykłych tekstur tworzonych na podstawie
plików graficznych.
Najpierw deklarujemy opis powierzchni DirectDrawSurface7, która będzie potem teksturą:
Dim ddsd As DDSURFACEDESC2
Opis ten należy odpowiednio uzupełnić:
With ddsd
.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
.ddsCaps.lCaps = DDSCAPS_TEXTURE Or DDSCAPS_3DDEVICE
.lHeight = texture_height
.lWidth = texture_width
End With
Najpierw uzupełniliśmy flagi - wartość DDSD_CAPS oznacza, że przy tworzeniu
powierzchni będzie brane pod uwagę pole ddsCaps, które okresliliśmy potem. Istotne są także
rozmiary tekstury, które określimy sami (nie tworzymy tekstury z pliku graficznego, więc
wymiary są nieznane).
Oczywiście należy zaznaczyć, że tworzona powierzchnia będzie używana jako tekstura
DDSCAPS_TEXTURE, a także, że będziemy do niej renderować obraz trójwymiarowy
(DDSCAPS_3DDEVICE)
Następnie określiliśmy rozmiary naszej tekstury.
Teraz już możemy ją utworzyć:
Set texture = DD.CreateSurface(ddsd)
Uwaga ! Jeśli nasza karta graficzna nie wspiera bezpośredniego renderowania do tekstury, to
można próbować renderować obraz do tylnego bufora, a następnie skopiować go do tekstury.
Rzecz jasna, będzie to trwało znacznie dłużej niż w pierwszym przypadku. Załóżmy jednak,
że wszystko poszło dobrze.
Teraz przyszedł czas na utworzenie ViewPortu skojarzonego z naszą teksturą. Strukturę tą
określaliśm zwykle w inicjalizacji i dotyczyła ona tylnego bufora. Określała na jakiej części
bufora będzie renderowany obraz. Tą samą sytuacją mamy teraz - chcemy renderować obraz
na całej strukturze, więc:
With texture_ViewPort
.lHeight = texture_height
.lWidth = texture_width
.lX = 0
.lY = 0
.maxz = 1
.minz = 0
End With
Texture_height, texture_width - jak łatwo się domysleć to rozmiary naszej tekstury. Jeśli tylne
bufor zawierał bufor głębokości, to powinno się go też dołączyć do tekstury.
Zmianom uległa także procedura renderowania obrazu. Do określenia bufora, do którego jest
renderowany obraz służy metoda:
Device.SetRenderTarget surface
W ten sposób można się przełączać pomiędzy buforami. Od tego momentu wszelkie
polecenia rysujące odnoszą się do bufora surface. Kiedy przełączymy się na rysowanie do
tekstury, należy też ustawić odpowiednio ViewPort - jeśli wymiary tekstury są inne niż
wymiary ekranu.
Załączony przykład pozwala obserwować model z dwóch stron - z punktu widzenia
obserwatora i z punktu widzenia kamery (to co ona widzi, jest wyświetlane na teksturze).
Sterowanie przy pomocy strzałek kursora i strzałek z klawiatury numerycznej.
Mapowanie sześcienne - teoria
Nasz przedmiot można umieścić w pewnym hipotetycznym sześcianie, którego ścianki
będą odpowiednią leżeć na osiach +X, -X, +Z, -Z itd.
Rozkładając ten sześcian w postaci płaskiej siatki otrzymamy:
Weźmy dla przykładu ściankę 0 (+X) i wyrenderujmy do niej odpiednio obraz. Najpierw
należy skierować kamerę tak, aby patrzyła dokładnie w kierunku osi +X. Można ją ustawić w
pozycji (0, 0, 0) i kazać patrzeć na punkt (1, 0, 0). Górę kamery ustawiamy jako (0, 1, 0) wektor określający górę kamery i kierunek patrzenia powinny być prostopadłe do siebie. Dla
ścianki -X będzie podobnie - zmieni się tylko punkt na który kamera patrzy (-1, 0, 0).
Korzystając z powyższego rysunku można łatwo określić posotałe wektory.
vFrom
vAt
vUp
+X (0, 0, 0)
(1, 0, 0)
(0, 1, 0)
-X
(0, 0, 0)
(-1, 0, 0)
(0, 1, 0)
+Y
(0, 0, 0)
(0, 1, 0)
(0, 0, -1)
-Y
(0, 0, 0)
(0, -1, 0)
(0, 0, 1)
+Z
(0, 0, 0)
(0, 0, 1)
(0, 1, 0)
-Z
(0, 0, 0)
(0, 0, -1)
(0, 1, 0)
Gdzie:
vFrom - pozycja kamery.
vAt - punkt na jaki patrzy kamera
vUp - wektor określający górę kamery.
Przemieszczamy kamerę, zmieniamy cel renderowania na odpowiednią mapę sześcienną,
czyścimy ją, a następnie renderujemy do niej obraz. Sprzęt zadba o odpowiednie
przeskalowanie powstałego obrazu, a także automatycznie wygeneruje współrzędne tekstury.
Tworzenie map sześciennych
Najpierw musimy zadecydować czy potrzebne będzie nam wszystkich sześć map
sześciennych. Im więcej, tym dłużej będzie trwało wyświetlenia całej sceny. Załóżmy, że
chcemy wykorzystać wszystkie sześć map sześciennych. Jak w przypadku każdej tekstury
będziemy korzystać z powierzchni DirectDrawSurface7Potrzebujemy sześciu takich
powierzchni, więc można zadeklarować tablicę.
Public Face(5) As DirectDrawSurface7
Daklarujemy także:
Dim ddsd As DDSURFACEDESC2 'opis powierzchni DirectDraw
Dim caps As DDSCAPS2 'właściwości powierzchni
Uzupełniamy strukturę ddsd:
With ddsd
.lFlags = DDSD_CAPS Or DDSD_WIDTH Or DDSD_HEIGHT
ddsd.lHeight = height
ddsd.lWidth = width
.ddscaps.lCaps = DDSCAPS_TEXTURE Or DDSCAPS_COMPLEX Or
DDSCAPS_3DDEVICE
.ddscaps.lCaps2 = DDSCAPS2_CUBEMAP Or DDSCAPS2_CUBEMAP_ALLFACES
End With
Pole lFlags jest dla nas zrozumiałe. Wymiary także. Należy jednak zaznaczyć, że powinny
być one potęgami dwójki, zaś z ich wielkością nie należy za bardzo przesadzać, gdyż im
większe mapy sześcienne, tym dłużej trwa renderowania, chociaż zwiększamy wtedy
dokładność odbitego obrazu. Mamy dwie nowości - flagę DDSCAPS_COMPLEX w polu
lCaps - oznacza to, że powierzchnia jest złożona i zawiera w sobie inne powierzchnie - jakie ?
To deklarujemy dalej.
Pole lCaps2 ustawione na powyższe wartości oznacza, ze nasza powierzchnia będzie mapą
sześcienną oraz, że chcemy wygenerować wszystkich sześć map sześciennych.
Poleceniem:
Set .Face(0) = DD.CreateSurface(ddsd)
Tworzymy podstawową mapę sześcienną. Jest to mapa patrzenia w dodatnim kierunku osi X.
Pozostałe mapy sześcienne są zawarte w powierzchni Face(0) i musimy się do nich jakoś
dostać, aby potem można było przełączać cel renderowania:
caps.lCaps2 = DDSCAPS2_CUBEMAP_NEGATIVEX Or DDSCAPS2_CUBEMAP
Set .Face(1) = .Face(0).GetAttachedSurface(caps)
W powyższy sposób tworzymy mapę patrzenia w ujemnym kierunku osi X.
Polecenie:
Set .Face(1) = .Face(0).GetAttachedSurface(caps)
wyciąga z podstawowej mapy powierzchnię wewnętrzną i kojarzy ją z powierzchnią Face(1)
Podobnie postepujemy dla pozostałych map.
Kiedy już nasza mapa sześcienna (czy raczej mapy) są stworzone, należy jeszcze utworzyć
ViewPort z nimi skojarzony - ponieważ będziemy renderować na całych mapach, a nie na ich
fragmentach, więc pola lHeight i lWidth ustawiamy odpowiednio na wysokość i szerokość
mapy sześciennej.
Spójrzmy dalej na procedurę renderowania obrazu. Najpierw wywołujemy procedurę
RenderEnvMap, która przygotowuje mapę sześcienną. Na początku zapamiętujemy
ważniejsze ustawienia - np. macierz przekształcenia wodoku czy ViewPort zwiżany z tylnym
buforem. Służą do tego serie wywołań device.GetTransform i device.GetViewport. Następnie
ustawiamy wektor pozycji kamery na (0, 0, 0), bo nasz przedmiot ma środek właśnie w tym
punkcie. Potem zmieniamy cel renderowania na kolejną mapę sześcienną, ustawiamy punkt
na jaki patrzy kamera, górę kamery, tworzymy macierz widoku:
DX.ViewMatrix MatView, vFrom, vat, vup, 0
I ustawiamy ją:
Device.SetTransform D3DTRANSFORMSTATE_VIEW, MatView
Kąt widzenia powinien być ustawiony na 90 stopni (w przeciwnym wypadku, obrazy na
poszczególnych sciankach będą zachodzić na siebie). Jeśli tak nie jest, to należy
zmodyfikować macierz projekcji:
DX.ProjectionMatrix MatProj, near, far, PI / 2
Device.SetTransform D3DTRANSFORMSTATE_PROJECTION, MatProj
Dalej renderujemy otoczenie, analogicznie jak przy renderowaniu do tylnego bufora. Gdy
obsłużymy już wszystkie mapy sześcienne, ponownie przełączamy się na renderowanie do
tylnego bufora.
Ponieważ podane wartości wektorów w tabelce dotyczyły sytuacji, gdy kamera jest
skierowana wzdłuż wektora (0, 0, 1), więc przy obrotach kamery musimy odpowiednio
przetransformować wektory podane w tabelce. Korzystamy przy tym z kąta oboru kamery
angle. Tworzymy macierz obrotu wzdłuż osi Y MatRotView (tylko wzdłuż tej osi obraca się
kamera w przykładzie). Dla kąta obrotu 90 stopni wektory kierunkowe dla map sześciennych
nie powinny być obrócone, dlatego odejmujemy 90 stopni. Aby przetransformować dany
wektor, należy go zwyczajnie przemnożyć przez daną macierz. Służy do tego procedura
VectorTransformCoord. Jeśli nasza kamera nie obracałaby się, to można od razu ustalić
wektory kierunkowe dla kamery i nie zmieniać ich potem.
Następnie renderujemy już całą scenę, wraz z przedmiotem na którym mają być odbicia.
Kiedy już wyświetliliśmy całe nasze otoczenia, to należy uczynić aktywną mapę sześcienną.
Dokonujemy tego jak w przypadku zwykłej tekstury, przy czym nakładamy mapę
podstawową:
Device.SetTexture 0, Face(0)
Należy jeszcze poinformować Direct3D, żeby automatycznie policzył współrzedne tekstury
dla mapy sześciennej:
Device.SetTextureStageState 0, D3DTSS_TEXCOORDINDEX,
D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR
Żeby przywrócić tradycyjne okreśolanie spółrzednyhc tekstur, czyli na podstawie danych tu I
tv zawartych w wektorach wywołujemu:
Device.SetTextureStageState 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU