Ve druhé části seriálu o multimediální knihovně Pyglet si ukážeme další možnosti grafické knihovny OpenGL, jejíž funkce je možné z Pygletu velmi snadno volat. Prozatím se zaměříme na demonstrační příklady provádějící vykreslování scény v ploše (2D), příště si ukážeme i tvorbu plnohodnotných prostorových (3D) scén.

Obsah

1. Multimediální knihovna Pyglet: volání funkcí OpenGL

2. Způsob pojmenování funkcí v knihovně OpenGL

3. Vykreslení všech základních geometrických primitiv podporovaných knihovnou OpenGL

4. Nastavení vlastností bodů (GL_POINTS)

5. Nastavení vlastností úseček (GL_LINES)

6. Vykreslení trojúhelníků, nastavení vzorků vykreslování

7. Trojúhelníky

8. Pás trojúhelníků

9. Trs trojúhelníků

10. Čtyřúhelníky

11. Polygony

12. Má význam používat čtyřúhelníky a polygony na moderním GPU?

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

14. Odkazy na Internetu

1. Multimediální knihovna Pyglet: volání funkcí OpenGL

V úvodní části seriálu o multimediální knihovně Pyglet jsme si ukázali strukturu jednodušších aplikací a taktéž jsme si řekli, jak je možné Pyglet zkombinovat se známou grafickou knihovnou OpenGL. Připomeňme si, že se v Pygletu používají takzvané callback funkce volané ve chvíli, kdy dojde ke vzniku nějaké události, například při požadavku na překreslení okna atd. Nejjednodušší aplikace, která vykreslí RGB trojúhelník s využitím možností poskytovaných knihovnou OpenGL, může vypadat takto:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()
    glBegin(GL_TRIANGLES)
    glColor3f(1, 0, 0)
    glVertex2f(window.width/2, 0)
    glColor3f(0, 1, 0)
    glVertex2f(0, window.height)
    glColor3f(0, 0, 1)
    glVertex2f(window.width, window.height)
    glEnd()

pyglet.app.run()

Obrázek 1: Demonstrační příklad popsaný v úvodní části tohoto seriálu.

2. Způsob pojmenování funkcí v knihovně OpenGL

Většina funkcí, které jsou v knihovně OpenGL deklarovány, používá poměrně důmyslnou syntaxi, kdy je již ze jména funkce zřejmé, kolik parametrů je při volání funkce použito a jakého jsou typu. To souvisí s tím, že tato knihovna je primárně určena pro jazyk C (a v minulosti taktéž pro FORTRAN). V případě Pythonu budeme mít práci jednodušší, protože se typ navazuje na hodnoty a nikoli na proměnné. Typická OpenGL funkce pro nastavení barvy vrcholu vypadá takto:

glColor3f(0.0f, 1.0f, 0.0f)

Jméno každé funkce z knihovny OpenGL začíná prefixem (předponou) gl. Podobnou vlastnost mají i funkce z knihoven, které na OpenGL více či méně navazují. Například všechny funkce z knihovny GLU začínají prefixem glu a u knihovny GLUT je použit prefix glut (tato knihovna nás však nemusí při použití Pyglet zajímat):

<strong>gl</strong>Color3f(0.0f, 1.0f, 0.0f)

Za prefixem gl následuje tělo jména funkce, které většinou značí vytvářený předmět (například Vertex) nebo vlastnost, která se nastavuje mění (například Color). Tělo jména funkce začíná velkým písmenem, a pokud se skládá z více slov, jsou počáteční písmena slov velká (například ClearColor). Ve funkcích nejsou použita podtržítka a neexistují dvě jména funkcí, která by se lišila pouze ve velikosti písem, protože by to dělalo problémy u programovacích jazyků, které velikosti písma nerozlišují, například Pascalu či již zmíněného FORTRANu:

gl<strong>Color</strong>3f(0.0f, 1.0f, 0.0f)

Za tělem jména funkce většinou následuje číslo, které značí počet parametrů. Z uvedeného příkladu je tedy zřejmé, že funkce bude mít tři parametry. Pokud funkce nemá žádné parametry, žádné číslo se nepíše (tedy ani 0):

glColor<strong>3</strong>f(0.0f, 1.0f, 0.0f)

Na závěr je jedním či dvěma znaky uveden typ parametrů. U většiny funkcí mají všechny parametry stejný typ, takže typ lze specifikovat. Pokud má funkce více parametrů, z nichž každý je odlišného typu, tyto znaky se neuvádí. Některé funkce existující ve více variantách umožňují místo skupiny parametrů stejného typu předat pole. Potom se uvádí typ současně s písmenem v:

glColor3<strong>f</strong>(0.0f, 1.0f, 0.0f)

Základní typy parametrů, s nimiž si většinou vystačíme:

  • byte
  • sshort
  • integer
  • fload
  • double

3. Vykreslení všech základních geometrických primitiv podporovaných knihovnou OpenGL

Minule jsme si řekli, že v klasické variantě grafické knihovny OpenGL existuje celkem deset základních geometrických tvarů nazývaných grafická primitiva. Tyto tvary můžeme z hlediska jejich vlastností (a počtu rozměrů) rozdělit do tří skupin:

  • body: pouze jediný typ primitiva
  • úsečky: jednotlivé úsečky, řetězec úseček (polyčáry) a uzavřený tvar tvořený úsečkami
  • plošné útvary: trojúhelníky, pás trojúhelníků, trs trojúhelníků, čtyřúhelníky, pás čtyřúhelníků, polygony

Vykreslování všech grafických primitiv začíná funkcí glBegin(jméno_primitiva) a končí funkcí glEnd(). Mezi těmito funkcemi se specifikují jednotlivé vrcholy objektů některou variantou funkce glVertex*(). Taktéž lze specifikovat barvu vrcholů atd. Podívejme se nyní na demonstrační příklad, v němž jsou všechna podporovaná grafická primitiva vykreslena. Povšimněte si především způsobu použití „příkazových závorek“ glBegin() a glEnd():

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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

def draw_points():
    glColor3f(1.0, 1.0, 1.0);                # nastaveni barvy pro kresleni
    glBegin(GL_POINTS);                      # nyni zacneme vykreslovat body
    glVertex2i( 50,  50);
    glVertex2i(100,  50);
    glVertex2i(100, 100);
    glVertex2i( 50, 100);
    glEnd();

def draw_lines():
    glColor3f(1.0, 0.0, 1.0);
    glBegin(GL_LINES);                       # nyni zacneme vykreslovat usecky
    glVertex2i(150,  50);
    glVertex2i(200,  50);
    glVertex2i(200, 100);
    glVertex2i(150, 100);
    glEnd();

def draw_line_strip():
    glColor3f(0.0, 1.0, 1.0);
    glBegin(GL_LINE_STRIP);                  # nyni vykreslime polycaru
    glVertex2i(250,  50);
    glVertex2i(300,  50);
    glVertex2i(300, 100);
    glVertex2i(250, 100);
    glEnd();

def draw_line_loop():
    glColor3f(1.0, 1.0, 0.0);
    glBegin(GL_LINE_LOOP);                   # nyni vykreslime uzavrenou polycaru
    glVertex2i(350,  50);
    glVertex2i(400,  50);
    glVertex2i(400, 100);
    glVertex2i(350, 100);
    glEnd();

def draw_triangles():
    glColor3f(0.0, 0.0, 1.0);
    glBegin(GL_TRIANGLES);                   # vykresleni trojuhelniku
    glVertex2i( 50, 150);
    glVertex2i(100, 150);
    glVertex2i(100, 200);
    glVertex2i( 50, 200);
    glEnd();

def draw_triangle_strip():
    glColor3f(0.0, 1.0, 0.0);
    glBegin(GL_TRIANGLE_STRIP);              # vykresleni pruhu trojuhelniku
    glVertex2i(150, 150);
    glVertex2i(150, 200);
    glVertex2i(200, 200);
    glVertex2i(200, 150);
    glEnd();

def draw_triangle_fan():
    glColor3f(1.0, 0.0, 0.0);
    glBegin(GL_TRIANGLE_FAN);                # vykresleni trsu trojuhelniku
    glVertex2i(300, 150);
    glVertex2i(250, 160);
    glVertex2i(270, 190);
    glVertex2i(290, 200);
    glVertex2i(310, 200);
    glVertex2i(330, 190);
    glVertex2i(350, 160);
    glEnd();

def draw_quads():
    glColor3f(1.0, 0.5, 0.5);
    glBegin(GL_QUADS);                       # vykresleni ctyruhelniku
    glVertex2i( 50, 250);
    glVertex2i(100, 250);
    glVertex2i(100, 300);
    glVertex2i( 50, 300);
    glEnd();

def draw_quad_strip():
    glColor3f(0.5, 0.5, 1.0);
    glBegin(GL_QUAD_STRIP);                  # vykresleni pruhu ctyruhleniku
    glVertex2i(150, 250);
    glVertex2i(150, 300);
    glVertex2i(200, 240);
    glVertex2i(200, 310);
    glVertex2i(250, 260);
    glVertex2i(250, 290);
    glVertex2i(300, 250);
    glVertex2i(300, 300);
    glEnd();

def draw_polygon():
    glColor3f(0.5, 1.0, 0.5);
    glBegin(GL_POLYGON);                     # vykresleni konvexniho polygonu
    glVertex2i(350, 260);
    glVertex2i(370, 240);
    glVertex2i(390, 240);
    glVertex2i(410, 260);
    glVertex2i(410, 280);
    glVertex2i(390, 300);
    glVertex2i(370, 300);
    glVertex2i(350, 280);
    glEnd();

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    draw_points()

    draw_lines()
    draw_line_strip()
    draw_line_loop()

    draw_triangles()
    draw_triangle_strip()
    draw_triangle_fan()

    draw_quads()
    draw_quad_strip()

    draw_polygon()


pyglet.app.run()

Obrázek 2: Screenshot dnešního prvního demonstračního příkladu: všechna grafická primitiva.

4. Nastavení vlastností bodů (GL_POINTS)

Při vykreslování jednotlivých bodů je možné specifikovat především jejich barvu (což je ostatně logické), funkcí glPointSize() velikost bodu a taktéž přepínat mezi vykreslením bodů bez antialiasingu glDisable(GL_POINT_SMOOTH) a s antialiasingem glEnable(GL_POINT_SMOOTH). V tomto případě mohou být větší body vykresleny jako kolečka; se zakázaným antialiasingem se vždy bude jednat o čtverce. Ostatně podívejme se na příklad, jehož výstup se může na různých GPU odlišovat (nejlépe a taktéž nejpomaleji funguje SW vykreslování):

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    glDisable(GL_POINT_SMOOTH)                  # zakazani antialiasingu bodu
    glPointSize(1.0)                            # velikost vykreslovanych bodu je jeden pixel
    glBegin(GL_POINTS) 
    for i in xrange(0, 10):                     # vykresleni prvni rady bodu ruzne barvy
        step = i/10.0
        glColor3f(step, 0.5, 1.0-step)          # zmena barvy uvnitr prikazovych "zavorek" glBegin()/glEnd()
        glVertex2f(50.0+300.0*step, 50.0) 
    glEnd() 

    glDisable(GL_POINT_SMOOTH)                  # zakazani antialiasingu bodu
    for i in range(0, 10):                      # vykresleni druhe rady bodu ruzne velikosti a barvy
        step = i/10.0
        glColor3f(step, 0.5, 1.0-step)          # zmena barvy vne prikazovych "zavorek" glBegin()/glEnd()
        glPointSize(step*20.0+1.0)              # zmena velikosti vykreslovanych bodu
        glBegin(GL_POINTS) 
        glVertex2f(50.0+300.0*step, 100.0) 
        glEnd() 

    glEnable(GL_POINT_SMOOTH)                   # povoleni antialiasingu bodu
    for i in range(0, 10):                      # vykresleni treti rady bodu ruzne velikosti a barvy
        step = i/10.0
        glColor3f(step, 0.5, 1.0-step)          # zmena barvy vne prikazovych "zavorek" glBegin()/glEnd()
        glPointSize(step*20.0+1.0)              # zmena velikosti vykreslovanych bodu
        glBegin(GL_POINTS) 
        glVertex2f(50.0+300.0*step, 150.0) 
        glEnd() 


pyglet.app.run()

Obrázek 3: Screenshot dnešního druhého demonstračního příkladu. Povšimněte si, že body jsou na mém počítači vždy vykresleny jako čtverce, nikoli jako kolečka.

5. Nastavení vlastností úseček (GL_LINES)

Ve třetím demonstračním příkladu si ukážeme způsob nastavení vlastností úseček, což je grafické primitivum používané (nejenom) při zobrazování drátových modelů (wireframe). Úsečky jsou vykresleny s různou tloušťkou (glLineWidth), s povoleným či zakázaným antialiasingem a taktéž s použitím uživatelsky definovaných vzorků (glLineStipple). Každý vzorek je reprezentován šestnáctibitovou hodnotou, kde nulové bity představují „díry“ a jedničkové bity „čárky“ či „tečky“. Povšimněte si také, že když má každý vrchol úsečky jinou barvu, je při vykreslení provedena lineární transformace barev:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    patterns=[0xff00, 0xf0f0, 0xcccc, 0x5555, 0xfe10, 0x5e32]

    glDisable(GL_LINE_SMOOTH)                   # zakazani antialiasingu usecek
    glDisable(GL_LINE_STIPPLE)                  # zakazani maskovani pixelu na care

    glLineWidth(1.0)                            # tloustka usecky je jeden pixel
    glBegin(GL_LINES) 
    for i in xrange(0, 10):                     # vykresleni prvni rady usecek ruzne barvy
        step = i/10.0
        glColor3f(step, 0.0, 1.0-step)          # zmena barvy uvnitr prikazovych "zavorek" glBegin()/glEnd()
        glVertex2f(50.0+300.0*step, 20.0) 
        glColor3f(step, 1.0, 1.0-step) 
        glVertex2f(100.0+300.0*step, 70.0) 
    glEnd() 

    for i in xrange(0, 10):                     # vykresleni druhe rady usecek ruzne tloustky
        step = i/10.0
        glLineWidth(step*10.0+0.1)              # zmena tloustky usecky
        glBegin(GL_LINES) 
        glColor3f(step, 0.0, 1.0-step)          # zmena barvy uvnitr prikazovych "zavorek" glBegin()/glEnd()
        glVertex2f(50.0+300.0*step, 90.0) 
        glColor3f(step, 1.0, 1.0-step) 
        glVertex2f(100.0+300.0*step, 140.0) 
        glEnd() 

    glEnable(GL_LINE_SMOOTH)                    # povoleni antialiasingu usecek
    for i in xrange(0, 10):                     # vykresleni treti rady usecek ruzne tloustky
        step= i/10.0
        glLineWidth(step*10.0+0.1)              # zmena tloustky usecky
        glBegin(GL_LINES) 
        glColor3f(step, 0.0, 1.0-step)          # zmena barvy uvnitr prikazovych "zavorek" glBegin()/glEnd()
        glVertex2f(50.0+300.0*step, 160.0) 
        glColor3f(step, 1.0, 1.0-step) 
        glVertex2f(100.0+300.0*step, 210.0) 
        glEnd() 

    glDisable(GL_LINE_SMOOTH)                   # zakazani antialiasingu usecek
    glEnable(GL_LINE_STIPPLE)                   # povoleni maskovani pixelu na care
    glLineWidth(1.0)                            # tloustka usecky je jeden pixel
    glColor3f(1.0, 1.0, 1.0)                    # zmena barvy vne prikazovych "zavorek" glBegin()/glEnd()

    for i in xrange(0, 6):
        glLineStipple(2, patterns[i])           # nastaveni masky pri kresleni usecek
        glBegin(GL_LINES) 
        glVertex2i(50, 250+i*20)                # vykresleni usecky
        glVertex2i(350, 250+i*20) 
        glEnd() 


pyglet.app.run()

Obrázek 4: Screenshot dnešního třetího demonstračního příkladu: různé varianty úseček.

6. Vykreslení trojúhelníků, nastavení vzorků vykreslování

Způsob vykreslování izolovaných trojúhelníků jsme si již ukázali minule, ovšem u trojúhelníků je možné nastavovat i další vlastnosti. Zejména se to týká nastavení vykreslovacího vzorku, přičemž vzorek je reprezentován bitovou mapou o velikosti 32×32 pixelů, což znamená, že celá mapa je uložena ve 128 bajtech (32×32/8). V Pythonu můžeme vzorek reprezentovat seznamem, který se posléze převede na pole bajtů. Seznam 128 bajtových hodnot může vypadat následovně:

pattern1=[
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
    0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
    0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
    0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
    0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
    0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
    0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
    0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
    0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
    0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
    0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
    0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
    0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
    0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
    0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08]

Převod na pole bajtů zajistí následující výraz:

pattern1_gl = (GLubyte * len(pattern1))(*pattern1)

Teprve takto vytvořené pole je možné předat do funkce glPolygonStipple():

glPolygonStipple(pattern1_gl)

7. Trojúhelníky

Podívejme se nyní na demonstrační příklad, v němž se vykreslí jednotlivé trojúhelníky představované grafickým primitivem GL_TRIANGLES. Každý trojúhelník je vykreslen odlišným stylem:

Příkaz Vliv na vykreslování
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) vykreslí se vyplněné trojúhelníky
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) vykreslí se pouze hrany trojúhelníků
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT) vykreslí se pouze vrcholy trojúhelníků
glPolygonStipple(pattern) pro vyplnění se použije předaný vzorek 32×32 pixelů

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

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


def draw_triangle(x, y):
    glBegin(GL_TRIANGLES) 
    glColor3f(1.0, 0.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x, y) 
    glColor3f(0.0, 1.0, 0.0) 
    glVertex2i(x+100, y) 
    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x+50, y+80) 
    glEnd() 

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    pattern1=[                               # prvni vyplnovy vzorek
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
        0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
        0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
        0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
        0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
        0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
        0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
        0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
        0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
        0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
        0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08]

    pattern1_gl = (GLubyte * len(pattern1))(*pattern1)

    pattern2=[                               # druhy vyplnovy vzorek
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa]

    pattern2_gl = (GLubyte * len(pattern2))(*pattern2)

    glClearColor(0.0, 0.0, 0.0, 0.0)            # nastaveni mazaci barvy na cernou
    glClear(GL_COLOR_BUFFER_BIT)                # vymazani bitovych rovin barvoveho bufferu

    glPointSize(5.0)                            # velikost bodu je rovna peti pixelum
    glLineWidth(2.0)                            # tloustka usecek je rovna dvema pixelum
    glEnable(GL_POINT_SMOOTH)                   # povoleni antialiasingu bodu
    glEnable(GL_LINE_SMOOTH)                    # povoleni antialiasingu usecek
    glDisable(GL_POLYGON_STIPPLE)               # zakazat vzorek
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)   # vykreslovani vyplnenych trojuhelniku
    draw_triangle(50, 50) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)   # vykreslovani pouze hran trojuhelniku
    draw_triangle(180, 50) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_POINT)  # vykreslovani pouze vrcholu trojuhelniku
    draw_triangle(310, 50) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)   # vykreslovani vyplnenych trojuhelniku
    glEnable(GL_POLYGON_STIPPLE)                # povolit vzorek
    glPolygonStipple(pattern1_gl)               # zadat prvni vzorek
    draw_triangle(110, 190) 
    glPolygonStipple(pattern2_gl)               # zadat druhy vzorek
    draw_triangle(240, 190) 


pyglet.app.run()

Obrázek 5: Screenshot dnešního čtvrtého demonstračního příkladu: různé varianty trojúhelníků.

8. Pás trojúhelníků

Často používané primitivum představuje souvislý pás složený z trojúhelníků. Použít ho lze například při vykreslování stěn složitějších těles. Toto primitivum je univerzálnější než trs trojúhelníků. Zadávání začíná příkazem glBegin(GL_TRIANGLE_STRIP), po němž následují jednotlivé vrcholy. První tři vrcholy definují první trojúhelník. Každý další vrchol definuje další trojúhelník, jenž má s předchozím trojúhelníkem společnou hranu. Použitím tohoto primitiva lze ušetřit až dvě třetiny volání funkce glVertex*():

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


def draw_triangle_strip(x, y):
    glBegin(GL_TRIANGLE_STRIP) 
    glColor3f(1.0, 0.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x, y) 
    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x+50, y+80) 
    glColor3f(0.0, 1.0, 0.0) 
    glVertex2i(x+100, y) 
    glColor3f(1.0, 1.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x+150, y+80) 
    glColor3f(0.0, 1.0, 1.0) 
    glVertex2i(x+200, y) 
    glColor3f(1.0, 0.0, 1.0) 
    glVertex2i(x+250, y+80) 
    glColor3f(1.0, 1.0, 1.0) 
    glVertex2i(x+300, y) 
    glEnd() 

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    glClearColor(0.0, 0.0, 0.0, 0.0)            # nastaveni mazaci barvy na cernou
    glClear(GL_COLOR_BUFFER_BIT)                # vymazani bitovych rovin barvoveho bufferu

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    draw_triangle_strip(50, 50) 

    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    draw_triangle_strip(50, 200) 


pyglet.app.run()

Obrázek 6: Screenshot dnešního pátého demonstračního příkladu: pás trojúhelníků.

9. Trs trojúhelníků

Toto primitivum je určeno pro snížení datového toku při zadávání vrcholů trojúhelníků. Používá se například při vykreslování vrchlíků koule. Zadávání začíná příkazem glBegin(GL_TRIANGLE_FAN), po němž následují jednotlivé vrcholy. První tři vrcholy definují první trojúhelník. Každý další vrchol definuje další trojúhelník, protože ostatní dva vrcholy trojúhelníku jsou shodné vždy s prvním zadaným vrcholem a s předposledním vrcholem. Tvoří se tak jakýsi deštník, v němž mají všechny trojúhelníky společný jeden vrchol. Použitím tohoto primitiva lze ušetřit až dvě třetiny volání funkce glVertex*():

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


def draw_triangle_fan(x, y):
    glBegin(GL_TRIANGLE_FAN) 
    glColor3f(1.0, 0.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x+175, y) 
    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x+50, y+80) 
    glColor3f(0.0, 1.0, 0.0) 
    glVertex2i(x+100, y+80) 
    glColor3f(1.0, 1.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x+150, y+80) 
    glColor3f(0.0, 1.0, 1.0) 
    glVertex2i(x+200, y+80) 
    glColor3f(1.0, 0.0, 1.0) 
    glVertex2i(x+250, y+80) 
    glColor3f(1.0, 1.0, 1.0) 
    glVertex2i(x+300, y+80) 
    glEnd() 

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    glClearColor(0.0, 0.0, 0.0, 0.0)            # nastaveni mazaci barvy na cernou
    glClear(GL_COLOR_BUFFER_BIT)                # vymazani bitovych rovin barvoveho bufferu

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    draw_triangle_fan(50, 50) 

    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    draw_triangle_fan(50, 200) 


pyglet.app.run()

Obrázek 7: Screenshot dnešního pátého demonstračního příkladu: trs trojúhelníků.

10. Čtyřúhelníky

Toto primitivum umožňuje zadávat rovinné konvexní čtyřúhelníky. Zadávání začíná příkazem glBegin(GL_QUADS), po němž následují jednotlivé vrcholy. Použití je podobné jako u trojúhelníků, ale musíme zaručit, že vrcholy čtyřúhelníku budou ležet v jedné rovině a čtyřúhelník bude konvexní (to je u trojúhelníku zaručeno vždy). Pokud tyto podmínky nebudou splněny, nemusí být (a pravděpodobně ani nebude) vykreslení korektní, protože interpolátory v grafickém akcelerátoru většinou nedovedou detekovat hranici, kde se má vykreslování přerušit:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


def draw_quad(x, y):
    glBegin(GL_QUADS) 
    glColor3f(1.0, 0.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x, y) 
    glColor3f(0.0, 1.0, 0.0) 
    glVertex2i(x+100, y) 
    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x+100, y+100) 
    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x, y+100) 
    glEnd() 

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    pattern1=[                               # prvni vyplnovy vzorek
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
        0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
        0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
        0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
        0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
        0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
        0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
        0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
        0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
        0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
        0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08]

    pattern1_gl = (GLubyte * len(pattern1))(*pattern1)

    pattern2=[                               # druhy vyplnovy vzorek
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa]

    pattern2_gl = (GLubyte * len(pattern2))(*pattern2)

    glClearColor(0.0, 0.0, 0.0, 0.0)            # nastaveni mazaci barvy na cernou
    glClear(GL_COLOR_BUFFER_BIT)                # vymazani bitovych rovin barvoveho bufferu

    glPointSize(5.0)                            # velikost bodu je rovna peti pixelum
    glLineWidth(2.0)                            # tloustka usecek je rovna dvema pixelum
    glEnable(GL_POINT_SMOOTH)                   # povoleni antialiasingu bodu
    glEnable(GL_LINE_SMOOTH)                    # povoleni antialiasingu usecek
    glDisable(GL_POLYGON_STIPPLE)               # zakazat vzorek
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)   # vykreslovani vyplnenych ctyruhelniku
    draw_quad(50, 50) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)   # vykreslovani pouze hran ctyruhelniku
    draw_quad(180, 50) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_POINT)  # vykreslovani pouze vrcholu ctyruhelniku
    draw_quad(310, 50) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)   # vykreslovani vyplnenych ctyruhelniku
    glEnable(GL_POLYGON_STIPPLE)                # povolit vzorek
    glPolygonStipple(pattern1_gl)               # zadat prvni vzorek
    draw_quad(110, 190) 
    glPolygonStipple(pattern2_gl)               # zadat druhy vzorek
    draw_quad(240, 190) 


pyglet.app.run()

Obrázek 8: Screenshot dnešního šestého demonstračního příkladu.

11. Polygony

Jedná se o nejsložitější primitivum, pomocí něhož lze kreslit polygony zadané větším počtem vrcholů. Musíme však zaručit, že všechny vrcholy budou ležet v jedné rovině a výsledný polygon bude konvexní. V mnoha případech je tato podmínka těžko splnitelná, a proto se polygon rozděluje na jednotlivé trojúhelníky (tesselace). Zadávání polygonu začíná příkazem glBegin(GL_POLYGON), po němž následují souřadnice jednotlivých vrcholů:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *

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


def draw_polygon(x, y):
    glBegin(GL_POLYGON) 

    glColor3f(1.0, 0.0, 0.0)                 # kazdy vertex bude vykresleny jinou barvou
    glVertex2i(x, y) 

    glColor3f(0.0, 1.0, 0.0) 
    glVertex2i(x+50, y+85) 

    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x+150, y+85) 

    glColor3f(0.0, 0.0, 1.0) 
    glVertex2i(x+200, y) 
 
    glColor3f(1.0, 0.0, 1.0) 
    glVertex2i(x+150, y-85) 
 
    glColor3f(1.0, 0.0, 1.0) 
    glVertex2i(x+50, y-85) 

    glEnd() 

@window.event
def on_draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glLoadIdentity()

    pattern1=[                               # prvni vyplnovy vzorek
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
        0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
        0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
        0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
        0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
        0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
        0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
        0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
        0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
        0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
        0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08]

    pattern1_gl = (GLubyte * len(pattern1))(*pattern1)

    pattern2=[                               # druhy vyplnovy vzorek
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa,
        0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xaa]

    pattern2_gl = (GLubyte * len(pattern2))(*pattern2)

    glClearColor(0.0, 0.0, 0.0, 0.0)            # nastaveni mazaci barvy na cernou
    glClear(GL_COLOR_BUFFER_BIT)                # vymazani bitovych rovin barvoveho bufferu

    glPointSize(5.0)                            # velikost bodu je rovna peti pixelum
    glLineWidth(2.0)                            # tloustka usecek je rovna dvema pixelum
    glEnable(GL_POINT_SMOOTH)                   # povoleni antialiasingu bodu
    glEnable(GL_LINE_SMOOTH)                    # povoleni antialiasingu usecek
    glDisable(GL_POLYGON_STIPPLE)               # zakazat vzorek
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)   # vykreslovani vyplnenych ctyruhelniku
    draw_polygon(10, 110) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)   # vykreslovani pouze hran ctyruhelniku
    draw_polygon(220, 110) 
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)   # vykreslovani vyplnenych ctyruhelniku
    glEnable(GL_POLYGON_STIPPLE)                # povolit vzorek
    glPolygonStipple(pattern1_gl)               # zadat prvni vzorek
    draw_polygon(10, 320) 
    glPolygonStipple(pattern2_gl)               # zadat druhy vzorek
    draw_polygon(220, 320) 


pyglet.app.run()

Obrázek 9: Screenshot dnešního sedmého demonstračního příkladu.

12. Má význam používat čtyřúhelníky a polygony na moderním GPU?

Na moderních GPU je použití čtyřúhelníků a polygonů poněkud problematické, protože tyto grafické akcelerátory jsou většinou upraveny pro rychlé vykreslování trojúhelníků se všemi výhodami a nevýhodami, které toto řešení přináší (trojúhelník je vždy konvexní, všechny jeho vrcholy leží v jedné rovině atd.). Standardní OpenGL sice stále podporuje i práci se čtyřúhelníky a obecnými konvexními polygony, ovšem většinou na úkor rychlosti. Tato grafická primitiva totiž jsou ještě před posláním do grafického akcelerátoru rozdělena (teselována) na trojúhelníky, což je samozřejmě časově náročnější, než pouhý přesun vrcholů trojúhelníků do GPU. Z tohoto důvodu je lepší, aby byly všechny vykreslované 3D modely již předem převedeny na trojúhelníkové sítě, samozřejmě pokud je to možné (většina modelovacích SW tento převod provede automaticky).

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

Všech osm dnes popsaných demonstračních příkladů bylo uloženo 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 OpenGL a GLU (což se většinou provede automaticky v rámci instalace Pygletu):

Příklad Odkaz
09_primitives.py https://github.com/tisnik/presentations/blob/master/pyglet/09_primitives.py
10_points.py https://github.com/tisnik/presentations/blob/master/pyglet/10_points.py
11_lines.py https://github.com/tisnik/presentations/blob/master/pyglet/11_lines.py
12_triangles.py https://github.com/tisnik/presentations/blob/master/pyglet/12_triangles.py
13_triangle_strip.py https://github.com/tisnik/presentations/blob/master/pyglet/13_triangle_strip.py
14_triangle_fan.py https://github.com/tisnik/presentations/blob/master/pyglet/14_triangle_fan.py
15_quads.py https://github.com/tisnik/presentations/blob/master/pyglet/15_quads.py
16_polygons.py https://github.com/tisnik/presentations/blob/master/pyglet/16_polygons.py

14. 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