Programação com OpenGL/Modern OpenGL Introduction

Origem: Wikilivros, livros abertos por um mundo aberto.
Nosso primeiro programa

Introdução[editar | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

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
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 | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

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 | editar código-fonte]

  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