Programação com OpenGL/Modern OpenGL Introduction
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);
}
#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 comow=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]- ↑ 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