Koło informatyczne 2013

Wprowadzenie do OpenGL

Rozpoczynamy prosty kurs tworzenia grafiki trójwymiarowej przy wykorzystaniu biblioteki OpenGL. Jako środowisko będziemy używali Borland C++ Buildera. Materiały do zajęć zaczerpnięte są  częściowo z dostępnego w sieci kursu NeHe. Oczywiście będą one przystosowane do specyfiki programowania w środowisku Borland C++ Builder.

Biblioteka OpenGL (Open Graphic Library - Otwarta Biblioteka Graficzna) zawiera funkcje, które umożliwiają tworzenie zaawansowanej grafiki trójwymiarowej. Programy napisane pod tę bibliotekę można szybko przenosić na inne platformy, np. do systemu Linux.

Przygotowanie środowiska

Zanim zaczniemy tworzyć jakąkolwiek grafikę 3D, musimy utworzyć odpowiedni projekt w środowisku Borland C++ Builder, który posłuży za szablon przyszłych projektów. W tym celu utwórz projekt, który zawiera pojedynczą formę i umieść na niej komponent Timer (z palety System). Następnie ustaw własności wg poniższej listy:

 

Form1

BorderStyle = bsSizable

Caption = Grafika OpenGL

ClientHeight = 512

ClientWidth = 512

Name = frm3D

Position = poScreenCenter

Timer1

Interval = 10

Name = tmrAnimate

 

Zapisz projekt na dysku. Nadaj mu nazwę glprj. Moduł zapisz pod nazwą glunit.

Do katalogu projektowego skopiuj poniższe dwa pliki:

 

 

Dodaj do projektu plik glaux.lib. W tym celu wybierz z menu opcje Project→Add to Project.., w oknie dialogowym wybierz katalog projektowy, następnie na dole wybierz pliki typu Library files lib., wskaż plik glaux.lib i zatwierdź go przyciskiem Otwórz.

 

Uwaga

Projekt ten zapamiętamy na końcu w składzie (ang. repository) Borland C++ Buildera, co pozwoli nam na szybki start przy tworzeniu aplikacji OpenGL. Dlatego stosuj się do proponowanych przez nas nazw, gdyż w ten sposób zachowasz spójność z resztą kursu.

 

Teraz przystąpimy do tworzenia kodu. Wciśnij klawisz F12, aby przejść do edytora. Następnie naciśnij Ctrl+F6, aby do edytora załadować plik nagłówkowy modułu glunit.h. Pod dyrektywami #include dopisz:

 

#ifndef glunitH
#define glunitH
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>

#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glaux.h>
#include <float.h>

//---------------------------------------------------------------------------
class Tfrm3D : public TForm
{
__published: // IDE-managed Components

Teraz w klasie obiektu dodajemy kilka definicji w sekcjach private i public:

 

private:	// User declarations
    HDC hdc;
    HGLRC hrc;
    int PixelFormat;

public:		// User declarations
    __fastcall Tfrm3D(TComponent* Owner);
    void __fastcall SetPixelFormatDescriptor();
    void __fastcall SetupRC();
};
//---------------------------------------------------------------------------
extern PACKAGE Tfrm3D *frm3D;
//---------------------------------------------------------------------------
#endif

 

Naciśnij ponownie Ctrl+F6, aby w oknie edytora pojawił się kod modułu glunit.cpp. W konstruktorze klasy umieść kod:

 

//---------------------------------------------------------------------------
__fastcall Tfrm3D::Tfrm3D(TComponent* Owner)
        : TForm(Owner)
{
  _control87(MCW_EM, MCW_EM);
}
//---------------------------------------------------------------------------

 

Teraz musimy wpisać do modułu kilka funkcji pomocniczych - nie przejmuj się, jeśli nie rozumiesz ich przeznaczenia w tym momencie. Są one potrzebne do ustawienia parametrów biblioteki OpenGL w środowisku Borland C++ Builder. Funkcje umieść na końcu modułu.

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::SetPixelFormatDescriptor()
{
  PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, 
        PFD_TYPE_RGBA, 
        24, 
        0,0,0,0,0,0, 
        0,0, 
        0,0,0,0,0, 
        32, 
        0, 
        0, 
        PFD_MAIN_PLANE, 
        0, 
        0,0,0 
        }; 
  PixelFormat = ChoosePixelFormat(hdc, &pfd);
  SetPixelFormat(hdc, PixelFormat, &pfd); 
} 
//---------------------------------------------------------------------------
void __fastcall Tfrm3D::SetupRC()
{
  glClearColor(0,0,0,0);
  glClear(GL_COLOR_BUFFER_BIT);
  glFlush();
}
//---------------------------------------------------------------------------

 

Zadaniem funkcji SetPixelFormatDescriptor jest przygotowanie Windows do rysowania za pomocą biblioteki OpenGL na obiekcie Canvas okna aplikacji.

Funkcja SetupRC ustawia kolor tła okna i wypełnia tym kolorem bufor koloru biblioteki OpenGL.

Reszta niezbędnych funkcji to funkcje obsługi zdarzeń formy oraz komponentu Timer. Przypominam, że funkcję obsługi zdarzenia tworzy się w okienku Object Inspector na zakładce Events przez dwukrotne kliknięcie myszką po prawej stronie nazwy zdarzenia. Wtedy środowisko tworzy szablon odpowiedniej funkcji. Należy jedynie wypełnić jej treść.

Utwórz funkcję obsługi zdarzenia OnCreate dla formy i wpisz poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::FormCreate(TObject *Sender)
{
  hdc = GetDC(Handle);
  SetPixelFormatDescriptor();
  hrc = wglCreateContext(hdc);
  wglMakeCurrent(hdc, hrc);
  SetupRC();
}
//---------------------------------------------------------------------------

 

Utwórz funkcję obsługi zdarzenia OnDestroy i wpisz poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::FormDestroy(TObject *Sender)
{
  ReleaseDC(0,hdc);
  wglMakeCurrent(hdc, NULL);
  wglDeleteContext(hrc);
}
//---------------------------------------------------------------------------

 

Nasz szablon będzie obsługiwał zmianę rozmiaru okna. Dlatego utwórz funkcję obsługi zdarzenia onResize i wprowadź do niej poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::FormResize(TObject *Sender)
{
  glViewport(0, 0, ClientWidth, ClientHeight); //
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0f,(GLfloat)ClientWidth/(GLfloat)ClientHeight,0.1f,300.0f);
  glMatrixMode(GL_MODELVIEW);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // kolor tła
  glEnable(GL_DEPTH_TEST);  // włącza bufor głębokości
  glDepthFunc(GL_LESS);   
  glEnable(GL_CULL_FACE);   // włącza opcję eliminacji ścian
  glFrontFace(GL_CW);       // ściany o wierzchołkach ułożonych zgodnie z ruchem wskazówek
                            // zegara będą traktowane jako zwrócone przodem
  glCullFace(GL_BACK);      // pomija rysowanie ścian odwróconych tyłem
}
//---------------------------------------------------------------------------

 

Ostatnie zdarzenie odnosi się do komponentu tmrAnimate (Timer). Kliknij jego nazwę w oknie Object TreeView. Następnie utwórz funkcję obsługi zdarzenia onTimer i wpisz do niej poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::tmrAnimateTimer(TObject *Sender)
{
  tmrAnimate->Enabled = false; // blokujemy timer
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity(); // zerujemy przekształcenia

  // Tutaj umieszczamy program dla OpenGL

  glTranslatef(0.0f,0.0f,-6.0f);

  glBegin(GL_TRIANGLES); // rysujemy trójkąt

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

  glEnd();

  // Koniec kodu dla OpenGL

  SwapBuffers(hdc);
  tmrAnimate->Enabled = true; // odblokowujemy timer
}
//---------------------------------------------------------------------------

 

Wyjaśnienia wymaga blokowanie Timera. Otóż może się zdarzyć, że na wolniejszym komputerze obsługa zdarzenia onTimer będzie dłuższa niż 20 ms. Wtedy następne przerwanie wystąpiłoby w trakcie tworzenia sceny i zniszczyłoby efekty całej naszej pracy. Aby temu zapobiec, funkcja obsługująca zdarzenie onTimer blokuje Timer na samym początku swego działania i odblokowuje go tuż przed zakończeniem.

 

Kod dla biblioteki OpenGL będziemy umieszczali w podanym miejscu wewnątrz funkcji obsługi zdarzenia onTimer. Przykładowy kod, który teraz tam umieściliśmy, rysuje biały trójkąt. Dokładniej omówimy to w dalszej części zajęć.

Sprawdź projekt, kompilując go i uruchamiając. Jeśli wszystko jest w porządku, powinieneś otrzymać czarne okno z białym trójkątem. Przy zmianie rozmiaru okna trójkąt też odpowiednio zmienia wymiary.

 

obrazek

 

Teraz na podstawie tego projektu utworzymy szablon, który posłuży nam za punkt startowy dla przyszłych projektów. Z menu wybierz opcję ProjectAdd to Repository. W oknie dialogowym dodawania do składu wpisz:

 

obrazek

 

Pamiętaj, aby nie usuwać z dysku katalogu z tym projektem, ponieważ zawiera on szablon. Aby przetestować ten szablon, utwórz na dysku nowy katalog projektowy, zamknij wszystkie projekty (opcja menu FileClose All), następnie wybierz FileNewOther..., w oknie dialogowym New Items wybierz zakładkę Projects.

 

obrazek

 

 

Następnie zaznacz Projekt OpenGL i kliknij OK. W oknie dialogowym Select Directory wejdź do katalogu (Uwaga: nie wystarczy go wskazać, musi być otwarty), w którym ma zostać utworzony projekt i kliknij OK. Na podstawie naszego szablonu zostanie utworzona kopia projektu, w której znajdą się już wszystkie pliki. Jesteś gotowy do rozpoczęcia nauki programowania biblioteki OpenGL.

 

Rysowanie podstawowych figur w OpenGL

Układ współrzędnych przestrzennych

Podstawową rzeczą, którą musisz opanować w OpenGL, jest sposób określania położenia wierzchołków figur. Przestrzenny układ współrzędnych w OpenGL wygląda następująco:

 

obrazek

 

Układ współrzędnych w przestrzeni trójwymiarowej posiada trzy wzajemnie prostopadłe do siebie osie: OX, OY i OZ. Każdy wierzchołek v (ang. vertex) posiada w tym układzie trzy współrzędne vx, vy i vz, które jednoznacznie określają jego położenie w przestrzeni trójwymiarowej.

 

obrazek

 

Jednostka odległości, wg której określane są te współrzędne, jest umowną liczbą, która zależy od ustawień początkowych w OpenGL. Ustawienia widoku w naszej aplikacji dokonywane są wewnątrz obsługi zdarzenia onResize. Znajduje się tam wywołanie funkcji:

 

gluPerspective(45.0f,(GLfloat)ClientWidth/(GLfloat)ClientHeight,0.1f,300.0f);

 

Ustawia ona "sposób patrzenia" na tworzoną scenę. Pierwszy parametr, 45.0f, tzw. określa kąt w stopniach ostrosłupa widzenia. Większy kąt obejmie więcej przestrzeni, lecz spowoduje efekt rybiego oka, czyli zniekształcenia perspektywiczne. Możesz sobie później poeksperymentować z tymi parametrami.

Kolejny parametr określa współczynnik kształtu prostokąta widzenia. Jest to stosunek szerokości do wysokości obszaru płótna Canvas. Zwróć uwagę, że przed podzieleniem oba parametry zostają przekonwertowane na postać GLfloat, czyli na liczby zmiennoprzecinkowe typu float. Bez tej konwersji komputer wykonałby dzielenie całkowite, a wynikiem byłoby 1 lub zero, w zależności co większe, wysokość czy szerokość okna.

Ostatnie dwa parametry, 0.1f i 300.0f, to tzw. głębia bufora wyświetlania. Punkty będą rysowane, jeśli ich współrzędne z będą się zawierały w zakresie od -0.1 do -300. Punkty bliższe i dalsze nie będą rysowane.

Współrzędne w OpenGL są liczbami zmiennoprzecinkowymi. Ponieważ oś OZ jest skierowana w kierunku obserwatora, to wierzchołki o współrzędnej z większej są bliżej obserwatora od wierzchołków o współrzędnej z mniejszej.

Układ współrzędnych można przesuwać względem położenia obserwatora przy pomocy funkcji

 

glTranslatef(dx,dy,dz);

 

Funkcja ta przyjmuje trzy parametry typu float, które definiują przesunięcie odpowiednio względem osi bieżącego układu. Poeksperymentuj z parametrami tej funkcji w programie.

 

Rysowanie punktów

Rysowanie w OpenGL odbywa się pomiędzy wywołaniami dwóch funkcji:

 

glBegin(typ)
rozpoczęcie rysowania. Parametr typ określa rodzaj rysowanego obiektu.
glEnd()
zakończenie rysowania

 

Obiekty zawsze rysowane są w bieżącym układzie współrzędnych – jeśli ten układ przesuniesz za pomocą glTranslatef() (lub innych, które poznamy później), to rysowany obiekt będzie przesunięty. Przesunięcie układu współrzędnych nie wpływa na wcześniej narysowane punkty.

Do rysowania punktów wykorzystujemy funkcję ogólną glVertex##(). Literkami ## oznaczyliśmy rodzaj funkcji. OpenGL umożliwia definiowanie współrzędnych na wiele różnych sposobów. Aby nie powodować na początku zbytniego zamieszania, umówmy się, że będziemy korzystali z dwóch rodzajów funkcji:

 

glVertex2f(x,y)
określa współrzędne punktu na płaszczyźnie OXY, Dla takiego wierzchołka współrzędna z  jest równa 0.
glVertex3f(x,y,z)
określa współrzędne punktu w przestrzeni OXYZ.

 

Napiszemy teraz prosty kod, który narysuje na płaszczyźnie OXY cztery punkty w narożnikach kwadratu o boku 2 ze środkiem w środku układu współrzędnych. Kod wprowadzamy do funkcji obsługi zdarzenia on|Timer dla tmrAnimarte w podanym miejscu, o ile nie zostanie wyraźnie napisane inaczej.

 

  // Tutaj umieszczamy program dla OpenGL

  glTranslatef(0.0f,0.0f,-3.0f);  // przesuwamy układ w tył o 3 jednostki

  glBegin(GL_POINTS);             // rysujemy punkty w narożnikach kwadratu

    glVertex2f( 1.0f, 1.0f);      // v1
    glVertex2f( 1.0f,-1.0f);      // v2
    glVertex2f(-1.0f,-1.0f);      // v3
    glVertex2f(-1.0f, 1.0f);      // v4

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Punkty są malutkie. Możemy je powiększyć za pomocą funkcji:

 

void glPointSize(GLfloat size);

 

Parametr size określa wielkość punktu. Nie wszystkie wielkości są dostępne w OpenGL. Dla środowiska Windows można przyjąć zakres od 0.5 do 10.0 z krokiem co 0.125. Wywołanie funkcji wstaw przed glBegin() dla wszystkich rysowanych punktów.

 

  // Tutaj umieszczamy program dla OpenGL

  glTranslatef(0.0f,0.0f,-6.0f);  // przesuwamy układ w tył o 3 jednostki

  glPointSize(10.0f);             // duże punkty

  glBegin(GL_POINTS);             // rysujemy punkty w narożnikach kwadratu

    glVertex2f( 1.0f, 1.0f);      // v1 o wielkości 10
    glVertex2f( 1.0f,-1.0f);      // v2 o wielkości 10
    glVertex2f(-1.0f,-1.0f);      // v3 o wielkości 10
    glVertex2f(-1.0f, 1.0f);      // v4 o wielkości 10

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Punkty są zawsze rysowane w takiej wielkości, jaka została ustawiona przed wywołaniem glBegin(). Jeśli zatem chcesz otrzymać punkty o różnych wielkościach, to musisz je rysować w osobnych blokach glBegin()...glEnd().

 

  // Tutaj umieszczamy program dla OpenGL

  glTranslatef(0.0f,0.0f,-3.0f);  // przesuwamy układ w tył o 3 jednostki

  glPointSize(10.0f);
  glBegin(GL_POINTS);
    glVertex2f( 1.0f, 1.0f);     // v1 o wielkości 10
  glEnd();

  glPointSize(5.0f);
  glBegin(GL_POINTS);
    glVertex2f( 1.0f,-1.0f);     // v2 o wielkości 5
  glEnd();

  glPointSize(2.0f);
  glBegin(GL_POINTS);
    glVertex2f(-1.0f,-1.0f);     // v3 o wielkości 2
  glEnd();

  glPointSize(1.0f);
  glBegin(GL_POINTS);
    glVertex2f(-1.0f, 1.0f);     // v4 o wielkości 1
  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Kolejny przykład będzie rysował punkty algorytmicznie, tak aby wypełniały one sześcian. Dodamy również ruch przez określenie przesunięcia w osi OX i OY układu współrzędnych przed rozpoczęciem rysowania punktów. Przesunięcie to będzie się zmieniało przy każdym przerwaniu onTimer, co w efekcie da wrażenie ruchu figury. Wprowadź kod dla OpenGL:

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  GLfloat x,y,z;

  glTranslatef(Tx,Ty,-6.0f); // przesuwamy układ współrzędnych

  // modyfikujemy przesunięcie dla następnego kadru animacji

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  glPointSize(2.0f); // wszystkie punkty o rozmiarze 2

  glBegin(GL_POINTS);

    for(x = -1; x <= 1; x += 0.25)
      for(y = -1; y <= 1; y += 0.25)
        for(z = -1; z <= 1; z += 0.25) glVertex3f(x,y,z);

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Do określania kolorów punktów służy funkcja

 

glColor##()

 

Podobnie jak przy glVertex##(), tutaj również mamy wiele możliwości określenia kolorów. Na początek określmy tę funkcję jako:

 

glColor3f(r,g,b); Kolor jest definiowany przez trzy składowe, które mogą przyjmować wartość od 0 do 1:

r  – składowa czerwona (ang. Red)
g  – składowa zielona (ang. Green)
b  – składowa niebieska (ang. Blue)

 

Funkcję glColor##() możesz umieszczać zarówno przed glBegin() jak i przed każdym wierzchołkiem. Poniższy kod rysuje nasz sześcian z różnymi kolorami punktów, które zależą od położenia punktu w przestrzeni. Poeksperymentuj z nim trochę.

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  GLfloat x,y,z;

  glTranslatef(Tx,Ty,-6.0f); // przesuwamy układ w tył o 6 jednostek

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  glPointSize(3.0f); // wszystkie punkty o rozmiarze 3

  glBegin(GL_POINTS);

    for(x = -1; x <= 1; x += 0.25)
      for(y = -1; y <= 1; y += 0.25)
        for(z = -1; z <= 1; z += 0.25)
        {
          glColor3f((x+1)/2,(y+1)/2,1-(y+1)/2);
          glVertex3f(x,y,z);
        }

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Rysowanie linii

Gdy już umiemy rysować punkty w OpenGL, to kolejnym logicznym krokiem jest nauka rysowania linii. Linie rysujemy w bloku glBegin(GL_LINES) ... glEnd(), podając kolejno po dwa punkty, które określają końce linii:

 

glBegin(GL_LINES);
  glVertex##(...);  // punkt startowy linii
  glVertex##(...);  // punkt końcowy linii
  ...
glEnd();

 

Poniższy przykład rysuje trzy pary skrzyżowanych linii. Wprowadź go w miejsce kodu rysującego punkty w sześcianie. Zwróć uwagę na sposób rysowania każdej z par – rysujemy jedną, przesuwamy układ współrzędnych i rysujemy drugą.

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  int i;

  glTranslatef(Tx,Ty,-6.0f); // przesuwamy układ w tył o 6 jednostek

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  for(i = 0; i < 3; i++)
  {
    glTranslatef(0.0f,0.0f,-1.0f); // przesuwamy układ o 1 w tył

    glBegin(GL_LINES);

      glVertex2f(-1.0f,-1.0f); // początek pierwszej linii
      glVertex2f( 1.0f, 1.0f); // koniec pierwszej linii

      glVertex2f(-1.0f, 1.0f); // początek drugiej linii
      glVertex2f( 1.0f,-1.0f); // koniec drugiej linii

    glEnd();
  }

  // Koniec kodu dla OpenGL

 

obrazek

 

Kolory linii ustalamy za pomocą funkcji glColor##(), którą musimy umieścić przed wywołaniem funkcji glVertex##() definiującymi punkty krańcowe linii. W powyższym programie wprowadź zmianę w pętli:

 

    for(i = 0; i < 3; i++)
    {
      glTranslatef(0.0f,0.0f,-1.0f); // przesuwamy układ o 1 w tył

      glBegin(GL_LINES);

        glColor3f(1.0f,0.0f,0.0f);  // pierwsza linia czerwona
        glVertex2f(-1.0f,-1.0f);    // początek pierwszej linii
        glVertex2f( 1.0f, 1.0f);    // koniec pierwszej linii

        glColor3f(0.0f,1.0f,0.0f);  // druga linia zielona
        glVertex2f(-1.0f, 1.0f);    // początek drugiej linii
        glVertex2f( 1.0f,-1.0f);    // koniec drugiej linii

      glEnd();

    }

 

obrazek

 

Jeśli umieścisz wywołanie glColor##() przed wywołaniem glVertex##() dla obu końców linii, to OpenGL będzie rysowało linię z płynnym przejściem kolorów:

W programie zmień zawartość pętli:

 

    for(i = 0; i < 3; i++)
    {
      glTranslatef(0.0f,0.0f,-1.0f); // przesuwamy układ o 1 w tył

      glBegin(GL_LINES);

        glColor3f(1.0f,0.0f,0.0f);  // początek czerwony
        glVertex2f(-1.0f,-1.0f);    // początek pierwszej linii
        glColor3f(1.0f,1.0f,0.0f);  // koniec żółty
        glVertex2f( 1.0f, 1.0f);    // koniec pierwszej linii

        glColor3f(0.0f,1.0f,0.0f);  // początek zielony
        glVertex2f(-1.0f, 1.0f);    // początek drugiej linii
        glColor3f(0.0f,0.0f,1.0f);  // koniec niebieski
        glVertex2f( 1.0f,-1.0f);    // koniec drugiej linii

      glEnd();

    }

 

obrazek

 

Szerokość linii ustalamy za pomocą funkcji

 

glLineWidth(GLfloat width);

 

Szerokość może być w granicach od 0.5 do 10.0 z krokiem co 0.125 (czyli zupełnie tak samo jak dla wielkości punktów). Funkcję glLineWidth() umieszczamy przed blokiem glBegin() ... glEnd(), w którym jest rysowana linia. Jeśli chcesz mieć linie o różnych szerokościach, to musisz użyć dla każdej z nich osobnego bloku glBegin() ... glEnd(), a przed każdym z tych bloków określić szerokość linii (tak samo postępowaliśmy w przypadku punktów).

W programie zmień zawartość pętli:

 

    for(i = 0; i < 3; i++)
    {
      glTranslatef(0.0f,0.0f,-1.0f); // przesuwamy układ o 1 w tył

      glLineWidth(6.0f-2*i);         // szerokość linii
      
      glBegin(GL_LINES);

        glColor3f(1.0f,0.0f,0.0f);  // początek czerwony
        glVertex2f(-1.0f,-1.0f);    // początek pierwszej linii
        glColor3f(1.0f,1.0f,0.0f);  // koniec żółty
        glVertex2f( 1.0f, 1.0f);    // koniec pierwszej linii

        glColor3f(0.0f,1.0f,0.0f);  // początek zielony
        glVertex2f(-1.0f, 1.0f);    // początek drugiej linii
        glColor3f(0.0f,0.0f,1.0f);  // koniec niebieski
        glVertex2f( 1.0f,-1.0f);    // koniec drugiej linii

      glEnd();

    }

 

obrazek

 

Rysowanie łamanych

Łamana jest figurą, która składa się z ciągu linii połączonych końcami. Łamana może być otwarta (koniec ostatniej linii nie łączy się z początkiem pierwszej linii) lub zamknieta (koniec ostatniej linii łączy się z początkiem pierwszej linii).  Punkty łamanej definiujemy za pomocą funkcji glVertex##() w bloku glBegin(GL_LINE_STRIP) ... glEnd() dla łamanych otwartych i glBegin(GL_LINE_LOOP) ... glEnd() dla łamanej zamkniętej. Pierwszy program spiralę. Ponieważ korzysta on z funkcji trygonometrycznych, dodaj na początku programu dyrektywę:

 

#include <math.h>

 

Następnie wprowadź kod do funkcji tmrAnimateTimer():

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  int i;

  glTranslatef(Tx,Ty,-10.0f); // przesuwamy układ w tył o 10 jednostek

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  glBegin(GL_LINE_STRIP); // rysowanie łamanych otwartych

    // generujemy ciąg punktów spirali

    for(i = 0; i < 200; i++) glVertex3f(cos(i/10.0),sin(i/10.0),i/50.0);

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Następny program rysuje pięć okręgów jeden nad drugim w odległości co 0.5 jednostki na osi z.

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  int i,j;

  glTranslatef(Tx,Ty,-10.0f); // przesuwamy układ w tył o 10 jednostek

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  for(j = 0; j < 5; j++)
  {
    glTranslatef(0.0f,0.0f,0.5f); // przesuwamy się do przodu o 0.5 jednostki

    glBegin(GL_LINE_LOOP); // rysowanie łamanych zamkniętych

      for(i = 0; i < 40; i++) glVertex2f(cos(6.28 * (i/40.0)),sin(6.28 * (i/40.0)));

    glEnd();
  }

  // Koniec kodu dla OpenGL

 

obrazek

 

Rysowanie trójkątów

W OpenGL trójkąt jest podstawowym obiektem, który tworzy ściany. Trójkąt określamy wewnątrz bloku glBegin(GL_TRANGLES) ... glEnd(). Definicja trójkąta składa się z trzech wierzchołków. Wprowadź poniższy kod do funkcji tmrAnimateTimer().

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  glTranslatef(Tx,Ty,-6.0f); // przesuwamy układ w tył o 6 jednostek

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  glBegin(GL_TRIANGLES); // rysowanie trójkątów

    glVertex2f( 0.0f, 0.0f); // pierwszy trójkąt
    glVertex2f(-1.0f, 0.0f);
    glVertex2f(-0.5f, 1.0f);

    glVertex2f( 0.0f, 0.0f); // drugi trójkąt
    glVertex2f( 1.0f, 0.0f);
    glVertex2f( 0.5f,-1.0f);

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Zmień zawartość bloku, aby dodać kolory:

 

    glBegin(GL_TRIANGLES);      // rysujemy trójkąty

      glColor3f(1.0f,1.0f,0.0f);// żółty
      glVertex2f( 0.0f, 0.0f);  // pierwszy trójkąt
      glVertex2f(-1.0f, 0.0f);
      glVertex2f(-0.5f, 1.0f);

      glColor3f(0.0f,1.0f,1.0f);// cyjan
      glVertex2f( 0.0f, 0.0f);  // drugi trójkąt
      glVertex2f( 1.0f, 0.0f);
      glVertex2f( 0.5f,-1.0f);

    glEnd();

 

obrazek

 

Jeśli kolor określisz przed każdym wierzchołkiem, to wnętrze trójkąta będzie kolorowane gradientowo.

 

    glBegin(GL_TRIANGLES);      // rysujemy trójkąty

      glColor3f(1.0f,1.0f,0.0f);// żółty
      glVertex2f( 0.0f, 0.0f);  // pierwszy trójkąt
      glColor3f(0.0f,1.0f,0.0f);// zielony
      glVertex2f(-1.0f, 0.0f);
      glColor3f(0.0f,0.0f,1.0f);// niebieski
      glVertex2f(-0.5f, 1.0f);

      glColor3f(1.0f,1.0f,0.0f);// żółty
      glVertex2f( 0.0f, 0.0f);  // drugi trójkąt
      glColor3f(1.0f,0.0f,0.0f);// czerwony
      glVertex2f( 1.0f, 0.0f);
      glColor3f(0.0f,1.0f,1.0f);// cyjan
      glVertex2f( 0.5f,-1.0f);

    glEnd();

 

obrazek

 

Trójkąty w programie znajdowały się na płaszczyźnie OXY. Nic jednakże nie stoi na przeszkodzie, aby były rysowane w przestrzeni 3D. Poniższy program rysuje trzy trójkąty, które tworzą ściany boczne czworościanu. Zwróć uwagę na kolejność określania wierzchołków trójkąta. Umówmy się na przyszłość, że będzie ona taka, aby patrząc na przód ściany, punkty były określane zgodnie z ruchem wskazówek zegara – jeśli będzie na odwrót, to OpenGL nie narysuje ściany. Zostało to określone w obsłudze zdarzenia onResize przy pomocy funkcji:

 

  glEnable(GL_CULL_FACE);   // włącza opcję eliminacji ścian
  glFrontFace(GL_CW);       // ściany o wierzchołkach ułożonych zgodnie z ruchem wskazówek
                            // zegara będą traktowane jako zwrócone przodem
  glCullFace(GL_BACK);      // pomija rysowanie ścian odwróconych tyłem

 

Opcja ta przyspiesza rysowanie sceny, ponieważ pomija ściany, które są odwrócone tyłem, a zatem niewidoczne dla obiektów pełnych (jeśli obiekty mają w ścianach dziury, przez które widać ich wnętrze, to pomijanie ścian należy wyłączyć). Więcej o tej opcji podamy na następnych zajęciach.

 

  glBegin(GL_TRIANGLES);       // rysujemy trójkąty

    glColor3f(1.0f,1.0f,0.0f); // trójkąt żółty
    glVertex3f( 0.0f, 1.0f, 0.0f);
    glVertex3f( 1.0f,-1.0f, 0.0f);
    glVertex3f( 0.0f, 0.0f, 1.0f);

    glColor3f(0.0f,1.0f,0.0f); // trójkąt zielony
    glVertex3f( 1.0f,-1.0f, 0.0f);
    glVertex3f(-1.0f,-1.0f, 0.0f);
    glVertex3f( 0.0f, 0.0f, 1.0f);

    glColor3f(1.0f,0.0f,1.0f); // trójkąt purpurowy
    glVertex3f(-1.0f,-1.0f, 0.0f);
    glVertex3f( 0.0f, 1.0f, 0.0f);
    glVertex3f( 0.0f, 0.0f, 1.0f);

  glEnd();

 

obrazek

 

Rysowanie czworoboków

Czworobok definiowany jest cztery kolejne wierzchołki w bloku glBegin(GL_QUADS) ... glEnd(). Poniższy kod rysuje dwa czworoboki połączone ze sobą pod katem 90 stopni. Również zwróć uwagę na kolejność definiowania wierzchołków, która jest zgodna z ruchem wskazówek zegara.

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat Tx = -2;    // przesunięcie w osi OX
  static GLfloat Ty = -2;    // przesunięcie w osi OY
  static GLfloat dTx = 0.02; // przyrost w osi OX
  static GLfloat dTy = 0.03; // przyrost w osi OY

  glTranslatef(Tx,Ty,-6.0f); // przesuwamy układ w tył o 6 jednostek

  Tx += dTx; if(Tx < -2 || Tx > 2) dTx = -dTx;
  Ty += dTy; if(Ty < -2 || Ty > 2) dTy = -dTy;

  glBegin(GL_QUADS); // rysujemy czworoboki

    glColor3f(1.0f,1.0f,0.0f); // czworobok czerwony
    glVertex3f( 1.0f, 0.0f, 0.0f);
    glVertex3f(-1.0f, 0.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f( 1.0f, 1.0f, 1.0f);

    glColor3f(0.0f,1.0f,0.0f); // czworobok zielony
    glVertex3f( 1.0f, 0.0f, 0.0f);
    glVertex3f( 1.0f,-1.0f, 1.0f);
    glVertex3f(-1.0f,-1.0f, 1.0f);
    glVertex3f(-1.0f, 0.0f, 0.0f);

  glEnd();

  // Koniec kodu dla OpenGL

 

obrazek

 

Podsumowanie

Omówione na zajęciach elementy nazywają się prymitywami OpenGL. Prymitywy rysujemy wewnątrz bloku glBegin() ... glEnd. Do funkcji glBegin() przekazujemy jako parametr rodzaj prymitywu, który ma zostać narysowany. Poznane prymitywy, to:

 

Nazwa stałej Opis
GL_POINTS
Rysuje punkty
GL_LINES
Rysuje linie
GL_LINE_STRIP
Rysuje łamaną otwartą
GL_LINE_LOOP
Rysuje łamaną zamkniętą
GL_TRIANGLES
Rysuje trójboczną ścianę
GL_QUADS
Rysuje czworoboczną ścianę

 


 

Wewnątrz bloku glBegin() ... glEnd() używamy funkcji glVertex##() do określenia współrzędnych punktów, które tworzą prymitywy.

 

glVertex2f(x,y)
określa współrzędne punktu na płaszczyźnie OXY, Dla takiego wierzchołka współrzędna z  jest równa 0.
glVertex3f(x,y,z)
określa współrzędne punktu w przestrzeni OXYZ.

 


 

Wielkość punktów określa funkcja:

 

void glPointSize(GLfloat size);

 

Parametr size  może przyjmować wartości od 0.5 do 10.0 z krokiem co 0.125. Funkcję tę należy wywołać przed blokiem glBegin() ... glEnd().

 


 

Grubość linii określa funkcja:

 

glLineWidth(GLfloat width);

 

Parametr width  może przyjmować wartości od 0.5 do 10.0 z krokiem co 0.125. Funkcję tę należy wywołać przed blokiem glBegin() ... glEnd().

 


 

Do określania kolorów służy funkcja

 

glColor3f(r,g,b);
Kolor jest definiowany przez trzy składowe, które mogą przyjmować wartość od 0 do 1:

r  – składowa czerwona (ang. Red)
g  – składowa zielona (ang. Green)
b  – składowa niebieska (ang. Blue)

 

Funkcję można wywoływać tuż przed wywołaniem glVertex##(). Jeśli figura posiada kilka wierzchołków, to kolor będzie rysowany gradientowo przy przejściu z jednego wierzchołka do drugiego.

 


 

Układ współrzędnych przesuwamy za pomocą funkcji:

 

glTranslatef(GLfloat dx, GLfloat dy, GLfloat dz);

 

Po przesunięciu układu współrzędnych punkty będą rysowana w nowym miejscu przestrzeni. Przesunięcie nie wpływa na punkty już narysowane w innym bloku glBegin() ... glEnd().

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

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