In diesem Tutorial werde ich euch zeigen, wie ihr euer OpenGL-Programm startklar für das Rendering eurer ersten Objekte macht und damit ein simples Dreieck darstellen könnt.

Nachdem ihr euer Visual Studio Projekt aus dem letzten Tutorial mit GLFW eingerichtet habt, könnt ihr nun die entsprechenden Dateien für euer Projekt erstellen und den GLFW-Header einbinden. Anschließend werdet ihr ein OpenGL-Fenster öffnen und dort euer erstes Polygon präsentieren.

OOP-Grundgerüst erstellen

Für alle die noch nicht viel mit objektorientierten Programmieren in C++ am Hut haben, werde ich hier kurz das Prinzip erläutern. Grundsätzlich bestehen alle Klassen immer jeweils aus zwei Dateien: dem Header und der Implementierung.

  • Im Header wird lediglich die Klasse mitsamt ihren Variablen und Methoden defininiert. Die .h-Datei hat daher keine Funktion, sondern dient als Schema für die Implementierung.
  • In der .cpp-Datei wird diese definierte Klasse nun implementiert. Hier findet die ganze Logik der Methoden statt. Der vorher erstellte Header wird dabei am Anfang eingebunden.

In der Main-Funktion des Programms, welche sich bei mir meistens in einer main.cpp befindet, arbeitet ihr anschließend mit diesen Klassen bzw. deren Objekten.

Euer Programm sollte für dieses Tutorial letztendlich so aufgebaut sein:

  • Main.cpp: Hier wird ein Objekt der OpenGL-Klasse angelegt und dessen Methoden aufgerufen.
  • Main.h: In diese Header-Datei steckt ihr möglichst alle externen includes. Sie wird in der Main.cpp und OpenGL.cpp eingebunden.
  • OpenGL.cpp: Implementiert die OpenGL-Klasse. Hier kommt die gesamte Logik hinein.
  • OpenGL.h: Definiert die OpenGL-Klasse, ihre Methoden und Variablen. Sie wird in der Main.cpp und OpenGL.cpp eingebunden.

Erstellt also zunächst einmal diese vier Dateien in eurem Projekt und bindet wie gerade beschrieben die Header in den .cpp-Files ein. Vergesst bei den Headern den include-Guard nicht, um das mehrfache Einbinden durch den Compiler zu verhindern:

#ifndef MAIN_H_INCLUDED
#define MAIN_H_INCLUDED

//Includes, etc.

#endif

und

#ifndef OPENGL_H_INCLUDED
#define OPENGL_H_INCLUDED

//Die OpenGL-Klassendefinition

#endif

Main.h: GLFW-Header einbinden

Beim Download von GLFW war unter anderem ein include-Ordner dabei. Diesen kopiert ihr nun in euren Projektordner zu den soeben erstellten .cpp- und .h-Dateien.

In eurer Main.h bindet ihr nun den darin befindlichen GLFW.h-Header ein. Wenn ihr schon dabei seid, bindet am bestenauch gleich den stdlib-Header (C Standard General Utilities Library) ein, um z.B. per Exit() jederzeit das Programm beenden zu können.

//Main.h - Beinhaltet externe includes, etc. für das Programm.
#ifndef MAIN_H_INCLUDED
#define MAIN_H_INCLUDED

#include <stdlib.h>
#include "include/GL/glfw.h"

#endif

Damit seid ihr nun definitiv startbereit, um mit dem eigentlichen Programmieren zu beginnen.

OpenGL.h: OpenGL-Klasse im Header definieren

Ich werde euch nun fortan das meiste direkt im Code selbst erklären. Ihr könnt ihn natürlich so wie er ist in eure Anwendung kopieren. Am Ende des Artikels könnt ihr aber auch das fertige Projekt für dieses Tutorial herunterladen.

//OpenGL.h - Definiert die OpenGL-Klasse.
#ifndef OPENGL_H_INCLUDED
#define OPENGL_H_INCLUDED

#include "main.h"

class OpenGL
{
public:
	/* Methoden */

	//Konstruktor dem die Höhe und Breite des Fensters übergeben werden.
	OpenGL(int w, int h);

	//Destruktor - Schließt Anwendungsfenster
	~OpenGL();

	//In der MainLoop werden in einer Endlosschleife Update und Draw aufgerufen. 
	//Sie endet erst, wenn running = false ist.
	void MainLoop();

private:
	/* Methoden */

	//Hier wird das Fenster geöffnet und der Rest für die OpenGL-Szene initialisiert.
	void Init();

	//Hier findet sämtliche Logik statt, die in jedem Frame aufgerufen wird.
	void Update();

	//In der Draw-Methode werden die Objekte in den Backbuffer gezeichnet.
		//Erklärung: Alles wird zunächst in den Backbuffer gezeichnet und 
		//erst am Schluss der Back- mit dem Frontbuffer getauscht.
		//Das Bild welches wir sehen ist immer der Frontbuffer.
		//Durch dieses Prinzip erscheint das Bild stets ruhig und flüssig.
	void Draw();


	/* Variablen */

	//True wenn das Programm laufen soll. 
	//Kann z.B. durch Esc auf false gesetzt werden um das Programm am Ende des Durchlaufs zu beenden.
	bool running;

	//Auflösung
	int width;
	int height;
};

#endif

OpenGL.cpp: OpenGL-Klasse implementieren

Nun kommen wir zur eigentlichen Funktion der OpenGL-Anwendung. Nachdem ihr das Fenster via GLFW initialisiert habt, wird via Endlosschleife die Logik bzw. das Zeichnen der Objekte für jeden Frame erledigt.

In diesem Fall wird in der Draw()-Methode ein weißes Dreieck gezeichnet. Dies geschieht im sogenannten Immediate-Modus, in welchem der Grafikkarte direkt gesagt wird, was sie zu zeichnen hat. In einem späteren Tutorial werde ich euch zeigen, wie ihr eure Objekte mithilfe von OpenGL 1.5 vorher in den Grafikspeicher hochladet und so eure Performance um ein Vielfaches steigern könnt.

//OpenGL.cpp - Implementiert die OpenGL-Klasse und beinhaltet sämliche Funktionen und Logik.
#include "opengl.h"

//Konstruktor
OpenGL::OpenGL(int w, int h)
{
	//Übergibt die Breite und Höhe aus den Argumenten an die entsprechenden Variablen.
	width = w;
	height = h;

	//Ruft die Init-Methode zur Initialisierung auf.
	Init();
}

//Destruktor - Schließt Anwendungsfenster
OpenGL::~OpenGL()
{
	glfwTerminate();
}

//Hier wird das Fenster geöffnet und der Rest für die OpenGL-Szene initialisiert.
void OpenGL::Init()
{
	//GLFW muss anfangs initialisiert werden.
	glfwInit();

	//Erstellt das Anwendungsfenster mit den gewünschten Parametern. Beendet Programm wenn etwas schief läuft.
		//width = Breite des Fensters
		//height = Höhe des Fensters
		//8, 8, 8 = Die ersten drei 8er bestimmen die Anzahl der Rot-, Grün- und Blau-Bits und 
		//damit die Anzahl der Farben (hier: 24-bit)
		//8 = Der vierte 8er bestimmt die Anzahl der Alpha-Bits für die Transparenz
		//24 = Bestimmt die Anzahl der Depth-Buffer-Bits
		//0 = Bestimmt die Anzahl der Stencil-Buffer-Bits (z.B. für Schatten)
		//GLFW_WINDOW = Bestimmt, dass die Anwendung im Fenstermodus ausgeführt wird; GLFW_FULLSCREEN = Vollbild.
	if(!glfwOpenWindow(width,height,8,8,8,8,24,0,GLFW_WINDOW)){
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	//Das Fenster läuft nun. Daher auf true setzen.
	running = true;

	//Gibt dem Fenster einen Titel
	glfwSetWindowTitle("Mein erstes Polygon");

	//Legt die Hintergrundfarbe fest
	glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
}

//In der MainLoop werden in einer Endlosschleife Update und Draw aufgerufen. 
//Außerdem werden die Fenstergröße aktualisiert und die Buffer geleert.
//Sie endet erst, wenn running = false ist.
void OpenGL::MainLoop()
{
	do
	{
		//Aktualisiert Fenstergröße beim Verschieben/Vergrößern/Verkleinern des Fensters.
		glfwGetWindowSize( &width, &height );

		//Leere Buffer
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		//Nun ruft ihr jeden Frame Update() und Draw() auf.
		Update();
		Draw();

		//Säubert die Buffer und fordert die Befehle darin auf, so schnell wie möglich fertig zu werden
			//Besonders wichtig für Netzwerk und Eingabe.
		glFlush();

		//Tauscht den Back- mit dem Frontbuffer. 
			//Erklärung dazu siehe Kommentar bei Draw() in der Header-Datei
		glfwSwapBuffers();
	}
	while(running);
}
	
//Hier findet sämtliche Logik statt, die in jedem Frame aufgerufen wird.
void OpenGL::Update()
{
	//Setzt beim Druck der Esc-Taste oder beim Schließend des Fensters running auf false, 
	//wodurch das Programm beendet wird.
	if(glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam( GLFW_OPENED ))
	{
		running = false;
	}
}

//In der Draw-Methode werden die Objekte in den Backbuffer gezeichnet.
void OpenGL::Draw()
{
	//Zeichnet ein Dreieck im Immediate-Modus, der relativ langsam ist, aber für ein 2D-Polygon vorerst reicht.
	//Zunächst wird festgelegt was gezeichnet wird (TRIANGLES).
	glBegin(GL_TRIANGLES); 

		//Dann werden die einzelnen Vertices im Raum gezeichnet (x = links/rechts, y = oben/unten, z = vorne/hinten), 
		//wobei immer drei Vertices zu einem Dreieck verbunden werden.
		glVertex3f( 0.0f, 1.0f, 0.0f);
		glVertex3f( 1.0f,-1.0f, 0.0f);
		glVertex3f(-1.0f,-1.0f, 0.0f);
	glEnd();
}

Main.cpp: Verwendung der OpenGL-Klasse

Nachdem ihr nun eure Klasse für das Rendern mit OpenGL erstellt habt, müsst ihr von dieser nur noch ein Objekt erstellen und die MainLoop()-Methode aufrufen. Dies geschieht in der Main.cpp.

//Main.cpp - Haupt- und Startteil des Programms. Hier wird ein OpenGL-Objekt erstellt und gestartet
#include "main.h"
#include "opengl.h"

//Die Main-Funktion wird beim Programmstart aufgerufen
int main(int argc, char **argv) 
{
	//Erstellt das OpenGL-Objekt mit der Auflösung 800x600.
	OpenGL* ogl = new OpenGL(800,600);
	
	//Startet die MainLoop, die in der Endlosschleife läuft bis das Programm beendet wird.
	ogl->MainLoop();

	//Löscht das OpenGL-Objekt und ruft dessen Destruktor auf
	delete ogl;

	return 0;
}

Im Großen und Ganzen habt ihr damit ein Grundgerüst für OpenGL-Anwendungen mit der GLFW-Library geschaffen. Bei etwaigen Problemen hinterlasst einfach einen Kommentar und ich werde versuchen euch bei euren Problemen zu helfen ;)

Fertiges OpenGL-First-Polygon-Projekt als ZIP herunterladen