PyOpenGL Shader Error

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

Hallo.
Vor kurzem habe ich festgestellt, das das Tutorial, mit dem ich versucht habe, OpenGL zu lernen, entsetzlich veraltet ist und ich bin deswegen auf das englische Wikibook umgeschwenkt, was ja angeblich "modernes" OpenGL nutzt. Und da braucht man angeblich für vieles einen Shader. Und dieser bereitet mir Probleme.
Ich will im Moment das C++-Programm aus diesem Teil nach Python übersetzen und dabei kriege ich eine seeehr lange Fehlermeldung.
Mein Quellcode:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from sys import exit

import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

class Szene:
    
    def __init__(self):
        self.laeuft = True
        self.dreieck_punkte = [
            0.0, 0.8,
            -0.8, -0.8,
            0.8, -0.8
        ]
        self.init_grafik()
    
    def init_grafik(self):
        pygame.init()
        self.screen = pygame.display.set_mode((800, 450), pygame.OPENGL)
        glutInit()
        self.init_shader()
    
    # der vermutlich fehlerhafte Teil
    def init_shader(self):
        self.shaders = dict()
        self.shaders["vertex"] = glCreateShader(GL_VERTEX_SHADER)
        vertex_shader_source = [
            "#version 440",
            "attribute vec2 coord2d;",
            "void main (void) {",
            "   gl_Position = vec4(coord3d, 0.0, 1.0);"
            "}"            
        ]
        glShaderSource(self.shaders["vertex"], 1, vertex_shader_source, None)
        glCompileShader()
        
    
    def hauptschleife(self):
        while self.laeuft:
            for event in pygame.event.get():
                self.verarbeite_event(event)
            self.aktualisiere()
            glFlush()
            pygame.display.flip()
        exit()
    
    def verarbeite_event(self, event):
        self.laeuft = not ((event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE))
    
    def aktualisiere(self):
        pass

if __name__ == '__main__':
    szene = Szene()
    szene.hauptschleife()
Es ist noch nicht ganz komplett. Und beim Zwischentest taucht ein Fehler auf:

Code: Alles auswählen

Traceback (most recent call last):
  File "c:\python34\lib\site-packages\OpenGL\latebind.py", line 41, in __call__
    return self._finalCall( *args, **named )
TypeError: 'NoneType' object is not callable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\Roman\Documents\ogl-test.py", line 58, in <module>
    szene = Szene()
  File "C:\Users\Roman\Documents\ogl-test.py", line 20, in __init__
    self.init_grafik()
  File "C:\Users\Roman\Documents\ogl-test.py", line 26, in init_grafik
    self.init_shader()
  File "C:\Users\Roman\Documents\ogl-test.py", line 38, in init_shader
    glShaderSource(self.shaders["vertex"], 1, vertex_shader_source, None)
  File "c:\python34\lib\site-packages\OpenGL\latebind.py", line 45, in __call__
    return self._finalCall( *args, **named )
  File "c:\python34\lib\site-packages\OpenGL\wrapper.py", line 579, in wrapperCall
    pyArgs = tuple( calculate_pyArgs( args ))
  File "c:\python34\lib\site-packages\OpenGL\wrapper.py", line 436, in calculate_pyArgs
    yield converter(args[index], self, args)
  File "c:\python34\lib\site-packages\OpenGL\converters.py", line 305, in stringArray
    value = [as_8_bit(x) for x in arg]
TypeError: ("'int' object is not iterable", <bound method StringLengths.stringArray of <OpenGL.converters.StringLengths object at 0x000000000312C550>>)
Hab ich was falsch gemacht oder ist mein PyOpenGL kaputt?
Falls es wichtig ist, ich benutze Python 3.4 x86_64, ein dazu passendes PyOpenGL 3.1 auf einem PC mit Win7 x86_64.

Danke und Lg Y.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Raten beim Programmieren ist eine schlechte Idee. Der glShaderSource-Aufruf sieht falsch aus.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also die Meldung an sich ist ja klar: Das ``arg`` aus Zeile 24 der Fehlermeldung

Code: Alles auswählen

   value = [as_8_bit(x) for x in arg]
ist ein Integerwert und kein Iterable!

Wieso da ein falscher Typ auftaucht ist natürlich viel schwieriger.

Zeile 39 in Deinem Code löst das ganze ja offenbar aus. Da Du in dieser einiges an Parametern übergibst, liegt die Vermutung nahe, dass diese nicht ganz korrekt sind oder in falscher Reihenfolge und das Problem verursachen...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Üpsilon: Schau doch einfach mal in die Dokumentation was die Python-Funktion `glShaderSource()` für Argumente erwartet.
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

Erstmal danke für die Antworten.

Den glShaderSource-Aufruf hatte ich fast 1:1 aus dem Wikibook übertragen. Aber ich habe mal auf opengl.org nachgeguckt und dann die glShaderSource-Zeile dadurch ersetzt:

Code: Alles auswählen

glShaderSource(self.shaders["vertex"], len(vertex_shader_source),
                       vertex_shader_source, [len(string) for string in vertex_shader_source])
Dann kam wieder der gleiche Fehler. Und auch wenn es streng genommen keinen Sinn ergibt, habe ich dann die len(vertex_shader_source) in eckige Klammern gesetzt und es kam eine andere Fehlermeldung. Das lag allerdings daran, dass die Zeile darunter (glCompileShader) falsch war, richtig ist

Code: Alles auswählen

glCompileShader(self.shaders["vertex"])
. Und jetzt funktioniert es. Also, eigentlich funktioniert es nicht, aber das kann es auch gar nicht, weil es noch nicht fertig ist.
Lg Y.
PS: Die angebotene Summe ist beachtlich.
BlackJack

@Üpsilon: Du solltest vielleicht nicht in einem Tutorial für C++ schauen wie die Funktionssignaturen aussehen, sondern in der Dokumentation der Anbindung an OpenGL die Du verwendest. Das mit der Angabe der Längen ist bei C doch nur notwendig weil in C weder Zeichenketten noch Arrays eine einfache/effiziente Möglichkeit bieten die Länge zur Laufzeit zu ermitteln. Python hat diese Einschränkung nicht und die Dokumentation zu der Funktion hat dort die Signatur `glShaderSource(shaderObj, string)`.
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

@Blackjack: Ok, nächstes Mal schaue ich auf der Seite von PyOpenGL nach.

@alle: Neues Problem. Ich komme einfach nicht auf die Shader klar. Ich versuche immer noch, den Quellcode aus dem Wikibook nach Python zu übersetzen, und jetzt müsste das eigentlich funktionieren und auf dem Bildschirm ein Dreieck anzeigen.

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from sys import exit

import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

class Szene:
    
    def __init__(self):
        self.laeuft = True
        self.dreieck_punkte = [
            0.0, 0.8,
            -0.8, -0.8,
            0.8, -0.8
        ]
        self.init_grafik()
    
    def init_grafik(self):
        pygame.init()
        self.screen = pygame.display.set_mode((800, 450), pygame.OPENGL)
        glutInit()
        glClearColor(0.9, 0.9, 0.9, 0.0)
        self.init_shader()
    
    def init_shader(self):
        shaders = dict()
        # Vertex-Shader
        shaders["vertex"] = glCreateShader(GL_VERTEX_SHADER)
        vertex_shader_source = "\n".join([
            "#version 310",
            "attribute vec2 coord2d;",
            "void main (void) {",
            "   gl_Position = vec4(coord3d, 0.0, 1.0);",
            "}"
        ])
        glShaderSource(shaders["vertex"], vertex_shader_source)
        glCompileShader(shaders["vertex"])
        # Fragment-Shader
        shaders["fragment"] = glCreateShader(GL_FRAGMENT_SHADER)
        fragment_shader_source = "\n".join([
            "#version 310",
            "void main (void) {",
            "   gl_FragColor[0] = 1.0;",
            "   gl_FragColor[1] = 0.0;",
            "   gl_FragColor[2] = 0.0;",
            "}"
        ])
        glShaderSource(shaders["fragment"], fragment_shader_source)
        glCompileShader(shaders["fragment"])
        # Zusammenführen
        self.shader_programm = glCreateProgram()
        for shader in shaders.values():
            glAttachShader(self.shader_programm, shader)
        glLinkProgram(self.shader_programm)
        self.attribut_coord2d = glGetAttribLocation(self.shader_programm, "coord2d")
        # die Variable ´´attribut_coord2d`` hab ich so seltsam benannt, weil sie im Tutorial
        # auch so benannt habe und keine Ahnung habe, was sie macht und mir deshalb kein
        # besserer Name einfällt.
        glUseProgram(self.shader_programm)
    
    def hauptschleife(self):
        while self.laeuft:
            for event in pygame.event.get():
                self.verarbeite_event(event)
            self.aktualisiere()
            glFlush()
            pygame.display.flip()
        self.quit()
    
    def verarbeite_event(self, event):
        self.laeuft = not ((event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE))
    
    def aktualisiere(self):
        glEnableVertexAttribArray(self.attribut_coord2d)
        glVertexAttribPointer(self.attribut_coord2d,
                              2,
                              float,
                              False,
                              0,
                              self.dreieck_punkte)
        glDrawArrays(GL_TRIANGLES, 0, 3)
        glDisableVertexAttribArray(self.attribut_coord2d)
    
    def quit(self):
        glDeleteProgram(self.shader_programm)
        exit()
    

if __name__ == '__main__':
    szene = Szene()
    szene.hauptschleife()
Und wieder kommt eine seeehr lange Fehlermeldung. Das scheint eines der wichtigsten Features von OpenGL zu sein :mrgreen:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Users\Roman\Documents\ogl-test.py", line 91, in <module>
    szene = Szene()
  File "C:\Users\Roman\Documents\ogl-test.py", line 20, in __init__
    self.init_grafik()
  File "C:\Users\Roman\Documents\ogl-test.py", line 27, in init_grafik
    self.init_shader()
  File "C:\Users\Roman\Documents\ogl-test.py", line 59, in init_shader
    self.attribut_coord2d = glGetAttribLocation(self.shader_programm, "coord2d")
  File "c:\python34\lib\site-packages\OpenGL\latebind.py", line 61, in __call__
    return self.wrapperFunction( self.baseFunction, *args, **named )
  File "c:\python34\lib\site-packages\OpenGL\GL\VERSION\GL_2_0.py", line 397, in glGetAttribLocation
    return baseOperation( program, name )
  File "c:\python34\lib\site-packages\OpenGL\platform\baseplatform.py", line 402, in __call__
    return self( *args, **named )
  File "c:\python34\lib\site-packages\OpenGL\error.py", line 232, in glCheckError
    baseOperation = baseOperation,
OpenGL.error.GLError: GLError(
	err = 1282,
	description = b'Der Vorgang ist ung\xfcltig.',
	baseOperation = glGetAttribLocation,
	cArguments = (3, b'coord2d\x00'),
	result = -1
)
Und ich habe wieder die gleiche Frage. Hab ich was falsch gemacht oder ist mein PyOpenGL kaputt?
PS: Die angebotene Summe ist beachtlich.
BlackJack

@Üpsilon: Das Attribut wird im Shader-Programm nicht verwendet, also wird es von Shader-Compiler wegoptimiert, also kann man es nicht binden. Weil's ja eh nicht verwendet wird, macht es auch keinen Sinn es abzufragen. Vielleicht möchtest Du ja Dein Shader-Programm noch mal *genau* mit dem aus dem Tutorial vergleichen. *Dort* wird das Attribut im Shader nämlich verwendet.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Du solltest bei der Gelegenheit auch mal deine Kriterien überlegen, nach denen du entscheidest, ob du Code übernimmst oder nicht. In dem von dir verlinktem Tutorial wird doch auch die Fehlerbehandlung gezeigt, falls etwas beim Compilieren der Shader nicht funktioniert. Hättest du den auch umgestzt, dann hättest du den Fehler selbst leicht finden können. Es gibt übrigens eine sehr nützliche Funktion für solche Probleme: glGetShaderInfoLog.
Das Leben ist wie ein Tennisball.
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

BlackJack hat geschrieben:Vielleicht möchtest Du ja Dein Shader-Programm noch mal *genau* mit dem aus dem Tutorial vergleichen. *Dort* wird das Attribut im Shader nämlich verwendet.
EyDu hat geschrieben:Es gibt übrigens eine sehr nützliche Funktion für solche Probleme: glGetShaderInfoLog.
Danke für die Tipps. Durch genaues Vergleichen und glGetShaderInfoLog hab ich den Fehler jetzt korrigiert: es lag an der Versionsangabe und einem Tippfehler: coord2d/coord3d.

So, und nachdem ich noch einen weiteren Bug rausbefördet habe, bin ich (vermutlich) fertig. Falls es irgendwen interessiert, hier das funktionierende Programm.

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from sys import exit

import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

class Szene:
    
    def __init__(self):
        self.laeuft = True
        self.dreieck_punkte = [
            0.0, 0.8,
            -0.8, -0.8,
            0.8, -0.8
        ]
        self.init_grafik()
    
    def init_grafik(self):
        pygame.init()
        self.screen = pygame.display.set_mode((800, 450), pygame.OPENGL)
        glutInit()
        glClearColor(0.9, 0.9, 0.9, 0.0)
        self.init_shader()
    
    def init_shader(self):
        shaders = dict()
        # Vertex-Shader
        shaders["vertex"] = glCreateShader(GL_VERTEX_SHADER)
        vertex_shader_source = "\n".join([
            "#version 120",
            "attribute vec2 coord2d;",
            "void main (void) {",
            "   gl_Position = vec4(coord2d, 0.0, 1.0);",
            "}"
        ])
        glShaderSource(shaders["vertex"], vertex_shader_source)
        glCompileShader(shaders["vertex"])
        print("### Vertex-Shader ###")
        print(glGetShaderInfoLog(shaders["vertex"]))
        # Fragment-Shader
        shaders["fragment"] = glCreateShader(GL_FRAGMENT_SHADER)
        fragment_shader_source = "\n".join([
            "#version 120",
            "void main (void) {",
            "   gl_FragColor[0] = 1.0;",
            "   gl_FragColor[1] = 0.0;",
            "   gl_FragColor[2] = 0.0;",
            "}"
        ])
        glShaderSource(shaders["fragment"], fragment_shader_source)
        glCompileShader(shaders["fragment"])
        print("### Fragment-Shader ###")
        print(glGetShaderInfoLog(shaders["fragment"]))
        # Zusammenführen
        self.shader_programm = glCreateProgram()
        for shader in shaders.values():
            glAttachShader(self.shader_programm, shader)
        glLinkProgram(self.shader_programm)
        self.attribut_coord2d = glGetAttribLocation(self.shader_programm, "coord2d")
        glUseProgram(self.shader_programm)
    
    def hauptschleife(self):
        while self.laeuft:
            for event in pygame.event.get():
                self.verarbeite_event(event)
            self.aktualisiere()
            glFlush()
            pygame.display.flip()
        self.quit()
    
    def verarbeite_event(self, event):
        self.laeuft = not ((event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE))
    
    def aktualisiere(self):
        glClear(GL_COLOR_BUFFER_BIT)
        glEnableVertexAttribArray(self.attribut_coord2d)
        glVertexAttribPointer(self.attribut_coord2d,
                              2,
                              GL_FLOAT,
                              False,
                              0,
                              self.dreieck_punkte)
        glDrawArrays(GL_TRIANGLES, 0, 3)
        glDisableVertexAttribArray(self.attribut_coord2d)
    
    def quit(self):
        glDeleteProgram(self.shader_programm)
        exit()
    

if __name__ == '__main__':
    szene = Szene()
    szene.hauptschleife()
PS: Die angebotene Summe ist beachtlich.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ein paar Dinge kann man da noch verbessern. Als erstes würde ich die ganzen *-Importe ersetzen. Dann hat man zwar überall ``gl.glXYZ`` stehen, aber dann müllst du dir nicht gleich den ganzen Namensraum zu. Ich würde mal vermuten, dass sich bei den drei *-Importen so einige Namen gegenseitig überschreiben. Das kann zu unerwarteten Problemen führen.

Der Name "Szene" ist etwas ungünstig gewählt, der Begriff hat im Grafikbereich bereits eine Bedeutung. Du hast hier aber viel mehr als nur die Szene, bei dir steckt ja vieles mehr drin. Da fällt mir auch noch auf, dass du deutsche und englische Bezeichner bunt mischt. Du solltest dich für eine Sprache entscheiden und das dann konsequent durchziehen, sonst ist es anstrengend zu lesen.

Du solltest noch ein paar Konstanten einbauen, bei der Auflösung und der Hintergrundfarbe bietet sich das zum Beispiel an. Als Hintergrundfarbe würde ich an deiner Stelle auch etwas auffälligeres nehmen, wie (255, 0, 255), dann fallen dir sehr schnell Fehler beim Rendering auf.

Die Shader würde ich so nicht verwalten. Üblicherweise hat man mehrere Vertex- und Fragment-Shader, deren Wahl von allen möglichen Parametern abhängt. Häufig gehören sie direkt zu einem zu rendernden Objekt.

Die Erzeugung deines Shader-Codes ist extrem umständlich. Spar dir das join und verwende Triple-Quote-Strings. Für solche Fälle gibt es sie. Da dein Shadercode konstant ist, solltest du diesen auch noch in entsprechende Konstanten packen. Außerdem ist der Code zum Erzeugen der beiden Shader identisch. Mache da eine Funktion draus und rufe sie einmal für den Vertex-Shader auf und einmal für den Fragmentshader. Du wirst die Funktion so oder so benötigen, wenn du mehrere Shader hast.

Du behandelst noch immer keine Fehler. Die Ausgabe von Logs ist nicht sinnvoll. Wenn ein nicht behandelbarer Fehler passiert, dann soll das Programm gefällgst abschmieren und die Fehlermeldung ausgeben. Alles andere macht wenig Sinn. Wenn irgendwann mal etwas schief geht ist es unglaublich anstregend unnütze print-Ausgaben zu lesen um an die Ursache zu kommen. Beim Linken der Shader können übrigens auch Fehler auftreten, das solltest du dir viel genauer anschauen.

Zeile 76 ist ein wenig Lang, mehr als 80 Zeichen sollten es nicht sein.
Das Leben ist wie ein Tennisball.
BlackJack

@EyDu: Hier würde ich auch Sternchenimporte verwenden und jedes mal ein wenig Hass auf die Autoren des Bindings haben, denn die C-Funktionen die dort gewrappet werden fangen alle mit den Namen der Bibliothek als Präfix an. Halt weil C keine Namensräume kennt. Und dann stecken die das zwar in verschiedene Namensräume, belassen aber den nun eigentlich überflüssigen Präfix an den Namen. Grmpf. :evil:
Antworten