Programmieren unter MS-Windows

Kapitel 20: Windows NT - 3D-Graphik mit OpenGL

Letzte Änderung: 17.3.97 von B. Tritsch

Überblick

Zurück zum Index "PC- und MS-Windows-Support"

Zurück zum Inhalt


OpenGL - Eine Einführung

OpenGL wurde von Silicon Graphics, Inc. (SGI) entwickelt und ist eine verbreitete Geräte- und Betriebssystem-unabhängige Bibliothek für die Erstellung von dreidimensionalen Graphiken. Sowohl Windows NT als auch Windows 95 bieten Unterstützung für OpenGL. Zusätzlich zur Standard-OpenGL-Bibliothek, werden unter MS-Windows zusätzlich eine Reihe von Funktionen zur Verfügung gestellt, die eine nahtlose Integration in das Konzept der GDI Device Contexts bieten. Diese zusätzlichen Funktionen werden durch ein führendes wgl identifiziert.

Der Sinn der OpenGL-Bibliothek ist es, zwei- und dreidimensionale Objekte in einen Frame Buffer zu "rendern", der mit einem Pixelspeicher in der Graphik-Hardware eines PCs vergleichbar ist. OpenGL ist streng prozedural. Das heißt, daß nicht das Aussehen eines Objekts beschrieben wird sondern wie das Objekt gezeichnet wird. Hierzu bedient sich OpenGL zwei- oder dreidimensionalen Vertices. Diese repräsentieren einen Punkt, z.B. den Endpunkt einer Linie oder die Ecke eines Polygons. Die nächste Ebene sind Primitive, die aus einem oder mehreren Vertices bestehen.

Wie Vertices zu Primiteven zusammengesetzt werden und in den Frame Buffer gezeichnet werden, wird durch eine Vielzahl von Einstellmöglichkeiten kontrolliert. Eine Applikation kann beispielsweise eine dreidimensionale Transformationsmatrix spezifizieren, die definiert wie die Koordinaten eines Objekts in die Koordinaten einer Zeichenoberfläche transformiert werden. Mit OpenGL kann man weiterhin Oberflächen zeichnen, Beleuchtungsspezifikationen anwenden und Texturen verwenden. Weiterhin kann man mit OpenGL einzelnen Pixel manipulieren, abhängig von Angaben wie Pixeltiefe oder Durchsichtigkeit.

OpenGL und Windows-Programmierung

Bevor die OpenGL-Bibliothek unter MS-Windows verwendet werden kann, müssen eine Reihe von Initialisierungsschritte ausgeführt werden. Jede Windows-OpenGL-Applikation muß seinen Rendering Context mit seinem Device Context assoziieren.Hierzu muß zuerst die Win32-Funktion SetPixelFormat und danach die Funktion wglCreateContext mit dem Device Context Handle als Parameter aufgerufen werden. Ist dies erfogreich, gibt wglCreateContext einen Rendering Context Handle des Typs HGLRC zurück.

OpenGL unter Windows kennt zwei Typen von Pixel-Modi: Einen Modus, der die direkte Angabe von Pixelfarben erlaubt und einen Modus, der die Auswahl der Farbe aus einer Palette unterstützt. Es gibt spezielle Anforderungen durch OpenGL an ein Ausgabefenster. Es muß mit den Fensterstilen WS_CLIPSIBLINGS und WS_CLIPCHILDREN initialisiert sein um OpenGL-Kompatibilität zu gewährleisten. Um die Performance der Applikation zu erhöhen sollte das Fenster einen NULL-Hintergrund-Brush haben, da dieser sowieso durch die OpenGL-Bibliothek gelöscht wird.

Bevor ein Rendering Context verwendet werden kann, muß er mit der wglMakeCurrent-Funktion als der aktuelle Context bestimmt werden. Ist der Rendering Context bereit um Kommandos entgegenzunehmen können weitere Initialisierungs-Routinen aufgerufen werden, so z.B. um den Frame Buffer zu löschen, Koordinatentransformationen aufzusetzen, Lichtquellen zu konfigurieren oder andere Optionen zu setzen. Ein solcher Initialisierungsschritt, der nicht ausgelassen werden darf, ist der Aufruf der glViewport-Funktion. Sie initialisiert oder modifiziert die Größe des Rendering Viewports. Typischerweise wird diese Funktion ganz zu Anfang der Applikation aufgerufen und dann jedesmal wenn sie eine WM_SIZE-Meldung erhält. Diese zeigt eine Größenänderung des Fensters an.

Eine OpenGL-Beispielanwendung in C

Eine OpenGL-Applikation besteht aus einer Reihe von Vertex-Operationen, die in Paaren von glBegin- und glEnd-Aufrufen eingeschlossen sind. Der glBegin-Aufruf spezifiziert den Primitivtyp, der durch die folgenden Vertex-Operationen definiert wird. Die nachfolgende Liste zeigt die möglichen Typen der Primitive auf:

glBegin-Parameter

Beschreibung

GL_POINTS Eine Serie von Punkten
GL_LINES Eine Serie von Linien
GL_LINE_STRIP Eine verbundene Gruppe von Liniensegmenten
GL_LINE_LOOP Eine verbundene, geschlossene Gruppe von Liniensegmenten
GL_TRINGLES Ein Satz von Dreiecken
GL_TRIANGLE_STRIP Ein Satz von verbundenen Dreiecken
GL_TRIANGLE_FAN Ein Satz von verbundenen Dreiecken
GL_QUADS Ein Satz von Vierecken
GL_QUAD_STRIP Ein Satz von verbundenen Vierecken
GL_POLYGON Ein Polygon

Im folgenden wird eine sehr einfache OpenGL-Applikation aufgezeigt. Sie zeigt einen leicht rotierten Kubus, um ihm eine dreidimensionale Erscheinung zu geben. In ihrer Einfachheit ist diese Applikation die Hello-World-Version von OpenGL.

#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

HGLRC hglrc;

void DrawHello(HWND hwnd)
{
    HDC hDC;
    PAINTSTRUCT paintStruct;
    RECT clientRect;
    GLfloat lightPos[4] = {-1.0F, 2.0F, 0.2F, 0.0F};

    hDC = BeginPaint(hwnd, &paintStruct);
    if (hDC != NULL)
    {
        GetClientRect(hwnd, &clientRect);
        wglMakeCurrent(hDC, hglrc);
        glViewport(0, 0, clientRect.right, clientRect.bottom);
        glLoadIdentity();
        glClear(GL_COLOR_BUFFER_BIT);
        glColor4d(1.0, 1.0, 1.0, 1.0);
        glRotated(30.0, 0.0, 1.0, 0.0);
        glRotated(15.0, 1.0, 0.0, 0.0);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

        glBegin(GL_QUADS);

        glNormal3d(0.0, -1.0, 0.0);
        glVertex3d(0.5, -0.5, 0.5);
        glVertex3d(-0.5, -0.5, 0.5);
        glVertex3d(-0.5, -0.5, -0.5);
        glVertex3d(0.5, -0.5, -0.5);

        glNormal3d(0.0, 0.0, -1.0);
        glVertex3d(-0.5, -0.5, -0.5);
        glVertex3d(-0.5, 0.5, -0.5);
        glVertex3d(0.5, 0.5, -0.5);
        glVertex3d(0.5, -0.5, -0.5);

        glNormal3d(1.0, 0.0, 0.0);
        glVertex3d(0.5, -0.5, -0.5);
        glVertex3d(0.5, 0.5, -0.5);
        glVertex3d(0.5, 0.5, 0.5);
        glVertex3d(0.5, -0.5, 0.5);

        glNormal3d(0.0, 0.0, 1.0);
        glVertex3d(-0.5, -0.5, 0.5);
        glVertex3d(-0.5, 0.5, 0.5);
        glVertex3d(0.5, 0.5, 0.5);
        glVertex3d(0.5, -0.5, 0.5);

        glNormal3d(-1.0, 0.0, 0.0);
        glVertex3d(-0.5, -0.5, 0.5);
        glVertex3d(-0.5, 0.5, 0.5);
        glVertex3d(-0.5, 0.5, -0.5);
        glVertex3d(-0.5, -0.5, -0.5);

        glNormal3d(0.0, 1.0, 0.0);
        glVertex3d(-0.5, 0.5, 0.5);
        glVertex3d(0.5, 0.5, 0.5);
        glVertex3d(0.5, 0.5, -0.5);
        glVertex3d(-0.5, 0.5, -0.5);

        glEnd();
        glFlush();
        wglMakeCurrent(NULL, NULL);
        EndPaint(hwnd, &paintStruct);
    }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg,
                         WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_PAINT:
            DrawHello(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                        LPSTR d3, int nCmdShow)
{
    MSG msg;
    HWND hwnd;
    WNDCLASS wndClass;
    HDC hDC;
    PIXELFORMATDESCRIPTOR pfd;
    int iPixelFormat;

    if (hPrevInstance == NULL)
    {
        memset(&wndClass, 0, sizeof(wndClass));
        wndClass.style = CS_HREDRAW | CS_VREDRAW;
        wndClass.lpfnWndProc = WndProc;
        wndClass.hInstance = hInstance;
        wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndClass.lpszClassName = "HELLO";
        if (!RegisterClass(&wndClass)) return FALSE;
    }
    hwnd = CreateWindow("HELLO", "HELLO",
                        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                        NULL, NULL, hInstance, NULL);
    hDC = GetDC(hwnd);
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.iLayerType = PFD_MAIN_PLANE;
    pfd.cDepthBits = 16;
    iPixelFormat = ChoosePixelFormat(hDC, &pfd);
    SetPixelFormat(hDC, iPixelFormat, &pfd);
    hglrc = wglCreateContext(hDC);
    ReleaseDC(hwnd, hDC);
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    while (GetMessage(&msg, NULL, 0, 0))
        DispatchMessage(&msg);
    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(hglrc);
    return msg.wParam;
}

Die Initialisierung der Applikation wird durch die Funktionen ChoosePixelFormat und wglCreateContext geleistet. Danach gelangt sie in die Nachrichtenschleife. Wenn die Nachrichtenschleife beendet wird, sorgen die Funktionen wglMakeCurrent und wglDeleteContext für Aufräumarbeiten.

Die Fensterprozedur behandelt nur zwei Nachrichten: WM_PAINT und WM_DESTROY. Wenn die WM_PAINT-Nachricht erhalten wird, folgt der Aufruf der DrawHello-Funktion. Dies ist die Funktion, in der die OpenGL-Operationen stattfinden. Zunächst werden Initialisierungsroutinen (glViewport, GetClientRect) aufgerufen. Danach wird der Frame Buffer gelöscht und eine Identity Transformationsmatrix wird geladen. Die Transformationsmatrix wird durch zwei aufeinanderfolgende Rotationen verändert (glRotate). Die Rotation wird gefolgt von Funktionen für die Beleuchtung und die Leuchtquellen.

Nach dieser Initialisierung beginnt das eigentliche Zeichnen. Eine Serie von sechs Rechtecken wird gezeichnet, wobei für jedes Rechteck der Normalenvektor über die Funktion glNormal3d definiert wird. Wenn die Konstruktion der sechs Primitive beendet ist, zeigt der Aufruf der glFlush-Funktion die Vollendung der OpenGL-Operationen an. Hierbei wird der Device Context freigegeben.

OpenGL und die MFC

OpenGL kann auch in MFC-Applikationen verwendet werden. Hierzu muß zunächst sichergestellt werden, daß die Bibliothek OPENGL32.LIB zum Projekt gefügt wird. Danach muß für das Setzen der richtigen Fenster-Flags (WS_CLIPSIBLINGS und WS_CLIPCHILDREN) im View-Fenster gesorgt werden. Dies kann z.B. durch das Modifizieren der PreCreateWindow Member-Funktion geschehen.

Danach erfolgt die Initialisierung des Rendering Context in der OnCreate Member-Funktion und das eigentliche Zeichnen in der OnDraw Member-Funktion. Die einzelnen aufgerufenen OpenGL-Funktionen unterscheiden sich nur in Details von ihren Gegenstücken in der C-Implementation.

Zum nächsten Kapitel