Grafika 3D
 
  Zarejestruj się
::  Newsy  ::  Pliki  ::  Twoje Konto  ::  Forum  ::
Menu
· Strona główna
· Forum
· Linki
· Lista u?ytkowników
· O nas...
· Pliki
· Statystyki
· Twoje Konto
Tutoriale
· API
· Matematyka
· Teoria
· Direct3D
· OpenGL
· Techniki
Kto Jest Online
Aktualnie jest 45 gość(ci) i 0 użytkownik(ów) online.

Jesteś anonimowym użytkownikiem. Możesz się zarejestrować za darmo klikając tutaj
Tutoriale - OpenGL - Bryły

No jak widać mój poprzedni artykuł spodobał się Robalowi, więc teraz ja zajmę się działem OpenGL :-).

Oj, dzisiaj będzie dużo do napisania (jak dla Was to do przeczytania :-) zajmiemy się bowiem bryłami. Ale oprócz konstruowania samych brył przejdziemy w końcu (jak obiecałem poprzednio) do „prawdziwego” środowiska 3D, czyli rzutu perspektywicznego, dodamy też bufor Z. Mówię „prawdziwego”, bo tak naprawdę rzut prostokątny to też 3D, ale dopiero przy rzucie perspektywicznym dokładnie widać, że to rzeczywiście 3D :-). Dodałem również niejaką interakcję z programem - może nie za dużo, ale zawsze coś. Szczerze mówiąc chciałem też dodać przejście na tryb pełnoekranowy, ale stwierdziłem, że to mogłoby być trochę za dużo i nieco zaciemnić samo renderowanie brył, więc zrezygnowałem. Ale nie martwcie się, co ma wisieć, nie utonie, więc na pewno niedługo Waszym oczom powinien pokazać się artykuł poświęcony temu zagadnieniu. Na razie musicie się zadowolić możliwością powiększania i pomniejszania okna z zachowaniem proporcji obiektów.
No dobra koniec gadania, bierzemy się do roboty. Będziemy używać szablonu z poprzedniego przykładu, ale nieco go zmodyfikujemy, choć jeśli ktoś chce sobie poćwiczyć, to nic nie stoi na przeszkodzie, żeby napisał wszystko od podstaw :-D.

Na początku kodu zobaczymy takie dwie linie:

#pragma comment( lib, "opengl32.lib" )
#pragma comment( lib, "glu32.lib" )

Tym, którzy nigdy nie zetknęli się z komendą #pragma (jest to, fachowo mówiąc, dyrektywa preprocesora), śpieszę wyjaśnić, że to co tu widzą to nie, jak mogliby przypuszczać, jakieś pragmatyczne komentarze :-) do bibliotek opengl32.lib i glu32.lib, ale dołączenie tych bibliotek przy linkowaniu (konsolidacji) do naszego skompilowanego programu. Efekt jest dokładnie taki sam, jak dodanie tych bibliotek w ustawieniach naszego projektu, ale o tym można czasem zapomnieć, a ktoś, kto nie ma jakiejś dużej styczności z programowaniem może, być lekko zdezorientowany, kiedy podczas linkowania wyskoczy mu np. coś takiego:

glSolids.obj : error LNK2001: unresolved external symbol __imp__glFlush@0

No to teraz już wiemy, że takie cosik (fajne nie :-) to nic innego, jak brak implementacji funkcji, które są zdefiniowane w pliku *.h (u nas gl.h), a zaimplementowane w dll-u o nazwie opengl32.dll. To właśnie funkcje z dll-a są eksportowane w pliku .lib, a dzięki temu możemy ich używać (mam nadzieję, że wszyscy rozumieją o co chodzi? Jeśli nie, to odsyłam do literatury poświęconej programowaniu Windows i bibliotekom dll :-).
Na początku możemy zobaczyć również trzy nowe zmienne:
bool keys[256];
bool mp=false;
int  mode=0;
Służą one do obsługi klawiatury. Nie jest to może najlepsza metoda obsługi, ale na początek wystarczy. Pierwsza zmienna to tablica, której zadaniem jest przechowywanie wartości logicznej dla każdego klawisza tzn. jeśli klawisz M jest naciśnięty, to keys['M']=TRUE, w przeciwnym przypadku keys['M']=FALSE :-). Drugi parametr jest również związany z klawiaturą (a dokładniej z klawiszem M), ale jego działanie wyjaśnię później, tak jak działanie trzeciego :-).
W ostatnim przykładzie dodałem funkcję InitGLWindow(). Dziś ją trochę przerobimy - po pierwsze będzie się nazywała InitGL(), a służyć będzie do inicjalizacji samego OpenGL-a. Jej treść również się zmieni. Nie zmieni się natomiast miejsce, w którym ją wywołujemy. W funkcji zobaczymy kilka nowych (nieznanych nam jeszcze) wywołań funkcji gl-owskich. Otóż i one:

glEnable( GL_DEPTH_TEST );

Cóż to za funkcja i co ona wykonuje? Najpierw może trochę teorii. Otóż w OpenGL-u wszystkie zmienne stanu - OpenGL jest tzw. maszyną stanu jak już wiemy - (np. buffor Z, światło itd...) włącza się za pomocą właśnie funkcji:

glEnable( Glenum param );

Gdzie param jest jedną z wartości zdefiniowanych wewnątrz API - wystarczy, że zaglądniecie do pliku gl.h, a znajdziecie tam całe mnóstwo różnych stałych (pod opisem ENABLE :-). U nas funkcja ta włącza buffor Z.

glDepthFunc( GL_LEQUAL );

Funkcja związana z buforem Z. Za jej pomocą ustalamy, jakim porównaniem ma się posługiwać nasz bufor Z, czyli które piksele mają być wyświetlone na ekranie. Oto dostępne wartości:
  • GL_NEVER - czyli warunek nigdy nie jest spełniony. Jak nie trudno się domyślić, podając ten warunek spowodujemy, że na ekranie nic się nie pojawi.
  • GL_LESS - warunek jest spełniony (tzn. piksel jest rysowany na ekranie i zapisywany w buforze Z), kiedy obecna (sprawdzana) wartość z jest mniejsza od poprzednio zapamiętanej.
  • GL_LEQUAL - warunek jest spełniony, kiedy sprawdzana wartość z jest mniejsza lub równa wartości poprzednio zapamiętanej.
  • GL_EQUAL - warunek jest spełniony, kiedy sprawdzana wartość z jest równa wartości poprzednio zapamiętanej.
  • GL_NOTEQUAL - warunek jest spełniony, kiedy sprawdzana wartość z jest różna od poprzednio zapamiętanej.
  • GL_GREATER - warunek jest spełniony, kiedy sprawdzana wartość z jest większa od wartości poprzednio zapamiętanej.
  • GL_GEQUAL - warunek jest spełniony, kiedy sprawdzana wartość z jest większa lub równa od poprzednio zapamiętanej.
  • GL_ALWAYS - warunek zawsze spełniony. Efekt jest taki, jakby bufor Z był wyłączony.
My używamy GL_LEQUAL, bo jest to normalny tryb pracy. Tryb ten jest domyślny (czyli jeśli nie wywołamy tej funkcji, to bufor Z będzie działać tak, jakbyśmy ją wywołali), ale chciałem pokazać, że takie coś jest, więc wywołałem tę funkcję.

glEnable( GL_CULL_FACE );

Kolejne wywołanie funkcji ustawiającej stan OpenGL-a. Tym razem włączamy usuwanie niewidocznych powierzchni. Co to takiego? Otóż, jak powszechnie wiadomo, jednym z najważniejszych czynników w grafice 3D jest szybkość i jakość obrazu. W prostych słowach chodzi o to, że kiedy rysujemy na ekranie sześcian, to możemy zobaczyć tylko zewnętrzne części jego ścian - nie widzimy tego, co jest w środku. No to skoro nie widzimy, to po co to rysować? Oczywiście, że jest to zupełnie bezsensowne, dlatego właśnie powstało coś takiego jak usuwanie niewidocznych powierzchni. Zapytacie pewnie, jakim kurczę sposobem komputer (a właściwie program :-) wie, która część ściany jest z przodu, a która z tyłu? I tu właśnie pojawia się problem - otóż bez naszej pomocy nie ma on o tym żadnego pojęcia. Jak w takim razie udzielić mu tej pomocy? Robimy to definiując poszczególne trójkąty, czy też kwadraty. To, która część ściany jest tylna, a która przednia, nasz program wie, z kolejności w jakiej podajemy mu wierzchołki. Są dwie możliwości - albo podamy wierzchołki zgodnie z ruchem wskazówek zegara, albo przeciwnie. Do kolejności wrócę, przy omawianiu naszej funkcji renderującej. Wspomniałem, że ma to również wpływ na jakość renderowanych obiektów - jeśli chcecie zobaczyć jaki, to weźcie tą linię (tzn. glEnable( GL_CULL_FACE )) w komentarz i przypatrzcie się sześcianowi.

glCullFace( GL_BACK );

Czyli mówimy OpenGL, której strony ma nie renderować. U nas jest to tył (chyba logiczne prawda? :-). Istnieją też inne możliwości - GL_FRONT oraz GL_FRONT_AND_BACK. Mam nadzieję, że wszyscy rozumieją jaki jest efekt, jeśli podamy takie parametry :-D. GL_BACK jest wartością domyślną.

glFrontFace( GL_CCW );

Wywołanie tej funkcji określa, którą stronę wielokąta traktujemy jako przednią. Możliwe są dwie wartości:
  • GL_CCW - czyli strona przednia to ta, której wierzchołki ułożone są przeciwnie do ruchu wskazówek zegara,
  • GL_CW - czyli przednia strona to ta, której wierzchołki ułożone są zgodnie z ruchem wskazówek zegara.
My wybieramy GL_CCW, ponieważ podawaliśmy wierzchołki w kierunku przeciwnym do ruchu wskazówek zegara. Jest to również ustawienie domyślne.

glHint( GL_PERSPECTIVE_CORECTION_HINT, GL_NICEST );

Jest to ostatnia funkcja w InitGL(). Ustala ona jakość interpolacji dla tekstur i koloru, czyli dokładność, z jaką będą obliczane. Drugi parametr może być jedną z trzech wartości:
  • GL_NICEST - czyli najlepsza jakość i najwięcej obliczeń,
  • GL_FASTEST - czyli najszybsze obliczenia, a co za tym idzie gorsza jakość,
  • GL_DONT_CARE - czyli OpenGL sam ustali jakość - wybierze jedną z powyższych wartości.
Pierwszy parametr też może przybierać kilka innych wartości, ale nie jest to nam w tej chwili do niczego potrzebne. Jeśli zacznie być, to na pewno tego nie pominę :-).

No, to chyba tyle, jeśli chodzi o naszą funkcję inicjującą (sporo tego, jak na sześć linijek kodu ;-). Zajmijmy się teraz drugą funkcją globalną - pozwalającą na zachowanie proporcji naszych obiektów przy zmianie wielkości okna. Nazywa się ResizeGLWindow(), a jako parametry przyjmuje dwie zmienne całkowite - szerokość i wysokość okna (a właściwie obszaru klienta). Ma ona również za zadanie ustalenie rzutowania perspektywicznego i bryły obcinania. No to popatrzmy cóż to za cudo.
if( wysokosc == 0 )
{
    wysokosc=1;
}
Linijka ta zapewnia nam, że nie będziemy dzielić przez 0, ale to chyba oczywiste.
glViewport( 0, 0, szerokosc, wysokosc );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
Te funkcje znamy już dobrze, więc nie będę ich opisywał. Jeśli ktoś nie pamięta do czego służą, to niech zaglądnie do artykułu o macierzach w OpenGL.

gluPerspective( 45.0f, (float) szerokosc / (float) wysokosc, 0.01, 100 );

To natomiast jest kolejna bardzo użyteczna funkcja z biblioteki pomocniczej glu (kolejna, bo poznaliśmy już gluOrtho2D). Jej zadaniem jest ustalenie rzutowania perspektywicznego i bryły obcinania. Pierwszym przyjmowanym parametrem jest kąt rozwarcia naszej bryły widzenia w kierunku pionowym. Następnie jest stosunek szerokości naszego okna do jego wysokości - to właśnie on zapewnia prawidłowe powiększanie i pomniejszanie naszej sceny. Dalej są dwa parametry definiujące odpowiednio bliższą (przednią) i dalszą (tylną) płaszczyznę obcinania. Jeśli ktoś nie wie jak to wygląda, lub chciałby się dowiedzieć czegoś więcej na temat rzutowania i bryły obcinania, to odsyłam do działu "Matematyka" - tak każdy dowie się tego, co powinien na ten temat wiedzieć (a powinien całkiem sporo :-). To była właściwie jedyna funkcja wymagająca wytłumaczenia. Teraz jeszcze tylko musimy naszą funkcję zmieniającą rozmiar okna dodać w odpowiednie miejsce. Tym miejscem jest funkcja obsługi komunikatów naszej kochanej Windozy :-). Musimy obsłużyć komunikat WM_SIZE, który jest wywoływany, kiedy okno zmieni rozmiar (kiedy zmieni, a nie zmienia - to inny komunikat). Dodajemy więc linie:
case WM_SIZE:
    ResizeGLWindow( LOWORD( lParam ), HIWORD( lParam ) );
    break;
Jak widać szerokość i wysokość naszego obszaru klienta jest zakodowana w zmiennej lParam. Tak więc musimy odkodować nasze wartości - LOWORD to makro pozwalające wyciągnąć mniej znaczące słowo, a HIWORD bardziej znaczące. Przy okazji omawiania funkcji obsługi komunikatów wspomnę jeszcze, że zmieniła się ona trochę w miejscu, gdzie wywoływany jest komunikat WM_KEYDOWN. Teraz jest tam keys[wParam]=TRUE, a to dlatego, że parametr wParam przechowuje wartość numeryczną naciśniętego klawisza. Tak więc zaznaczmy, który klawisz został naciśnięty. Natomiast w komunikacie WM_KEYUP mamy: keys[wParam]=FALSE - no chyba proste :-) klawisz został zwolniony, więc odnotowujemy to w naszej tablicy.
Zmieniła się także, ale tylko troszkę, funkcja WinMain(). Jest tam teraz dodatkowo sprawdzanie, czy klawisz M został naciśnięty - tu właśnie pojawia się nasza zmienna mp - jeśli chcecie wiedzieć do czego służy, to weźcie ją w komentarz. Widzimy, że jeśli warunek jest spełniony, to wywoływana jest funkcja gl-owska (nie muszę chyba opisywać do czego służy zmienna mode i jak jej używam? - w końcu to kurs OpenGL-a, a nie C++ ;-).

glPolygonMode( GL_FRONT_AND_BACK, ... );

Jest to funkcja, której zadaniem jest ustalić sposób renderowania wielokątów. Można je renderować na trzy sposoby - wypełnione, jako szkielet (same linie), lub jako same punkty. Jako pierwszy argument przyjmuje takie same wartości, jak funkcja glCullFace(...) (czyli GL_FRONT_AND_BACK, GL_BACK, GL_FRONT). Za ich pomocą ustala się, która strona wielokąta ma być wyrenderowana jako pełna, a która jako siatka (można oczywiście, tak jak my, renderować obie strony jednocześnie). Jako drugi parametr podajemy jedną z trzech wartości:
  • GL_FILL - renderuje daną stronę wielokąta jako wypełnioną
  • GL_LINE - renderuje daną stronę wielokąta jako siatkę (WAŻNE: GL_LINE, a nie GL_LINES!!!)
  • GL_POINT - renderuje daną stronę wielokąta jako punkty (znowu: GL_POINT, a nie GL_POINTS!!!)
Na koniec została nam jeszcze funkcja renderująca i to nieszczęsne ułożenie wierzchołków. Szczerze mówiąc, to nie zaszły tam jakieś drastyczne zmiany. Jest tylko trochę więcej wywołań glVertex() i glColor(), a reszta jest praktycznie nie zmieniona.

Jedną z ważniejszych zmian, jakie zaszły, jest dodanie jednej wartości do funkcji glClear(). Tą wartością jest GL_DEPTH_BUFFER_BIT - jak widać jest połączony operacją bitową | (Or) z poprzednim parametrem. Pojawienie się tego parametru powoduje, że przy każdym wywołaniu funkcji renderującej jest czyszczony bufor głębokości (bufor Z), a połączenie jej z poprzednią wartością powoduje, że bufor koloru również zostanie wyczyszczony.

Zostało mi jeszcze omówić tylko kolejność wierzchołków. Zrobię to na przykładzie :

glVertex3f( 0.0f, 1.0f, 0.0f );
glVertex3f(-1.0f,-1.0f, 1.0f );
glVertex3f( 1.0f,-1.0f, 1.0f );

Są to wierzchołki jednej ze ścian naszej piramidy. Ich ułożenie jest takie:
  • Pierwszy wierzchołek leży na osi Y i przesunięty jest do góry.
  • Drugi przesunięty jest w lewo po osi X, w dół po osi Y i do przodu po osi Z.
  • Trzeci przesunięty jest w prawo po osi X, w dół po osi Y i do przodu po osi Z.
Wygląda to więc mniej więcej tak:


Tak więc jak widać układamy wierzchołki przeciwnie do ruchu wskazówek zegara, co powoduje, że z tej strony jest przód. Jeśli natomiast na nasz obrazek popatrzylibyśmy od tyłu, (tzn. od wewnątrz monitora) to zauważylibyśmy, że z tamtej strony wierzchołki ułożone są zgodnie z ruchem wskazówek zegara. No to wyjaśniła się tajemnica, w jaki sposób program wie, jak te nasze niewidoczne powierzchnie usuwać - nie renderuje tej strony, która jest ułożona zgodnie z ruchem wskazówek zegara).
Mam nadzieję, że wyjaśniło się wszystko, a przynajmniej dość dużo jeśli chodzi o bryły - tzn. o podstawy ich konstrukcji i renderowania. Będę szedł zgodnie ze ścieżką obraną przez Robala - czyli następny tutorialik będzie o światłach w OpenGL, przy okazji pokażę, jak możemy przyspieszyć renderowanie używając list wyświetlania. A mniej więcej taki efekt powinien otrzymać każdy uruchamiając ten program.



Kod źródłowy

©Copyright by Domino   



Tutoriale - OpenGL
Nasze newsy s� w RSS: backend.php
PHP-Nuke Copyright © 2005 by Francisco Burzi. This is free software, and you may redistribute it under the GPL. PHP-Nuke comes with absolutely no warranty, for details, see the license.
Tworzenie strony: 0.05 sekund

:: Layout strony został stworzony przez www.nukemods.com w oparciu o styl phpbb2 Helius, którego autorem jest Cyberalien ::