Pular para o conteúdo principal

Aula 07 — Leitura de Objetos e Superfícies Ocultas

Objetivos​

Ao final desta aula você deve ser capaz de:

  • Comparar wireframes, malhas poligonais e representações de sólidos, reconhecendo que cada estrutura responde a perguntas diferentes sobre o objeto.
  • Construir uma malha triangular indexada com uma lista de vértices e uma tabela de faces, sem repetir coordenadas compartilhadas.
  • Orientar consistentemente os vértices das faces e explicar como essa orientação define frente, verso e vetor normal.
  • Diferenciar teste de profundidade e descarte de faces traseiras, diagnosticando o defeito visual produzido quando cada mecanismo é desligado.
  • Calcular a normal de uma face por produto vetorial e relacioná-la à orientação usada no descarte de faces traseiras.
  • Carregar um modelo de um arquivo Wavefront OBJ, convertendo seus índices para a mesma malha indexada usada nos exemplos escritos à mão.

Conteúdo​

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

Na Aula 06, a pirâmide já tinha cinco vértices e uma tabela indexada de faces. Cada face, porém, era usada apenas para percorrer seu contorno com GL_LINE_LOOP. Isso bastava para desenhar o wireframe, mas ainda não exigia decidir qual lado da face era exterior nem atribuir-lhe uma normal. Nesta aula, a mesma organização em VERTICES e FACES passa a produzir superfícies preenchidas; por isso, a ordem dos índices e as normais tornam-se essenciais.

Por que a ordem das faces muda em relação à Aula 06?

No wireframe, percorrer os vértices de uma face em sentido horário ou anti-horário produz as mesmas linhas. A tabela da Aula 06, portanto, podia ter as faces laterais orientadas para dentro sem alterar a imagem. Ao ativar o descarte de faces traseiras, isso deixa de ser indiferente: a tabela usada aqui inverte a ordem necessária para que todas as normais apontem para fora. Reaproveitar literalmente as faces da aula anterior faria as quatro laterais desaparecerem com o culling ativo.

Modelagem geométrica é o conjunto de métodos usados para descrever a forma e as propriedades geométricas de um objeto de maneira que um sistema gráfico possa armazená-lo, transformá-lo e exibi-lo. Não existe uma única representação melhor para tudo: a estrutura adequada depende das operações que o programa precisa realizar.

Uma caixa de representação geométrica se ramifica para quatro famílias: arestas ou wireframe, que preservam contorno e conectividade linear; fronteira ou malha e B-Rep, com faces, orientação e normais; construção por CSG e varredura, com operações e parâmetros; e ocupação por voxels e octree, com interior, colisão e volume.
Cada família de representação guarda uma coisa diferente sobre o objeto — e é essa escolha que decide quais perguntas o programa consegue responder sem recalcular tudo.

Formas de representação​

Cada família da Figura 1 é uma resposta diferente à pergunta "o que exatamente guardar sobre este objeto".

RepresentaçãoArmazena principalmentePonto forteLimitação típica
Wireframevértices e arestassimples, leve e rápidoimagem ambígua; não define superfícies nem interior
Malha poligonalvértices e facesadequada à rasterização e a diferentes níveis de detalheaproxima superfícies curvas; não garante, sozinha, um sólido válido
B-Repfronteira orientada e conectividadedescreve a casca de um sólidoexige consistência topológica
CSGárvore de primitivas e operações booleanasconstrução paramétrica por união, interseção e diferençaprecisa ser convertida para exibição convencional
Varreduraseção geratriz e trajetóriamodela extrusões e sólidos de revoluçãodepende da escolha da seção e do caminho
Voxels / octreecélulas ocupadas no espaçotestes de pertencimento e colisãoalta resolução pode consumir muita memória

Uma malha poligonal representa a superfície como um conjunto discreto de faces. Triângulos são preferidos porque três pontos não colineares sempre definem um plano, todo triângulo é convexo e o hardware gráfico os processa diretamente. Quadriláteros e polígonos maiores acabam triangulados antes da rasterização.

Superfície não é automaticamente sólido

Uma coleção de triângulos pode ter buracos, faces duplicadas, auto-interseções ou orientações incompatíveis. Para delimitar um sólido, a malha deve ser fechada e coerente: cada aresta interna é compartilhada pelas faces esperadas, e suas orientações concordam sobre qual lado é exterior.

Malha indexada: coordenadas uma vez, conectividade por índices​

Em uma malha indexada, VERTICES guarda as coordenadas e FACES guarda índices. Assim, faces vizinhas compartilham o mesmo vértice de verdade, em vez de manter cópias independentes da mesma coordenada.

VERTICES = [
(0.0, 1.5, 0.0), # 0: ápice
(-1.0, 0.0, -1.0), # 1..4: base
(1.0, 0.0, -1.0),
(1.0, 0.0, 1.0),
(-1.0, 0.0, 1.0),
]

FACES = [
(0, 2, 1), (0, 3, 2), (0, 4, 3), (0, 1, 4),
(1, 2, 3), (1, 3, 4), # base quadrada em dois triângulos
]

Os quatro triângulos laterais compartilham o ápice de índice 0. Se ele for alterado, todas as faces que o referenciam mudam juntas. Essa separação entre geometria (coordenadas) e topologia (quem se liga a quem) reduz redundância e evita fendas causadas por cópias divergentes.

A economia cresce depressa. Nesta pirâmide, seis faces guardam 18 índices em vez de 54 números soltos; no toro de 1024 faces do terceiro exemplo desta aula, são 3072 índices contra 9216 coordenadas — além das coordenadas armazenadas uma vez na lista de vértices. Essa comparação conta índices e coordenadas separadamente; a economia em bytes depende do tipo usado para cada valor.

Ambiente e execução dos exemplos​

Os arquivos completos são executáveis com Python 3.10+, PySide6, PyOpenGL e contexto compatível com OpenGL 2.1. Na raiz do repositório:

uv run aula-07-modelagem-geometrica/malha_indexada.py
uv run aula-07-modelagem-geometrica/visibilidade.py
uv run aula-07-modelagem-geometrica/carga_modelos.py

Para downloads isolados, instale as dependências com python -m pip install PySide6 PyOpenGL e execute python nome_do_arquivo.py. Guarde toro.obj junto de carga_modelos.py, ou passe seu caminho como argumento. As setas giram os objetos e Esc fecha as janelas. Os trechos curtos desta página são recortes para leitura.

Exemplo 1 — da estrutura de arame à malha​

📥 Baixe o arquivo completo: malha_indexada.py

malha_indexada.py (trecho principal)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if self.wireframe else GL_FILL)
glBegin(GL_TRIANGLES)
for face, cor in zip(FACES, CORES):
glColor3f(*cor)
for indice in face:
glVertex3f(*VERTICES[indice])
glEnd()

Pressione W para alternar entre wireframe e preenchimento. Pressione T para sobrepor a triangulação ao modelo preenchido. A geometria não muda: só muda a forma como as mesmas faces são rasterizadas.

A Figura 2 compara três execuções da mesma malha: somente arestas, faces preenchidas e preenchimento com arestas sobrepostas. O preenchimento oculta as arestas traseiras que eram visíveis no wireframe. T não triangula o objeto: ele apenas revela os triângulos que já estão em FACES. Para ver a diagonal da base, gire o modelo até enxergar sua parte inferior.

Três capturas da pirâmide: linhas claras com arestas traseiras visíveis, faces coloridas e as mesmas faces delimitadas por linhas escuras.
A representação muda, mas os cinco vértices e os seis triângulos permanecem iguais.

Orientação das faces e normais​

OpenGL considera, por padrão, frontal uma face cujos vértices aparecem em ordem anti-horária na tela. Ao construir uma malha fechada, organizamos as faces em sentido anti-horário quando vistas do exterior. Inverter dois índices troca a orientação do triângulo e, portanto, o sinal de sua normal.

Para uma face com vértices aa, bb e cc, dois vetores do plano são u=b−au=b-a e v=c−av=c-a. A normal é o produto vetorial normalizado:

n^=(b−a)×(c−a)∥(b−a)×(c−a)∥\hat{n}=\frac{(b-a)\times(c-a)}{\lVert(b-a)\times(c-a)\rVert}

A regra da mão direita conecta a ordem dos vértices à direção da normal: os dedos acompanham a→b→ca \rightarrow b \rightarrow c e o polegar aponta para n^\hat{n}. A Figura 3 mostra os dois casos lado a lado — a mesma geometria, mudando só a ordem dos índices na tabela de faces.

Dois quadros com o mesmo triângulo de vértices rotulados a, b e c. No quadro (a), rotulado 'ordem (a, b, c)', duas setas azuis saem de a — uma para b, rotulada u = b menos a, e outra para c, rotulada v = c menos a; uma seta circular cinza dentro do triângulo indica percurso anti-horário e uma seta verde grossa sai do centro do triângulo para cima e para a direita, rotulada n com acento circunflexo. No quadro (b), rotulado 'ordem (a, c, b)', as mesmas setas azuis aparecem com os rótulos u e v trocados, a seta circular cinza aponta no sentido horário e uma seta vermelha sai do centro do triângulo para baixo e para a esquerda, atravessando a face em traço tracejado e saindo em traço cheio, rotulada menos n com acento circunflexo.
A geometria é a mesma nos dois quadros: o que muda é a ordem em que os três índices são enviados. Trocar dois deles inverte o produto vetorial e transforma uma face frontal em traseira.

A normal calculada ajuda a conferir a orientação, mas glNormal3f não controla o descarte: a API usa a ordem dos vértices projetados e glFrontFace. Inverter só a normal afetará a iluminação na Aula 08, sem trocar frente e verso. Se o produto vetorial tiver comprimento zero, os pontos são colineares ou coincidentes: o triângulo é degenerado e não tem uma normal unitária definida.

Se uma face some assim que o descarte de faces traseiras é ligado, a primeira suspeita é a orientação invertida. A Aula 08 retoma as normais para calcular a interação entre luz e superfície.

Superfícies ocultas: dois mecanismos, duas perguntas​

Em uma projeção 3D, vários fragmentos podem disputar o mesmo pixel. O teste de profundidade mantém, para cada posição da tela, o fragmento mais próximo já encontrado. Por isso o quadro precisa limpar tanto a cor quanto a profundidade:

glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS) # padrão: aceita o fragmento mais próximo
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

O descarte de faces traseiras (back-face culling) elimina antes da rasterização faces orientadas para longe do observador:

glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
glFrontFace(GL_CCW)

glDepthFunc escolhe a comparação feita no depth buffer. O padrão GL_LESS aceita o novo fragmento quando sua profundidade é menor que a já armazenada. Outras comparações, como GL_LEQUAL, existem para casos específicos. De modo semelhante, glCullFace(GL_BACK) descarta o verso; GL_FRONT descarta a frente e GL_FRONT_AND_BACK descarta ambas — esta última opção é útil para diagnóstico, mas não deixa polígonos para rasterizar.

Eles não são substitutos, porque respondem a perguntas diferentes:

Teste de profundidadeDescarte de faces traseiras
Pergunta que respondequal fragmento está mais perto nesta posição da telaesta face está voltada para longe do observador
Dado que usaa profundidade de cada fragmentoa orientação dos vértices na tela
Quando agedepois da rasterização, fragmento a fragmentoantes da rasterização, face a face
Funciona entre objetos diferentessimnão
Precisa de malha fechada para executarnãonão; exige cuidado com superfícies abertas

Para as superfícies opacas desta aula, o teste de profundidade resolve a oclusão por pixel. O descarte economiza o processamento de faces traseiras, mas não compara distâncias entre superfícies. A fração descartada depende da geometria e da câmera; não é necessariamente metade das faces.

Numa malha fechada e coerente, vista de fora, retirar o descarte normalmente preserva a imagem se o teste de profundidade estiver ativo. Uma folha ou uma parede sem espessura, porém, pode precisar mostrar os dois lados. O descarte funciona também em malhas abertas; cabe ao programa decidir se ocultar o verso é adequado. Transparência requer cuidados além deste roteiro de superfícies opacas.

Esses não são os únicos algoritmos de visibilidade. O algoritmo do pintor ordena superfícies do fundo para a frente; BSP-trees particionam o espaço; Warnock subdivide a tela; e técnicas como A-buffer, scan-line Z-buffer, depth sort, células e portais atendem a diferentes tipos de cena. Nesta aula, o depth buffer e o back-face culling são o foco porque estão disponíveis diretamente no pipeline do OpenGL usado pelos exemplos.

Exemplo 2 — profundidade e descarte sob controle​

📥 Baixe o arquivo completo: visibilidade.py

O segundo exemplo separa os dois mecanismos de visibilidade:

TeclaAlternaExperimento
Dteste de profundidadegire o objeto e observe faces distantes cobrindo faces próximas quando desligado
Cdescarte de faces traseirascompare uma face correta e a mesma face invertida
Iinverte/restaura a terceira faceintroduz um erro de orientação sem editar o arquivo
Rrestaura estados e rotaçãoprofundidade ligada, descarte desligado, faces corretas
visibilidade.py (trecho principal)
glEnable(GL_DEPTH_TEST) if self.depth else glDisable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE) if self.culling else glDisable(GL_CULL_FACE)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

Experimento — isolar profundidade e orientação​

  1. Pressione R. Com descarte desligado, use D para comparar profundidade ligada e desligada no mesmo ângulo, como na Figura 4. Sem a comparação de profundidade, triângulos desenhados depois podem cobrir os mais próximos.
  2. Pressione R e C. Use I para inverter a terceira face, como na Figura 5. O defeito aparece sem desligar a profundidade. Desligue C: a face volta, embora seus índices continuem invertidos. R restaura tudo.

Na pirâmide convexa, ativar o descarte pode esconder o problema causado pela falta de profundidade: as faces traseiras deixam de ser desenhadas. Por isso a primeira comparação exige descarte desligado. Isso não torna os dois mecanismos equivalentes em cenas com vários objetos ou concavidades.

Duas pirâmides no mesmo ângulo, com descarte desligado: com profundidade, as faces próximas cobrem as distantes; sem profundidade, a sobreposição das cores passa a depender da ordem de desenho.
Desligar o teste de profundidade deixa a ordem de desenho decidir qual superfície aparece.
Três pirâmides com profundidade ativa: malha correta com descarte, uma face invertida com descarte e a mesma face invertida sem descarte. O painel central apresenta uma abertura.
Desligar o descarte revela a face invertida, mas só corrigir seus índices repara a orientação.

Carga de modelos: quando a malha não cabe no código-fonte​

Até aqui, toda malha foi digitada no próprio programa. Isso funciona enquanto o modelo tem cinco vértices; não funciona para nada que se pareça com um objeto real. Modelos são criados em ferramentas de modelagem, como Blender, 3ds Max e Maya, e exportados para formatos de troca, que o programa lê em tempo de execução.

O Wavefront OBJ é o formato de troca mais simples de ler: texto puro, uma diretiva por linha, sem cabeçalho binário. Ele guarda exatamente a estrutura que esta aula construiu à mão.

RegistroSignificadoCorresponde a
v x y zum vérticeum elemento de VERTICES
vn x y zuma normalum elemento de NORMAIS
vt u vuma coordenada de texturaassunto de uma aula posterior
f a b cuma face, por índicesum elemento de FACES

Geometria no OBJ, aparência no MTL​

O OBJ pode fazer referência a uma biblioteca de materiais com mtllib e selecionar um material para as faces seguintes com usemtl. A biblioteca fica em um arquivo MTL separado:

Registro MTLInformação
newmtlnome que poderá aparecer em usemtl
Ka, Kd, Kscoeficientes ambiente, difuso e especular
Nsexpoente de brilho especular
dopacidade do material
illummodelo de iluminação sugerido pelo arquivo
map_Kdarquivo de textura associado à cor difusa

Esses registros descrevem parâmetros; eles não iluminam o objeto sozinhos. O programa ainda precisa criar fontes, configurar o modelo de reflexão e aplicar os materiais durante o desenho. A Aula 08 faz essa ligação. O carregador desta aula lê apenas geometria e normais para manter o primeiro parser OBJ pequeno e auditável.

A pirâmide dos exemplos anteriores, escrita em OBJ, é literalmente a mesma malha indexada:

piramide.obj
# ápice e quatro vértices da base
v 0.0 1.5 0.0
v -1.0 0.0 -1.0
v 1.0 0.0 -1.0
v 1.0 0.0 1.0
v -1.0 0.0 1.0
# quatro faces laterais e a base triangulada
f 1 3 2
f 1 4 3
f 1 5 4
f 1 2 5
f 2 3 4
f 2 4 5

Quatro armadilhas de quem escreve o primeiro carregador​

  1. Os índices do OBJ começam em 1. A face f 1 3 2 é a tupla (0, 2, 1) em Python. Esquecer a conversão normalmente provoca IndexError assim que uma face referencia o último vértice, pois o índice len(vertices) fica fora da faixa válida da lista.
  2. Índices negativos contam de trás para frente. f -3 -2 -1 são os três últimos vértices lidos até ali. Exportadores usam essa forma para permitir concatenar arquivos.
  3. Faces podem ter mais de três vértices. Um polígono de nn lados vira n−2n-2 triângulos por triangulação em leque, todos compartilhando o primeiro vértice. O leque só é correto para polígonos convexos — para um polígono côncavo ele produz triângulos fora da face.
  4. Um vértice de face pode vir em quatro formas: v, v/vt, v//vn e v/vt/vn. Separar o campo por / e ler o que interessa é obrigatório; tratar 1//1 como um inteiro falha.

Normais: as do arquivo ou as da malha?​

Um OBJ pode ou não trazer vn. Quando não traz, as normais são calculadas da própria geometria, com o mesmo produto vetorial da seção anterior. Quando traz, vale usá-las: elas podem carregar uma intenção que a geometria sozinha não expressa — uma quina viva, por exemplo, é representada associando normais diferentes aos cantos das faces que compartilham a mesma posição, e a média das faces vizinhas apagaria justamente a quina.

Escala e posição do modelo carregado​

Um arquivo qualquer pode vir em qualquer unidade e em qualquer posição. Carregar sem ajustar é a causa mais comum do "abri e não apareceu nada": o modelo ficou grande ou pequeno demais para a escala da cena, ou está longe da origem e ficou fora do volume de visualização. O exemplo centraliza o modelo na origem e o escala pela maior dimensão de sua caixa envolvente antes de desenhar.

Exemplo 3 — lendo um modelo de arquivo​

📥 Baixe o programa: carga_modelos.py e o modelo: toro.obj

carga_modelos.py (trecho principal)
# Cada canto guarda índices independentes de posição e de normal.
pedacos = campo.split("/")
iv = _indice(pedacos[0], len(vertices))
inormal = None
if len(pedacos) == 3 and pedacos[2]:
inormal = _indice(pedacos[2], len(normais_lidas))
cantos.append((iv, inormal))

# Depois de ler os cantos: triangulação em leque.
for k in range(1, len(cantos) - 1):
triangulo = (cantos[0], cantos[k], cantos[k + 1])
faces.append(tuple(canto[0] for canto in triangulo))

No arquivo completo, as normais também são guardadas por canto de triângulo. Assim, duas faces podem compartilhar a posição e usar normais diferentes. Se algum canto não tiver normal, este carregador usa as normais calculadas para a malha inteira; ele não mistura as duas origens no mesmo desenho.

O toro.obj tem 512 vértices e 1024 faces — uma superfície fechada, curva e não convexa, e por isso um caso muito melhor que a pirâmide para observar a triangulação e a visibilidade de uma superfície com concavidades:

TeclaAlternaO que observar
Wwireframe / preenchidoa densidade da malha que produz a curva aparente
Cdescarte de faces traseirasem uma malha fechada e coerente, a imagem não muda
Nnormais do arquivo / calculadaso título informa a origem selecionada; sem vn, o terminal informa a indisponibilidade
0orientação inicialretorna ao mesmo enquadramento
L, Siluminação e tonalização opcionaisponte para a Aula 08; deixe a iluminação desligada nesta atividade

O modo inicial usa cores alternadas por triângulo, com iluminação desligada. A tecla N seleciona os dados, mas só altera a iluminação quando L e o modo suave (S) estão ativos. Isso evita interpretar uma ausência de mudança visual como falha na leitura de vn.

O programa também imprime, ao abrir, quantos índices a malha indexada usa contra quantas coordenadas seriam necessárias se cada face guardasse as suas.

O que este carregador não faz

Ele lê geometria e normais, e ignora materiais. Em um OBJ, mtllib aponta para um arquivo MTL e usemtl seleciona um material definido nele; registros como newmtl, Ka, Kd, Ks, Ns, d e illum descrevem nome, componentes de reflexão, brilho, opacidade e modelo de iluminação. O carregador também ignora coordenadas de textura, grupos e suavização. É o suficiente para esta aula; texturas e materiais entram mais adiante no curso. Um carregador de produção também verificaria faces degeneradas e orientações incoerentes. Este exemplo já rejeita índices zero e fora da faixa, mas não é um validador geral de OBJ.

Na Figura 6, o mesmo arquivo OBJ aparece em wireframe e com triângulos preenchidos. Para reproduzir o enquadramento, pressione 0 e depois a seta para baixo nove vezes; isso eleva a rotação X de −20° para 25° e deixa o furo visível. O furo central pertence à geometria do toro; ele não indica uma face perdida. Já uma falha localizada que aparece ao ativar C sugere uma orientação incorreta e deve ser investigada na tabela de faces.

Duas capturas do toro: malha de linhas com furo central e superfície preenchida por triângulos em dois tons de azul.
O carregamento reconstrói a conectividade do arquivo; preencher a malha torna sua superfície visível.

Da modelagem à visibilidade​

Fluxo iniciado em vértices e faces indexadas, seguido por orientação, transformação e câmera. O fluxo passa pelo descarte de faces, rasterização e teste de profundidade até a imagem.
A modelagem define os dados que todas as etapas seguintes consomem: cada ligação a partir das faces indexadas representa uma decisão tomada no modelo e cobrada depois, na imagem.

Sem faces não há superfície para rasterizar; sem orientação consistente o descarte falha; sem profundidade a visibilidade depende da ordem acidental do desenho. Como mostra a Figura 7, nenhuma dessas etapas consegue corrigir um problema introduzido na anterior.


Atividades de Laboratório​

Aquecimento​

  1. Em uma cópia de malha_indexada.py, altere apenas a altura do ápice em VERTICES[0]. Preveja quais quatro faces mudam; execute e confira. Restaure a coordenada antes da próxima comparação.
  2. Repita o experimento de profundidade e orientação com R, D, C e I. Explique por que desligar C faz a face reaparecer sem reparar os índices.
  3. Salve o piramide.obj mostrado no conteúdo e abra-o com python carga_modelos.py piramide.obj. Confirme seis triângulos e ausência de normais no arquivo pela mensagem do terminal.

Atividade — Octaedro como malha triangular indexada​

A Figura 8 compara o ponto de partida executável com a solução da atividade. As cores são escolhidas por face e não dependem de luz: nesta aula, elas servem para acompanhar os índices e diagnosticar a visibilidade.

À esquerda, seis pontos amarelos do arquivo-base; à direita, octaedro preenchido com cores distintas nas faces, no mesmo enquadramento.
A atividade transforma seis posições isoladas em uma superfície fechada de oito triângulos.

} />

📥 Baixe o ponto de partida: octaedro_base.py

Objetivo. Completar um octaedro alongado, formado por duas pirâmides de base quadrada, usando seis vértices e oito faces triangulares, calcular suas normais e renderizá-lo com visibilidade correta, como na Figura 8.

Roteiro​

Passo 1 — reconheça os vértices e conecte as faces (TODO 1). Execute o arquivo-base: aparecem seis pontos. Identifique topo, fundo e os quatro pontos do equador em VERTICES. Preencha FACES com quatro triângulos ligados ao topo e quatro ao fundo. Faces vizinhas devem percorrer a aresta compartilhada em sentidos opostos. O desenho ainda mostra pontos até o passo 4.

Passo 2 — calcule e confira as normais (TODO 2). Implemente normal_face com o produto vetorial normalizado. Para cada face, calcule também seu centro pela média dos três vértices: como este modelo é convexo e centrado na origem, o produto escalar entre centro e normal deve ser positivo. Valor negativo indica orientação para dentro. Esse teste não é um validador geral para modelos côncavos. A normal será usada na iluminação da Aula 08; aqui ela ajuda a conferir a geometria, sem precisar de GL_LIGHTING.

Passo 3 — configure a visibilidade (TODO 3). Habilite GL_DEPTH_TEST e GL_CULL_FACE em initializeGL. O programa já define descarte de faces traseiras e frente anti-horária. Não acrescente luzes nem materiais.

Passo 4 — desenhe a malha (TODO 4). Substitua GL_POINTS por GL_TRIANGLES. Percorra enumerate(FACES), use CORES[numero] com glColor3f e envie as três posições de cada face. Cada índice seleciona um elemento de VERTICES; não envie o índice como coordenada.

Passo 5 — introduza e corrija um defeito. Gire com esquerda/direita e confira a superfície fechada. Troque dois índices de uma face visível e observe seu desaparecimento com o descarte ativo. Restaure os índices e confirme a correção. Se a malha sumir inteira, confira o sentido das faces; se faces distantes cobrirem as próximas, confira o teste de profundidade.

Checklist de conclusão​

  • O modelo tem exatamente 6 vértices compartilhados e 8 faces triangulares.
  • Nenhuma face desaparece indevidamente durante uma volta completa.
  • As cores permitem distinguir as faces durante a rotação.
  • O teste de profundidade continua correto em qualquer ângulo.
  • normal_face não contém normais escritas manualmente.

Para responder​

  1. Quantas coordenadas seriam repetidas se cada uma das oito faces armazenasse seus três vértices separadamente?
  2. Por que trocar a ordem de dois índices inverte a normal?
  3. Por que GL_CULL_FACE não substitui GL_DEPTH_TEST?
  4. Exporte seu octaedro como um arquivo .obj e abra-o com carga_modelos.py. Que ajuste os índices exigiram?

Atividade — Auditoria de um modelo OBJ​

Objetivo. Diagnosticar problemas de orientação, triangulação e normais em um modelo carregado de arquivo, relacionando o texto do OBJ ao resultado renderizado.

Use carga_modelos.py e toro.obj. Crie uma cópia chamada toro_auditoria.obj para preservar o modelo original.

Roteiro​

Passo 1 — confira o original. Abra toro.obj e confirme 512 vértices e 1.024 triângulos no terminal. Pressione C: visto de fora, o modelo correto continua fechado. Deixe iluminação desligada e restaure o ângulo com 0.

Passo 2 — altere somente a orientação. Na cópia toro_auditoria.obj, escolha uma linha f e troque dois campos inteiros — por exemplo, a//na b//nb c//nc passa a a//na c//nc b//nb. Preserve a associação entre posição e normal de cada canto. Abra explicitamente a cópia:

python carga_modelos.py toro_auditoria.obj

Confira: ative C e gire até encontrar a face alterada. O triângulo pode estar no lado oculto no enquadramento inicial; ausência de defeito nesse ângulo não prova que a orientação está correta. Desligue C e compare. A contagem de vértices e triângulos não muda quando apenas a ordem é invertida.

Passo 3 — altere somente a disponibilidade de normais. Restaure a ordem da face. Remova as linhas vn e converta cada campo v//vn de f em apenas v. Apagar só as linhas vn deixa referências inválidas: nesse caso o carregador deve recusar o arquivo, em vez de adivinhar uma normal.

Confira: abra a cópia novamente e pressione N. O terminal informa que só existem normais calculadas. A geometria e as cores por triângulo continuam iguais. A ausência de mudança visual é esperada, pois a iluminação está desligada. O uso visual dessas normais é assunto da Aula 08.

Passo 4 — restaure e explique. Abra novamente o original e confirme as contagens e as normais disponíveis. Distinga defeito de orientação, referência inválida e ausência legítima de normais; cada um exige uma correção diferente.

Checklist de conclusão​

  • O modelo original abre com 512 vértices e 1.024 triângulos.
  • A face invertida produz um defeito reproduzível com o descarte ativo.
  • O modelo sem vn usa as normais calculadas a partir da geometria.
  • O arquivo original permanece inalterado.

Para responder​

  1. Por que desligar o descarte faz uma face invertida reaparecer sem corrigir a malha?
  2. Qual informação se perde ao remover vn?
  3. Que validação você adicionaria ao carregador para detectar a face invertida antes da renderização?

Exercícios (checkpoints)​

Questões dissertativas​

Q

Explique por que um wireframe de um cubo pode ser visualmente ambíguo e quais informações uma malha de faces acrescenta.

Q

Uma malha é desenhada corretamente sem culling, mas algumas faces somem quando GL_CULL_FACE é ativado. Qual é a causa mais provável e como diagnosticá-la?

Q

Um colega escreveu um carregador de OBJ e algumas faces aparecem ligadas aos vértices errados, às vezes sem mensagem de erro. Que falha na conversão de índices pode explicar o sintoma?

Q

Um modelo carregado de arquivo traz normais (vn). Em que situação vale usar essas normais em vez de recalculá-las pela média das faces vizinhas?

Questões objetivas​

Quiz5 questões

1. Qual estrutura evita repetir as coordenadas de um vértice compartilhado por várias faces?

  • a)Uma lista de vértices e uma tabela de faces por índices
  • b)Uma lista independente de coordenadas para cada face
  • c)Somente uma tabela de arestas
  • d)Um depth buffer maior
  • e)Uma normal diferente para cada pixel

2. Qual mecanismo decide qual fragmento está mais próximo em uma posição da tela?

  • a)Descarte de faces traseiras
  • b)GL_SMOOTH
  • c)Teste de profundidade
  • d)Normalização das normais
  • e)Triangulação em leque

3. Ao trocar dois vértices de um triângulo, o que acontece?

  • a)Sua área dobra
  • b)Sua orientação e sua normal se invertem
  • c)Ele deixa de ser planar
  • d)O depth buffer é desativado
  • e)Sua posição é transladada

4. Em um arquivo OBJ, a linha 'f 1 3 2' corresponde a qual tupla de índices em uma lista Python?

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

5. Um arquivo OBJ traz uma face convexa com cinco vértices. Quantos triângulos a triangulação em leque produz?

  • a)Dois
  • b)Três
  • c)Quatro
  • d)Cinco
  • e)Seis

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 — glNormal. Disponível em: registry.khronos.org/OpenGL-Refpages/gl2.1

Aprofundamento (opcionais)​