![]() |
![]() ![]()
Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2011 mgr
Jerzy Wałaszek
|
Trójwymiarowa grafika komputerowa jest dynamicznie rozwijającą się dziedziną informatyki. Jakość obrazów komputerowych osiągnęła dzisiaj już taki poziom, iż film lub zdjęcie nie może być dowodem w sądzie (przykładem był pewien proces w USA, gdzie jako dowód pokazano zdjęcie oskarżonego popełniającego przestępstwo, na drugi dzień obrona przyniosła identyczne zdjęcie, na którym zamiast oskarżonego był prokurator - najlepsi eksperci nie znaleźli na nim żadnych śladów fotomontażu). Seria zajęć na kole informatycznym w I LO będzie miała na celu przybliżenie tej dziedziny zdolnym uczniom - niestety, materiał informatyki w liceum pomija te zagadnienia całkowicie. Powodem jest prawdopodobnie usunięcie rachunku macierzowego z programu nauczania matematyki. Z drugiej strony jest to dziedzina bardzo skomplikowana i jej opanowanie wymaga dużego wysiłku.
Podstawowym narzędziem nauki będzie język C++ oraz biblioteki SDL i OpenGL. Na początku zrealizujemy podstawowe zagadnienia w czystym SDL z wykorzystaniem naszej biblioteki newgfx. Następnie przejdziemy do środowiska OpenGL i poznamy profesjonalne metody tworzenia grafiki 3D. Podstawowym warunkiem sukcesu jest opanowanie prezentowanego tutaj materiału i wykonanie proponowanych ćwiczeń. Jeśli czegoś nie rozumiesz, zapytaj nauczyciela lub przeczytaj ponownie materiał. Nie pozostawiaj białych plam w swojej wiedzy, gdyż bardzo szybko stracisz orientację w "przestrzeni 3D" :).
Oś OX jest pozioma i biegnie w prawo.
Oś OY jest pionowa i biegnie do góry.
Oś OZ jest prostopadła do płaszczyzny osi OX i OY, biegnie w kierunku obserwatora.
Na osiach ustalmy miary, które nazwiemy współrzędnymi odpowiednio x, y i z. Początek układu współrzędnych znajduje się w punkcie o mierze 0 każdej z osi. Oznaczamy go za pomocą trójki liczb (0,0,0).
Umieśćmy w przestrzeni dowolny punkt P.
Dokonajmy teraz rzutów prostokątnych punktu P na płaszczyzny zawierające osie OX-OY (Πxy), OX-OZ (Πxz) i OY-OZ (Πyz):
Współrzędne tych rzutów odpowiadają współrzędnym x,y,z punktu w przestrzeni trójwymiarowej. Zatem każdy punkt będziemy określać trójką liczb x,y,z. W pamięci komputera będzie to struktura zawierająca 3 liczby zmiennoprzecinkowe:
};
Współrzędne x, y, z tych wierzchołków są następujące:
wierzchołek | x | y | z |
0 | -100 | 100 | 100 |
1 | 100 | 100 | 100 |
2 | 100 | -100 | 100 |
3 | -100 | -100 | 100 |
4 | -100 | 100 | -100 |
5 | 100 | 100 | -100 |
6 | 100 | -100 | -100 |
7 | -100 | -100 | -100 |
Wierzchołki możemy umieścić w tablicy V, której elementami są struktury typu vertex3D. W języku C++ robimy to następująco:
// Definicja wierzchołków figury const int VN = 8; // Liczba wierzchołków vertex3D V[] = {{-100, 100, 100},{ 100, 100, 100},{ 100,-100, 100},{-100,-100, 100}, {-100, 100,-100},{ 100, 100,-100},{ 100,-100,-100},{-100,-100,-100}};
Krawędź jest linią, którą będziemy rysowali od wierzchołka początkowego v1 do końcowego v2. Jeśli znamy numery tych wierzchołków, to linię definiujemy następująco:
struct line3D { int v1,v2; };
Ponumerujmy kolejne krawędzie naszego sześcianu:
W poniższej tabelce mamy definicje poszczególnych krawędzi sześcianu. Definicja określa kolejno numer wierzchołka startowego i numer wierzchołka końcowego krawędzi:
krawędź | v1 | v2 |
0 | 0 | 1 |
1 | 1 | 2 |
2 | 2 | 3 |
3 | 3 | 0 |
4 | 4 | 5 |
5 | 5 | 6 |
6 | 6 | 7 |
7 | 7 | 4 |
8 | 0 | 4 |
9 | 1 | 5 |
10 | 2 | 6 |
11 | 3 | 7 |
Podobnie jak wierzchołki, krawędzie również umieszczamy w tablicy, której elementami są struktury typu line3D:
// Definicja krawędzi figury const int LN = 12; // Liczba krawędzi line3D L[] = {{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7}};
Obiekt jest zdefiniowany.
Ustalmy położenie powierzchni graficznej SDL_Surface na płaszczyźnie Πxy - zawierającej osie OX i OY. Od razu umówmy się, że powierzchnia ta zostanie ułożona, tak aby środek układu współrzędnych pokrywał się ze środkiem powierzchni:
Jeśli przyjrzysz się dokładnie powyższemu rysunkowi, to stwierdzisz, że układ współrzędnych powierzchni SDL_Surface nie zgadza się z układem współrzędnych przestrzennych:
Wynika stąd konieczność przeliczenia współrzędnych przestrzennych na współrzędne powierzchni graficznej - nazwijmy je współrzędnymi ekranowymi. Jeśli tego nie zrobimy, otrzymamy obraz przesunięty i do góry nogami. Na szczęście transformacja jest bardzo prosta.
xe = w/2 + x
ye = h/2 - y
w - szerokość powierzchni SDL_Surface
h - wysokość powierzchni SDL_Surface
Teraz wyznaczymy obraz punktu P na płaszczyźnie SDL (pod uwagę będziemy brali tylko punkty, których współrzędna z jest nieujemna), najpierw we współrzędnych 3D, a później we współrzędnych samej płaszczyzny SDL_Surface. Istnieje kilka sposobów wyznaczania takiego obrazu - my zajmiemy się tzw. rzutem perspektywicznym. Zasada jest następująca:
W celu wyznaczenia współrzędnych x i y punktu P rozważmy dwa rzuty prostokątne tej sytuacji:
![]() na płaszczyznę Πxz |
![]() na płaszczyznę Πyz |
W obu tych przypadkach sytuacja jest podobna i możemy utworzyć proste proporcje (z twierdzenia Talesa lub z podobieństwa trójkątów prostokątnych):
A stąd już prosto:
Otrzymane współrzędne przestrzenne przeliczamy na współrzędne ekranowe:
Współrzędne xe i ye są liczbami całkowitymi. Określone są dla x' > -d i y' > -d.
Ponieważ obserwator jest w punkcie d poza płaszczyzną graficzną SDL, to z tego punktu widzenia oś OZ biegnie w drugą stronę, czyli w głąb ekranu:
Wynika z tego, iż do definicji obiektów możemy przyjąć układ przestrzeni 3D lub układ obserwatora, który różni się zwrotem osi OZ. Wzory w obu przypadkach pozostają identyczne.
Tworzymy tablicę VE współrzędnych ekranowych wierzchołków figury. Tablica ta składa się ze współrzędnych x,y na powierzchni graficznej SDL_Surface. Elementami tej tablicy będą struktury:
struct vertex2D { Sint32 x,y; };
Tablica ma tyle samo elementów co tablica V, czyli w przypadku naszego sześcianu - 8. Dokonujemy transformacji przestrzennej punktów V, a następnie wynik przekształcamy na współrzędne ekranowe i zapisujemy w tablicy VE (musimy sprawdzić, czy punkty x' i y' spełniają ostatni warunek - jeśli nie, to jako xe i ye zapisujemy jakąś dużą wartość). Teraz przetwarzamy tablicę krawędzi L. Dla każdej pary wierzchołków rysujemy linię pomiędzy współrzędnymi ekranowymi punktu startowego i końcowego. Jeśli współrzędne ekranowe są duże, to rezygnujemy z narysowania linii - jest to przypadek, gdy współrzędna z punktu P wyszła poza punkt obserwatora d.
Poniższy program animuje nasz sześcian, przesuwając go w przestrzeni do różnych punktów, ale tak dobranych, aby wszystkie punkty wierzchołkowe dało się zobrazować na powierzchni graficznej (z będzie zawsze nieujemne).
Program wykorzystuje bibliotekę newgfx.
Code::Blocks |
// Obiekty szkieletowe 3D // (C)2012 Koło Informatyczne // I LO w Tarnowie //------------------------------- #include "newgfx.h" #include <cmath> #include <list> const double PI=3.1415926535897; // Definicja typów danych struct vertex3D { double x,y,z; }; struct vertex2D { Sint32 x,y; }; struct line3D { int v1,v2; }; // Definicja wierzchołków figury const int VN = 8; // Liczba wierzchołków vertex3D V[] = {{-100, 100, 100},{ 100, 100, 100},{ 100,-100, 100},{-100,-100, 100}, {-100, 100,-100},{ 100, 100,-100},{ 100,-100,-100},{-100,-100,-100}}; // Definicja krawędzi figury const int LN = 12; // Liczba krawędzi line3D L[] = {{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7}}; // Tablica współrzędnych ekranowych wierzchołków vertex2D VE[VN]; int main(int argc, char *argv[]) { int waiting = 1; SDL_Surface * screen; SDL_Event event; double kx = PI, ky = 0, kz = 0; // Kąty pozycji na elipsie double sx = 0, sy = 100, sz = 1000; // Współrzędne środka elipsy double rx = 700, ry = 600, rz = 900; // Promienie elipsy double tx,ty,tz; // Wektor przesunięcia double d = 1000; // Punkt obserwatora double m; // Mnożnik SDL_Rect r; // Prostokąt wymazywania if(!SDL_Init(SDL_INIT_VIDEO)) { atexit(SDL_Quit); screen = SDL_SetVideoMode(1280, 1024, 32, SDL_HWSURFACE | SDL_FULLSCREEN); // Ustawiamy prostokąt wymazywania r.x = r.y = 0; r.w = screen->w; r.h = screen->h; do { // Obliczamy wektor przesunięcia tx = sx + rx * cos(kx); ty = sy + ry * sin(ky); tz = sz + rz * cos(kz); // modyfikujemy kąty kx += 0.005; if(kx > PI+PI) kx = 0; ky += 0.0075; if(ky > PI+PI) ky = 0; kz += 0.0125; if(kz > PI+PI) kz = 0; // Obliczamy współrzędne ekranowe for(int i = 0; i < VN; i++) { m = d / (V[i].z + tz + d); // obliczamy mnożnik VE[i].x = (screen->w >> 1) + m * (V[i].x + tx); VE[i].y = (screen->h >> 1) - m * (V[i].y + ty); } if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); // Rysujemy szkielet figury w kolorze białym for(int i = 0; i < LN; i++) { gfxClipWuLine(screen,VE[L[i].v1].x,VE[L[i].v1].y,VE[L[i].v2].x,VE[L[i].v2].y,0xffffff); gfxClipWuLine(screen,VE[L[i].v1].x+1,VE[L[i].v1].y,VE[L[i].v2].x+1,VE[L[i].v2].y,0xffffff); gfxClipWuLine(screen,VE[L[i].v1].x,VE[L[i].v1].y+1,VE[L[i].v2].x,VE[L[i].v2].y+1,0xffffff); gfxClipWuLine(screen,VE[L[i].v1].x+1,VE[L[i].v1].y+1,VE[L[i].v2].x+1,VE[L[i].v2].y+1,0xffffff); } if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); // Uaktualniamy ekran if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); SDL_FillRect(screen, &r, 0); // Wymazujemy szkielet figury if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); // Czekamy na klawisz ESCAPE if (SDL_PollEvent(&event)) if ((event.type == SDL_QUIT) || ((event.type == SDL_KEYDOWN) && (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 0; } while(waiting); } return 0; } |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe