Ponto V!

Home OpenGL Entendendo o loop principal
Vinícius Godoy de Mendonça
Entendendo o loop principalImprimir
Escrito por Vinícius Godoy de Mendonça

No artigo anterior, você deve ter notado que o método main estava dentro de um loop. Hoje, vamos entender qual é a importância daquele loop, quais as consequências dele e como usa-lo para produzir animações.

Todo jogo roda num loop, formado pelos seguintes passos:

  1. Processar os eventos externos (entradas do usuário, dados da rede);
  2. Executar a lógica do jogo;
  3. Desenhar a tela.

Filmes no computador

Uma animação, no cinema, é produzida por uma série de quadros estáticos, mostrados um-a-um. O olho, incapaz de captar a troca de quadros, enxerga como se tudo estivesse em movimento. Nos computadores, todas as animações são produzidas de maneira similar.

Entretanto, temos apenas a presença de dois quadros. Isso porque o computador é rápido o suficiente para pintar um quadro enquanto enxergamos outro. Quando o novo quadro está pronto, ele rapidamente troca os dois de lugar: o quadro que estava sendo pintado passa a ser visto e o quadro que estava sendo visto é apagado e repintado. Essa técnica é conhecida como double buffering. É o comando SDL_GL_SwapBuffers() quem efetivamente troca as duas imagens de lugar. Uma aplicação sem double buffering sofre um problema chamado flickering. O usuário percebe a tela sendo apagada e redenhada e, para ele, a tela parece estar piscando.

Essa troca das duas imagens também não pode ser feita de qualquer jeito. No início, observou-se que se a imagem fosse apagada e a nova repintada a qualquer momento corria-se o risco do monitor exibir algumas vezes a imagem no meio da pintura. Ou seja, parte da imagem exibiria o conteúdo do buffer novo e parte do buffer antigo. Na prática, o usuário percebia pequenas manchas na tela, um efeito conhecido como tearing. O problema foi resolvido da seguinte forma: desenha-se o buffer em segundo plano na memória de vídeo e, quando pronto, é dado um comando ao hardware do vídeo, que só irá trocar as imagens no início do próximo ciclo de desenho. Essa técnica é conhecida como page flipping (virar a página). Veja no desenho abaixo:

swapbuffers

Outra diferença do computador para o cinema é que o tempo entre os quadros não é constante. Na verdade, ele varia de acordo com o que é processado entre os quadros, com o equipamento utilizado para desenha-lo e com a complexidade do desenho sendo exibido em si. Por isso, entre um desenho e outro, devemos levar em consideração essas variações de tempo (para jogos e animações simples, é possível criar um algoritmo que mantém a taxa de atualizações entre quadros constante. Há uma extensa discussão sobre isso no livro Killer Game Programming in Java, disponível online no site do prof. doutor Andrew Davison ou versão impressa na Amazon .)

O tempo entre dois quadros

Para um jogo suave e convincente, é fundamental sabermos o tempo entre dois quadros. O OpenGL não fornece nenhuma função que nos permita saber essa informação, porém, mais uma vez, a SDL vem em nosso resgate com a função SDL_GetTicks(). Essa função retorna o número de milisegundos transcorridos desde o início da aplicação.

Dessa forma, podemos calcular facilmente o tempo entre dois quadros, observe:

//lastTicks guarda quando foi pintado o último quadro
lastTicks = SDL_GetTicks();
while (true)
{
   Uint32 thisTicks = SDL_GetTicks();
   //Calcula em ticks a diferença de tempo
   //entre o quadro atual e o anterior
   ticks = thisTicks - lastTicks;
   //Atualizamos lastTicks
   lastTicks = thisTicks;

   //Processa o jogo
   processEvents();
   processLogics();
   draw();
}

Limpando a tela

Antes de começar a desenhar, é necessário limpar a tela. O OpenGL não faz essa operação por padrão, você pode até mesmo “reproveitar” uma tela já pintada e desenhar sobre ela apenas as diferenças. Isso, no entanto, é extremamente trabalhoso.

Primeiramente, temos que definir qual cor será aplicada na tela quando ela for apagada. Fazemos isso usando o comando:

void glColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha )

Que recebe como parâmetro a quantidade de vermelho, verde e azul, mais o componente alfa (por enquanto, deixe esse último componente em 0, ele será explicado só bem mais tarde, ao falarmos de blending). Os valores de verde, vermelho e azul podem variar entre 0, para nenhuma cor, ou 1 para o máximo de cor.

Em seguida, temos que usar o comando

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

E a tela está limpa. Os parâmetros desse comando também serão tratados em detalhes mais tarde. O que você precisa saber agora, é que ele limpa os buffers de cores e de profundidade, que é onde a imagem está.

Movendo objetos pela tela

Como os objetos se movimentam pela tela? Para dar a sensação de que um objeto se move, basta desenharmos ele um pouco deslocado a cada iteração. Voltando um pouquinho na física, lembraremos que a velocidade é dada pela fórmula:

velocidade = distância / tempo

Se você não gosta de física, basta lembrar que o velocímetro de seu carro obedece essa fórmula te indicando a velocidade através dos quilômetros (distância) por hora (tempo) percorridos. Em nosso caso, a velocidade será medida por duas unidades comuns: pixels por milissegundos, quando quisermos fazê-la andar, ou graus por milissegundos, se estivermos girando. Assim, vamos supor que queiramos que o triângulo desenhado no programa anterior rode em 360º em 2 segundos. Essa é uma velocidade efetiva de 180º por segundo ou, 0,180 graus / milisegundo.

A distância (em graus) que deveríamos mover o triângulo entre dois quadros (com tempo de ticks milisegundos entre si) então é:

velocidade = distância / tempo
distancia = velocidade * tempo
distanciaEmGrausParaGirar = 0,180º/ms * ticks

O programa abaixo, faz essa rotação. Apenas os métodos processLogics(), draw() e o main() foram alterados, preste especial atenção neles. Além disso, duas três novas variáveis globais foram criadas. Decidi manter o código todo para você poder dar copy&paste em seu IDE:

#include "SDL.h"
#include "GL/gl.h"

#include <stdexcept>
#include <iostream>

SDL_Surface* window;

//Controle do tempo
Uint32 lastTicks;
Uint32 ticks;

//Total graus para rodar
float degreesToRotate;

int createFlags(bool fullscreen)
{

    //Iniciamos com a OpenGL e paleta de hardware
    int flags = SDL_OPENGL | SDL_HWPALETTE;

    if (fullscreen)
        flags |= SDL_FULLSCREEN;

    const SDL_VideoInfo* info = SDL_GetVideoInfo();

    //Criarmos uma superfície de hardware,
    //se este estiver disponível
    if (info->hw_available)
        flags |= SDL_HWSURFACE;
    else
        flags |= SDL_SWSURFACE;

    //Aceleração por hardware?
    if(info -> blit_hw)
        flags |= SDL_HWACCEL;

    return flags;
}

int setupOpenGL(int bpp, bool fullscreen)
{
    //Atributos do opengl
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
    SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, bpp);
    SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 0);
    SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 0);
    SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 0);
    SDL_GL_SetAttribute( SDL_GL_ACCUM_ALPHA_SIZE, 0);

    return createFlags(fullscreen);
}

void setup(int width, int height, int bpp, bool fullscreen)
{
    //Inicializamos o subsistema de video.
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
        throw std::runtime_error(SDL_GetError());

    //Tentamos criar a janela
    window = SDL_SetVideoMode(width, height, bpp,
    setupOpenGL(bpp, fullscreen));

    //Sem sucesso? Lançamos uma exceção com o erro.
    if (window == NULL)
        throw std::runtime_error(SDL_GetError());

    glViewport(0,0, width, height);

    //Configuramos a função de des-inicialização
    atexit (SDL_Quit);
}

/** Espera o usuário pressionar o x da janela. */
void processEvents()
{
    SDL_Event event;

    while (SDL_PollEvent(&event) != 0)
    {
        switch (event.type)
        {
            case SDL_QUIT:
                exit(0); //Fechamos a apliação
                break;
        }
    }
}

void draw()
{
    glPushMatrix();
        //Limpa a tela
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //Gira o triângulo
        glRotatef(degreesToRotate, 0,0,1);

        //Desenha o triângulo
        glBegin(GL_TRIANGLES);
            glColor3f(1,0,0);
            glVertex2f(0.0f,0.5f);
            glColor3f(0,1,0);
            glVertex2f(-0.5f, -0.5f);
            glColor3f(0,0,1);
            glVertex2f(0.5f, -0.5f);
        glEnd();
    glPopMatrix();
}

void processLogics()
{
    //Distância para girar (em graus) =
    //velocidade (0.180f) * tempo (ticks)
    float distance = 0.180f * ticks;
    degreesToRotate += distance;
}

int main(int argc,char* argv[])
{
    try
    {
        setup(640, 480, 8, false); //Faz a mágica acontecer
        degreesToRotate = 0;
        lastTicks = SDL_GetTicks();

        //Loop principal do jogo
        while (true)
        {
            Uint32 thisTicks = SDL_GetTicks();
            //Calcula em ticks a diferença de tempo entre
            //o quadro atual e o anterior
            ticks = thisTicks - lastTicks;

            //Atualizamos lastTicks
            lastTicks = thisTicks;

            processEvents();
            processLogics();
            draw();

            //Trocamos a superfície de desenho
            //pela exibida na tela
            SDL_GL_SwapBuffers();
        }
    }
    catch (std::exception &e)
    {
        std::cout << "Error: "<< e.what();
        exit(1);
    }
}

Mais material para leitura

Aqui mesmo no Ponto V, encontramos dois ótimos materiais sobre esse assunto. O primeiro é o artigo Animação baseada em tempo, do Paulo V. Radtke e o segundo é o artigo O loop de animação, de minha autoria.

Além disso, você pode ler online o já citado livro Killer Game Programming in Java, mais especificamente no capítulo 2, An animation framework. O material está em inglês, mas trabalha também questões como imprecisão de timers, e diferentes abordagens de game loop.

Revisando

Você aprendeu que:

  • Animações feitas pelo computador tem apenas dois quadros: o que está sendo exibido e o que está sendo pintado, e esses quadros são guardados em buffers na memória de vídeo;
  • Como limpar a tela e definir a cor de fundo com o comando glClearColor e glClear;
  • Que o comando GL_SwapBuffers tem duas funções básicas: Enviar os comandos do OpenGL efetivamente para o hardware de vídeo e trocar o buffer de desenho com o que está sendo exibido na tela;
  • Que o loop de um jogo se divide entre processar eventos, processar lógica e pintar a tela;
  • Que o tempo entre duas iterações do loop (portanto, entre dois quadros) não é constante;
  • Como é possível calcular o tempo entre dois quadros usando a função SDL_GetTicks;
  • E que devemos levar em consideração esse tempo na hora de fazer nossos desenhos.

Comentários (8)
  • Tiago  - Dúvida de API...
    avatar

    E ai, blz

    Como estou pensando em fazer um curso para programação de jogos, estou começando a estudar sozinho e comprei o livro "OpenGL Uma Abordagem Prática e Objetiva"... Nele é usado a API glut para criar a janela... Tinha visto em algum outro site que a glut não é adequada para jogos, e no seu artigo anterior você disse que a glut não é adequada para jogos. Então to pensando em começar a estuda, junto com OpenGL, SDL, QT ou GTK, apesar de não saber exatamente qual a diferença entre elas.

    Alguem poderia me da alguma dica?

    vlw

  • Vinícius Godoy de Mendonça  - Ambientes de Janela
    avatar

    A Qt e o Gtk tem um enfoque mais em aplicações de janelas mesmo, com botõezinhos, caixas de texto, etc...

    A SDL é uma biblioteca de acesso de um nível um pouco mais baixo, mas já suficiente para jogos. O legal dela é que ela também dá suporte a controles, sockets e threads, que são bastante úteis para jogos.

    Particularmente, acho uma das melhores bibliotecas gráficas para jogos, mesmo integrada a OpenGL.

    Já para janelas comuns, a Qt e a wxWidgets são ótimas opções.

  • mateus  - audio
    avatar

    Muito bom seus artigos, resolveu muitos dos meus problemas!
    Mas procurei e não encontrei nenhum artigo sobre a implementação de audio! :(
    Obrigado.

  • Vinícius Godoy de Mendonça
    avatar

    Oi Mateus.

    Para ver como tocar áudio usando a SDL, você poderá seguir esse tutorial:
    http://diogorbg.blogspot.com/2007/12/tocando-msica-e-sons-com-sdlmixer .html

    O foco desses artigos é em opengl, o que por si só já era bastante conteúdo.

  • mateus
    avatar

    beleza, brigadão! :)

  • Wallace  - Dúvida com tempo entre dois quadros
    avatar

    Oi, no tutorial #6 em que entra-se em alguns detalhes da arquitetura da SDL é dito:

    Citou:
    Essa decisão de design tem uma consequência muito importante: os comandos da OpenGL não são imediatamente jogados para o hardware. Eles são agrupados para serem enviados mais tarde – o que não só otimiza o uso da rede, mas também abre margem para outras otimizações. Por consequência, um erro comum de muitos programadores é tentar medir o tempo levado pelos comandos de pintura simplesmente adicionando funções antes e depois de um comando (ou conjunto de comandos) o que, nessa arquitetura, certamente gerará resultados inválidos. Afinal, não há garantias de que os comandos serão imediatamente executados, e, o que acaba sendo medindo com essa estratégia é a velocidade que a OpenGL armazena os comandos para futura execução.

    E aqui é dito que para calcular o tempo entre dois frames usa-se exatamente esse mecanismo. Na seção "O tempo entre dois quadros".
    Poderia discorrer um pouco mais sobre o assunto? Fiquei confuso.

    Obrigado

  • ViniGodoy
    avatar

    O problema que descrevo, é que tem gente que tenta medir o tempo levado entre um glBegin() e um glEnd(), mas esse tempo sempre será próximo de 0, pois os comandos mesmo só serão enviados no quando os buffers são trocados.

    Como o SDL_GL_SwapBuffers() está sendo contado no tempo do quadro, então, fica possível garantir a taxa de quadros.

  • Bryan Gabriel  - Funçoes da GL nao sao reconhecidas
    avatar

    As funçoes da GL nao estao sendo reconhecidas pelo compilador

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter