Pular para o conteúdo principal

Aula 06 — Transformações Geométricas 3D em OpenGL

Objetivos​

Ao final desta aula você deve ser capaz de:

  • Estender as três transformações geométricas — translação, escala e rotação — de 2D (Aula 04) para 3D, com matrizes homogêneas 4×4.
  • Explicar por que a rotação em 3D exige um eixo, não só um ângulo, e aplicar glRotatef em torno de qualquer um dos três eixos principais.
  • Diferenciar a navegação da câmera (Aula 05) da navegação do objeto (esta aula): quem se move, e por que os mesmos comandos produzem efeitos diferentes na tela.
  • Selecionar corretamente GL_MODELVIEW e GL_PROJECTION, reinicializar a matriz corrente com glLoadIdentity e isolar instâncias com glPushMatrix/glPopMatrix.
  • Compor várias instâncias de um mesmo modelo 3D por meio de translação e escala, sem acumular acidentalmente as transformações entre os objetos.
  • Criar, chamar e liberar uma display list no contexto OpenGL ativo, reutilizando a geometria sob transformações diferentes.
  • Montar um objeto composto por primitivas diferentes, preservando o referencial local de cada parte com a pilha de matrizes.

Conteúdo​

Retomando: da câmera para o objeto​

Na Aula 05, a câmera navegava e os objetos ficavam parados, com vértices puros — nenhuma chamada glTranslatef, glRotatef ou glScalef foi usada naquela aula. A justificativa, na época, era isolar uma variável: só a câmera deveria mudar a imagem, para que o efeito de gluLookAt ficasse claro sem se misturar com transformações de objeto.

Essa restrição acaba aqui. Você já conhece glTranslatef, glRotatef e glScalef desde a Aula 04 — só que em 2D, onde a rotação girava sempre em torno do mesmo eixo implícito (zz) e a translação/escala tinham só duas componentes. Esta aula estende as três para os três eixos, e devolve aos objetos a capacidade de se mover: agora é o objeto que navega, não (só) a câmera.

Transformações Geométricas 3D​

O Sistema de Referência do Universo (SRU) em 3D já apareceu na Aula 05: três eixos ortogonais xx, yy, zz, seguindo a regra da mão direita adotada pelo OpenGL. As três transformações fundamentais continuam as mesmas — translação, escala e rotação — mas cada uma ganha uma terceira componente.

Como em 2D (Aula 04), o processamento usa coordenadas homogêneas para permitir a composição por multiplicação de matrizes. A diferença é só o tamanho: em 3D, um ponto (x,y,z)(x, y, z) vira (x,y,z,1)(x, y, z, 1), e as matrizes passam a ser 4×4 — três eixos mais a linha/coluna homogênea, em vez das 3×3 da Aula 04.

Translação e escala generalizam de forma direta, só acrescentando TzT_z e EzE_z:

T(Tx,Ty,Tz)=[100Tx010Ty001Tz0001]E(Ex,Ey,Ez)=[Ex0000Ey0000Ez00001]T(T_x, T_y, T_z) = \begin{bmatrix} 1 & 0 & 0 & T_x \\ 0 & 1 & 0 & T_y \\ 0 & 0 & 1 & T_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \qquad E(E_x, E_y, E_z) = \begin{bmatrix} E_x & 0 & 0 & 0 \\ 0 & E_y & 0 & 0 \\ 0 & 0 & E_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

A rotação é onde 2D e 3D realmente divergem. Em 2D só existe um eixo perpendicular ao plano — por isso bastava um ângulo, e o eixo nem precisava ser mencionado. Em 3D existem infinitos eixos possíveis passando pela origem: um ângulo sozinho não diz em torno de que reta o objeto gira. Por isso a rotação 3D precisa de dois dados — ângulo e eixo —, e a forma matricial muda conforme o eixo escolhido. Para os três eixos principais:

Rx(θ)=[10000cos⁡θ−sin⁡θ00sin⁡θcos⁡θ00001]Ry(θ)=[cos⁡θ0sin⁡θ00100−sin⁡θ0cos⁡θ00001]R_x(\theta) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \qquad R_y(\theta) = \begin{bmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} Rz(θ)=[cos⁡θ−sin⁡θ00sin⁡θcos⁡θ0000100001]R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

Repare que RzR_z é exatamente a rotação 2D da Aula 04, só emoldurada em 4×4 — faz sentido, já que girar em torno de zz não deveria mexer em zz. As três seguem a regra da mão direita revisada na Aula 05: com o polegar apontando no sentido positivo do eixo de rotação, os dedos fecham no sentido positivo do ângulo. Confira com Ry(90°)R_y(90°) aplicada a (0,0,1)(0, 0, 1) — o eixo zz positivo: o resultado é (1,0,0)(1, 0, 0), exatamente a regra "zz gira para xx" já usada na Aula 05 para justificar o sentido do yaw da câmera.

Um eixo qualquer, não só x, y ou z

glRotatef(ângulo, x, y, z) aceita qualquer vetor como eixo, não só os três principais — é a chamada rotação de Rodrigues, e a matriz resultante generaliza as três acima. Esta aula usa só os três eixos principais, que já bastam para tudo que vem a seguir; a fórmula geral fica para quem quiser se aprofundar (ver Referências).

Exemplo 1 — as três transformações, agora em três eixos​

📥 Baixe o arquivo completo: tg3d_cubo.py

tg3d_cubo.py (trecho principal)
glPushMatrix()
glTranslatef(0.0, 1.0, 0.0) # mesma base do cubo de referência
if self.modo == "T":
glTranslatef(self.tx, self.ty, self.tz)
elif self.modo == "E":
glScalef(self.ex, self.ey, self.ez)
else:
eixo_vetor = {"X": (1, 0, 0), "Y": (0, 1, 0), "Z": (0, 0, 1)}[self.eixo]
glRotatef(self.angulo, *eixo_vetor)
_desenhar_cubo_aramado(0.6)
glPopMatrix()

O que observar. Pressione T, E ou R para trocar de modo; no modo R, X/Y/Z trocam o eixo de rotação. Gire em torno de Y até 90° e compare com a conta de Ry(90°)R_y(90°) acima: uma aresta que apontava para +z+z deve passar a apontar para +x+x. O glPushMatrix()/glPopMatrix() em volta de cada cubo é exatamente a mesma disciplina de pilha_instancias2d.py (Aula 04): sem ele, a transformação de um cubo vazaria para o próximo.

Câmera e objeto entram na mesma matriz GL_MODELVIEW, embora sejam configurados por chamadas diferentes — gluLookAt para a câmera e glTranslatef/glRotatef para o objeto. O efeito na tela é diferente porque o papel de cada transformação também é diferente:

  • Mover a câmera (Aula 05, via gluLookAt) reposiciona o referencial a partir do qual tudo é visto. Os objetos continuam parados no SRU; quem muda é o ponto de vista.
  • Mover o objeto (esta aula, via glTranslatef/glRotatef dentro do glPushMatrix/glPopMatrix daquele objeto) altera a posição de um objeto dentro do SRU, sem tocar nos demais nem na câmera.

Exemplo 2 — navegação do objeto​

📥 Baixe o arquivo completo: piramide_navegavel.py

piramide_navegavel.py (trecho principal)
def paintGL(self) -> None:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
gluLookAt(*OLHO, *ALVO, *UP) # câmera fixa, primeira chamada

self._desenhar_chao()

glPushMatrix()
glTranslatef(*self.pos)
glRotatef(self.rot_y, 0.0, 1.0, 0.0) # guinada: em torno de Y
glRotatef(self.rot_x, 1.0, 0.0, 0.0) # arfagem: em torno de X
desenhar_piramide_aramada()
glPopMatrix()

O que observar. As setas giram a pirâmide (guinada em Y, arfagem em X); W/A/S/D a transladam. A câmera não se move em nenhum momento — gluLookAt é chamado uma única vez, com os mesmos parâmetros, sempre. Esta é a diferença central em relação à Aula 05: lá, o observador se movia sobre objetos parados; aqui, o observador está parado e é o objeto que acumula glTranslatef/glRotatef a cada tecla.

piramide_navegavel.py usa essa segunda abordagem — a mesma do exemplo Exemplo3DPiramide.cpp do Prof. Rieder, em que as variáveis de translação e rotação do objeto são atualizadas a cada tecla e reaplicadas no desenho, do mesmo jeito que self.pos/self.rot_x/self.rot_y fazem aqui.

Não confunda essas variáveis com a função PosicionaObservador do mesmo exemplo: aquela monta a câmera (é o gluLookAt da Aula 05, e o equivalente dela no arquivo-base da atividade desta aula chama-se _posicionar_observador); estas movem o objeto. As duas escrevem na mesma GL_MODELVIEW, em momentos diferentes do mesmo quadro.

Transformações Geométricas 3D em OpenGL​

As funções já são conhecidas da Aula 04 — a novidade é só a terceira componente:

Função (2D, Aula 04)Função (3D, esta aula)
glTranslatef(Tx, Ty, 0)glTranslatef(Tx, Ty, Tz)
glScalef(Ex, Ey, 1)glScalef(Ex, Ey, Ez)
glRotatef(ângulo, 0, 0, 1)glRotatef(ângulo, x, y, z) — eixo explícito

As variantes com sufixo f recebem valores de precisão simples; as variantes glTranslated, glScaled e glRotated recebem valores de precisão dupla. O papel geométrico é o mesmo.

Antes de alterar uma matriz, é preciso selecionar qual delas ficará corrente:

ChamadaMatriz selecionadaUso nesta unidade
glMatrixMode(GL_MODELVIEW)modelo-visãocâmera e transformações dos objetos
glMatrixMode(GL_PROJECTION)projeçãoglOrtho ou gluPerspective
glMatrixMode(GL_TEXTURE)texturatransformação de coordenadas de textura; não usada nos exemplos

glLoadIdentity() reinicializa a matriz que estiver selecionada. Portanto, a sequência segura é selecionar a matriz, reinicializá-la e só então aplicar as operações daquela etapa. Trocar o modo no lugar errado pode fazer uma transformação de objeto deformar a projeção, ou fazer uma projeção entrar por engano na matriz de modelo-visão.

glPushMatrix, glPopMatrix e glLoadIdentity continuam exatamente como na Aula 04 — e voltam a ser indispensáveis aqui: a Aula 05 não precisou deles porque nenhum objeto se transformava individualmente; agora que objetos voltam a se mover, isolar a transformação de um deles do próximo volta a importar.

Display lists: compilar uma vez, reutilizar muitas vezes​

Quando uma geometria é desenhada repetidamente sem mudar seus vértices, uma display list permite registrar uma sequência de comandos OpenGL e executá-la depois por um identificador. O padrão combina diretamente com o instanciamento desta aula: a lista guarda a geometria local; cada instância aplica sua própria matriz e chama a mesma lista.

Fonte desta seção: OpenGL Programming Guide — Chapter 7: Display Lists, com os exemplos adaptados para Python, PyOpenGL e o ciclo de vida do QOpenGLWidget do PySide6.

lista_piramide = int(glGenLists(1))

glNewList(lista_piramide, GL_COMPILE)
desenhar_piramide() # glBegin, glVertex..., glEnd
glEndList()

# Em paintGL, sob a matriz de cada instância:
glCallList(lista_piramide)

# Antes de destruir o contexto:
glDeleteLists(lista_piramide, 1)

O ciclo de vida tem quatro etapas:

OperaçãoPapel
glGenLists(1)reserva um identificador; no PyOpenGL, o retorno é usado diretamente como um inteiro
glNewList(id, modo) ... glEndList()delimita os comandos que serão compilados
glCallList(id)executa a sequência armazenada no estado OpenGL corrente
glDeleteLists(id, 1)libera o identificador e os recursos associados

Com GL_COMPILE, os comandos são somente armazenados: a geometria não é desenhada durante a criação da lista. GL_COMPILE_AND_EXECUTE armazena e também executa os comandos naquela ocasião. Para inicializar recursos sem produzir um quadro intermediário, GL_COMPILE costuma deixar a intenção mais clara.

Recurso do pipeline de compatibilidade

Display lists são um recurso legado, disponível no contexto OpenGL 2.1 de compatibilidade usado nesta unidade, mas ausente do perfil core moderno. Em aplicações atuais, buffers de vértices e objetos de vértices (VBOs/VAOs) são a abordagem usual. Aqui a lista é útil para compreender reutilização, estado e instanciamento no pipeline fixo — não como recomendação para um projeto OpenGL moderno.

O que realmente fica armazenado​

Os valores enviados aos comandos OpenGL são avaliados durante a compilação. Se uma variável Python mudar depois de glEndList(), a lista não muda com ela. Laços, cálculos trigonométricos e chamadas de funções Python também rodam naquele momento; o que fica armazenado é a sequência de comandos OpenGL produzida por eles.

escala = 0.5
glNewList(modelo, GL_COMPILE)
glScalef(escala, escala, escala) # a lista registra 0.5
desenhar_piramide()
glEndList()

escala = 2.0 # não altera a lista já compilada

Uma lista não oferece acesso de leitura nem edição aos comandos internos. Para alterá-la, compile novamente o mesmo identificador ou apague-a e crie outra. Também não é um formato de arquivo: ela pertence ao contexto OpenGL corrente, não pode ser salva como um modelo e deixa de existir quando o contexto é destruído. Listas muito pequenas ainda podem custar mais para chamar do que economizam; elas fazem mais sentido quando há trabalho suficiente e bastante reutilização.

Cuidados no PySide6 + PyOpenGL

O construtor Python (__init__) acontece antes de existir um contexto OpenGL ativo. Por isso, crie a lista em initializeGL, use-a em paintGL e chame glDeleteLists enquanto o contexto ainda estiver corrente. Mantenha fora da lista os estados que precisam variar por instância — no exemplo, cor, translação e escala — e compile somente a geometria comum.

Exemplo 3 — uma lista, cinco instâncias​

📥 Baixe o arquivo completo: display_lists.py

display_lists.py (trecho principal)
def initializeGL(self):
self.lista_piramide = int(glGenLists(1))
glNewList(self.lista_piramide, GL_COMPILE)
desenhar_piramide()
glEndList()

def paintGL(self):
for x, escala, cor in INSTANCIAS:
glPushMatrix()
glTranslatef(x, 0.0, 0.0)
glScalef(escala, escala, escala)
glColor3f(*cor)
glCallList(self.lista_piramide)
glPopMatrix()

Execute o exemplo e pressione L: a cena alterna entre a display list e a função de desenho imediato equivalente. As setas giram o conjunto para deixar evidente que todas as pirâmides reutilizam a mesma geometria local. Observe que glColor3f, glTranslatef e glScalef ficam fora da lista; se fossem compilados dentro dela, todas as chamadas repetiriam aqueles valores.


Atividades de Laboratório​

Aquecimento​

Antes da atividade, use os dois exemplos da aula para experimentar:

  1. Em tg3d_cubo.py, no modo R, gire em torno de X até 90° e depois em torno de Z até 90°, partindo sempre de zero. O resultado final é o mesmo cubo na mesma orientação? Relacione com a não comutatividade da composição de matrizes, já vista na Aula 04.
  2. Ainda em tg3d_cubo.py, tente prever de cabeça o resultado de glRotatef(90, 0, 1, 0) sobre um vértice em (0,0,1)(0, 0, 1), usando a matriz Ry(θ)R_y(\theta) desta aula — depois confira girando o cubo e observando uma aresta que comece alinhada ao eixo zz.
  3. Em piramide_navegavel.py, translade a pirâmide para longe da câmera (W repetidas vezes) e gire-a 180° em Y (Left/Right). Ela ainda aponta para o mesmo lugar? O que isso diz sobre a ordem translação-depois-rotação (ou o contrário) no seu código?
  4. Compare piramide_navegavel.py (Aula 06) com cena_navegavel_solucao.py (Aula 05): as duas têm teclas de seta para girar. Qual delas gira a câmera e qual gira o objeto? Como você percebe a diferença só olhando para a cena, sem ler o código?

Atividade Prática Individual — Pirâmides de Gizé​

Dois quadros lado a lado, cada um representando a janela do programa com fundo preto. À esquerda, rotulado '(a) sem glPushMatrix/glPopMatrix', dois triângulos minúsculos em traço bege, próximos um do outro e deslocados para a esquerda do quadro. À direita, rotulado '(b) resultado esperado', três triângulos em traço bege alinhados da esquerda para a direita e crescendo de tamanho, rotulados Miquerinos, Quéfren e Quéops.
Os dois quadros do roteiro: à esquerda, o efeito de acumular transformações quando cada instância não é isolada na pilha de matrizes; à direita, o resultado esperado, com cada pirâmide na posição e no tamanho da tabela de instanciamento.

Objetivo. Reproduzir, em wireframe, as três pirâmides da Necrópole de Gizé a partir de um único modelo. Cada instância deve preservar sua própria translação e escala com glPushMatrix()/glPopMatrix().

Avaliação

Esta é uma Atividade Prática Individual da disciplina, avaliada como Task Point. A entrega é o código-fonte, submetido na tarefa correspondente do Moodle. Os valores numéricos das tabelas abaixo fazem parte do enunciado: reproduza-os como estão.

Ponto de partida. O arquivo-base já configura a janela, a projeção em perspectiva, a câmera sintética e a função desenhar_piramide_aramada(). Complete apenas a lista de instâncias e o laço que as desenha. Ao rodar sem os TODOs resolvidos, a janela abre vazia — o título continua mostrando o estado da câmera, então dá para confirmar que o programa está de pé.

📥 Baixe o arquivo completo: piramides_gize_base.py

Roteiro​

Passo 1 — Preserve o modelo. Não altere os vértices de desenhar_piramide_aramada(). As três pirâmides devem ser instâncias do mesmo modelo, diferenciadas somente pelas matrizes aplicadas antes da chamada de desenho.

Passo 2 — Defina as instâncias. Use os valores do material da disciplina:

PirâmidePosiçãoTranslaçãoEscala uniforme
Miquerinosesquerda, menor(−2,0,−5)(-2, 0, -5)(0,45,0,45,0,45)(0{,}45, 0{,}45, 0{,}45)
Quéfrencentro(0,0,0)(0, 0, 0)(0,9,0,9,0,9)(0{,}9, 0{,}9, 0{,}9)
Quéopsdireita, maior(2,0,5)(2, 0, 5)(1,1,1)(1, 1, 1)

Passo 3 — Isole cada transformação. Para cada item da lista, salve a matriz com glPushMatrix(), aplique glTranslatef e glScalef, desenhe a pirâmide e restaure a matriz com glPopMatrix().

Dois cuidados neste passo. O primeiro é a ordem entre as duas transformações: pela regra "T, R, S no código" da Aula 04, a chamada mais próxima do desenho é a que age primeiro sobre o vértice. Você quer que a pirâmide seja redimensionada em torno da própria origem e só depois seja levada até a posição da tabela; a ordem oposta faria a escala multiplicar também o deslocamento.

O segundo é o isolamento em si. Vale rodar uma vez sem o glPushMatrix()/glPopMatrix() antes de acrescentá-los: como as transformações em OpenGL são cumulativas, a segunda pirâmide herda a transformação da primeira e a terceira herda as duas anteriores — o resultado é o quadro (a) da Figura 1, com as três encolhidas e amontoadas em um canto. Ver o estrago na tela ensina mais do que ler sobre ele.

Passo 4 — Confira a aparência e o observador. O arquivo-base já traz tudo isto em constantes, uma por especificação do enunciado — confira que nada foi alterado:

EspecificaçãoValor
Espessura de linha5.0
Cor de linha(0.863, 0.796, 0.710)
Cor de fundo(0.0, 0.0, 0.0, 0.0)
Posição do observadorobs = (1.75, 1.75, 8.5)
Ângulos iniciaisrot_x = 0, rot_y = 45

O giro inicial de 45° em torno de yy não é decorativo: as três translações da tabela descrevem uma diagonal no plano xzxz, como a das pirâmides reais no platô de Gizé. Sem esse giro, a fileira recuaria quase na direção da visada e as pirâmides apareceriam uma atrás da outra.

Passo 5 — Confira o resultado. As três pirâmides devem aparecer separadas, com tamanhos progressivos: a menor à esquerda, a intermediária ao centro e a maior à direita, como no quadro (b) da Figura 1. Use as setas para inspecionar a composição de outros ângulos, W/S para aproximar e afastar o observador, e 0 para voltar ao estado inicial.

Repare que a diferença de tamanho na tela é a soma de dois efeitos independentes: a escala do enunciado (Miquerinos é de fato a menor das três) e a projeção em perspectiva da Aula 05 (o que está mais longe projeta menor). Girando a cena até ver a fileira de outro ângulo, dá para separar um efeito do outro.

Checklist de conclusão​

  • As três instâncias usam a mesma função de desenho.
  • Miquerinos, Quéfren e Quéops usam exatamente as translações e escalas da tabela.
  • Cada instância está isolada por glPushMatrix()/glPopMatrix().
  • glTranslatef vem antes de glScalef no código, de modo que a escala aja primeiro sobre o vértice.
  • Fundo, cor e espessura das linhas correspondem às especificações.
  • Redimensionar a janela não distorce as pirâmides.

Para responder​

Registre as respostas em um comentário no topo do arquivo:

  1. O que aconteceria com Quéfren e Quéops se o glPopMatrix() fosse removido do laço? Explique em termos de acumulação de matrizes, e calcule — a partir da tabela — em que posição e com que escala cada uma das duas iria parar.
  2. Por que é melhor reutilizar uma única função de desenho e variar apenas translação e escala do que criar três listas independentes de vértices?
  3. Se glScalef fosse escrito antes de glTranslatef no laço, qual das três pirâmides sairia do lugar, e por que as outras duas não denunciariam o erro? (Olhe os valores da tabela antes de responder.)
  4. O arquivo-base usa FOVY = 78, mais aberto do que os 45–60° dos outros exemplos do curso. Troque por 45.0 e rode de novo: o que acontece com Quéops? Explique com o que você viu na Aula 05 sobre o fovy de gluPerspective e a forma do volume de visualização.

Atividade — Pedra Lapidada: composição de partes​

Janela escura contendo uma composição em linhas alaranjadas. Um cubo ocupa o centro; uma pirâmide aponta para a esquerda e compartilha sua base com a face esquerda do cubo; outra aponta para a direita e compartilha sua base com a face direita.
A pedra lapidada é formada por três partes: um cubo central e duas instâncias da mesma pirâmide, orientadas em sentidos opostos.

Objetivo. Construir o objeto composto da Figura 2 e fazê-lo navegar como uma única unidade, sem incorporar as posições finais diretamente nas listas de vértices.

Esta atividade adapta o exercício de Visualização 3D às ferramentas atuais da disciplina. A janela, a câmera fixa, o teclado e as duas geometrias locais já estão preparados no arquivo-base.

Baixe o ponto de partida: pedra_lapidada_base.py. Ao executar o arquivo sem completar os TODOs, aparece somente o cubo central.

Roteiro​

Passo 1 — Reconheça os referenciais locais. O cubo tem lado 2 e está centrado na origem. A pirâmide reutilizável tem a base no plano local x=0x=0 e o ápice em x=1,5x=1{,}5; portanto, antes de qualquer transformação, ela aponta para +x+x.

Passo 2 — Acople a ponta direita. Em desenhar_pedra_lapidada(), salve a matriz com glPushMatrix(), translade a pirâmide para (1,0,0)(1,0,0), chame desenhar_arestas() com sua geometria e restaure a matriz. A base local x=0x=0 deve coincidir com a face x=1x=1 do cubo.

Passo 3 — Inverta a orientação sem duplicar vértices. Para a ponta esquerda, salve novamente a matriz, translade para (−1,0,0)(-1,0,0) e gire a pirâmide 180° em torno de yy. Pela ordem das chamadas OpenGL, escreva glTranslatef antes de glRotatef: a rotação deve agir primeiro no modelo local e a translação deve levar o resultado até a face esquerda.

Passo 4 — Navegue com o conjunto. Use as setas para girar a pedra e W/S/A/D para transladá-la. As três partes devem permanecer encaixadas em todos os ângulos. Essas transformações do conjunto ficam fora dos glPushMatrix()/glPopMatrix() internos de cada ponta.

Checklist de conclusão​

  • O cubo central e as duas pirâmides formam um único contorno fechado.
  • As duas pontas reutilizam VERTICES_PIRAMIDE e ARESTAS_PIRAMIDE.
  • Cada pirâmide tem seu próprio par glPushMatrix()/glPopMatrix().
  • A ponta esquerda usa rotação de 180° em torno de yy, sem uma segunda lista de vértices.
  • Girar ou transladar o conjunto não separa as três partes.
  • A tecla 0 restaura a posição e a orientação iniciais.

Para responder​

  1. Por que a translação da ponta direita é exatamente (1,0,0)(1,0,0), e não (2,0,0)(2,0,0)?
  2. O que aconteceria se a rotação de 180° fosse aplicada também à ponta direita?
  3. Por que os glPushMatrix()/glPopMatrix() das pontas ficam dentro da transformação global do conjunto?
  4. Como você construiria uma versão mais alongada alterando a escala do cubo sem abrir uma fresta nas bases das pirâmides?

Exercícios​

Questões dissertativas​

Q1

Em 2D (Aula 04), a rotação bastava um ângulo. Em 3D, glRotatef exige também um eixo. Explique essa diferença em termos do número de eixos de rotação possíveis em cada dimensão.

Q2

Na atividade das Pirâmides de Gizé, por que cada instância deve ficar entre glPushMatrix e glPopMatrix? O que aconteceria se as três translações e escalas fossem aplicadas em sequência, sem restaurar a matriz?

Q3

Câmera (Aula 05) e objeto (Aula 06) entram na mesma matriz GL_MODELVIEW, mas são configurados por chamadas e com papéis diferentes. Explique essa diferença.

Q4Difícil

Em piramide_navegavel.py, o método paintGL chama glTranslatef(*self.pos) e só depois glRotatef(self.rot_y, ...)/glRotatef(self.rot_x, ...), antes de desenhar. As teclas W/A/S/D deslocam a pirâmide sempre nos mesmos eixos do mundo (x e z), mesmo depois de girá-la com as setas — o deslocamento nunca acompanha a orientação da pirâmide. Explique por que, usando a regra 'T, R, S no código' desta aula e da Aula 04.

Questões objetivas​

Quiz6 questões

1. glRotatef(90, 0, 1, 0) é aplicado a um vértice em (0, 0, 1). Qual é o resultado?

  • a)(1, 0, 0)
  • b)(-1, 0, 0)
  • c)(0, 1, 0)
  • d)(0, 0, -1)
  • e)(0, 0, 1) — sem mudança

2. Por que a rotação 2D (Aula 04) bastava com um ângulo, mas a rotação 3D precisa também de um eixo?

  • a)Por uma limitação da OpenGL, sem relação com a geometria
  • b)Porque em 2D só existe um eixo de rotação possível (implícito, perpendicular ao plano); em 3D há infinitos eixos passando pela origem
  • c)Porque em 3D os ângulos são medidos em radianos, não em graus
  • d)Porque glRotatef não aceita rotações em torno de z
  • e)Não há diferença real: o eixo em glRotatef é só um parâmetro decorativo

3. Na Aula 05, quem se move é a câmera; nesta aula, quem se move é o objeto. As duas abordagens alteram a mesma matriz OpenGL. Qual?

  • a)GL_PROJECTION
  • b)GL_MODELVIEW
  • c)GL_TEXTURE
  • d)GL_VIEWPORT — não existe esse modo de matriz
  • e)As duas usam matrizes diferentes, sem relação

4. No laço das Pirâmides de Gizé, glTranslatef aparece ANTES de glScalef, antes da chamada de desenho. Qual transformação afeta os vértices PRIMEIRO?

  • a)glTranslatef, por vir primeiro no código
  • b)glScalef, por estar mais perto da chamada de desenho
  • c)As duas ao mesmo tempo, como uma única operação
  • d)Nenhuma: a ordem das chamadas não influencia o resultado
  • e)Depende da versão do OpenGL

5. Em piramide_navegavel.py, glTranslatef(*self.pos) vem ANTES de glRotatef(...) no código, antes de desenhar a pirâmide. Pela regra 'T, R, S no código', as teclas W/A/S/D movem a pirâmide em relação a que referencial?

  • a)Ao referencial do mundo (eixos x/z fixos) — o deslocamento não acompanha a rotação da pirâmide
  • b)Ao referencial local da pirâmide — o deslocamento sempre acompanha para onde ela está apontando
  • c)Depende do ângulo atual de rotação, varia a cada quadro
  • d)Não há diferença: os dois referenciais coincidem sempre em OpenGL
  • e)Ao referencial da câmera, já que gluLookAt também usa GL_MODELVIEW

6. Uma display list é compilada quando escala vale 0.5. Depois de glEndList(), a variável Python passa a valer 2.0, sem recompilar a lista. Qual escala a chamada glCallList reproduz?

  • a)0.5, pois o argumento enviado ao comando OpenGL foi capturado durante a compilação
  • b)2.0, pois a lista conserva uma referência para a variável Python
  • c)1.0, pois display lists ignoram transformações geométricas
  • d)A média 1.25 entre o valor antigo e o novo
  • e)Depende da matriz GL_PROJECTION

Referências​

Principais (essenciais)​

  • AZEVEDO, E.; CONCI, A.; LETA, F. R. Computação Gráfica. Rio de Janeiro: Elsevier, 2003–2008. 2 v. (004.92 A994c)
  • FOLEY, J. D. et al. Computer Graphics: Principles and Practice. 3. ed. Addison-Wesley, 2013. (004.92 C738)
  • HEARN, D.; BAKER, M. P.; CARITHERS, W. R. Computer Graphics with OpenGL. 4. ed. Pearson Prentice Hall, 2011. (004.92 H436co)
  • OpenGL Programming Guide. Chapter 7 — Display Lists.
  • The Khronos Group. OpenGL 2.1 Reference Pages — glRotate. Disponível em: registry.khronos.org/OpenGL-Refpages/gl2.1

Aprofundamento (opcionais)​

  • COHEN, M.; MANSSOUR, I. H. OpenGL: uma abordagem prática e objetiva. São Paulo: Novatec, 2006. (004.92 C678o)
  • HETEM JUNIOR, A. Computação gráfica. Rio de Janeiro: LTC, 2006. (004.92 H589c)
  • BURDEA, G.; COIFFET, P. Virtual Reality Technology. 2. ed. Wiley-Interscience, 2003. (004.94 B949v)
  • Wikipedia. Rotation matrix. Disponível em: en.wikipedia.org/wiki/Rotation_matrix
  • Wikipedia. Rodrigues' rotation formula. Disponível em: en.wikipedia.org/wiki/Rodrigues%27_rotation_formula