Krzysztof Wesołowski Programowanie, elektronika, automatyka – doświadczenia i projekty

12gru/08

Integracja i używanie biblioteki GLUT dla OpenGL z kompilatorem GNAT

Dziś wpis gościnny, na temat używania bibliotek GLUT i OpenGL'a w języku Ada. Autorem wpisu jest Jakub Tutro, student II roku Automatyki i Robotyki. Opisuje on pokrótce podstawy używania ww. bibliotek do celów wizualizacji wszelkich symulacji i obiektów trójwymiarowych, co ma duże znaczenie dla walorów estetycznych i użyteczności wszelkich programów.

Zapraszam serdecznie do przeczytania, oraz zgłaszania wszelkich uwag w komentarzach do tego wpisu lub bezpośrednio do autora na adres jtutro@gmail.com.

Instalacja GLUT'a i win32ada

  1. Instalujemy Eclipse zgodnie z tym tutorialem.
  2. Ściągamy OpenGL and GLUT Bindings for Windows
  3. Ściągamy win32ada (np ze stronki http://libre.adacore.com => klikamy Download GNAT GPL Edition => wybieramy x86-windows i potem w "katalogu" win32ada mamy plik win32ada-gpl-2008-nt.exe
  4. instalujemy win32ada do katalogu z GNAT'em z domyślnymi ustawieniami.
  5. Kopiujemy plik glut32.dll z ..\Glut3.7.6\Bindings do ..\Windows\system, dzięki temu nie będziemy musieli mieć go w katalogu z każdym plikiem exe.
  6. Kopiujemy plik libglut32.a z katalogu ..\Glut3.7.6\Bindings do ..\GNAT\2008\lib\win32ada
  7. Kopiujemy pliki win32-glut.ads i win32-glut.adb z katalogu ..\Glut3.7.6\Bindings do katalogu ..\GNAT\2008\include\win32ada
  8. Wchodzimy wierszem poleceń do katalogu ..\GNAT\2008\include\win32ada i odpalamy "gnatmake win32-glut"
  9. Pliki które utworzyły sie podczas kompilacji, czyli: win32.ali , win32-gl.ali i win32-glut.ali i takie same tylko z rozszerzeniem *.o przenosimy do ..\GNAT\2008\lib\win32ada ..i to wszystko :) Teraz by móc korzystać z biblioteki GLUT w naszym projekcie dodajemy do pliku *.gpr naszego projekt na początku linijkę: with "win32ada"; oraz gdzieś kawałek dalej, już wewnątrz projektu: package Linker is for Default_Switches ("Ada") use ("-lwin32ada", "-lglu32", "-lopengl32", "-lglut32"); end Linker; i w razie potrzeby dołączamy w naszych plikach pakiety win32.glut, win32.glu i win32.gl . Wszystko można zobaczyć w przykładowych plikach ze źródłami.

1. Wstęp.

OpenGL jest biblioteką graficzną przeznaczoną do tworzenia grafiki dwu- i trój- wymiarowej. Dzięki tej bibliotece możemy tworzyć aplikacje o jakości nie ustępującej dzisiejszym grom, które notabene też w pewnej części są tworzone na bazie tej biblioteki. OpenGL nie jest odrębnym językiem programowania, ale interfejsem API, czyli Application Programming Interface. Podstawową cechą OpenGL?a jest to, że jest on maszyną stanów. Na stan składa wiele trybów działania i parametrów aktualnych, które można ustawiać i zmieniać, a także odkładać na stosie a później je odtwarzać. Wszystkie ustawienia mają wpływ na otrzymany rezultat. Ustawiony parametr ma stałą wartość aż do jego następnej zmiany. Przykładami takich parametrów mogą być: kolor, tekstura, ustawienia oświetlenia, macierz projekcji, macierz modelowania. OpenGL ma bardzo duże możliwości, ale jest jednocześnie interfejsem niskopoziomowym. Zawiera on funkcje pozwalające rysować pojedyncze linie, wielokąty, zmieniające macierze widoku, ale brak w nim np. funkcji pozwalających ustawić obserwatora w danym punkcie w przestrzeni i skierować jego wzrok na inny punkt, czy też funkcji pozwalających rysować nam całe bryły, takie jak sfery, stożki, prostopadłościany, ostrosłupy. Funkcje te udostępnia dodatek do OpenGL?a w postaci biblioteki pomocniczej GLU (GL Utility Library). Poza bibliotekami GL (czysty OpenGL) i GLU możemy wykorzystywać jeszcze inne biblioteki dodatkowe. Związane jest to z tym, że GL zajmuje się w gruncie rzeczy tylko renderingiem grafiki i nie udostępnia żadnych funkcji związanych z tworzeniem interfejsu, obsługą urządzeń peryferyjnych takich jak myszka, czy klawiatura, a także obsługą środowiska okienkowego. Dodatkową biblioteką posiadającą takie funkcje jest np. GLUT, czyli GL Utility Toolkit.

2. Składnia.

OpenGL ma bardzo specyficzną składnię. Niemalże wszystkie polecenia zaimplementowane są jako globalne funkcje i procedury. Wiele funkcji wykonuje te same operacje i różnią się tylko zbiorem argumentów. Nazwy wszystkich funkcji poprzedzone są przedrostkiem gl-, glu- lub glut- w zależności od biblioteki z której pochodzą. Nie można też nie wspomnieć, że biblioteka ta definiuje własne typy danych. Co prawda odpowiadają one typom danych ze znanych języków programowania, jednak dzięki temu, że są one zdefiniowane wewnątrz biblioteki uzyskujemy dużą przenośność kodu stworzonego z jej uzyciem. Dodatkowo na przenośność wpływa także składnia o której wspominałem wcześniej. Dzięki temu, że wszystkie funkcje i procedury są globalne, a nie są np. obiektami typowymi dla języka C++, to nie ma większego problemu z przystosowaniem i używaniem OpenGL?a w np. Adzie.

Podstawowe typy danych w OpenGL:

Typ OpenGL Ilość bitów Opis
GLboolean 1 Typ logiczny
GLbyte 8 Liczba całkowita ze znakiem
GLubyte 8 Liczba całkowita bez znaku
GLchar 8 Znak tekstowy
GLshort 16 Liczba całkowita ze znakiem
GLushort 16 Liczba całkowita bez znaku
GLint 32 Liczba całkowita ze znakiem
GLuint 32 Liczba całkowita bez znaku
GLsizei 32 Liczba całkowita nieujemna
GLenum 32 Typ wyliczeniowy całkowity
GLfloat 32 Liczba zmiennoprzecinkowa
GLdouble 32 Liczba zmiennoprzecinkowa(większa dokładność)

3. Układ współrzędnych.

Biblioteka OpenGL stosuje prawoskrętny układ współrzędnych kartezjańskich, w którym oś OZ skierowana jest prostopadle do płaszczyzny monitora. Warto także pamiętać, że literatura informatyczna preferuje lewoskrętne układy współrzędnych z osią OZ skierowaną w głąb monitora.

4. Barwy.

Biblioteka OpenGL wykorzystuje barwy z palety RGB, opierającej się na trzech podstawowych kolorach: czerwonym, zielonym i niebieskim. W razie potrzeby można uzupełnić palety barw o kanał alfa (RGBA). Wybór trybu w jakim będzie generowany obraz jest dokonywany podczas tworzenia okna renderingu.

6. Inicjalizacja ? czyli od czego zacząć.

Zacznę od tego, że wszystkie funkcje do obsługi OpenGL w naszym programie umieścimy w osobnym pakiecie, aby zwiększyć przejrzystość naszego programu. Dlatego też tworzymy pakiet my_OpenGL (my_OpenGL.ads, my_OpenGL.ads), który włączamy do naszego programu. Omówię najpierw pokrótce budowę tego pakietu i funkcje w nim użyte, a potem przejdziemy do przykładów i rozbudowy pakietu. (pliki pakietu do pobrania tutaj). Pierwsza i najważniejszą, jeżeli w ogóle chcemy korzystać z OpenGL i GLUT?a, procedurą jest Init_OpenGL. Procedura ta odpowiedzialna jest za inicjalizację OpenGL i tworzenie okna renderingu (czyli po prostu okna w którym możemy sobie porysować). Jako parametry przyjmuje Stringa z nazwą jaka ma się wyświetlać na pasku tytułowym okna, oraz zmienna typu Integer w której zapisuje numer utworzonego okna (nie będziemy tego używać, gdyż będziemy tworzyć tylko jedno okno). W części deklaracyjnej tej procedury mamy trochę ?pragma czarów?, które są niezbędne żeby zainicjalizować GLUT?a, nie pytajcie po co, bo tak po prostu jest, że gdy chcemy zrobić coś fajnego w Adzie to w 99% kończy się to na imporcie z C? kopiujemy to grzecznie tam gdzie nam potrzebne i sobie jest i działa ? taki dogmat ideologii ?GLUT in ADA?. Analizujemy po kolei wszystkie funkcje i procedury zawarte w procedurze Init_OpenGL:

Inicjalizacja biblioteki GLUT:

Listing:
  1. GlutInit (Argc'Access, Argv);

Inicjalizacja bufora ramki (parametry podajemy jako logiczną sumę, odpowiednio: podwójne buforowanie, bufor barw RGB):

Listing:
  1. GlutInitDisplayMode (GLUT_DOUBLE or GLUT_RGB);

Ustawianie rozmiarów okna które chcemy stworzyć (w pikselach ? szerokość i wysokość):

Listing:
  1. GlutInitWindowSize(640, 480);

Ustawianie pozycji okna (odległość lewego górnego rogu okna od lewego górnego rogu ekranu w pikselach):

Listing:
  1. GlutInitWindowPosition(100,100);

Tworzenie okna rendering o tytule Title:

Listing:
  1. Window := GlutCreateWindow(Title);

Przypisanie procedury Display jako głównej procedury generującej scenę. W GLUT?cie jest taka idea, że podczas pracy program wchodzi w nieskończoną pętlę (GlutMainLoop) i kręci się w niej uruchamiając różne funkcje i procedury, do generowania sceny, obsługi peryferiów itd. Aby wszystko działało musimy przypisać odpowiednie funkcje, bądź procedury funkcjom GLUT?a. Podajemy więc wskaźniki do naszych funkcji GLUT?owi.

Listing:
  1. GlutDisplayFunc (Display'Unrestricted_Access);

Przypisanie procedury Reshape jako procedury uruchamianej w czasie zmiany wymiarów okna:

Listing:
  1. GlutReshapeFunc (Reshape2'Unrestricted_Access);

Czyszczenie okna kolorem (1.0, 1.0, 1.0 ? czyli po prostu białym ? to będzie nasze tło):

Listing:
  1. GlClearColor(1.0, 1.0, 1.0, 1.0);

Włączanie opcji która pozwala nam używać barw podczas rysowania:

Listing:
  1. glEnable(GL_COLOR_MATERIAL);

7. Skąd w OpenGL?u jakieś macierze i do czego mogą się przydać?!

Wszystkie operacje jakie wykonujemy na naszej scenie i jej obiektach są tak naprawdę operacjami na macierzach. Jest to o tyle dobre rozwiązanie, że po pierwsze za pomocą macierzy można łatwo opisywać przekształcenia, a po drugie składanie przekształceń sprowadza się wtedy do mnożenia macierzy, co sprawia, że wszystko jest łatwe i intuicyjne. Mamy do dyspozycji 3 rodzaje macierzy oraz stosy na które możemy je odkładać i z których możemy je podnosić. Wyboru macierzy dokonujemy za pomocą procedury: glMatrixMode (GLenum)

gdzie parametrem może być: GL_MODELVIEW ? macierz widoku ? służy do wykonywania przekształceń geometrycznych takich jak translacje o wektor, obroty itp. GL_PROJECTION ? macierz rzutowania ? jak sama nazwa wskazuje definiuje rzutowanie przestrzeni 3D na płaszczyznę ekranu GL_TEXTURE ? macierz tekstury (nie będzie tutaj omawiana)

Gdy wybierzemy jakąś macierz, wszystkie operacje wykonywane później będą dotyczyły tej właśnie macierzy, aż do momentu, gdy nie wybierzemy innej. Każda macierz na początku zawiera śmieci, więc musimy ją wyczyścić ładując do niej macierze jednostkową za pomocą procedury: glLoadIdentity

W procedurze Reshape użyliśmy macierzy rzutowania i zdefiniowaliśmy rzutowanie perspektywiczne przy pomocy procedury:

Listing:
  1. gluPerspective (GLdouble fovy,
  2.                 GLdouble aspect,
  3.                 GLdouble zNear,
  4.                 GLdouble zFar);

gdzie parametr fovy określa w stopniach kąt widzenia obserwatora zawarty w płaszczyźnie YZ, a aspect jest stosunkiem szerokości do wysokości przedniej płaszczyzny odcinania, czyli górnej podstawy ostrosłupa ograniczającego scenę 3D. Parametry zNear i zFar skolei ograniczają bryłę obcinania od przodu i od tyłu.

Bryła obcinania to taki ścięty ostrosłup, który ogranicza przestrzeń która będzie widoczna dla obserwatora (tzn elementy sceny które leżą w tej przestrzeni będą widoczne) umożliwia to ukrywanie elementów które znajdują się daleko, na horyzoncie, albo tych które znajdują się bardzo blisko, a także ustalenie kąta pod jakim widzimy obiekty. Proponuję poeksperymentować z różnymi wartościami parametrów tej funkcji. Zajmiemy się teraz obszarem renderingu. Normalnie obszar renderingu zajmuje początkowo całe okno, i nie jest modyfikowany podczas zmiany rozmiarów tego okna. W efekcie elementy sceny 3D zawsze znajdują się w tym samym miejscu względem lewego dolnego narożnika okna. W aplikacjach pracujących w systemach okienkowych problem zmiany rozmiaru okna jest jednak tak powszechny, że wymaga specjalnego potraktowania. Jednym z możliwych sposobów jego rozwiązania jest dynamiczna modyfikacja obszaru renderingu. Służy to tego procedura (operująca na macierzy rzutowania):

Listing:
  1. glViewport (GLint x, GLint y,GLsizei width, GLsizei height)

której parametry oznaczają:x, y - współrzędne lewego dolnego narożnika obszaru renderingu względem lewego dolnego narożnika okna, width - szerokość okna renderingu, height - wysokość okna renderingu. Domyślnie obszar renderingu zajmuje całe okno udostępnione dla aplikacji OpenGL. Możemy jednak modyfikować obszar renderingu na dwa sposoby. Pierwszy polega na objęciu obszarem renderingu całego dostępnego okna, drugi na takim wyborze okna renderingu aby okno zachowało pierwotny aspekt obrazu, czyli stosunek szerokości do wysokości. Oczywiście przy zastosowaniu pierwszej metody elementy sceny będą zazwyczaj zdeformowane (procedura Reshape1). W drugim przypadku ustawiamy okno renderingu, by zawsze wypadało na środku naszego okna aplikacji. Dzięki temu zachowujemy aspekt i elementy sceny nie deformują się (procedura Reshape2). Aby zobaczyć efekt wystarczy podmienić procedurę w funkcji GlutReshapeFunc na Reshape1 lub Reshape2.

Generowanie sceny 3D - podstawy.

Teraz omówię działanie najważniejszej procedury w naszym programie, odpowiedzialnej za generowanie sceny 3D ? procedury Display. Procedura przekazywana do GlutDisplayFunc odpowiedzialna jest za rysowanie całej sceny 3D, czyli w gruncie rzeczy tego co widzimy w naszym oknie aplikacji. Najpier zaczniemy od prostego przykładu - rysowanie wersorów układu współrzędnych -każdy w innym kolorze. Żeby wogóle możliwe było posługiwanie sie kolorami musimy mieć włączoną opcje:

Włączanie używania kolorów dla materiałów

Listing:
  1. gluLookAt(GLdouble eyex,
  2.           GLdouble eyey,
  3.           GLdouble eyez,
  4.           GLdouble centerx,
  5.           GLdouble centery,
  6.           GLdouble centerz,
  7.           GLdouble upx,
  8.           GLdouble upy,
  9.           GLdouble upz);

(Nawiasem wspomnę, że zanim zaczniemy rysować coś w 2D musimy wyłączyć oświetlenie, jeżeli go używamy, gdyż może się okazać, że światło o pewnej barwie, po odbiciu od jakiegoś obiektu padnie na rysowaną przez nas linię i często całkowicie zmieni jej kolor! To dla ciekawskich, którzy sobie poszukają w internecie o oświetleniu :) )

Pierwsza procedura w Display służy do ustawiania pozycji obserwatora, oraz punktu na który ma patrzeć:

Listing:
  1. glClearColor (1.0,1.0,1.0,0.0);
  2. glClear (GL_COLOR_BUFFER_BIT);

pierwsze trzy argumenty to pozycja oka obserwatora w układzie współrzędnych, następne trzy to pozycja punktu na który patrzy obserwator, a kolejne trzy to (i tutaj własna interpretacja) wektor którego zwrot wskazuje grzywkę obserwatora (górę) :D:D:D generalnie chodzi o to, że jeżeli oko obserwatora zaczepimy w punkcie [eyex,eyey,eyez] to jeszcze możemy kręcić całym obserwatorem wokół i w zależności o tego jak go obrócimy będzie widział obraz normalnie, bokiem, albo do góry nogami. Nie jest ważna długość tego wektora gdyż i tak zawsze przekształcany jest on na wektor jednostkowy.

Następnie mamy dwie funkcje dotyczące kolorów:

Listing:
  1. glBegin (GL_LINES);
  2. glColor3d (1.0,0.0,0.0);
  3. glVertex3f (0.0, 0.0, 0.0);
  4. glVertex3f (1.0, 0.0, 0.0);
  5.  
  6. glColor3d (0.0,1.0,0.0);
  7. glVertex3f (0.0, 0.0, 0.0);
  8. glVertex3f (0.0, 1.0, 0.0);
  9.  
  10. glColor3d (0.0,0.0,1.0);
  11. glVertex3f (0.0, 0.0, 0.0);
  12. glVertex3f (0.0, 0.0, 1.0);
  13. glEnd;

pierwszą już znamy - odpowiada za wyczyszczenie całej sceny kolorem zdefiniowanym w nawiasie (w formacie RGB, gdzie skala każdego koloru rozciąga się od 0.0 do 1.0), w naszym przypadku białym, natomiast druga funkcja służy do czyszczenia bufora koloru.

Teraz zaczyna się prawdziwe rysowanie. Cały blok:

Listing:
  1. glMatrixMode(GL_MODELVIEW);
  2. glLoadIdentity;

służy do rysowania lini. Tak więc po kolei. Pierwsza procedura (glBegin) mówi bibliotece że zaczynamy rysowanie jakiegoś prymitywu (prosty element rysowany za pomocą czystego OpenGL'a). Funkcja ta przyjmuje typ wyliczeniowy i jej argument może przyjmować następujące wartości:

GL_POINTS - rysowanie punktów (Vertex {n} definiuje n-ty punkt, rysowanych jest n punktów) GL_LINES - rysowanie niezależnych lini (dwa koleje Vertex {2n-1 i 2n} definiują linię, rysowanych jest n/2 niezależnych linii) GL_LINE_STRIP - rysowanie grupy połączonych lini (Vertex {n i n+1} definiują jeden segment, rysowanych jest n-1 lini) GL_LINE_LOOP - rysowanie zamkniętej grupy lini (działa tak jak poprzednia procedura tylko dodatkowo łączy pierwszy i odstatni Vertex) GL_TRIANGLES - rysowanie trójkąta (każda kolejna trójka Vertex'ów {3n-2, 3n-1 i 3n} definiuje jeden trójkąt, rysowanych jest n/3 trójkątów) GL_TRIANGLE_STRIP - rysuje grupę połączonych trójkątów (trzy pierwsze Vertex'y tworzą jeden trójką, następny tworzą Vertexy o numerach 2,3,4 itd, ogólnie każdy kolejny trójkąt tworzą Vertex'y {n, n+1, n+2}, rysowanych jest n-2 trójkątów) GL_TRIANGLE_FAN - rysuje wiatraczek z trójkątów (pierwszy Vertex jest środkiem wiatraczka i wspólnym wierzchołkiem wszystkich trójkątów, a każda kolejna para definiuje nowy trójkąt, ogólnie każdy trójką budują Vertex'y {1, n+1 i n+2}, rysowanych jest n-2 trójkątów) GL_QUADS - rysuje kwadrat (cztery kolejne Vertex'y {4n-3, 4n-2, 4n-1 i 4n} definiują osobny kwadrat, rysowanych jest n/4 kwadratów) GL_QUAD_STRIP - rysuje grupę kwadratów (cztery pierwsze Vertex'y definiują pierwszy kwadrat, a potem każdy następny kwadrat budowany jest z ostatnich dwóch Vertex'ów poprzedniego kwadratu i dwóch nowych, ogólnie budowanych jest n/2-1 kwadratów) GL_POLYGON - rysuje pojedynczy wielobok (każdy Vertex jest wierzchołkiem wieloboku)

następnie mamy kolejne Vertex'y, czy tak naprawdę poprostu współrzędne punktów w przestrzeni. W między czasie używam także procedury do zmiany koloru. Procedura glColor3d przyjmuje kolor w formacie RGB (jako GLdouble) i ustawia aktualny kolor, jaki będzie wykorzystywany do rysowania, aż do momentu gdy go zmienimy. Rysowanie prymitywu kończymy procedurą glEnd. Całą procedurę rysowania Display kończymy natomiast wywołaniem procedury glutSwapBuffers, która to procedura odpowiada za wypisanie całego bufora na ekran. Generalnie chodzi o to, że rysując tak naprawdę rysujemy w buforze a nie na ekranie i dopiero na końcu całą narysowaną scenę zrzucamy z bufora na ekran. Zapobiega to mruganiu ekranu w trakcie rysowania co można czasem zaobserwowac przy niebugorowanych pakietach rysujących). Teraz możemy odpalić nasz pierwszy programik i zobaczyć jak pięknie narysuje nam wersory w trzech kolorkach :)

Przekształcenia geometryczne.

Stworzymy sobie nową procedurę Display1 która będzie kopią Display i w tej kopii będziemy dodawać nowe rzeczy. Następnie podmienimy procedury w glutDisplayFunc (z Display na Display1). Najpierw jednak chciałbym wytłumaczyć pewną własność przekształceń geometrycznych które są podstawą ideologii biblioteki GL. Jeżeli chcemy otrzymać jakiś obiekt w danym punkcie przestrzeni i odpowiednio zorientowany, możemy go narysować w dowolnym miejscu przestrzeni i w dowolnej orientacji (czyli np w środku układu współrzędnych tak by krawędzie pokrywały się z osiami - inaczej mówiąc tak by było nam łatwo go narysować), a potem za pomocą przekształceń takich jak translacja o wekor i obrót, umieścić go w zadanym miejscu i pozycji. Ideologia biblioteki GL polega na czymś trochę odwrotnym, czyli przekształceniach macierzy widoku (czyli tak jakby operacje na względnym układzie współrzędnych, który sobie gdzieś przenosimy i obracamy) i dopiero następnie narysowania obiektu (w naszym przekształconym układzie współrzędnych). Wszystko stanie się jasne gdy zobaczymy przykład. Chcę tylko jeszcze wspomnieć, że składanie przekształceń jako macierzy jest banalnie proste i sprowadza się do mnożenia macierzy.

W nowej funkcji Display1 dopisujemy na początku następujący blok:

Listing:
  1. glTranslatef(1.0,1.0,1.0);
  2. glBegin(GL_QUADS);
  3. glColor3d (0.6,0.8,0.8);
  4. glVertex3f (0.0, 0.0, 0.0);
  5. glVertex3f (1.0, 0.0, 0.0);
  6. glVertex3f (1.0, 1.0, 0.0);
  7. glVertex3f (0.0, 1.0, 0.0);
  8. glEnd;

pierwsza procedura wybiera macierz modelu jako aktywną a druga ładuje do niej macierz jednostkową, czyli taka która odpowiada nie przekształconemu układowi. Jest to konieczne z uwagi na to, że macierze te nie są kasowane ani tracone podczas pracy programu, są one tak jakby zmiennymi globalnymi całej aplikacji, tak więc przy kolejnym uruchomieniu funkcji Display1 na wejsciu mamy już przekształconą macierz, tak więc kolejne przekształcenia jakie wykonujemy są składane z wykonanymi poprzednio i w efekcie wszystko się psuje. jeżeli dalej nie rozumiesz o co chodzi zaraz wyjaśnię to na przykładzie. Na końcu procedury Display1 dodajemy drugi blok odpowiedzialny za rysowanie kwadratu:

Listing:
  1. ...
  2.       glPushMatrix;
  3.  
  4.       glTranslatef(1.0,2.0,3.0);
  5.       glBegin(GL_QUADS);
  6.       glColor3d (0.6,0.8,0.8);
  7.       glVertex3f (0.0, 0.0, 0.0);
  8.       glVertex3f (1.0, 0.0, 0.0);
  9.       glVertex3f (1.0, 1.0, 0.0);
  10.       glVertex3f (0.0, 1.0, 0.0);
  11.       glEnd;
  12.  
  13.       glPopMatrix;
  14.  
  15.       glTranslatef(-1.0,-2.0,-3.0);
  16.       glBegin(GL_QUADS);
  17.       glColor3d (0.6,0.8,0.8);
  18.       glVertex3f (0.0, 0.0, 0.0);
  19.       glVertex3f (1.0, 0.0, 0.0);
  20.       glVertex3f (1.0, 1.0, 0.0);
  21.       glVertex3f (0.0, 1.0, 0.0);
  22.       glEnd;

procedura glTranslatef odpowiada za translacje o wektor, natomiast reszta jest nam znana. Teraz dołóżmy jeszcze jeden taki sam blok i zobaczymy co się stanie... Drugi narysowany kwadrat jest powyżej pierwszego, co oznacza, że drugie przekształcenie zostało dodane do pierwszego. ... a co jeśli chcielibyśmy narysować pierwszy kwadrat w punkcie (-1,-1,-1) ? Oczywiście możemy najpierw wykonać przekształcenie przeciwne do pierwszego, a potem przekształcenie glTranslatef(-1.0,-2.0,-3.0)... to jest dobrze gdy jest mało operacji i wiemy jakie transformacje wykonano wcześniej... a co jeżeli wewnątrz procedury Display wywołujemy jakąś funkcje która zajmuje się rysowaniem i czyta dane z pliku... za każdym razem trzeba by zapamiętywać wszystkie przekształcenia! Ale jest lepszy sposób. Po prostu kopię danej macierzy nad która pracujemy możemy sobie po prostu odłożyć na stosie, potem wykonywać przekształcenia jakie chcemy, a po narysowaniu tego co trzeba porzucić obecna macierz i podnieść kopię ze stosu... Zrobimy to w bardzo prosty sposób. Zmienioną procedurę zapisałem jako Display2. teraz kod wygląda tak:

Listing:
  1. glutKeyboardFunc(&keyPressed);
  2. glutKeyboardUpFunc(&keyUp);
  3. glutSpecialFunc(&specialKeyPressed);
  4. glutSpecialUpFunc(&specialKeyUp);

Warto przed każdą operacją rysowania odkładać na stos macierz modelu. Od razu mówię, że nie można przyjać że macierz modelu jest zawsze jednostkowa na początku wiec nie trzeba jej pamiętać, ponieważ jeżeli chcemy ustawić odpowiednio całą scenę (wszystkie jej elementy) to wykonujemy przekształcenia na macierzy zanim jeszcze cokolwiek zaczniemy rysować i to tak przekształcona macierz dopiero powinna być odkładaną na stos przed każdym rysowaniem.

Obsługa klawiatury. Czas dodać trochę życia do naszego programu i nauczyć się wpływać na co co jest wyświetlane w czasie rzeczywistym. GLUT dostarcza funkcji do obsługi podstawowych peryferiów takich jak klawiatura, myszka, joystick, i tablet. Do obsługi klawiatury mamy trzy procedury:

Listing:
  1. procedure keyPressed( Key: GLubyte; X, Y: Integer ) is
  2. begin
  3.     case Key is
  4.          -- gdy wcisnelismy ESC
  5.          when 27 => OpenGL.GLUT.glutDestroyWindow(Win);
  6.          when others => null;
  7.     end case;
  8. end keyPressed;

Pierwsza procedura ustala jaka funkcję zostanie wywołana po wciśnięciu klawisza (tutaj keyPressed). Jej parametry to jedna zmienna typu character, która zawiera klawisz jaki został wciśnięty oraz dwa parametry typu integer które zawierają współrzędne myszki po wciśnięciu klawisza. Przykładowa funkcja:

Listing:
  1. glutSpecialFunc (specialKeyPressed'Unrestricted_Access);

Analogiczną do pierwszej jest funkcja druga. Różni się ona od tamtej tylko tym, że wywołuje funkcję daną w parametrze dopiero po puszczeniu klawisza. Kolejną podobną funkcją jest trzecia której parametr zostanie wywołany po wciśnięciu klawisza specjalnego. Parametry identyczne jak wyżej za wyjątkiem pierwszego, który jest typu integer i dalej ostatnia - analogicznie jak w glutKeyboardUpFunc, z poprawką na pierwszy parametr. Istnieją pre definiowane klawisze, których znaczenia nie trudno się domyślić:

Nazwa Stałej Wartośc liczbowa
GLUT_KEY_F1 1
GLUT_KEY_F2 2
GLUT_KEY_F3 3
GLUT_KEY_F4 4
GLUT_KEY_F5 5
GLUT_KEY_F6 6
GLUT_KEY_F7 7
GLUT_KEY_F8 8
GLUT_KEY_F9 9
GLUT_KEY_F10 10
GLUT_KEY_F11 11
GLUT_KEY_F12 12
GLUT_KEY_LEFT 100
GLUT_KEY_UP 101
GLUT_KEY_RIGHT 102
GLUT_KEY_DOWN 103
GLUT_KEY_PAGE_UP 104
GLUT_KEY_PAGE_DOWN 105
GLUT_KEY_HOME 106
GLUT_KEY_END 107
GLUT_KEY_INSERT 108

numery te odpowiadają odpowiednim klawiszom podczas wywoływania funkcji glutSpecialFunc i glutSpecialUpFunc. Zrobimy, więc tak by dało się obracać całą scenę wokół osi Z za pomocą strzałek lewo/prawo. Musimy więc dodać linijkę do procedury Init_OpenGL:

Listing:
  1. procedure specialKeyPressed(i: in integer; x: in integer; y: in integer) is
  2.    begin
  3.       case i is
  4.          when GLUT_KEY_LEFT => rotateZ := rotateZ + 3.0;
  5.          when GLUT_KEY_RIGHT => rotateZ := rotateZ - 3.0;
  6.          when others => NULL;
  7.       end case;
  8.       glutPostRedisplay;
  9.    end specialKeyPressed;

oraz napisać procedurę specialKeyPressed. Ja to zrobiłem tak:

Listing:
  1.       glRotated(angle => rotateZ,
  2.                 x     => 0.0,
  3.                 y     => 0.0,
  4.                 z     => 1.0);

rotateZ jest typu GLdouble i zadeklarowałem ją w pliku "my_OpenGL.ads". Na końcu mamy procedure glutPostRedisplay. Wymusza ona przerysowanie sceny, co jest konieczne by zobaczyć jakikolwiek efekt. Teraz wystarczy dodać w funkcji Display2 zaraz na początku wywołanie:

Listing:
  1. MouseButton (button: Integer; state: Integer; x:Integer; y:Integer)

która przyjmuje kąt obrotu w stopniach, jako GLdouble, oraz wektor wokół którego ma zostać wykonany obrót (długość nie jest ważna i tak zostanie sprowadzony do wektora jednostkowego). Obsługa myszki. Obsługa myszki składa się z dwóch etapów i jest wykonywana przez dwie funkcje. Pierwsza z nich to:

Listing:
  1. MouseMotion (x: Integer; y: Integer)

która wykrywa naciśnięcie i zwolnienie lewego przycisku myszki. W zależności od stanu lewego przycisku myszki ustawiana jest wartość zmiennej globalnej button state na przekazaną przez parametr state. Druga funkcja:

Listing:
  1. glutMouseFunc (MouseButton'Unrestricted_Access)
  2. glutMotionFunc (MouseMotion'Unrestricted_Access)

wywoływana jest podczas ruchu kursora myszki. Parametry x i y obu funkcji oznaczają współrzędne kursora myszki w odniesieniu do układu współrzędnych okna, które oczywiście nie mają nic wspólnego ze współrzędnymi określonymi w scenie 3D. Parametr button funkcji MouseButton określa który przycisk myszki został naciśnięty lub zwolniony. Parametr ten przyjmuje jedną z wartości: ? GLUT_LEFT_BUTTON - lewy przycisk myszki,1. Przekształcenia geometryczne 8 ? GLUT_MIDDLE_BUTTON - środkowy przycisk myszki, ? GLUT_RIGHT_BUTTON - prawy przycisk myszki. Jeżeli w danym systemie myszka ma tylko dwa przyciski, wartość GLUT MIDDLE BUTTON nie będzie generowana. Natomiast w przypadku myszki z jednym przyciskiem funkcja generuje jedynie wartość GLUT_LEFT_BUTTON. Ostatni nieopisany parametr funkcji MouseButton to state, który określa czy przycisk myszki został naciśnięty (stała GLUT_UP) czy zwolniony (stała GLUT_DOWN). Aby opisane funkcje obsługi myszki działały należy je dołączyć do listy funkcji zwrotnych dopisując w procedurze Init_OpenGL:

Listing:
  1. glutWireSphere (GLdouble radius, GLint slices, GLint stacks)

Rysowanie brył.
Kula
Powierzchnia kuli rysowana jest jako szkielet składający się z południków i równoleżników (rysunek 1). Powierzchnię kuli, której środek znajduje się w początku układu współrzędnych rysuje funkcja:

Listing:
  1. glutWireCube (GLdouble size)

której parametry oznaczają: ? radius - promień kuli, ? slices - ilość południków, ? stacks - ilość równoleżników. Sześcian Sześcian o boku długości size i środku położonym w początku układu współrzędnych rysuje funkcja:

Listing:
  1. glutWireCone (GLdouble base, GLdouble height, GLint slices, GLint stacks)

Stożek
Stożek rysowany jest podobnie jak kula - jako szkielet oparty na równoległe do podstawy ?południki? i tworzące biegnące od wierzchołka stożka do krawędzi jego podstawy (rysunek 2). Stożek ze środkiem podstawy umieszczonym w początku układu współrzędnych i wierzchołkiem umieszczonym na dodatniej półosi OZ rysuje funkcja:

Listing:
  1. glutWireTorus (GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings)

której parametry oznaczają: ? base - promień podstawy stożka, ? height - wysokość stożka, ? slices - ilość tworzących, ? stacks - ilość ?południków?.
Torus
Kolejna bryłą obrotową dostępną w bibliotece GLUT jest torus rysowany jako seria walców o nierównoległych podstawach (rysunek 3) i osi obrotu pokrywającej się z osią OZ. Torus rysuje funkcja:

Listing:
  1. glutWireOctahedron

której parametry oznaczają odpowiednio: ? innerRadius - promień koła tworzącego torus, ? outerRadius - promień torusa,1. Przekształcenia geometryczne 5 ? sides - ilość ścian bocznych, z których składa się pojedynczy walec, ? rings - ilość walców, z których składa się torus.
Ośmiościan
Ośmiościan o środku położonym w początku układu współrzędnych rysuje procedura:

Listing:
  1. glutWireTetrahedron

Czworościan Czworościan o środku położonym w początku układu współrzędnych rysuje procedura:

  1.  

Większość brył ma też odpowiedniki w których "wire" w nazwie procedury zastąpiono słowem "solid". Funkcje te służą do rysowania modeli z ścianami wypełnionymi polygonami zamiast samych krawędzi.

Dodałem na końcu procedury Display2 rysowanie ośmiościanu i sześcianu i to by było na tyle, efekt można zobaczyć tutaj:

Źródła: Kod źródłowy przykładu

Bibliografia:
http://januszg.hg.pl/index.html
http://pyopengl.sourceforge.net/
http://pl.wikipedia.org/

Tagged as: , , Komentarze
Komentarze (5) Trackbacks (0)
  1. Mam pytanie odnośnie światła. Czy wiesz jak je zaimplementowac, czy probowales na podanym zrodle? Mam poblem z uzyciem swiatla(nic sie nie dzieje albo cala scena znika pod jednolitym kolorem) Widziałem przykłady pisane w C.

  2. Żeby używać oświetlenia, to oprócz współrzędnych, każdy wierzchołek musi mieć określony wektor normalny. Bo troche to mało intuicyjne, ale jeżeli nie napiszesz sam PixelShadera to standardowa obsługa oświetlenia działa na wierzchołki, a nie na powierzchnie. Dlatego potrzebny jest ten wektor normalny określający w którą strone dany wierzchołek jest skierowany. Opis jak się do tego zabrać jest tutaj
    link

  3. Lub, co też całkiem użyteczne w naszych prostych programach skorzystać z bibliotek GLU i GLUT, które rysują stożki, walce i wiele innych dużo bardziej złożonych obiektów/powierzchni. Odnośnie znikania sceny pod jednolitym kolorem bardzo możliwe że musisz użyć w czasie inicjalizacji openGL funkcji GlEnable(GL_COLOR_MATERIAL), gdyż prawdopodobnie (nie rozgryzłem tego do końca) OpenGL ignoruje ustawienia podane w trakcie rysowania za pomocą glColor uznając tylko specyfikacje materiału (glMaterial), powyższe nakarze mu traktować kolo tak podany jako częsć specyfikacji materiału. Więcej możesz przeczytać np tutaj: http://www.gamedev.net/reference/articles/article1682.asp

  4. „Kopiujemy plik glut32.dll z ..\Glut3.7.6\Bindings do ..\Windows\system, dzięki temu nie będziemy musieli mieć go w katalogu z każdym plikiem exe.”

    Czy tu nie chodzi przypadkiem o folder system32 ?


Trackbacks are disabled.