Koło informatyczne 2013

Do ćwiczeń utwórz nowy projekt OpenGL.

 

Typy danych w OpenGL

Biblioteka OpenGL jest z założenia biblioteką przenośną na różne platformy sprzętowe. Aby zapewnić tę przenośność, posiada własne typy danych. Oczywiście nie musisz ich stosować w swoim programie, jeśli nie planujesz uruchamiania kodu na innym sprzęcie.

 

Typ danych w OpenGL Wewnętrzna reprezentacja Odpowiednik w C++ Sufiks literowy funkcji
GLbyte liczba całkowita 8-bitow signed char b
GLshort liczba całkowita 16-bitów short int s
GLint, GLsizei liczba całkowita 32-bity long int I
GLfloat, GLclampf liczba zmiennoprzecinkowa 32-bity float f
GLdouble, GLclampd liczba zmiennoprzecinkowa 64-bity double d
GLubyte, GLboolean liczba całkowita bez znaku 8-bitów unsigned char ub
GLushort liczba całkowita bez znaku 16-bitów unsigned short int us
GLuint, GLenum, GLbitfield liczba całkowita bez znaku 32-bitów    unsigned long int ui

 

clamp pochodzi od color amplitude (amplituda koloru) i jest stosowane w funkcjach definiujących składowe koloru.

GLboolean stosowane jest do wartości logicznych.

Sufiks literowy funkcji służy do oznaczania typów argumentów. Poznaliśmy dotychczas kilka funkcji, które stosują tę cechę:

 

glVertex2f() - funkcja posiada 2 argumenty typu GLfloat

glColor3f() - funkcja posiada 3 argumenty typu GLfloat

...

 

Nazwy wszystkich typów rozpoczynają się od GL, aby wskazać ich przynależność do OpenGL.

 

Podstawowe składniki OpenGL

Funkcje API zawarte w OpenGL są podzielone na trzy różne biblioteki.

 

Biblioteka Nazwa pliku Plik nagłówkowy Prefiks  funkcji
Pomocnicza glaux.lib glaux.h aux
OpenGL opengl32.dll gl.h gl
Użytkowa glu32.dll glu.h glu

 

Prefiks funkcji określa jej przynależność do danej biblioteki. Dotychczas używaliśmy funkcji z biblioteki OpenGL. Wszystkie posiadały przedrostek gl:

 

glBegin()
glEnd()
glVertex3f()
glTranslatef()
...

 

Transformacje przestrzeni 3D

Tworzenie sceny w OpenGL podzielone jest na dwa etapy. Najpierw ustala się tzw. położenie obserwatora. Proces ten możesz sobie wyobrazić jako ustawienie kamery w określonym miejscu sceny i skierowanie jej w wybranym kierunku. W naszym programie dokonujemy tego w obsłudze zdarzenia onResize. Przyjrzyjmy się funkcji obsługującej to zdarzenie:

 

//---------------------------------------------------------------------------
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
}
//---------------------------------------------------------------------------

 

Na początku mamy wywołanie funkcji glViewport(). Jej parametry definiują prostokąt widoku na obszarze okna, w którym będzie rysowana scena (możesz mieć w oknie kilka widoków sceny, np. z różnych kierunków).

 

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

 

Prostokąt ten rozpoczyna się w punkcie o współrzędnych x,y i posiada długość width oraz wysokość height. W naszym przypadku prostokąt widoku obejmuje cały obszar okna aplikacji.

Druga funkcja glMatrixMode(GL_PROJECTION) wybiera do działań macierz projekcji, czyli macierz obserwatora. Macierz ta określa położenie obserwatora oraz sposób patrzenia na scenę.

glLoadIdentity() umieszcza w macierzy projekcji macierz jednostkową. Jak pamiętamy z poprzednich zajęć, macierz jednostkowa jest przekształceniem tożsamościowym, tzn. nie zmienia widoku sceny (to tak jak w mnożeniu 1, które nie zmienia wyniku mnożenia). Macierz jednostkową ładujemy zawsze wtedy, gdy chcemy rozpocząć transformacje od początku.

Kolejna funkcja gluPerspective() należy do biblioteki użytkowej. Ustala ona sposób prezentacji sceny w prostokącie widoku, który ustawiła funkcja glViewport(). W tym przypadku zostaje wybrany widok perspektywiczny. Cechuje się on tym, iż przedmioty znajdujące się bliżej obserwatora stają się większa, a przedmioty znajdujące się dalej stają się mniejsze. Funkcja mnoży macierz projekcji przez macierz perspektywy. Ponieważ wcześniej ustawiliśmy w macierzy projekcji macierz jednostkową (którą możemy potraktować jak w mnożeniu jedynkę), to w efekcie w macierzy projekcji pojawi się teraz macierz perspektywy.

Kolejne wywołanie funkcji glMatrixMode(GL_MODELVIEW) wybiera do działań macierz widoku obiektu, co przenosi nas do drugiego etapu, który będzie realizowany w funkcji obsługi zdarzenia onTimer. Macierz widoku obiektu jest używana do przekształceń układu współrzędnych sceny. Macierz projekcji jest jakby punktem startowym tych przekształceń.

Aby zilustrować te rozważania, stwórzmy prosty program. Najpierw wpisz w funkcji obsługi zdarzenia onResize następujący 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);
  glTranslatef(0.0f,0.0f,-6.0f);   // przesuwamy początkowy układ w tył o 6 jednostek
  glMatrixMode(GL_MODELVIEW);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  glEnable(GL_DEPTH_TEST);  // włącza bufor głębokości
  glDepthFunc(GL_LEQUAL);
  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
}
//---------------------------------------------------------------------------

 

Zwróć uwagę, że transformacji dokonaliśmy tutaj w macierzy projekcji. Zatem początkowy widok naszego układu współrzędnych będzie taki, iż układ ten jest cofnięty o 6 jednostek. Transformacji tych nie musimy już wykonywać w układzie obiektu. Kolejny kod wklej do funkcji obsługi zdarzenia onTimer. Kod rysuje obracający się sześcian.

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat alpha = 0;        // kąt obrotu

  glRotatef(alpha,1.0f,1.0f,1.0f); // wykonujemy tylko obrót układu współrzędnych

  alpha += 1; if(alpha > 360) alpha = 0;

  glBegin(GL_QUAD_STRIP);          // cztery ściany boczne
    glColor3f(1.0f,0.0f,0.0f);
    glVertex3f(-1.0, 1.0, 1.0);
    glVertex3f(-1.0, 1.0,-1.0);

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

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

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

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

  glBegin(GL_QUADS);              // przód i tył
    glColor3f(1.0f,0.0f,0.0f); glVertex3f(-1.0, 1.0, 1.0);
    glColor3f(1.0f,1.0f,0.0f); glVertex3f( 1.0, 1.0, 1.0);
    glColor3f(0.0f,1.0f,0.0f); glVertex3f( 1.0,-1.0, 1.0);
    glColor3f(0.0f,0.0f,1.0f); glVertex3f(-1.0,-1.0, 1.0);

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

  // Koniec kodu dla OpenGL

 

obrazek

 

W funkcji FormResize() umieść komentarz przed wywołaniem gluPerspective() i dodaj pod spodem wpis:

 

glOrtho(-2.0f,2.0f,-2.0f,2.0f,0.0f,8.0f);

 

Funkcja glOrto() umieści w macierzy projekcji macierz rzutu prostokątnego. W rzucie prostokątnym wymiary przedmiotów nie zmieniają się w zależności od odległości od obserwatora. Parametry są następujące:

 

void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal);

left, right  – określają współrzędne pionowych płaszczyzn obcinania. Płaszczyzny te znajdują się odpowiednio przy lewej i prawej krawędzi prostokąta widoku.

bottom, top  – współrzędne dolnej i górnej poziomej płaszczyzny obcinania. Płaszczyzny te znajdą się na dolnej i górnej krawędzi prostokąta widoku.

nearVal  – określa odległość od obserwatora, poniżej której ściany nie będą rysowane.

farVal  – określa odległość od obserwatora, powyżej której ściany nie będą rysowane.

 

Gdy uruchomisz teraz nasz program, perspektywa zniknie.

 

obrazek

 

Pomimo tych wad, rzut prostokątny jest czasem przydatny. Sprawdź co się dzieje, gdy zmieniasz wymiary swojego okna, np. rozciągnij je w poziomie lub w pionie.

Z projektu usuń funkcję glOrto() i przywróć gluPerspective().

 

Ukrywanie ścian odwróconych tyłem do obserwatora

Na scenie część ścian jest odwrócona tyłem do obserwatora. Ściany takie zwykle nie są widoczne, dlatego możemy pominąć ich rysowanie, włączając odpowiednią opcję w OpenGL. Aby ukrywanie ścian odwróconych tyłem nie powodowało błędów w wyglądzie obiektu, musimy wiedzieć, w jaki sposób OpenGL rozpoznaje, że ściana jest odwrócona tyłem do obserwatora. Cała magia zawiera się w kolejności wierzchołków, które definiujemy w bloku glBegin(); ... glEnd(). Otóż, możemy się umówić, że ściana będzie zwrócona do nas przodem, jeśli kolejne wierzchołki będą definiowane zgodnie z ruchem wskazówek zegara (lub przeciwnie do niego). Spójrz na poniższy rysunek:

 

obrazek

 

Gdy tak zdefiniowana ściana odwraca się tyłem do obserwatora, kolejność wierzchołków zmienia się na przeciwną do ruchu wskazówek zegara. Gdy włączymy ukrywanie ścian odwróconych tyłem, OpenGL przed narysowaniem ściany szybko sprawdzi sobie kolejność jej wierzchołków. Jeśli będzie przeciwna do ruchu wskazówek zegara, to ściana nie zostanie narysowana. Przy bardzo skomplikowanej scenie opcja ta przyspiesza tworzenie rysunku.

Opcja ukrywania ścian w naszym programie jest włączana w funkcji obsługi zdarzenia onResize. Aby to zadziałało prawidłowo, należy wywołać trzy funkcje:

 

glEnable(GL_CULL_FACE)  –  jest to ogólna funkcja uaktywniająca określoną parametrem opcję w OpenGL. GL_CULL_FACE włącza ukrywanie ścian. Opcję tę wyłączasz za pomocą funkcji glDisable(GL_CULL_FACE). Standardowo opcja jest wyłączona.
glFrontFace(GL_CW)  –  ta funkcja informuje OpenGL, iż ściany zwrócone przodem do obserwatora stosują kolejność wierzchołków zgodną z ruchem wskazówek zegara. Parametr GL_CW określa tę kolejność. Możliwa jest również kolejność odwrotna do ruchu wskazówek zegara GL_CCW. Standardowo jest ustawiony kierunek odwrotny do ruchu wskazówek zegara, czyli GL_CCW.
glCullFace(GL_BACK)  –  ta funkcja określa, które ściany mają być pomijane. Parametr GL_BACK powoduje pomijanie ścian, które są odwrócone tyłem. Możliwe są jeszcze dwie kolejne opcje:

GL_FRONT - nie będą rysowane ściany odwrócone przodem do obserwatora
GL_FRONT_AND_BACK - nie będą rysowane żadne ściany

 

Pobaw się chwilę programem obracającym sześcian, stosując różne opcje w wymienionych powyżej funkcjach.

 

obrazek

 

Bufor głębokości

Na scenie 3D ściany bliższe zakrywają ściany dalsze. Wszystko zależy od kolejności rysowania ściany. Jeśli OpenGL narysuje najpierw ścianę bliższą, a następnie dalszą, to ta druga pokryje ścianę bliższą, co spowoduje błędy w odbiorze grafiki przez człowieka. Aby szybko zobaczyć ten efekt, zmień w naszym programie funkcję obsługi zdarzenia onResize:

 

//---------------------------------------------------------------------------
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);
  glTranslatef(0.0f,0.0f,-6.0f);   // przesuwamy początkowy układ w tył o 6 jednostek
  glMatrixMode(GL_MODELVIEW);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//  glEnable(GL_DEPTH_TEST);  // włącza bufor głębokości
  glDepthFunc(GL_LEQUAL);
//  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
}
//---------------------------------------------------------------------------

 

obrazek

 

Do eliminacji tego niepożądanego efektu OpenGL wykorzystuje tzw. bufor głębokości. Polega on na tym, iż z każdym pikselem ekranu skojarzona jest liczba przechowywana w buforze głębokości. Liczba ta określa "odległość" tego piksela w przestrzeni 3D od obserwatora. Na początku tworzenia sceny bufor głębokości jest ustawiany tak, iż piksele ekranu są w największej możliwej odległości od obserwatora. Rysując ścianę, OpenGL wylicza odległość każdego jej piksela. Jeśli w buforze dla tego piksela jest wartość mniejsza, to piksel jest już zajęty przez ścianę leżącą bliżej obserwatora, zatem nowy piksel nie będzie rysowany. W przeciwnym razie piksel jest rysowany, a w buforze zostaje umieszczona jego odległość od obserwatora. Dzięki temu ściany leżące dalej nie będą przesłaniały ścian już narysowanych bliżej.

Aby OpenGL stosowało bufor głębokości, należy włączyć tę opcję za pomocą wywołania:

 

glEnable(GL_DEPTH_TEST);

 

Następnie należy określić warunki rysowania piksela za pomocą wywołania funkcji:

 

glDepthFunc(warunek);

 

Warunków jest wiele:

 

GL_NEVER  –  piksele nigdy nie są rysowane, ściany się nie pojawią
GL_LESS  –  piksel jest rysowany, jeśli jego odległość jest mniejsza od odległości w buforze. Jest to standardowa wartość.
GL_EQUAL  –  piksel jest rysowany, jeśli jego odległość jest równa odległości w buforze.
GL_LEQUAL  –  piksel jest rysowany, jeśli jego odległość jest mniejsza lub równa odległości w buforze.
GL_GREATER  –  piksel jest rysowany, jeśli jego odległość jest większa od odległości w buforze.
GL_NOTEQUAL  –  piksel jest rysowany, jeśli jego odległość różni się od odległości w buforze.
GL_GEQUAL  –  piksel jest rysowany, jeśli jego odległość jest większa lub równa odległości w buforze.
GL_ALWAYS  –  piksel jest zawsze rysowany.

 

Na początku funkcji obsługi zdarzenia onTimer mamy wywołanie funkcji:

 

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 

Zeruje ona bufor koloru (powierzchnia graficzna robi się czarna, gdyż taki kolor tła wybrano funkcją glClearColor(0.0f, 0.0f, 0.0f, 0.0f) w onResize – spróbuj zmienić kolor tła na inny) oraz ustawia bufor głębokości.

Kolejny kod rysuje trzy sześciany wirujące wokół wspólnej osi. Dodatkowo w obróconym układzie sześciany wirują kolejno wokół poszczególnych osi układu współrzędnych. Poeksperymentuj na nim z włączaniem/wyłączaniem bufora głębokości. Próbuj zbliżać lub oddalać od siebie sześciany, modyfikując przesunięcia za pomocą glTranslatef(). Również spróbuj wyłączyć funkcję glClear() przez umieszczenie przed nią komentarza.

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat alpha = 0;       // kąt obrotu

  glRotatef(alpha,1.0f,1.0f,1.0f);// wykonujemy tylko obrót układu współrzędnych
  glTranslatef(-3.0f,0.0f,0.0f);  // na pozycję pierwszego sześcianu

  alpha += 1; if(alpha > 360) alpha = 0;

  for(int i = 0; i < 3; i++)
  {
    glRotatef(alpha,(i==0),(i==1),(i==2)); // obracamy układ współrzędnych

    glBegin(GL_QUAD_STRIP);
      glColor3f(1.0f,0.0f,0.0f);
      glVertex3f(-1.0, 1.0, 1.0);
      glVertex3f(-1.0, 1.0,-1.0);

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

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

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

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

    glBegin(GL_QUADS);
      glColor3f(1.0f,0.0f,0.0f); glVertex3f(-1.0, 1.0, 1.0);
      glColor3f(1.0f,1.0f,0.0f); glVertex3f( 1.0, 1.0, 1.0);
      glColor3f(0.0f,1.0f,0.0f); glVertex3f( 1.0,-1.0, 1.0);
      glColor3f(0.0f,0.0f,1.0f); glVertex3f(-1.0,-1.0, 1.0);

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

    glRotatef(-alpha,(i==0),(i==1),(i==2));
    glTranslatef(3.0f,0.0f,0.0f);
  }

  // Koniec kodu dla OpenGL

 

obrazek   obrazek
Animacja z buforem głębokości   Animacja bez bufora głębokości i bez czyszczenia okna.

 

Proste obiekty 3D

Gdy umiemy już operować przestrzenią w OpenGL, nauczymy się rysować kilka prostych obiektów 3D. Tworzenie obiektów w przestrzeni 3D wymaga dobrej znajomości geometrii oraz wyobraźni przestrzennej.

 

Czworościan foremny

Czworościan foremny (tetraedr) jest figurą posiadającą cztery identyczne ściany będące trójkątami równobocznymi. Na początek zajmijmy się prostym przypadkiem, gdy nasz tetraedr jest wpisany w sześcian:

 

obrazek

Przypadek ten jest prosty, ponieważ wierzchołki sześcianu można bardzo łatwo wyznaczyć. Jeśli umówimy się, że środek układu współrzędnych znajduje się w środku sześcianu, a sześcian ma boki o długości 1, to współrzędne x, y i z kolejnych wierzchołków są następujące:

 

v0 = ( 0,5 -0,5  0,5)
v1 = (-0,5  0,5  0,5)
v2 = ( 0,5  0,5 -0,5)
v3 = (-0,5 -0,5 -0,5)

 

Gdy znamy już punkty wierzchołkowe, możemy określić każdą ścianę jako ciąg trzech wierzchołków podanych w kolejności zgodnej z ruchem wskazówek zegara (kolejność ta jest istotna, jeśli w programie włączyliśmy ukrywanie ścian odwróconych tyłem do obserwatora):

 

obrazek

S0 = (v0 v1 v2)
S1 = (v1 v3 v2)
S2 = (v3 v0 v2)
S3 = (v0 v3 v1)

 

Taką figurę możemy określić za pomocą trzech tablic:

 

V - tablica definiująca wierzchołki. Każdy element jest strukturą trzech współrzędnych x, y i z.

S - tablica definiująca ściany trójkątne. Każda ściana zawiera 3 liczby całkowite będące numerami kolejnych wierzchołków (zgodnie z ruchem wskazówek zegara).

C - tablica definiująca kolory ścian. Każdy element zawiera 3 składowe R, G i B koloru ściany.

 

Po tych ustaleniach możemy przystąpić do napisania odpowiedniego kodu. Na początku programu umieść następujący kod:

 

//---------------------------------------------------------------------------
void Tetraedr(GLfloat v[][3], int s[][3], GLfloat c[][3])
{
  glBegin(GL_TRIANGLES);           // ściany trójkątne
    for(int i = 0; i < 4; i ++)    // kolejne ściany
    {
      glColor3fv(c[i]);            // kolor i-tej ściany
      for(int j = 0; j < 3; j++)
        glVertex3fv(v[ s[i][j] ]);
    }
  glEnd();
}
//---------------------------------------------------------------------------

 

W funkcji Tetraedr() zastosowaliśmy nowe funkcje do definicji koloru oraz wierzchołka:

 

glColor3fv(adres);
glVertex3fv(adres);

 

Parametrem tych funkcji jest adres obszaru pamięci, w którym przechowywane są kolejno 3 liczby typu GLfloat będące wartościami r, g, b lub współrzędnymi x, y i z wierzchołka. Użycie tych funkcji upraszcza nasz kod. Jeśli mamy tablicę dwuwymiarową, np. T[4][3], to pierwszy indeks określa rząd w tej tablicy. Element T[i] jest adresem i-tego wiersza. Wykorzystujemy to w funkcjach, przekazując jako parametr i-ty wiersz tablicy kolorów lub wiersz tablicy wierzchołków o numerze przechowywanym w s[i][j], a to jest numer j-tego wierzchołka, który tworzy ścianę.

Zmień kod w obsłudze Timera:

 

  // Tutaj umieszczamy program dla OpenGL

  // Definiowanie figury

  // Wierzchołki
  static GLfloat V[][3] = {{ 0.5f,-0.5f, 0.5f},  // v0
                           {-0.5f, 0.5f, 0.5f},  // v1
                           { 0.5f, 0.5f,-0.5f},  // v2
                           {-0.5f,-0.5f,-0.5f}}; // v3

  // Ściany
  static int S[][3] = {{0,1,2},  // S0
                       {1,3,2},  // S1
                       {3,0,2},  // S2
                       {0,3,1}}; // S3

  // Kolory ścian
  static GLfloat C[][3] = {{1.0f,0.0f,0.0f},  // kolor S0, czerwony
                           {0.0f,1.0f,0.0f},  // kolor S1, zielony
                           {0.0f,0.0f,1.0f},  // kolor S2, niebieski
                           {1.0f,1.0f,0.0f}}; // kolor S3, żółty

  static GLfloat alphax = 0; // kat obrotu
  static GLfloat alphay = 0; // kat obrotu
  static GLfloat alphaz = 0; // kat obrotu

  glTranslatef(0.0f,0.0f,-3.0f);
  glRotatef(alphax,1.0f,0.0f,0.0f);
  glRotatef(alphay,0.0f,1.0f,0.0f);
  glRotatef(alphaz,0.0f,0.0f,1.0f);

  Tetraedr(V,S,C);

  // kąt dla następnej klatki animacji

  alphax += 0.4; if(alphax > 360) alphax = 0;
  alphay += 1.0; if(alphay > 360) alphay = 0;
  alphaz += 0.6; if(alphaz > 360) alphaz = 0;

  // Koniec kodu dla OpenGL

 

obrazek

 

Funkcję rysującą obiekt możemy w prosty sposób uogólnić na dowolną ilość ścian trójkątnych. Również kolory mogą dotyczyć wierzchołków, a nie całych ścian. Zamień w programie funkcję Tetraedr() na następującą:

 

//---------------------------------------------------------------------------
void Figure3D(int n, bool cmode, GLfloat v[][3], int s[][3], GLfloat c[][3])
{
  glBegin(GL_TRIANGLES);
    for(int i = 0; i < n; i ++)      // tworzymy n ścian trójkątnych
      if(cmode)                      // tryb koloru
      {                              // true - kolor ścian
       glColor3fv(c[i]);             // kolor wg numeru ściany
       for(int j = 0; j < 3; j++)
         glVertex3fv(v[ s[i][j] ]); 
      }
      else                           // false - kolor wierzchołków
        for(int j = 0; j < 3; j++)
        {
           glColor3fv(c[ s[i][j] ]); // kolor wg numeru wierzchołka
           glVertex3fv(v[ s[i][j] ]);
        }
  glEnd();
}
//---------------------------------------------------------------------------

 

Pierwszy parametr n określa liczbę ścian figury. Drugi parametr określa tryb koloru:

true – tablica c definiuje kolory ścian
false – tablica c definiuje kolory wierzchołków

 

Kod animacji tworzy teraz dwa czworościany, jeden ze stałymi kolorami ścian, a drugi z przejściami tonalnymi. Zastanów się, jak to działa.

 

  // Tutaj umieszczamy program dla OpenGL

  // Wierzchołki
  static GLfloat V[][3] = {{ 0.5f,-0.5f, 0.5f},  // v0
                           {-0.5f, 0.5f, 0.5f},  // v1
                           { 0.5f, 0.5f,-0.5f},  // v2
                           {-0.5f,-0.5f,-0.5f}}; // v3

  // Ściany
  static int S[][3] = {{0,1,2},  // S0
                       {1,3,2},  // S1
                       {3,0,2},  // S2
                       {0,3,1}}; // S3

  // Kolory ścian
  static GLfloat C[][3] = {{1.0f,0.0f,0.0f},  // kolor S0, czerwony
                           {0.0f,1.0f,0.0f},  // kolor S1, zielony
                           {0.0f,0.0f,1.0f},  // kolor S2, niebieski
                           {1.0f,1.0f,0.0f}}; // kolor S3, żółty

  static GLfloat alpha = 0;        // kat obrotu

  glTranslatef(0.0f,0.0f,-5.0f);   // cofamy układ 0 5 jednostek 

  glRotatef(alpha,0.0f,1.0f,0.5f); // obracamy układ w płaszczyźnie nieco pochyłej

  glTranslatef(-1.0f,0.0f,0.0f);   // przemieszczamy układ do pierwszej figury

  for(int i = 0; i < 2; i++)
  {
    Figure3D(4,i,V,S,C);           // rysujemy figurę

    glTranslatef(2.0f,0.0f,0.0f);  // przemieszczamy układ do drugiej figury
  }

  // kąt dla następnej klatki animacji

  alpha += 1; if(alpha > 360) alpha = 0;

  // Koniec kodu dla OpenGL

 

obrazek

 

Piramidy w Egipcie

Teraz zrealizujemy mały projekt. Należy stworzyć prostą animację dwóch piramid, które są ostrosłupami o podstawie kwadratu.

 

obrazek

 

Scenę będą tworzyły dwie piramidy o boku podstawy 1 oraz kwadrat pustyni o boku 3, czyli w sumie trzy obiekty. Kwadrat pustyni ma być poziomy w stosunku do obserwatora, zatem będzie leżał w płaszczyźnie OXZ.

 

obrazek

 

Powyższy rysunek przedstawia ułożenie piramid względem siebie na kwadracie pustyni.

Najpierw musimy określić dane dla pojedynczej piramidy.

 

obrazek

Wierzchołki:

 

v0 = ( 0,0  0,7  0,0)
v1 = ( 0,5  0,0 -0,5)
v2 = ( 0,5  0,0  0,5)
v3 = (-0,5  0,0  0,5)
v4 = (-0,5  0,0 -0,5)

 

Ściany:

 

S0 = (v0 v1 v2)
S1 = (v0 v4 v1)
S2 = (v0 v3 v4)
S3 = (v0 v2 v3)

W tych samych tablicach zdefiniujemy kwadrat pustyni. Będzie on zbudowany z dwóch trójkątów:

 

obrazek

 

 

Wierzchołki:

 

v5 = ( 1,5  0,0 -1,5)
v6 = ( 1,5  0,0  1,5)
v7 = (-1,5  0,0  1,5)
v8 = (-1,5  0,0 -1,5)

 

Ściany:

 

S4 = (v5 v6 v7)
S5 = (v7 v8 v5)

 

Kod animacji:

 

  // Tutaj umieszczamy program dla OpenGL

  // Definiowanie figury

  static GLfloat V[][3] = {{ 0.0f, 0.7f, 0.0f},   // v0 - wierzchołki piramidy
                           { 0.5f, 0.0f,-0.5f},   // v1
                           { 0.5f, 0.0f, 0.5f},   // v2
                           {-0.5f, 0.0f, 0.5f},   // v3
                           {-0.5f, 0.0f,-0.5f},   // v4
                           { 1.5f, 0.0f,-1.5f},   // v5 - wierzchołki kwadratu pustyni
                           { 1.5f, 0.0f, 1.5f},   // v6
                           {-1.5f, 0.0f, 1.5f},   // v7
                           {-1.5f, 0.0f,-1.5f}};  // v8

  static int S[][3] = {{0,1,2},                   // S0 - ściany piramidy
                       {0,4,1},                   // S1
                       {0,3,4},                   // S2
                       {0,2,3},                   // S3
                       {5,6,7},                   // S4 - ściany pustyni
                       {7,8,5}};                  // S5

  static GLfloat C[][3] = {{1.0f,1.0f,0.0f},      // kolor S0, żółty
                           {0.8f,0.8f,0.0f},      // kolor S1, żółty
                           {0.6f,0.6f,0.0f},      // kolor S2, żółty
                           {0.8f,0.8f,0.0f},      // kolor S3, żółty
                           {0.8f,0.6f,0.2f},      // kolor pustyni
                           {0.8f,0.6f,0.2f}};     // kolor pustyni

  static GLfloat alpha = 0;        // kąt obrotu

  glTranslatef(0.0f,-0.3f,-4.0f);  // cofamy i obniżamy układ

  glRotatef(15.0f,1.0f,1.0f,0.0f); // pochylamy go nieco w kierunku obserwatora

  glRotatef(alpha,0.0f,1.0f,0.0f); // obrót o zmienny kąt

  Figure3D(2,true,V,&S[4],&C[4]);  // rysujemy pustynię jako dwa trójkąty

  glTranslatef(-0.7f,0.0f,-0.7f);  // pozycja pierwszej piramidy

  Figure3D(4,true,V,S,C);          // pierwsza piramida

  glTranslatef(1.4f,0.0f,1.4f);    // pozycja drugiej piramidy

  Figure3D(4,true,V,S,C);          // druga piramida

  // kąt dla następnej klatki animacji

  alpha += 1; if(alpha > 360) alpha = 0;

  // Koniec kodu dla OpenGL

 

obrazek

 

Osiedle mieszkaniowe

Na podobnej zasadzie jak piramidy zrealizujemy kolejny prosty projekt małego osiedla mieszkaniowego. Najpierw musimy zaprojektować domek.

 

obrazek

 

Domek będzie definiowało 10 wierzchołków:

 

obrazek

 

Wierzchołki:

 

v0 = ( 1,0  0,0 -0,5)
v1 = ( 1,0  0,0  0,5)
v2 = ( 1,0  1,0  0,5)
v3 = ( 1,0  1,5  0,0)
v4 = ( 1,0  1,0 -0,5)
v5 = (-1,0  0,0 -0,5)
v6 = (-1,0  0,0  0,5)
v7 = (-1,0  1,0  0,5)
v8 = (-1,0  1,5  0,0)
v9 = (-1,0  1,0 -0,5)

 

Teraz na podstawie wierzchołków określimy poszczególne ściany, których jest 14. Podział ścian domku na trójkąty jest tutaj dowolny. Wybraliśmy jeden z możliwych.

 

obrazek   obrazek

Ściany:

 

S0  = (v0 v1 v4)
S1  = (v1 v2 v4)
S2  = (v2 v3 v4)
S3  = (v1 v6 v7)
S4  = (v1 v7 v2)
S5  = (v2 v7 v3)
S6  = (v7 v8 v3)
S7  = (v6 v5 v7)
S8  = (v5 v9 v7)
S9  = (v7 v9 v8)
S10 = (v0 v4 v5)
S11 = (v5 v4 v9)
S12 = (v8 v9 v4)
S13 = (v3 v8 v4)

 

Mając definicje wierzchołków i ścian, możemy stworzyć prostą aplikację testową:

 

  // Tutaj umieszczamy program dla OpenGL

  // Definiowanie figury

  static GLfloat V[][3] = {{ 1.0f, 0.0f,-0.5f},     // v0
                           { 1.0f, 0.0f, 0.5f},     // v1
                           { 1.0f, 1.0f, 0.5f},     // v2
                           { 1.0f, 1.5f, 0.0f},     // v3
                           { 1.0f, 1.0f,-0.5f},     // v4
                           {-1.0f, 0.0f,-0.5f},     // v5
                           {-1.0f, 0.0f, 0.5f},     // v6
                           {-1.0f, 1.0f, 0.5f},     // v7
                           {-1.0f, 1.5f, 0.0f},     // v8
                           {-1.0f, 1.0f,-0.5f}};    // v9
                           
  static int S[][3] = {{0,1,4},    // S0
                       {1,2,4},    // S1
                       {2,3,4},    // S2
                       {1,6,7},    // S3
                       {1,7,2},    // S4
                       {2,7,3},    // S5
                       {7,8,3},    // S6
                       {6,5,7},    // S7
                       {5,9,7},    // S8
                       {7,9,8},    // S9
                       {0,4,5},    // S10
                       {5,4,9},    // S11
                       {8,9,4},    // S12
                       {3,8,4}};   // S13

  static GLfloat C[][3] = {{0.6f,0.6f,0.6f},      // Kolor ściany S0
                           {0.6f,0.6f,0.6f},      // Kolor ściany S1
                           {0.6f,0.6f,0.6f},      // Kolor ściany S2
                           {0.7f,0.7f,0.7f},      // Kolor ściany S3
                           {0.7f,0.7f,0.7f},      // Kolor ściany S4
                           {0.6f,0.0f,0.0f},      // Kolor ściany S5
                           {0.6f,0.0f,0.0f},      // Kolor ściany S6
                           {0.6f,0.6f,0.6f},      // Kolor ściany S7
                           {0.6f,0.6f,0.6f},      // Kolor ściany S8
                           {0.6f,0.6f,0.6f},      // Kolor ściany S9
                           {0.7f,0.7f,0.7f},      // Kolor ściany S10
                           {0.7f,0.7f,0.7f},      // Kolor ściany S11
                           {0.5f,0.0f,0.0f},      // Kolor ściany S12
                           {0.5f,0.0f,0.0f}};     // Kolor ściany S13

  static GLfloat alpha = 0;        // kąt obrotu

  glTranslatef(0.0f,-0.3f,-4.0f);  // cofamy i obniżamy układ

  glRotatef(15.0f,1.0f,1.0f,0.0f); // pochylamy go nieco w kierunku obserwatora

  glRotatef(alpha,0.0f,1.0f,0.0f); // obrót o zmienny kąt

  Figure3D(14,true,V,S,C);         // rysujemy domek
  
  // kąt dla następnej klatki animacji

  alpha += 1; if(alpha > 360) alpha = 0;

  // Koniec kodu dla OpenGL

 

obrazek

 

Resztę pracy wykonaj sam. Napisz program, który narysuje kwadrat murawy w kolorze ciemnozielonym, a następnie rozmieść na nim domki. Stwórz kilka wariantów takiego rozmieszczenia. Wykorzystaj powyższy kod do animacji widoku.

 

obrazek obrazek
obrazek obrazek

 

Nowe funkcje

glColor3fv(adres) – definiuje kolor na podstawie 3 elementowej tablicy
glVertex3fv(adres) – definiuje wierzchołek na podstawie 3 elementowej tablicy

 


   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