Pular para o conteúdo principal

Aula 05 — Câmera Sintética e Projeções

Objetivos​

Ao final desta aula você deve ser capaz de:

  • Explicar por que visualizar uma cena 3D exige duas etapas que não existiam no pipeline 2D: definir uma câmera sintética e escolher uma projeção.
  • Descrever os dois parâmetros que definem uma câmera sintética — posição e orientação (ponto-alvo e vetor up) — e o Sistema de Referência da Câmera (SRC) que nascem deles.
  • Diferenciar projeção paralela ortográfica de projeção em perspectiva, pela forma das projetantes e pelo volume de visualização que cada uma produz.
  • Relacionar os parâmetros de gluLookAt, glOrtho e gluPerspective com os conceitos de câmera e de volume de visualização.
  • Aplicar a regra da mão direita para prever o sentido de um ângulo de rotação em torno de um eixo — e verificar, em vez de supor, quando o vetor de partida não é um dos eixos do mundo.
  • Implementar uma câmera navegável (posição e ângulo de visada controlados pelo teclado) e alternar, em tempo real, entre as duas projeções desta aula.

Conteúdo​

Retomando: o que muda em relação à Aula 04​

Até aqui, toda a cena era 2D: um conjunto de vértices no plano xyxy, posicionado pelas transformações geométricas da Aula 04 e mapeado da window para a viewport. Bastava decidir que retângulo do mundo mostrar (gluOrtho2D) e onde, na tela, mostrá-lo (glViewport).

Em 3D isso não é suficiente. Um objeto tem posição nos três eixos, mas a tela continua sendo uma superfície 2D — e existem infinitas maneiras de "achatar" uma cena 3D em uma imagem plana, dependendo de de onde e em que direção se olha, e de como as três dimensões viram duas. São exatamente essas duas decisões, novas nesta aula, que entram entre a modelagem da cena e o mapeamento já conhecido:

  1. Câmera sintética — de onde e para onde se olha.
  2. Projeção — como o volume 3D visível vira uma imagem plana.

Só depois dessas duas etapas o processo volta a ser o que já era: mapear o resultado para a viewport e rasterizar.

Visualização Tridimensional​

O Sistema de Referência do Universo (SRU) em 3D é formado por três eixos ortogonais entre si — xx, yy e zz — em vez de dois. Toda coordenada no mundo passa a ser uma tripla (x,y,z)(x, y, z).

A convenção de sinais entre os três eixos não é arbitrária: fixados dois deles, o terceiro só pode apontar para um de dois sentidos possíveis, e a escolha entre eles é a regra da mão direita. O OpenGL adota o SRU de mão direita: com o polegar no sentido positivo de um eixo, os dedos se fecham no sentido positivo de rotação em torno dele.

Dois diagramas lado a lado. À esquerda, os eixos x, y e z partindo de uma origem O, com x para a direita, y para cima e z na diagonal inferior esquerda (saindo da tela), e um arco indicando o ângulo positivo de rotação no sentido anti-horário. À direita, um esquema de eixo vertical rotulado 'eixo (polegar)' com um arco ao redor rotulado 'dedos (rotação)', ilustrando a regra da mão direita: o polegar aponta no sentido positivo do eixo e os dedos indicam o sentido positivo da rotação em torno dele.
A regra da mão direita: o polegar aponta no sentido positivo do eixo em torno do qual se gira; os dedos, ao se fechar, indicam o sentido positivo do ângulo de rotação.

Isso já apareceu, sem nome, na Aula 04: um ângulo de rotação positivo é sempre o sentido anti-horário — quando visto de quem olha do eixo de rotação para a origem. Um ângulo negativo gira no sentido horário. A regra vale para qualquer eixo, não só para o eixo zz implícito das rotações 2D: é por isso que ela precisa de nome próprio a partir de agora, quando os três eixos entram em jogo ao mesmo tempo.

Visualizar uma cena 3D é mais complexo do que visualizar uma cena 2D justamente por isso: além da posição dos objetos, é preciso decidir de onde a cena é observada — e essa decisão, sozinha, já muda a imagem final sem que um único vértice da cena tenha sido tocado.

Câmera Sintética​

Um mesmo conjunto de objetos, visto de lugares diferentes, produz imagens diferentes — a mesma ideia por trás de uma fotografia: a cena não muda, muda o lugar de onde e a direção para onde a máquina aponta.

Uma câmera sintética formaliza essa escolha com dois grupos de parâmetros:

  • Posição do observador — um ponto (x,y,z)(x, y, z) no SRU: de onde se olha.
  • Orientação do observador — para onde e com que inclinação vertical se olha, dada por um ponto-alvo (o que a câmera está olhando, normalmente o centro da cena) e um vetor up (que direção é "para cima" do ponto de vista da câmera).
Diagrama esquemático de uma câmera sintética. Um pequeno ícone em forma de câmera, rotulado 'observador', está posicionado acima e à esquerda de um quadrado rotulado 'alvo'. Uma seta azul liga o observador ao alvo, rotulada 'mira (para o alvo)'. Uma seta verde curta, rotulada 'up', sai do observador para cima. Três setas menores saem do observador representando os eixos xc, yc e zc do Sistema de Referência da Câmera, com uma nota explicando que zc aponta do alvo para o observador. Abaixo, à esquerda, os eixos xu e yu marcam a origem do SRU, para contraste.
Posição (o olho) e orientação (o alvo e o vetor up) definem o Sistema de Referência da Câmera (SRC) — o referencial a partir do qual a cena passa a ser vista.

A partir da posição e da orientação, é possível construir um novo sistema de referência, centrado no observador: o Sistema de Referência da Câmera (SRC). No pipeline clássico do OpenGL, esse referencial continua seguindo a regra da mão direita: o eixo zcz_c aponta do alvo para o observador e, por isso, a câmera olha no sentido de −zc-z_c. Alguns materiais descrevem a imagem vista pela câmera com uma convenção de mão esquerda; aqui adotaremos a convenção efetivamente usada pelas matrizes de visualização do OpenGL. É esse referencial que permite responder perguntas como "qual objeto está mais perto?" ou "um objeto está encobrindo outro?": a resposta depende de onde está o observador, não apenas da posição dos objetos no SRU.

Exemplo 1 — quatro pontos de vista, uma cena só​

📥 Baixe o arquivo completo: camera_sintetica.py

camera_sintetica.py (trecho principal)
PONTOS_DE_VISTA = [
((0.0, 3.0, 9.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)), # 1: frontal
((9.0, 2.0, 0.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)), # 2: lateral
((0.0, 9.0, 0.01), (0.0, 0.0, 0.0), (0.0, 0.0, -1.0)), # 3: vista de pássaro
((-3.0, 0.6, 3.0), (1.0, 0.5, -1.0), (0.0, 1.0, 0.0)), # 4: rente ao chão
]

def paintGL(self) -> None:
glLoadIdentity()
olho, alvo, up = PONTOS_DE_VISTA[self.indice_vista]
gluLookAt(*olho, *alvo, *up)
# ... desenha a cena, que não muda ...

O que observar. Pressione 1, 2, 3 e 4 e compare os quatro quadros. Nenhum vértice da cena mudou entre um e outro — só os três parâmetros de gluLookAt (olho, alvo, up). O ponto de vista 3 (olho quase sobre o alvo, olhando para baixo) exige um up diferente de (0,1,0)(0,1,0): com a câmera olhando quase reto para baixo, o vetor "para cima do mundo" deixa de servir como "para cima da câmera", e é preciso escolher outro — aqui, (0,0,−1)(0, 0, -1).

Por que gluLookAt recebe nove números, não um

Três parâmetros bastariam para a posição. Mas orientação sozinha — "olhando para ali" — ainda deixa a câmera livre para girar em torno do próprio eixo de mira, como uma câmera de mão que pode ficar "de cabeça para baixo" apontando para o mesmo lugar. O vetor up resolve essa liberdade que sobra: ele não precisa ser exatamente perpendicular à mira (gluLookAt corrige isso internamente), só precisar não ser paralelo a ela.

Projeções​

Com a câmera definida, a cena já está posicionada em relação ao observador — mas ainda em três dimensões. Projetar é o processo que reduz essa cena 3D a uma imagem 2D: cada vértice do objeto é ligado ao centro de projeção (ou a uma direção comum, na paralela) por uma projetante, e o ponto em que essa projetante cruza um plano de projeção é a posição do vértice na imagem.

Nem toda a cena é projetada: só o que está dentro do volume de visualização (view frustum) — a região do espaço delimitada pela window, e por um plano frontal (near) e um plano traseiro (far), que descartam o que está longe demais ou perto demais da câmera. A forma desse volume — e portanto o que "perto" e "longe" significam para o objeto — é justamente o que distingue os dois tipos de projeção desta aula.

Projeção Paralela Ortográfica​

As projetantes são paralelas entre si e cruzam o plano de projeção em um ângulo de 90°. O volume de visualização resultante é um paralelepípedo: um objeto tem o mesmo tamanho na imagem estando perto ou longe da câmera, porque a distância não entra em nenhuma conta.

No referencial da câmera, quando o plano de projeção está alinhado a xyxy, a conta assume uma forma especialmente simples: a profundidade zz não altera as coordenadas projetadas xx e yy. Por isso, dois objetos iguais conservam o mesmo tamanho aparente mesmo estando a profundidades diferentes. Faces inclinadas em relação ao plano de projeção ainda sofrem encurtamento aparente; para obter medidas diretamente comparáveis, o desenho técnico usa vistas ortográficas alinhadas às faces de interesse.

Projeção em Perspectiva​

As projetantes convergem para um único ponto, o centro de projeção — a posição do observador. O volume de visualização é um tronco de pirâmide: objetos mais distantes projetam menores do que objetos próximos do mesmo tamanho, exatamente como no olho humano ou em uma câmera fotográfica real.

Dois diagramas lado a lado. À esquerda, 'Paralela ortográfica': um volume em forma de paralelepípedo entre um plano frontal (near) menor à frente e um plano traseiro (far) do mesmo tamanho ao fundo, com um objeto pequeno dentro e setas azuis paralelas entre si representando as projetantes. À direita, 'Perspectiva': um ponto no topo rotulado 'centro de projeção' com setas convergindo para ele a partir de um plano frontal (near) pequeno e um plano traseiro (far) bem maior, formando um volume em tronco de pirâmide, com um objeto dentro.
Projeção paralela ortográfica (projetantes paralelas, volume em paralelepípedo) e projeção em perspectiva (projetantes convergentes, volume em tronco de pirâmide) — a mesma cena, dois volumes de visualização diferentes.

Exemplo 2 — ortográfica × perspectiva lado a lado​

📥 Baixe o arquivo completo: projecoes_comparacao.py

projecoes_comparacao.py (trecho principal)
def _desenhar_metade(self, x0, largura, altura, ortografica: bool) -> None:
glViewport(x0, 0, largura, altura)
aspecto = largura / float(altura)

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
if ortografica:
meia_h = ORTHO_META_ALTURA
glOrtho(-meia_h * aspecto, meia_h * aspecto, -meia_h, meia_h, 0.1, 100.0)
else:
gluPerspective(35.0, aspecto, 0.1, 100.0)

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt(*OLHO, *ALVO, *UP)
self._desenhar_cena()

O que observar. Os dois blocos têm exatamente o mesmo tamanho real (SEMI_LADO = 0.6) e estão ligeiramente separados na horizontal para que um não encubra o outro; a diferença relevante é a distância à câmera. Na metade ortográfica, os dois aparecem do mesmo tamanho na tela — a distância não afeta a projeção. Na metade em perspectiva, o bloco distante aparece visivelmente menor. É a mesma diferença da Figura 3, agora com uma cena real desenhada dos dois jeitos ao mesmo tempo.

Câmera e Projeção em OpenGL​

Há uma função para cada um dos três conceitos desta aula. Repare no prefixo: glOrtho é do núcleo do OpenGL, enquanto gluPerspective e gluLookAt vêm da GLU, a biblioteca utilitária apresentada na Aula 01 — as duas são conveniências construídas sobre chamadas mais primitivas.

FunçãoEtapaParâmetros
gluLookAt(obsx, obsy, obsz, alvox, alvoy, alvoz, upx, upy, upz)Câmera sintéticaPosição do observador; ponto-alvo; vetor up
glOrtho(left, right, bottom, top, near, far)Projeção paralela ortográficaLimites da window nos três eixos
gluPerspective(fovy, aspect, near, far)Projeção em perspectivaÂngulo de visão vertical; proporção da viewport; planos de corte

Repare que glOrtho recebe seis limites (como uma extensão 3D do gluOrtho2D já conhecido), enquanto gluPerspective recebe um ângulo de abertura (fovy, em graus) e a proporção da viewport — é essa combinação que determina a forma do tronco de pirâmide, sem que seja preciso calcular os quatro cantos à mão.

Em gluPerspective, near e far são distâncias positivas medidas a partir do observador, com 0 < near < far. Eles não são coordenadas zz assinadas. Já em glOrtho, os dois valores entram como limites do volume ortográfico; em ambos os casos, tudo que fica fora do intervalo de profundidade é recortado.

glMatrixMode: em qual matriz cada chamada escreve​

Nenhuma dessas três funções recebe como parâmetro qual matriz deve alterar: todas escrevem na matriz corrente. Quem escolhe a matriz corrente é glMatrixMode(modo), e há três modos:

ModoMatriz selecionadaQuem escreve nela
GL_MODELVIEWModelo-visão: posiciona a cena em relação ao observadorgluLookAt; glTranslatef, glRotatef, glScalef (Aula 04, retomadas em 3D na Aula 06)
GL_PROJECTIONProjeção: a forma do volume de visualizaçãoglOrtho, gluPerspective e o gluOrtho2D da Aula 03
GL_TEXTURETextura: transforma as coordenadas de textura, não a geometriafora do escopo desta aula — volta na Aula 07

glLoadIdentity() também age sobre a matriz corrente: ele a reinicia como matriz identidade, isto é, "nenhuma transformação aplicada". É por isso que quase todo resizeGL desta disciplina tem sempre a mesma forma — seleciona GL_PROJECTION, chama glLoadIdentity(), monta a projeção e volta para GL_MODELVIEW. Essa última linha parece supérflua e não é: sem ela, as transformações do paintGL acabam sendo escritas na matriz de projeção, e a cena some ou aparece deformada sem nenhuma mensagem de erro.

Traduzindo a tabela para as três funções desta aula:

  • glOrtho e gluPerspective escrevem na matriz de projeção, exatamente como gluOrtho2D na Aula 03: elas descrevem a forma do volume de visualização, sem dizer nada sobre onde a câmera está.
  • gluLookAt escreve na matriz de modelo-visão — a mesma que já carregava, na Aula 04, as transformações geométricas dos objetos (glTranslatef, glRotatef, glScalef), e que voltará a carregá-las em 3D na Aula 06. Ela entra como se fosse a transformação inversa do observador: em vez de levar a câmera até a cena, traz a cena inteira até a câmera, que fica parada na origem do SRC. Por isso gluLookAt é sempre a primeira chamada depois de glLoadIdentity() no paintGL — tudo que for desenhado depois herda essa base.
A projeção vem antes da câmera

As duas matrizes são independentes uma da outra. Antes de desenhar, ambas precisam estar corretamente configuradas, e gluLookAt deve entrar na modelo-visão recém-reinicializada antes das transformações dos objetos.

Nos programas em C com IUP do material do Prof. Rieder, em que a projeção e o desenho acontecem na mesma função, isso vira uma regra literal de ordem no texto do programa: gluPerspective sempre antes de gluLookAt, e em GL_PROJECTION. Nos exemplos desta disciplina a separação é mais visível — a projeção fica no resizeGL (ou no topo do paintGL, quando depende de um estado que o usuário controla, como o fovy do Exemplo 3) e gluLookAt abre o paintGL —, mas a exigência é exatamente a mesma.

Por que glPushMatrix / glPopMatrix não aparecem nesta aula

Você já os conhece da Aula 04: eles isolam transformações para que uma não vaze para o próximo objeto. Nesta aula, porém, nenhum objeto é transladado, rotacionado ou escalado individualmente — só a câmera muda —, e por isso não há nada a isolar. Eles voltam a ser indispensáveis na Aula 06, quando as transformações geométricas passam a valer para os três eixos.

Exemplo 3 — zoom e pan em 3D​

📥 Baixe o arquivo completo: zoom_pan_3d.py

zoom_pan_3d.py (trecho principal)
def keyPressEvent(self, event) -> None:
tecla = event.key()
if tecla in (Qt.Key.Key_Plus, Qt.Key.Key_Equal):
self.fovy = max(FOVY_MIN, self.fovy / FATOR_ZOOM)
elif tecla == Qt.Key.Key_Minus:
self.fovy = min(FOVY_MAX, self.fovy * FATOR_ZOOM)
elif tecla in (Qt.Key.Key_Left, Qt.Key.Key_Right):
lateral = self._vetor_lateral()
sinal = -1.0 if tecla == Qt.Key.Key_Left else 1.0
for i in range(3):
desloc = sinal * PASSO_PAN * lateral[i]
self.olho[i] += desloc
self.alvo[i] += desloc

O que observar. Compare com zoom_pan.py da Aula 03: lá o zoom mudava os limites de gluOrtho2D; aqui ele estreita ou alarga o fovy de gluPerspective — uma lente mais "fechada" ou mais "aberta", sem mover a câmera do lugar. O pan também muda de mecanismo: em vez de deslocar o centro da window (que em 3D nem existe como conceito isolado), ele desloca olho e alvo juntos, na direção lateral da câmera — calculada pelo produto vetorial entre a mira e o up, a mesma conta que gluLookAt faz por baixo dos panos para montar o SRC.

Repare também que, como em zoom_pan.py, a projeção é recalculada a cada quadro dentro de paintGL, e não só no resizeGL: ela depende de um estado que o usuário controla (self.fovy), não só do tamanho da janela.

O processo de visualização 3D​

Diagrama de cinco caixas conectadas por setas, da esquerda para a direita: Modelagem (objeto no SRO), Câmera (SRU para SRC), Projeção (paralela ou perspectiva), Mapeamento (window para viewport) e Rasterização (pixels na tela). As duas primeiras caixas têm tom azul escuro, a terceira e a quarta têm tons de azul mais claro e âmbar, respectivamente, e a última tem tom âmbar.
O processo completo de visualização 3D: às etapas já conhecidas da Aula 03 (mapeamento e rasterização), somam-se a câmera sintética e a projeção — as duas novidades desta aula.

As duas últimas etapas — mapeamento e rasterização — são exatamente as da Aula 03, agora aplicadas ao resultado já achatado em 2D pela projeção. É por isso que uma cena 3D, no fim, passa pelo mesmo glViewport que uma cena 2D: a diferença toda aconteceu antes, nas etapas de câmera e projeção.


Atividades de Laboratório​

Aquecimento​

Antes da atividade, use os três exemplos da aula para experimentar:

  1. Em camera_sintetica.py, alterne entre os pontos de vista 1 a 4 e, para cada um, escreva de memória (antes de olhar o código) os valores de olho que você espera — depois confira no título da janela.
  2. Ainda em camera_sintetica.py, no ponto de vista 3 (vista de pássaro), tente trocar o up de volta para (0,1,0)(0, 1, 0) e rode de novo. O que acontece? Por que o alvo deixa de ajudar a definir a orientação quando a mira fica quase paralela ao up?
  3. Em projecoes_comparacao.py, mude DISTANCIA_BLOCO_LONGE para um valor mais próximo de DISTANCIA_BLOCO_PERTO e rode de novo. O que acontece com a diferença de tamanho na metade em perspectiva? E na ortográfica?
  4. Em zoom_pan_3d.py, pressione - repetidamente até fovy chegar perto de FOVY_MAX. O que acontece com a cena? Relacione com a Figura 3: o que muda na forma do tronco de pirâmide quando o ângulo de abertura cresce?
  5. Ainda em zoom_pan_3d.py, pressione + repetidamente até fovy chegar perto de FOVY_MIN. Esse efeito de campo de visão muito estreito tem nome em fotografia — pesquise "efeito teleobjetiva" e relacione com o que você está vendo.

Atividade — Cena Navegável 3D​

Vista de cima de uma grade retangular representando o chão da cena, com cinco retângulos arredondados espalhados em posições e profundidades diferentes, representando os blocos. No canto inferior esquerdo, um ponto azul marcado 'observador' tem uma seta indicando a direção inicial e duas linhas tracejadas abrindo um ângulo a partir dele, representando o campo de visão. Eixos x e z marcam a orientação da vista de cima.
Mapa de referência da cena (vista de cima): o layout relativo dos blocos e a posição inicial do observador. Posições e tamanhos exatos ficam por sua conta — como nas Figuras 14 e 15 da Aula 03.

Objetivo. Implementar uma câmera navegável — posição e ângulo de visada controlados pelo teclado — sobre uma cena 3D fixa, e alternar em tempo real entre projeção ortográfica e projeção em perspectiva.

Ponto de partida. O programa já abre a janela, configura o contexto e desenha um chão em grade; os blocos da cena e a lógica da câmera estão por sua conta.

📥 Baixe o arquivo completo: cena_navegavel_base.py

Por que sem transformações geométricas?

Você já sabe posicionar objetos com glTranslatef desde a Aula 04, e aqui a restrição é deliberada: os blocos desta cena são desenhados com vértices puros, para que a única coisa capaz de mudar a imagem seja a câmera. gluLookAt não move os vértices de cada objeto separadamente. Ele monta a transformação de visualização, equivalente a aplicar à cena o movimento inverso da câmera. A distinção entre câmera e objeto — e por que ambas entram na mesma matriz GL_MODELVIEW — é o assunto da nota "Por que glPushMatrix / glPopMatrix não aparecem nesta aula", mais acima.

Roteiro​

Passo 1 — Modele a cena. Em BLOCOS, acrescente pelo menos quatro blocos — (cx, cy, cz, semi-lado, cor) — em posições e profundidades diferentes, seguindo o layout relativo da Figura 5. Lembre-se de que, na convenção desta aula, a câmera parte olhando para −z-z: coloque os blocos em valores de zz negativos em relação à posição inicial do observador (self.eye), ou eles nascerão atrás de você.

Passo 2 — Calcule a direção da frente. Complete _direcao_frente. Em self.yaw = 0 ela deve devolver (0,0,−1)(0, 0, -1) — já é o que o método faz. O que falta é generalizar para qualquer self.yaw: é uma rotação em torno do eixo yy, partindo desse vetor.

Antes de escrever a fórmula, responda no papel: girando o SRU de mão direita em torno de +y+y no sentido positivo (Figura 1, dedos de +z+z para +x+x), para que lado o vetor (0,0,−1)(0, 0, -1) se move — para +x+x ou para −x-x? Guarde a resposta: o Passo 4 pede para compará-la com o sentido que você escolher para as teclas de rotação, e as duas coisas não são obrigadas a coincidir.

Passo 3 — Calcule a direção lateral. Complete _direcao_lateral, a partir de self._direcao_frente() e do vetor UP fixo (0,1,0)(0, 1, 0) — é o mesmo produto vetorial que zoom_pan_3d.py usa para o pan, só que agora recalculado a cada quadro porque a frente muda com self.yaw.

Confira no interpretador, sem depender da tela: em self.yaw = 0, _direcao_lateral() deve devolver algo paralelo a (1,0,0)(1, 0, 0).

Passo 4 — Monte o alvo e chame gluLookAt. Em paintGL, calcule o ponto-alvo como self.eye deslocado por _direcao_frente() e passe self.eye, o alvo e UP para gluLookAt. Sem este passo, a chamada provisória continua olhando para a origem e a orientação não acompanha corretamente a posição e o yaw do observador.

Passo 5 — Alterne a projeção. Em _configurar_projecao, complete o if: quando self.ortografica for True, chame glOrtho com meia-altura ORTHO_META_ALTURA (a meia-largura é a meia-altura vezes o aspect ratio da janela) e planos near/far 0.1 e 100.0; senão, mantenha gluPerspective(FOVY, aspecto, 0.1, 100.0).

Passo 6 — Implemente a navegação. Em keyPressEvent, complete o bloco de W/S/A/D: W e S andam para frente e para trás (usando _direcao_frente()); A e D fazem strafe lateral (usando _direcao_lateral()), sem girar self.yaw. PASSO_MOVIMENTO é o tamanho do passo. As teclas de rotação (Left e Right) e a de alternar projeção (O) já estão prontas — foi decidido por você, no Passo 2, se elas giram para o lado esperado.

Se o sentido da rotação estiver invertido em relação à sua expectativa, não troque Qt.Key.Key_Left por Qt.Key.Key_Right só para "consertar na tecla" — volte ao Passo 2 e descubra qual sinal da fórmula está causando isso: um defeito de sinal se corrige na origem, não no sintoma.

Passo 7 — Confronte com a referência. Compare o layout da sua cena, de cima, com a Figura 5. Navegue até conseguir ver todos os blocos a partir da posição inicial, girando só com Left/Right. Alterne O em pelo menos duas posições diferentes e confirme que a projeção ortográfica "achata" a diferença de tamanho entre blocos próximos e distantes, como no Exemplo 2.

Checklist de conclusão​

  • Pelo menos quatro blocos aparecem, em profundidades diferentes.
  • W/S andam para frente/trás; A/D fazem strafe sem girar.
  • Left/Right giram o ângulo de visada sem transladar o observador.
  • O alterna entre as duas projeções, visivelmente.
  • Nenhuma chamada glTranslatef, glRotatef ou glScalef foi usada.
  • Redimensionar a janela não distorce a cena em nenhuma das duas projeções.

Para responder​

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

  1. No Passo 2, qual sinal você escolheu para a fórmula de _direcao_frente, e ele coincidiu com a previsão que você fez pela regra da mão direita antes de escrever o código? Se não coincidiu, explique por que não — a resposta tem a ver com o vetor de partida ser −z-z, não +z+z.
  2. Suponha que UP fosse (0,0,1)(0, 0, 1) em vez de (0,1,0)(0, 1, 0), sem mudar mais nada. O que aconteceria com a navegação? (Não precisa testar — raciocine a partir do que _direcao_lateral calcula.)
  3. Se você quisesse impedir o observador de atravessar os blocos — uma forma simples de detecção de colisão —, em que método do arquivo essa verificação entraria, e o que ela precisaria comparar?

Exercícios​

Questões dissertativas​

Q1

Por que a visualização de uma cena 3D precisa de duas etapas — câmera sintética e projeção — que não existiam no pipeline 2D da Aula 03?

Q2

gluLookAt recebe posição, alvo E vetor up — três grupos de parâmetros, não dois. Por que a posição e o alvo sozinhos não bastam para definir a orientação da câmera?

Q3

Um engenheiro projetando uma peça mecânica e um jogo em primeira pessoa precisam, cada um, de um tipo de projeção diferente. Diga qual é cada um e justifique pela forma do volume de visualização.

Q4

gluLookAt altera a matriz GL_MODELVIEW; glOrtho e gluPerspective alteram a matriz GL_PROJECTION. Explique a diferença de papel entre as duas matrizes, e por que gluLookAt não poderia estar do lado da projeção.

Q5Difícil

Na Atividade, girar o SRU em torno de +y no sentido positivo (regra da mão direita) leva +z para +x. Mas para o vetor de frente da câmera — que parte de -z, não de +z — 'yaw crescente' precisou ser definido com o sinal invertido para girar o observador para a direita. Explique por que essas duas rotações, aparentemente a mesma regra, produzem sentidos opostos.

Q6

No Exemplo 1 (janela_viewport.py, Aula 03), um viewport com proporção diferente da window fazia a cena esticar. Em gluPerspective, o parâmetro aspect cumpre o papel equivalente. O que acontece se ele for passado com um valor errado — por exemplo, sempre 1.0, independente do tamanho real da janela?

Questões objetivas​

Quiz8 questões

1. Uma câmera é definida por gluLookAt(5, 0, 0, 0, 0, 0, 0, 1, 0). Qual vetor representa o eixo zc do SRC (do alvo para o observador)?

  • a)(1, 0, 0)
  • b)(-1, 0, 0)
  • c)(0, 1, 0)
  • d)(0, 0, 1)
  • e)(0, 0, -1)

2. Dois blocos do mesmo tamanho real estão a distâncias diferentes da câmera. Sob projeção paralela ortográfica, como eles aparecem na imagem?

  • a)Do mesmo tamanho, como no Exemplo 2 desta aula
  • b)O mais distante aparece maior
  • c)O mais distante aparece menor, como na perspectiva
  • d)Nenhum dos dois aparece, por estarem fora do volume
  • e)O tamanho depende só do fovy

3. O fovy de uma câmera em gluPerspective é reduzido de 90° para 20°, mantendo aspect, near e far. O que acontece com a cena?

  • a)Nada muda: fovy só afeta a cor do fundo
  • b)O campo de visão se estreita; objetos parecem mais próximos e maiores (efeito teleobjetiva)
  • c)O campo de visão se alarga; mais cena passa a caber na tela
  • d)A projeção deixa de ser em perspectiva
  • e)A cena passa a ser cortada pelo plano near

4. Girando o SRU de mão direita em torno do eixo +x no sentido positivo, para onde o eixo +y se move?

  • a)Em direção a +z
  • b)Em direção a -z
  • c)Em direção a +x (não se move)
  • d)Em direção a -y
  • e)A regra da mão direita não se aplica ao eixo x

5. Por que glOrtho (3D) exige os parâmetros near e far, que gluOrtho2D (Aula 03) não tinha?

  • a)Porque em 3D a window sozinha não delimita o volume ao longo da profundidade — near/far fecham essa dimensão que o 2D não tinha
  • b)Por compatibilidade com versões antigas do OpenGL, sem efeito real
  • c)Porque near/far substituem left/right/bottom/top
  • d)Porque glOrtho não aceita coordenadas negativas sem eles
  • e)Near e far só existem em gluPerspective; glOrtho não os usa de fato

6. Qual é o formato do volume de visualização de uma projeção em perspectiva?

  • a)Paralelepípedo
  • b)Cone
  • c)Tronco de pirâmide
  • d)Cilindro
  • e)Esfera

7. Um resizeGL faz glMatrixMode(GL_PROJECTION), glLoadIdentity(), gluPerspective(...) — e esquece de voltar para glMatrixMode(GL_MODELVIEW) no fim. O que acontece?

  • a)Nada: o modo de matriz volta sozinho ao padrão a cada quadro
  • b)O programa lança um erro de OpenGL na próxima chamada de desenho
  • c)As chamadas seguintes (gluLookAt, glTranslatef...) passam a escrever na matriz de PROJEÇÃO, e a cena aparece deformada ou some — sem nenhum erro
  • d)Só a próxima chamada é afetada; da seguinte em diante tudo volta ao normal
  • e)gluPerspective é ignorado, e a projeção continua a padrão

8. Em um programa, gluLookAt é chamado DEPOIS de glTranslatef(2, 0, 0) no mesmo paintGL, sem glLoadIdentity() entre os dois. O que acontece?

  • a)gluLookAt sobrescreve o efeito de glTranslatef, porque só um deles vale por vez
  • b)As duas transformações se combinam na mesma matriz GL_MODELVIEW, na ordem em que foram chamadas — o resultado não é simplesmente 'a câmera em gluLookAt'
  • c)O programa lança um erro, porque gluLookAt exige ser a primeira chamada após glLoadIdentity()
  • d)glTranslatef é ignorado, porque só afeta GL_PROJECTION
  • e)A ordem não importa: multiplicação de matrizes é comutativa

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)
  • The Khronos Group. OpenGL 2.1 Reference Pages — gluLookAt. Disponível em: registry.khronos.org/OpenGL-Refpages/gl2.1
  • The Khronos Group. OpenGL 2.1 Reference Pages — gluPerspective. Disponível em: registry.khronos.org/OpenGL-Refpages/gl2.1

Aprofundamento (opcionais)​