Hi,
wie angekündigt gibt es hier nun mein OpenGL Tutorial worauf der kleine OpenGL Kurs aufbauen wird..
Über die geschichte etc von OpenGL werde ich mal nichts erzählen (weiß auchnet sonderlich viel darüber
), und komme gleich richtig zur sache.
Eine sache noch für die leute hier, die OpenGL schon besser können... dieses Tutorial ist für anfänger gedacht! Einige dinge sind evtl umständlich, oder unpassend gemacht.. aber es ist so leichter den sinn zu verstehen
Vorbereitung:
- Delphi starten und ein neues Projekt anlegen
- OpenGL12.pas und Textures.pas hier runterladen.
- OpenGL12 in die Uses der Form packen
Der erste schritt:
Bevor wir mit OpenGL arbeiten können, muß es ersteinmal Initialisiert werden, hierfür benötigen wir 2 Variablen:
Delphi-Quelltext
1: 2: 3:
| var h_DC: hDC; hRC: HGLRC; |
in welchen die Adresse des RenderContext für unsere OpenGL Scene gespeichert wird.
Delphi-Quelltext
1: 2: 3: 4: 5:
| InitOpenGL; h_DC:=GetDC(Handle); SetDCPixelFormat(h_DC); hRC:=wglCreateContext(h_DC); wglMakeCurrent(h_DC,hRC); |
Diese 5 Zeilen sollten in's OnCreate Ereigniss der Form.
InitOpenGL:
Wird erst seit der OpenGL12.pas benötigt und ist wichtig, damit die Variablen, Objekte etc der OpenGL12.pas erzeugt werden.
h_DC:=GetDC(Handle);:
Hier holen wir uns den DeviceContext von unserem Form.
SetDCPixelFormat(h_DC);:
Hier wird das PixelFormat der Scene eingestellt (siehe unten).
hRC:=wglCreateContext(h_DC);:
Erstellt den OpenGL RenderContext (Befehle die mit wgl, bzw gl beginnen sind OpenGL spezifische Befehle. wgl ist zudemnoch Windows Spezifisch)
wglMakeCurrent(h_DC,hRC);:
Diese Zeile gibt an welches der aktuelle RenderContext ist, am anfang eher nebensächlich da wir sowieso nur ein OpenGL Fenster haben.. aber wenn ihr mal ein Programm schreibt wo es mehrere OpenGL Fenster gibt werdet ihr drauf zurückkommen
So, damit währe die Initialisierung schon fast geschafft, fehlt nurnoch die procedur SetDCPixelFormat. Das meißte hier sollte schon vom Namen der Variable her klar sein...
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
| procedure SetDCPixelFormat(Handle: HDC); var nPixelFormat: GLUint; const pfd: PIXELFORMATDESCRIPTOR = ( nSize: sizeof( PIXELFORMATDESCRIPTOR ); nVersion: 1; dwFlags: PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER; iPixelType: PFD_TYPE_RGBA; cColorBits: 16; cRedBits: 0; cRedShift: 0; cGreenBits: 0; cBlueBits: 0; cBlueShift: 0; cAlphaBits: 0; cAlphaShift: 0; cAccumBits: 0; cAccumRedBits: 0; cAccumGreenBits: 0; cAccumBlueBits: 0; cAccumAlphaBits: 0; cDepthBits: 16; cStencilBits: 0; cAuxBuffers: 0; iLayerType: PFD_MAIN_PLANE; bReserved: 0; dwLayerMask: 0; dwVisibleMask: 0; dwDamageMask: 0 ); begin nPixelFormat:=ChoosePixelFormat(h_DC, @pfd); SetPixelFormat(h_DC,nPixelFormat,@pfd); end; |
und damit wäre die Initialisierung fertig
Als nächstes schreiben wir uns eine kleine eigene Procedur, namens
InitGL welche so aussehen sollte:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| procedure TForm1.InitGL; begin glClearColor(0,0,0,0); glClearDepth(1); glDepthFunc(GL_LESS); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST);
glViewport(0,0,Width,Height); glMatrixMode(GL_PROJECTION); glLoadIdentity; gluPerspective(45,Width/Height,1,100); glMatrixMode(GL_MODELVIEW); end; |
glClearColor:
Hiermit bestimmen wir die Hintergrundfarbe unserer Scene. (Ganz genau genommen wird wenn dieser Befehl aufgerufen wird die komplette Scene it der hier angegebenen Farbe übermalt)
glClearDepth:
Hiermit wird unser Tiefenbuffer geleert. (Komme ich später genauer drauf zurück)
glDepthFunc:
Der hier verwendete Parameter gibt an, wie der TiefenTest von OpenGL Funktioniert. GL_LESS z.B. bedeutet das die Objekte die näher beim Betrachter liegen die weiter entfernten überdecken. Bei GL_GREATER wäre es z.B. genau andersrum.
glShadeModel:
Gibt an welche ShadingTechnik verwendet werden soll. GL_SMOOTH wird für weiche kanten benutzt, GL_FLAT für harte.
glEnable:
Mit glEnable werden div. funktionen von OpenGL aktiviert, in diesem fall wird GL_DEPTH_TEST aktiviert, also unser Tiefentest. (Welcher dafür verantwortlich ist das weiter entfernte objekte von den näheren überdeckt werden)
glViewport;
Viewport ist gibt die fläche an, in der die Scene angezeigt wird. i.d.R. wählt man hier die komplette fläche, aber es kann u.U. sinnvoll sein mal nur einen kleinen ausschnitt zu benutzen.
glMatrixMode:
Stellt unsere WeltMatrix um. In dem fall auf die ProjektionsMatrix.
mit GL_MODELMATRIX kommen wir zurück in die ModellierungsMatrix.
glLoadIdentity:
Mit glLoadIdentity wird die Scene zurück auf 0 gesetzt. Also, wenn man die Scene rotiert und verschiebt, kann man hiermit einfach auf den ausgangspunkt bei 0 zurückgehen.
gluPerspective:
Bedeutet das wir eine PerspektivKamera benutzen (keine Orthogonale). Der erste Parameter gibt den KameraWinkel an, der zweite das verhältniss.
Parameter 3 und 4 sind die sogenannte clipping plane... also alles was ausserhalb der ClippingPlane ist, wird nicht gerendert (in dem fall alles was näher als 1 Einheit, oder weiter weg als 100 Einheiten ist)
So, nun als letztes noch eine Procedur
DrawScene, und alles ist perfekt
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| procedure TForm1.DrawScene; begin glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glLoadIdentity; SwapBuffers(h_DC); end; |
glClear:
Leert wie der name schon sagt die Scene, in diesem fall wird die Scene mit der Farbe die bei der Initialisierung bei glClearColor angegeben wurde übermalt und der TiefenBuffer geleert.
glLoadIdentity:
Setzt unsere Scene wieder auf den 0 Punkt zurück. (siehe oben)
SwapBuffers:
Ist nur wichtig wenn ihr bei eurem PixelFormat DoubleBuffered eingestellt habt, hiermit wird zwischen den beiden Buffern hin und her gewechselt.
So, als allerletztes müßen wir die 2 Proceduren noch aufrufen.
InitGL wird am ende vom OnCreate ereigniss aufgerufen.
DrawScene im OnIdle der Scene.
Hierfür benutzt ihr am besten für den anfang die TApplication Komponente, ein doppelklick auf den OnIdle Event der Kompo:
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); begin DrawScene; Done:=False; end; |
Done müßt ihr auf alle fälle auf False setzen, da sonst das OnIdle Event nicht wieder aufgerufen wird. (Siehe OnlineHilfe)
Wenn ihr jetzt auf F9 drückt solltet ihr eine leere OpenGL Scene vor euch haben...
Ok, aber eine leere Scene ist nicht so das wahre... nicht wahr..?
Kommen wir also nu ndazu, wie wir die Scene füllen.
Als erstes mal gibt es 3 wichtige Befehle:
glTranslate(X, Y, Z: Single);
Wie der Name schon sagt wird hiermit die Scene um die angegeben Koordinaten verschoben.
glTranslate(2,0,0) würde unsere Scene z.B. um 2 Einheiten in X-Richtung verschieben.
glRotate(Angle, X, Y, Z: Single);
Hiermit rotieren wir die Scene um den angegeben Winkel (Angle) auf der angegeben Achse.
um die Scene z.B. um 27° auf der Z Achse zu drehen würde es so aussehen: glRotate(27,0,0,1);
glScale(X, Y, Z: Single);
Hiermit Scalieren (Zoomen) wir unsere Scene... z.B:
glScale(2,2,2) würde alle Objekte doppelt so groß zeichnen wie sie sind.
glScale(0.5,0.5,0.5) alle nur halb so groß.
oder glScale(2,1,1) würde das Objekt auf der X-Achse strecken.
Diese 3 Befehle werden wohl die sein, die euch am anfang mit am häufigsten begegnen... auch wenn sie für leute die nochnie irgendwas mit 3D Kram gemacht haben sehr verwirrend sein können.. wenn ihr es erstmal ausprobiert habt, kapiert ihr da schon
(Die die 3D Programme benutzen sind hier klar im Vorteil *g*)
Als nächstes kommen wir zu den Farben...
Eine farbe wird in OpenGL mit
glColor3f angegeben, wobei das 3f am ende dafür steht das hier 3 Parameter vom typ f (float) erwartet werden. ein 2i würde bedeutetn das 2 Integer Werte erwartet werden.. 4ub = 4 Paratemer vom typ Bate..
ok, glColor3f erwartet also 3 Single-Werte.
Das was viele hier verwirren wird ist das Farbschema von OpenGL, denn Farben werden hier in der regel nicht von 0..255 vergeben, sondern nur von 0..1
Ein paar Beispiele:
Weiß: 255,255,255 ist in OpenGL 1,1,1
Schwarz: 0,0,0 = 0,0,0
Grün: 0,255,0 = 0,1,0
HellesRot: 0,128,0 = 0,0.5,0
etc etc etc... Im grudne ganz einfach
Für die, die sich daran absolut nicht gewöhnen können/wollen bleibt noch glColor3ub wo als Parameter herkömmliche Byte Werte erwartet werden... (aber ich würde euch empfehlen euch gleich an den OpenGL standard zu gewöhnen, da er an anderen stellen öffters mal in dieser Form gebraucht wird)
Kommen wir also nun zum eigentlichen Zeichnen unserer Scene.
In OpenGL kann man nur folgene grunddinge Zeichnen:
Einen einfachen punkt (Pixel) (GL_POINTS)
Eine Linie (GL_LINES)
Mehrere Verbundene Linien (GL_LINE_STRIP)
Mehrere Verbundene Linien wo die Anfangs Koordinate = End Koordinate (GL_LINE_LOOP)
Ein Dreieck (GL_TRIANGLES)
Mehrere Verbundene Dreiecke (GL_TRIANGLE_STRIP)
Ein "Dreiceksfächer" (GL_TRIANGLE_FAN)
Ein Quadrat (GL_QUADS)
Mehrere Verbundene Dreiecke (GL_QUAD_STRIP)
Ein Polygon (GL_POLYGON)
uns interressiert als erstesmal nur das einfache Quadrat.
Delphi-Quelltext
1: 2: 3:
| glBegin(GL_QUADS); glEnd; |
Hier teilen wir OpenGL mit das wir gleich ein Quad Zeichnen möchten (glBegin), und wenn wir fertig sind damit (glEnd)
Die Koordinaten werden in OpenGL mit glVertex übergeben, da wir das ganze 3D und nicht 2D haben möchten benutzen wir glVertex3f = 3 Float Parameter für X, Y und Z.
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| glBegin(GL_QUADS); glVertex3f(1,-1,-5); glVertex3f(1,1,-5); glVertex3f(-1,1,-5); glVertex3f(-1,-1,-5); glEnd; |
Hier übergeben wir OpenGL also 4x3 Koordinaten, welche die Eckpunkte unseres Quadrates ergeben.
Das -5 am ende ist unsere Z Koordinate. Wenn wir hier 0 hinmachen würden, würden wir nix sehen da wir dann in dem Quadrat drinständen. Deswegen bewegen wir die Koordinate in den Bildschrim hinein um 5 Einheiten.
Das ganze könnte man aber auch so lösen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| glTranslate(0,0,-5); glBegin(GL_QUADS); glVertex3f(1,-1,0); glVertex3f(1,1,0); glVertex3f(-1,1,0); glVertex3f(-1,-1,0); glEnd; |
Wir schieben die ganze Scene also ersteinmal in den Bildschirm hinein und zeichen dann das Quad.
Wenn ihr jetzt F9 drückt, solltet ihr ein Weißes Quadrat in der mitte eurer Scene sehen.
Machen wir das ganze mal ein wenig Bunt, um zu verdeutlichen welcher Koordinate wo auf dem Bildschirm ist
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| glTranslate(0,0,-5); glBegin(GL_QUADS); glColor3f(1,0,0); glVertex3f(1,-1,0); glColor3f(1,1,0); glVertex3f(1,1,0); glColor3f(0,1,0); glVertex3f(-1,1,0); glColor3f(0,0,1); glVertex3f(-1,-1,0); glEnd; |
Hier weisen wir also jeder Koordinate eine eigene Farbe zu, womit recht gut verdeutlicht wird welche Koordinate wo gezeichnet wird
Wenn wir jetzt das ganze Quad z.B. Rot haben wollen, brauchen wir nicht jeder Koordinate ihre eigene Farbe zuzuweisen, sondern können am anfang vor dem Zeichnen einmal glColor3f(1,0,0) aufrufen, dann wird die Farbe rot solange benutzt bis man wieder eine andere farbe wählt.
So, als nächstes wollen wir unser Quad mal rotieren lassen, ok?
Hierfür brauchen wir als erstes eine Globale Variable.
Delphi-Quelltext
1: 2:
| var RotateX: Single; |
nun setzen wir hinter unser glTranslate ein glRotate:
Delphi-Quelltext
1: 2: 3: 4:
| glTranslate(0,0,-5); glRotate(RotateX,1,0,0); glBegin(GL_QUADS); [...] |
Bedeutet, wir drehen unsere Scene auf der X achse um den Wert RotateX.
Jetzt noch diese 3 Zeilen an's ende unserer Draw Procedur:
Delphi-Quelltext
1: 2: 3:
| RotateX:=RotateX + 1; if RotateX>360 then RotateX:=RotateX - 360; |
Hier erhöhen wir also RotateX dauernd um 1, und sobald es größer als 360 ist, ziehen wir 360 ab.
Ein druck auf F9 müßte unser Quad nun rotieren lassen.
Hier solltet ihr nun auch den ersten 3D Effekt sehen
So, als letztes für dieses Tutorial werde ich euch nun noch Zeigen wir ihr Texturen verwenden könnt, hierfür braucht ihr die Textures.pas aus dem ZIP (Siehe oben) und müßt diese in das Projekt einbinden.
Der grund warum wir hier eine externe datei verwenden ist ganz einfach, es ist extrem kompliziert sowas selbst zu machen
Ich hab mich die letzten Tage hingesetzt und mal eine Texture-Unit geschrieben, davor habe ich auch immer fremde genommen dafür.
Ok, nachdem wir die Unit nun eingebunden haben, brauchen wir mal wieder eine neue Varbiable:
Delphi-Quelltext
und am Ende von OnCreate (es muß NACH der Initialisation von OpenGL geschehen, da sonst kein Speicher auf der GrafikKarte für die Textur reserviert wird.):
Delphi-Quelltext
1:
| MyTex:=TTextur.Create('C:\Programme\Delphi\Bla.bmp'); |
Als format gehen hier Bitmaps, JPEG und TGA Dateien.
Bevor wir die Textur nun aber verwenden können, müßen wir unsere Zeichen Routine noch ein wenig abändern. Hier kommt nun der befehl glTexCoord2fzur geltung.
mit glTexCoord2f geben wir die Koordinaten für unsere Textur an, also die TexturKoordinaten
Da eine Textur 2D und nicht 3D ist, gibt es hier nur 2 Parameter.
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| glBegin(GL_QUADS); glTexCoord2f(1,0); glVertex3f(1,-1,0); glTexCoord2f(1,1); glVertex3f(1,1,0); glTexCoord2f(0,1); glVertex3f(-1,1,0); glTexCoord2f(0,0); glVertex3f(-1,-1,0); glEnd; |
Ihr seht, wir müßen für jede Koordinate des Quadrates auch die TexturKoordinate festlegen. (Spielt damit einfahc mal ein wenig rum, wenn ihr es seht versteht ihr es schon..
)
So, nun müßen wir OpenGL noch mitteilen das wir Texturen benutzen möchten, das tun wir wieder im OnCreate, also bei der Initialisierung der Scene.
Delphi-Quelltext
1:
| glEnable(GL_TEXTURE_2D); |
Wir aktivieren also Texturierung.
So, nun noch angeben welche Textur wir möchten, und fertig ist's:
Delphi-Quelltext
1: 2: 3: 4: 5:
| [...] glRotate(RotateX,1,0,0); MyTex.Bind; glBegin(GL_QUADS); [...] |
So, ein druck auf F9 sollte euch nun eure Textur auf dem Quad zeigen welches rotiert...
Und ich denke das reicht für den einstieg erstmal... am besten ihr spielt nun ein wenig damit rum, indem ihr z.B. aus 6 Quads einen Würfel bastelt etc.
Oder ihr verändert das RotateX nicht in der RenderLoop, sondern z.B. bei einem druck auf eine Taste.. so das ihr das Quad mit den Tasten rotieren könnt
etc etc etc...
Hoffe das tutorial hat euch gefallen.
Au'revoir,
Aya~
PS: Ich will ja nun zu dme Tutorial hier noch einen kleinen IRC Kurs geben, hierzu bitte nur im OffTopic Thread reden, denn hier sollen nur die dinge hin, die für das Tutorial wichtig sind. Also Termin etc für das IRC Treffen wird im OffTopic ausgemacht.