Aufbau und Programmablauf der OpenGL-Tutorials

Mit dem Ziel, Sie bei der Einarbeitung in die zum Download stehenden OpenGL-Tutorials zu unterstützen, befasst sich der heutige Artikel mit dem prinzipiellen Aufbau und Ablauf der einzelnen Programmbeispiele.



Als oberste Instanz ist die CAppMain-Klasse für alle notwendigen Initialisierungsarbeiten bei Programmstart (CAppMain()), für alle notwendigen Aufräumarbeiten bei Programmende (~CAppMain()), für die Szenenberechnung (Calculate_Scene()) sowie für die Szenendarstellung (Render_Scene()) verantwortlich. In ihrer Eigenschaft als Teil der Game Shell (Windows-Rahmenanwendung) delegiert die CAppMain-Klasse alle anwendungsspezifischen Details – Initialisierung der Spielewelt, notwendige Aufräumarbeiten sowie die Szenenberechnung und -darstellung – an ihre einzige Membervariable, einer Instanz der CGamesWorld-Klasse, weiter.

class CAppMain
{
public:

    CGamesWorld* GamesWorld;

    CAppMain()
    {
        GamesWorld = NULL;

        Setup_AdditionalMathParameter();

        // Textur-Filter-Einstellungen:
        Init_SamplerObjects();

        GamesWorld = new CGamesWorld;

        // Kamera ausrichten:
        Reset_CameraRotation();

        // Spielsteuerung initialisieren:
        if(Init_DirectInput(g_ApplicationInstanceHandle,
           g_MainWindowHandle) == S_FALSE)
        {
            Add_To_Log("keine Spielsteuerung möglich!!!");
            SendMessage(g_MainWindowHandle,WM_CLOSE,0,0);
            return;
        }
    }

    ~CAppMain()
    {
        SAFE_DELETE(GamesWorld)
        Shutdown_DirectInput();
        Delete_SamplerObjects();
    }

    void Calculate_Scene(void)
    {
        GamesWorld->Calculate_Scene();
    }

    void Render_Scene(void)
    {
        GamesWorld->Render_Scene();
    }
};

CAppMain* AppMain = NULL;


Die Initialisierung einer CAppMain-Instanz bei Programmstart erfolgt in der Init_AppMain()-Funktion, für die Freigabe der Instanz bei Programmende ist die Funktion Shutdown_AppMain() zuständig.

void Init_AppMain(void)
{
    AppMain = new CAppMain;
    Add_To_Log("AppMain Initialisation complete");
}

void Shutdown_AppMain(void)
{
    SAFE_DELETE(AppMain)
    Add_To_Log("AppMain Shutdown complete");
}


Verantwortlich für die Szenendarstellung ist die DrawOpenGLScene()-Funktion.

BOOL DrawOpenGLScene(void)
{
    glClearColor(g_BackgroundScreenColor.x, g_BackgroundScreenColor.y,
                 g_BackgroundScreenColor.z, g_BackgroundScreenColor.w);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    AppMain->Render_Scene();

    return TRUE;
}


Hauptverantwortlich für die Aktualisierung des Programmfortschritts (Spielgeschehens) ist die GameMainRoutine()-Funktion. Zu ihren Aufgaben gehört

  • die Verarbeitung von Maus- und Tastatureingaben,
  • die Aktualisierung der Kameraposition,
  • die Aktualisierung der Blickrichtung,
  • das Timing (Aufrechterhaltung einer konstanten Framerate (Frame Based Rendering) bzw. Zulassen beliebiger Frameraten (Time Based Rendering))
  • die Szenenberechnung (Bewegung der Spieleobjekte, Kollisionserkennung, Sichtbarkeitstests, usw.)
  • sowie die Szenendarstellung.

void GameMainRoutine(void)
{
    g_StartTime = GetTickCount();

    // Verarbeitung von Maus- und Tastatureingaben,
    // Aktualisierung der Kameraposition:

    ReadImmediateDataMouse();
    ReadImmediateDataKeyboard();

    // Aktualisierung der Blickrichtung:
    Calculate_CameraRotation();

    if(g_Pause == false)
    {
        // An dieser Stellewird das gesamte Spielgeschehen
        // aktualisiert und gerendert:

        AppMain->Calculate_Scene();

        // neue Szene in den Back Buffer zeichnen, alte Szene bleibt
        // weiterhin sichtbar im Front Buffer:
        DrawOpenGLScene();

        // neue Szene sichtbar machen (Flip zwischen Back und Front Buffer)
        // Im Prinzip tauschen die Zeiger, die auf die jeweiligen Buffer-
        // Speicherbereiche zeigen ihre Zieladressen

        SwapBuffers(g_DeviceContextHandle);
    }

    if(g_TimingStyle == 1) // Frame Based Rendering
    {
        while((g_dt = GetTickCount()-g_StartTime) <= g_InvMaxFrameRate);
        g_FrameRate = 1000.0f/g_dt;
        g_NormalFrameTime += 0.001f*g_dt;
    }
    else if(g_TimingStyle == 2) // Time Based Rendering
    {
        g_dt = GetTickCount()-g_StartTime;
        g_FrameRate = 1000.0f/g_dt;
        g_NormalFrameTime += 0.001f*g_dt;
    }

    // Wir mitteln die Frameszeit über zwei Frames, um mögliche
    // Schwankungen etwas abzufedern:
    g_NormalFrameTime *= 0.5f;

    if(g_NormalFrameTime >
       g_MaximalAcceptedFrameTimeUsedForPhysicalCalculations)
        g_NormalFrameTime =
        g_MaximalAcceptedFrameTimeUsedForPhysicalCalculations;

     g_FrameTime = g_NormalFrameTime; // * GameSpeedFactor
}

In der WinMain()-Funktion laufen schließlich alle Fäden zusammen. Im ersten Schritt werden mithilfe der InitResolutionAndRenderOptions()-Funktion alle benötigten renderspezifischen Informationen aus einer Konfigurationsdatei in das Programm eingelesen. Unter Berücksichtigung der eingelesenen Informationen wird im zweiten Schritt die OpenGL-Anwendung (CreateOpenGLWindow()) und im Anschluss daran die Spielewelt (Init_AppMain()) initialisiert.
Nach Abschluss der Initialisierungsarbeiten tritt die Anwendung in die Spielschleife ein. Bis zu dem Zeitpunkt, an dem die Schleife wieder verlassen wird (bei Programmende), wird in jedem Schleifendurchgang das Spielgeschehen aktualisiert (GameMainRoutine()).
Nach dem Verlassen der Spielschleife werden schließlich alle anstehenden Aufräumarbeiten durchgeführt und alle verwendeten Ressourcen wieder freigegeben.

int WINAPI WinMain(HINSTANCE hInstance,    
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,        
                   int nCmdShow)           
{
    MSG msg; // Windows Message Struktur
    BOOL done=FALSE; // BOOL Variable zum Verlassen der Spielschleife

    // renderspezifische Informationen einlesen:
    InitResolutionAndRenderOptions("ResolutionAndRendering.txt");

    Add_To_Log("ResolutionAndRendering.txt readed");

    // OpenGL-Anwendung initialisieren:
    if(!CreateOpenGLWindow(L"OpenGL v3.3"))
    {
        Add_To_Log("window not created");
        PostQuitMessage(0);
        g_BeginShutdown = true;
    }
    else
    {
        // Spielewelt initialisieren:
        Init_AppMain();
        Add_To_Log("Init_AppMain completed");
    }

    // Spielschleife:
    while(!done)
    {
        // Nachrichtenweiterleitung, ihre Verarbeitung erfolgt
        // in der WndProc()-Callback-Funktion:
        if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
        {
            if(msg.message==WM_QUIT)
            {
                done = TRUE;
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else
        {
            // Anwendung aktiv (nicht minimiert):
            if(g_active)
            {
                if(KEYDOWN(VK_ESCAPE) || g_BeginShutdown == true)
                {
                    done = TRUE;
                }
                else
                {
                    // Aktualisierung des Spielgeschehens:
                    GameMainRoutine();
                }
            }
        }
    }

    // Aufräumarbeiten:
    Shutdown_AppMain();
    KillOpenGLWindow();
    return (msg.wParam);
}


Zu guter letzt werfen wir noch einen kurzen Blick auf die WndProc()-Callback-Funktion, die für die Verarbeitung windows-spezifischer Nachrichten zuständig ist:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg,
                         WPARAM wParam, LPARAM lParam)
{
    switch(uMsg) // Nachrichten auswerten
    {
        // Windows-Anwendung aktivieren oder deaktivieren:
        case WM_ACTIVATE:
        {
            // Wurde das Anwendungsfenster minimiert?
            if(!HIWORD(wParam))
            {
                // Anwendungsfenster nicht minimiert
                g_active = true;
            }
            else
            {
                // Fenster minimiert
                // Aktualisierung des Spielgeschehens unterbrochen
                g_active = false;
            }
            return 0;
        }
        // Verhindern, dass sich der Bildschirmschoner aktiviert oder der
        // Monitor in den Energiesparmodus wechselt:
        case WM_SYSCOMMAND: // Verarbeitung von Systembefehlen
        {
            switch(wParam)
            {
                case SC_SCREENSAVE:
                case SC_MONITORPOWER:
                return 0;
            }
            break;
        }
        // Programm soll beendet werden:
        case WM_CLOSE:
        {
            // Nachricht zum Beeenden des Programms senden:
            PostQuitMessage(0);
            return 0;
        }
    }
    // Übergabe aller nicht bearbeiteten Nachrichten an DefWindowProc:
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}