Pythonstuff GLSL in English Pythonstuff GLSL auf Deutsch Pythonstuff GLSL Pythonstuff
PythonStuff Home
 

 

Beispiel 1 - Pyglet's graphics.py wird interaktiv

Hier geht es um eine Reihe von Python-Programmen, die ich gemacht habe, um die Library Pyglet, OpenGL und insbesondere GLSL besser zu verstehen.

Das ist das erste Beispiel - begonnen habe ich mit graphics.py von Alex Holkner. Das Original befindet sich im Pyglet Repository.

Ich habe die Funktion erweitert, um eine Basis für alle GLSL Demoprogramme zu haben.

und ein paar Screenshots:

Wireframe Sphere Shaded Torus

Wireframe Torus Shaded Sphere

Um die Demo laufen zu lassen, benötigt man Pyglet und das “euclid.py” Modul von Alex Holkner - alles dafür Nötige ist auf der Seite zur Installation.

(Falls jemand “euclid.py” sucht: das File liegt im Verzeichnis App/Lib/site-packages/shader des Installationspaketes glslpythonpack.zip).

Die erweiterte Version zeigt:

  • HTML-Text um anzuzeigen, was die Demo tut (der Text verschwindet automatisch nach 10 Sekunden und kann mit der “H”-Taste ein-/ausgeschaltet werden)
  • eine Frames-pro-Sekunde (FPS) Anzeige um die (In-)effizenz des Renderings überprüfen zu können
  • Tastatursteuerung für die Objekt-Rotation - Stop, manuelles Rotieren zusätzlich zum automatischen Rotieren
  • Tastatursteuerung für das Licht - die primäre Lichtquelle kann bewegt werden
  • per Taste ein-/ausschaltbare Wireframe-Darstellung (hauptächlich um den folgenden Punkt anzuzeigen :-))
  • Eine Kugel mit gleichmässig verteilten Ecken zusätzlich zum Torus

HTML-Text

Ein nettes Feature der Pyglet Library - mit HTML-Code in einem String, kann man ein Objekt erzeugen mit

label = pyglet.text.HTMLLabel(html, # location=location,
                              width=window.width//2,
                              multiline=True, anchor_x='center', anchor_y='center')

und dieses im “on_draw()” Event Handler darstellen mit

@window.event
def on_draw():
  label.draw()

jedesmal wenn das Fenster neu gezeichnet wird.

Ich lasse den Text durch ein Pyglet Event nach 10 Sekunden verschwinden:

def dismiss_dialog(dt):
    global showdialog
    showdialog = False
pyglet.clock.schedule_once(dismiss_dialog, 10.0)

Behandlung der Tastatur

Das ist auch recht einfach - um die “showdialog”-Variable mit der “H”-Taste umzuschalten schreibe ich

@window.event
def on_key_press(symbol, modifiers):
    global showdialog
  if symbol == key.H:
      showdialog = not showdialog

Das ist eine nette Eigenschaft von Python - leicht zu lesen und zu verbessern :-)

FPS Zähler anzeigen

Das ist eine Pyglet Standardfunktion. Das Einzige, was dabei zu beachten ist, dass in die aktuelle Texture gerendert wird, also muss die Textur aktiv sein, um den Zähler zu sehen.

fps_display = pyglet.clock.ClockDisplay() # see programming guide pg 48
@window.event
def on_draw():
    fps_display.draw()  

Kugel mit gleichmässig verteilten Ecken

Zusätzlich zum “batch1”, der die Vertex Arrays für den Torus enthält:

batch1  = pyglet.graphics.Batch()
torus = Torus(1, 0.3, 80, 25, batch=batch1)

wollte ich einen “batch2” (Umschaltung zwischen den beiden Figuren mit der “F”-Taste) mit einer regelmässigen Kugel:

batch2  = pyglet.graphics.Batch()
sphere = Sphere(1.2, 4, batch=batch2)

Warum regelmässig ? Wenn Du Dir eine Weltkugel vorstellst mit dem Äquator und den Meridianen, fällt auf, dass an den Polen seltsame Dinge mit den Koordinaten geschehen. Um die zu vermeiden, funktioniert als einfachste Lösung die Dreiecks-Teilung:

Finde eine regelmässige (“platonische”) Figur, die sich in eine Kugel einschreiben lässt und ausschliesslich aus gleichseitigen Dreiecken besteht - drei gibt's zur Auswahl:

  • Tetraeder (4 Dreiecke, 4 Ecken, 6 Kanten)
  • Oktaeder (8 Dreiecke, 6 Ecken, 12 Kanten)
  • Ikosaeder (20 Dreiecke, 12 Ecken, 30 Kanten)

Ich habe mit einem Oktaeder begonnen, weil dann die Eck-Koordinaten am einfachsten sind:

        self.vv.append( Vector3( 1.0, 0.0, 0.0 ) ) # North
        self.vv.append( Vector3(-1.0, 0.0, 0.0 ) ) # South
        self.vv.append( Vector3( 0.0, 1.0, 0.0 ) ) # A
        self.vv.append( Vector3( 0.0, 0.0, 1.0 ) ) # B
        self.vv.append( Vector3( 0.0,-1.0, 0.0 ) ) # C
        self.vv.append( Vector3( 0.0, 0.0,-1.0 ) ) # D

Dann wird jedes Dreieck (rekursiv) in 4 kleinere gleichseitige Dreiecke geteilt. Die neuen Ecken (drei Stück) werden “nach aussen” zur Kugeloberfläche verschoben. Wiederholen bis Du genug hast :-)

Hier ein Bild der verschiedenen Rekursions-Schritte. Die Ecken eines Dreiecks sind mit roten Punkten markiert, damit man den Unterteilungs-Algorithmus leicht sieht.

Die Implementierung dieses Algorithmus in Python wäre einfach und elegant, aber in seiner einfachen Form entstehen gemeinsame Ecken - diese liegen dann als Duplikate in der Ecken-Liste. Um sie zu vermeiden, muss ich über die “bereits behandelten” Kanten Buch führen:

Wenn eine Kante neu ist (“N”), werden die Ecken in die “vertex”-Liste aufgenommen. War die Kante schon dran (“X”), suche ich in der Liste nach der Ecke mit den entsprechenden Koordinaten und verwende deren Index.

    def myindex( self, list, value ):
        for idx, obj in enumerate(list):
            if abs(obj-value) < 0.0001:
              return idx
        raise ValueError # not found

Die Buchführung macht den Code eher hässlich - Ich habe das dumpfe Gefühl, dass das besser geht (indem ich die Dreiecke irgendwie sinnvoll durchnummeriere - dann könnte ich den gesuchten Index direkt berechnen statt in einer Liste von Fliesskomma-Vektoren herumzusuchen).

Na gut, aber hier stehen wir. Für Details schau' in den Pyglet Demo Base Code.

Das ganze Ding ist die Basis für meine GLSL Experimente - Wir beginnen mit einer ordentlichen GLSL Beleuchtung (inklusive einem hübschen Glanzlicht).


English version, Start
Impressum & Disclaimer