OpenGL mit SDL und GLEW auf dem Raspberry Pi
In diesem Tutorial zeige ich wie du OpenGL mit SDL und GLEW auf dem Raspberry Pi implementierst. Aktuell beschäftige ich mich wieder intensiv mit der Grafikprogrammierung, diese ist mein geheimes Steckenpferd. Ich weiß nicht wieso, aber die Arbeit mit einer Grafik API (OpenGL,DirectX, Vulkan, …) macht mir richtig viel Spaß.
OpenGL mit SDL und GLEW auf dem Raspberry Pi
Im Studium war die Grafikprogrammierung hauptsächlich auf Windows und den Konsolen konzentriert. Linux war unbedeutend und die mobilen Geräte noch nicht leistungsstark genug. Das ist aktuell ganz anders, weshalb ich nun verstärkt diese Geräte betrachte. Allen voran der Raspberry Pi. Zum Thema Grafikprogrammierung am Raspberry Pi gibt es viel zu wenig interessante Artikel, perfekt also für meinen Blog da ein paar interessierte Leser zu gewinnen.
Bibliotheken installieren
Bevor du mit dem Programmieren beginnen kannst brauchst du zwei Bibliotheken. SDL und GLEW. Unter Linux (genauer gesagt Debian) werden diese wie folgt installiert:
sudo apt-get install libsdl2-dev libglew-dev
Was hast du da installiert und wozu brauchst du das?
- SDL
Die SDL Bibliothek (Simple DirectMedia Layer) bietet low-level Zugriff auf die Hardware unter anderem auch die Grafikkarte. Das alles systemunabhängig, weshalb man mit ein Programm Plattform überreifend erstellen kann. Wir verwenden die SDL im ersten Schritt nur dazu ein Fenster zu erstellen. In das zeichnen wir dann mit OpenGL. - GLEW
GLEW ist jene Bibliothek um moderne OpenGL Erweiterungen verwenden zu können. Wir brauchen diese hauptsächlich deshalb, weil unter Windows nur OpenGL 1.1 ausgeliefert wird und wir dadurch auch neuere Funktionen verwenden können (Stichwort Shader). Unter Linux ist das zwar nicht unbedingt notwendig, da wir aber Plattform übergreifend entwickeln müssen wir das so machen.
Einfaches Beispiel
Am besten beginnt man mit einem recht einfachen Beispiel. Das Setup, also bis wir endlich ein Fenster erzeugt und dort etwas mit OpenGL gezeichnet haben, ist der schwierigste Part. Dank der Verwendung von SDL wird das Rahmenprogramm stark gekürzt und ist relativ einfach verständlich. Du solltest aber bereits halbwegs gut C++ programmieren können. Der Source Code:
#include #define GLEW_STATIC // damit GLEW statisch gelinkt werden kann #include <GL/glew.h> #define SDL_MAIN_HANDLED // eigene Hauptfunktion für SDL #ifdef _WIN32 // nur für Windows #include #ifdef _WIN32 // nur für Windows #include <SDL.h> #pragma comment(lib, "SDL2.lib") #pragma comment(lib, "glew32s.lib") #pragma comment(lib, "opengl32.lib") #else #include <SDL2/SDL.h> // Linux / MacOS #endif struct Vertex { float x; float y; float z; }; int main(int argc, char** argv) { // Fenster initialisieren SDL_Window* window; if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl; std::cin.get(); return 1; } // OpenGL initialisieren // Frame Buffer definieren SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); // 8 bit für rot SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); // 8 bit für grün SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); // 8 bit für blau SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); // 8 bit für alpha SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32); // 32 bit für Pixel (optional) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // Double Buffering aktivieren // Fenster erzeugen window = SDL_CreateWindow("OpenGL Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_OPENGL); SDL_GLContext glContext = SDL_GL_CreateContext(window); // GLEW initialisieren (Context muss bereits existieren) GLenum err = glewInit(); if (err != GLEW_OK) { std::cout << "glewInit Error: " << glewGetErrorString(err) << std::endl; std::cin.get(); return 2; } std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl; // die Eckpunkte des Dreiecks definieren Vertex vertices[] = { Vertex{-0.5f, -0.5f, 0.0f}, Vertex{ 0.0f, 0.5f, 0.0f }, Vertex{ 0.5f, -0.5f, 0.0f } }; uint32_t numVertices = 3; // Buffer auf der Grafikkarte erstellen GLuint vertexBuffer; glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex), vertices, GL_STATIC_DRAW); // OpenGL Vertex Format angeben glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), offsetof(struct Vertex, x)); // Game Loop bool close = false; do { // Buffer löschen (mit Farbe initialisieren) glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // mit welcher Farbe soll gelöscht werden? glClear(GL_COLOR_BUFFER_BIT); // Buffer löschen glDrawArrays(GL_TRIANGLES, 0, numVertices); // Double Buffering (angezeigter Buffer tauschen) SDL_GL_SwapWindow(window); // Events abfragen SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { close = true; } } } while (!close); SDL_Quit(); return 0; }
Es reicht, wenn du eine neue Datei main.cpp erstellst und den Code dort hinein kopierst. Diese Quelldatei kann auf der Kommandozeile mit dem folgenden Befehl zur ausführbaren Datei openglengine kompiliert werden:
g++ -g -std=c++11 main.cpp -o openglengine -lGL -lSDL2 -lGLEW
Wer den Komfort einer IDE lieber hat kann beispielsweise CodeLite verwenden. Ich habe diese C++ IDE auf dem Raspberry Pi in einem anderen Artikel bereits vorgestellt. Ausführen kann man das neu erstellte Programm mit einem Doppelklick oder von der Konsole mit:
Achtung: man muss das von einem grafischen Desktop aus starten. D.h. über SSH werden wir kein Fenster erstellen können.
Achtung: am Raspberry Pi ist aktuell OpenGL Version 3.1 installiert. Man muss das bei der Verwendung von GLEW Funktionen beachten. Unter Windows auf einer aktuellen Grafikkarte hat man sehr wahrscheinlich eine OpenGL Version von 4.6, weshalb Programme die dort erstellt werden möglicherweise am Raspberry Pi nicht funktionieren.
Ich habe das Programm aus der CodeLite IDE heraus gestartet und das sieht dann beispielsweise so aus:
Fazit
Wow, wir können mit OpenGL ein Dreieck mit drei Punkten zeichnen. Das klingt jetzt nicht mega toll, der erste Schritt ist aber nun getan. Es wird nun sehr einfach neue Funktionen hinzuzufügen und Dynamik in unser Programm zu bringen. Im nächsten Schritt schreiben wir das Programm noch so um, dass es die aktuelle Grafikpipeline mit Shadern verwendet.