11.07.2015 Views

OpenGL Avançado

OpenGL Avançado

OpenGL Avançado

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Universidade Federal de Santa MariaDepartamento de Eletrônica e ComputaçãoProf. Cesar Tadeu PozzerDisciplina: Computação Gráfica Avançadapozzer@inf.ufsm.br05/10/2013<strong>OpenGL</strong> – Conceitos AvançadosNeste módulo serão vistos conceitos mais avançados da programação em <strong>OpenGL</strong>, que tratam: Otimizações: Vertex Array, Display List, VAO, VBO e otimizações de pipeline; Realismo: Iluminação, material e textura; Pipeline <strong>OpenGL</strong> Tudo o que está vem vermelho faz parte da antiga API do <strong>OpenGL</strong>, mas ainda com suportegarantido. http://pyopengl.sourceforge.net/documentation/deprecations.html1. Arquitetura Cliente-ServidorO <strong>OpenGL</strong> 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 <strong>OpenGL</strong>enquanto que o servidor é responsável pelo processamento (render) destes dados e exibição na tela. Caso nãoexistirem computadores em rede, o mesmo computador exerce o papel de cliente e servidor (maior parte doscasos). Esse conceito é importante para melhor compreensão de conceitos avançados do <strong>OpenGL</strong>. Veja oscomandos glFlush() e glFinish() para uso em uma aplicação distribuída que faça uso de cliente eservidor em máquinas diferentes conectadas por uma rede.Clientserver2. Vertex ArrayUsado para: Reduzir o número de chamada de funções, que podem causar redução de desempenho da aplicaçãoo 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 dechamadas. 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 mesmocubo poderia ser desenhado com apenas 8 vértices.Com o uso de vertex array (<strong>OpenGL</strong> 1.1), os 20 vértices do polígono poderiam ser colocados em um vetor epassados em uma única chamada para o <strong>OpenGL</strong>. O mesmo poderia ocorrer com os vetores normais. Emrelaçã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 oservidor.1


Para ativar os buffers de dados, deve-se utilizar o comando glEnableClientState(), que recebe comoparâ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_ARRAYvoid 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 comandoglVertexPointer() é usado para indicar o vetor de vértices. O comando glNormalPointer() paraespecificaçã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 cadacoordenada do array. size é o número de coordenadas por vértice (2, 3, ou 4). Não necessária para especificação denormais (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 entredois vértices.Para enviar os dados para o processamento (enviar para o servidor na arquitetura cliente-servidor do<strong>OpenGL</strong>), 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âmetrossão: count: É o úmero de elementos do vetor indices: Vetor de índices dos elementos2


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,1, 2, 6, 5,0, 1, 5, 4,0, 3, 2, 1,0, 4, 7, 3,2, 3, 7, 6};glEnableClientState (GL_VERTEX_ARRAY);glEnableClientState (GL_COLOR_ARRAY);4 5glVertexPointer(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);73 2061Face 0 Face 1 Face N4 5 6 7 1 2 6 5 0 1 5 … 6V 0 V 1 V 2 V 3 V 4 V 5 V 6 V 7C 0 C 1 C 2 C 3 C 4 C 5 C 6 C 7Para a especificação de um único vértice por vez de um vertex array pode-se utilizar o comandoglArrayElement(GLint). O seguinte trecho de código tem o mesmo efeito que o comandoglDrawElements() 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 maiorcomo 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 asnormais (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 oscomandos convencionais como glVertex*() e glNormal*(). Para o caso do cubo, seriamnecessá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, comoterrenos. Para objetos volumétricos fechados como cubos, não faz sentido ter-se normaiscompartilhadas entre as 3 faces adjacentes, o que exige a replicação de vértices. O mesmo se aplicapara cilindros.3. Display ListÉ um grupo de comandos <strong>OpenGL</strong> 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 <strong>OpenGL</strong>podem ser armazenados em um display list. Não podem ser utilizados especialmente comandos relacionadoscom 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-seguardar os comandos que definem o objeto e então exibir a display list toda vez que o objeto precisar serdesenhado. No caso de um carro que possui 4 rodas, pode-se gerar um display list com a geometria de umaroda e invocar a renderização, uma vez para cada roda, lembrando de aplicar as devidas transformações paraposicionar 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 paraagilizar o processamento.Para criar um display list deve-se utilizar os seguintes comandos: glGenLists(), glNewList() eglEndList() 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 oparâmetro range for igual a 1, gera um único índice que é o valor retornado pela função. O índiceretornado é usado pelas funções glNewList() e glCallList(). O retorno zero significa erro.4


a cada vez que o array for referenciado, diminuindo o desempenho da aplicação como umtodo.• A Display List é uma função do servidor, logo os dados não precisam ser reenviados a cadarenderizaçã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 derecriá-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 arenderização, ou que tudo seja armazenado em um buffer apenas. Deve-se notar que a GPU nãosabe como estes dados estão organizados, para isso há a necessidade de uma estrutura que descrevacomo interpretá-los para a correta renderização da geometria.Abaixo iremos construir um exemplo utilizando VBO. Note que o código está adequado ao<strong>OpenGL</strong> 3.2+.A partir do <strong>OpenGL</strong> 3.2 foi inserido o conceito de profile na criação de contextos, sendo eles o coree o compatibility. Enquanto que o primeiro expõem as últimas e mais modernas funcionalidades daAPI, tornando a utilização de shaders uma exigência, o segundo inclui as funções relativas aopipeline 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 <strong>OpenGL</strong>, destamaneira, depende diretamente do sistema operacional ou APIs utilizadas para este fim.O código abaixo é utilizado na API GLFW para que o contexto <strong>OpenGL</strong> suporte ao mínimo aversão 3.2 da API e exclusivamente as funcionalidades definidas no core (a criação de um contextonã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 <strong>OpenGL</strong> é facilitada pelo uso da APIGLEW. 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 programadorarmazena, cria ou importa os seus dados, uma vez que diferentes API’s para importação de modelos3D 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 representadopor um único VBO. Se o VBO tiver poucos dados o ganho de performance em relação a outras técnicaspode 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 umoverhead desnecessário.Se os dados do VBO forem muito grandes é possível que não possam ser movidospara a VRAM (Video RAM)A formatação dos dados (ordem de armazenamento) também pode influenciar no desempenho final,a wiki oficial do <strong>OpenGL</strong> 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 32bytes, 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ênciade 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 ordemde 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á coordenara 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 seremcriados, neste exemplo um para dados e outro para índices, e posteriormente utilizadas parareferenciá-los. O <strong>OpenGL</strong> não permite a mistura de dados de vértices aos de índices, por isso anecessidade 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 armazenamentodo id de cada buffer.2. Vinculação do buffer criado ao <strong>OpenGL</strong> 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, constGLvoid * 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 embytes 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 serqualquer 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 vezGrupo B (“os dados serão movidos do(a):”) DRAW: aplicação para <strong>OpenGL</strong> READ: <strong>OpenGL</strong> para aplicação COPY: <strong>OpenGL</strong> para <strong>OpenGL</strong> (DRAW + READ)8


A fim de descobrir o melhor local para armazenar os dados dos VBOs criados a implementação do<strong>OpenGL</strong> (driver de vídeo) utilizará o parâmetro usage da função glBufferData() como “dica” e, porisso, 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 dedados utilizados.// Criação do VBO de dados de vérticesglGenBuffers(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 índicesglGenBuffers(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 índicesCó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 <strong>OpenGL</strong> com glBindBuffer().2. Ativação e definição dos atributos dos vérticesvoid 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 umponteiro) para o primeiro atributo do tipo definido no array3. 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 definidocom as funções glEnableVertexAttribArray() e glVertexAttribPointer(), respectivamente. Atravésda função glVertexAttribPointer() a formatação dos dados é enviada ao <strong>OpenGL</strong>. Note que noCódigo 3 existem três chamadas a essa função, cada uma definindo a estrutura de dados utilizadaneste exemplo. Observe estas funções e compare-as com a Figura 1.// Bind dos VBOs a serem utilizadosglBindBuffer(GL_ARRAY_BUFFER, id_vbuffer);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id_ibuffer);// Ativação e definição dos atributos dos vérticesglEnableVertexAttribArray(0); // texcoord_uvglVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,(void*)(sizeof(GLfloat) * 6));glEnableVertexAttribArray(1); // normal_xyz9


glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,(void*)(sizeof(GLfloat) * 3));glEnableVertexAttribArray(2); // vertex_xyzglVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,(void*)(0));// Chamada da função de desenhoglDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_INT, (void*)0);Código 3. Exemplo VBO. Etapa de renderização. Informações referentes à Figura 1 estãosublinhadas.Note que o índice utilizado para a habilitação e definição de um determinado atributo através dasfunçõ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 osvértices. (Quando utilizado shader os índices devem ser correspondentes).Uma boa prática de programação a ser seguida é sempre retornar ao estado do <strong>OpenGL</strong> antes daativação de atributos, buffers, flags ou outros, procurando desabilitar recursos utilizadospontualmente, 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 doVBO (Código 3) e deve sempre ser seguida para evitar problemas desnecessários em aplicaçõesmaiores 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 <strong>OpenGL</strong>.A modificação de um VBO pode ser dada utilizando a função glBufferSubData(), caso em queparte dos dados são reenviados para o buffer (note que é preciso ter cópias válidas dos dados nocliente e no servidor), ou utilizando a função glMapBuffer(), a qual mapeia o buffer para o espaçode memória do cliente. O uso da segunda opção é preferível por não exigir cópia dos dados nocliente 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 oprogramador 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 <strong>OpenGL</strong> que encapsulam todos os estados necessáriospara a definição de dados de vértices. Eles definem o formato desses dados bem como seus arraysfonte [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 BufferObjets, 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, osatributos para os VBOs devem ser habilitados e passados para que o VAO saiba onde estão os1


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 quetoda 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 derenderização é realizada. Compare com a mesma etapa do VBO.// Bind do VAOGLuint id_vao;glGenVertexArrays(1, &id_vao);glBindVertexArray(id_vao); // início do registro dos estados pelo VAO// Bind dos VBOs a serem utilizadosglBindBuffer(GL_ARRAY_BUFFER, id_vbuffer);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id_ibuffer);// Ativação e definição dos atributos dos vérticesglEnableVertexAttribArray(0); // TexCoordPointerglVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,(void*)(sizeof(GLfloat) * 6));glEnableVertexAttribArray(1); // NormalPointerglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,(void*)(sizeof(GLfloat) * 3));glEnableVertexAttribArray(2); // VertexPointerglVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8,(void*)(0));// Chamada da função de desenho NÃO DEVE SER CHAMADAglDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_INT, (void*)0);glBindVertexArray(0); // fim do registro dos estados pelo VAOCó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 paraque possam ser usados pelo <strong>OpenGL</strong>. 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 <strong>OpenGL</strong> veja [6].5. Exibição de FPS – Frames Per SecondPassos: Calcular o FPS Guardar o estado de cada atributos ativo com o comando glPushAttrib(GLbitfieldmask). Este comando aceita diversas constantes como argumentos. Os dois mais genéricos sãoGL_ALL_ATTRIB_BITS e GL_ENABLE_BIT. Este comando tem o mesmo efeito doglPushMatrix(), só que este opera sobre pilha de atributos. Desabilitar todos os atributos com glDisable(), para não interferirem na impressão do valor doFPS, 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áficaHabilitar a matriz de Modelview,Desenhar os caracteres com glRasterPos2f(), glutBitmapCharacter() eglutBitmapWidth();Restaurar os atributos com glPopAttrib();Para mais detalhes, veja o demo FPS.6. TexturaPara dar as superfícies características mais realistas, o <strong>OpenGL</strong> 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íciedos polígonos. A idéia é mapear cada face do modelo em coordenadas de textura, geralmente representadapor uma imagem, como na seguinte figura. A definição de coordenadas de textura, para cada triângulo domodelo, que neste exemplo é um modelo de personagem animado 3D (Formato MD2), é feita pelomodelador (artista gráfico).A textura faz parte do estágio de rasterização do pipeline gráfico do <strong>OpenGL</strong>. A rasterização é a conversãode dados geométricos (polígonos) e pixels (textura) em fragmentos. Cada fragmento corresponde a umpixel no framebuffer.O mapeamento da textura para os fragmentos nem sempre preserva as proporções. Dependendo dageometria do objeto, podem haver distorções da textura ao ser aplicada. Também pode acontecer de ummesmo texel (texture element) ser aplicado a mais de um fragmento como um fragmento mapear váriostexels. Para isso operações de filtragem são aplicadas.Uma textura é um mapa retangular de dados (geralmente cores como em uma imagem). O <strong>OpenGL</strong> oferecealgumas maneiras de se especificar textura: pela especificação de coordenadas para cada vértice do modeloou 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 algunsparâ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 por1


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 qualquerdimensão. Se a textura não for potência de 2, deve-se garantir que a posição de memória do pixel querepresenta o início de uma linha seja múltipla de 4 (mesmo formato adotado pelo padrão de imagemBMP 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 atextura. Para habilitar o recurso de textura utilizam-se os comandosglEnable(GL_TEXTURE);glEnable(GL_TEXTURE_2D);Cada textura esta associada a um ID que é usado pelo <strong>OpenGL</strong> para definição da textura ativa. A cadamomento existe uma única textura ativa, que é aplicada sobre todos os objetos que possuem coordenadas detextura.Para criar um ID utiliza-se o comando glGenTextures(1,&id) que indica que serão criados 1 índicede textura, e que este índice será armazenado na variável intera id.Par se criar, habilitar e desabilitar a textura, utiliza-se o comandovoid glBindTexture(GLenum target, GLuint textureName);O parâmetro textureName corresponde ao ID da textura. O parâmetro target pode assumirGL_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 <strong>OpenGL</strong>.Após a criação da textura (nome), deve-se configurar parâmetros de aplicação da textura sobre umfragmento.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 paraparam.PnameGL_TEXTURE_WRAP_SGL_TEXTURE_WRAP_TGL_TEXTURE_MAG_FILTERGL_TEXTURE_MIN_FILTERparamGL_CLAMP, GL_REPEATGL_CLAMP, GL_REPEATGL_NEAREST, GL_LINEARGL_NEAREST, GL_LINEAR,GL_NEAREST_MIPMAP_NEAREST,GL_NEAREST_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_NEAREST,GL_LINEAR_MIPMAP_LINEAROs parâmetros GL_TEXTURE_WRAP_S/T representam como a textura será aplicada sobre objeto, caso oobjeto 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 porqueapós a projeção, pode ocorrer de parte de um texel aplicado sobre um pixel (magnification) ou de váriostexels 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áriascopias da textura em diferentes resoluções (1/4+1/16+.... ≈ 1/3, ou seja, aumento de 33% do uso dememória), facilitando assim a escolha da melhor textura a ser aplicada sobre o objeto em função da sua áreade 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 detextura em várias resoluções.internalFormat: indica como estão armazenados os dados no array pixels (imagem). Podeassumir vários valores. O parâmetro mais comum é GL_RGB, para indicar que o array é um vetor decoordenadas RGB.width: largura da imagem em pixelsheight: altura da imagem em pixelsborder: 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 utiliza1 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. Porisso é 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 comglVertex*() fazem uso do valor corrente de textura. Geralmente utiliza-se o comandoglTexCoord2f() para definir as coordenadas s e t. Coordenadas de textura são multiplicadas pela matrizde textura antes que o mapeamento ocorra. Por default esta matriz é a identidade. A matriz de textura podeconter vários tipos de transformações como rotações, translações, perspectivas, etc. Para aplicartransformações nesta matriz deve-se setar a matriz de textura como corrente com o comandoglMatrixMode(GL_TEXTURE). Com isso pode-se simular a rotação de uma textura sobre umasuperfí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


Os processos do nível de aplicação executam na CPU e os dois demais na GPU. Os 3 estágios executamsimultaneamente. Antes do surgimento de placas aceleradoras, todo este trabalho era executadoexclusivamente pela CPU. Hoje em dia há uma separação das tarefas, o que permite aliviar o uso da CPUpara 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çãoEntrada do usuárioTransformações domodelo e de câmeraConversão vetorialmatricial - rasterizaçãoCinemática dos objetose teste de colisãoProcessamento da IADescarte de objetos forado campo de visãoIluminaçãoProjeçãoRecorteOperações defragmentos: textura, fog,blending, etc.Frame buffer de cor eprofundidadeExtração da geometria aser visualizadaMapeamento para telaEtapas do processo de renderização. Adaptada de [3].Como mostrado na seguinte figura, o pipeline de geometria opera sobre vértices, enquanto o pipeline derasterização opera sobre pixels (fragmentos).Operações por vérticeAplicaçãoTransformaçãoModelagem eVisualizaçãoIluminação Projeção ClippingMapeamentode TelaRasterizaçãoMapeamentode TexturaCombinaçãoFlog/BlendTeste α, s, zFrameBufferOperações por pixelAplicaçãoGeometriaRasterizaçãoEstá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ãoesté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 havervários fragmentos que projetam em um mesmo pixel. Somente o mais próximo será o visível (veralgoritmo z-buffer).Accumulation Buffer: semelhante ao buffer de cor, com a diferença que este pode ser usado paraacumular uma série de imagens. Isso é útil em operações de super-amostragem, antialiasing e motionblur.Stencil Buffer: buffer usado para restringir o desenho a certas partes da tela. Pode ser usado paradesenhar o interior do carro (painel, direção, etc). Somente a área na região do pára-brisa é que podeser 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 | ...). Paramaiores detalhes de uso destes buffers, consulte [1].O buffer de profundidade é alterado pelo algoritmo z-buffer. Este buffer tem o mesmo tamanho e forma queo 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 adistâ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 odado pixel, está mais próxima que a última primitiva avaliada. Neste caso deve-se atualizar o bufferde 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 PipelineComo os 3 estágios conceituais (aplicação, geometria e rasterização) executam simultaneamente, o que formais 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 degeometria, 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 obtermaior realismo.A estratégia mais simples para se detectar o gargalo é realizar uma série de testes, onde cada teste afetasomente um único estágio. Se o tempo total for afetado, ou seja, reduzir o frame rate, então o gargalo foiencontrado. 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 dosoutros estágios, pela simples substituição dos comandos glVertex*() por glColor*(). Neste caso osestágios de geometria e rasterização não vão ter o que processar. Se o desempenho não aumentar, ogargalo está na aplicação. Outra estratégia é incluir um laço de repetição que realiza N cálculosmatemá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 nesteestá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, ouverificar 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 feitosimplesmente aumentando-se ou reduzindo-se o tamanho da janela onde as imagens estão sendorenderizadas. 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 é desabilitandooperações de textura, fog, blending.Existem várias estratégias para se otimizar os estágios do pipeline. Algumas abordagens comprometem aqualidade 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 maisrápidos. Isso pode ser obtido ativando-se flags de compilação e conhecendo detalhes das linguagensde programação. Algumas dicas gerais de código/memória são listadas. Para maiores detalhesconsulte [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 eiluminação.o Em relação a transformações, se o objeto precisar ser movido para uma posição estática, aoinvés de aplicar a transformação a cada renderização, pode-se aplicar as transformações emum 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, ouprimitivas 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 paraserem rasterizados em aproximadamente 50%;o Desenhar objetos mais próximos do observador primeiro. Isso evita gravações sucessivas noz-buffer;o Desabilitar recursos como fog, blending ou uso de filtros de textura mais baratos, como obilinear 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 dememó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 PipelineA velocidade de renderização depende do estágio mais lento, e é medida em FPS (frames por segundo). Paraeste cálculo, deve-se desabilitar o recurso de double-buffer, visto que a troca de buffer neste caso ocorre emsincronismo 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 framecorrente.Considerando-se o monitor com uma taxa de atualização de 72 Hz, significa que a cada t = 13. 8milisegundos a tela é atualizada. Com o recurso de double-buffer ativado, a taxa de atualização deve sermúltiplo deste valor. Se o tempo de renderização consome 20 ms (50 Hz) em single-buffer, a taxa cairá para2*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].Monitor13.8 ms 13.8 ms 13.8 ms 13.8 ms13.8 msPipeline20 ms 7.78 ms 20 ms 7.78 ms20 msUma forma de contornar os problemas do uso do double-buffer é o uso de tiple-buffer. Neste caso existemdois back buffers e um front buffer. Neste caso, enquanto o monitor está exibindo o font buffer, podem estarsendo gerados 1 ou 2 back buffers. No caso do uso de double-buffer, o o refresh do monitor for de 60 Hz e otempo 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 degeração for pouco maior que 1/60 s, a taxa de atualização cai para 30 fps. Com o uso de triple-buffer, a taxase mantém próxima de 60 fps. O inconveniente do uso de triple-buffer é que o tempo de resposta para açõesdo usuário é um pouco maior, especialmente se o fps for baixo.8. Tópicos adicionaisVários tópicos suportados pelo <strong>OpenGL</strong> não foram tratados neste material. Para detalhes sobre assuntoscomo blending, fog, antialiasing, bitmaps, quádricas, NURBS, picking, e extensões <strong>OpenGL</strong>, 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-bufferdata.[5] "Vertex Specification," 8 8 2012. [Online]. Available: http://www.opengl.org/wiki/Vertex_Specification.[6] "<strong>OpenGL</strong> 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. <strong>OpenGL</strong>: 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] <strong>OpenGL</strong>.org, Vertex Specification. Disponível em:http://www.opengl.org/wiki/Vertex_Specification[11] oZone3D.Net, <strong>OpenGL</strong> Tutorials. Disponível em:http://www.ozone3d.net/tutorials/opengl_vbo_p2.php#part_262

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!