Lição 1: Minha primeira Janela em OpenGL: Versão MS-Windows-Only (sem usar GLUT)

Muito do código deste tutorial foi escrito originalmente por Jeff Molofee e Fredric Echols e é  uma tradução do OpenGL Tutorial de Jeff Molofee & Neon Helium. Jeff o reescreveu totalmente em Janeiro de 2000. Durante a tradução ao português, a formatação original, com muitas tabelas e figuras mostrando bordas de tabelas para o texto explicativo, foi modificada de forma a facilitar a carga das páginas e a torná-las mais leves.

O objetivo desta lição é ensiná-lo a criar sua primeira janela em OpenGL  usando técnicas de programação sem utilizar o GLUT.

Se você for programar sem o GLUT, você vai ter de mexer com coisas como a API do Sistema de Janelas, que varia muito de MS-Windows para Unix/Linux. Isto significa, que sem GLUT você vai escrever código de um pouco mais baixo nível:

Importante: Você usar o código GLUT ou o código que está aqui vai fazer pouca diferença para a grande maioria dos exercícios da disciplina ine 5341 e para o resto deste tutorial porque daqui para frente nós vamos praticamente mexer somente na rotina de desenho redesenhaMundo(). E todo o código que vai alí é GLUT-independente (puro OpenGL). Eventualmente um ou outro exercício vai usar algum menu ou método de navegação de alto nível, GLUT-exclusivo. Sempre avisaremos nos tutoriais, quando formos usar algo da API do GLUT, indicando que, se você se decidiu por código baseado na versão do programa principal que está nesta página e não na versão GLUT, você não poder usar aquela função.
 
 



Objetivos

O objetivo desta lição é ensiná-lo a criar sua primeira janela em OpenGL e, de lambuja, a compilar um programinha gráfico  em Microsoft C++. A janela que você vai criar pode ter molduras ou ocupar a tela toda, ter qualquer resolução e qualquer profundidade de cores que você queira. O código apresentado aqui foi escrito de forma a poder ser reutilizado em muitas aplicações futuras utilizando OpenGL. A grande maioria dos tutoriais a seguir será baseada no código que vamos ver abaixo.

O código deste tutorial está escrito de uma forma fácil de se entender: Explicamos a parte do código que vai vir e aí vem o código. Em seguida explicamos o que tem de ser programado a seguir e depois apresentmos o código correspondente a essa explicação. Se você copiar somente o código apresentado, você vai poder compilar e sair executando seus programas.

Vamos iniciar o tutorial diretamente escrevendo código. Todos os exemplos daqui para frente são baseados no procedimento de se criar uma aplicação OpenGL com Microsoft Visual C++. Se você quiser ver código para Unix/Linux veja os links no fim dessa página e procure pela ocumentação do XEmacs na Internet para saber como usar um bom editor de programas em Unix.

Em Visual C++, inicie invocando o ambiente de desenvolvimento Visual C++ e crie um novo projeto Win32-console (não uma aplicação console/DOS e também não uma aplicação Win32 pura->você não poder usar printf() para debugar). Feito isto, você vai passar para a etapa seguinte que á a da linkagem das bibliotecas do OpenGL. No Visual C++, vá para Project -> Settings -> Link -> "Object/Library Modules". Lá, antes da linha contendo kernel32.lib adicione OpenGL32.lib (ou OpenGL.lib, caso você queira usar a implementação da Silicon Graphics e não a da Microsoft), GLu32.lib e GLaux.lib.  

Feito isto, clique em OK. Você está pronto para digitar codigo...



Estrutura geral de um Programa OpenGL

Podemos imaginar um programa OpenGL como dividido em várias seções. Cada seção é representada por uma função, a qual será invocada ou do programa principal ou de outra seção.

Seção 1: Declaração de Variáveis Gobais e Inicialização
GL-Includes...
GL-Variaveis...
GLvoid redimensionaJanela(GLsizei width, GLsizei height)  {....}

Seção 2: Inicialização de OpenGL
int InitGL(GLvoid) {....}

Seção 3: Desenhar a Cena OpenGL
int redesenhaMundo(GLvoid)    {....}

Seção 4: Terminar o Programa e Fechar as Janelas
GLvoid KillGLWindow(GLvoid)    {....}

Seção 5: Criar a janela OpenGL
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)  {....}

Seção 6: Controle de Mensagens
LRESULT CALLBACK WndProc( HWND  hWnd, UINT  uMsg, WPARAM  wParam, LPARAM  lParam)   {....}

Seção 7: Função Principal em OpenGL
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
       MessageBox( Fullscreen ??? );
       CreateGLWindow( parâmetros da janela, nome, etc );
       while(!done)                                        // Loop principal, roda até done=TRUE
        {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))           // Tem alguma mensagem esperando?
            {
             TranslateMessage(&msg);DispatchMessage(&msg); // Traduz e Despacha a mensagem
            }
        else
            {
             redesenhaMundo();
             SwapBuffers(hDC);              // Desenhe no buffer escondido e troque.
            }
        }
       KillGLWindow();                                     // Terminar a Janela
    }
 



Declaração de Variáveis Gobais e Inicialização

As quatro primeiras linhas incluem os headerfiles que são responsáveis pelos protótipos das funções contidas nas bibliotecas que você acabou de instruir o linkador para adicionar ao código-objeto.

#include <windows.h>                                                            // Header File For Windows
#include <gl\gl.h>                                                              // Header File For The OpenGL32 Library
#include <gl\glu.h>                                                             // Header File For The GLu32 Library
#include <gl\glaux.h>                                                           // Header File For The GLaux Library
A seguir você precisa declarar as variáveis globais que você vai necessitar em seu programa. Como este programa vai criar uma janela OpenGL em branco, não há muita coisa que precisamos declarar. As poucas variáveis que você vai declarar aqui vão conter objetos extremamente  importantes e que serão necessários em todo e qualquer programa OpenGL que você programar no futuro. Portanto preste atenção na explicação de seu significado.

A primeira linha declara um Contexto de Renderização hRC (RenderingContext). Todo programa OpenGL está ligado a um contexto de renderização, que é responsável por ligar as chamadas OpenGL a um Contexto de Dispositivo  hDC (Device Context).  Para que seu programa seja capaz de desenhar uma janela, você necessita criar um contexto de dispositivo, que conecta a janela à GDI, ou interface do dispositivo gráfico de seu computador (Graphics Device Interface), o driver de seu vídeo.

A terceira variável, hWnd, conterá o ponteiro manipulador do sistema operacional (handle) para a janela que você criou e a quarta linha cria uma instância do programa OpenGL.

HGLRC           hRC=NULL;                                                       // Contexto de Renderização Permanente
HDC             hDC=NULL;                                                       // Contexto de Dispositivo Grafico Privado
HWND            hWnd=NULL;                                                      // Handle da Janela
HINSTANCE       hInstance;                                                      // Instância da Aplicação
A primeira linha abaixo declara um vetor que será usado paar monitorar teclas apertadas no teclado. Há muitas maneiras de se fazer isto e essa é uma e é a que vamos usar neste tutorial.

A variável active será usada para armazenar o estado, minimizado ou não, da janela. Se a janela está minimizada, podemos fazer qualquer coisa, desde terminar o programa até suspender sua execução. No noso caso, vamos suspender a execução para não torrar processador rodando uma aplicação gráfica em background onde ninguém a vê.

A variável fullscreen é óbvia. Se o programa estiver rodando em modo maximizado, a variável fullscreen conterá TRUE. É importante que essa variável seja global para que se possa consultá-la de qualquer rotina no programa.

bool    keys[256];                                                              // Vetor para armazenar tecladas
bool    active=TRUE;                                                            // Flag de janela ativa
bool    fullscreen=TRUE;                                                        // Modo tela cheia também é default


Agora nós temos de definir WndProc(). A função CreateGLWindow() possui uma referência a WndProc() mas WndProc() vem depois de CreateGLWindow(). Em C nós temos de declarar protótipos de funções que somente são declaradas mais tarde para que o compilador possa colocar referências a elas no código.

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                           // Protótipo de WndProc


A tarefa da próxima seção é a de redimensionar a cena em OpenGL de acordo com as modificações de tamanho sofridas pela janela, supondo-se que você não está em modo maximizado. Esta rotina será chamada pelo menos uma vez para inicializar a sua perspectiva. A cena será sempre redimensionada de acordo com a largura e altura da janela.

GLvoid redimensionaJanela(GLsizei width, GLsizei height)                        // Redimensione e inicializa a GL Window
{
        if (height==0)                                                          // Evite divisão por zero..
        {
                height=1;                                                       // ..fazendo altura = 1
        }

        glViewport(0, 0, width, height);                                        // Resete o viewport atual


As próximas linhas de código inicializam a tela para a visualização em perspectiva. A perspectiva é calculada com um ângulo de visada de 45 graus em relação às altura e largura da janela (window gráfico, não viewport). Os valores 0.1f e 100.0f indicam o ponto inicial e final do volume canônico a ser utilizado.

glMatrixMode(GL_PROJECTION) indica que as próximas duas linhas de código vão afetar a matriz de projeção (transformada de perspectiva).  glLoadIdentity() é similar a uma resetagem, retornando a matriz de perspectiva a seu estado original. Depois da chamada a glLoadIdentity() inicializamos a perspectiva para a cena. glMatrixMode(GL_MODELVIEW) indica que quaisquer novas transformações afetarão a matriz de visualização do modelo (modelview matrix). Esta é a matriz onde as informações sobre os objetos estão armazenadas.  Por enquanto você não precisa entender todos os detalhes deste procedimento, apenas observe que estas linhas de código tem de estar aqui desta forma.

        glMatrixMode(GL_PROJECTION);                                            // Selecione a Projection Matrix
        glLoadIdentity();                                                       // Resete a Projection Matrix

        // Calcule o Aspect Ratio do Window e do Volume Canônico
        gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

        glMatrixMode(GL_MODELVIEW);                                             // Selecione a Modelview Matrix
        glLoadIdentity();                                                       // Resete a Modelview Matrix
}


Inicialização de OpenGL

Na próxima seção de código faremos todas as definições necessárias ao OpenGL. Nós iremos definir qual a cor usada para limpar a tela, qual profundidade de cores terá a janela, habilitar sombreamento suavizado, etc. Esta rotina será chamada até que a janela OpenGL seja criada e retorna um valor, com o qual não vamos nos preocupar no momento, pois a nossa inicialização é simples e nada deverá dar errado.

int InitGL(GLvoid)                                                              // All Setup For OpenGL Goes Here
{
        glShadeModel(GL_SMOOTH);                                                // Habilita sombreamento suavizado
                                                                                // Mistura cores de forma suave me um polígono
A próxima linha define a cor de limpeza da janela. Os valores de cores em OpenGL possuem uma escla de 0.0f a 1.0f, indicando 1.0f o valor mais forte. A função glClearColor possue 4 parâmetros: componente vermelha, componente verde, componente azul e valor alfa.  Em uma aula posterior veremos a teori adas cores e será explicado o significado extao destes valores. Por enquanto setaremos um fundo preto, colocando todos os valores em zero.
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                                   // Fundo negro


The next three lines have to do with the Depth Buffer. Think of the depth buffer as layers into the screen. The depth buffer keeps track of how deep objects are into the screen. We won't really be using the depth buffer in this program, but just about every OpenGL program that draws on the screen in 3D will use the depth buffer. It sorts out which object to draw first so that a square you drew behind a circle doesn't end up on top of the circle. The depth buffer is a very important part of OpenGL.
 

        glClearDepth(1.0f);                                                     // Inicialização do Depth Buffer
        glEnable(GL_DEPTH_TEST);                                                // Habilita teste de profundidade
        glDepthFunc(GL_LEQUAL);                                                 // Define qual teste de profundidade vai ser feito
Definimos agora que a perspectiva a ser calculada é a mais bonitinha possível, definida através da constante GL_NICEST. É meio lenta, mas vamos usar mesmo assim.
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);                      // Calculos de perspectiva legaizinhos
        return TRUE;                                                            // Fazemos de conta que sempre dá tudo certo retornando OK
}


Desenhar o Mundo OpenGL

Esta função é onde o código de desenho será colocado. Tudo o que você planeja mostrar deve ser chamado de dentro desta rotina, sendo que todos os tutoriais posteriores a este vão apenas adicionar ou modificar código dentro desta função, entre glLoadIdentity() e return TRUE, sempre que se quiser mostrar algo. Se você retornar algum valor diferente de TRUE nesta rotina, o o program aserá terminado.

int redesenhaMundo(GLvoid)                                                      // É aqui que desenhamos TUDO
{
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                     // Limpe a tela e o buffer
        glLoadIdentity();                                                       // Resete a corrente Modelview Matrix
        return TRUE;                                                            // Tudo deu certo!
}




Terminar o Programa e Fechar as Janelas

A próxima função é chamada imediatamente antes do término do programa. A tarefa de KillGLWindow() é a de liberar o Rendering Context, o Device Context e finalmente a janela através do Window Handle. O código abaixo possui um monte de checagem de erros, que vai ajudá-lo a debugar caso haja alguma coisa que tenha dado errado.

GLvoid KillGLWindow(GLvoid)                                                     // Acabe a janela da forma adequada
{
A primeira coisa que fazemos, é ver se estamos em modo maximizado. Caso estejamos, devemos retornar ao desktop, pois algumas placas de vídeo têm problemas, se destruímos uma janela em modo maximizado. Assim, desabilitamos o modo maximizado primeiro, depois mandamos bala.
        if (fullscreen)                                                         // Are We In Fullscreen Mode?
        {
Usamos ChangeDisplaySettings(NULL,0) para retornar ao modo normal. Passando NULL como primeiro e 0 como segundo parâmetros força o MS Windows a utilizar os valores correntemente armazenados no registro do Windows, que são os valores padrão para a aplicação. Feito isto, voltamos a mostrar o cursor.
                ChangeDisplaySettings(NULL,0);                                  // If So Switch Back To The Desktop
                ShowCursor(TRUE);                                               // Show Mouse Pointer
        }
Abaixo checamos se possuímos um contexto de renderização em hRC. Caso não tenhamos, o programa vai passar adiante e ver se temos um contexto de dispositivo.
        if (hRC)                                                                // Temos um Rendering Context?
        {
Caso tenhamos um contexto de renderização, vamos tentar liberá-lo, separando o hRC do hDC. Observe que estamos checando erros aravés dos valores de retorno das funções.
                if (!wglMakeCurrent(NULL,NULL))                                 // Somos capazes de liberar os contextos DC e RC?
                {
Se formos incapazes de liberar os contextos de DC e RC, abrimos uma jnela de diálogo MessageBox(). O NULL significa que a janela de diálogo não possui janela pai. A constante MB_OK significa que queremos um botão rotulado "OK" na janela. MB_ICONINFORMATION faz com que apareça um ícone com um i minúsculo em um círculo (janela de informação).
                        MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
                }
A seguir tentamos liberar o RC explicitamente. Se der errado também, abrimos outra janela.
                if (!wglDeleteContext(hRC))                                     // Somos capazes de liberar o RC?
                {
Se fomos incapazes de liberar o RC explicitamente, colocamos NULO nele.
                        MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
                }
                hRC=NULL;                                                       // Sete o RC para NULL
        }
A seguir tentamos liberar o DC explicitamente. Se der errado também, abrimos outra janela.
        if (hDC && !ReleaseDC(hWnd,hDC))                                        // Somos capazes de liberar o DC
        {
                MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
                hDC=NULL;                                                       // Setamos o DC para NULL
        }
A seguir verificamos se existe um handle de janela e tentamos libera-lo DC explicitamente usando DestroyWindow(hWnd). Se der errado também, abrimos mais outra janelae setamos o handle para NULL.
        if (hWnd && !DestroyWindow(hWnd))                                       // Are We Able To Destroy The Window?
        {
                MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
                hWnd=NULL;                                                      // Set hWnd To NULL
        }
A última coisa a fazer é deregistrar nossa classe Windows. Isto nos permitirá destruir a janela de forma apropriada e abrir outra sem receber a mensagem de erro "Windows Class already registered"
        if (!UnregisterClass("OpenGL",hInstance))                       // Are We Able To Unregister Class
        {
                MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
                hInstance=NULL;                                         // Set hInstance To NULL
        }
}


Criar a janela OpenGL

Uma janela fixa como maximizada necessitaria de muito menos código, mas o objetivo deste tutorial é prover ferramentas para o leitor programar suas próprias aplicações depois, porisso vamos ver código para janelas de tamanho que pode ser alterado pelo usuário.

A função retorna BOOL (TRUE ou FALSE) e toma 5 parâmetros:  título da janela, width (largura) da janela, height (altura) da janela, bits (profundidade 16/24/32) da janela e finalmente fullscreenflag TRUE para janela maximizada ou FALSE para janela. Retornamos um booleano que nos diz se deu para abrir a janela.

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
Quando solicitamos a windows que encontre um formato de pixel compatível com aquele que queremos, o valor do modo que MS Windows encontrou será guardado em PixelFormat.

                GLuint          PixelFormat;                                                                                                       // Modo de vídeo

wc será usada para conter a estrutura de nossa Window Class e contém informação sobre a janela. Modificando atributos da instância, podemos modificar a aparência e o comportamento da janela. Depois de criar uma janela, você TEM DE registrar uma classe para ela.

        WNDCLASS        wc;                                                     // Estrutura de Windows Class
dwExStyle e dwStyle armazenam as informações de janela no modo normal e extendido. Usamos variáveis aqui para podermos modificar o estilo da janela dependendo de que janela queremos criar.
        DWORD           dwExStyle;                                              // Window Extended Style
        DWORD           dwStyle;                                                // Window Style
As próximas 5 linhas de código tomam o canto superior esquerdo e o canto inferior direito de um retângulo. Usaremos estes valores para ajustar a janela de forma que a área em que desenhamos tenha exatamente a resolução que queremos, pois se criamos diretamente uma janela com um determinado tamanho, as bordas da mesma vão tomar parte da resolução.
        RECT WindowRect;                                                        // Toma os cantos do retângulo da janela
        WindowRect.left=(long)0;                                                // Seta valor esquerdo para 0
        WindowRect.right=(long)width;                                           // Seta valor dierito para Width
        WindowRect.top=(long)0;                                                 // Seta valor superior para  0
        WindowRect.bottom=(long)height;                                         // Seta valor inferior para Height
Na próxima linha fazemos a global fullscreen igual a fullscreenflag. Dessa forma fizemos uma janela maximizada, caso o valor de fullscreenflag tenha sido TRUE. Se não mantemos sempre essas duas variáveis sincronizadas e terminarmos o programa em modo maximizado mas com fullscreen com valor falso, eventualmente MS Windows não redesenhará o desktop por supor que já está lá.
        fullscreen=fullscreenflag;                                              // Set The Global Fullscreen Flag
Agora, vamos criar uma instância para  a nossa janela e definir a Window Class.

Os estilos CS_HREDRAW e CS_VREDRAW forçam a janela a se redesenhar sempre que redimensionada. CS_OWNDC cria um DC privado para a janela. Isto significa que o contexto de dispositivo não é compartilhado por outra aplicação. WndProc é a função que espera por mensagens em nosso programa. Nenhum outra dado é usado, conseq    uentemente zeramos os outros campos.

Depois setamos hIcon para NULL, o que significa que não teremos íone e como ponteiro para o mouse usamos a flecha padrão. A cor de fundo não importa pois a setamos no programa e também não queremos um menu neste programa e setamos o ponteiro para menu para NULL e o nome da classe pode ser o que a gente quiser. Usaremos "ine5341-OpenGL".

        hInstance               = GetModuleHandle(NULL);                        // Grab An Instance For Our Window
        wc.style                = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;           // Redraw On Move, And Own DC For Window
        wc.lpfnWndProc          = (WNDPROC) WndProc;                            // WndProc Handles Messages
        wc.cbClsExtra           = 0;                                            // No Extra Window Data
        wc.cbWndExtra           = 0;                                            // No Extra Window Data
        wc.hInstance            = hInstance;                                    // Set The Instance
        wc.hIcon                = LoadIcon(NULL, IDI_WINLOGO);                  // Load The Default Icon
        wc.hCursor              = LoadCursor(NULL, IDC_ARROW);                  // Load The Arrow Pointer
        wc.hbrBackground        = NULL;                                         // No Background Required For GL
        wc.lpszMenuName         = NULL;                                         // We Don't Want A Menu
        wc.lpszClassName        = "ine5341-OpenGL";                             // Set The Class Name
Registramos a Classe. Se algo der errado, teremos uma mensagem de erro e depois de clicar na mensagem, o programa termina.
        if (!RegisterClass(&wc))                                                // Tente registrar a Window Class
        {
                MessageBox(NULL,"Não consegui registrar a Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Termine o Programa e Retorne FALSE
        }
Vemos se o prorama é para ser rodado maximizado ou em janela. Se for maximizado, tentamos fazê-lo virar tela cheia.
        if (fullscreen)                                                         // Tentamos Fullscreen Mode?
        {
A próxima seção de código é algo onde todo mundo tem problemas: Mudar para modo tela cheia. Temos de ter certeza de que a largura e altura a serem usadas em modo tela cheia são as mesmas a serem usada dentro de uma janela e, muito importante, mude para o modo tela cheia ANTES de criar a janela. Neste código abaixo, não há necessidade de se preocupar com a altura e a largura, que o programa cuida delas.
                DEVMODE dmScreenSettings;                                       // Modo do Dispositivo
                memset(&dmScreenSettings,0,sizeof(dmScreenSettings));           // Certifica-se de que a memória foi limpa
                dmScreenSettings.dmSize=sizeof(dmScreenSettings);               // Tamanho da estrutura Devmode 
                dmScreenSettings.dmPelsWidth    = width;                        // Selected Screen Width
                dmScreenSettings.dmPelsHeight   = height;                       // Selected Screen Height
                dmScreenSettings.dmBitsPerPel   = bits;                         // Selected Bits Per Pixel
                dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
No código acima abrimos espaço para armazenar as nossas setagens de vídeo. Setamos a largura, altura e prodfundidade que queremos. No código abaixo tentamos realizar este modo de vídeo. Nas linhas abaixo, a rotina ChangeDisplaySettings tenta mudar para um modo de vídeo compatível com o que setamos em dmScreenSettings. Usamos o parâmetro CDS_FULLSCREEN quando mudamos de modo para que a barra no alto da tela seja removida, sem modificar nenhuma outra janela aberta nesse instante.
                // Tente ajustar o modo selecionado e pegar os resultados. 
                if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
                {
Se não foi possivel ajustar o modo desejado, será executado o código abaixo oferecendo duas opções ao usuário: rodar em janela ou terminar o programa.
                        // Se o modo falha, termine e abra uma janela de diálogo
                        if (MessageBox(NULL,"O Modo Fullscreen não funcionou. Deseja usar janelas ?","ine5341 GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
                        {
Se o usuário decidiu usar modo janela, então fullscreen será FALSE
                                fullscreen=FALSE;                               // Selecione modo de janelas (Fullscreen=FALSE)
                        }
                        else
                        {
                                // Abra uma janela avisando o usuário de que o programa vai terminar.
                                MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
                                return FALSE;                                   // Exit And Return FALSE
                        }
                }
        }
Já que o usuário pode ter se decidido por rodar o programa em uma janela, checamos se fullscreen é TRUE ou FALSE novamente, antes de ajustar o tipo de janela e vídeo.
        if (fullscreen)                                                         // Ainda estamos em modo tela cheia?
        {
Se ainda estamos em modo fullscreen, ajustaremos o estilo extendido para WS_EX_APPWINDOW, que fará com que quaisquer janelas em primeiro plano vão para a barra de tarefas assim que a nossa janela se tornar visível. Como estilo de janela, criaremos uma janela WS_POPUP. Esta janela não possui borda, o que a faz um perfeita janela maximizada.
                dwExStyle=WS_EX_APPWINDOW;                                      // Window Extended Style
                dwStyle=WS_POPUP;                                               // Windows Style
Finalmente desabilitamos o apontador do mouse. Fica mais bonitinho....
                ShowCursor(FALSE);                                              // Esconda o Mouse Pointer
        }
        else
        {
Se estivermos usando uma janela ao invés de modo tela cheia, adicionaremos WS_EX_WINDOWEDGE ao estilo extendido para dar um efeito mais 3D à janela. Como estilo usaremos WS_OVERLAPPEDWINDOW ao invés de  WS_POPUP. WS_OVERLAPPEDWINDOW cria uma janela com barra de título, borda de redimensionamento, menu e botões de maximização/minimização.
                dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;                   // Window Extended Style
                dwStyle=WS_OVERLAPPEDWINDOW;                                    // Windows Style
        }
Na linha abaixo, ajuste a janela de acordo com o estilo, fazendo-a ter exatamente a resolução requisitada. Normalmente as bordas vão se sorepor a partes da janela. Usando AdjustWindowRectEx nenhuma parte da cena OpenGL será coberta pelas bordas. Ao invés disso, a janela será feita um pouco maior. Em modo tela cheia este comando não tem efeito.
        AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);             // Acerte o tamanho da janela
Agora criaremos a nossa janela e checaremos se foi criada adquadamente, passando a CreateWindowEx() todos os parâmetros necessários. Nós não desejamos uma janela pai e também não queremos um menu, porisso setamos estes parâmetros para NULL. Passamos a instância d janela e um NULL para o último parâmetro.

Observe que incluímos os estilos WS_CLIPSIBLINGS e  WS_CLIPCHILDREN juntamente com o estilo de janela que nos decidimos a usar. Estes estilos TEM DE ser usados em OpenGL para que opere adequadamente.

        if (!(hWnd=CreateWindowEx(      dwExStyle,                              // Extended Style For The Window
                                        "OpenGL",                               // Class Name
                                        title,                                  // Window Title
                                        WS_CLIPSIBLINGS |                       // Required Window Style
                                        WS_CLIPCHILDREN |                       // Required Window Style
                                        dwStyle,                                // Selected Window Style
                                        0, 0,                                   // Window Position
                                        WindowRect.right-WindowRect.left,       // Calculate Adjusted Window Width
                                        WindowRect.bottom-WindowRect.top,       // Calculate Adjusted Window Height
                                        NULL,                                   // No Parent Window
                                        NULL,                                   // No Menu
                                        hInstance,                              // Instance
                                        NULL)))                                 // Don't Pass Anything To WM_CREATE
Agora vamos ver se a janela foi criada adequadamente. Caso sim, hWnd conterá o manipulador (handle) da janela. Se não estiver lá, criamos uma janela de erro.
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Agora descrevemos um formato de pixel que suporta OpenGL e bufferização dupla, juntamente com RGBA  (red, green, blue, alpha channel). Tentamos encontrar um formato de pixel que bata com a profundidade de cor que selecionamos (16bit,24bit,32bit).

Finalmente setamos um Z-Buffer de 16 bits. O Z-Buffer é responsável por conter as partes visíveis de uma cena, levando em conta a profundidade dessas partes e a forma como sua aparência e cor serão tratadas em função disso. Sempre que temos uma cena 3D, teremos objetos na cena que poderão estar encobrindo outros. Existem muitas formas de cálculo de visibilidade. Uma das mais eficientes utiliza um Z-Buffer, que armazena a parte visível de cada objeto e sua distância do usuário, ajudando a formar a cena visualizada de forma eficiente. Para entender melhor o Z-Buffer dê uma olhada no material deste link.

Os parâmetros restantes ou não são usados ou não são importantes no momento.

        static  PIXELFORMATDESCRIPTOR pfd=                                      // pfd Tells Windows How We Want Things To Be
        {
                sizeof(PIXELFORMATDESCRIPTOR),                                  // Size Of This Pixel Format Descriptor
                1,                                                              // Version Number
                PFD_DRAW_TO_WINDOW |                                            // Format Must Support Window
                PFD_SUPPORT_OPENGL |                                            // Format Must Support OpenGL
                PFD_DOUBLEBUFFER,                                               // Must Support Double Buffering
                PFD_TYPE_RGBA,                                                  // Request An RGBA Format
                bits,                                                           // Select Our Color Depth
                0, 0, 0, 0, 0, 0,                                               // Color Bits Ignored
                0,                                                              // No Alpha Buffer
                0,                                                              // Shift Bit Ignored
                0,                                                              // No Accumulation Buffer
                0, 0, 0, 0,                                                     // Accumulation Bits Ignored
                16,                                                             // 16Bit Z-Buffer (Depth Buffer)
                0,                                                              // No Stencil Buffer
                0,                                                              // No Auxiliary Buffer
                PFD_MAIN_PLANE,                                                 // Main Drawing Layer
                0,                                                              // Reserved
                0, 0, 0                                                         // Layer Masks Ignored
        };
Se não houve problemas durante a criação da janela, vamos tentar obter um OpenGL Device Context. Caso nõ consigamos, emitimos uma mensagem de erro e terminamos o programa.
        if (!(hDC=GetDC(hWnd)))                                                 // Did We Get A Device Context?
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Se conseguimos um contexto de dispositivo, tentaremos achar um formato de pixel que bata com algum descrito acima. Se não der certo, também emitimos uma mensagem de erro e terminamos o programa.
        if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))                         // Did Windows Find A Matching Pixel Format?
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Se MS Windows achou um formato de pixel compatível com as nossas especificações, tentaremos ajustá-lo. Caso não dê, de novo: mensagem de erro e fim.
        if(!SetPixelFormat(hDC,PixelFormat,&pfd))                               // Are We Able To Set The Pixel Format?
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Se ajustamos o formato de pixel adequadamente, tentaremos ajustar o contexto de renderização. Caso não dê: outra mensagem de erro e fim.
        if (!(hRC=wglCreateContext(hDC)))                                       // Are We Able To Get A Rendering Context?
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Se até agora não houve erros e conseguimos tanto um DC como um RC, tudo o que temos de fazer é ativar o contexto de renderização. Se não der, mensagem e fim.
        if(!wglMakeCurrent(hDC,hRC))                                            // Try To Activate The Rendering Context
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Se tudo correu bem e a nossa janela OpenGL foi criada, vamos mostrá-la, ajustá-l apara ser a janela em primeiro plano, dando-lhe maior prioridade e então setar o foco para ela. Então chamamos ReSizeGLScene passando o tamanho da tela para ajustar a perspectiva d tela OpenGL.
        ShowWindow(hWnd,SW_SHOW);                                               // Show The Window
        SetForegroundWindow(hWnd);                                              // Slightly Higher Priority
        SetFocus(hWnd);                                                         // Sets Keyboard Focus To The Window
        ReSizeGLScene(width, height);                                           // Set Up Our Perspective GL Screen
Finalmente pulamos para InitGL() onde poderemso setar iluminação, texturas e tudo o mais que precisa ser ajustado. Você pode realizar seu próprio controle de erros em InitGL(), e devolder TRUE (tudo OK) or FALSE (algo errado). Se a função retornar falso, o programa será terminado.
        if (!InitGL())                                                          // Initialize Our Newly Created GL Window
        {
                KillGLWindow();                                                 // Reset The Display
                MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
                return FALSE;                                                   // Return FALSE
        }
Se tudo deu certo até agora, retornamos TRUE a WinMain(). Isto evita que o prorama termine.
        return TRUE;                                                            // Success
}


Controle de Mensagens

Aqui as mensagens de janela são tratadas. Quando registramos a Window Class, lhe dissemos para chamar a função WndProc() para tratar as mensagens da janela.

LRESULT CALLBACK WndProc(       HWND    hWnd,                                   // Handle For This Window
                                UINT    uMsg,                                   // Message For This Window
                                WPARAM  wParam,                                 // Additional Message Information
                                LPARAM  lParam)                                 // Additional Message Information
{
Setamos uMsg como a variável que é usada nos CASES.
        switch (uMsg)                                                           // Check For Windows Messages
        {
Se uMsg contém WM_ACTIVE checamos para ver se nossa janela ainda está ativa. Se foi minimizada, a variável active será FALSE.
                case WM_ACTIVATE:                                               // Watch For Window Activate Message
                {
                        if (!HIWORD(wParam))                                    // Check Minimization State
                        {
                                active=TRUE;                                    // Program Is Active
                        }
                        else
                        {
                                active=FALSE;                                   // Program Is No Longer Active
                        }

                        return 0;                                               // Return To The Message Loop
                }
Se a mensagem for WM_SYSCOMMAND (system command) compararemos wParam com os comando do case. Se wParam for SC_SCREENSAVE ou SC_MONITORPOWER isto significa que ou um screensaver está tentando se iniciar ou o monitor está para ir para modo desligado. Retornando 0, evitamos que ambas as coisas aconteçam.
                case WM_SYSCOMMAND:                                             // Intercept System Commands
                {
                        switch (wParam)                                         // Check System Calls
                        {
                                case SC_SCREENSAVE:                             // Screensaver Trying To Start?
                                case SC_MONITORPOWER:                           // Monitor Trying To Enter Powersave?
                                return 0;                                       // Prevent From Happening
                        }
                        break;                                                  // Exit
                }
Se uMsg é WM_CLOSE a janela foi fechada. Enviremos uma mensgaem de término que o loop principl interceptará e a variável done será setada para TRUE e o loop principal em WinMain() vai parar, fazendo com que o programa termine.
                case WM_CLOSE:                                                  // Did We Receive A Close Message?
                {
                        PostQuitMessage(0);                                     // Send A Quit Message
                        return 0;                                               // Jump Back
                }
Se uma tecla está sendo pressionada, podemos descobrir qual é lendo wParam. Então fazemos a célula daquela tecla em keys[] ser TRUE. Assim podemos mais tarde saber qual tecla foi pressionada. Isto também permite que mais de uma tecla seja pressionada ao mesmo tempo.
                case WM_KEYDOWN:                                                // Is A Key Being Held Down?
                {
                        keys[wParam] = TRUE;                                    // If So, Mark It As TRUE
                        return 0;                                               // Jump Back
                }
Se uma tecla foi solta, podemos da mesma forma descobrir através de wParam. Fazemos então a célula daquela tecla em keys[] ser FALSE. Dessa forma eu posso saber se uma determinad tecla ainda está sendo apertada ou já foi solta.
                case WM_KEYUP:                                                  // Has A Key Been Released?
                {
                        keys[wParam] = FALSE;                                   // If So, Mark It As FALSE
                        return 0;                                               // Jump Back
                }
Toda vez que redimensionamos a janela, uMsg conterá WM_SIZE. Lendo LOWORD e HIWORD em lParam descobrimos então o novo tamanho. Passamos este tamanho a ReSizeGLScene() para redimensionar a cena OpenGL.
                case WM_SIZE:                                                   // Resize The OpenGL Window
                {
                        redimensionaJanela(LOWORD(lParam),HIWORD(lParam));      // LoWord=Width, HiWord=Height
                        return 0;                                               // Jump Back
                }
        }
Quaisquer mensagens que não nos importam serão repassadas a  DefWindowProc para que MS Windows resolva o assunto.
        // Pass All Unhandled Messages To DefWindowProc
        return DefWindowProc(hWnd,uMsg,wParam,lParam);
}


Função Principal em OpenGL

Aqui é o ponto de entrada da aplicação. Aqui são chamadas as rotinas de criação de janela, tratamento de mensagens e interação humana.

int WINAPI WinMain(     HINSTANCE       hInstance,                              // Instance
                        HINSTANCE       hPrevInstance,                          // Previous Instance
                        LPSTR           lpCmdLine,                              // Command Line Parameters
                        int             nCmdShow)                               // Window Show State
{
Setaremos duas variáveis. msg será usada para ver se há alguma mensagem esperando. done inicia como FALSE e será verdadeira quando o usuário ou um erro determinar o término do programa. Tão logo done muda para TRUE, o programa termina.
        MSG     msg;                                                            // Windows Message Structure
        BOOL    done=FALSE;                                                     // Bool Variable To Exit Loop
Esta parte é completamente opcional: Abre uma janela qu pergunta se o usuário quer rodar o programa em modo tela cheia.
        // Ask The User Which Screen Mode They Prefer
        if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
        {
                fullscreen=FALSE;                                               // Windowed Mode
        }
Aqui criamos a nossa janela OpenGL. Passamos título, tamanho, profundidade de cores e se será aberta em tela cheia ou não. Se a função retornar 0, a janela não foi criada e o programa termina.
        // Create Our OpenGL Window
        if (!CreateGLWindow("ine 5341 - OpenGL",640,480,16,fullscreen))
        {
                return 0;                                                       // Quit If Window Was Not Created
        }
Aqui começamos o loop, que iterará até done ser TRUE.
        while(!done)                                                            // Loop That Runs Until done=TRUE
        {
A primeira coisa que fazemos é ver se exitem mensagens na fila. Ao invés de usar  GetMessage(), que pára o programa até vir uma mensagem, usamos PeekMessage().
                if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))                       // Is There A Message Waiting?
                {
Agora vemos se há uma mensagem de término de programa WM_QUIT causada por PostQuitMessage(0).
                        if (msg.message==WM_QUIT)                               // Have We Received A Quit Message?
                        {
                                done=TRUE;                                      // If So done=TRUE
                        }
                        else                                                    // If Not, Deal With Window Messages
                        {
Se a mensagem não for de término, traduzimo-la e enviamo-la para WndProc().
                                TranslateMessage(&msg);                         // Translate The Message
                                DispatchMessage(&msg);                          // Dispatch The Message
                        }
                }
                else                                                            // If There Are No Messages
                {
Se não há mensagens, desenhamos a nossa cena OpenGL. Primeiramente checamos para ver se a janela está ativa. Se a tecla ESC foi apertada, done é TRUE e o programa deve terminar.
                        // Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
                        if (active)                                             // Program Active?
                        {
                                if (keys[VK_ESCAPE])                            // Was ESC Pressed?
                                {
                                        done=TRUE;                              // ESC Signalled A Quit
                                }
                                else                                            // Not Time To Quit, Update Screen
                                {
Se o programa está ativo e ESC não foi apertada, renderizamos a cena e trocamos o buffer. A dupla buferização nos permite realizar animação sem chuvisco através de transferência somente de cenas prontas, depois que foram desenhadas no buffer em memória.
                                        DrawGLScene();                          // Draw The Scene
                                        SwapBuffers(hDC);                       // Swap Buffers (Double Buffering)
                                }
                        }
O próximo trecho de código permite que se aperte F1 par ir de tela cheia para modo janela.
                        if (keys[VK_F1])                                        // Is F1 Being Pressed?
                        {
                                keys[VK_F1]=FALSE;                              // If So Make Key FALSE
                                KillGLWindow();                                 // Kill Our Current Window
                                fullscreen=!fullscreen;                         // Toggle Fullscreen / Windowed Mode
                                // Recreate Our OpenGL Window
                                if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
                                {
                                        return 0;                               // Quit If Window Was Not Created
                                }
                        }
                }
        }
Se a variável done não for mais falsa, fechamos tudo e terminamos o programa.
        // Shutdown
        KillGLWindow();                                                         // Kill The Window
        return (msg.wParam);                                                    // Exit The Program
}




Links para Código Fonte

Organizado e escrito originalmente por Jeff Molofee (NeHe)
Adapatado para a Língua Portuguesa por Aldo von Wangenheim (UFSC)