Dnešní díl seriálu o multimediální knihovně Pyglet je věnovaný technice mipmappingu, která je často používaná pro zamezení vzniku vizuálních chyb vznikajících v dynamicky se měnících scénách a animacích popř. při použití textur se složitějším vzorkem.

Obsah

1. Multimediální knihovna Pyglet: použití mipmappingu při texturování

2. Texturování bez použití mipmappingu

3. Podvzorkování textury a vznik moaré

4. Vytvoření rastrového obrázku s texturou šachovnice

5. První demonstrační příklad – zobrazení šachovnice v 3D scéně bez použití mipmappingu

6. Použití mipmappingu

7. Podpora mipmappingu v grafické knihovně OpenGL

8. Automatické generování mipmap v nadstavbové knihovně GLU

9. Automatické vygenerování mipmapy v knihovně Pyglet

10. Nastavení filtrů při použití mipmappingu

11. Druhý demonstrační příklad – použití mipmappingu v praxi

12. Ukázka různých nastavení mipmappingu

13. Úplný zdrojový kód druhého demonstračního příkladu

14. Funkce knihovny OpenGL a pomocné knihovny GLU, které byly použity ve zdrojových kódech

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

16. Odkazy na Internetu

1. Multimediální knihovna Pyglet: použití mipmappingu při texturování

Mipmapping představuje v oblasti 3D grafiky poměrně dobře známou techniku, která je v praxi poměrně často používaná pro odstranění či alespoň pro zmenšení některých vizuálních chyb (resp. přesněji řečeno nežádoucích rozdílů mezi jednotlivými snímky) vzniklých při pohybu těles s nanesenou texturou v trojrozměrné scéně nebo při pohybu celé scény (tj. při změně orientace nebo pozice pozorovatele/kamery). Obraz těles s nanesenou texturou se na obrazovce při pohybu zmenšuje, zvětšuje či jinak deformuje, čímž pochopitelně také dochází k nutnosti zvětšování a zmenšování textury při nanášení texelů (rastrových elementů textury) na zobrazované pixely.

2. Texturování bez použití mipmappingu

Nejprve se podívejme na nám již známé techniky řešení tohoto problému. Již minule jsme si řekli, že při zvětšování a zmenšování textury můžeme pro zamezení některých vizuálních chyb využít filtraci. Nejrychlejšího zobrazení scény však dosáhneme vypnutím filtrace a výběrem vždy pouze jednoho nejbližšího texelu z textury pro každý vykreslovaný pixel:

glTexParameteri(GL_TEXTURE_2D,
                GL_TEXTURE_MAG_FILTER,
                GL_NEAREST)

glTexParameteri(GL_TEXTURE_2D,
                GL_TEXTURE_MIN_FILTER,
                GL_NEAREST)

Obrázek 1: Přiblížení textury při použití filtrů GL_NEAREST, tj. barva pixelů se vypočte z nejbližšího texelu.

Předchozí způsob je sice nejrychlejší, současně však nikterak neřeší vznik artefaktů, například takzvaného moaré, způsobeného podvzorkováním obrazu. V knihovně OpenGL a tím pádem i v Pygletu lze využít několika způsobů, které zamezují vznikům těchto artefaktů při zvětšování a zejména zmenšování textur. Pokud se vykresluje statická scéna, je možné použít pouze vhodné filtrace, pracující na principu výběru několika sousedních texelů a jejich bilineární interpolace při výpočtu barvy vykreslovaného pixelu:

glTexParameteri(GL_TEXTURE_2D,
                GL_TEXTURE_MAG_FILTER,
                GL_LINEAR)

glTexParameteri(GL_TEXTURE_2D,
                GL_TEXTURE_MIN_FILTER,
                GL_LINEAR);

Obrázek 2: Přiblížení textury při použití filtrů GL_LINEAR, tj. použitím bilineární interpolace.

3. Podvzorkování textury a vznik moaré

Nevýhodou předchozího způsobu je určité zpomalení vykreslování, zejména díky faktu, že se musí provádět více přístupů do texturovací paměti, která v dnešní době stále představuje úzké hrdlo grafických akcelerátorů. V některých případech, zejména při požadavku na vyšší kvalitu obrázků, je možné použít antialiasing, který se v OpenGL provádí tak, že se scéna vykreslí ve vyšším rozlišení, než je rozlišení obrazovky, a poté se provede snížení rozlišení pomocí filtrace s vhodným jádrem filtru (kernelem). Rozdíl mezi scénou vykreslenou bez antialiasingu a s antialiasingem je patrný z dalších dvou screenshotů. Moderní grafické akcelerátory většinou umožňují provádět antialiasing celé scény. Tato technika je nazývaná FSAA neboli Full Scene AntiAliasing. Nevýhodou je samozřejmě nižší rychlost vykreslování.

Obrázek 3: Trojrozměrná scéna vykreslená bez povoleného antialiasingu.

Obrázek 4: Stejná scéna jako na předchozím obrázku vykreslená se zapnutým antialiasingem.

Poznámka: antialiasing implementovaný s použitím takzvaného supersamplingu, se velmi často používá u raytracerů.

Při animacích však kromě moaré vznikají i další artefakty, z nichž nejviditelnější je „problikávání“ pixelů na příliš zmenšených texturách, tj. například texturách nanesených na vzdálených objektech. V těchto případech už běžná filtrace (pracující pouze pro těsné okolí nanášených texelů) není příliš prospěšná, protože s každým pohybem otexturovaného tělesa se může v rovině textury provést posun až o několik desítek texelů, čímž dochází k prudké změně vykreslované barvy.

4. Vytvoření rastrového obrázku s texturou šachovnice

V dnešních dvou demonstračních příkladech budeme používat texturu šachovnice. Abyste si mohli sami vyzkoušet, jak se bude zobrazení takové textury lišit pro různé velikosti políček na šachovnici, bude textura vytvořená následujícím jednoduchým skriptem. Povšimněte si, že rozměry šachovnice jsou 256×256 pixelů, což odpovídá – jak uvidíme dále – požadavkům na rozměry textury, z nichž se bude vytvářet mipmapa:

#!/usr/bin/env python

# Vytvoreni textury sachovnice

from PIL import Image

IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256

SQUARE_SIZE = 16

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT))

for y in range(0, IMAGE_HEIGHT):
    for x in range(0, IMAGE_WIDTH):
        color = BLACK
        xs = x / SQUARE_SIZE
        ys = y / SQUARE_SIZE
        if (xs + ys) % 2 == 0:
            color = WHITE

        image.putpixel((x, y), color)

image.save("checker.png")

Obrázek 5: Textura vytvořená výše popsaným skriptem.

5. První demonstrační příklad – zobrazení šachovnice v 3D scéně bez použití mipmappingu

Použití textury šachovnice vytvořené předchozím skriptem je vlastně velmi snadné, protože jen postačuje nepatrně upravit příklady, s nimiž jsme se již seznámili v předchozích částech tohoto seriálu. V 3D scéně bude zobrazeno jen jediné těleso – čtvercová plocha pokrytá šachovnicovou texturou:

def draw_floor():
    glBegin(GL_QUADS)                         # vykresleni podlahy
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-50.0, -5.0, -50.0)
    glTexCoord2f(5.0, 0.0)
    glVertex3f(-50.0, -5.0,  50.0)
    glTexCoord2f(5.0, 5.0)
    glVertex3f(50.0, -5.0,  50.0)
    glTexCoord2f(0.0, 5.0)
    glVertex3f(50.0, -5.0, -50.0)
    glEnd()

Při zobrazení bude možné si zvolit filtry použité při přiblížení či naopak vzdálení textury. Řízení texturovací jednotky provádí tento kód:

def set_filters(minFilterEnabled, magFilterEnabled):
    minFilter = GL_LINEAR if minFilterEnabled else GL_NEAREST
    magFilter = GL_LINEAR if magFilterEnabled else GL_NEAREST
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter)

Nastavení filtrů pak přibližně následující úryvek kódu, který umožňuje, aby se klávesami I a A filtry zapnuly či vypnuly (každý zvlášť):

minFilterEnabled = True                    # filtry pouzite pri texturovani
magFilterEnabled = True

if keys[key.I]:
    minFilterEnabled = not minFilterEnabled
if keys[key.A]:
    magFilterEnabled = not magFilterEnabled

Úplný zdrojový kód dnešního první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

depthBufferEnabled = False                 # povoleni ci zakaz Z-bufferu
texturesEnabled = True                     # povoleni ci zakaz textur

minFilterEnabled = True                    # filtry pouzite pri texturovani
magFilterEnabled = True


def create_window():
    return pyglet.window.Window(width=500,
                                height=500,
                                caption="Pyglet library")


window = create_window()


def load_texture(filename):
    image_stream = open(filename, 'rb')
    image = pyglet.image.load(filename, file=image_stream)
    return image.get_texture()


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)
    glDisable(GL_CULL_FACE)                # zadne hrany ani steny se nebudou odstranovat
    glDepthFunc(GL_LESS)                   # funkce pro testovani fragmentu

    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)

    glEnable(GL_TEXTURE_2D)                # povoleni texturovani


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


def draw_floor():
    glBegin(GL_QUADS)                         # vykresleni podlahy
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-50.0, -5.0, -50.0)
    glTexCoord2f(5.0, 0.0)
    glVertex3f(-50.0, -5.0,  50.0)
    glTexCoord2f(5.0, 5.0)
    glVertex3f(50.0, -5.0,  50.0)
    glTexCoord2f(0.0, 5.0)
    glVertex3f(50.0, -5.0, -50.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 i Z/W bufferu
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    else:                    # vymazani vsech bitovych rovin barvoveho bufferu
        glClear(GL_COLOR_BUFFER_BIT)


def set_filters(minFilterEnabled, magFilterEnabled):
    minFilter = GL_LINEAR if minFilterEnabled else GL_NEAREST
    magFilter = GL_LINEAR if magFilterEnabled else GL_NEAREST
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter)


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


@window.event
def on_draw():
    global depthBufferEnabled
    global texturesEnabled
    global minFilterEnabled
    global magFilterEnabled

    if keys[key.Z]:
        depthBufferEnabled = not depthBufferEnabled
    if keys[key.T]:
        texturesEnabled = not texturesEnabled
    if keys[key.I]:
        minFilterEnabled = not minFilterEnabled
    if keys[key.A]:
        magFilterEnabled = not magFilterEnabled

    clear_buffers(depthBufferEnabled)
    set_depth_buffer(depthBufferEnabled)
    set_filters(minFilterEnabled, magFilterEnabled)
    set_textures(texturesEnabled)

    # zacatek kodu modifikujiciho projekcni matici
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()                       # vymazani projekcni matice
    gluPerspective(fov, 1.0, nearPlane, farPlane)

    # zacatek kodu modifikujiciho modelview matici
    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)
    glTranslatef(0, 0, -40)                # posun objektu

    draw_floor()                           # vykresleni objektu - podlahy


texture = load_texture('checker.png')
keys = key.KeyStateHandler()
window.push_handlers(keys)

pyglet.app.run()

Obrázek 6: Screenshot prvního příkladu: při přiblížení textury se barva pixelu odvozuje z nejbližšího texelu.

Obrázek 7: Screenshot prvního příkladu: při přiblížení textury je použita bilineární interpolace.

6. Použití mipmappingu

Pro zamezení problikávání se používají textury uložené ve více rozlišeních (tj. úrovních detailu). Při vykreslování otexturovaného povrchu se nejdříve zjistí relativní velikost povrchu vůči celé textuře a poté se vybere vhodné rozlišení textury, která se posléze na vykreslovanou stěnu nanese. Tento postup má nevýhodu v tom, že při postupném zmenšování objektu by docházelo ke skokové změně textury (přešlo by se k menšímu rozlišení textury). Proto se zavádí další úroveň interpolace, kdy se vybere nejbližší větší a nejbližší menší textura a barva pixelů se vypočte interpolací mezi těmito dvěma texturami.

Nyní tedy zbývá pouze volba vhodného rozlišení textur. Z hlediska implementace interpolátoru na grafickém akcelerátoru je nejvýhodnější, aby se rozlišení textury v horizontálním i vertikálním směru snižovalo vždy na polovinu. Počet texelů je v každé následující textuře zmenšen na čtvrtinu až do dosažení textury o velikosti 1×1 pixel. Princip tvorby mipmapy je ukázán na dalším obrázku:

Obrázek 8: Princip tvorby mipmapy.

Obrázek 9: Ukázka textur vhodných pro tvorbu mipmapy

7. Podpora mipmappingu v grafické knihovně OpenGL

V grafické knihovně OpenGL jsou mipmapy samozřejmě také podporovány, protože se jedná o velmi často používanou renderovací (vykreslovací) pomůcku. Pro každé nastavované rozlišení textury je zapotřebí zavolat již dříve popsanou funkci, jejíž céčková hlavička vypadá následovně:

void glTexImage2D(
    GLenum  target,
    GLint   level,
    GLint   components,
    GLsizei width,
    GLsizei height,
    GLint   border,
    GLenum  format,
    GLenum  type,
    const GLvoid *pixels
); 

kde se do parametru nazvaného level zadává úroveň textury v hierarchii. Nulou specifikujeme texturu se základním (tj. nejvyšším) rozlišením, jedničkou texturu s rozlišením polovičním atd. V texturovací paměti grafického akcelerátoru se vytvoří takzvaná mipmapa, tj. textura, ve které jsou uloženy všechny velikosti textur rozdělených na jednotlivé barevné složky RGB. Ukázka mipmapy je zobrazena na následujícím obrázku. Z tohoto obrázku je patrné, že se jedná o hierarchickou strukturu, ve které se dají jednoduše adresovat korespondující pixely v různých rozlišeních. Při adresaci přitom vystačíme pouze s aditivními operacemi a bitovými posuny, což jsou pro grafický akcelerátor ideální operace.

Obrázek 10: Ukázka interní reprezentace mipmapy uložené v texturovací paměti grafického akcelerátoru

8. Automatické generování mipmap v nadstavbové knihovně GLU

Textury s více úrovněmi detailu můžeme buď vytvořit programově s použitím různých filtrů, nebo je možné použít funkce pro automatické generování mipmap. Tyto funkce jsou obsaženy v nadstavbové knihovně GLU a mají tvar:

int gluBuild1DMipmaps(
    GLenum         target,
    GLint          components,
    GLint          width,
    GLenum         format,
    GLenum         type,
    const GLvoid * data
);

int gluBuild2DMipmaps(
    GLenum         target,
    GLint          components,
    GLint          width,
    GLint          height,
    GLenum         format,
    GLenum         type,
    const GLvoid * data
);

Parametry těchto funkcí a jejich význam odpovídá parametrům funkcí glTexImage1D() a glTexImage2D().

9. Automatické vygenerování mipmapy v knihovně Pyglet

V knihovně Pyglet máme situaci značně usnadněnou, protože ve chvíli, kdy je k dispozici obrázek o rozlišení, které je mocninou čísla 2, je možné z takového obrázku přímo získat mipmapu. O všechny přepočty se postará knihovna Pyglet automaticky. Podívejme se nyní, jak se mipmapa vytvoří v praxi. V předchozím příkladu jsme používali uživatelskou funkci nazvanou load_texture(), která načetla rastrový obrázek a vytvořila z něho běžnou texturu:

def load_texture(filename):
    image_stream = open(filename, 'rb')
    image = pyglet.image.load(filename, file=image_stream)
    return image.get_texture()

Pokud namísto volání metody get_texture() použijeme metodu get_mipmapped_texture(), získáme skutečně mipmapu, ovšem jen ve chvíli, kdy jsou rozměry textury rovny 2n×2n pro nějakou celočíselnou konstantu n:

def load_texture(filename):
    image_stream = open(filename, 'rb')
    image = pyglet.image.load(filename, file=image_stream)
    return image.get_mipmapped_texture()

10. Nastavení filtrů při použití mipmappingu

Při použití mipmappingu je také možné specifikovat další typy filtrů použitých při zmenšování textur. Kromě výběru nejbližšího souseda (GL_NEAREST) a interpolace nejbližších sousedů (GL_LINEAR) jsou nově k dispozici i interpolace prováděné mezi dvěma texturami (vybírá se nejbližší menší a nejbližší větší textura):

  • Při nastaveném filtru GL_NEAREST_MIPMAP_NEAREST je pro obarvení pixelu vybrán nejbližší texel z nejbližší větší nebo menší textury. Tento filtr poskytuje vizuálně nejhorší výsledky, vykreslování je však nejrychlejší.
  • GL_NEAREST_MIP­MAP_LINEAR – vybere dva nejbližší texely z obou textur a provede mezi nimi lineární interpolaci. Tímto filtrem se dají jednoduše odstranit nepříjemné skokové změny v obraze, které nastávají v případě, že se zobrazované těleso příliš zvětší nebo zmenší a provede se tak výběr jiné dvojice textur z mipmapy.
  • GL_LINEAR_MIPMAP_NEAREST – provádí bilineární interpolaci nejbližších texelů v jedné textuře. Při zmenšování nebo zvětšování tělesa mohou nastat skokové změny ve vykreslované textuře.
  • GL_LINEAR_MIPMAP_LINEAR – nejprve se použije interpolace pro výpočet barev texelů v obou texturách a poté se výsledná barva spočte další interpolací mezi dvěma předešlými (ve skutečnosti se tedy provádí takzvaná trilineární interpolace). Tento filtr je sice nejpomalejší, ale na druhou stranu poskytuje nejlepší vizuální výsledky.

11. Druhý demonstrační příklad – použití mipmappingu v praxi

V dnešním druhém demonstračním příkladu bude mipmapping použit a navíc bude i konfigurovatelný. Způsob zobrazení je řízen třemi globálními proměnnými:

minFilterEnabled = True                    # filtry pouzite pri texturovani
magFilterEnabled = True
minFilterMode = GL_NEAREST_MIPMAP_NEAREST  # rezim mipmap

Obsah těchto proměnných je možné modifikovat stiskem kláves I a A (povolení filtrů při texturování) a taktéž klávesami 14 (čtyři možnosti popsané v desáté kapitole):

if keys[key.I]:
    minFilterEnabled = not minFilterEnabled
if keys[key.A]:
    magFilterEnabled = not magFilterEnabled
if keys[key._1]:
    minFilterMode = GL_NEAREST_MIPMAP_NEAREST
if keys[key._2]:
    minFilterMode = GL_NEAREST_MIPMAP_LINEAR
if keys[key._3]:
    minFilterMode = GL_LINEAR_MIPMAP_NEAREST
if keys[key._4]:
    minFilterMode = GL_LINEAR_MIPMAP_LINEAR

Samotná změna způsobu texturování je implementována ve funkci nazvané set_filters():

def set_filters(minFilterEnabled, magFilterEnabled, minFilterMode):
    minFilter = GL_LINEAR if minFilterEnabled else GL_NEAREST
    magFilter = GL_LINEAR if magFilterEnabled else GL_NEAREST
    if minFilterEnabled:
        minFilter = minFilterMode
    else:
        minFilter = GL_NEAREST
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter)

Zbylé části zdrojového kódu se vlastně neliší od příkladu prvního.

12. Ukázka různých nastavení mipmappingu

Díky tomu, že při běhu příkladu můžeme volit parametry mipmappingu, bylo získání následujících screenshotů velmi snadné:

Obrázek 11: Mipmapping je zakázán, v zadní části obrázku můžeme vidět moaré.

Obrázek 12: Mipmapping je povolen a nastaven na GL_NEAREST_MIPMAP_NEAREST, povšimněte si chyby v zadní části šachovnice.

Obrázek 13: Mipmapping je povolen a nastaven na GL_NEAREST_MIPMAP_LINEAR.

Obrázek 14: Mipmapping je povolen a nastaven na GL_LINEAR_MIPMAP_NEAREST.

Obrázek 15: Mipmapping je povolen a nastaven na GL_LINEAR_MIPMAP_LINEAR (nejkvalitnější výsledek).

13. Úplný zdrojový kód druhého demonstračního příkladu

Úplný zdrojový kód dnešního druhého demonstračního příkladu vypadá takto:

#!/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

depthBufferEnabled = False                 # povoleni ci zakaz Z-bufferu
texturesEnabled = True                     # povoleni ci zakaz textur

minFilterEnabled = True                    # filtry pouzite pri texturovani
magFilterEnabled = True
minFilterMode = GL_NEAREST_MIPMAP_NEAREST  # rezim mipmap


def create_window():
    return pyglet.window.Window(width=500,
                                height=500,
                                caption="Pyglet library")


window = create_window()


def load_texture(filename):
    image_stream = open(filename, 'rb')
    image = pyglet.image.load(filename, file=image_stream)
    return image.get_mipmapped_texture()


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)
    glDisable(GL_CULL_FACE)                # zadne hrany ani steny se nebudou odstranovat
    glDepthFunc(GL_LESS)                   # funkce pro testovani fragmentu

    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)

    glEnable(GL_TEXTURE_2D)                # povoleni texturovani


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


def draw_floor():
    glBegin(GL_QUADS)                         # vykresleni podlahy
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-50.0, -5.0, -50.0)
    glTexCoord2f(5.0, 0.0)
    glVertex3f(-50.0, -5.0,  50.0)
    glTexCoord2f(5.0, 5.0)
    glVertex3f(50.0, -5.0,  50.0)
    glTexCoord2f(0.0, 5.0)
    glVertex3f(50.0, -5.0, -50.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 i Z/W bufferu
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    else:                    # vymazani vsech bitovych rovin barvoveho bufferu
        glClear(GL_COLOR_BUFFER_BIT)


def set_filters(minFilterEnabled, magFilterEnabled, minFilterMode):
    minFilter = GL_LINEAR if minFilterEnabled else GL_NEAREST
    magFilter = GL_LINEAR if magFilterEnabled else GL_NEAREST
    if minFilterEnabled:
        minFilter = minFilterMode
    else:
        minFilter = GL_NEAREST
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter)


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


@window.event
def on_draw():
    global depthBufferEnabled
    global texturesEnabled
    global minFilterEnabled
    global magFilterEnabled
    global minFilterMode

    if keys[key.Z]:
        depthBufferEnabled = not depthBufferEnabled
    if keys[key.T]:
        texturesEnabled = not texturesEnabled
    if keys[key.I]:
        minFilterEnabled = not minFilterEnabled
    if keys[key.A]:
        magFilterEnabled = not magFilterEnabled
    if keys[key._1]:
        minFilterMode = GL_NEAREST_MIPMAP_NEAREST
    if keys[key._2]:
        minFilterMode = GL_NEAREST_MIPMAP_LINEAR
    if keys[key._3]:
        minFilterMode = GL_LINEAR_MIPMAP_NEAREST
    if keys[key._4]:
        minFilterMode = GL_LINEAR_MIPMAP_LINEAR

    clear_buffers(depthBufferEnabled)
    set_depth_buffer(depthBufferEnabled)
    set_filters(minFilterEnabled, magFilterEnabled, minFilterMode)
    set_textures(texturesEnabled)

    # zacatek kodu modifikujiciho projekcni matici
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()                       # vymazani projekcni matice
    gluPerspective(fov, 1.0, nearPlane, farPlane)

    # zacatek kodu modifikujiciho modelview matici
    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)
    glTranslatef(0, 0, -40)                # posun objektu

    draw_floor()                           # vykresleni objektu - podlahy


texture = load_texture('checker.png')
keys = key.KeyStateHandler()
window.push_handlers(keys)

pyglet.app.run()

14. Funkce knihovny OpenGL a pomocné knihovny GLU, které byly použity ve zdrojových kódech

V demonstračních příkladech, které byly uvedeny v dnešním článku, byly použity následující funkce převzaté z knihoven OpenGL a GLU:

  1. glLoadIdentity
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glLoadIdentity.xml
  2. glRotate
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glRotate.xml
  3. glMatrixMode
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glMatrixMode.xml
  4. glViewport
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glViewport.xml
  5. glColor3f
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glColor.xml
  6. glClearColor
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glClearColor.xml
  7. glClear
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glClear.xml
  8. glTexEnvi
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  9. glTexEnvf
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  10. glTexEnviv
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  11. glTexEnvfv
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  12. glBegin
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glBegin.xml
  13. glEnd
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glBegin.xml
  14. glBindTexture
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glBindTexture.xml
  15. glTexParameteri
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexParameter.xml
  16. glEnable
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glEnable.xml
  17. glDisable
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glEnable.xml
  18. glDepthFunc
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glDepthFunc.xml
  19. glVertex3f
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glVertex.xml
  20. glTexCoord2f
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexCoord.xml
  21. gluLookAt
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
  22. gluPerspective
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml

15. 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
46_make_checker.py https://github.com/tisnik/presentations/blob/master/pyglet/46_make_checker.py
47_checker.py https://github.com/tisnik/presentations/blob/master/pyglet/47_checker.py
48_checker_mipmap.py https://github.com/tisnik/presentations/blob/master/pyglet/48_checker_mipmap.py

Poznámka: nejprve si spusťte první skript, který vytvoří texturu použitou v obou dalších příkladech.

16. Odkazy na Internetu

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