V dnešní části seriálu o multimediální knihovně Pyglet si podrobněji popíšeme princip Phongova osvětlovacího modelu, který se – pokud je ovšem povolený – používá pro výpočet barvy na povrchu trojrozměrných těles. Také si popíšeme způsoby definování normál (normálových vektorů) k povrchu těles, protože při využití Phongova osvětlovacího modelu je zapotřebí nějakým způsobem určit normálu ke každému bodu na povrchu tělesa.

Obsah

1. Multimediální knihovna Pyglet: Phongův osvětlovací model použitý v OpenGL

2. Phongův osvětlovací model

3. Význam členů rovnice Phongova osvětlovacího modelu

4. Normálové vektory

5. Normalizace normálových vektorů

6. Typy stínování v OpenGL

7. Kvadriky vykreslované přes GLU a normálové vektory jejich ploch

8. Nastavení ambientní složky materiálu

9. Nastavení difúzní složky materiálu

10. Nastavení odlesků

11. Specifikace stínovacího modelu

12. První demonstrační příklad – vykreslení modelu koule, materiál má nastavenou jen ambientní složku

13. Druhý demonstrační příklad – vykreslení modelu koule, materiál má nastavenou ambientní a difúzní složku

14. Třetí demonstrační příklad – vykreslení modelu koule, materiál má nastavené i odlesky

15. Čtvrtý demonstrační příklad – osvětlovací model a těleso s plochami bez normálových vektorů

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

17. Odkazy na Internetu

1. Multimediální knihovna Pyglet: Phongův osvětlovací model použitý v OpenGL

V grafické knihovně OpenGL, jejíž funkce lze z Pygletu volat, se pro výpočet barvy povrchu těles (složených z plošek) používá takzvaný Phongův osvětlovací model (pozor – neplést s Phongovým stínováním, což je sice podobně pojmenovaná, ovšem odlišná technologie zmíněná v šesté kapitole). Tento osvětlovací model byl při návrhu specifikace knihovny OpenGL zvolen z toho důvodu, aby byl výpočet osvětlení co nejrychlejší, ale současně, aby osvětlení výsledné scény působilo přirozeně. Výsledkem obou těchto snah je samozřejmě určitý kompromisní model, který však poskytuje pozoruhodně kvalitní výsledky. Odborníci na počítačovou grafiku samozřejmě mohou oprávněně namítnout, že existují i fyzikálně přesnější modely, například model Cook-Torrancův, ty jsou však výpočetně složitější, proto se nehodí pro real-time grafiku (hry, dema, multimediální aplikace), na kterou je knihovna Pyglet a samozřejmě taktéž OpenGL primárně zaměřena. Tyto přesnější modely lze využít například v některých pokročilejších raytracerech (ale nutno říci, že zdaleka ne ve všech).

Obrázek 1: Phongův osvětlovací model se používá i v raytracerech pro fotorealistické zobrazení scén.

2. Phongův osvětlovací model

V Phongově osvětlovacím modelu se světlo na povrchu tělesa rozkládá do tří typů světelných složek: ambientní složky (ambient light), difúzní složky (diffuse light) a odlesků (specular light). V tomto modelu se navíc celková intenzita světla počítá zvlášť pro každou barevnou složku z barvového modelu RGB (red, green, blue). Pro výpočet se používá vztah, který je aplikován na každou barevnou složku zvlášť, ve skutečnosti tedy počítáme intenzity Ired, Igreen, Iblue. Rovnice pro výpočet barvy každého bodu na ploše objektů vypadá následovně:

I = caIa + cdId(NL) + csIs(VR)n

3. Význam členů rovnice Phongova osvětlovacího modelu

Význam jednotlivých členů použitých ve výše uvedeném vztahu je následující:

  • I představuje celkovou intenzitu světla tak, jak je vnímána uživatelem. Ve skutečnosti se vždy spočítají tři na sobě nezávislé barevné složky R, G, B, takže se předchozí vztah počítá třikrát (Ired, Igreen, Iblue).
  • Ia představuje intenzitu ambientní složky světla, tj. té části světla, která na osvětlované těleso dopadá ze všech směrů rovnoměrně. Intenzita odraženého světla (tj. součinu caIa) je nezávislá na vzájemné poloze zdroje, tělesa a pozorovatele. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření světla.
  • Id představuje intenzitu difúzní složky světla, tj. té části světla, která dopadá na těleso z jednoho světelného zdroje (ať už bodového či směrového) a odráží se do všech směrů rovnoměrně. Intenzita odraženého světla (tj. součinu cdId(NL)) je závislá na vzájemné poloze normály stěny tělesa N a vektoru dopadu světla L (viz další text). Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření světla.
  • Is představuje intenzitu odlesků, tj. té části světla, která dopadá na těleso z jednoho světelného zdroje (opět může jít buď o bodový nebo směrový zdroj světla) a odráží se převážně v jednom směru podle zákona odrazu světelných paprsků. Intenzita tohoto odraženého světla závisí na vzájemné poloze světelného zdroje, povrchu tělesa a pozice pozorovatele. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření světla.
  • ca je koeficient (resp. přesněji řečeno faktor) materiálu pro ambientní složku světla. Pro běžné materiály nabývá tento koeficient hodnot v rozmezí 0 až 1, kde nulová hodnota znamená, že se ambientní světlo od materiálu vůbec neodráží, hodnota 1 naopak nastavuje úplnou odrazivost. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření materiálu.
  • cd je koeficient materiálu pro difúzní složku světla. Pro běžné materiály nabývá tento koeficient hodnot v rozmezí 0 až 1 s podobným významem jako u předchozího koeficientu. Ve většině případů lze ambientní a difúzní koeficient materiálu nastavit na stejnou hodnotu, což je v OpenGL samozřejmě podporováno. Hodnota tohoto členu v předchozí rovnici se nastavuje při vytváření materiálu.
  • cs je koeficient odlesků s vlastnostmi podobnými jako u předchozích dvou koeficientů. Zde je nutné si uvědomit, že ve skutečnosti každý koeficient nastavujeme třikrát pro tři barevné složky RGB. Hodnota těchto koeficientů potom udává barvu materiálu při osvětlení bílým světlem, protože pouze bílé světlo je nastaveno na hodnotu (1.0, 1.0, 1.0). Při jiném osvětlení se mezi sebou pronásobí intenzity složek světelného zdroje a koeficienty materiálů, takže například sytě červený objekt bude v sytě modrém světle na obrazovce černý!
  • V součinu NL je použita normála k povrchu tělesa N a vektor ze světelného zdroje do bodu na povrchu tělesa, jehož barvu počítáme. Pokud jsou tyto vektory lineárně závislé (tj. rovnoběžné), je světelný zdroj umístěn kolmo nad povrchem tělesa a tím pádem je difúzní složka světla nejvíce intenzivní. Význam těchto dvou vektorů je zobrazen na prvním obrázku. Ještě před výpočtem skalárního součinu NL musí být oba vektory normalizovány, tj. jejich velikost musí být jednotková.
  • V součinu VR je použit vektor ideálně odraženého paprsku R a vektor z pozice pozorovatele do bodu na povrchu tělesa V, jehož barvu počítáme. Vektor R lze vypočítat ze znalosti vektorů L (vektor světla) a N (normála k povrchu) – postup je naznačen na druhém obrázku. Význam těchto dvou vektorů je zobrazen na prvním obrázku. Podobně jako u vektorů N a L, i vektory V a R musí být normalizovány.
  • n je exponent u výrazu (VR)n, který udává míru lesklosti tělesa. Difúzní těleso bude mít exponent nulový, vysoce lesklé těleso zde může mít hodnotu například 50. Čím vyšší je hodnota tohoto exponentu, tím jsou odlesky na tělese menší (plochou) ale intenzivnější. Vliv velikosti exponentu na lesklosti je zobrazený na třetím obrázku.

Obrázek 2: Vektory použité v Phongově osvětlovacím modelu. L představuje vektor světelného paprsku od světelného zdroje, N je normálový vektor plošky, kam světlo dopadá, R je přesně odražený paprsek světla a V je vektor směřující k pozorovateli (kameře).

Obrázek 3: Postup při výpočtu odraženého paprsku – vektoru R. Aby byl výpočet korektní, musí být vektor N normalizován.

Obrázek 4: Vliv velikosti exponentu na lesklost materiálu. Čím je n vyšší, tím světlejší ale současně i menší je lesklá ploška.

4. Normálové vektory

Při výpočtu osvětlení je bezpodmínečně nutné zadávat u každého vrcholu jeho normálu (normálový vektor), jinak by nebylo možné spočítat barvu na povrchu tělesa (viz předchozí vzorec, kde se vyskytuje vektor N). Pokud není normála nastavena, bere se implicitní hodnota normály N=(0.0, 0.0, 1.0) (tj. normála míří ve směru osy z) což není v naprosté většině případů korektní. Příkaz pro nastavení normály je velmi podobný příkazu pro nastavení barvy nebo polohy vertexu, avšak na rozdíl od těchto příkazů, které existovaly ve více verzích s různým počtem parametrů, se vždy zadávají tři složky normálového vektoru: nx, ny a nz. Normála vrcholu je, podobně jako barva, stavová veličina, takže je možné jednu hodnotu normály použít i pro více vrcholů. V závislosti na typu dat použitých pro reprezentaci normály je možné zvolit jednu z těchto funkcí (v Pythonu se bude většinou jednat o typ double):

void glNormal3b(
  GLbyte nx,  
  GLbyte ny,  
  GLbyte nz   
);
 
void glNormal3s(
  GLshort nx,  
  GLshort ny,  
  GLshort nz   
);

void glNormal3i(
  GLint nx,  
  GLint ny,  
  GLint nz   
);
 
void glNormal3f(
  GLfloat nx,  
  GLfloat ny,  
  GLfloat nz   
);
 
void glNormal3d(
  GLdouble nx,  
  GLdouble ny,  
  GLdouble nz   
);

Další možností je použití funkcí, které jako svůj parametr akceptují pole o třech prvcích (opět je možné použít i v Pythonu):

void glNormal3bv(
  const GLbyte *v   
);
 
void glNormal3sv(
  const GLshort *v   
);
 
void glNormal3iv(
  const GLint *v   
);
 
void glNormal3fv(
  const GLfloat *v   
);
 
void glNormal3dv(
  const GLdouble *v   
);

5. Normalizace normálových vektorů

Pro korektní funkčnost výpočtu osvětlení musí být každý normálový vektor normalizován, tj. délka tohoto vektoru musí být jednotková. Normalizaci je možné provést buď programově, nebo automaticky s využitím příslušných funkcí OpenGL.

Programová normalizace spočívá v tom, že se vypočte délka normálového vektoru podle vzorce:

L=sqrt(nx2+ny2+nz2)

Touto délkou se následně vydělí každá složka normálového vektoru:
nx‚=nx / L
ny‚=ny / L
nz‚=nz / L

Druhou možností je povolení automatické normalizace, což se provede funkcí glEnable(GL_NORMALIZE). Zakázání se provede příkazem glDisable(GL_NORMALIZE). Při provádění automatické normalizace se však buď programově nebo na grafickém akcelerátoru provádí relativně složité matematické operace, jako jsou odmocniny a podíly, což může výrazně zpomalovat vykreslování celé trojrozměrné scény. Proto je výhodnější normalizaci provést programově před začátkem vykreslování nebo mít již normály uložené v normalizované podobě, tak jak to dělají některé 3D modelovací programy.

6. Typy stínování v OpenGL

V knihovně OpenGL lze používat dva typy stínování: konstantní a Gouraudovo. Pomocí speciálních programovacích technik však lze dosáhnout i Phongova stínování (pozor, neplést s Phongovým osvětlovacím modelem, který byl popsán výše).

Při použití konstantního stínování je pro každou plošku (tj. například trojúhelník) spočítána jedna barva a celá ploška je při vykreslování touto barvou vykreslena. Výsledkem je, že jsou na zobrazovaném tělese jasně patrné všechny hrany mezi ploškami, což je nepříjemné zvláště u oblých tvarů.

Problém viditelnosti hran částečně řeší Gouraudovo stínování, u kterého se spočítají barvy jednotlivých vrcholů (normály jsou zadané pro vrcholy, takže se přímo použije dříve uvedený vzorec) a vykreslovaná ploška mezi těmito vrcholy je vyplněna přechodem mezi těmito barvami. Výsledkem jsou jemnější hrany, které jsou méně viditelné. Gouraudovo stínování samozřejmě způsobuje také vizuální chyby, protože se osvětlení počítá pouze ve vrcholech a ignorují se světelné podmínky na ploškách. To například znamená, že zejména na velkých plochách jsou jasně patrné absence odlesků.

Konstantní stínování se zapne příkazem glShadeModel(GL_FLAT), Gouraudovo stínování potom příkazem glShadeModel(GL_SMOOTH). Ukázky obou typů stínování jsou předvedeny v trojici demonstračních příkladů.

7. Kvadriky vykreslované přes GLU a normálové vektory jejich ploch

V předchozí části tohoto seriálu jsme si popsali způsob vykreslování kvadrik. Taktéž jsme si řekli, že při výpočtu ploch kvadrik (resp. přesněji řečeno při výpočtu trojúhelníků a čtyřúhelníků, z nichž se skládají plochy) je možné automaticky vypočítat i normálové vektory. Navíc je možné specifikovat, zda se mají normálové vektory vypočítat vždy pro celou plochu (konstantní stínování) či naopak pro každý vrchol (Gouraudovo stínování). Přesnější je výpočet normálového vektoru pro každý vrchol zvlášť, což zajišťuje následující příkaz:

gluQuadricNormals(quadric, GLU_SMOOTH)      # smer generovanych normal

Funkce, která kvadriku, v tomto případě konkrétně kouli, vykreslí, může vypadat následovně:

def draw_quadric(drawStyle, slices, stacks):
    radius = 8.0
    quadric = gluNewQuadric()                   # vytvoreni kvadriky
    gluQuadricDrawStyle(quadric, drawStyle)     # nastaveni vlastnosti kvadriky
    gluQuadricNormals(quadric, GLU_SMOOTH)      # smer generovanych normal
    gluSphere(quadric, radius, slices, stacks)  # vykresleni kvadriky
    gluDeleteQuadric(quadric)                   # zruseni kvadriky

Nastavení způsobu vykreslování kvadriky (body, úsečky, plošky) i počtu „rovnoběžek“ a „poledníků“ je v dnešních demonstračních příkladech řízeno trojicí globálních proměnných:

quadricDrawStyle = GLU_FILL                     # styl vykreslovani kvadriky
sphereSlices = 20                               # rozdeleni koule na 'poledniky'
sphereStacks = 20                               # rozdeleni koule na 'rovnobezky'

Hodnoty těchto proměnných se mění v callback funkci on_key_press(), která je zavolána ve chvíli, kdy uživatel stiskne nějakou klávesu:

@window.event
def on_key_press(symbol, modifiers):
    global quadricDrawStyle
    global sphereSlices, sphereStacks

    if symbol == key.L:
        quadricDrawStyle = GLU_LINE
    if symbol == key.P:
        quadricDrawStyle = GLU_POINT
    if symbol == key.F:
        quadricDrawStyle = GLU_FILL
    if symbol == key.PAGEUP:
        sphereStacks += 1
    if symbol == key.PAGEDOWN:
        if sphereStacks > 1:
            sphereStacks -= 1
    if symbol == key.HOME:
        if sphereSlices > 1:
            sphereSlices -= 1
    if symbol == key.END:
        sphereSlices += 1

K dispozici jsou tedy následující klávesové zkratky:

Klávesa Význam
P vykreslení bodů na kouli
L vykreslení drátového (wireframe) modelu
F vykreslení plošek
PageUp zvýšení počtu „rovnoběžek“
PageDown snížení počtu „rovnoběžek“
Home snížení počtu „poledníků“
End zvýšení počtu „poledníků“

8. Nastavení ambientní složky materiálu

Ambientní složka materiálu, tedy koeficient ca, se nastavuje funkcí glMaterialfv(), přičemž v prvním parametru se předává konstanta GL_FRONT či GL_BACK popř. GL_FRONT_AND_BACK (nastavení přední či zadní stěny), ve druhém parametru vždy konstanta GL_AMBIENT a v parametru třetím pak pole se třemi či čtyřmi prvky s koeficienty pro jednotlivé barvové složky. Stěna tělesa bude pro ambientní světlo červená:

materialAmbient = [1.0, 0.0, 0.0, 1.0]     # ambientni slozka barvy materialu

materialAmbient_gl = float_array(materialAmbient)
glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient_gl)

Povšimněte si, že pro převod seznamu čtyř čísel typu float na céčkovské pole používáme pomocnou funkci float_array(). S touto funkcí se setkáme i v dalších kapitolách:

def float_array(list):
    ''' Prevod seznamu na pole prvku typ GLfloat '''
    return (GLfloat * len(list))(*list)

Obrázek 5: Koule, jejíž materiál má nastavenou jen ambientní složku, koule je vykreslena sekvencí plošek.

Obrázek 6: Koule, jejíž materiál má nastavenou jen ambientní složku, koule je vykreslena sekvencí úseček.

9. Nastavení difúzní složky materiálu

Difúzní složka materiálu, tedy koeficient ca se nastavuje stejnou funkcí, ovšem v jejím druhém parametru je nutné předat konstantu GL_DIFFUSE a nikoli GL_AMBIENT. V následujícím úryvku kódu je difúzní složka shodná pro všechny barvové složky, takže osvětlená část tělesa bude spíše šedivá a nikoli červená:

materialDiffuse = [0.7, 0.7, 0.7, 0.0]     # difuzni slozka barvy materialu

materialDiffuse_gl = float_array(materialDiffuse)
glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse_gl)

Obrázek 7: Koule, jejíž materiál má nastavenou jen difúzní složku, koule je vykreslena sekvencí plošek.

Obrázek 8: Koule, jejíž materiál má nastavenou jen difúzní složku, koule je vykreslena sekvencí úseček.

10. Nastavení odlesků

Podobným způsobem se nastavuje i koeficient odlesků cs, ovšem nesmíme zapomenout ani na exponent n udávající míru lesklosti tělesa. Pro nastavení koeficientu odlesků i exponentu s mírou lesklosti se stále používá funkce glMaterialfv(), ovšem v prvním případě se ve druhém parametru předává konstanta GL_SPECULAR a v případě druhém pak konstanta GL_SHININESS. Povšimněte si, že materialShininess je jednorozměrný seznam a po konverzi pak jednorozměrné pole:

materialSpecular = [1.0, 1.0, 1.0, 1.0]    # barva odlesku
materialShininess = [50.0]                 # faktor odlesku

materialSpecular_gl = float_array(materialSpecular)
materialShininess_gl = float_array(materialShininess)

# nastaveni barvy odlesku
glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular_gl)

# nastaveni faktoru odlesku
glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess_gl)

Obrázek 9: Koule, jejíž materiál má povoleny odlesky a je vykreslen s využitím Gouraudova stínování.

11. Specifikace stínovacího modelu

Víme již, že si při použití knihovny OpenGL můžeme vybrat mezi konstantním stínováním a Gouraudovým stínováním. V demonstračních příkladech bude volba stínování určena hodnotou globální proměnné shadeModel:

shadeModel = GL_SMOOTH                     # algoritmus vyplnovani plosek

Nastavení stínování přes OpenGL se provede jednoduše:

def set_shade_model(shadeModel):
    glShadeModel(shadeModel)

Změnu stínování může vyzkoušet uživatel stlačením kláves X (konstantní) a C (Gouraudovo):

@window.event
def on_key_press(symbol, modifiers):
    global shadeModel

    if symbol == key.X:
        shadeModel = GL_FLAT
    if symbol == key.C:
        shadeModel = GL_SMOOTH

Podívejme se na rozdíly mezi oběma stínovacími modely:

Obrázek 10: Gouraudovo stínování.

Obrázek 11: Konstantní stínování.

12. První demonstrační příklad – vykreslení modelu koule, materiál má nastavenou jen ambientní složku

V prvním demonstračním příkladu je vykreslena kvadrika (konkrétně koule), ovšem její materiál má nastavenou pouze ambientní složku světla. To znamená, že výsledná scéna bude postrádat hloubku:

#!/usr/bin/env python

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

fov = 70.0                                 # zorny uhel (field of view)
nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0
r3 = 0.0

depthBufferEnabled = True                  # povoleni ci zakaz Z-bufferu

shadeModel = GL_SMOOTH                     # algoritmus vyplnovani plosek

quadricDrawStyle = GLU_FILL                # styl vykreslovani kvadriky
sphereSlices = 20                          # rozdeleni koule na 'poledniky'
sphereStacks = 20                          # rozdeleni koule na 'rovnobezky'


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

keys = key.KeyStateHandler()

# parametry, ktere ovlivnuji osvetleni
materialAmbient = [1.0, 0.0, 0.0, 1.0]     # ambientni slozka barvy materialu
materialDiffuse = [0.0, 0.0, 0.0, 0.0]     # difuzni slozka barvy materialu
lightPosition = [10.0, 10.0, 20.0, 0.0]    # pozice svetla


def float_array(list):
    ''' Prevod seznamu na pole prvku typ GLfloat '''
    return (GLfloat * len(list))(*list)


def set_material():
    materialAmbient_gl = float_array(materialAmbient)
    materialDiffuse_gl = float_array(materialDiffuse)

    # nastaveni ambientni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient_gl)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse_gl)


def set_light():
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)

    # nastaveni pozice svetla
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)

    glEnable(GL_LIGHTING)                  # globalni povoleni stinovani
    glEnable(GL_LIGHT0)                    # povoleni prvniho svetla


def init():
    glClearColor(0.0, 0.0, 0.8, 0.0)       # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)       # nastaveni rezimu vykresleni modelu
    glPolygonMode(GL_BACK, GL_FILL)
    # zakaz odstranovani hran nebo sten podle jejich normal
    glDisable(GL_CULL_FACE)
    glDepthFunc(GL_LESS)                   # funkce pro testovani fragmentu

    glShadeModel(GL_SMOOTH)                # nastaveni stinovaciho rezimu
    glPointSize(3.0)                       # velikost vykreslovanych bodu

    set_material()
    set_light()


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


def draw_quadric(drawStyle, slices, stacks):
    radius = 8.0
    quadric = gluNewQuadric()                   # vytvoreni kvadriky
    gluQuadricDrawStyle(quadric, drawStyle)     # nastaveni vlastnosti kvadriky
    gluQuadricNormals(quadric, GLU_SMOOTH)      # smer generovanych normal
    gluSphere(quadric, radius, slices, stacks)  # vykresleni kvadriky
    gluDeleteQuadric(quadric)                   # zruseni kvadriky


def set_depth_buffer(depthBufferEnabled):
    if depthBufferEnabled:
        glEnable(GL_DEPTH_TEST)
    else:
        glDisable(GL_DEPTH_TEST)


def clear_buffers(depthBufferEnabled):
    if depthBufferEnabled:
        # vymazani vsech bitovych rovin barvoveho bufferu i Z bufferu
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    else:
        # vymazani vsech bitovych rovin barvoveho bufferu
        glClear(GL_COLOR_BUFFER_BIT)


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


def set_modelview_matrix(rotation1, rotation2):
    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(rotation1, 1.0, 0.0, 0.0)    # rotace objektu
    glRotatef(rotation2, 0.0, 1.0, 0.0)


def set_shade_model(shadeModel):
    glShadeModel(shadeModel)


@window.event
def on_key_press(symbol, modifiers):
    global depthBufferEnabled
    global quadricDrawStyle
    global sphereSlices, sphereStacks
    global shadeModel

    if symbol == key.Z:
        depthBufferEnabled = not depthBufferEnabled
    if symbol == key.L:
        quadricDrawStyle = GLU_LINE
    if symbol == key.P:
        quadricDrawStyle = GLU_POINT
    if symbol == key.F:
        quadricDrawStyle = GLU_FILL
    if symbol == key.X:
        shadeModel = GL_FLAT
    if symbol == key.C:
        shadeModel = GL_SMOOTH
    if symbol == key.PAGEUP:
        sphereStacks += 1
    if symbol == key.PAGEDOWN:
        if sphereStacks > 1:
            sphereStacks -= 1
    if symbol == key.HOME:
        if sphereSlices > 1:
            sphereSlices -= 1
    if symbol == key.END:
        sphereSlices += 1


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

    if keys[key.LEFT]:
        r2 -= 2
    if keys[key.RIGHT]:
        r2 += 2
    if keys[key.UP]:
        r1 -= 2
    if keys[key.DOWN]:
        r1 += 2
    if keys[key.Q]:
        r3 -= 2
    if keys[key.W]:
        r3 += 2

    clear_buffers(depthBufferEnabled)
    set_shade_model(shadeModel)
    set_depth_buffer(depthBufferEnabled)
    set_projection_matrix(fov, nearPlane, farPlane)
    set_modelview_matrix(r1, r2)

    # nastaveni pozice svetla
    glPushMatrix()
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)
    glRotatef(r3, 1.0, 0.0, 0.0)
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)
    glPopMatrix()

    draw_quadric(quadricDrawStyle, sphereSlices, sphereStacks)


window.push_handlers(keys)
pyglet.app.run()

13. Druhý demonstrační příklad – vykreslení modelu koule, materiál má nastavenou ambientní a difúzní složku

Ve druhém příkladu je vykreslena koule, jejíž materiál má nastavenou ambientní a současně i difúzní složku. Výsledek je už plastičtější:

#!/usr/bin/env python

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

fov = 70.0                                 # zorny uhel (field of view)
nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0
r3 = 0.0

depthBufferEnabled = True                  # povoleni ci zakaz Z-bufferu

shadeModel = GL_SMOOTH                     # algoritmus vyplnovani plosek

quadricDrawStyle = GLU_FILL                # styl vykreslovani kvadriky
sphereSlices = 20                          # rozdeleni koule na 'poledniky'
sphereStacks = 20                          # rozdeleni koule na 'rovnobezky'


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

keys = key.KeyStateHandler()

# parametry, ktere ovlivnuji osvetleni
materialAmbient = [0.0, 0.0, 0.0, 1.0]     # ambientni slozka barvy materialu
materialDiffuse = [0.7, 0.7, 0.7, 0.0]     # difuzni slozka barvy materialu
lightPosition = [10.0, 10.0, 20.0, 0.0]    # pozice svetla


def float_array(list):
    ''' Prevod seznamu na pole prvku typ GLfloat '''
    return (GLfloat * len(list))(*list)


def set_material():
    materialAmbient_gl = float_array(materialAmbient)
    materialDiffuse_gl = float_array(materialDiffuse)

    # nastaveni ambientni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient_gl)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse_gl)


def set_light():
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)

    # nastaveni pozice svetla
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)

    glEnable(GL_LIGHTING)                  # globalni povoleni stinovani
    glEnable(GL_LIGHT0)                    # povoleni prvniho svetla


def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)       # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)       # nastaveni rezimu vykresleni modelu
    glPolygonMode(GL_BACK, GL_FILL)
    # zakaz odstranovani hran nebo sten podle jejich normal
    glDisable(GL_CULL_FACE)
    glDepthFunc(GL_LESS)                   # funkce pro testovani fragmentu

    glShadeModel(GL_SMOOTH)                # nastaveni stinovaciho rezimu
    glPointSize(3.0)                       # velikost vykreslovanych bodu

    set_material()
    set_light()


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


def draw_quadric(drawStyle, slices, stacks):
    radius = 8.0
    quadric = gluNewQuadric()                   # vytvoreni kvadriky
    gluQuadricDrawStyle(quadric, drawStyle)     # nastaveni vlastnosti kvadriky
    gluQuadricNormals(quadric, GLU_SMOOTH)      # smer generovanych normal
    gluSphere(quadric, radius, slices, stacks)  # vykresleni kvadriky
    gluDeleteQuadric(quadric)                   # zruseni kvadriky


def set_depth_buffer(depthBufferEnabled):
    if depthBufferEnabled:
        glEnable(GL_DEPTH_TEST)
    else:
        glDisable(GL_DEPTH_TEST)


def clear_buffers(depthBufferEnabled):
    if depthBufferEnabled:
        # vymazani vsech bitovych rovin barvoveho bufferu i Z bufferu
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    else:
        # vymazani vsech bitovych rovin barvoveho bufferu
        glClear(GL_COLOR_BUFFER_BIT)


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


def set_modelview_matrix(rotation1, rotation2):
    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(rotation1, 1.0, 0.0, 0.0)    # rotace objektu
    glRotatef(rotation2, 0.0, 1.0, 0.0)


def set_shade_model(shadeModel):
    glShadeModel(shadeModel)


@window.event
def on_key_press(symbol, modifiers):
    global depthBufferEnabled
    global quadricDrawStyle
    global sphereSlices, sphereStacks
    global shadeModel

    if symbol == key.Z:
        depthBufferEnabled = not depthBufferEnabled
    if symbol == key.L:
        quadricDrawStyle = GLU_LINE
    if symbol == key.P:
        quadricDrawStyle = GLU_POINT
    if symbol == key.F:
        quadricDrawStyle = GLU_FILL
    if symbol == key.X:
        shadeModel = GL_FLAT
    if symbol == key.C:
        shadeModel = GL_SMOOTH
    if symbol == key.PAGEUP:
        sphereStacks += 1
    if symbol == key.PAGEDOWN:
        if sphereStacks > 1:
            sphereStacks -= 1
    if symbol == key.HOME:
        if sphereSlices > 1:
            sphereSlices -= 1
    if symbol == key.END:
        sphereSlices += 1


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

    if keys[key.LEFT]:
        r2 -= 2
    if keys[key.RIGHT]:
        r2 += 2
    if keys[key.UP]:
        r1 -= 2
    if keys[key.DOWN]:
        r1 += 2
    if keys[key.Q]:
        r3 -= 2
    if keys[key.W]:
        r3 += 2

    clear_buffers(depthBufferEnabled)
    set_shade_model(shadeModel)
    set_depth_buffer(depthBufferEnabled)
    set_projection_matrix(fov, nearPlane, farPlane)
    set_modelview_matrix(r1, r2)

    # nastaveni pozice svetla
    glPushMatrix()
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)
    glRotatef(r3, 1.0, 0.0, 0.0)
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)
    glPopMatrix()

    draw_quadric(quadricDrawStyle, sphereSlices, sphereStacks)


window.push_handlers(keys)
pyglet.app.run()

14. Třetí demonstrační příklad – vykreslení modelu koule, materiál má nastavené i odlesky

Ve třetím příkladu je u modelu koule nastaven materiál s ambientní složkou, difúzní složkou a současně i s odlesky. Mělo by se tedy jednat o nejkvalitnější způsob zobrazení:

#!/usr/bin/env python

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

fov = 70.0                                 # zorny uhel (field of view)
nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0
r3 = 0.0

depthBufferEnabled = True                  # povoleni ci zakaz Z-bufferu

shadeModel = GL_SMOOTH                     # algoritmus vyplnovani plosek

quadricDrawStyle = GLU_FILL                # styl vykreslovani kvadriky
sphereSlices = 20                          # rozdeleni koule na 'poledniky'
sphereStacks = 20                          # rozdeleni koule na 'rovnobezky'


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

keys = key.KeyStateHandler()

# parametry, ktere ovlivnuji osvetleni
materialAmbient = [1.0, 0.0, 0.0, 1.0]     # ambientni slozka barvy materialu
materialDiffuse = [0.7, 0.7, 0.7, 0.0]     # difuzni slozka barvy materialu
materialSpecular = [1.0, 1.0, 1.0, 1.0]    # barva odlesku
materialShininess = [50.0]                 # faktor odlesku
lightPosition = [10.0, 10.0, 20.0, 0.0]    # pozice svetla


def float_array(list):
    ''' Prevod seznamu na pole prvku typ GLfloat '''
    return (GLfloat * len(list))(*list)


def set_material():
    materialAmbient_gl = float_array(materialAmbient)
    materialDiffuse_gl = float_array(materialDiffuse)
    materialSpecular_gl = float_array(materialSpecular)
    materialShininess_gl = float_array(materialShininess)

    # nastaveni ambientni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient_gl)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse_gl)

    # nastaveni barvy odlesku
    glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular_gl)

    # nastaveni faktoru odlesku
    glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess_gl)


def set_light():
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)

    # nastaveni pozice svetla
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)

    glEnable(GL_LIGHTING)                  # globalni povoleni stinovani
    glEnable(GL_LIGHT0)                    # povoleni prvniho svetla


def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)       # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)       # nastaveni rezimu vykresleni modelu
    glPolygonMode(GL_BACK, GL_FILL)
    # zakaz odstranovani hran nebo sten podle jejich normal
    glDisable(GL_CULL_FACE)
    glDepthFunc(GL_LESS)                   # funkce pro testovani fragmentu

    glShadeModel(GL_SMOOTH)                # nastaveni stinovaciho rezimu
    glPointSize(3.0)                       # velikost vykreslovanych bodu

    set_material()
    set_light()


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


def draw_quadric(drawStyle, slices, stacks):
    radius = 8.0
    quadric = gluNewQuadric()                   # vytvoreni kvadriky
    gluQuadricDrawStyle(quadric, drawStyle)     # nastaveni vlastnosti kvadriky
    gluQuadricNormals(quadric, GLU_SMOOTH)      # smer generovanych normal
    gluSphere(quadric, radius, slices, stacks)  # vykresleni kvadriky
    gluDeleteQuadric(quadric)                   # zruseni kvadriky


def set_depth_buffer(depthBufferEnabled):
    if depthBufferEnabled:
        glEnable(GL_DEPTH_TEST)
    else:
        glDisable(GL_DEPTH_TEST)


def clear_buffers(depthBufferEnabled):
    if depthBufferEnabled:
        # vymazani vsech bitovych rovin barvoveho bufferu i Z bufferu
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    else:
        # vymazani vsech bitovych rovin barvoveho bufferu
        glClear(GL_COLOR_BUFFER_BIT)


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


def set_modelview_matrix(rotation1, rotation2):
    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(rotation1, 1.0, 0.0, 0.0)    # rotace objektu
    glRotatef(rotation2, 0.0, 1.0, 0.0)


def set_shade_model(shadeModel):
    glShadeModel(shadeModel)


@window.event
def on_key_press(symbol, modifiers):
    global depthBufferEnabled
    global quadricDrawStyle
    global sphereSlices, sphereStacks
    global shadeModel

    if symbol == key.Z:
        depthBufferEnabled = not depthBufferEnabled
    if symbol == key.L:
        quadricDrawStyle = GLU_LINE
    if symbol == key.P:
        quadricDrawStyle = GLU_POINT
    if symbol == key.F:
        quadricDrawStyle = GLU_FILL
    if symbol == key.X:
        shadeModel = GL_FLAT
    if symbol == key.C:
        shadeModel = GL_SMOOTH
    if symbol == key.PAGEUP:
        sphereStacks += 1
    if symbol == key.PAGEDOWN:
        if sphereStacks > 1:
            sphereStacks -= 1
    if symbol == key.HOME:
        if sphereSlices > 1:
            sphereSlices -= 1
    if symbol == key.END:
        sphereSlices += 1


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

    if keys[key.LEFT]:
        r2 -= 2
    if keys[key.RIGHT]:
        r2 += 2
    if keys[key.UP]:
        r1 -= 2
    if keys[key.DOWN]:
        r1 += 2
    if keys[key.Q]:
        r3 -= 2
    if keys[key.W]:
        r3 += 2

    clear_buffers(depthBufferEnabled)
    set_shade_model(shadeModel)
    set_depth_buffer(depthBufferEnabled)
    set_projection_matrix(fov, nearPlane, farPlane)
    set_modelview_matrix(r1, r2)

    # nastaveni pozice svetla
    glPushMatrix()
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)
    glRotatef(r3, 1.0, 0.0, 0.0)
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)
    glPopMatrix()

    draw_quadric(quadricDrawStyle, sphereSlices, sphereStacks)


window.push_handlers(keys)
pyglet.app.run()

Obrázek 12: Koule s malým počtem plošek vykreslená při použití konstantního stínování

Obrázek 13: Ani zvýšení počtu plošek neznamená nutně kvalitní zobrazení scény. Lepší je sice snížit počet plošek, ale zapnout Gouraudovo stínování.

Obrázek 14: Wireframe (drátový) model i s odlesky.

15. Čtvrtý demonstrační příklad – osvětlovací model a těleso s plochami bez normálových vektorů

Ve čtvrtém demonstračním příkladu, jehož zdrojový kód vám pravděpodobně bude povědomý z předchozích částí tohoto seriálu, je ukázáno, co se stane ve chvíli, kdy se snažíme vykreslit osvětlený trojrozměrný model, ale nenastavíme přitom korektně normálové vektory jednotlivých stěn:

#!/usr/bin/env python

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

fov = 70.0                                 # zorny uhel (field of view)
nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0
r3 = 0.0

depthBufferEnabled = True                  # povoleni ci zakaz Z-bufferu

texturesEnabled = False                    # povoleni ci zakaz textur

shadeModel = GL_SMOOTH                     # algoritmus vyplnovani plosek


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

image_stream = open("gnome-globe.png", "rb")
image = pyglet.image.load('gnome-globe.png', file=image_stream)
texture = image.get_texture()

keys = key.KeyStateHandler()

# parametry, ktere ovlivnuji osvetleni
materialAmbient = [1.0, 0.0, 0.0, 1.0]     # ambientni slozka barvy materialu
materialDiffuse = [0.7, 0.7, 0.7, 0.0]     # difuzni slozka barvy materialu
materialSpecular = [1.0, 1.0, 1.0, 1.0]    # barva odlesku
materialShininess = [50.0]                 # faktor odlesku
lightPosition = [10.0, 10.0, 20.0, 0.0]    # pozice svetla


def float_array(list):
    ''' Prevod seznamu na pole prvku typ GLfloat '''
    return (GLfloat * len(list))(*list)


def set_material():
    materialAmbient_gl = float_array(materialAmbient)
    materialDiffuse_gl = float_array(materialDiffuse)
    materialSpecular_gl = float_array(materialSpecular)
    materialShininess_gl = float_array(materialShininess)

    # nastaveni ambientni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient_gl)
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse_gl)

    # nastaveni barvy odlesku
    glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular_gl)

    # nastaveni faktoru odlesku
    glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess_gl)


def set_light():
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)

    # nastaveni pozice svetla
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)

    glEnable(GL_LIGHTING)                  # globalni povoleni stinovani
    glEnable(GL_LIGHT0)                    # povoleni prvniho svetla


def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)       # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)       # nastaveni rezimu vykresleni modelu
    glPolygonMode(GL_BACK, GL_FILL)
    # zakaz odstranovani hran nebo sten podle jejich normal
    glDisable(GL_CULL_FACE)
    glDepthFunc(GL_LESS)                   # funkce pro testovani fragmentu

    glShadeModel(GL_SMOOTH)                # nastaveni stinovaciho rezimu
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glBindTexture(GL_TEXTURE_2D, texture.id)

    set_material()
    set_light()


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



def draw_walls():
    glBegin(GL_QUADS)                         # vykresleni otevrene krychle - sten domecku
    glColor3f(0.0, 0.0, 1.0)                  # modra barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 5.0, -5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f( 5.0, -5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)                  # zelena barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 5.0,  5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f( 5.0,  5.0, -5.0)

    glColor3f(1.0, 0.0, 0.0)                  # cervena barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f(-5.0,  5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f(-5.0,  5.0, -5.0)

    glColor3f(1.0, 1.0, 0.0)                  # zluta barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 5.0,  5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f( 5.0,  5.0, -5.0)
    glEnd()



def draw_roof():
    glBegin(GL_TRIANGLES)                      # vykresleni strechy domecku z trojuhelniku
    glColor3f(0.0, 1.0, 1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(1.0, 0.0, 1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(1.0, 1.0, 1.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(0.0, 0.0, 0.0)
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



def set_depth_buffer(depthBufferEnabled):
    if depthBufferEnabled:
        glEnable(GL_DEPTH_TEST)
    else:
        glDisable(GL_DEPTH_TEST)


def clear_buffers(depthBufferEnabled):
    if depthBufferEnabled:
        # vymazani vsech bitovych rovin barvoveho bufferu i Z bufferu
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    else:
        # vymazani vsech bitovych rovin barvoveho bufferu
        glClear(GL_COLOR_BUFFER_BIT)


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


def set_modelview_matrix(rotation1, rotation2):
    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(rotation1, 1.0, 0.0, 0.0)    # rotace objektu
    glRotatef(rotation2, 0.0, 1.0, 0.0)


def set_shade_model(shadeModel):
    glShadeModel(shadeModel)


@window.event
def on_key_press(symbol, modifiers):
    global depthBufferEnabled
    global shadeModel

    if symbol == key.Z:
        depthBufferEnabled = not depthBufferEnabled
    if symbol == key.X:
        shadeModel = GL_FLAT
    if symbol == key.C:
        shadeModel = GL_SMOOTH


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

    if keys[key.LEFT]:
        r2 -= 2
    if keys[key.RIGHT]:
        r2 += 2
    if keys[key.UP]:
        r1 -= 2
    if keys[key.DOWN]:
        r1 += 2
    if keys[key.Q]:
        r3 -= 2
    if keys[key.W]:
        r3 += 2

    clear_buffers(depthBufferEnabled)
    set_shade_model(shadeModel)
    set_depth_buffer(depthBufferEnabled)
    set_projection_matrix(fov, nearPlane, farPlane)
    set_modelview_matrix(r1, r2)

    # nastaveni pozice svetla
    glPushMatrix()
    lightPosition_gl = (GLfloat * len(lightPosition))(*lightPosition)
    glRotatef(r3, 1.0, 0.0, 0.0)
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition_gl)
    glPopMatrix()

    if texturesEnabled:
        glEnable(GL_TEXTURE_2D)
    else:
        glDisable(GL_TEXTURE_2D)

    draw_walls()
    draw_roof()



window.push_handlers(keys)
pyglet.app.run()

Obrázek 15: Screenshot získaný ze čtvrtého demonstračního příkladu.

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

Všechny 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 balíčku s Pygletem, viz též úvodní díl tohoto seriálu):

Příklad Odkaz
60_ambient_light.py https://github.com/tisnik/presentations/blob/master/pyglet/60_ambient_light.py
61_diffuse_light.py https://github.com/tisnik/presentations/blob/master/pyglet/61_diffuse_light.py
62_ambient_diffuse_specular.py https://github.com/tisnik/presentations/blob/master/pyglet/62_ambient_diffuse_specular.py
63_surface_without_normals.py https://github.com/tisnik/presentations/blob/master/pyglet/63_surface_without_normals.py

17. Odkazy na Internetu

  1. Pyglet: Module Hierarchy
    https://pythonhosted.org/pyglet/api/module-tree.html
  2. Learning Modern OpenGL
    https://www.codeproject.com/articles/771225/learning-modern-opengl
  3. OpenGL Utility Library
    https://en.wikipedia.org/wiki/OpenGL_Utility_Library
  4. GLU Specification
    https://www.opengl.org/registry/doc/glu1.3.pdf
  5. The Perlin noise math FAQ
    https://mzucker.github.io/html/perlin-noise-math-faq.html
  6. Perlin noise
    https://en.wikipedia.org/wiki/Perlin_noise
  7. Perlin Noise Generator (Python recipe)
    http://code.activestate.com/recipes/578470-perlin-noise-generator/
  8. Simplex noise demystified
    http://www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
  9. glTexEnv – příkaz OpenGL
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  10. glGetTexEnv – příkaz OpenGL
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glGetTexEnv.xml
  11. Pyglet Home Page
    https://bitbucket.org/pyglet/pyglet/wiki/Home
  12. Dokumentace k verzi 1.2
    https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/
  13. Dokumentace k verzi 1.2 ve formátu PDF
    https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/
  14. PyOpenGL
    http://pyopengl.sourceforge.net/
  15. The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
    https://www.in-ulm.de/~mascheck/various/shebang/
  16. Shebang (Unix)
    https://en.wikipedia.org/wiki/Shebang_%28Unix%29
  17. Domovská stránka systému LÖVE
    http://love2d.org/
  18. Simple DirectMedia Layer (home page)
    http://www.libsdl.org/
  19. Simple DirectMedia Layer (Wikipedia)
    https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
  20. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  21. Pyglet event loop
    http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html
  22. Decorators I: Introduction to Python Decorators
    http://www.artima.com/weblogs/viewpost.jsp?thread=240808
  23. 3D Programming in Python – Part 1
    https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/
  24. A very basic Pyglet tutorial
    http://www.natan.termitnjak.net/tutorials/pyglet_basic.html
  25. Alpha blending
    https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending