Grafika OpenGL
C++ Builder i Delphi.




Builder / Delphi a OpenGL

5 funkcji

Prosty program

Łatwiejsze rysowanie

Animacja

Oświetlenie


Strona domowa ...

Biblioteka OpenGL jest zespołem algorytmów, stworzonym do programistycznej wizualizacji wirtualnego świata.
OpenGL to nieograniczone możliwości kreowania obrazu i bardzo duża, zoptymalizowana szybkość działania.
Wstęp do OpenGL jest raczej prosty - szczególnie dla programistów języka C. Nieco problemów może sprawiać włączanie algorytmów OpenGL w strukturę kodu, generowaną przez Builder czy Delphi, ale nie są to kłopoty poważne.

Napiszemy jednookienkowy program, wykorzystujący grafikę OpenGL. Będziemy pisać w kompilatorze C++ Builder wersja 1.0, ale programiści Delphi niech zwrócą uwagę na wielkie podobieństwo generowanego kodu.

Alorytmy umieścimy w 5 (lub 4, gdy bez animacji) funkcjach - reakcjach na zdarzenia z udziałem okienka.

Algorytmy OpenGL dla wygody sformuujemy w postaci 5 funkcji, których wywołania umieścimy odpowiednio w 5 wspominanych funkcjach - reakcjach na zdarzenia.

Mam nadzieję, że uzyskasz w ten sposób zrozumiały szkic aplikacji, pozwalającej na dalsze wycieczki w świat OpenGL.

Builder + OpenGL


Algorytmy OpenGL zmieścimy w 5 funkcjach - reakcjach na zdarzenia:

- OnCreate - tutaj zainicjujemy pracę z biblioteką,
- OnDestroy - zakończymy pracę.
- OnResize - odpowiemy na zmianę geometrii okienka.
- OnPaint - wykreślimy scenę.
- OnKeyDown - przydamy ewentualnej interaktywności.

Funkcje powyższe należą do świata formy (okienka). Automatyczną animację utworzymy, dodatkowo umieszczając w okienku komponent Timer (zakładka System) i generując funkcję tyknięcia Timera:

- OnTimer - animacja.

W pliku CPP nie możemy zapomnieć o doklejeniu nagłówków (deklaracji) symboli biblioteki OpenGL. Ponieważ potrzebne nagłówki znajdują się (a przynajmniej powinny) w podkatalogu o nazwie gl domyślnego katalogu (czyli include w katalogu instalacyjnym Buildera), w górnej części pliku CPP piszemy:

...
#include "gl/gl.h"
#include "gl/glu.h"
...

Samo mięso OpenGL, w postaci plików opengl32.lib i glu.lib, powinny znalezć się w domyślnym katalogu z bibliotekami (czyli lib w katalogu instalacyjnym Buildera).

Tu są pliki biblioteki OpenGL (36 kB)

Pięć funkcji, które musisz napisać


Algorytmy OpenGL można już umieszczać w funkcjach - reakcjach na omówione wyżej zdarzenia. Ale lepiej - chocby na przyszłość - będzie zebrać je w kilku funkcjach:

bool otworz_OpenGL( void);

void inicjuj_projekcje( void);

void inicjuj_scene( void);

void scena( void);

void zamknij_OpenGL( void);

Funkcje te należy zadeklarować w pliku H (jako prywatne składniki formy):

class TForm1 : public TForm
{
...
private:
HDC hdc;
HGLRC hrc;
bool otworz_OpenGL( void);
void inicjuj_projekcje( void);
void inicjuj_scene( void);
void scena( void);
void zamknij_OpenGL( void);
...
};

W pliku CPP trzeba spisać ich mięsa.

Schemat programu w C++ Builder będzie teraz wyglądał tak:

//---------------------------------------------------------------------------
// Start programu
void __fastcall TForm1::FormCreate(TObject *Sender)
{
otworz_OpenGL();
}
//---------------------------------------------------------------------------
// Zmieniono rozmiar okienka - odbuduj teatr OpenGL
// Wazne jest, że funkcja ta jest automatycznie wołana przy starcie -
// dlatego wywołanie poniższych dwóch algorytmów pominęliśmy w FormCreate()
void __fastcall TForm1::FormResize(TObject *Sender)
{
inicjuj_projekcje();
inicjuj_scene();
scena();
}
//---------------------------------------------------------------------------
// Odświez obraz
void __fastcall TForm1::FormPaint(TObject *Sender)
{
scena();
}
//---------------------------------------------------------------------------
// Koniec prezentacji
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
zamknij_OpenGL();
}
//---------------------------------------------------------------------------
// Koniec
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,TShiftState Shift)
{
if( Key == VK_ESCAPE)
Close();
}

Jest sens zamknąć algorytmy OpenGL w owych pięciu funkcjach - całość staje się dużo czytelniejsza i łatwiejsza do opanowania.

Funkcje otworz_OpenGL(), inicjuj_projekcje() i zamknij_OpenGL() w zasadzie piszemy raz a dobrze - przynajmniej na początku nie będziemy w nich grzebać. Ich algorytmy proszę wyciągnąć z dowolnego z zamieszczonych tutaj przykładów.

Funkcja inicjuj_scene() jest potrzebna do zadawania rozkładu świateł i rodzaju powierzchni. Na początek wewnątrz tej funkcji będziemy tylko czyścić okienko.

Funkcja scena() to zasadnicze pole bitwy - tutaj opisuje sie wszystkie elementy wirtualnego świata (w naszym przykładzie jest to czworokąt). Do opisu wykorzystuje się tzw. prymitywy OpenGL - punkty, linie, wielokąty, ...

Prosty, statyczny program


Najprostszy program wykreśla czworokąt. Oto kluczowa funkcja scena():

//---------------------------------------------------------------------------
// Wykreślenie obiektu na scenie.
void TForm1 :: scena( void)
{
float a = 30.0;

glClear(GL_COLOR_BUFFER_BIT);
glColor3f( 0.8, 0.8, 0.0);
// Teraz będzie opis obiektu
glBegin( GL_QUADS);
glVertex3f( -a, 0, -200.0);
glVertex3f( 0, -a, -200.0);
glVertex3f( a, 0, -200.0);
glVertex3f( 0, a, -200.0);
glEnd();

// popędzenie aparatu renderującego ...
glFlush();
// ... i wyświetlenie grafiki z bufora
SwapBuffers(hdc);
}

Czyścimy tzw. bufor koloru, ustalamy kolor roboczy i wykreślamy czworokąt, podając lokalizacje (x, y, z) każdego z czterech wierzchołków.
Potem popędzamy ewentualne zaległe operacje w aparacie kreślącym OpenGL i Windows i wysyłamy bufor obrazu na ekran.

Tu są pliki trywialnego przykładu (znaczy trywialne są funkcje inicjuj_scene() i scena())(4 kB)

Łatwiej kreślić blisko punktu (0,0,0)


W poprzednim przykładzie czworokąt leżał -200 metrów w głąb ekranu. Teraz narysujemy go w poblizu punktu (0, 0, 0), ale przed wyświetleniem przesuniemy o 200 metrów w tył. Efekt będzie taki sam, ale zazwyczaj o wiele łatwiej definiuje się rzeczywistość rozłożoną wokół środka układu współrzędnych ...

void TForm1 :: scena( void)
{
float a = 30.0;

glClear(GL_COLOR_BUFFER_BIT);
glColor3f( 0.8, 0.8, 0.0);

// zapamiętanie wyglądu sceny
glPushMatrix();
// odsunięcie obiektu
glTranslatef( 0.0, 0.0, -200.0);
//opis obiektu
glBegin( GL_QUADS);
glVertex3f( -a, 0, 0.0);
glVertex3f( 0, -a, 0.0);
glVertex3f( a, 0, 0.0);
glVertex3f( 0, a, 0.0);
glEnd();
// odtworzenie pierwotnego wyglądu sceny
glPopMatrix();

glFlush();
SwapBuffers(hdc);
}

Przybyły 3 instrukcje - przesunięcie o 200 metrów, ale także przechowanie widoku przed owym przesunięciem i odtworzenie po zakończonym wykreślaniu. Czworokątna płytka była przesuwana tylko w celu zobrazowania. Po wykreśleniu nadal spoczywa w środku układu współrzędnych.

Tu są pliki tego przykładu w C++ Builder (4 kB)

Animacja


Animacja polega na okresowym zmienianiu jakiegoś parametru wykreślanej sceny, np. kąta, pod jakim widzimy naszą płytkę.
Do funkcji scena() wprowadzimy parametr - kąt obrotu obiektu przed wykreśleniem:

//---------------------------------------------------------------------------
// Wykreślenie obiektu na scenie.
void TForm1 :: scena( void)
{
float a = 30.0;

glClear(GL_COLOR_BUFFER_BIT);
glColor3f( 0.8, 0.8, 0.0);

glPushMatrix();

glTranslatef( 0.0, 0.0, -200.0);
glRotatef( alfa, 0.0, 1.0, 0.0);

glBegin( GL_QUADS);
glVertex3f( -a, 0, 0.0);
glVertex3f( 0, -a, 0.0);
glVertex3f( a, 0, 0.0);
glVertex3f( 0, a, 0.0);
glEnd();

glPopMatrix();
glFlush();
SwapBuffers(hdc);
}

Przybyła jedna funkcja - obrócenie obiektu wokół osi OY o kąt alfa. Kąt ten jest zadeklarowany w części prywatnej w pliku H, zainicjowany na zero w funkcji - reakcji na OnCreate i sukcesywnie popychany w funkcji - reakcji na tyknięcie zegara OnTimer:

//---------------------------------------------------------------------------
// Animacja sceny
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
float deltax = 5.0;

alfa += deltax;
alfa = fmod( alfa, 360.);

scena();
}

Tu są pliki tego przykładu w C++ Builder (4 kB)

Oświetlenie


Oświetlenie zmusi nas do ingerencji w funkcję inicjuj_scenę(). Zadamy tam rozkład świateł i określimy cechy materiału płytki.
Oświetlenie będzie teraz wyliczane, zatem z funkcji scena() usuniemy linię ustawiania koloru.
W OpenGL znajdziemy 3 typy światła - otaczające (ambient), rozproszone (difussion) i lustrzane (specular). Dwa ostatnie typy mają właściwości kierunkowe.
Znajdziemy też opisy optycznych właściwości powierzchni.

//---------------------------------------------------------------------------
// Zdefiniowanie światła i jego cech (położenie, kolory składowych ogólnej,
// rozproszonej i lustrzanej). Zdefiniowanie właściwości materiału
void TForm1 :: inicjuj_scene( void)
{
float swiatlo_otoczenia[] = { 0.1, 0.1, 0.1, 1.0};
float swiatlo_rozproszone[] = { 0.2, 0.2, 0.6, 1.0};
float swiatlo_odbite[] = { 1.0, 0.2, 0.2, 1.0};
float swiatlo_pozycja[] = { 0.0, 0.0, -20.0, 1.0};
// przeliczaj kolor obydwu stron płytki
glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1);
// włącz analizę oświetlenia
glEnable( GL_LIGHTING);
// światło oblewające
glLightfv( GL_LIGHT0, GL_AMBIENT, swiatlo_otoczenia);
// światło rozproszone
glLightfv( GL_LIGHT0, GL_DIFFUSE, swiatlo_rozproszone);
// światło odbite
glLightfv( GL_LIGHT0, GL_SPECULAR, swiatlo_odbite);
// Ustawienie żarówki w określonym miejscu (za plecami)
glLightfv( GL_LIGHT0, GL_POSITION, swiatlo_pozycja);
// włączenie żarówki nr 0
glEnable( GL_LIGHT0);
// aktywowanie analizy właściwości materiału
glEnable( GL_COLOR_MATERIAL);
// front i tył są zanurzone w świetle otaczającym ...
glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT);
// ... i rozpraszają światło żarówki ...
glColorMaterial( GL_FRONT_AND_BACK, GL_DIFFUSE);
// tylko front odbija je lustrzanie
glColorMaterial( GL_FRONT, GL_SPECULAR);
// określenie rozwarcia stożka odbicia lustrzanego
glMateriali( GL_FRONT, GL_SHININESS, 128);

glClearColor( 0.0, 0.0, 0.1, 0.0);
}

Tu są pliki tego przykładu w C++ Builder (4 kB)

(c) Astar'05, 2003.08.20