OpenGL for Java
Ziele Bewegte Simulationen grafisch darstellen können (effizient, realistisch, dreidimensional) Grundfunktionen von OpenGL beherrschen Mathematische Voraussetzungen für Computer Grafik verstehen
Wozu OpenGL? Computer Grafik Echtzeit 3D Grafik sehr rechenintensiv Einfache, leicht parallelisierbare Algorithmen (wenig Code, wenige Kontrollstrukturen) Viele wichtige Anwendungen geeignet für Spezialhardware (Grafikkarte) Weiterer Vorteil durch Grafik Karte: Weniger Hauptspeicherzugriffe, indem grafische Objekte direkt auf der Grafikkarte gespeichert werden. OpenGL: High level Software API für Grafik Hardware
Was ist OpenGL? Spezifikation einer Software Schnittstelle zur Grafik Hardware (API) System- und Programmiersprachen unabhängig ca. 250 Befehle und was nicht? GUI Programmierung, Sound, Netzwerk, usw
Wie funktioniert s? Benutzerprogramm: Aufruf von OpenGL API Funktionen Position, Form und Farbe von 3D Objekten (Würfel, Kugel, ) Position und Farbe von Lichtquellen Oberflächenbeschaffenheit (Textur) von Objekten Position und Blickrichtung der Kamera OpenGL Koordinatensystem Transformationen (Drehung, Verschiebung ) Projektion der 3D Szene auf ein 2D Bild Farbverlauf auf Flächen (Licht/Schatten) Sichtbare/verdeckte Flächen Transformation von Texturen
Geschichte von OpenGL 1982 SGI beginnt mit der Entwicklung für High End Grafik Workstations 1992 OpenGL Version 1.0 Seit 1992 Open GL Architecture Review Board (ARB) Mitglieder: Compaq, ATI, nvidia, HP, IBM, Apple, Microsoft, Heute: OpenGL Version 2.0 Grafikkarten Wettbewerb, Erweiterungen
Zusätzliche Bibliotheken GLU (OpenGL Utilities) Einfache Funktionen zum Zeichnen komplexerer Objekte (Kugeln, Zylinder, Scheibe, ) Gekrümmte Flächen: NURBS Viele nützliche Hilfsfunktionen GLUT (OpenGL Utility Toolkit) Torus, Tetrahedron, Octahedron, Text Plattformunabhängige GUI Funktionen (Fenster, Maus, Tastatur) Für GUI Funktionen verwenden wir Java!
Zusätzliche Bibliotheken Anwendungsprogramm GLUT GLU WGL/GLX/AGL Window System OpenGL Grafik Hardware Systemunabhängig Systemabhängig
Anwendungsprogramm GL4Java JNI GLUT GLU WGL/GLX/AGL Window System OpenGL Grafik Hardware Systemunabhängig Systemabhängig
Triang Das erste OpenGL Programm Starter.java Fenster erzeugen (JFrame) Zeichenfläche (Canvas3D) erzeugen und anzeigen Canvas3D.java Abgeleitet von GLAnimCanvas (GL4Java Klasse) preinit, init: Initialisierung display: Wird aufgerufen, um Bildschirm neu zu zeichnen reshape: Wird aufgerufen, wenn Fenstergröße geändert wird.
import javax.swing.*; import java.awt.*; Starter.java class Starter { public static void main(string[] args) { // Erzeugen des OpenGL Canvas Canvas3D canvas3d = new Canvas3D(640,480); // Erzeugen des Anwendungsfensters JFrame frame = new JFrame("Triangles for Java"); // OpenGL Canvas dem Frame hinzufügen Container pane = frame.getcontentpane(); pane.add(canvas3d); } } // Frame Grösse setzen und anzeigen frame.setsize(640,480); frame.setvisible(true); frame.setdefaultcloseoperation(jframe.exit_on_close);
Canvas3D.java import gl4java.awt.glcanvas; class Canvas3D extends GLAnimCanvas { // Konstruktor für Gl4Java Klasse GLCanvas public Canvas3D(int w, int h) { super(w, h); } // Globale OpenGL Optionen (z.b. double buffering) setzen public void preinit() { } // Einmaliges Initialisieren (z.b. Farbe zum Löschen) public void init() { } // Wird aufgerufen, wenn Bildschirm neu gezeichnet werden muss public void display() { } } // Wird aufgerufen, wenn Fenstergröße geändert wird public void reshape(int width, int height) { }
TriangleVie reshape(int width, int height) Bildschirmbereich auf den gezeichnet werden soll in Pixel glviewport( 0,0,width,height ); Matrix, die 3D nach 2D Projektion macht Perspektivische Projektion 60 Grad Blickwinkel, Clipping Abstand 2 bis 4 glmatrixmode( GL_PROJECTION ); glloadidentity(); gluperspective(60, 1.0, 2.0, 4.0); // Matrix, die Zeichenkoordinatensystem positioniert glmatrixmode( GL_MODELVIEW );
display Löschen was zuvor gemalt wurde glclear(gl_color_buffer_bit GL_DEPTH_BUFFER_BIT); Farbe rot einstellen glcolor3f(1.0f,0.0f,0.0f); Zeichenkoordinatensystem 3 Einheiten nach hinten setzen glloadidentity(); gltranslatef(0.0f, 0.0f, -3.0f); Dreieck durch Eckpunkte zeichnen glbegin(gl_triangles); glvertex3f(-1.0f, -1.0f, 0.0f); // links unten glvertex3f( 1.0f, -1.0f, 0.0f); // rechts unten glvertex3f( 0.0f, 1.0f, 0.0f); // mitte oben glend();
init Interpolation wenn Eckpunkte unterschiedliche Farbe haben glshademodel(gl_flat); Hintergrundfarbe Weiss glclearcolor(1.0f, 1.0f, 1.0f, 0.0f); preinit Double Buffering (flüssiger bei bewegter Grafik) super.doublebuffer = true; Für Stereo Brillen super.stereoview = false;
TriangleInterpolatio Schattierungsmodell auf GL_SMOOTH setzen Jedem Eckpunkt des Dreiecks eine andere Farbe geben gl.glbegin(gl_triangles); gl.glcolor3f(1.0f,0.0f,0.0f); rot gl.glvertex3f(-1.0f, -1.0f, 0.0f); links unten gl.glcolor3f(0.0f,1.0f,0.0f); grün gl.glvertex3f( 1.0f, -1.0f, 0.0f); rechts unten gl.glcolor3f(0.0f,0.0f,1.0f); blau gl.glvertex3f( 0.0f, 1.0f, 0.0f); mitte oben gl.glend();
TriangleRotation, TriangleRotationVie Dreieck um 20 Grad um z-achse nach links drehen gl.glrotatef( 20.0f, 0.0f, 0.0f, 1.0f ); z-achse
Matrizen in OpenGL Model View Matrix glmatrixmode(gl_modelview); Legt Position und Orientierung des Koordinatensystems fest, in das als nächstes gezeichnet wird. glrotate(), gltranslate(), 3D nach 2D Projektions Matrix glmatrixmode(gl_projection); Bestimmt, wie eine 3D Szene auf ein 2D Bild projiziert wird. - Orthogonalprojektion: glortho() - Perspektivische Projektion: gluperspective(), glfrustum() Wird i.a. nur einmal gesetzt und nicht mehr geändert.
Koordinatensystem Transformationen in OpenGL glvertex(x,y,z) Objektkoordinaten Multiplikation mit Model View Matrix glrotate(),gltranslate() Kamerakoordinaten Multiplikation mit Projektions Matrix gluperspective() 2D Koordinaten Skalieren, Verschieben glviewport() Bildschirmkoordinaten (Pixel)
Etwas Mathematik Transformationen (Translation, Rotation, Projektion) Koordinatensysteme Homogene Koordinaten
OpenGL Primitive gl.glbegin(gl_triangles); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. 1 2 3 5 4 6 gl.glend();
OpenGL Primitive gl.glbegin(gl_points); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. gl.glend(); 1 3 2 Größe der Punkte gl.glpointsize(float size)
OpenGL Primitive gl.glbegin(gl_lines); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. 1 4 3 2 gl.glend(); Liniendicke gl.gllinewidth(float size)
OpenGL Primitive gl.glbegin(gl_line_strip); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. 1 4 3 2 gl.glend();
OpenGL Primitive gl.glbegin(gl_line_loop); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. 1 4 3 2 gl.glend();
OpenGL Primitive gl.glbegin(gl_triangle_strip); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. gl.glend(); 1 2 3 4 5
OpenGL Primitive gl.glbegin(gl_triangle_fan); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. gl.glend(); 1 2 5 3 4
OpenGL Primitive gl.glbegin(gl_quads); 1 4 gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. gl.glend(); 2 6 5 3 8 7
OpenGL Primitive gl.glbegin(gl_quad_strip); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 gl.glvertex( ); Punkt 4 usw. 1 2 3 6 4 5 gl.glend();
OpenGL Primitive gl.glbegin(gl_polygon); gl.glvertex( ); Punkt 1 gl.glvertex( ); Punkt 2 gl.glvertex( ); Punkt 3 usw. 1 2 3 5 4 gl.glend(); Ränder dürfen sich nicht schneiden, sonst ist unklar was innen und außen ist! Die Eckpunkte müssen alle in einer Ebene liegen, sonst ist unklar welche Fläche gemalt werden soll! 1 2 3 1 4 5 4 Polygone müssen konvex sein! 5 2 3
Polygone GL_TRIANGLES, GL_QUADS, GL_POLYGON Def. Vorderseite: Reihenfolge der Eckpunkte gegen Uhrzeigersinn 3 3 1 2 2 1 Vorderseite Rückseite 4 3 1 2 1 2 4 3
Polygone Polygone gefüllt, als Linien oder nur die Eckpunkte zeichnen gl.glpolygonmode( GL_FRONT, GL_POINT ) GL_BACK GL_LINE GL_FRONT_AND_BACK GL_FILL Nur Vorderseite/Rückseite zeichnen Effizienz Verdeckte Flächen bei geschlossenen Körpern gl.glenable(gl_cull_face) gl.glcullface( GL_FRONT ) GL_BACK GL_FRONT_AND_BACK
Bewegte Grafik In der init Methode Geschwindigkeit (Bilder pro Sekunde) super.setanimatefps(60.0); // 60 Aufrufe von display() pro Sekunde Starten und anhalten super.start(); super.stop();
TriangleAnimation, TriangleAnimationVie Bewegte Grafik Rotierendes Dreieck um seine (lokale) y-achse Vorderseite ausgefüllt, Rückseite Linien
Cube, CubeLigh 3D Grafik Problem: Kein richtiger 3D Eindruck wenn Flächen ausgefüllt sind! Lösung: Licht und Schatten Lichtquellen (Position, Richtung, Farbe, Ausbreitung, ) Oberflächen (Normalenvektor, Reflektionseigenschaften, Farbe, )
Mehr Mathematik Licht Reflektion Normalenvektoren
CubeLightVie Diffuse Reflektion ohne Glanz mit Glanz
Normalenvektoren gl.glbegin(gl_quads); gl.glend() // Vorderseite gl.glnormal3f( 0.0f, 0.0f, 1.0f); gl.glvertex3f(-1.0f,-1.0f, 1.0f); gl.glvertex3f( 1.0f,-1.0f, 1.0f); gl.glvertex3f( 1.0f, 1.0f, 1.0f); gl.glvertex3f(-1.0f, 1.0f, 1.0f); // Rechte Seite gl.glnormal3f( 1.0f, 0.0f, 0.0f); gl.glvertex3f( 1.0f,-1.0f, 1.0f); gl.glvertex3f( 1.0f,-1.0f,-1.0f); gl.glvertex3f( 1.0f, 1.0f,-1.0f); gl.glvertex3f( 1.0f, 1.0f, 1.0f); // usw
Normalenvektoren gl.glnormal3f( 0.0f, 0.0f, 1.0f); Aktuellen Normalenvektor setzen. Dieser bleibt so lange gültig, bis ein neuer Normalenvektor gesetzt wird Möglich: An jedem Eckpunkt ein anderer Normalenvektor. Anwendung: Gekrümmte Flächen, die aus vielen Einzelpolygonen bestehen. Lichtverlauf an den Kanten dann glatter ( smooth shading ).
Licht // Licht aktivieren gl.glenable(gl_lighting); Ab jetzt Farbberechnung nur noch mit Licht und Oberflächen! Aufrufe von glcolor werden ignoriert! // Einzelne Lichtquellen einschalten (maximal 8) gl.glenable(gl_light0); gl.glenable(gl_light1); gl.glenable(gl_light2);
Licht // Position der i-ten Lichtquelle // Unendlich weit in z-richtung, daher parallele Strahlen float[] position = { 0.0f, 0.0f, 1.0f, 0.0f }; gl.gllightfv( i, GL_POSITION, position ); // Farbe der i-ten Lichtquelle (RGBA) float[] color = { 1.0f, 1.0f, 1.0f, 1.0f } gl.gllightfv( i, GL_DIFFUSE, color); GL_AMBIENT GL_SPECULAR
Licht (Spotlight) // Richtung der Lichtstrahlen float[] direction = { 0.0f, 0.0f, -1.0f } gl.gllightfv( i, GL_SPOT_DIRECTION, direction ); // Öffnungswinkel der Lichtstrahlen gl.gllightf( i, GL_SPOT_CUTOFF, angle); osition // Exponentielles Abfallen von der Mitte zum Rand gl.gllightf( i, GL_SPOT_Exponent, exp); angle direction
Material // Reflektionseigenschaften (RGBA) float[] color = { 1.0f, 0.0f, 0.0f, 1.0 } gl.glmaterialfv( GL_FRONT, GL_DIFFUSE, color ); GL_BACK GL_AMBIENT GL_FRONT_AND_BACK GL_SPECULAR GL_AMBIENT_AND_DIFFUSE GL_EMISSION // Glanz (für specular reflection) gl.glmaterialf( GL_FRONT, GL_SHININESS, factor); GL_BACK GL_FRONT_AND_BACK
CubeSpo Material Weißen, rotierenden Würfel mit einem roten und einem blauen Spotlight anstrahlen. Die Lichtquellen drehen sich nicht mit! Was passiert wenn man backface culling abschaltet? Würde ein nicht-konvexes Objekt richtig dargestellt?
Texturen Idee: Bild auf die Oberflächen von Objekten kleben 1 0 0 1 Texturbild Polygon Jedem Eckpunkt des Polygons einen Punkt der Textur zuordnen. Texturkoordinaten: gltexcoord2f() Farbwerte innerhalb des Polygons durch Interpolation.
Texturen import gl4java.utils.textures.pngtextureloader; // Texturbild lesen (Abmessungen müssen 2er Potenz sein!) PngTextureLoader texload = new PngTextureLoader(gl, glu); texload.readtexture("textures/ambrosil.png"); // Textur in Speicher auf Grafik Karte laden glteximage2d( GL_TEXTURE_2D, 0 1, GL_RGB, texload.getimagewidth(), texload.getimageheight(), 0 2, GL_RGB, GL_UNSIGNED_BYTE, texload.gettexture() ); 1 Mip Maps 2 Rand // Interpolation gltexparameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); GL_TEXTURE_MIN_FILTER GL_NEAREST
Texturen CubeIntersect Zwei rotierende Marmorwürfel nebeneinander zeichnen so dass sie sich überschneiden. Jeder Würfel dreht sich um eine Achse durch seinen Mittelpunkt! Würfel zeichnen in eigene Methode kapseln und zweimal aufrufen. Momentane Matrix auf Stapel speichern bzw. zurück holen: glpushmatrix, glpopmatrix Hidden Surface Problem: gl.glenable(gl_depth_test); z-puffer Algorithmus Warum reicht backface culling nicht mehr aus?
GLU Quadrics Kugel, Zylinder, Scheibe // Quadric erzeugen bzw. löschen long quad = glu.glunewquadric(); glu.gludeletequadric( quad ); // Kugel glu.glusphere( quad, radius, kuchenstücke, stapel ); // Zylinder glu.glucylinder( quad, radius_unten, radius_oben, höhe, kuchenstücke, stapel ); // Scheibe glu.gludisk( quad, radius_innen, radius_außen, kuchenstücke, ringe );
Quadric GLU Quadrics // Darstellung glu.gluquadricdrawstyle( quad, GLU_FILL ); GLU_POINT GLU_LINE GLU_SHILOUETTE // Normalenvektoren automatisch erzeugen glu.gluquadricnormals( quad, GLU_SMOOTH ); GLU_FLAT GLU_NONE // Texturkoordinaten automatisch erzeugen glu.gluquadrictexture( quad, GL_TRUE ); GL_FALSE
EarthMoon GLU Quadrics Flat shading statt smooth shading bei der Berechnung der Normalenvektoren Mond um Erde kreisen lassen (moon.png) Texturen in init-methode laden und auf Grafikkarte speichern // Platz für ID s für 2 Texturobjekte int[] textures = new int[2]; // Texturobjekte erzeugen, ID s in textures speichern gl.glgentextures(2, textures); // i-te Textur zur aktuellen Textur machen gl.glbindtexture(gl_texture_2d, textures[i]);
Positionierung der Kamera glu.glulookat ( double eyex, double eyey, double eyez, // Punktvektor double atx, double aty, double atz, // Punktvektor double upx, double upy, double upz // Richtungsvektor ); eye up at
Positionierung der Kamera glu.glulookat ( double eyex, double eyey, double eyez, // Punktvektor double atx, double aty, double atz, // Punktvektor double upx, double upy, double upz // Richtungsvektor ); In Wirklichkeit wird die gesamte Szene bewegt! Die Kamera steht nach wie vor im Koordinatenursprung und schaut in negative z-richtung. Multiplikation der Model View Matrix mit einer entsprechenden Matrix von links. (Transformation bzgl. Ursprungskoordinatensystem!) Daher: glulookat gleich nach glloadidentity aufrufen!
Positionierung der Kamera Kamera mit Cursor Tasten in x- und y-richtung bewegen. Kamera soll dabei immer gerade aus in negative z-richtung schauen. import java.awt.event.keyevent; import java.awt.event.keylistener; // Klasse Canvas3D ist Key Listener class Canvas3D extends GLAnimCanvas implements KeyListener // Im Konstruktor von Canvas3D addkeylistener(this); // Callback Funktionen public void keytyped(keyevent e){} public void keyreleased(keyevent e){} public void keypressed(keyevent e) { }
LookAt, LookAtView
Pic Picking & Selection Ziel: 3D Objekte mit der Maus anklicken Problem: Verdeckungen (welches Objekt wurde angeklickt?) Umrechnen von Mauskoordinaten in Weltkoordinaten Vorgehen: Bild intern neu zeichnen, allerdings nur einen ca. 4x4 Pixel Ausschnitt um die aktuelle Mausposition. Mitprotokollieren welches Objekt in welcher Tiefe gemalt wurde. Ergebnisliste (Objekte mit Tiefeninformation) auswerten.
Picking & Selection Ausschnitt um die aktuelle Mausposition (x,y) zeichnen. Dazu PickMatrix links an Projektionsmatrix multiplizieren // Projektionsmatrix neu berechnen gl.glloadidentity(); glu.glupickmatrix(x,height-y, 4, 4, viewport); glu.gluperspective(60, 1.0,2.0,4.0); Liste initialisieren, in der die getroffenen Objekte gespeichert werden. Render Mode auf GL_SELECT setzen. int[] namebuffer = new int[100]; gl.glselectbuffer(100,namebuffer); gl.glrendermode(gl_select); gl.glinitnames(); gl.glpushname(0);
Picking & Selection Bild intern neu zeichnen. Objekte durch Zahlen benennen, z.b. gl.glloadname(42); gl.glrectf(-1.0,1.0,-1.0,1.0); Auf Render Mode GL_RENDER zurückschalten. int hits = gl.glrendermode(gl_render); Für jedes getroffene Objekt enthält namebuffer nun vier Zahlen: 1 minimale und maximale Tiefe des getroffenen Ausschnitts Objektname, der während des Zeichnens mit glloadname gesetzt wurde
Picking & Selection Projektionsmatrix muss danach wieder hergestellt werden! Angeklicktes Objekt ist dasjenige mit geringster Tiefe (namebuffer durchsuchen!) Tiefeninformation ist unsigned int. Gibt s in Java aber nicht! int 2 30 unsigned int
Picking & Selection Erde- Mond System erweitern so dass die Planeten angeklickt werden können. Angeklickter Planet soll stehen bleiben und sich beim nächsten Klick weiterbewegen.