OpenGL Game Tutorial
Visual C++ 6.0



Kamera, procházení scénou


Příklad a zdroják ke stažení (306k)


Výpočet FPS
Jednoduché načítání objektů ze souboru *.txt
Načítání textur ze souboru *.bmp
Kamera, procházení scénou
Rotace kamery pomocí myši

glGenLists()
wglUseFontBitmaps()
glPushAttrib()
glPopAttrib()
glListBase()
glCallLists()
glDeleteLists()
glColor3f()
glEnable(GL_BLEND)
glDisable(GL_BLEND)
glBlendFunc(GL_ONE, GL_ONE)
glTranslatef()
glRasterPos2f()
glPrint()
gluLookAt()
glNewList()
glEndList()
glEnable(GL_TEXTURE_2D)
glBindTexture()
glBegin(GL_TRIANGLE_FAN)
glEnd()
glTexCoord2f()
glPushMatrix()
glPopMatrix()
glTranslatef()
glRotatef()




Výpočet FPS

Funkce CalculateFrameRate() počítá FPS.

void CalculateFrameRate(void)                          // Vypočítej FPS
{
    static int frames_per_second = 0;                  // Hodnota  FPS
    static float last_time = 0.0f;                     // Hodnota času posledního snímku
    static char str_frame_rate[50] = {0};              // String hodnota FPS

    float current_time = GetTickCount() * 0.001f;      // Zjisti aktuální čas v milisekundách a převeď na sekundy

    ++frames_per_second;                               // Přičti snímek

    if( current_time - last_time > 1.0f )              // Jestliže rozdíl hodnot aktuálního času a času posledního snímku je větší než 1 (1 sekunda)
    {
          last_time = current_time;                    // Tak předej hodnotu aktuálního času, času posledního snímku
          fps = frames_per_second;                     // Předej hodnotu frames_per_second globální proměnné fps
          frames_per_second = 0;                       // Vynuluj hodnotu FPS
    }
}


Výsledek pošle globální proměnné fps, kterou vykreslí na obrazovku funkce DrawFonts(), když hodnotu fps vložíme do funkce jako konstantu. Tato funkce může mimochodem vykreslit na obrazovku jakýkoli text.

void DrawFonts(const char *fmt, ...)                        // Vykresli text
{
     char text[256];                                        // String o 256 znacích
     va_list ap;                                            // Ukazatel na List argumentů, které chceme vložit do řetězce

     if (fmt == NULL)                                       // Jestliže fmt neobsahuje žádný text
          return;                                           // Tak nic nedělej a odejdi z funkce

     va_start(ap, fmt);                                     // Zkus vytvořit kopii fmt v Listu ap
         vsprintf(text, fmt, ap);                           // Konvertuj znaky na aktuální čísla znaků
     va_end(ap);                                            // Vymaž ukazatel na List argumentů

     glPushAttrib(GL_LIST_BIT);                             // Začátek Listu Bitů
     glListBase(base - 32);                                 // Nastav znaky od znaku 32 výš, jako základní Display List
     glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);     // Vykresli Display List
     glPopAttrib();                                         // Konec Listu Bitů
}

Tato funkce vykresluje znaky, které jsou uloženy v hodnotách base Display Listu. Tyto hodnoty musíme nastavit ve funkci BuildFont()

void BuildFont(void)                                             // Vytvoř font
{
     HFONT     font;                                             // ID - identifikátor Windows Fonu

     base = glGenLists(96);                                      // Najdi 96 prázdných Listů

     font = CreateFont(     -12,                                 // Velikost
                              0,                                 // Šířka
                              0,                                 // Úhel vykreslení
                              0,                                 // Orientace úhlu
                              FW_BOLD,                           // Tučný
                              FALSE,                             // Italic
                              FALSE,                             // Nepodtržený
                              FALSE,                             // Nepřeškrtlý
                              ANSI_CHARSET,                      // Kódování
                              OUT_TT_PRECIS,                     // TrueType
                              CLIP_DEFAULT_PRECIS,               // Základní ořezávání
                              ANTIALIASED_QUALITY,               // Výstupní kvalita
                              FF_DONTCARE|DEFAULT_PITCH,         // Styl
                              "Courier New");                    // Jméno

    SelectObject(hDC, font);                                     // Vyber objekt
    wglUseFontBitmaps(hDC, 32, 96, base);                        // Vytvoř bitmaty znakové sady od 32 do 96 a ulož do base
    DeleteObject(font);                                          // Vymaž font
}

Funkce BuildFont() je volána jen jednou funkcí InitScene().

int InitScene(void)              // Načti scénu
{
     if (!Texture1.LoadGLTextures("Textures/picture01.bmp"))     // Zkus načíst texturu
     {
          return FALSE;          // Jestliže se to nepodaří, tak opusť fukci
     }
     if (!Texture1.LoadGLTextures("Textures/picture02.bmp"))     // Zkus načíst texturu
     {
          return FALSE;          // Jestliže se to nepodaří, tak opusť fukci
     }

     LoadAllObjects();           // Načti objekty ze souboru 
     InitCube();                 // Načti krychli
     BuildFont();                // Vytvoř bitmap fonty 

     return TRUE;                // Vrať TRUE pokud vše proběhlo OK 
}

Funkce CalculateFrameRate() a DrawFonts() je volána funkcí DrawFps(). Tato funkce vykreslí text žlutý a průhledný, aby byl lépe vidět.

void DrawFps(void)
{
     CalculateFrameRate();                // Vypočítej FPS
     glColor3f(1,1,0);                    // Barvu textu nastav na žlutou
     glEnable(GL_BLEND);                  // Zapni průhlednost
     glBlendFunc(GL_ONE, GL_ONE);         // Vše, co je černé je průhledné
     glLoadIdentity();                    // Resetuj prostorovou matici
     glTranslatef(0.0f, 0.0f, -0.1f);     // Přesuň text o 0,1 před kameru
     glRasterPos2f(-0.054f, 0.039f);      // Pozici textu nastav do levého horního rohu render okna
     DrawFonts("fps %1i",fps);            // Napiš text do render okna
     glDisable(GL_BLEND);                 // Vypni průhlednost
     glColor3f(1,1,1);                    // Barvu nastav zpět na bílou
}

Funkce DrawFps() je volána v nekonečné smyčce fukcí DrawScene().

int DrawScene(void)        // Vykresli scénu
{
       Camera1.Look();     // Pohybuj a rotuj kamerou
       DrawCube();         // Vykresli krychli
       DrawFps();          // Vykresli FPS

     return TRUE;          // Vrať TRUE pokud vše proběhlo OK
}





Jednoduché načítání objektů ze souboru *.txt

Soubor objektu cube.txt má tuto podobu
Vertices: 8
-1.0    -1.0     1    v0
-1.0     1.0     1    v1
 1.0    -1.0     1    v2
 1.0     1.0     1    v3
 1.0    -1.0    -1    v4
 1.0     1.0    -1    v5
-1.0    -1.0    -1    v6
-1.0     1.0    -1    v7
Hodnota za Vertices: nám zadává, kolik vertexů obsahuje objekt. Protože jde o krychli, tak máme 8 vrcholů Další hodnoty jsou X, Y, Z pozice jednotlivých vrcholů. v0 - v7 je jen informativní text, který se nenačítá. To samé platí o textu Vertices:.

cube.jpg(6 kb)

Objekt cube2.txt má stejnou strukturu s jinými hodnotami.

Pro načítání objektů a jejich vykreslování si vytvoříme tyto datové typy

//----------------------------------------------------------------------------------------------------//
//-------- Definuj struktury
//----------------------------------------------------------------------------------------------------//

typedef struct               // Struktura 3D bodů
{
     GLfloat     x, y, z;    // Bod x, y, z
} TPoint3d;

//----------------------------------------------------------------------------------------------------//

typedef struct                // Struktura objektů
{
        int  verts;           // Počet vrcholů
        TPoint3d  *points;    // Vrchol je složen z bodu x, y, z
} TGLObject;

//----------------------------------------------------------------------------------------------------//

Vytvoříme si objekty typu TGLObject
TGLObject  cube1, cube2;     //Objekt cube1, cube2
A budeme volat funkci pro načítání objektů ze souborů cube.txt, cube2.txt

void LoadAllObjects(void)                              // Načti objekty
{
    cube1 = LoadObject("Objects/cube.txt", &cube1);    // Načti hodnoty uložené v souboru cube.txt a předej je objektu cube1
    cube2 = LoadObject("Objects/cube2.txt", &cube2);   // Načti hodnoty uložené v souboru cube2.txt a předej je objektu cube2
}

Funkce pro načítání vypadá takto

TGLObject LoadObject(char *name, TGLObject *k)            // Načti objekt se souboru (name)
{
    int     ver;                                          // Pomocná proměnná pro počet vrcholů objektu
    GLfloat  rx,ry,rz;                                    // Pomocná proměnná pro X, Y, Z pozici vrcholu objektu
    FILE  *filein;                                        // Definice souboru, ktery budeme číst
    char  oneline[255];                                   // Definice řádku souboru s maximálně 255 znaky

    if ((filein = fopen(name, "rt"))== NULL)              // Zkus otevřít soubor pro čtení v textovem módu "rt"
    {
                                                          // Jestli se to nezdaří, vyhoď chybovou hlášku
          MessageBox(NULL,"Cannot open input file","Error",MB_OK | MB_ICONSTOP);
     }
    else                                                  // Jinak pokračuj v práci s načteným souborem
    {
      ReadLine(filein,oneline);                           // Volej funkci pro načtení řádky ze souboru
      sscanf(oneline, "Vertices: %d\n", &ver);            // Přečti z první řádky počet vrcholů a výsledek předej proměnné ver
      k->points = new TPoint3d[ver];                      // Dynamicky alokuj paměť pro počet vrcholů objektu
      k->verts = ver;                                     // Načtenou hodnotu počtu vrcholů předej vrcholům objektu

      for (int i=0;i<ver;i++)                             // Cykluj podle počtu vrcholů
        {
           ReadLine(filein,oneline);                       // Čti řádek po řádku
           sscanf(oneline, "%f %f %f", &rx, &ry, &rz);    // Najdi v řádce tři čísla typu float a předej je hodnotám rx, ry, rz
           k->points[i].x = rx;                           // Předej bodu X (aktuálniho vrcholu) hodnotu rx
           k->points[i].y = ry;                           // Předej bodu Y (aktuálního vrcholu) hodnotu ry
           k->points[i].z = rz;                           // Předej bodu Z (aktuálního vrcholu) hodnotu rz
        }
      fclose(filein);                                     // Zavři soubor
    }
    return *k;                                            // Vrať funkci hodnoty objektu
}

Tato funkce vrátí hodnoty vrcholů k, pomocí kterých vykreslujeme objekty (krychle)

void InitCube(void)            // Vykresli krychli
{
glNewList(1,GL_COMPILE);

...
...
...

// Čelní stěna krychle
glBegin(GL_TRIANGLE_FAN);      // Začni vykreslovat čtverec
  glTexCoord2f(0.0f, 1.0f);  glVertex3d( cube1.points[0].x, cube1.points[0].y, cube1.points[0].z);     // Horní levý bod
  glTexCoord2f(0.0f, 0.0f);  glVertex3d( cube1.points[1].x, cube1.points[1].y, cube1.points[1].z);     // Dolní levý bod
  glTexCoord2f(1.0f, 0.0f);  glVertex3d( cube1.points[2].x, cube1.points[2].y, cube1.points[2].z);     // Dolní pravý bod
  glTexCoord2f(1.0f, 1.0f);  glVertex3d( cube1.points[3].x, cube1.points[3].y, cube1.points[3].z);     // Horní pravý bod
glEnd();                      // Ukonči vykreslování čtverce

...
...
...

}

Funkce LoadObject() používá pro načtení řádky ze souboru funkci ReadLine()

void ReadLine(FILE *f,char *s)                      // Čti řádek ze souboru f a ukládej do řetězce s
{
    do                                              // Cykluj
      {
        fgets(s, 255, f);                           // Ćti znak po znaku maximálně však 255
      } while ((s[0] == '/') || (s[0] == '\n'));    // Dokud se nedostaneš na konec řádky

    return;                                         // Vrať funkci hodnotu načteného řádku
}

Funkce pro načítání objektů se samozřejmě provádějí jen jednou, při startu aplikace. Při startu aplikace také musíme zapnout DEPTH buffer glEnable(GL_DEPTH_TEST);. Pokud bychom nechali vypnutý DEPTH buffer, objekty by se vykreslovali v tom pořadí, v jakém jsou volány, takže by se stalo, že by se zadní stěna krychle vykreslila před přední.

int InitScene(void)              // Načti scénu
{
     if (!Texture1.LoadGLTextures("Textures/picture01.bmp"))     // Zkus načíst texturu
     {
          return FALSE;          // Jestliže se to nepodaří, tak opusť fukci
     }
     if (!Texture1.LoadGLTextures("Textures/picture02.bmp"))     // Zkus načíst texturu
     {
          return FALSE;          // Jestliže se to nepodaří, tak opusť fukci
     }

     LoadAllObjects();           // Načti objekty ze souboru 
     InitCube();                 // Načti krychli
     BuildFont();                // Vytvoř bitmap fonty 

     return TRUE;                // Vrať TRUE pokud vše proběhlo OK 
}





Načítání textur ze souboru *.bmp

Pro načítání textur ze souboru *.bmp jsem vytvořil třídu KTexture

class KTexture                              // Třída textur
{
public:
     KTexture() {}                          // Konstruktor textury

     // Proměnné
     int Number;                            // Počet textur
     unsigned int TextureList[256];         // Definice proměnné pro 2d texturu

     // Funkce
     int LoadGLTextures(char *Filename);    // Načítání textury *.bmp
};

Funkce pro načítání textury LoadGLTextures() vypadá takto:

int KTexture::LoadGLTextures(char *Filename)                    // Načti obrázek *.bmp a převeď ho na texturu
{
     int Status=FALSE;                                          // Kontrolu stavu načítaní nastav na FALSE



     AUX_RGBImageRec *TextureImage[1];                           // Alokuj paměť pro obraz textury

     memset(TextureImage,0,sizeof(void *)*1);                    // Nastav ukazatel na NULL

     // Jestliže pude načíst obrázek
     if (TextureImage[0]=LoadBMP(Filename))
     {
          Status=TRUE;                                           // Kontrolu stavu načítání nastav na TRUE

          glGenTextures(1, &TextureList[Number]);                // Vytvoř texturový List

          // Začátek generování textury
          glBindTexture(GL_TEXTURE_2D, TextureList[Number]);     // Nastav texturový List s následujícími parametry

          // Parametry 2D textury
          glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

          // Lineárně rozlož (roztáhni) texturu do celého objektu, pokud je textura menší nez plocha objektu
          glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

          // Lineárně rozlož (zmenši) texturu, pokud je textura větší než plocha objektu
          glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
          // Konec generování textury
     }

     if (TextureImage[0])                         // Jestliže existuje obraz textury 0
     {
          if (TextureImage[0]->data)              // Jestliže obraz textury obsahuje nějaká data
          {
               free(TextureImage[0]->data);       // Tak data vymaž z paměti
          }

          free(TextureImage[0]);                  // Vymaž celou strukturu obrazu textury
     }

     Number++;                                    // Přičti počet textur

     return Status;                               // Vrať funkci stav načítání
}

Textura se vytváří z obrázku *.bmp, který načítá funkce LoadBMP()

AUX_RGBImageRec *LoadBMP(char *Filename)        // Načti obrázek *.bmp
{
     FILE *File=NULL;                           // Handle souboru

     if (!Filename)                             // Zkus jestli existuje jméno souboru
     {
          return NULL;                          // Jinak ukonči funkci
     }

     File=fopen(Filename,"r");                  // Otevři soubor pro čtení

     if (File)                                  // Jestliže soubor existuje
     {
          fclose(File);                         // Uzavři soubor
          return auxDIBImageLoad(Filename);     // Načti bitmapu z otevřeného souboru
     }

     return NULL;                               // Jestliže se náčítání souboru nezdaří, tak vrať NULL
}

Načítání textur volá funkce InitScene() jen jednou při startu aplikace.

int InitScene(void)              // Načti scénu
{
     if (!Texture1.LoadGLTextures("Textures/picture01.bmp"))     // Zkus načíst texturu
     {
          return FALSE;          // Jestliže se to nepodaří, tak opusť fukci
     }
     if (!Texture1.LoadGLTextures("Textures/picture02.bmp"))     // Zkus načíst texturu
     {
          return FALSE;          // Jestliže se to nepodaří, tak opusť fukci
     }

     LoadAllObjects();           // Načti objekty ze souboru 
     InitCube();                 // Načti krychli
     BuildFont();                // Vytvoř bitmap fonty 

     return TRUE;                // Vrať TRUE pokud vše proběhlo OK 
}





Kamera, procházení scénou

Pro procházení scénou jsem vytvořil třídu KCamera

class KCamera             // Třída kamer
{
public:
     KCamera();           // Konstruktor kamery

     // Proměnné
     KVector3 Position;   // Pozice kamery
     KVector3 Angle;      // Úhel rotace kamery
     KVector3 View;       // Pozice bodu, na který kouká kamera
     KVector3 Up;         // Pozice Up Vectoru
     KVector3 Strafe;     // Vektor pro posunutí do stran
     float speed;         // Rychlost kamery

     // Funkce
     void RotateView(float angle, float X, float Y, float Z);     // Rotace bodu, na který kouká kamera
     void SetViewByMouse();              // Rotace kamery pomocí myši (First person view)
     void StrafeCamera(float speed);     // Posun kamery doprava, nebo doleva podle hodnoty speed (+ nebo -)
     void MoveCamera(float speed);       // Pohyb kamery vpřed a vzad podle hodnoty speed (+ nebo -)
     void Look();                        // Volá funkci gluLookAt() pro nastavení kamery, podle předem vypočtených hodnot
     void GoForward();                   // Pohyb dopředu
     void GoBackward();                  // Pohyb dozadu
     void GoUp();                        // Pohyb nahoru
     void GoDown();                      // Pohyb dolu
     void StrafeLeft();                  // Pohyb doleva
     void StrafeRight();                 // Pohyb doprava
     void TurnLeft();                    // Rotace doleva
     void TurnRight();                   // Rotace doprava
     void TurnUp();                      // Rotace nahoru
     void TurnDown();                    // Rotace dolu
};

Třída KCamera používá třídu KVector3, která obsahuje potřebné vektorové algoritmy.

class KVector3          // Třída vektorů
{
public:
      // Základní konstruktor vektorů
     KVector3() {}

      // Konstruktor, který nám dovoluje kdykoli nastavit vektor
     KVector3(float X, float Y, float Z)
     {
          x = X; y = Y; z = Z;
     }

      // Přetížený operátor+ aby šel celý vektor sečíst
     KVector3 operator+(KVector3 vVector)
     {
          // Vrať výsledek součtu vektoru
          return KVector3(vVector.x + x, vVector.y + y, vVector.z + z);
     }

      // Přetížený operátor- aby šel celý vektor odečíst
     KVector3 operator-(KVector3 vVector)
     {
          // Vrať výsledek rozdílu vektoru
          return KVector3(x - vVector.x, y - vVector.y, z - vVector.z);
     }

      // Přetížený operátor* aby šel celý vektor násobit
     KVector3 operator*(float num)
     {
          // Vrať výsledek součinu vektoru
          return KVector3(x * num, y * num, z * num);
     }

      // Přetížený operátor/ aby šel celý vektor dělit
     KVector3 operator/(float num)
     {
          // Vrať výsledek dělení vektoru
          return KVector3(x / num, y / num, z / num);
     }

      // Hodnota 3D vektoru
     float x, y, z;
};

Tyto algoritmy vypadají takto:

//----------------------------------------------------------------------------------------------------//

// Vypočítej kolmý vektor k vektorům vVector1 a vVector2
KVector3 Cross(KVector3 vVector1, KVector3 vVector2)
{
     KVector3 vNormal;

     // Vypočítej kolmý vektor (Cross product)
     vNormal.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
     vNormal.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
     vNormal.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));

     // Vrať výsledek
     return vNormal;
}

//----------------------------------------------------------------------------------------------------//

// Vypočítej velikost vektoru
float Magnitude(KVector3 vNormal)
{
     // Podle rovnice:  magnitude = sqrt(V.x^2 + V.y^2 + V.z^2) :
     // V je vektor, sqrt je druhá odmocnina, ^2 znamená na druhou
     return (float)sqrt( (vNormal.x * vNormal.x) +
                         (vNormal.y * vNormal.y) +
                         (vNormal.z * vNormal.z) );
}

//----------------------------------------------------------------------------------------------------//

// Normalizuj vektor, aby jeho velikost nepřesahovala hodnotu 1, nebo -1
KVector3 Normalize(KVector3 vVector)
{
     // Vypočítej velikost vektoru
     float magnitude = Magnitude(vVector);

     // Vyděl vektor vypočtenou hodnotou velikosti vektoru, aby jsme dostali hodnotu mezi 1, -1
     vVector = vVector / magnitude;

     // Vrať výsledek
     return vVector;
}

//----------------------------------------------------------------------------------------------------//

Pohyb kamery provádí OpenGL funkce gluLookAt()

void KCamera::Look()
{
     // Rotuj pomocí myši
     SetViewByMouse();

     // Pohybuj a rotuj kamerou
     gluLookAt(Position.x, Position.y, Position.z,     // Pozice bodu kamery
               View.x, View.y, View.z,                 // Pozice bodu, kam kouká kamera
               Up.x, Up.y, Up.z);                      // Pozice Up vektoru Y=1;
}

Abychom mohli pohybovat a rotovat kamerou, musíme si vypočítat pozici kamery Position a pozici bodu, kam kouká kamera View. Up vektor Up měnit nebudeme.

Nejdříve si nastavíme v konstruktoru třídy KCamera základní hodnoty

KCamera::KCamera()           // Konstruktor třídy KCamera
{
     // Nastav tyto počáteční hodnoty kamery
     Position.x = 0.0;       // X pozice kamery
     Position.y = 0.0;       // Y pozice kamery
     Position.z = -10.0;     // Z pozice kamery

     Angle.x = 0;            // X úhel rotace kamery
     Angle.y = 0;            // Y úhel rotace kamery
     Angle.z = 0;            // Z úhel rotace kamery


     View.x =  0.0;          // X pozice bodu, kam kouká kamera
     View.y =  0.0;          // Y pozice bodu, kam kouká kamera
     View.z =  -1.0;         // Z pozice bodu, kam kouká kamera

     Up.x =  0.0;            // X pozice Up vektoru
     Up.y =  1.0;            // Y pozice Up vektoru
     Up.z =  0.0;            // Z pozice Up vektoru

     speed = 0.1f;           // Rychlost pohybu kamery
}

Funkce RotateView() vypočítá pozici bodu, na který kouká kamera

// Vypočítej pozici bodu, na který kouká kamera
void KCamera::RotateView(float angle, float x, float y, float z)
{
     KVector3 vNewView;

     // Vypočítej vektor pohledu kamery
     KVector3 vView = View - Position;

     // Vytvoř si hodnoty sin, cos, úhlu rotace
     float cosTheta = (float)cos(angle);
     float sinTheta = (float)sin(angle);

     // Najdi novou x pozici pro nový bod, na který kouká kamera
     vNewView.x  = (cosTheta + (1 - cosTheta) * x * x)          * vView.x;
     vNewView.x += ((1 - cosTheta) * x * y - z * sinTheta)      * vView.y;
     vNewView.x += ((1 - cosTheta) * x * z + y * sinTheta)      * vView.z;

     // Najdi novou y pozici pro nový bod, na který kouká kamera
     vNewView.y  = ((1 - cosTheta) * x * y + z * sinTheta)      * vView.x;
     vNewView.y += (cosTheta + (1 - cosTheta) * y * y)          * vView.y;
     vNewView.y += ((1 - cosTheta) * y * z - x * sinTheta)      * vView.z;

     // Najdi novou z pozici pro nový bod, na který kouká kamera
     vNewView.z  = ((1 - cosTheta) * x * z - y * sinTheta)      * vView.x;
     vNewView.z += ((1 - cosTheta) * y * z + x * sinTheta)      * vView.y;
     vNewView.z += (cosTheta + (1 - cosTheta) * z * z)          * vView.z;

     // Přičti k pozici kamery nový rotační vektor, čímž vypočítáš nový bod na který kouká kamera
     View = Position + vNewView;
}

Funkce MoveCamera() vypočítá pohyb kamery vpřed, nebo vzad, podle hodnoty speed

// Pohni kamerou vpřed, nebo vzad podle hodnoty speed (+ nebo -)
void KCamera::MoveCamera(float speed)
{
     KVector3 vVector = View - Position;  // Vypočítej vektor pohledu kamery
     vVector = Normalize(vVector);        // Normalizuj vektor

     Position.x += vVector.x * speed;     // Vypočítej novou hodnotu pozice bodu X kamery, podle vektoru pohledu a rychlosti posunutí
     Position.y += vVector.y * speed;     // Vypočítej novou hodnotu pozice bodu Y kamery, podle vektoru pohledu a rychlosti posunutí
     Position.z += vVector.z * speed;     // Vypočítej novou hodnotu pozice bodu Z kamery, podle vektoru pohledu a rychlosti posunutí
     View.x += vVector.x * speed;         // Vypočítej novou hodnotu vektoru pohledu X, podle vektoru pohledu a rychlosti posunutí
     View.y += vVector.y * speed;         // Vypočítej novou hodnotu vektoru pohledu Y, podle vektoru pohledu a rychlosti posunutí
     View.z += vVector.z * speed;         // Vypočítej novou hodnotu vektoru pohledu Z, podle vektoru pohledu a rychlosti posunutí
}

Funkce StrafeCamera() vypočítá pohyb kamery doleva, nebo doprava, podle hodnoty speed

// Posuň kameru doprava, nebo doleva podle hodnoty speed (+ nebo -)
void KCamera::StrafeCamera(float speed)
{
     // Vypočítej novou hodnotu pozice kamery, podle vektoru posunutí a rychlosti posunutí
     Position.x += Strafe.x * speed;
     Position.z += Strafe.z * speed;

     // Vypočítej novou hodnotu vektoru pohledu, podle vektoru posunutí a rychlosti posunutí
     View.x += Strafe.x * speed;
     View.z += Strafe.z * speed;
}

Další funkce volají předešlé podle toho, jaký pohyb májí vykonat.

//----------------------------------------------------------------------------------------------------//

void KCamera::GoForward()          // Pohni kamerou dopředu
{
     MoveCamera(speed);
}

//----------------------------------------------------------------------------------------------------//

void KCamera::GoBackward()          // Pohni kamerou dozadu
{
     MoveCamera(-speed);
}

//----------------------------------------------------------------------------------------------------//

void KCamera::GoUp()          // Pohni kamerou nahoru
{
     Position.y += speed;     // Pohni pozicí kamery
     View.y += speed;         // A zároveň pohni bodem, na který kamera kouká
}

//----------------------------------------------------------------------------------------------------//

void KCamera::GoDown()        // Pohni kamerou dolu
{
     Position.y -= speed;     // Pohni pozicí kamery
     View.y -= speed;         // A zároveň pohni bodem, na který kamera kouká
}

//----------------------------------------------------------------------------------------------------//

void KCamera::StrafeLeft()                             // Pohni kamerou doleva
{
     KVector3 vCross = Cross(View - Position, Up);     // Vypočítej kolmý vektor osy na vektor pohledu kamery
     Strafe = Normalize(vCross);                       // Normalizuj vektor posunutí
     StrafeCamera(-speed);                             // Pohni kamerou o hodnotu rychlosti kamery
}

//----------------------------------------------------------------------------------------------------//

void KCamera::StrafeRight()                            // Pohni kamerou doprava
{
     KVector3 vCross = Cross(View - Position, Up);     // Vypočítej kolmý vektor osy na vektor pohledu kamery
     Strafe = Normalize(vCross);                       // Normalizuj vektor posunutí
     StrafeCamera(speed);                              // Pohni kamerou o hodnotu rychlosti kamery
}

//----------------------------------------------------------------------------------------------------//

void KCamera::TurnLeft()               // Otoč kamerou doleva
{
     Angle.y = speed;                  // Nastav rychlost rotace
     RotateView(Angle.y, 0, 1, 0);     // Rotuj bodem, na který kouká kamera
}

//----------------------------------------------------------------------------------------------------//

void KCamera::TurnRight()              // Otoč kamerou doleva
{
     Angle.y = -speed;                 // Nastav rychlost rotace
     RotateView(Angle.y, 0, 1, 0);     // Rotuj bodem, na který kouká kamera
}

//----------------------------------------------------------------------------------------------------//

void KCamera::TurnUp()                                  // Otoč kamerou nahoru
{
/*
     KVector3 vAxis = Cross(View - Position, Up);       // Vypočítej kolmý vektor osy na vektor pohledu kamery
     vAxis = Normalize(vAxis);                          // Normalizuj vektor osy
     Angle.z = speed;                                   // Nastav rychlost rotace
     RotateView(Angle.z, vAxis.x, vAxis.y, vAxis.z);    // Rotuj bodem, na který kouká kamera
*/
}

//----------------------------------------------------------------------------------------------------//

void KCamera::TurnDown()                                 // Otoč kamerou dolu
{
/*
     KVector3 vAxis = Cross(View - Position, Up);        // Vypočítej kolmý vektor osy na vektor pohledu kamery
     vAxis = Normalize(vAxis);                           // Normalizuj vektor osy
     Angle.z = -speed;                                   // Nastav rychlost rotace
     RotateView(Angle.z, vAxis.x, vAxis.y, vAxis.z);     // Rotuj bodem, na který kouká kamera
*/
}

//----------------------------------------------------------------------------------------------------//


Poslední dvě funkce TurnUp() a TurnDown() jsem vypnul, protože kolidují s rotací kamery pomocí myši, viz. dále. Pokud však myš nebudete chtít použít, tyto dvě funkce se hodí.

Funkce pohybu kamery se volají tehdy, když zmáčkneme určité klávesy. Aplikace odchytává zprávy klávesnice WM_KEYDOWN (klávesu jsem stlačil) a WM_KEYUP (klávesu jsem pustil)

// Odchytávej zprávy render okna
LRESULT CALLBACK WndProc(     HWND     hWnd,                   // Handle render okna
                              UINT     uMsg,                   // Zprávy render okna
                              WPARAM   wParam,                 // Dodatečné informační zprávy okna
                              LPARAM   lParam)                 // Dodatečné informační zprávy okna
{
     switch (uMsg)                                             // Testuj zprávy Windows
     {
          case WM_ACTIVATE:                                    // Hlídej zprávu Window o aktivaci
          {
               if (!HIWORD(wParam))                            // Zkus, jestli není okno minimalizované. Pokud není minimalizovaný
               {
                    active=TRUE;                               // Tak nastav program jako aktivní
               }
               else                                            // Pokud je okno minimalizovaný
               {
                    active=FALSE;                              // Tak nastav program jako neaktivní
               }

               return 0;                                       // Vrať se a hlídej dál
          }

          case WM_SYSCOMMAND:                                  // Hlídej, jestli systém nežádá o spuštění spořiče obrazovky, nebo usporného režimu
          {
               switch (wParam)                                 // Jestliže se tak stane
               {
                    case SC_SCREENSAVE:                        // Spořič chce startovat ?
                    case SC_MONITORPOWER:                      // Usporný režim chce startovat ?
                    return 0;                                  // Hoď jim nulu ať neotravujou
               }
               break;                                          // Vrať se a hlídej dál
          }

          case WM_CLOSE:                                       // Hlídej, jestli není požadavek na ukončení aplikace. Jestli se tak stane
          {
               PostQuitMessage(0);                             // Pošli ukončovací zprávu
               return 0;                                       // Vrať se
          }

          case WM_KEYDOWN:                                     // Hlídej, jestli mačkám klávesu. Jestliže mačkám klávesu tak
          {
               keys[wParam] = TRUE;                            // Pošli kladnou zprávu TRUE a její parametr. Např klávesa s kódem 40 je keys[40] = TRUE;
               return 0;                                       // Vrať se a hlídej dál
          }

          case WM_KEYUP:                                       // Hlídej, jestli uvolńuju klávesu. Jestliže uvolńuju klávesu
          {
               keys[wParam] = FALSE;                           // Pošli zápornou zprávu FALSE a její parametr. Např klávesa s kodem 40 je keys[40] = FALSE;
               return 0;                                       // Vrať se a hlídej dál
          }

          case WM_SIZE:                                        // Hlídej, jestli neměním velikost okna. Pokud ji měním
          {
               ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));   // Volej funkci pro změnu OpenGL scény. LoWord=Width, HiWord=Height
               return 0;                                       // Vrať se a hlídej dál
          }
     }

     // Další zprávy mě nezajímaj, tak předej funkci DefWindowProc, ať si s nima Windows poradi sám
     return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

V hlavní vstupní funkci WinMain() budeme obsluhovat zprávy klávesnice.

// Hlavní vstupní funkce aplikace. Zavolej vytvářecí rutiny, obsluhuj zprávy a pohlídej interakci s mými požadavky.
int APIENTRY WinMain(HINSTANCE hInstance,       // Instance aplikace
                     HINSTANCE hPrevInstance,   // Předchozí instance
                     LPSTR     lpCmdLine,       // Parametry příkazového řádku
                     int       nCmdShow)        // Sestavení okna
{
     MSG   msg;                                 // Struktura Windows zpráv
     BOOL  done=FALSE;                          // Proměnna pro ukončeni smyčky. Pokud je done=FALSE aplikace beží, pokud se změní na done=TRUE aplikace se ukončí

     // Zeptej se mě, jestli chci používat FullScreen mód, nebo okenní mód
     if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
     {
          fullscreen=FALSE;                             // Okenní mód
     }

     // Zkus volat funkci pro vytvořeni OpenGL render okna, s následujícimi parametry. Pokud se to nepodaří tak
     if (!CreateGLWindow("Moving Camera",SCREEN_WIDTH,SCREEN_HEIGHT,COLOR_BITS,fullscreen))
     {
          return 0;                                     // Ukonči aplikaci
     }

     // Hlavni smyčka aplikace
     while(!done)                                       // Smyčka funguje pokud je done=FALSE
     {
          if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))     // Testuj, jestli nepřišla nějaká zpráva ?
          {
               if (msg.message==WM_QUIT)                // Jestliže přišla zpráva o ukončení aplikace tak
               {
                    done=TRUE;                          // Ukonči aplikaci  done=TRUE
               }
               else                                     // Jinak
               {
                    TranslateMessage(&msg);             // Přelož zprávu
                    DispatchMessage(&msg);              // Odešli zprávu a dej ji k dispozici funkci WndProc(), nebo at´ se s ní zabývá Windows
               }
          }
          else                                          // Když nejsou žádné zprávy
          {
               // Vykresli OpenGL Scénu
               if (active)                              // Je aplikace aktivní ?
               {
                    if (keys[VK_ESCAPE])                // Klepnul jsem na ESC ?
                    {
                         done=TRUE;                     // Tak ukonči aplikaci
                    }
                    else                                // Jestliže jsem neklepnul na klávesu ESC
                    {
                         DrawGLScene();                 // Volej funkci pro kreslení scény
                         SwapBuffers(hDC);              // Prohoď buffery (Double Buffering)
                                                        // Použitím double bufferu kreslíme vše na skrytou obrazovku.
                                                        // Když prehazujeme buffer, viditelna obrazovka se stane skrytou
                                                        // a skrytá se zobrazí. Touto cestou nevidíme naší scénu blikat.

                    }
               }



               if (keys[VK_F1])                         // Jestliže jsem klepnul na klávesu F1?
               {
                    keys[VK_F1]=FALSE;                  // Tak vrať klávese F1 opět hodnotu FALSE
                    KillGLWindow();                     // Ukonči OpenGL instance a zavři okno
                    fullscreen=!fullscreen;             // Přepínej mezi FullScreen a Okennim módem
                    // Vytvoř nové OpenGL render okno
                    if (!CreateGLWindow("NeHe's OpenGL Framework",SCREEN_WIDTH,SCREEN_HEIGHT,COLOR_BITS,fullscreen))
                    {
                         return 0;                      // Ukonči aplikaci, pokud se okno nepovedlo vytvořit
                    }
               }

               if ((keys[VK_UP]) || (keys['W']))        // Jestliže jsem klepnul na klávesu šipka nahoru, nebo na klávesu W
               {
                    Camera1.GoForward();                // Přepočítej pozici kamery, aby se posunula dopředu
               }

               if ((keys[VK_DOWN]) || (keys['S']))      // Jestliže jsem klepnul na klávesu šipka dolu, nebo na klávesu S
               {
                    Camera1.GoBackward();               // Přepočítej pozici kamery, aby se posunula dozadu
               }

               if ((keys[VK_PRIOR]) || (keys['E']))     // Jestliže jsem klepnul na klávesu Page Up, nebo na klávesu E
               {
                    Camera1.GoUp();                     // Přepočítej pozici kamery, aby se posouvala nahoru

               }

               if ((keys[VK_NEXT]) || (keys['C']))      // Jestliže jsem klepnul na klávesu Page Down, nebo na klávesu C 
               {
                    Camera1.GoDown();                   // Přepočítej pozici kamery, aby se posouvala dolu
               }

               if (keys['A'])                           // Jestliže jsem klepnul na klávesu A
               {
                    Camera1.StrafeLeft();               // Přepočítej pozici kamery, aby se posouvala doleva
               }

               if (keys['D'])                           // Jestliže jsem klepnul na klávesu D
               {
                    Camera1.StrafeRight();              // Přepočítej pozici kamery, aby se posouvala doprava
               }

               if (keys[VK_LEFT])                       // Jestliže jsem klepnul na klávesu šipka doleva
               {
                    Camera1.TurnLeft();                 // Přepočítej rotaci kamery, aby rotovala doleva
               }

               if (keys[VK_RIGHT])                      // Jestliže jsem klepnul na klávesu šipka doprava
               {
                    Camera1.TurnRight();                // Přepočítej rotaci kamery, aby rotovala doprava
               }

               if (keys[VK_HOME])                       // Jestliže jsem klepnul na klávesu Home
               {
                    Camera1.TurnUp();                   // Přepočítej rotaci kamery, aby rotovala nahoru
               }

               if (keys[VK_END])                        // Jestliže jsem klepnul na klávesu End
               {
                    Camera1.TurnDown();                 // Přepočítej rotaci kamery, aby rotovala dolu
               }

               if (keys['Q'])                           // Jestliže jsem klepnul na klávesu Q
               {
               //     Camera1.InclineLeft();            // Přepočítej rotaci kamery, aby se naklonila doleva
               }

               if (keys['E'])                           // Jestliže jsem klepnul na klávesu E
               {
               //     Camera1.InclineRight();           // Přepočítej rotaci kamery, aby se naklonila doprava
               }



          }
     }

     // Ukončování aplikace
     KillGLWindow();                                    // Ukonči OpenGL instance a zavři okno
     return (msg.wParam);                               // Definitivně ukonči aplikaci
}





Rotace kamery pomocí myši

Funkce pro rotaci kamery pomocí myši SetViewByMouse() je součástí třídy KCamera

// Rotuj kamerou pomocí myši (First person view)
void KCamera::SetViewByMouse()
{
     POINT mousePos;                                // Pozice kurzoru myši
     int middleX = SCREEN_WIDTH  >> 1;              // X střed obrazovky. Bitový posun doprava, způsobí dělení dvěma šířky obrazovky
     int middleY = SCREEN_HEIGHT >> 1;              // Y střed obrazovky. Bitový posun doprava, způsobí dělení dvěma výšky obrazovky
     float angleY = 0.0f;                           // Rotace kolem osy Y, což způsobí otočení kamery doprava a doleva
     float angleZ = 0.0f;                           // Směr pohledu kamery nahoru a dolu
     static float currentRotX = 0.0f;               // Rotace kamery v X směru

     // Získej X,Y pozici kurzoru myši
     GetCursorPos(&mousePos);

     // Pokud je kurzor stále uprostřed okna, nic by se nemělo hýbat, proto opusť funkci
     if( (mousePos.x == middleX) && (mousePos.y == middleY) ) return;

     // Jinak vrať kurzor zpět do středu okna
     SetCursorPos(middleX, middleY);

     // Získej úhel Y a Z podle směru pohybu kurzoru
     angleY = (float)( (middleX - mousePos.x) ) / 500.0f;
     angleZ = (float)( (middleY - mousePos.y) ) / 500.0f;

     // Nastav stávájící rotaci kamery v X směru
     // Hodnotu pak použíj k tomu, aby kamera v X směru nerotovala 360 stupnu,
     // ale aby se zastavovala u stropu a u podlahy
     currentRotX -= angleZ;

     // Jestliže je stávájící rotace X (která je v radianech) větší než 1, tak vrať hodnotu zpět na 1
     if(currentRotX > 1.0f)
          currentRotX = 1.0f;
     // Jestliže je stávájící rotace X (která je v radianech) menší než -1, tak vrať hodnotu zpět na -1
     else if(currentRotX < -1.0f)
          currentRotX = -1.0f;
     // Jinak rotuj dále
     else
     {
          // Aby rotovala kamera nahoru a dolu, musíš vypočítat
          // kolmý vektor na vektor pohledu kamery.
          KVector3 vAxis = Cross(View - Position, Up);      // Vypočítej kolmý vektor osy na vektor pohledu kamery
          vAxis = Normalize(vAxis);                         // Normalizuj vektor osy

          RotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);    // Rotuj nahoru a dolu v nové ose
          RotateView(angleY, 0, 1, 0);                      // Rotuj doleva a doprava
     }
}

Tato funkce je volána v nekonečné smyčce spolu s funkcí gluLookAt()

void KCamera::Look()
{
     // Rotuj pomocí myši
     SetViewByMouse();

     // Pohybuj a rotuj kamerou
     gluLookAt(Position.x, Position.y, Position.z,     // Pozice bodu kamery
                 View.x, View.y, View.z,               // Pozice bodu, kam kouká kamera
                 Up.x, Up.y, Up.z);                    // Pozice Up vektoru Y=1;
}

Fukcí DrawScene().

int DrawScene(void)        // Vykresli scénu
{
       Camera1.Look();     // Pohybuj a rotuj kamerou
       DrawCube();         // Vykresli krychli
       DrawFps();          // Vykresli FPS

     return TRUE;          // Vrať TRUE pokud vše proběhlo OK
}

A to je vše. Chtěl bych poděkovat NEHE mu, od kterého jsem převzal základ OpenGL aplikace. Dále bych chtěl poděkovat DigiBen ovi, od kterého jsem se naučil pohybovat kamerou. Taky mi pohohl RAJSOFT junior, od kterého jsem převzal pár překladů z NEHE tutorialů.

Příště se vrhneme na vytváření okolní krajiny (Sky Box).

Kwan




Home