Temat: Transformacje 3D 1 Wstęp teoretyczny
Transkrypt
Temat: Transformacje 3D 1 Wstęp teoretyczny
Grafika komputerowa 3D Instrukcja laboratoryjna Temat: Transformacje 3D 11 Przygotował: dr inż. Grzegorz Łukawski, mgr inż. Maciej Lasota, mgr inż. Tomasz Michno 1 Wstęp teoretyczny Bardzo często programując grafikę 3D występuje potrzeba dokonywania przekształceń obiektów (przesuwanie, obracanie, skalowanie itp.). W bibliotece OpenGL, w celu dostarczenia tych funkcjonalności, zastosowano technikę polegającą na modyfikowaniu całego układu współrzędnych, dzięki czemu przekształcenia nakładają się na siebie. Od strony programisty operacja ta polega na modyfikowaniu macierzy modelowania, zazwyczaj za pomocą dostarczanych funkcji, opisanych w dalszej części instrukcji. 1.1 Przekształcenia 1.1.1 Translacja Translacja jest przekształceniem polegającym na przesunięciu obiektu o podany wektor (mówiąc ściślej przesunięciu całego układu współrzędnych). W bibliotece OpenGL realizowane jest to za pomocą funkcji: void glTranslatef(GLfloat x, GLfloat y, GLfloat z) void glTranslated(GLdouble x, GLdouble y, GLdouble z) Obie funkcje przyjmują 3 parametry: x, y oraz z, które informują bibliotekę o ile jednostek przesunąć obiekt na osiach OX, OY oraz OZ. Funkcja mnoży bieżąca macierz modelowania (widoku modelu) przez macierz translacji. Następnie nowa macierz staje się bieżącą macierzą modelowania. Przykład 1. Przesunięcie trójkąta o wektor [-250, -100, -100]: void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glColor3f(0.0, 0.0, 1.0); 1/7 glTranslatef(-250, -100, -100); /* <===== translacja, każdy obiekt po tym wywołaniu będzie przesunięty o ten wektor [-250, -100, -100] */ glBegin(GL_TRIANGLES); glVertex3f(0, 200, 0); glColor3f(0.0, 1.0, 1.0); glVertex3f(50, 220, 0); glColor3f(1.0, 1.0, 1.0); glVertex3f(100, 200, 0); glEnd(); glutSwapBuffers(); } 1.1.2 Skalowanie Skalowanie, podobnie jak w bibliotece Allegro, polega na zmniejszeniu lub powiększeniu obiektu o zadany współczynnik, zdefiniowany dla każdej z osi: void glScalef(GLfloat x, GLfloat y, GLfloat z) void glScaled(GLdouble x, GLdouble y, GLdouble z) Funkcje przyjmują współczynniki skalowania dla kolejnych osi. Wartość współczynnika równa 1 oznacza pozostawienie figury bez zmian. Przykład 2. Zmniejszenie obiektu w osi OX o połowę: glScalef(0.5, 1, 1); // zwykłe rysowanie trójkątów glBegin(GL_TRIANGLES); glVertex3f(0, 200, 0); // pierwszy punkt glColor3f(0.0, 1.0, 1.0); glVertex3f(50, 220, 0); // drugi punkt 2/7 glColor3f(1.0, 1.0, 1.0); glVertex3f(100, 200, 0); // trzeci punkt glEnd(); 1.1.3 Rotacja Rotacja w OpenGL polega na obrocie układu współrzędnych wokół podanego wektora (np. wektor [0, 1, 0] obróci nam wokół osi OY): void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) Parametr angle określa o jaki kąt (w stopniach) będzie dokonywany obrót, natomiast x, y i z są to poszczególne składowe wektora. Funkcja mnoży bieżącą macierz modelowania (widoku modelu) przez macierz obrotu. Kąt obrotu określany jest w kierunku przeciwnym do ruchu wskazówek zegara, sam obrót dokonywany jest wokół osi wyznaczonej przez wektor o początku w układzie współrzędnych i końcu w punkcie (x,y,z). Nowa macierz staje się bieżącą macierzą modelowania. 1.1.4 Własne mnożenie macierzy Istnieje również możliwość własnego, ręcznego wykonania wymienionych (i niewymienionych) przekształceń, za pomocą mnożenia bieżącej macierzy przez drugą macierz. Odpowiedzialna za to jest funkcja: void glMultMatrixd(const GLdouble * m); void glMultMatrixf(const GLfloat * m); Więcej informacji można znaleźć pod poniższym adresem: http://www.opengl.org/sdk/docs/man/xhtml/glMultMatrix.xml 1.2 Inne przydatne funkcje Poniżej zostały zebrane funkcje, które czasami są przydatne w aplikacjach z przekształceniami obiektów. 1.2.1 Resetowanie macierzy Na początku funkcji rysującej należy zawsze wczytać początkowe wartości do macierzy 3/7 modelowania. Dzięki temu unikniemy sytuacji, w której przekształcenia będą się nakładały na siebie „w nieskończoność”. Aby „zresetować” macierz widoku należy wywołać funkcję: void glLoadIdentity( void); Funkcja ta ładuje macierz jednostkową, dzięki czemu układ współrzędnych zostaje ustawiony na domyślnym położeniu. Oprócz macierzy modelowania, glLoadIdentity() można stosować do innych macierzy (tak na prawdę glLoadIdentity po prostu wczytuje macierz jednostkową do aktualnie używanej macierzy). Przełączanie pomiędzy macierzami wykonuje się za pomocą glMatrixMode: glMatrixMode(GL_PROJECTION); // Macierz projekcji glMatrixMode(GL_MODELVIEW); // macierz modelowania 1.2.2 Tryb wypełniania Tryb wypełniania wielokątów umożliwia decydowanie o tym, jak ma być wypełniona przednia, a jak tylna strona wielokąta. Do ustawia trybu wypełniania wielokątów służy funkcja: void glPolygonMode(GLenum face, GLenum mode) Jej pierwszy parametr określa, których ścian będzie dotyczyć operacja (GL_FRONT, GL_BACK, GL_FRONT_AND_BACK). Drugi parametr określa styl wypełniania ścian wielokąta (GL_LINE, GL_POINT, GL_FILL). Domyślnie jest ustawiony GL_FILL czyli wypełnianie całego wielokąta. GL_LINE oznacza że będą widocznie jedynie linie tzw. tryb wireframe (druciak), GL_POINT oznacza, że będą widoczne jedynie punkty wielokąta. 1.2.3 Widoczność i ukrywanie krawędzi Często niepotrzebne jest rysowanie wewnętrznych ścian obiektów, rysowanie takie zajmuje jedynie dodatkową moc obliczeniową komputera. OpenGL umożliwia automatyczne usuwanie niewidocznych ścian. Aby ukryć niewidoczne ściany należy włączyć opcję ukrywania niewidocznych powierzchni wielokątów za pomocą funkcji glEnable z parametrem GL_CULL_FACE. Następnie należy wywołać funkcję glCullFace. void glCullFace(GLenum face) Funkcja ta przyjmuje jeden z trzech parametrów GL_FRONT (ściana przednia), GL_BACK (ściana tylna), GL_FRONT_AND_BACK (ściana przednia i tylna). 4/7 Ukrywanie krawędzi wykorzystywane jest w przypadku rysowania obiektów w trybie tzw. wireframe (siatki). Do ukrywani krawędzi służy specjalna funkcja OpenGL: void glEdgeFlag(GLenum flag) Funkcja ta przyjmuje jeden z dwóch parametrów GL_TRUE (rysuj krawędź), GL_FALSE (nie rysuj krawędzi). Przykład 3: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glBegin(GL_POLYGON); glColor3f(0.5f, 0.5f, 0.5f); glEdgeFlag(GL_FALSE); glVertex3f(0.0f, 2.0f, 0.0f); glEdgeFlag(GL_TRUE); glVertex3f(2.0f, 2.0f, 0.0f); glVertex3f(2.0f, -2.0f, 0.0f); glVertex3f(-4.0f, -2.0f, 0.0f); glVertex3f(-5.0f, 0.0f, 0.0f); glEnd(); 1.2.4 Timer Czasami występuje potrzeba wykonywania kodu w pewnych, ustalonych odstępach czasu (np. animacja). W celu rozwiązania tego problemu programista/programiści biblioteki glut dodali do niej obsługę timerów. Timer jest funkcją, która wykonuje się co pewien zadany czas. Glut wymaga, aby funkcja ta miała ustalone parametry: void funkcjaTimera(int value); Następnie należy zarejestrować ją (podobnie jak funkcje rysowania i klawiatury) za pomocą następującej funkcji: void glutTimerFunc(unsigned int msecs, void (*func)(int value), value); Parametry są następujące: 5/7 msecs – czas w milisekundach, po którym zostanie wywołana funkcja timera func – wskaźnik na funkcję timera value – wartość przekazywana do funkcji timera (parametr value funkcji timera będzie ustawiany na tą wartość) Uwaga! Ustawienie timera w funkcji glutTimerFunc spowoduje tylko jednorazowe jego wywołanie. Dlatego w funkcji timera najlepiej jest użyć jej ponownie (patrz przykład 4). Glut umożliwia zadeklarowanie dowolnej liczby timerów (np. można zadeklarować dwa timery, które wykonują się o różnym czasie). Przykład 4: void funkcjaTimera(int val) { // kod wykonywany w timerze //... // odrysowanie ekranu po wprowadzeniu zmian (jeśli jest taka potrzeba): glutPostRedisplay(); // ponowne ustawienie timera (co 1000 ms = 1s): glutTimerFunc(1000, funkcjaTimera, 0); } int main(int argc, char *argv[]) { glutInit(&argc, argv); /* ... */ // ustawienie timera (wywoła funkcję funkcjaTimera po 1000 ms = 1s): glutTimerFunc(1000, funkcjaTimera, 0); glutMainLoop(); 6/7 return(0); } 2 Zadania Napisz program, który będzie wykonywał animacje (z użyciem timera) kilku obiektów (dowolnych) z użyciem przedstawionych w instrukcji transformacji 3D (translacja, obrót, skalowanie), np. odbijające się od ścian okna obiekty dostarczane przez gluta. Aplikacja powinna używając timera zmieniać kolory obiektów (co 5 sekund) oraz zmieniać tryb wypełniania (co 8 sekund) – można użyć dwóch timerów. Wskazówki i uwagi • nie używaj funkcji gluLookAt(); ponieważ wprowadza przekształcenia sceny • na końcu funkcji display() umieść wywołanie funkcji glutPostRedisplay(); która odświeża zawartość ekranu • do poprawnego wyświetlania obiektów należy prawidłowo obsłużyć bufor głębokości 1. Inicjalizacja trybu wyświetlania – dodanie parametru GLUT_DEPTH 2. Włączenie testu głębokości – parametr GL_DEPTH_TEST 3. Czyszczenie bufora głębokości przed rysowaniem – param. GL_DEPTH_BUFFER_BIT 7/7