Você está na página 1de 21

Universidade Federal de Santa Maria

Departamento de Eletrônica e Computação


Prof. Cesar Tadeu Pozzer
Disciplina: Computação Gráfica Avançada
pozzer@inf.ufsm.br
05/10/2013

OpenGL – Conceitos Avançados

Neste módulo serão vistos conceitos mais avançados da programação em OpenGL, que tratam:
 Otimizações: Vertex Array, Display List, VAO, VBO e otimizações de pipeline;
 Realismo: Iluminação, material e textura;
 Pipeline OpenGL
 Tudo o que está vem vermelho faz parte da antiga API do OpenGL, mas ainda com suporte
garantido. http://pyopengl.sourceforge.net/documentation/deprecations.html

1. Arquitetura Cliente-Servidor

O OpenGL trabalha com a filosofia cliente-servidor. Isso significa que se pode trabalhar em um computador
(cliente) e ver os resultados em outro (servidor). O cliente é responsável pela geração dos comandos OpenGL
enquanto que o servidor é responsável pelo processamento (render) destes dados e exibição na tela. Caso não
existirem computadores em rede, o mesmo computador exerce o papel de cliente e servidor (maior parte dos
casos). Esse conceito é importante para melhor compreensão de conceitos avançados do OpenGL. Veja os
comandos glFlush() e glFinish() para uso em uma aplicação distribuída que faça uso de cliente e
servidor em máquinas diferentes conectadas por uma rede.

Client server

2. Vertex Array

Usado para:
 Reduzir o número de chamada de funções, que podem causar redução de desempenho da aplicação
o Desenhar um polígono com 20 lados requer 22 chamadas de funções: uma para cada vértice
+ glBegin() + glEnd(). Passando-se as normais, pode-se duplicar o número de
chamadas.
 Evitar a replicação de vértices, resultando em economia de memória.
o Para se especificar um cubo, gasta-se 24 vértices, sendo cada vértice replicado 3x. O mesmo
cubo poderia ser desenhado com apenas 8 vértices.

Com o uso de vertex array (OpenGL 1.1), os 20 vértices do polígono poderiam ser colocados em um vetor e
passados em uma única chamada para o OpenGL. O mesmo poderia ocorrer com os vetores normais. Em
relação ao número de vértices, pode-se facilmente definir vértices compartilhados por várias faces.

Passos para se utilizar vertex array:


 Ativar o vetor de dados que se deseja utilizar. Os mais comuns são de vértices, normais e textura;
 Colocar os dados nos arrays. Estes dados, no modelo cliente-servidor, são armazenados no cliente;
 Desenhar o objeto com os dados dos arrays. Neste momento, os dados são transferidos para o
servidor.

1
Para ativar os buffers de dados, deve-se utilizar o comando glEnableClientState(), que recebe como
parâmetro constantes associadas a cada vetor:
 GL_VERTEX_ARRAY,
 GL_COLOR_ARRAY,
 GL_INDEX_ARRAY,
 GL_NORMAL_ARRAY,
 GL_TEXTURE_COORD_ARRAY, e
 GL_EDGE_FLAG_ARRAY

void glEnableClientState(GLenum array);


void glDisableClientState(GLenum array);

glEnableClientState (GL_COLOR_ARRAY);
glEnableClientState (GL_VERTEX_ARRAY);

Para a definição dos dados, existem comandos específicos para cada um dos 6 tipos de dado. O comando
glVertexPointer() é usado para indicar o vetor de vértices. O comando glNormalPointer() para
especificação de normais.

void glVertexPointer(GLint size,


GLenum type,
GLsizei stride,
const GLvoid *pointer);

void glNormalPointer(GLenum type,


GLsizei stride,
const GLvoid *pointer);

void glTextCoordPointer(GLenum type,


GLsizei stride,
const GLvoid *pointer);

void glColorPointer(GLint size,


GLenum type,
GLsizei stride,
const GLvoid *pointer);

No comando glVertexPointer():
 pointer especifica onde os dados das coordenadas podem ser acessados.
 type especifica o tipo de dados: GL_SHORT, GL_INT, GL_FLOAT, ou GL_DOUBLE de cada
coordenada do array.
 size é o número de coordenadas por vértice (2, 3, ou 4). Não necessária para especificação de
normais (sempre é 3). Para cor pode ser 3 ou 4.
 stride é o offset em bytes entre vértices consecutivos. Deve ser 0 se não existir espaço vago entre
dois vértices.

Para enviar os dados para o processamento (enviar para o servidor na arquitetura cliente-servidor do
OpenGL), existem vários comandos.

void glDrawElements(GLenum mode,


GLsizei count,
GLenum type,
void *indices);
O comando glDrawElements() define uma seqüência de primitivas geométricas onde os parâmetros
são:
 count: É o úmero de elementos do vetor
 indices: Vetor de índices dos elementos

2
 type: Indica o tipo de dados do array indices. Deve ser GL_UNSIGNED_BYTE,
GL_UNSIGNED_SHORT ou GL_UNSIGNED_INT.
 mode: indica o tipo de primitivas a serem geradas, como GL_POLYGON, GL_LINE_LOOP,
GL_LINES, GL_POINTS, etc.

Exemplo de uso:
GLint allVertex[] = { 0,0,0, 1,0,0, 1,1,0, 0,1,0,
0,0,1, 1,0,1, 1,1,1, 0,1,1};

//GLint allNormals[8] = {Nesta configuração, define-se uma normal por vértice};

GLfloat allColors[] = { 1,0,0, 0,1,0, 1,1,1, 1,0,1,


0,0,1, 1,1,1, 0,1,1, 0,0,0};

GLubyte allIndices[] = {4, 5, 6, 7,


3 2
1, 2, 6, 5,
0, 1, 5, 4,
0, 3, 2, 1, 7 6
0, 4, 7, 3, 1
0
2, 3, 7, 6};

glEnableClientState (GL_VERTEX_ARRAY); 4 5
glEnableClientState (GL_COLOR_ARRAY);
glVertexPointer(3, GL_INT, 0, allVertex);
glColorPointer( 3, GL_FLOAT, 0, allColors);
//glNormalPointer(GL_FLOAT, 0, allNormals);
glDrawElements(GL_QUADS, 6*4, GL_UNSIGNED_BYTE, allIndices);

Face 0 Face 1 Face N

4 5 6 7 1 2 6 5 0 1 5 … 6

V0 V1 V2 V3 V4 V5 V6 V7

C0 C1 C2 C3 C4 C5 C6 C7
Para a especificação de um único vértice por vez de um vertex array pode-se utilizar o comando
glArrayElement(GLint). O seguinte trecho de código tem o mesmo efeito que o comando
glDrawElements() apresentado anteriormente.

glBegin (GL_QUADS);
for (i = 0; i < 24; i++)
glArrayElement(allIndices[i]);
glEnd();

Observações:
• Existe um mapeamento direto de 1 para 1 entre o vetor de vértices com os vetores de normais,
cores, etc.
• O vetor de índices é GLubyte pois como existem apenas 24 = 6*4 índices, usar um tipo maior
como GLuint seria um desperdício de memória.
• Se um vértice compartilhado tiver normais diferentes em cada face, deve-se replicar os vértices e as
normais (o mesmo vale para as cores). Neste caso, perde-se a otimização de memória mas continua-

3
se ganhando com a redução de chamadas em relação a definição do objeto utilizando-se os
comandos convencionais como glVertex*() e glNormal*(). Para o caso do cubo, seriam
necessários 24 vértices (4 por face x 6 faces), 24 normais e 24 cores.
[http://www.songho.ca/opengl/gl_vertexarray.html]
• Este recurso do GL, quando usado com normais, é melhor aplicado em superfícies planares, como
terrenos. Para objetos volumétricos fechados como cubos, não faz sentido ter-se normais
compartilhadas entre as 3 faces adjacentes, o que exige a replicação de vértices. O mesmo se aplica
para cilindros.

3. Display List

É um grupo de comandos OpenGL que são armazenados para execução futura. Quando um display list é
chamado, os comandos são executados na ordem que foram definidos. Nem todos os comandos OpenGL
podem ser armazenados em um display list. Não podem ser utilizados especialmente comandos relacionados
com vertex array, comandos como glFlush(), glFinish(), glGenLists(), dentre outros.

Deve ser utilizada sempre que for necessário desenhar o mesmo objeto mais de uma vez. Neste caso, deve-se
guardar os comandos que definem o objeto e então exibir a display list toda vez que o objeto precisar ser
desenhado. No caso de um carro que possui 4 rodas, pode-se gerar um display list com a geometria de uma
roda e invocar a renderização, uma vez para cada roda, lembrando de aplicar as devidas transformações para
posicionar cada roda no devido lugar.

Os dados de um display list residem no servidor, reduzindo deste modo o trafego de informações.
Dependendo da arquitetura do servidor, o display list pode ser armazenado em memória especial para
agilizar o processamento.

Para criar um display list deve-se utilizar os seguintes comandos: glGenLists(), glNewList() e
glEndList() como mostrado no seguinte exemplo:

GLuint id_objeto;

void init()
{
id_objeto = glGenLists(1);
glNewList(id_objeto, GL_COMPILE);
//comandos de desenho do objeto
//vértices, transformações, etc.
glEndList();
}

void display()
{
...
glRotated(10, 1, 0, 0);
glCallList(id_objeto);

glTranslated(10, 31, 0);


glCallList(id_objeto);
...
glSwapBuffers();
}

 GLuint glGenLists(GLsizei range): Aloca um intervalo contínuo de índices. Se o


parâmetro range for igual a 1, gera um único índice que é o valor retornado pela função. O índice
retornado é usado pelas funções glNewList() e glCallList(). O retorno zero significa erro.

4
 void glNewList (GLuint list, GLenum mode): Especifica o início do display list.
Todos os comandos válidos especificados antes do comando glEndList() são guardados no
display list.
o List: identificador do display list.
o Mode: aceita dois argumentos: GL_COMPILE_AND_EXECUTE e GL_COMPILE (indica
que os comandos não devem ser executados à medida que são inseridos no display list).
 void glEndList (void): indica o fim do display list.
 void glCallList (GLuint list): Executa o display list referenciado por list. Os
comandos são executados na ordem que foram especificados, do mesmo modo caso não fosse
utilizado este recurso.

Além de reduzir o número de chamada de funções, bem como a transferência de dados para o servidor,
reduz-se também processamento para geração dos objetos. Supondo que fosse necessário gerar um círculo
(ou uma esfera) por coordenadas polares, diversos cálculos de ângulos podem ser realizados uma única vez,
visto que apenas comandos OpenGL são armazenados na display list para processamento futuro.
 Entretanto, uma vez gerada o display list, os valores das coordenadas do círculo não podem mais ser
alterados.
 Comandos GLUT também pode ser inseridos, uma vez que são desmembrados em comandos básicos
do OpenGL.
 Pode-se criar display list hierárquicos, onde um display list invoca outros.

...
id_objeto = glGenLists(1);
glNewList(id_objeto, GL_COMPILE);
glBegin(GL_POLYGON);
for(int i=0; i<1000; i++)
{
x = cos(ang)*radius;
y = sin(ang)*radius;
ang+=(360.0/1000);
glVertex2f(x,y);
}
glEnd();
glEndList();
...

Para maiores detalhes dos benefícios do uso de display list e recursos adicionais, consulte [1].

4. Vertex Buffer Object e Vertex Array Object

Por Cícero Pahins e Guilherme Schardong, 2013

Buffer Objects são espaços de memória alocados no contexto do OpenGL que servem para
armazenar dados. Vertex Buffer Objects (VBO) são usados para armazenar dados de vértices
(coordenadas, cores, normais e outros dados relevantes), os quais são necessários para desenhar
geometria na tela. A maior vantagem de usar VBOs é que os dados ficam armazenados na GPU, o
que evita que o programador tenha que enviá-los para a GPU a cada ciclo de renderização.

Os VBOs foram concebidos para aliar as funcionalidades dos Vertex Arrays e Display Lists,
enquanto evitam suas desvantagens:

• Os Vertex Arrays reduzem o número de chamadas de funções do OpenGL e proporciona o


uso redundante de vértices compartilhados. No entanto, a desvantagem dessa técnica é que
as funções do Vertex Array estão no cliente e os dados devem ser reenviados para o servidor

5
a cada vez que o array for referenciado, diminuindo o desempenho da aplicação como um
todo.

• A Display List é uma função do servidor, logo os dados não precisam ser reenviados a cada
renderização. O problema surge quando há a necessidade de alterar os dados já enviados,
uma vez que uma Display List compilada não admite alterações, causando a necessidade de
recriá-la para cada alteração a ser executada.

Os VBOs permitem que o usuário defina como os dados serão armazenados na memória da GPU,
ou seja, permite que, por exemplo, haja vários buffers, cada um contendo um dado relevante para a
renderização, ou que tudo seja armazenado em um buffer apenas. Deve-se notar que a GPU não
sabe como estes dados estão organizados, para isso há a necessidade de uma estrutura que descreva
como interpretá-los para a correta renderização da geometria.

Abaixo iremos construir um exemplo utilizando VBO. Note que o código está adequado ao
OpenGL 3.2+.

A partir do OpenGL 3.2 foi inserido o conceito de profile na criação de contextos, sendo eles o core
e o compatibility. Enquanto que o primeiro expõem as últimas e mais modernas funcionalidades da
API, tornando a utilização de shaders uma exigência, o segundo inclui as funções relativas ao
pipeline fixo e depreciadas, ou seja, aquelas que possuem seu uso desaconselhado.

A escolha do profile a ser utilizado é feita no momento da criação do contexto OpenGL, desta
maneira, depende diretamente do sistema operacional ou APIs utilizadas para este fim.

O código abaixo é utilizado na API GLFW para que o contexto OpenGL suporte ao mínimo a
versão 3.2 da API e exclusivamente as funcionalidades definidas no core (a criação de um contexto
não suportado pelo driver de vídeo resultará em erro):

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

O uso das funcionalidades (extensões) mais recentes do OpenGL é facilitada pelo uso da API
GLEW. Desta maneira, procure sempre fazer a linkagem dela em seu projeto. Veja mais em [1].

Primeiramente precisamos definir a lógica da estrutura de dados que será armazenada na GPU.
Como dito mais acima, há duas opções: utilizar vários buffers, cada um contendo um tipo de dado,
ou, somente um buffer para todos os dados. A escolha depende diretamente de como o programador
armazena, cria ou importa os seus dados, uma vez que diferentes API’s para importação de modelos
3D oferecem diferentes interfaces. Outro fator a ser considerado é o tamanho do VBO resultante
[2]:

• Pequeno
 Mais facilmente gerenciado, uma vez que cada objeto da cena pode ser representado
por um único VBO.
 Se o VBO tiver poucos dados o ganho de performance em relação a outras técnicas
pode não ser aparente. Muito tempo irá ser perdido na troca de buffers.
• Grande
 A renderização de vários objetos pode utilizar somente uma chamada de função,
melhorado a performance.

6
 Caso o gerenciamento separado de objetos seja muito complicado, isto irá gerar um
overhead desnecessário.
 Se os dados do VBO forem muito grandes é possível que não possam ser movidos
para a VRAM (Video RAM)

A formatação dos dados (ordem de armazenamento) também pode influenciar no desempenho final,
a wiki oficial do OpenGL fornece informações das melhores práticas em [3].

Para este exemplo iremos armazenar três informações a respeito do vértice: posição (3 floats),
normal (3 floats) e coordenada de textura (2 floats), como demonstra a Figura 1.

Figura 1. Formatação de dados utilizada no exemplo de VBO.


Note que o tamanho total de cada informação (posição + normal + coordenada de textura) é de 32
bytes, ou seja, se alocarmos n informações a respeito do vértice (n * sizeof(informação))
encontraremos a primeira tupla nos bytes 0 à 31, a segunda nos bytes 32 à 63, e assim por diante.
Este modo de armazenamento é dito intercalado, e pode ser representado pela seguinte sequência
de caracteres:

(VVVNNNTT) (VVVNNNTT) (VVVNNNTT)...,

onde V = vertex, N = normal e T = texture coordinate.

Observe no Código 1 a etapa de declaração de variáveis e definição da estrutura de dados e ordem


de armazenamento.

GLuint id_vbuffer = 0;
GLuint id_ibuffer = 0;

struct Vertex {
GLfloat vertex_xyz[3];
GLfloat normal_xyz[3];
GLfloat texcoord_uv[2];
};

Vertex vertexdata[NUM_VERTS] = { ... };


GLuint indexdata[NUM_INDICES] = { 0, 1, 2, ... };

Código 1. Exemplo VBO. Etapa de declaração de variáveis e definição da estrutra de dados.


Semelhante a utilização de Vertex Array, é criado um vetor de índices (indexdata) que irá coordenar
a ordem de acesso ao vetor de dados (vertexdata).
As variáveis id_vbuffer e id_ibuffer serão utilizadas para armazenar os ids dos VBOs a serem
criados, neste exemplo um para dados e outro para índices, e posteriormente utilizadas para
referenciá-los. O OpenGL não permite a mistura de dados de vértices aos de índices, por isso a
necessidade da criação de dois buffers.

7
A criação de uma VBO requer 3 passos:
1. Geração de um novo buffer com glGenBuffers()

void glGenBuffers(GLsizei n, GLuint * buffers);

Parâmetros:
 n: número de buffers a serem criados.
 buffers: endereço de uma variável ou array do tipo GLuint para armazenamento
do id de cada buffer.

2. Vinculação do buffer criado ao OpenGL com glBindBuffer()


void glBindBuffer(GLenum target, GLuint buffer);

Parâmetros:
 target: especifica o tipo de dado a ser armazenado, pode ser:
 dado de vértice: GL_ARRAY_BUFFER
 dado de índice: GL_ELEMENT_ARRAY_BUFFER
 buffer: id do buffer.

3. Cópia dos dados com glBufferData()

void glBufferData(GLenum target, GLsizeiptr size, const


GLvoid * data, GLenum usage);

Parâmetros:
 target: deve ser o mesmo utilizado em glBindBuffer().
 size: tamanho em bytes do buffer a ser criado (corresponde ao tamanho total em
bytes dos dados).
 data: ponteiro para o array de dados a ser copiado. Caso NULL, o VBO irá
somente reservar o espaço de memória.
 usage: especifica o padrão de uso esperado do armazenamento de dados, pode ser
qualquer combinação entre os grupos abaixo (9 possibilidades):

 Grupo A (“o conteúdo do armazenamento será:”)


 STATIC: especificado uma vez e utilizado muitas vezes
 STREAM: especificado e utilizado repetidamente
 DYNAMIC: especificado uma vez e utilizado uma vez

 Grupo B (“os dados serão movidos do(a):”)


 DRAW: aplicação para OpenGL
 READ: OpenGL para aplicação
 COPY: OpenGL para OpenGL (DRAW + READ)

8
A fim de descobrir o melhor local para armazenar os dados dos VBOs criados a implementação do
OpenGL (driver de vídeo) utilizará o parâmetro usage da função glBufferData() como “dica” e, por
isso, ele é muito importante na busca por maior desempenho.
Observe atentamente o Código 2 e faça a correlação dos parâmetros de função e a estrutura de
dados utilizados.
// Criação do VBO de dados de vértices
glGenBuffers(1, &id_vbuffer);
glBindBuffer(GL_ARRAY_BUFFER, id_vbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * NUM_VERTS, vertexdata,
GL_STATIC_DRAW); // transferência do dados de vértices

// Criação do VBO de dados de índices


glGenBuffers(1, &id_ibuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id_ibuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * NUM_INDICES, indexdata,
GL_STATIC_DRAW); // transferência do dados de índices

Código 2. Exemplo VBO. Etapa de transferência de dados para GPU.


Criados os VBOs e transferidos os dados, resta a etapa de renderização, a qual requer 3 passos
(semelhante ao utilizado em Vertex Array):
1. Vinculação do buffer a ser utilizado pelo OpenGL com glBindBuffer().
2. Ativação e definição dos atributos dos vértices

void glEnableVertexAttribArray(GLuint index);

void glVertexAttribPointer(GLuint index, GLint size, GLenum type,


GLboolean normalized, GLsizei stride, const GLvoid * pointer);

Parâmetros:
 index: índice do atributo do vértice (correspondente ao vertex shader)
 size: especifica o número de componentes do atributo, podendo ser 1, 2, 3 ou 4
 type: especifica o tipo de dado de cada componente do array
 stride: especifica o offset (intercalação) entre atributos consecutivos
 pointer: especifica o ponto de início (definido como um offset, e não como um
ponteiro) para o primeiro atributo do tipo definido no array

3. Chamada da função de desenho com glDrawElements() ou outra.

Um atributo do vértice (posição, índice, normal, coordenada de textura, etc.) é habilitado e definido
com as funções glEnableVertexAttribArray() e glVertexAttribPointer(), respectivamente. Através
da função glVertexAttribPointer() a formatação dos dados é enviada ao OpenGL. Note que no
Código 3 existem três chamadas a essa função, cada uma definindo a estrutura de dados utilizada
neste exemplo. Observe estas funções e compare-as com a Figura 1.
// Bind dos VBOs a serem utilizados
glBindBuffer(GL_ARRAY_BUFFER, id_vbuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id_ibuffer);

// Ativação e definição dos atributos dos vértices


glEnableVertexAttribArray(0); // texcoord_uv
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,
(void*)(sizeof(GLfloat) * 6));

glEnableVertexAttribArray(1); // normal_xyz

9
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,
(void*)(sizeof(GLfloat) * 3));

glEnableVertexAttribArray(2); // vertex_xyz
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,
(void*)(0));

// Chamada da função de desenho


glDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_INT, (void*)0);

Código 3. Exemplo VBO. Etapa de renderização. Informações referentes à Figura 1 estão


sublinhadas.
Note que o índice utilizado para a habilitação e definição de um determinado atributo através das
funções glEnableVertexAttribArray() e glVertexAttribPointer() deve ser o mesmo, no exemplo é
utilizado o índice 0 para as coordenadas de textura, o índice 1 para as normais e o índice 2 para os
vértices. (Quando utilizado shader os índices devem ser correspondentes).
Uma boa prática de programação a ser seguida é sempre retornar ao estado do OpenGL antes da
ativação de atributos, buffers, flags ou outros, procurando desabilitar recursos utilizados
pontualmente, como no caso da criação ou renderização de VBOs.
O Código 4 ilustra a como desabilitar as funcionalidades utilizadas na etapa de renderização do
VBO (Código 3) e deve sempre ser seguida para evitar problemas desnecessários em aplicações
maiores ou mais complexas.
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

Código 4. Boa prática de programação em OpenGL.


A modificação de um VBO pode ser dada utilizando a função glBufferSubData(), caso em que
parte dos dados são reenviados para o buffer (note que é preciso ter cópias válidas dos dados no
cliente e no servidor), ou utilizando a função glMapBuffer(), a qual mapeia o buffer para o espaço
de memória do cliente. O uso da segunda opção é preferível por não exigir cópia dos dados no
cliente e servidor, mas há situações que o uso da primeira opção pode trazer maior desempenho [4].
No Código 5 é exibido o uso da função glMapBuffer(), a qual retorna um ponteiro que permite o
programador modificar os dados do VBO. São parâmetros o tipo de buffer e de acesso, nesta ordem.
glBindBuffer(GL_ARRAY_BUFFER, id_vbuffer);
Vertex* ptr = (Vextex*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

if (ptr)
{
updateMyVBO(ptr, ...);
glUnmapBuffer(GL_ARRAY_BUFFER);
}

Código 5. Exemplo VBO. Mapeamendo do VBO para o espaço de memória do cliente.

Vertex Array Objects (VAOs) são objetos OpenGL que encapsulam todos os estados necessários
para a definição de dados de vértices. Eles definem o formato desses dados bem como seus arrays
fonte [5], ou seja, descrevem como estão armazenados os atributos de vértices.
Note que o VAO não contém os dados dos vértices, estes estão armazenados em Vertex Buffer
Objets, mas somente referencia aqueles já existentes no buffer.
Para usar um VAO ele deve ser criado e definido como ativo (semelhante ao VBO). Feito isso, os
atributos para os VBOs devem ser habilitados e passados para que o VAO saiba onde estão os

1
dados e como eles estão armazenados. De maneira simplificada, um VAO registra os estados (flags)
necessárias para a “configuração do VBO”.
No Código 6 é exibido o uso de um VAO em conjunto com o exemplo de VBO anterior. Note que
toda a etapa de renderização do VBO (Código 3) é registrada na etapa de configuração do VAO,
com exceção da chamada a função de desenho (glDrawElements()). No Código 7 a etapa de
renderização é realizada. Compare com a mesma etapa do VBO.
// Bind do VAO
GLuint id_vao;
glGenVertexArrays(1, &id_vao);
glBindVertexArray(id_vao); // início do registro dos estados pelo VAO

// Bind dos VBOs a serem utilizados


glBindBuffer(GL_ARRAY_BUFFER, id_vbuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id_ibuffer);

// Ativação e definição dos atributos dos vértices


glEnableVertexAttribArray(0); // TexCoordPointer
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,
(void*)(sizeof(GLfloat) * 6));

glEnableVertexAttribArray(1); // NormalPointer
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,
(void*)(sizeof(GLfloat) * 3));

glEnableVertexAttribArray(2); // VertexPointer
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,
(void*)(0));

// Chamada da função de desenho NÃO DEVE SER CHAMADA


glDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_INT, (void*)0);

glBindVertexArray(0); // fim do registro dos estados pelo VAO

Código 6. Exemplo VAO. Etapa de configuração.


glBindVertexArray(id_vao);
glDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_INT, (void*)0);
glBindVertexArray(0);

Código 7. Exemplo VAO. Etapa de renderização.


Um VAO possui um determinado número de atributos que devem ser associados e habilitados para
que possam ser usados pelo OpenGL. Alguns exemplos de atributos são: dados de vértices, índices,
vetores normais e cores. A tentativa de renderização de um VAO “vazio” irá resultar em erro.
Para consultar os protótipos de função do OpenGL veja [6].

5. Exibição de FPS – Frames Per Second

Passos:
 Calcular o FPS
 Guardar o estado de cada atributos ativo com o comando glPushAttrib(GLbitfield
mask). Este comando aceita diversas constantes como argumentos. Os dois mais genéricos são
GL_ALL_ATTRIB_BITS e GL_ENABLE_BIT. Este comando tem o mesmo efeito do
glPushMatrix(), só que este opera sobre pilha de atributos.
 Desabilitar todos os atributos com glDisable(), para não interferirem na impressão do valor do
FPS, que é tratado como uma string:
o Cor material (GL_COLOR_MATERIAL)
o Coordenadas de textura (GL_TEXTURE_2D)
o Nevoeiro (GL_FOG);

1
o Iluminação (GL_LIGHTING)
o Z-buffer (GL_DEPTH_TEST)
o Composição (GL_BLEND)
 Setar a matriz de projeção como ortográfica
 Habilitar a matriz de Modelview,
 Desenhar os caracteres com glRasterPos2f(), glutBitmapCharacter() e
glutBitmapWidth();
 Restaurar os atributos com glPopAttrib();

Para mais detalhes, veja o demo FPS.

6. Textura

Para dar as superfícies características mais realistas, o OpenGL oferece além de recursos de iluminação,
recurso de texturização. Faz-se uso da técnica de texture mapping para aplicar a textura sobre a superfície
dos polígonos. A idéia é mapear cada face do modelo em coordenadas de textura, geralmente representada
por uma imagem, como na seguinte figura. A definição de coordenadas de textura, para cada triângulo do
modelo, que neste exemplo é um modelo de personagem animado 3D (Formato MD2), é feita pelo
modelador (artista gráfico).

A textura faz parte do estágio de rasterização do pipeline gráfico do OpenGL. A rasterização é a conversão
de dados geométricos (polígonos) e pixels (textura) em fragmentos. Cada fragmento corresponde a um
pixel no framebuffer.

O mapeamento da textura para os fragmentos nem sempre preserva as proporções. Dependendo da


geometria do objeto, podem haver distorções da textura ao ser aplicada. Também pode acontecer de um
mesmo texel (texture element) ser aplicado a mais de um fragmento como um fragmento mapear vários
texels. Para isso operações de filtragem são aplicadas.

Uma textura é um mapa retangular de dados (geralmente cores como em uma imagem). O OpenGL oferece
algumas maneiras de se especificar textura: pela especificação de coordenadas para cada vértice do modelo
ou pela geração automática de coordenadas de textura.

Como o assunto de textura é muito amplo, nesta seção são apresentados alguns comandos com alguns
parâmetros possíveis. Para maiores detalhes, consulte [1].

Restrições da textura: A textura deve ter preferencialmente dimensões em potência de 2, como por

1
exemplo: 64x128, 512x512, 128x64, etc.
 A função glTexImage2D(), como será visto, não funciona se a textura não seguir esta restrição.
 Outra função equivalente, a gluBuild2DMipmaps(), por sua vez, opera com textura de qualquer
dimensão.
 Se a textura não for potência de 2, deve-se garantir que a posição de memória do pixel que
representa o início de uma linha seja múltipla de 4 (mesmo formato adotado pelo padrão de imagem
BMP para imagens que não são potência de 2). Por exemplo, uma imagem em RGB de 3x4 pixels,
vai ocupar 12 bytes por linha, ao invés de 9, como esperado. Ver demo de textura.

Para se aplicar textura deve-se inicialmente habilitar a textura, criar a textura, definir parâmetros e aplicar a
textura. Para habilitar o recurso de textura utilizam-se os comandos

glEnable(GL_TEXTURE);
glEnable(GL_TEXTURE_2D);

Cada textura esta associada a um ID que é usado pelo OpenGL para definição da textura ativa. A cada
momento existe uma única textura ativa, que é aplicada sobre todos os objetos que possuem coordenadas de
textura.

Para criar um ID utiliza-se o comando glGenTextures(1,&id) que indica que serão criados 1 índice
de textura, e que este índice será armazenado na variável intera id.

Par se criar, habilitar e desabilitar a textura, utiliza-se o comando

void glBindTexture(GLenum target, GLuint textureName);

O parâmetro textureName corresponde ao ID da textura. O parâmetro target pode assumir


GL_TEXTURE_1D ou GL_TEXTURE_2D.
 Quando este comando for chamado com um ID de textura inteiro diferente de zero pela primeira vez,
um novo objeto de textura é criado com o nome passado.
 Se a textura já havia sido criada, ela é então habilitada.
 Se o parâmetro textureName for zero, desabilita o uso de textura do OpenGL.

Após a criação da textura (nome), deve-se configurar parâmetros de aplicação da textura sobre um
fragmento.

void glTexParameter{if}(GLenum target, GLenum pname, TYPE param);

 target: pode assumir GL_TEXTURE_1D ou GL_TEXTURE_2D.


 pname e param: formam uma dupla. Para cada valor de pname, existem possíveis valores para
param.

Pname param
GL_TEXTURE_WRAP_S GL_CLAMP, GL_REPEAT
GL_TEXTURE_WRAP_T GL_CLAMP, GL_REPEAT
GL_TEXTURE_MAG_FILTER GL_NEAREST, GL_LINEAR
GL_TEXTURE_MIN_FILTER GL_NEAREST, GL_LINEAR,
GL_NEAREST_MIPMAP_NEAREST,
GL_NEAREST_MIPMAP_LINEAR,
GL_LINEAR_MIPMAP_NEAREST,
GL_LINEAR_MIPMAP_LINEAR

Os parâmetros GL_TEXTURE_WRAP_S/T representam como a textura será aplicada sobre objeto, caso o
objeto for maior que a textura. GL_CLAMP usa a cor da borda e GL_REPEAT replica a textura.

1
GL_NEAREST indica que deve ser usado o texel com coordenadas mais próximas do centro do pixel.
GL_LINEAR faz uma média dos 2x2 texels vizinhos ao centro do pixel.

Os parâmetros GL_TEXTURE_MAG/MIN_FILTER indicam como a textura será filtrada. Isso ocorre porque
após a projeção, pode ocorrer de parte de um texel aplicado sobre um pixel (magnification) ou de vários
texels serem aplicados sobre um único pixel (minification). O argumento passado vai indicar como será
realizada a média ou interpolação dos valores.

A última etapa consiste na definição dos dados da textura. Para isso existem dois comandos:
glTexImage2D() e gluBuild2DMipmaps(). Mipmapping é uma técnica usada para criar várias
copias da textura em diferentes resoluções (1/4+1/16+.... ≈ 1/3, ou seja, aumento de 33% do uso de
memória), facilitando assim a escolha da melhor textura a ser aplicada sobre o objeto em função da sua área
de projeção (http://en.wikipedia.org/wiki/Mipmap):

void glTexImage2D(GLenum target,


GLint level,
GLint internalFormat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const GLvoid *pixels);

 level: comumente deve ser 0. Ver função gluBuild2DMipmaps() para geração automática de
textura em várias resoluções.
 internalFormat: indica como estão armazenados os dados no array pixels (imagem). Pode
assumir vários valores. O parâmetro mais comum é GL_RGB, para indicar que o array é um vetor de
coordenadas RGB.
 width: largura da imagem em pixels
 height: altura da imagem em pixels
 border: indica a largura da borda. Valor 0 significa sem borda.
 format: Formato dos dados. Use GL_RGB.
 type: Geralmente deve ser GL_UNSIGNED_BYTE para indicar que cada componente RGB utiliza
1 byte não sinalizado.
 pixels: contém os dados da imgem-textura. Ver restrições de dimensão de textura.

int gluBuild2DMipmaps(GLenum target,


GLint components,
GLint width,
GLint height,
GLenum format,
GLenum type,
void *data);

Esta função cria uma série de mipmaps e chama a função glTexImage*D() para carregar as imagens. Por
isso é uma função glu. Vários argumentos são os mesmos da função glTexImage2D().

Exemplo de Mipmap: textura original e cópias em escaladas de 2 (separada em RGB).

1
Para se especificar as coordenadas de textura para cada vértice utiliza-se o comando glTexCoord*().

void glTexCoord{1234}{sifd}(TYPE coords);


void glTexCoord{1234}{sifd}v(TYPE *coords);

Este comando seta as coordenadas de textura corrente (s, t, r, q). Vértices definidos na seqüência com
glVertex*() fazem uso do valor corrente de textura. Geralmente utiliza-se o comando
glTexCoord2f() para definir as coordenadas s e t. Coordenadas de textura são multiplicadas pela matriz
de textura antes que o mapeamento ocorra. Por default esta matriz é a identidade. A matriz de textura pode
conter vários tipos de transformações como rotações, translações, perspectivas, etc. Para aplicar
transformações nesta matriz deve-se setar a matriz de textura como corrente com o comando
glMatrixMode(GL_TEXTURE). Com isso pode-se simular a rotação de uma textura sobre uma
superfície.

(0,1) (1,1)

(0,0) (1,0)

Textura (0,3) (5,3)

(0,0) (5,0)

Mapeamento da textura. Adaptada de [3]

Um programa que usa textura tem a seguinte estrutura. Para maiores detalhes, veja o demo de textura.

int tex_id;
void init()
{
glGenTextures( 1, &tex_id );
glBindTexture( GL_TEXTURE_2D, tex_id );

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);


...

1
glTexImage2D(GL_TEXTURE_2D,...);

glEnable( GL_TEXTURE );
glEnable( GL_TEXTURE_2D );
}

void render()
{
glBindTexture( GL_TEXTURE_2D, tex_id );

glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex3f(v[0].x, v[0].y, v[0].z);
glTexCoord2f(3, 0);
glVertex3f(v[1].x, v[1].y, v[1].z);
glTexCoord2f(3, 3);
glVertex3f(v[2].x, v[2].y, v[2].z);
glTexCoord2f(0, 3);
glVertex3f(v[3].x, v[3].y, v[3].z);
glEnd();
}

Exemplos de geração de textura:

GLuint textureID[MAX_TEXTURES];
glGenTextures( 1, &textureID[0] );
glBindTexture( GL_TEXTURE_2D, textureID[0] );

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D,
GL_RGB,
img1->getWidth(),
img1->getHeight(),
GL_RGB,
GL_UNSIGNED_BYTE,
data);
glGenerateMipmap(GL_TEXTURE_2D);

glGenTextures( 1, &textureID[1] );
glBindTexture( GL_TEXTURE_2D, textureID[1] );

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGB,
img1->getWidth(),
img1->getHeight(),
0,
GL_RGB,
GL_UNSIGNED_BYTE,
data);

1
Geração automática de coordenadas de textura

O OpenGL permite que as coordenadas de textura sejam criadas automaticamente. Isso é muito útil em
diversas aplicações como por exemplo na geração de terrenos onde os vértices formam um reticulado.
Considere o caso de um malha poligonal com dimensão 10x10, com origem em (0,0). Independente do
número de faces que vai ter, deseja-se aplicar uma única textura sobre toda a malha, sem replicação. Neste
caso define-se uma escala 1/10 e especifica-se parâmetros de projeção da textura nos eixos x e z, como no
seguinte exemplo.

float escala = 1.0 / 10;


(10,10) float p1[4] = { escala, 0, 0, 0};
float p2[4] = { 0, 0, escala, 0};
glTexGenfv(GL_S, GL_OBJECT_PLANE, p1);
glTexGenfv(GL_T, GL_OBJECT_PLANE, p2);

Como o quadrado tem dimensão de 10, com coordenada inicial em (0,0), se escala valesse 1/20, seria
aplicada apenas ¼ da textura. Se escala = 1/5, seriam usadas 4 cópias da textura (com filtro GL_REPEAT)
para preencher o quadrado.

glGenTextures( 1, &id );
glBindTexture( GL_TEXTURE_2D, id);

float escala = 1.0 / 10;


float p1[4] = { escala, 0, 0, 0 };
float p2[4] = { 0, escala, 0, 0 };
glTexGenfv(GL_S, GL_OBJECT_PLANE, p1);
glTexGenfv(GL_T, GL_OBJECT_PLANE, p2);

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);


glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D,
GL_RGB,
img1->getWidth(),
img1->getHeight(),
GL_RGB,
GL_UNSIGNED_BYTE,
data);

Deve-se habilitar a geração automática de textura e após o uso desabilitar. Maiores detalhes podem ser vistos
em [1].

glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);

7. Pipeline de Renderização

Uma corrente não é mais forte que o seu elo mais fraco. – Anônimo

O pipeline gráfico de uma aplicação gráfica que faz uso do OpenGL (tradicional sem shanding) é composto
por 3 etapas: aplicação, geometria e rasterização, como mostrado na seguinte figura.

1
Os processos do nível de aplicação executam na CPU e os dois demais na GPU. Os 3 estágios executam
simultaneamente. Antes do surgimento de placas aceleradoras, todo este trabalho era executado
exclusivamente pela CPU. Hoje em dia há uma separação das tarefas, o que permite aliviar o uso da CPU
para outras tarefas como:
 processamento da IA (visto que ainda não existem placas destinadas a este propósito),
 interação com o usuário,
 simulação física (por pouco tempo, visto que estão surgindo placas dedicadas ou incorporadas à
placa de vídeo destinadas a este propósito),
 Algoritmos de remoção de objetos não visíveis (culling), etc.

Aplicação Geometria Rasterização

Entrada do usuário Transformações do Conversão vetorial


modelo e de câmera matricial - rasterização

Cinemática dos objetos


e teste de colisão Iluminação Operações de
fragmentos: textura, fog,
blending, etc.
Processamento da IA Projeção
Frame buffer de cor e
profundidade
Descarte de objetos fora Recorte
do campo de visão

Mapeamento para tela


Extração da geometria a
ser visualizada

Etapas do processo de renderização. Adaptada de [3].

Como mostrado na seguinte figura, o pipeline de geometria opera sobre vértices, enquanto o pipeline de
rasterização opera sobre pixels (fragmentos).

Operações por vértice

Transformação Mapeamento
Aplicação Modelagem e Iluminação Projeção Clipping de Tela
Visualização

Mapeamento Flog/Blend Frame


Rasterização de Textura Combinação Teste α, s, z Buffer

Operações por pixel


Aplicação
Geometria
Rasterização Estágios funcionais do pipeline. Extraído de [3]

O Framebuffer é composto por:


 Color buffers: guarda a cor dos pixels (fragmentos). Pode existir o left- e right-buffer (para visão
estéreo), e cada um destes pode ser single ou double (front- e back-buffer).

1
 Depth buffer: guarda a profundidade dos fragmentos em relação à posição da câmera. Podem haver
vários fragmentos que projetam em um mesmo pixel. Somente o mais próximo será o visível (ver
algoritmo z-buffer).
 Accumulation Buffer: semelhante ao buffer de cor, com a diferença que este pode ser usado para
acumular uma série de imagens. Isso é útil em operações de super-amostragem, antialiasing e motion
blur.
 Stencil Buffer: buffer usado para restringir o desenho a certas partes da tela. Pode ser usado para
desenhar o interior do carro (painel, direção, etc). Somente a área na região do pára-brisa é que pode
ser atualizada com informações da cena.

Para cada buffer, existem comandos de limpeza, como glClearColor(), glClearDepth(),


glClearAccum() e glClearStencil(), ou glClear(GL_COLOR_BUFFER_BIT | ...). Para
maiores detalhes de uso destes buffers, consulte [1].

O buffer de profundidade é alterado pelo algoritmo z-buffer. Este buffer tem o mesmo tamanho e forma que
o color buffer, onde cada pixel armazena a distância da câmera até a primitiva corrente mais próxima.
 Quanto uma primitiva estiver sendo renderizada para um certo pixel (amostragem), compara-se a
distância deste pixel em relação à câmera.
 Se a distância for menor que a distância já armazenada, significa que a primitiva corrente, para o
dado pixel, está mais próxima que a última primitiva avaliada. Neste caso deve-se atualizar o buffer
de profundidade, juntamente com o buffer de cor com informações da nova primitiva.
 Se a distância for maior, mantêm-se os buffers inalterados. O custo computacional do z-buffer é
O(n), onde n é número de primitivas sendo renderizadas.

7.1. Otimização do Pipeline

Como os 3 estágios conceituais (aplicação, geometria e rasterização) executam simultaneamente, o que for
mais lento (gargalo) vai determinar a velocidade de renderização (throughput).

Observações:
 Antes de tudo deve-se localizar o gargalo;
 Deve-se procurar otimizar o estágio mais lento para aumentar o desempenho do sistema;
 Se o estágio da aplicação for o mais lento, não se terá nenhum ganho otimizando o estágio de
geometria, por exemplo;
 Ao se otimizar o estágio mais lento, pode ocorrer de outro estágio se tornar o novo gargalo;
 Caso o gargalo não puder ser otimizado, pode-se melhor aproveitar os demais estágios para se obter
maior realismo.

A estratégia mais simples para se detectar o gargalo é realizar uma série de testes, onde cada teste afeta
somente um único estágio. Se o tempo total for afetado, ou seja, reduzir o frame rate, então o gargalo foi
encontrado. Para isso pode-se aumentar ou reduzir o processamento de um estágio.

 Teste no estágio da Aplicação: A estratégia mais simples neste caso é reduzir o processamento dos
outros estágios, pela simples substituição dos comandos glVertex*() por glColor*(). Neste caso os
estágios de geometria e rasterização não vão ter o que processar. Se o desempenho não aumentar, o
gargalo está na aplicação. Outra estratégia é incluir um laço de repetição que realiza N cálculos
matemáticos. Se o desempenho geral piorar, o gargalo era a aplicação.

 Teste no estágio de geometria: É o estágio mais difícil de se testar pois se a carga de trabalho neste
estágio for alterada, será alterada também em outros estágios. Existem duas estratégias de teste:
aumentar ou reduzir o número de fontes luminosas, o que não vai afetar os demais estágios, ou
verificar se o gargalo está no estágio da aplicação ou rasterização.

 Teste do estágio de Rasterização: É o estágio mais fácil de ser testado. Isso pode ser feito
simplesmente aumentando-se ou reduzindo-se o tamanho da janela onde as imagens estão sendo
renderizadas. Isso não interfere nem na aplicação e nem na geometria. Quanto maior for a janela,

1
maior será a quantidade de pixels a serem desenhados. Outro modo de testar é desabilitando
operações de textura, fog, blending.

Existem várias estratégias para se otimizar os estágios do pipeline. Algumas abordagens comprometem a
qualidade em troca de desempenho e outras são apenas melhores estratégias de implementação:

 Otimização do Estágio da Aplicação: otimizações para tornar o código e acesso à memória mais
rápidos. Isso pode ser obtido ativando-se flags de compilação e conhecendo detalhes das linguagens
de programação. Algumas dicas gerais de código/memória são listadas. Para maiores detalhes
consulte [2]:
o Evite divisões;
o Evite chamar funções com pouco código. Use funções inline;
o Reduzir uso de funções trigonométricas;
o Usar float ao invés de double;
o Usar const sempre que possível, para o compilador melhor otimizar o código;
o Evitar uso excessivo de funções malloc/free.

 Otimização do Estágio de geometria: Pode-se aplicar otimizações apenas sobre transformações e


iluminação.
o Em relação a transformações, se o objeto precisar ser movido para uma posição estática, ao
invés de aplicar a transformação a cada renderização, pode-se aplicar as transformações em
um pré-processamento antes da renderização iniciar;
o Outra estratégia é reduzir ao máximo o número de vértices, com o uso de vertex array, ou
primitivas do tipo STRIP ou FAN;
o Outra estratégia muito eficiente é aplicar culling, no estágio da aplicação;
o Uso de LOD;
o Reduzir o número de fontes luminosas;
o Verificar quais partes da cena necessitam iluminação;
o Iluminação flat é mais rápida que Gourdaud;
o Avaliar se é necessário iluminar os dois lados das superfícies;
o Uso de Light maps.

 Otimização do Estágio de Rasterização:


o Habilitar backface culling para objetos fechados. Isso reduz o número de triângulos para
serem rasterizados em aproximadamente 50%;
o Desenhar objetos mais próximos do observador primeiro. Isso evita gravações sucessivas no
z-buffer;
o Desabilitar recursos como fog, blending ou uso de filtros de textura mais baratos, como o
bilinear ao invés de mipmapping.

 Otimizações gerais:
o Reduzir a geometria da cena;
o Reduza a precisão em vértices, normais, cores e coordeandas de textura. Isso reduz uso de
memória e uso de barramento;
o Desabilitar recursos não utilizados;
o Minimizar mudança de estados;
o Evitar swap de texturas;
o Use display lists para objetos estáticos.

7.2. Avaliando o Desempenho do Pipeline

A velocidade de renderização depende do estágio mais lento, e é medida em FPS (frames por segundo). Para
este cálculo, deve-se desabilitar o recurso de double-buffer, visto que a troca de buffer neste caso ocorre em
sincronismo com o refresh do monitor.

2
Com o double buffer ativado, enquanto o front buffer está sendo exibido, o back buffer está sendo gerado.
Somente ocorre a troca de buffers (glutSwapBuffers) quando o monitor acabar de desenhar o frame
corrente.

Considerando-se o monitor com uma taxa de atualização de 72 Hz, significa que a cada t = 13. 8
milisegundos a tela é atualizada. Com o recurso de double-buffer ativado, a taxa de atualização deve ser
múltiplo deste valor. Se o tempo de renderização consome 20 ms (50 Hz) em single-buffer, a taxa cairá para
2*13. 8 = 36 Hz com o double-buffer ativo. Neste caso, o pipeline gráfico fica inativo por 13. 8*2 – 20 =
7.78 ms para cada frame [2].

Monitor 13.8 ms 13.8 ms 13.8 ms 13.8 ms 13.8 ms

Pipeline 20 ms 20 ms 20 ms
7.78 ms 7.78 ms

Uma forma de contornar os problemas do uso do double-buffer é o uso de tiple-buffer. Neste caso existem
dois back buffers e um front buffer. Neste caso, enquanto o monitor está exibindo o font buffer, podem estar
sendo gerados 1 ou 2 back buffers. No caso do uso de double-buffer, o o refresh do monitor for de 60 Hz e o
tempo de geração das cenas for menor que 1/60 s, então mantém-se uma taxa de 60 fps. Porem se o tempo de
geração for pouco maior que 1/60 s, a taxa de atualização cai para 30 fps. Com o uso de triple-buffer, a taxa
se mantém próxima de 60 fps. O inconveniente do uso de triple-buffer é que o tempo de resposta para ações
do usuário é um pouco maior, especialmente se o fps for baixo.

8. Tópicos adicionais

Vários tópicos suportados pelo OpenGL não foram tratados neste material. Para detalhes sobre assuntos
como blending, fog, antialiasing, bitmaps, quádricas, NURBS, picking, e extensões OpenGL, consulte [1].
Para técnicas de renderização como sombras, efeitos especiais e técnicas de otimização, consulte [2].

9. Referências Bibliográficas

[1] "GLEW," 22 07 2013. [Online]. Available: http://glew.sourceforge.net/.


[2] "opengl - How many VBOs do I use?," 28 12 2011. [Online]. Available:
http://stackoverflow.com/questions/8661629/how-many-vbos-do-i-use.
[3] "Vertex Specification Best Practices," 22 10 2012. [Online]. Available:
http://www.opengl.org/wiki/Vertex_Specification_Best_Practices.
[4] "Modifying only a specific element type of VBO buffer data?," 13 07 2011. [Online]. Available:
http://stackoverflow.com/questions/6681871/modifying-only-a-specific-element-type-of-vbo-buffer-
data.
[5] "Vertex Specification," 8 8 2012. [Online]. Available: http://www.opengl.org/wiki/Vertex_Specification.
[6] "OpenGL 4 Reference Pages," 19 09 2012. [Online]. Available:
http://www.opengl.org/sdk/docs/man/xhtml/.
[7] Woo, M.; Neider, J.; Davis, T.; Shreiner, D. OpenGL: Programming Guide. 5. ed. Massachusetts :
Addison-Wesley, 2005.
[8] Moller, T.; Haines, E.; Akenine, T. Real-Time Rendering. 2 ed. AK Peters, 2002.
[9] Celes, W. Notas de Aula. PUC-Rio, 2006.
[10] OpenGL.org, Vertex Specification. Disponível em:
http://www.opengl.org/wiki/Vertex_Specification
[11] oZone3D.Net, OpenGL Tutorials. Disponível em:
http://www.ozone3d.net/tutorials/opengl_vbo_p2.php#part_26

Você também pode gostar