Ve třetí části seriálu o multimediální knihovně Pyglet si ukážeme dva způsoby zobrazení prostorových (3D) scén, přičemž samotné vykreslování bude prováděno přes grafickou knihovnu OpenGL.

Obsah

1. Multimediální knihovna Pyglet: zobrazení prostorové scény

2. Nastavení kamery sledující prostorovou scénu

3. Pomocné funkce z GLU (OpenGL Utilities)

4. Scéna snímaná ortografickou kamerou

5. Funkce glOrtho() a gluLookAt()

6. Změna transformačních matic a nastavení aktuální transformační matice

7. První demonstrační příklad: scéna snímaná ortografickou kamerou

8. Skládání transformací

9. Druhý demonstrační příklad: otáčení objektem ve scéně

10. Scéna snímaná kamerou s perspektivou

11. Základní funkce pro nastavení kamery v perspektivním režimu

12. Třetí demonstrační příklad: perspektivní kamera

13. Repositář s demonstračními příklady

14. Seznam dnes popsaných funkcí knihovny OpenGL a GLU

15. Odkazy na Internetu

1. Multimediální knihovna Pyglet: zobrazení prostorové scény

Ve třetí části seriálu o multimediální knihovně Pyglet si ukážeme dva způsoby zobrazení prostorových (3D) scén, přičemž samotné vykreslování bude prováděno přes grafickou knihovnu OpenGL. Při zobrazení prostorové scény musíme vyřešit dva základní problémy. Prvním problémem je zadání údajů o scéně (tj. geometrických a vizuálních údajů o prostorových tělesech, které jsou v ní umístěny), druhým problémem je nastavení kamery neboli pozorovatele, kterou se na scénu díváme, tak, aby byla korektně zobrazena požadovaná část scény. To vyžaduje určitou dávku prostorové představivosti, protože při prvních pokusech se kamera pravděpodobně bude „dívat“ mimo oblast, v níž se nachází tělesa.

První problém je možné pro jednoduché scény řešit triviálně: tělesa ve scéně rozložíme na jednodušší části, které lze reprezentovat pomocí grafických primitiv popsaných minule. U trojrozměrných scén musíme pro každý vrchol (vertex) grafického primitiva zadat minimálně jeho souřadnice v prostoru (tj. buď trojici [x, y, z], nebo v některých případech dokonce čtveřici [x, y, z, w]). Ve skutečnosti však pro většinu těles musíme specifikovat i barvu vrcholu, jeho normálu, souřadnici do textury a/nebo materiál.

Druhý problém, tj. nastavení kamery, pomocí které pozorujeme scénu, lze řešit více způsoby. První způsob spočívá v nastavení transformačních matic Projection a ModelView s využitím dále popsaných funkcí glRotate(), glScale() a glTranslate(). Tento způsob má nevýhodu v tom, že nastavení transformačních matic tímto způsobem není příliš intuitivní (jedním z častých problémů při práci s grafickou knihovnou OpenGL je nesprávné nastavení pohledu na zobrazovanou scénu, takže programátor v horším případě uvidí pouze černou obrazovku, protože se kamera dívá mimo scénu někam do nekonečna). Řešení tohoto problému je popsáno ve druhé a ve třetí kapitole.

2. Nastavení kamery sledující prostorovou scénu

Aby nebylo nutné nastavovat pozici kamery jen nepřímo funkcemi glRotate(), glScale() a glTranslate(), byly do knihovny OpenGL přidány další funkce určené pouze pro práci s kamerou. Ve skutečnosti se pomocí těchto funkcí nepřímo nastavují transformační matice, takže výsledný efekt je stejný jako přímé volání funkcí pro změnu transformačních matic (samozřejmě s korektními parametry). Mezi funkce, které umožňují manipulaci s kamerou, patří:

  1. glOrtho(): nastavení takzvané ortogonální kamery, tj. takové projekce, u které není zkreslována velikost objektů se vzdáleností od kamery.
  2. glFrustum(): nastavení takzvané perspektivní kamery, tj. takové projekce, u které se zdánlivá velikost objektů na obrazovce zmenšuje s rostoucí vzdáleností od kamery.

3. Pomocné funkce z GLU (OpenGL Utilities)

Kromě základních funkcí glOrtho() a glFrustum() můžeme využít i pomocné funkce, které nám nabízí nadstavbová knihovna GLU (OpenGL Utilities). Tyto funkce mají výhodu v tom, že umožňují nastavení kamery pomocí poměrně snadno pochopitelných parametrů, které známe i z reálného světa. Jedná se o následující funkce:

  1. gluLookAt(): tato funkce nastaví pozici kamery, směr jejího pohledu a orientaci souřadného systému vůči kameře (směr „stropu“).
  2. gluOrtho2D(): nastavení jednoduché 2D ortografické projekce, tj. pohledu na rovinu X-Y. Blízká a vzdálená ořezávací rovina (viz dále) je v tomto případě nastavena na hodnotu –1 resp. +1.
  3. gluPerspective(): nastavení perspektivní kamery, ale s použitím jiných, intuitivnějších parametrů než u funkce glFrustum().

4. Scéna snímaná ortografickou kamerou

Termínem ortografická kamera (resp. přesněji řečeno ortografická projekce) označujeme takovou projekci, u které není zkreslována velikost objektů se vzdáleností od kamery. Ortografická projekce se používá například v různých CAD systémech pro zobrazení součástek nebo v architektonických aplikacích, protože zde lze jednoduše zjistit délky všech hran bez nutnosti složitých přepočtů.

Nastavení ortografické kamery lze provést například pomocí dvojice funkcí gluLookAt() a glOrtho(). Pomocí funkce glOrtho() se vhodně nastaví transformační matice Projection takovým způsobem, že všechna vykreslovaná grafická primitiva jsou ořezána podle šesti ořezávacích rovin. Tyto ořezávací roviny jsou paralelní s osami souřadného systému, vykreslovaný prostor je tedy tvořen osově orientovaným kvádrem (viewing volume). Všechny vrcholy ležící mimo tento kvádr jsou odstraněny.

5. Funkce glOrtho() a gluLookAt()

Funkce glOrtho() akceptuje šest parametrů nazvaným left, right, bottom, top, near a far s následujícím významem:

Index Parametr Význam
1 left souřadnice levé ořezávací roviny
2 right souřadnice pravé ořezávací roviny
3 bottom souřadnice dolní ořezávací roviny
4 top souřadnice horní ořezávací roviny
5 near souřadnice blízké ořezávací roviny
6 far souřadnice vzdálené ořezávací roviny

Význam jednotlivých ořezávacích rovin a jejich orientace k pozorovateli je naznačena na následujícím obrázku:

Obrázek 1: Význam parametrů funkce glOrtho().

Důležité je, že vzdálená a blízká ořezávací rovina jsou zadány ve směru záporné osy z. Pokud je kamera umístěna v počátku, tj. na souřadnicích[0, 0, 0], mají parametry funkce glOrtho() tento význam:

  • Trojice parametrů [left, bottom, -near] specifikuje bod, který je mapován (transformován) do levého dolního rohu vykreslovaného ok­na.
  • Trojice parametrů [right, top, -near] specifikuje bod, který je mapován (transformován) do pravého horního rohu vykreslovaného ok­na.
  • Parametr -far specifikuje umístění vzdálenější ořezávací roviny.

Druhým krokem v nastavení kamery je její umístění a natočení v prostoru. Tato operace se dá provádět přímou změnou matice ModelView, ale pro intuitivnější práci je výhodnější použít funkci gluLookAt(). Tato funkce akceptuje devět parametrů s následujícím významem:

Index Parametr Význam
1-3 eyex, eyey, eyez souřadnice, na kterých se kamera nachází
4-6 centerx, centery, centerz souřadnice bodu, na který se kamera dívá
7-9 upx, upy, upz vektor směřující „nahoru“ (na vykreslované scéně vertikální směr)

Pomocí těchto devíti parametrů lze vytvořit lokální souřadný systém kamery, který se skládá z vektorů F (forward), L (left) a U (up). Z prvních třech parametrů se vytvoří bod E (pozice kamery), ze druhých třech parametrů bod C (střed pohledu) a z rozdílu souřadnic těchto dvou bodů získáme vektor F (forward). Zbývá nám tedy dopočítat vektory L a U. Vektor L získáme vektorovým součinem vektorů F a up (poslední tři parametry):

L = up × F

Poslední vektor U se získá dalším vektorovým součinem vektorů F a L:

U = F × L.

Celá situace je znázorněná na dalším obrázku.

Obrázek 2: Kamera ve scéně a výpočet vektorů F, L a U.

6. Změna transformačních matic a nastavení aktuální transformační matice

V knihovně OpenGL existují tři transformační matice, které se postupně aplikují na body (vrcholy, vertexy) popř. i na normálové vektory vrcholů. První transformační matice se jmenuje ModelView matrix. Na tuto matici se můžeme dívat jako na spojení modelové matice a pohledové matice, protože se používá jak pro nastavení pozice kamery, tak i pro manipulaci s objekty (modely) ve scéně. Druhá transformační matice se jmenuje Projection matrix a používá se pro nastavení perspektivní projekce kamery. Třetí transformační matice se jmenuje Viewport matrix a používá se po provedení perspektivní projekce k mapování objektů z abtraktních souřadnic do souřadnic okna. Tato poslední matice ve skutečnosti pouze provádí transformaci v dvojrozměrné ploše, proto se s ní v knihovně OpenGL nepracuje jako s „plnohodnotnou“ maticí. Kromě těchto tří matic můžeme měnit matici, která se používá při mapování textur na povrch objektů. Tato matice se nazývá Texture matrix.

Při změně některé z transformačních matic musíme nejprve určit, kterou transformační matici budeme měnit. K tomuto účelu se používá funkce glMatrixMode(mode). Tato funkce má jeden parametr mode, kterým určujeme matici, kterou budeme dalšími příkazy změnit. Parametr může nabývat tří hodnot, reprezentovaných symbolickými konstantami:

  • GL_MODELVIEW – bude se měnit ModelView matrix, tj. matice, ve které jsou uloženy modelové a pohledové transformace (transformace objektů a nastavení kamery).
  • GL_PROJECTION – bude se měnit Projection matrix, tj. matice, která se používá pro nastavení perspektivní nebo ortogonální projekce kamery.
  • GL_TEXTURE – bude se měnit Texture matrix, tj. matice, která se používá při mapování textur na povrch objektů.

7. První demonstrační příklad: scéna snímaná ortografickou kamerou

Jak již bylo napsáno výše, funkcí glOrtho() se mění prvky v Projection matici. Před zavoláním této funkce je tedy nutné nastavit tuto matici jako aktuální a matici samotnou nastavit jako jednotkovou:

glMatrixMode(GL_PROJECTION)                  # zacatek modifikace projekcni matice
glLoadIdentity()                             # vymazani projekcni matice (=identita)
glOrtho(left, right, bottom, top, near, far) # nastaveni ortogonalni projekce

Vzhledem k tomu, že pomocí funkce gluLookAt() se mění prvky transformační matice ModelView, je ve většině běžných případů zapotřebí zavolat tuto sekvenci příkazů:

glMatrixMode(GL_MODELVIEW)                   # bude se menit modelova matice
glLoadIdentity()                             # nahrat jednotkovou matici

gluLookAt(eyex, eyey, eyez,                  # bod, odkud se kamera diva
          centerx, centery, centerz,         # bod, kam se kamera diva
          upx, upy, upz                      # poloha "stropu" ve scene

Základní práce s ortografickou kamerou, tedy nastavení pozice a orientace kamery pomocí funkce gluLookAt(), je ukázána v prvním demonstračním příkladu. Na scéně je zobrazeno jednoduché trojrozměrné těleso ve tvaru domečku složené z trojúhelníků a čtyřúhelníků. Tato grafická primitiva jsou vykreslena jen úsečkami, takže výsledkem je drátový model (wireframe):

#!/usr/bin/env python

import pyglet
from pyglet.gl import *
from pyglet.window import key
window = pyglet.window.Window(width=500,
                              height=500,
                              caption="Pyglet+OpenGL")



def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_LINE)          # nastaveni rezimu vykresleni dratoveho modelu
    glPolygonMode(GL_BACK, GL_LINE)           # jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE)                   # zadne hrany ani steny se nebudou odstranovat



@window.event
def on_resize(width, height):
    init()
    glViewport(0, 0, width, height)           # viditelna oblast pres cele okno



@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)              # vymazani vsech bitovych rovin barvoveho bufferu

    glMatrixMode(GL_PROJECTION)               # zacatek modifikace projekcni matice
    glLoadIdentity()                          # vymazani projekcni matice (=identita)
    glOrtho(-10, 10, -10, 10, -30, 30)        # nastaveni ortogonalni projekce
    glMatrixMode(GL_MODELVIEW)                # bude se menit modelova matice
    glLoadIdentity()                          # nahrat jednotkovou matici

    gluLookAt(4.0, 6.0, 18.0,                 # bod, odkud se kamera diva
              0.0, 2.0,  0.0,                 # bod, kam se kamera diva
              0.0, 1.0,  0.0)                 # poloha "stropu" ve scene

    glBegin(GL_QUADS)                         # vykresleni otevrene krychle - sten domecku
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)

    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glEnd()

    glBegin(GL_TRIANGLES)                      # vykresleni strechy domecku z trojuhelniku
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



pyglet.app.run()

Obrázek 3: Trojrozměrná scéna vykreslená prvním demonstračním příkladem.

8. Skládání transformací

S obsahem aktuálně nastavené transformační matice je možné manipulovat pomocí funkcí glLoadIdentity(), glLoadMatrix(), glMultMatrix(), glTranslate(), glScale() a glRotate().

Nejjednodušší z těchto funkcí je funkce glLoadIdentity(). Tato funkce nahraje do aktuálně nastavené transformační matice koeficienty odpovídající jednotkové matici, tj. matici, ve které jsou všechny prvky vynulovány s výjimkou prvků hlavní diagonály, které jsou nastaveny na jedničku. Taková matice hraje úlohu neutrálního prvku při násobení matic nebo při násobení vektoru maticí. Při nastavování některé transformační matice se v naprosté většině případů začíná touto funkcí, neboť pomocí ní matici „připravíme“ na aplikaci dalších transformací.

Funkce glLoadMatrix() se používá pro přímé nastavení prvků matice. Další funkce glMultMatrix() slouží k vynásobení aktuálně nastavené transformační matice maticí zadanou jako parametr této funkce.

Další tři funkce glTranslate(), glScale() a glRotate() jsou používány mnohem častěji než předchozí dvě funkce. U těchto funkcí se nemanipuluje přímo s jednotlivými prvky matice, ale zadávají se základní lineární transformace – posun, změna měřítka a rotace. Pro zadané transformace se vytvoří dočasná matice a aktuální matice (většinou je to matice ModelView) je touto dočasnou maticí vynásobena.

9. Druhý demonstrační příklad: otáčení objektem ve scéně

Ve druhém demonstračním příkladu je možné tělesem – domečkem – ve scéně rotovat, což je zařízeno dvojicí globálních proměnných, které se mění stiskem kurzorových kláves:

r1 = 0.0
r2 = 0.0

Změna proměnných r1 a r2:

keys = key.KeyStateHandler()
window.push_handlers(keys)

global r1, r2

if keys[key.LEFT]:
    r2 = r2 - 1
if keys[key.RIGHT]:
    r2 = r2 + 1
if keys[key.UP]:
    r1 = r1 - 1
if keys[key.DOWN]:
    r1 = r1 + 1

Rotace tělesa pomocí skládání transformací:

glMatrixMode(GL_MODELVIEW)                # bude se menit modelova matice
glLoadIdentity()                          # nahrat jednotkovou matici

gluLookAt(4.0, 6.0, 18.0,                 # bod, odkud se kamera diva
          0.0, 2.0,  0.0,                 # bod, kam se kamera diva
          0.0, 1.0,  0.0)                 # poloha "stropu" ve scene

glRotatef(r1, 1.0, 0.0, 0.0)              # rotace objektu
glRotatef(r2, 0.0, 1.0, 0.0) 

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *
from pyglet.window import key

r1 = 0.0
r2 = 0.0

window = pyglet.window.Window(width=500,
                              height=500,
                              caption="Pyglet+OpenGL")


keys = key.KeyStateHandler()
window.push_handlers(keys)



def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_LINE)          # nastaveni rezimu vykresleni dratoveho modelu
    glPolygonMode(GL_BACK, GL_LINE)           # jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE)                   # zadne hrany ani steny se nebudou odstranovat



@window.event
def on_resize(width, height):
    init()
    glViewport(0, 0, width, height)           # viditelna oblast pres cele okno



@window.event
def on_draw():
    global r1, r2

    if keys[key.LEFT]:
        r2 = r2 - 1
    if keys[key.RIGHT]:
        r2 = r2 + 1
    if keys[key.UP]:
        r1 = r1 - 1
    if keys[key.DOWN]:
        r1 = r1 + 1

    glClear(GL_COLOR_BUFFER_BIT)              # vymazani vsech bitovych rovin barvoveho bufferu

    glMatrixMode(GL_PROJECTION)               # zacatek modifikace projekcni matice
    glLoadIdentity()                          # vymazani projekcni matice (=identita)
    glOrtho(-10, 10, -10, 10, -30, 30)        # nastaveni ortogonalni projekce
    glMatrixMode(GL_MODELVIEW)                # bude se menit modelova matice
    glLoadIdentity()                          # nahrat jednotkovou matici

    gluLookAt(4.0, 6.0, 18.0,                 # bod, odkud se kamera diva
              0.0, 2.0,  0.0,                 # bod, kam se kamera diva
              0.0, 1.0,  0.0)                 # poloha "stropu" ve scene

    glRotatef(r1, 1.0, 0.0, 0.0)              # rotace objektu
    glRotatef(r2, 0.0, 1.0, 0.0) 

    glBegin(GL_QUADS)                         # vykresleni otevrene krychle - sten domecku
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)

    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glEnd()

    glBegin(GL_TRIANGLES)                      # vykresleni strechy domecku z trojuhelniku
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



pyglet.app.run()

Obrázek 4: Trojrozměrná scéna vykreslená druhým demonstračním příkladem.

Obrázek 5: Trojrozměrná scéna vykreslená druhým demonstračním příkladem.

10. Scéna snímaná kamerou s perspektivou

Nastavení perspektivní kamery je poněkud složitější, než nastavení kamery ortogonální, protože prostor viditelný touto kamerou nemá obecně tvar hranolu, ale komolého jehlanu. Musíme tedy vhodným způsobem specifikovat parametry tohoto jehlanu. Podobně jako u kamery nastavené do ortogonálního režimu, i zde se ty části těles, které se nachází mimo viditelný prostor, ořezávají.

11. Základní funkce pro nastavení kamery v perspektivním režimu

Základní funkcí pro nastavení perspektivní kamery je funkce glFrustum(), pomocí které se nastavují prvky transformační matice GL_PROJECTION. Při volání této funkce musíme zadat šest parametrů s následujícím významem:

Index Parametr Význam
1 left vzdálenost levé ořezávací roviny od počátku. Vzdálenost se udává ve směru osy x.
2 right vzdálenost pravé ořezávací roviny od počátku. Vzdálenost se opět udává ve směru osy x, podobně jako u předchozího parametru.
3 bottom vzdálenost spodní ořezávací roviny od počátku. Vzdálenost se udává ve směru osy y.
4 top vzdálenost horní ořezávací roviny od počátku. Vzdálenost je opět zadána ve směru osy y, podobně jako u předchozího parametru.
5 near vzdálenost od kamery k bližší ořezávací rovině kolmé na směr promítání. Vzdálenost je tedy zadána ve směru osy z.
6 far vzdálenost od kamery k vzdálenější ořezávací rovině kolmé na směr promítání. Stejně jako u předchozího parametru, i tato vzdálenost je zadána ve směru osy z.

Význam parametrů této funkce je znázorněn na šestém obrázku. Výsek viditelného prostoru ve tvaru komolého jehlanu je zadán rozměry menší podstavy (parametry left, right, bottom a top) a vzdáleností obou podstav od kamery. Úhel stěn je možné z těchto hodnot dopočítat.

Obrázek 6: Význam parametrů funkce glFrustum().

Kromě této funkce (která nastavuje transformační matici GL_PROJECTION) lze samozřejmě použít i výše popsanou funkci gluLookAt(), pomocí které se nastaví prvky transformační matice MODELVIEW. Pro korektní práci je nutné nejdříve nastavit měněnou matici funkcí glMatrixMode() a poté tuto matici nastavit na jednotkovou pomocí funkce glLoadIdentity().

S využitím funkce glFrustum() lze sice nastavit projekční transformační matici pro kameru v perspektivním režimu, ale zadávání šesti parametrů komolého jehlanu není příliš intuitivní. Proto byla do knihovny GLU (což je, jak již víme, nadstavba nad knihovnou OpenGL s mnoha užitečnými funkcemi) přidána funkce pro nastavení vlastností kamery v perspektivním režimu pomocí parametrů, jejichž význam známe z reálného světa (používají se například při popisu vlastností objektivů). Tato funkce se jmenuje gluPerspective() a akceptuje čtyři parametry:

Index Parametr Význam
1 fovy zorný úhel (Field Of View) kamery. Tento úhel je zadán v rovině x-z, která prochází počátkem. Běžně používané zorné úhly se pohybují v rozsahu 30–80 stupňů, je však možné zadat i úhly menší (teleobjektiv) nebo větší (rybí oko).
2 aspect poměr mezi šířkou a výškou zadávaného jehlanu. Tento parametr lze použít pro roztažení popř. smrsknutí obrázku ve směru jedné ze souřadných os. Hodnotu tohoto parametru můžeme získat podílem šířky a výšky podstavy jehlanu. Většinou se tato hodnota zjišťuje z rozměrů okna pro kreslení.
3 near vzdálenost od kamery k bližší ořezávací rovině kolmé na směr promítání. Vzdálenost je tedy zadána ve směru osy z. Význam tohoto parametru je tedy stejný jako u funkce glFrustum().
4 far vzdálenost od kamery k vzdálenější ořezávací rovině kolmé na směr promítání. Stejně jako u předchozího parametru, i tato vzdálenost je zadána ve směru osy z. Význam tohoto parametru je opět totožný s parametrem funkce glFrustum().

Význam jednotlivých parametrů funkce gluPerspective() je vysvětlen i na sedmém obrázku. Z tohoto obrázku je také patrné, jak jednotlivé parametry ovlivňují nastavení kamery. Dvojice parametrů fovy a aspect nastavuje tvar menší podstavy komolého jehlanu, zatímco parametry near a far nastavují výšku a velikost jehlanu.

Obrázek 7: Význam parametrů funkce gluPerspective().

12. Třetí demonstrační příklad: perspektivní kamera

Ve třetím a dnes i posledním demonstračním příkladu si ukážeme postup při nastavení kamery s perspektivním promítáním. Způsob zobrazení je řízen trojicí globálních proměnných:

fov = 70.0                                    # hodnota zorneho uhlu - field of view
nearPlane = 0.1;                              # blizsi orezavaci rovina
farPlane = 90.0;                              # vzdalenejsi orezavaci rovina

Nastavení projekční matice:

glMatrixMode(GL_PROJECTION)                   # zacatek modifikace projekcni matice
glLoadIdentity()                              # vymazani projekcni matice (=identita)
gluPerspective(fov, 1.0, nearPlane, farPlane);

Nastavení modelview matice s rotací tělesa:

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()                              # nahrat jednotkovou matici

gluLookAt(4.0, 6.0, 18.0,                     # bod, odkud se kamera diva
          0.0, 2.0,  0.0,                     # bod, kam se kamera diva
          0.0, 1.0,  0.0)                     # poloha "stropu" ve scene

glRotatef(r1, 1.0, 0.0, 0.0)                  # rotace objektu
glRotatef(r2, 0.0, 1.0, 0.0) 

Úplný zdrojový kód třetího demonstračního příkladu vypadá následovně:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *
from pyglet.window import key

fov = 70.0                                    # hodnota zorneho uhlu - field of view
nearPlane = 0.1;                              # blizsi orezavaci rovina
farPlane = 90.0;                              # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0

window = pyglet.window.Window(width=500,
                              height=500,
                              caption="Pyglet+OpenGL")


keys = key.KeyStateHandler()
window.push_handlers(keys)



def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_LINE)          # nastaveni rezimu vykresleni dratoveho modelu
    glPolygonMode(GL_BACK, GL_LINE)           # jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE)                   # zadne hrany ani steny se nebudou odstranovat



@window.event
def on_resize(width, height):
    init()
    glViewport(0, 0, width, height)           # viditelna oblast pres cele okno



@window.event
def on_draw():
    global r1, r2

    if keys[key.LEFT]:
        r2 = r2 - 1
    if keys[key.RIGHT]:
        r2 = r2 + 1
    if keys[key.UP]:
        r1 = r1 - 1
    if keys[key.DOWN]:
        r1 = r1 + 1

    glClear(GL_COLOR_BUFFER_BIT)              # vymazani vsech bitovych rovin barvoveho bufferu

    glMatrixMode(GL_PROJECTION)               # zacatek modifikace projekcni matice
    glLoadIdentity()                          # vymazani projekcni matice (=identita)
    gluPerspective(fov, 1.0, nearPlane, farPlane);

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()                          # nahrat jednotkovou matici

    gluLookAt(4.0, 6.0, 18.0,                 # bod, odkud se kamera diva
              0.0, 2.0,  0.0,                 # bod, kam se kamera diva
              0.0, 1.0,  0.0)                 # poloha "stropu" ve scene

    glRotatef(r1, 1.0, 0.0, 0.0)              # rotace objektu
    glRotatef(r2, 0.0, 1.0, 0.0) 

    glBegin(GL_QUADS)                         # vykresleni otevrene krychle - sten domecku
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)

    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glEnd()

    glBegin(GL_TRIANGLES)                      # vykresleni strechy domecku z trojuhelniku
    glColor3f(0.0, 0.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(1.0, 0.0, 0.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)

    glColor3f(0.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glColor3f(1.0, 1.0, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



pyglet.app.run()

Obrázek 8: Trojrozměrná scéna vykreslená třetím demonstračním příkladem.

Obrázek 9: Trojrozměrná scéna vykreslená třetím demonstračním příkladem.

13. Repositář s demonstračními příklady

Všechny tři dnes popsané demonstrační příklady byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář. Pro jejich spuštění je nutné mít nainstalovanou jak knihovnu Pyglet, tak i podpůrné grafické knihovny OpenGL a GLU (což se většinou provede automaticky v rámci instalace Pygletu, viz též úvodní díl tohoto seriálu):

Příklad Odkaz
17_3d_projection.py https://github.com/tisnik/presentations/blob/master/pyglet/17_3d_projection.py
18_rotation.py https://github.com/tisnik/presentations/blob/master/pyglet/18_rotation.py
19_perspective.py https://github.com/tisnik/presentations/blob/master/pyglet/19_perspective.py

14. Seznam dnes popsaných funkcí knihovny OpenGL a GLU

  1. glLoadIdentity
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glLoadIdentity.xml
  2. glTranslate
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml
  3. glRotate
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glRotate.xml
  4. glScale
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glScale.xml
  5. glMatrixMode
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glMatrixMode.xml
  6. glOrtho
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml
  7. glFrustum
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
  8. glViewport
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glViewport.xml
  9. gluLookAt
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
  10. gluPerspective
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml

15. Odkazy na Internetu

  1. Pyglet Home Page
    https://bitbucket.org/pyglet/pyglet/wiki/Home
  2. Dokumentace k verzi 1.2
    https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/
  3. Dokumentace k verzi 1.2 ve formátu PDF
    https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/
  4. PyOpenGL
    http://pyopengl.sourceforge.net/
  5. The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
    https://www.in-ulm.de/~mascheck/various/shebang/
  6. Shebang (Unix)
    https://en.wikipedia.org/wiki/Shebang_%28Unix%29
  7. Domovská stránka systému LÖVE
    http://love2d.org/
  8. Simple DirectMedia Layer (home page)
    http://www.libsdl.org/
  9. Simple DirectMedia Layer (Wikipedia)
    https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
  10. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  11. Pyglet event loop
    http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html
  12. Decorators I: Introduction to Python Decorators
    http://www.artima.com/weblogs/viewpost.jsp?thread=240808
  13. 3D Programming in Python – Part 1
    https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/
  14. A very basic Pyglet tutorial
    http://www.natan.termitnjak.net/tutorials/pyglet_basic.html