Programação com OpenGL/Modern OpenGL Introduction

Nosso primeiro programa

Introdução

editar

A maior parte da documentação sobre OpenGL usam recursos que estão ficando obsoletos, em particular os "pipelines fixo"(Fixed Pipeline). desde do OpenGL 2.0 acima existem o pipeline programável(Programmable Pipeline), aonde partes programáveis são completas com shaders, escrito em GLSL uma linguagem parecida com a C.

Como a documentação é para pessoas que estão aprendo OpenGL então nada melhor que usar um "OpenGL moderno" para começar. A programação de pipeline é muito mais flexível, mas não é tão intuitiva como as pipelines fixas.

Vamos no então começar com um código simples. Usaremos algo próximo do que é usado nos tutoriais do NeHe´s para o OpenGL 1.x, com exemplos e tutoriais para o melhor entendimento da teoria sobre as pipeline programáveis.

Fazer os primeiros vertex arrays(ordenação de vértices) e os shader é mais difíceis se comparamos com o antigo método que é mais simples porem com as desvantagens de um pipeline fixo, Porem no final, especialmente para os buffer de objetos o código será mais limpo e os gráfico serão mais rápidos.

O código dos exemplos estão em uma página de 'domínio publico. Fique a vontade para usar-los como quiser.

Compartilhe esta documentação com seus amigos! o Wikibook precisa de mais reconhecimento e contribuições :)

Notas:

  • É possível misturar pipeline fixa com uma pipeline programável, até certo ponto, mas a pipeline fixa está ficando obsoleta, e não está disponível no OpenGL ES 2.0 ( ou os derivados da WebGL ), então não vamos fazer eles.
  • Existe também o OpenGL 3 e 4, que introduziram os shaders geométricos, mas que são apenas uma mudança na formação da luz na versão anterior e uma vez que não está disponível em plataformas movéis até 2012, então nos concentraremos no OpenGL 2.0

Bibliotecas básicas

editar

Serão necessários a instalação das seguintes bibliotecas:

  • OpenGL
  • GLEW (OpenGL Extension Wrangler) : Para termos protótipos em C disponíveis ( com extensões do para OpenGL em várias plataformas)
  • GLUT (OpenGL Utility Toolkit) : Um controlador de janelas compatível com várias plataformas.

No Debian e Ubuntu GNU/Linux, os pacotes são chamados de:

  • libgl1-mesa-dev
  • libglew1.5-dev
  • freeglut3-dev

Certifique que as ferramentas básicas de compilação estão instaladas que vem neste pacote:

  • build-essential

Para se entender estas bibliotecas para OpenGL, examine as APIs, Bibliotecas e acrónimos

Nota: Nós escolhemos o GLUT porque ele é a biblioteca de composição de janelas mais minimalista que tem. Nós não vamos usar recursos avançados, e a maior parte dos nossos códigos estão em OpenGL plano, assim você não terá problema em se adaptar a outras bibliotecas maiores como a GLFW, SDL e SFML quando for criar seu novo jogo ou aplicativo. Nos poderemos escrever tutoriais específicos para estas bibliotecas.

Mostrando um triângulo em 2D

editar

Vamos começar pela parte mais fácil ao invés de sofrer com um programa muito complexo, e que demoraria muito para ficar pronto, vamos mostrar o básico para um programa funcional e então poderemos melhora-lo passo a passo,

O Triângulo é a unidade mais básica na programação 3D, atualmente todo que você vê em programa um jogo é feito de triângulos! pequenos, triângulos texturizados, mas ainda assim triângulos :)

Para mostrar um triângulo com pipeline programável, você precisará:

  • Um Makefile para compilar seu aplicativo
  • Inicializar o OpenGL com a ajuda das bibliotecas
  • Um conjunto de coordenadas ordenadas com 3 vértices para o triangulo
  • Um programa GLSL com:
    • um vertex shader: passando cada vertex individualmente, ele irá calculara as coordenadas na tela (2D).
    • um fragmento (pixel) shader: o OpenGL irá passar cada pixel que existe no triangulo, e então calculará as suas cores.
  • Passar as vertices para o vertex shader

Makefile

editar

Podemos compilar mais facilmente através de um comando make, para isto faremos um arquivo 'Makefile', que facilita a compilação do nosso exemplo, escreva isto no arquivo:

LDLIBS=-lglut -lGLEW -lGL
all: triangle

Para compilar nosso aplicativo, digite no terminal: make

Um Makefile com mais 'enfeite' pode ser assim:

CC=g++
LDLIBS=-lglut -lGLEW -lGL
all: triangle
clean:
        rm -f *.o triangle
.PHONY: all clean

Isto permite que você digite 'make clean' que ira remover os objetos executáveis gerados pela compilação. A regra .PHONE é para que o "all" e o "clean", não seja confundido com um arquivo, isto evita algumas confusões caso você possua arquivos com este nome na mesma pasta.

Se você usa um ambiente de desenvolvido diferente, veja a seção Configurando o OpenGL

Inicialização

editar

Vamos criar um arquivo chamado triangle.c:

/* Usando o padrão de saída fprintf */
#include <stdio.h>
#include <stdlib.h>
/* Use o glew.h ao inves do gl.h para declarar as funções do OpenGL */
#include <GL/glew.h>
/* Usando o GLUT com gerenciador de janelas */
#include <GL/glut.h>

/* COLOCAREMOS AS VARIAVEIS GLOBAIS AQUI MAIS TARDE */

int init_resources(void)
{
  /* PREENCHEREMOS DEPOIS */
  return 1;
}

void onDisplay()
{
  /* PREENCHEREMOS DEPOIS*/
}

void free_resources()
{
  /* PREENCHEREMOS DEPOIS */
}

int main(int argc, char* argv[])
{
  /* Funções necessárias para iniciar a GLUT */
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
  glutInitWindowSize(640, 480);
  glutCreateWindow("Meu Primeiro Triângulo");

  /* Iniciando as extensões de montagem */
  GLenum glew_status = glewInit();
  if (glew_status != GLEW_OK)
  {
    fprintf(stderr, "Erro: %s\n", glewGetErrorString(glew_status));
    return EXIT_FAILURE;
  }

  /* Quando as funções de inicialização são executadas sem erros,
  o programa pode iniciar os recursos */
  if (1 == init_resources())
  {
    /* Pode então mostrar se tudo correr bem */
    glutDisplayFunc(onDisplay);
    glutMainLoop();
  }

  /* Se o ocasionalmente programa sair ,
  liberamos os recursos da memória e completaremos ele com sucesso*/
  free_resources();
  return EXIT_SUCCESS;
}

Na função init_resources, nós criaremos nosso programa GLSL. Na função onDisplay, nós desenharemos o triangulo. Na função free_resources, destruiremos nosso programa GLSL.

Coordenadas (Vertex Array)

editar

Primeiro nosso primeiro triângulo será mostrado em 2D e depois mostraremos algo mais complexo. Nos descreveremos o triângulo com coordenadas 2D (x, y) com 3 pontos. Por padrão as coordenadas OpenGL estão no intervalo [-1, 1]

  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8
  };

Por enquanto manteremos esta estrutura de dados em mente, nos escreveremos os códigos depois.

Nota: as coordenadas estão entre -1 e +1, mas nossa janela não é quadrada! na próxima lição, mostraremos como corrigir a aparência.

Vertex Shader

editar

Agora faremos um programa em GLSL, que pegara cada ponto que foi passado pelas coordenadas, e depois mostrará na tela. Neste caso cada ponto vem de coordenadas prontas para exibição em 2D, então não alteraremos elas. Nosso programa em GLSL ficará assim:

#version 120
attribute vec2 coord2d;
void main(void) {
  gl_Position = vec4(coord2d, 0.0, 1.0);
}
 
Wikipedia
A Wikipédia tem mais sobre este assunto:
GLSL#Versions
  • #version 120 v1.20 é a versão do GLSL do OpenGL 2.1.
  • No OpenGL ES 2' o GLSL é baseada na GLSL v1.20, mas a versão é 1.00(#version 100). [1]
  • A função coord2d será para o nosso vertex; é uma variável que nós precisaremos declarar em nosso código C.
  • A função gl_Position é para o resultado que será apresentado na tela; uma variável de saída.
  • A função vec4 pega nossas coordenadas x e y, e depois preencher com 0 a coordenada z, e por ultimo como w=1.0 para a coordenada ficar homogénea (usado por Transformação de matrizes).

Agora precisamos fazer que o OpenGL compile este shader. Então vamos para função init_resources acima da função main

/*
Função: init_resources
Recebe: void
Retorna: int
Este função foi com todas a GLSL como o material
explicado neste exemplo.
Retorna 1 se tudo der certo, e 0 se ocorrer algum erro
*/
int init_resources(void)
{
  GLint compile_ok = GL_FALSE, link_ok = GL_FALSE;

  GLuint vs = glCreateShader(GL_VERTEX_SHADER);
  const char *vs_source = 
#ifdef GL_ES_VERSION_2_0
    "#version 100\n"  // OpenGL ES 2.0
#else
    "#version 120\n"  // OpenGL 2.1
#endif
    "attribute vec2 coord2d;                  "
    "void main(void) {                        "
    "  gl_Position = vec4(coord2d, 0.0, 1.0); "
    "}";
  glShaderSource(vs, 1, &vs_source, NULL);
  glCompileShader(vs);
  glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok);
  if (0 == compile_ok)
  {
    fprintf(stderr, "Error in vertex shader\n"); 
    return 0;
  }

Nós passamos as informações como uma string para glShader (depois de ler todo o código da shader diferente e mais conveniente). Nós especificaremos os tipo em GL_VERTEX_SHADER

Fragment Shader

editar

Uma vez que o OpenGL tem nossos 3 pontos na tela, encheremos os espaços entre eles para fazer um triângulo. Para cada pixel entre os 3 pontos, nós chamaremos um fragment shader, em nosso fragment shader, diremos que queremos preencher cada pixel com a cor azul:

#version 120
void main(void) {
  gl_FragColor[0] = 0.0;
  gl_FragColor[1] = 0.0;
  gl_FragColor[2] = 1.0;
}

Nos compilaremos semelhante-mente ao GL_FRAGMENT_SHADER. Vamos continuar nossa função init_resources:

  GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
  const char *fs_source =
    "#version 120           \n"
    "void main(void) {        "
    "  gl_FragColor[0] = 0.0; "
    "  gl_FragColor[1] = 0.0; "
    "  gl_FragColor[2] = 1.0; "
    "}";
  glShaderSource(fs, 1, &fs_source, NULL);
  glCompileShader(fs);
  glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok);
  if (!compile_ok) {
    fprintf(stderr, "Error in fragment shader\n");
    return 0;
  }

Programando o GLSL

editar

Uma programa em GLSL é a combinação dos vertex e fragment shader, Normamente eles trabalham em conjunto, e o vertex shader pode passar informações adicionais para o fragment shader.

Criaremos uma váriavel global abaixo do #include para gravar o identificador do programa:


GLuint program; //váriavel global é muito exigido em programas com GLUT mesmo não sendo recomendado.

Aqui é como um link(ligação) para o vertex e fragment shader no programa, continuaremos com o nosso init_resource com:

  program = glCreateProgram();
  glAttachShader(program, vs);
  glAttachShader(program, fs);
  glLinkProgram(program);
  glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
  if (!link_ok) {
    fprintf(stderr, "glLinkProgram:");
    return 0;
  }

Passaremos as vértices do triângulo para o Vertex Shader

editar

Nos mencionamos que passaríamos cada vértice do triângulo para o vertex shader, usando o atributo coord2d. Aqui é como declaremos ele em nosso código C.

Primeiro vamos criar uma segunda variável global:


GLint attribute_coord2d;

terminaremos nosso init_resources com:

  const char* attribute_name = "coord2d";
  attribute_coord2d = glGetAttribLocation(program, attribute_name);
  if (attribute_coord2d == -1) {
    fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
    return 0;
  }

  return 1;
}

Agora nós passaremos nossas vértices do triângulo para o vertex shader. Vamos escrever em nossa função onDisplay. cada seção é explicado nos comentários:

void onDisplay()
{
  /* Plano de fundo branco */
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);

  glUseProgram(program);
  glEnableVertexAttribArray(attribute_coord2d);
  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8,
  };
  /* Descreveremos nossa array de vértices para o OpenGL (ele não pode descobrir o formato automaticamente)*/
  glVertexAttribPointer(
    attribute_coord2d, // atributo
    2,                 // numero de elementos por vértice, que é (x,y)
    GL_FLOAT,          // o tipo de cada elemento
    GL_FALSE,          // Como os nossos valores são falsos.
    0,                 // sem informação extra em cada posição.
    triangle_vertices  // pontos para a array do C.
  );
  
  /* Puxando cada elemento do nosso buffer_vertices para o vertex shader */
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDisableVertexAttribArray(attribute_coord2d);

  /* Mostrando os resultados. */
  glutSwapBuffers();
}

O glVertexAttribPointer chamará o OpenGL para recuperar cada vertices que foi criada no buffer de dados do init_resources e passará para o vertex shader. Estes vertices definirão a posição de cada ponto na tela, formando um triângulo, cujo os pixel são coloridos pelo fragment shader.

Nota: em nosso próximo tutorial será o introduzido o conceito de Vertex Buffers Objects, que é levemente mais complexo e uma novidade para guarda os vértices na placa gráfica.

Agora só sobrou a parte do free_resources, que limpará tudo quando sairmos do programa. Não necessário neste caso especifico, mas é bom ter esta estrutura em seus aplicativos:

void free_resources()
{
  glDeleteProgram(program);
}

Nosso primeiro tutorial de OpenGL 2.0 está completo!

Referencias

editar
  1. Cf. [http>//www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf OpenGL ES Shading Language 1.0.17 Specification]. Khronos.org (2009-05-12). Página visitada em 2011-09-10. - A OpenGL ES Shading Language (também conhecido como GLSL ES ou ESSL) é baseado na OpenGL Shading Language (GLSL) versão 1.20
Navegue e baixe os códigos completos