Aula 02 — Primitivas de Desenho 2D
Objetivos
Ao final desta aula você deve ser capaz de:
- Escolher e aplicar a primitiva adequada (
GL_POINTS,GL_LINES,GL_LINE_STRIP,GL_LINE_LOOP,GL_TRIANGLES,GL_QUADS,GL_POLYGON) para desenhar uma forma 2D. - Prever que figura resulta de um mesmo conjunto de vértices quando se troca apenas o argumento de
glBegin. - Explicar o conceito de estado corrente no modo imediato e o efeito de
glColor3f,glPointSizeeglLineWidthsobre os vértices emitidos em seguida. - Definir o sistema de coordenadas do mundo com
gluOrtho2De corrigir a distorção causada pela proporção da janela (aspect ratio). - Justificar por que a projeção é definida em
resizeGLe não empaintGL. - Distinguir polígonos convexos de côncavos e explicar por que
GL_POLYGONexige convexidade. - Construir uma interface com
QMainWindow, menu e botões que controlem o conteúdo de um canvas OpenGL, respeitando os limites de onde é seguro chamar funçõesgl*. - Localizar na referência do OpenGL recursos não apresentados em aula — como
glLineStippleeglPolygonMode— e aplicá-los a partir de um enunciado.
Conteúdo
Retomando: o que muda em relação à Aula 01
Na Aula 01 desenhamos um triângulo e um cubo com um QOpenGLWidget mínimo.
Agora o foco sai do "como abrir uma janela" e vai para o que se pode desenhar
dentro dela. Três mudanças de estrutura acompanham essa virada:
| Aspecto | Aula 01 (helloworld2d.py) | Aula 02 |
|---|---|---|
| Onde a projeção é definida | paintGL (simplificação didática) | resizeGL — depende da janela |
| Sistema de coordenadas | (-1, 1, -1, 1) fixo | corrigido pelo aspect ratio — (-50, 50) nos exemplos de primitivas |
| Primitiva | Só GL_TRIANGLES | Todas as do modo imediato |
resizeGL?A matriz de projeção só precisa ser recalculada quando a janela muda de
tamanho — não a cada quadro. Recalculá-la dentro de paintGL funciona, mas
desperdiça trabalho e mistura responsabilidades. O resizeGL é chamado pelo Qt
uma vez antes do primeiro paintGL e depois a cada redimensionamento, o
que é exatamente o momento certo. A partir desta aula, use sempre esse padrão.
O Modo Imediato e o Estado Corrente
Todo desenho no pipeline fixed-function é uma sequência:
glBegin(<primitiva>)
# emite vértices com glVertex2f / glVertex3f
glEnd()
O OpenGL do modo imediato é uma máquina de estados. Comandos como
glColor3f, glPointSize e glLineWidth não desenham nada: eles alteram o
estado corrente, que passa a valer para todos os vértices emitidos a
partir dali, até ser alterado de novo — inclusive de um quadro para o
seguinte.
glBegin(GL_TRIANGLES)
glColor3f(1.0, 0.0, 0.0) # a partir daqui, vermelho
glVertex2f(-35, -14) # vermelho
glVertex2f(-21, 14) # vermelho
glVertex2f( -7, -14) # vermelho
glColor3f(0.0, 0.0, 1.0) # a partir daqui, azul
glVertex2f( 7, 14) # azul
glVertex2f( 21, -14) # azul
glVertex2f( 35, 14) # azul
glEnd()
Se cada vértice de um mesmo triângulo receber uma cor diferente, a GPU
interpola as cores no interior da face (o Gouraud shading visto na Aula
01). Uma única chamada de glColor3f antes do trio produz cor sólida.
glPointSize e glLineWidth ficam fora do glBeginNem todo comando é aceito entre glBegin e glEnd. glPointSize,
glLineWidth, glPolygonMode e as funções de matriz devem ser chamados
antes do glBegin. Chamá-los dentro gera o erro GL_INVALID_OPERATION —
que o OpenGL registra silenciosamente, sem interromper o programa.
As Primitivas do Modo Imediato
A tabela abaixo mostra, para uma mesma sequência de vértices , como cada primitiva os agrupa:
| Primitiva | Agrupamento | Resultado com 6 vértices |
|---|---|---|
GL_POINTS | cada vértice isolado | 6 pontos |
GL_LINES | pares: | 3 segmentos soltos |
GL_LINE_STRIP | 1 polilinha aberta (5 segmentos) | |
GL_LINE_LOOP | como STRIP + fecha | 1 contorno fechado (6 segmentos) |
GL_TRIANGLES | trios: | 2 triângulos |
GL_TRIANGLE_STRIP | cada novo vértice + os 2 anteriores | 4 triângulos |
GL_TRIANGLE_FAN | todos compartilham | 4 triângulos em leque |
GL_QUADS | quartetos: , ... | 1 quadrilátero (sobram 2 vértices) |
GL_POLYGON | todos os vértices | 1 polígono de 6 lados |
Vértices que sobram são descartados, sem aviso: com 5 vértices e
GL_TRIANGLES, sai 1 triângulo e 2 vértices ignorados. É uma fonte comum de
"desapareceu metade do desenho".
glBegin.O Sistema de Coordenadas do Mundo e o Aspect Ratio
gluOrtho2D(left, right, bottom, top) declara que retângulo do mundo é
visível na janela. Na Aula 01 usamos (-1, 1, -1, 1); nos exemplos de
primitivas desta aula a faixa vai de -50 a 50, mais confortável para
escrever coordenadas "em unidades de desenho" em vez de frações.
A escala em si é arbitrária: gui2d.py, o último exemplo da aula, mantém
a faixa de -1 a 1 e funciona igualmente bem — por isso seu triângulo é
escrito com vértices como -0.5. O que não é arbitrário é a correção
descrita a seguir, presente em todos os exemplos.
O problema: a janela quase nunca é quadrada. Se mapearmos um retângulo do mundo quadrado sobre uma janela de 600×400 pixels, tudo fica esticado na horizontal — um círculo vira elipse. A correção padrão é estender o retângulo do mundo no eixo mais longo da janela:
if w <= h:
gluOrtho2D(-50.0, 50.0, -50.0 * h / w, 50.0 * h / w)
else:
gluOrtho2D(-50.0 * w / h, 50.0 * w / h, -50.0, 50.0)
O efeito é que a razão entre unidade de mundo e pixel fica igual nos dois eixos: a forma é preservada e a janela mais larga simplesmente mostra mais mundo na horizontal, em vez de deformar o que já estava lá.
| Janela | gluOrtho2D resultante | Efeito |
|---|---|---|
| 400×400 | (-50, 50, -50, 50) | quadrado exato |
| 600×400 | (-75, 75, -50, 50) | mostra mais mundo na horizontal |
| 400×600 | (-50, 50, -75, 75) | mostra mais mundo na vertical |
Ao minimizar a janela, w ou h podem chegar a 0 — e as contas acima
dividiriam por zero. Por isso todos os exemplos começam o resizeGL com
w, h = max(1, w), max(1, h).
Exemplo 1 — GL_POINTS: um círculo de pontos
Os vértices são calculados por um laço que percorre a circunferência de raio 20 em passos de radianos — 14 pontos na volta completa.

pontos2d.py. Cada ponto é rasterizado como um quadrado de 5 pixels de lado — o arredondamento exigiria GL_POINT_SMOOTH.RAIO = 20.0
PASSO = math.pi / 7.0
def resizeGL(self, w: int, h: int) -> None:
w, h = max(1, w), max(1, h)
# 1) Onde desenhar, em pixels.
glViewport(0, 0, w, h)
# 2) Que retângulo do mundo é visível.
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
if w <= h:
gluOrtho2D(-50.0, 50.0, -50.0 * h / w, 50.0 * h / w)
else:
gluOrtho2D(-50.0 * w / h, 50.0 * w / h, -50.0, 50.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def paintGL(self) -> None:
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(0.92, 0.92, 0.95)
glPointSize(5.0) # lado do ponto, em PIXELS
glBegin(GL_POINTS)
ang = 0.0
while ang < 2 * math.pi:
glVertex2f(RAIO * math.cos(ang), RAIO * math.sin(ang))
ang += PASSO
glEnd()
O que observar. glPointSize é medido em pixels, não em unidades do
mundo — por isso os pontos não crescem quando a janela aumenta, enquanto o
raio do círculo cresce. Essa distinção entre grandezas do mundo (afetadas pela
projeção) e grandezas de rasterização (em pixels) vale também para
glLineWidth.
Por padrão, um ponto é rasterizado como um quadrado. Para obtê-lo
arredondado é preciso glEnable(GL_POINT_SMOOTH) — e o resultado depende do
suporte do driver.
Exemplo 2 — GL_LINES, GL_LINE_STRIP e GL_LINE_LOOP
Este exemplo usa exatamente os mesmos vértices do anterior. A única
mudança é o argumento de glBegin — e é essa comparação que interessa.

linhas2d.py com GL_LINES: os mesmos 14 vértices da Figura 2, agora consumidos aos pares — daí os sete vãos.A Figura 3 mostra o resultado com GL_LINES: os 14 vértices foram consumidos
aos pares, e por isso metade das ligações simplesmente não existe.
Trocando a constante, os mesmos vértices fecham a figura.
# Experimente: GL_LINES | GL_LINE_STRIP | GL_LINE_LOOP
PRIMITIVA = GL_LINES
def paintGL(self) -> None:
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(0.92, 0.92, 0.95)
glLineWidth(3.0)
glBegin(PRIMITIVA)
ang = 0.0
while ang < 2 * math.pi:
glVertex2f(RAIO * math.cos(ang), RAIO * math.sin(ang))
ang += PASSO
glEnd()
O que observar ao trocar a constante:
São 14 vértices — o mesmo laço do exemplo anterior:
| Primitiva | Figura resultante | Segmentos |
|---|---|---|
GL_LINES | 7 traços soltos, como uma estrela incompleta — os vértices são consumidos aos pares | 7 |
GL_LINE_STRIP | uma polilinha aberta: falta o traço que voltaria ao primeiro vértice | 13 |
GL_LINE_LOOP | os mesmos vértices, agora fechados — a melhor aproximação de círculo | 14 |
Aumentar o número de vértices (reduzindo PASSO) aproxima cada vez mais a
figura de um círculo. Esse é o princípio geral da computação gráfica: curvas
são aproximadas por segmentos de reta; o que muda é quantos.
Exemplo 3 — GL_TRIANGLES: a primitiva fundamental

triangulos2d.py. Seis vértices, dois triângulos independentes — e uma cor por trio, sem o degradê da Aula 01.Os seis vértices viraram dois triângulos — e como cada trio recebeu uma
única chamada de glColor3f, cada face saiu com cor sólida, sem o degradê da
Aula 01.
glBegin(GL_TRIANGLES)
glColor3f(0.95, 0.30, 0.30) # 1o. triângulo, vermelho
glVertex2f(-35.0, -14.0) # v0
glVertex2f(-21.0, 14.0) # v1
glVertex2f( -7.0, -14.0) # v2
glColor3f(0.35, 0.55, 0.95) # 2o. triângulo, azul
glVertex2f( 7.0, 14.0) # v3
glVertex2f( 21.0, -14.0) # v4
glVertex2f( 35.0, 14.0) # v5
glEnd()
O triângulo é a primitiva fundamental do hardware gráfico: é a menor figura que define um plano de forma não ambígua, é sempre convexa e é sempre planar — três propriedades que simplificam enormemente a rasterização. Toda malha 3D, por mais complexa, acaba decomposta em triângulos antes de chegar à GPU.
GL_TRIANGLE_STRIP e GL_TRIANGLE_FAN existem por eficiência: em uma
malha, vértices vizinhos são compartilhados por vários triângulos. Um STRIP
com vértices produz triângulos, contra do GL_TRIANGLES — ou
seja, transmite menos dados para o mesmo resultado.
Exemplo 4 — GL_QUADS e a ordem dos vértices

quadrados2d.py com WIREFRAME = False. Os oito vértices foram percorridos como contorno; inverter dois deles produz a gravata-borboleta descrita abaixo.glBegin(GL_QUADS)
glVertex2f(-45.0, -15.0) # v0, início do 1o. quadrilátero
glVertex2f(-45.0, 15.0) # v1
glVertex2f(-15.0, 15.0) # v2
glVertex2f(-15.0, -15.0) # v3
glVertex2f( 15.0, -15.0) # v4, início do 2o. quadrilátero
glVertex2f( 15.0, 15.0) # v5
glVertex2f( 45.0, 15.0) # v6
glVertex2f( 45.0, -15.0) # v7
glEnd()
Os quatro vértices devem ser dados percorrendo o contorno — horário ou
anti-horário, tanto faz, desde que seja um percurso. Se você trocar v2 por
v3 na ordem, o quadrilátero sai cruzado, em forma de gravata-borboleta.
Para conferir visualmente como os vértices foram ligados, o exemplo traz uma
constante WIREFRAME; com ela ativada:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) # desenha só o contorno
GL_QUADS no OpenGL modernoGL_QUADS e GL_POLYGON foram removidos do core profile 3.2+. Usamos
aqui porque estamos no perfil de compatibilidade (2.1), mas vale saber: em
OpenGL moderno, um quadrilátero é sempre montado como dois triângulos.
Exemplo 5 — GL_POLYGON e a restrição de convexidade

poligonos2d.py com a lista VERTICES. É o caso que GL_POLYGON garante: polígono convexo e simples.Os cinco vértices de VERTICES formam um pentágono convexo — e o
preenchimento sai correto. Trocar a lista pela CONCAVO é o que revela o
problema descrito a seguir.
VERTICES = [
(-20.0, -35.0),
(-35.0, 5.0),
( 0.0, 35.0),
( 35.0, 5.0),
( 20.0, -35.0),
]
glBegin(GL_POLYGON)
for x, y in VERTICES:
glVertex2f(x, y)
glEnd()
Um polígono é convexo quando, para quaisquer dois pontos internos, o segmento que os une fica inteiramente dentro da figura — de forma equivalente, quando todos os seus ângulos internos são menores que 180°. Uma estrela e um "L" são côncavos.
GL_POLYGON só garante resultado correto para polígonos convexos e simples
(sem lados que se cruzam). Com um polígono côncavo, o comportamento é
indefinido: normalmente a GPU preenche a região errada, sem emitir erro
algum. O arquivo do exemplo traz uma lista CONCAVO (uma estrela de 4 pontas)
justamente para você observar isso na prática.
Como desenhar formas côncavas então? Decompondo-as em triângulos —
triangulação. Manualmente, para formas simples; ou via gluTess* (o
tessellator da GLU), para o caso geral. É o que qualquer motor gráfico faz
internamente.
Elementos de Interface com PySide6
Até aqui, todas as janelas foram um QOpenGLWidget solto. Aplicações reais
precisam de menus, botões e barra de status ao redor do canvas. O Qt
resolve isso com a QMainWindow, que já traz as áreas prontas:
QMainWindow. O canvas OpenGL entra como um widget qualquer, dentro dos layouts do widget central.Widgets e layouts. No Qt, você não posiciona widgets por coordenadas: você
os coloca em layouts (QVBoxLayout, vertical; QHBoxLayout, horizontal),
que recalculam posições e tamanhos automaticamente quando a janela muda.
Layouts podem ser aninhados: um QHBoxLayout de botões dentro de um
QVBoxLayout que também contém o canvas.
Sinais e slots. A interação em Qt funciona pelo mecanismo de signals and
slots: um widget emite um sinal quando algo acontece (clicked,
triggered) e você conecta esse sinal a uma função:
botao_fechar = QPushButton("Fechar")
botao_fechar.clicked.connect(self.close)
Quem emite não precisa saber quem recebe: o botão apenas anuncia que foi clicado, e o Qt encaminha a chamada para todos os slots conectados. Um mesmo sinal pode alimentar vários slots, e o mesmo slot pode atender a vários sinais — é o que permite que o menu e um atalho de teclado disparem exatamente a mesma ação.
gl*Nunca chame funções gl* dentro de um slot de menu ou de botão. O
contexto OpenGL só está garantidamente ativo dentro de initializeGL,
resizeGL e paintGL. Um slot deve:
- alterar um atributo do canvas (o estado da cena);
- chamar
update(), que agenda um novopaintGL.
def definir_cor(self, r, g, b):
self.cor = (r, g, b) # só muda o estado
self.update() # o Qt chama paintGL no momento certo
Note que update() não desenha imediatamente: ele marca o widget como
"sujo" e devolve o controle. Chamar gl* fora da hora costuma "funcionar por
acaso" em uma máquina e falhar silenciosamente em outra.
O exemplo completo traz um menu Cor com as três cores (atalhos R, G e
B), um separador e um item Sair (Ctrl+Q); um botão Fechar; e uma
barra de status que reporta as coordenadas do clique no canvas. A tecla Esc
também fecha a janela — é para isso que serve o setFocusPolicy do construtor,
sem o qual o canvas nunca receberia o evento de teclado.

gui2d.py. O canvas OpenGL é o widget do meio; a barra de botões e a barra de status são Qt puro.O canvas OpenGL é apenas um widget entre outros dentro da QMainWindow:
o botão, o menu e a barra de status são widgets Qt comuns, e nenhum deles
desenha na cena — todos apenas alteram o estado do canvas e pedem um novo
paintGL.
📥 gui2d.py
class Canvas(QOpenGLWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.cor = (0.95, 0.30, 0.30) # estado da cena
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) # para receber teclado
def definir_cor(self, r, g, b):
self.cor = (r, g, b)
self.update() # agenda o paintGL
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(*self.cor)
glBegin(GL_TRIANGLES)
glVertex2f(-0.5, -0.5)
glVertex2f( 0.0, 0.5)
glVertex2f( 0.5, -0.5)
glEnd()
class JanelaPrincipal(QMainWindow):
def __init__(self):
super().__init__()
self.canvas = Canvas(self)
botao_fechar = QPushButton("Fechar")
botao_fechar.clicked.connect(self.close)
linha_botoes = QHBoxLayout()
linha_botoes.addWidget(botao_fechar)
linha_botoes.addStretch()
coluna = QVBoxLayout()
coluna.addLayout(linha_botoes)
coluna.addWidget(self.canvas, stretch=1)
central = QWidget()
central.setLayout(coluna)
self.setCentralWidget(central)
menu_cor = self.menuBar().addMenu("&Cor")
for rotulo, atalho, rgb in (
("&Vermelho", "R", (0.95, 0.30, 0.30)),
("V&erde", "G", (0.30, 0.95, 0.30)),
("&Azul", "B", (0.35, 0.55, 0.95)),
):
acao = QAction(rotulo, self)
acao.setShortcut(atalho)
# o default c=rgb "congela" o valor desta iteração
acao.triggered.connect(lambda _=False, c=rgb: self._trocar_cor(c))
menu_cor.addAction(acao)
No arquivo, a montagem do menu está separada em um método _montar_menu e a
troca de cor em _trocar_cor — aqui foram reunidas para caber em um trecho só.
_trocar_cor faz duas coisas: chama canvas.definir_cor(...) e atualiza a
barra de status; note que nenhuma delas é uma função gl*.
Eventos de mouse e teclado. Sinais e slots servem para ações de widgets (um botão clicado, um item de menu acionado). Já os eventos de entrada bruta chegam por sobrescrita de métodos da classe base:
| Método | Quando o Qt chama | Observação |
|---|---|---|
paintGL(self) | a cada repintar | único lugar onde se desenha |
resizeGL(self, w, h) | ao redimensionar | viewport e projeção |
mousePressEvent(self, event) | ao pressionar um botão do mouse | event.position() dá a posição no widget |
keyPressEvent(self, event) | ao pressionar uma tecla | exige setFocusPolicy(Qt.FocusPolicy.StrongFocus) |
O detalhe fácil de esquecer é o foco: um QOpenGLWidget não recebe eventos
de teclado até que uma política de foco seja definida — por isso o Canvas
chama setFocusPolicy no construtor.
O Qt entrega a posição do mouse com a origem no canto superior esquerdo, com o eixo Y crescendo para baixo. O OpenGL usa o canto inferior esquerdo, com Y crescendo para cima. Para converter:
y_opengl = self.height() - y_qt
O mousePressEvent do gui2d.py mostra as duas convenções lado a lado na
barra de status — clique perto do topo do canvas e compare os dois valores de
y. Esquecer essa conversão é a causa clássica de "o objeto aparece espelhado
verticalmente em relação ao clique".
Atividades de Laboratório
Aquecimento
Antes das atividades, use os exemplos da aula para experimentar:
- Em
linhas2d.py, troquePRIMITIVApelas três constantes e descreva a diferença; depois reduzaPASSOaté a figura parecer um círculo. - Em
pontos2d.py, redimensione a janela e explique por que os pontos não mudam de tamanho enquanto o círculo muda. - Em
quadrados2d.py, ativeWIREFRAMEe depois inverta a ordem de dois vértices de um quadrilátero — explique a figura resultante. - Em
poligonos2d.py, troqueVERTICESporCONCAVOe explique o preenchimento incorreto. Em seguida, desenhe a mesma estrela corretamente comGL_TRIANGLE_FAN— de que ponto o leque precisa partir, e o que isso obriga a acrescentar à lista de vértices? - Em
gui2d.py, acrescente um menu "Primitiva" que alterne entreGL_TRIANGLES,GL_LINE_LOOPeGL_POINTSno mesmo conjunto de vértices.
As duas atividades
São duas atividades independentes, cada uma com seu próprio arquivo de ponto de partida. Ambas usam Python com PySide6 e PyOpenGL, no pipeline fixed-function. Regras que valem para as duas:
- Trabalhe a partir do arquivo de ponto de partida; não comece do zero nem cole código pronto de outra fonte.
- Todo
TODOdo arquivo corresponde a um passo do roteiro. - Comente as decisões não óbvias no próprio código. Quem lê seu código depois precisa entender por que você escolheu aquela primitiva, não só o que ela desenha.
Atividade 1 — Mira 2D
Objetivo. Reproduzir a mira da Figura 9 usando exclusivamente primitivas de linha, sem transformações geométricas — apenas coordenadas escritas diretamente.
Ponto de partida. O programa já abre a janela e configura a projeção; o
paintGL está vazio.
Roteiro
Passo 1 — Entenda o espaço antes de desenhar.
Leia o resizeGL do arquivo base e determine, no papel, qual faixa de
coordenadas está visível quando a janela tem 640×480. Anote os limites em X e
em Y. Você vai precisar deles para dimensionar a mira — e para saber se um
vértice caiu fora da tela.
Passo 2 — Estabeleça o fundo. Ajuste a cor de fundo para o amarelo da referência. Rode o programa: se a janela abrir amarela, seu ciclo de vida está funcionando e o resto é desenho.
Passo 3 — Comece pelo elemento mais simples. Desenhe apenas o quadrado interno, com contorno contínuo. Escolha a primitiva que liga os vértices em sequência e fecha o contorno sozinho — consulte a tabela de primitivas desta aula. Defina cor e espessura antes de começar a emitir vértices.
Ao rodar, o quadrado apareceu do tamanho que você esperava? Se ficou grande ou minúsculo demais, releia o que você anotou no Passo 1: o problema está nas coordenadas ou no seu entendimento do espaço visível?
Passo 4 — Acrescente o quadrado externo, tracejado.
O tracejado não é uma primitiva diferente: é um estado do OpenGL. Procure
na referência do OpenGL 2.1 por GL_LINE_STIPPLE, glLineStipple e o par
glEnable/glDisable.
glLineStipple(fator, padrao) recebe um padrão de 16 bits: cada bit em 1
desenha, cada bit em 0 pula; o fator multiplica a largura de cada bit.
Antes de testar: desenhe no papel o traço que
0xAAAAproduz com fator 1 e com fator 4. Depois confira na tela se sua previsão estava certa.
Passo 5 — Descubra o efeito colateral.
Rode o programa com o quadrado externo tracejado desenhado antes do
interno, sem nenhum glDisable. O que aconteceu com o quadrado interno?
Explique o resultado usando o conceito de estado corrente e corrija.
Passo 6 — Desenhe os quatro braços. Repare bem na referência: os braços não tocam o centro — há um vazio ao redor da origem. Cada braço é um segmento com início e fim.
Desafio: os quatro braços cabem em um único bloco
glBegin/glEnd. Qual primitiva permite isso, e quantos vértices ela vai consumir? Faça primeiro com quatro blocos separados, depois reduza a um só e verifique que o resultado é idêntico.
Passo 7 — Teste o redimensionamento. Estique a janela na horizontal e depois na vertical. A mira continua com proporções corretas — quadrados quadrados, braços simétricos? Explique, em um comentário no código, qual trecho do programa garante isso.
Checklist de conclusão
- A janela abre com o fundo amarelo e a mira centrada.
- O quadrado externo é tracejado e o interno é contínuo.
- Existe vazio entre o centro e o início de cada braço.
- Os braços ultrapassam as bordas do quadrado externo.
- Redimensionar a janela não deforma a mira.
- Nenhuma chamada inválida entre
glBegineglEnd.
Para responder
Registre as respostas em um comentário no topo do arquivo:
- Quantos blocos
glBegin/glEndseu programa usa? Seria possível usar menos? E menos seria melhor? Justifique. - Se você trocasse a primitiva dos quadrados por
GL_POLYGON, o que mudaria na tela? E se trocasse porGL_LINE_STRIP? - A espessura do traço está em pixels. Ao maximizar a janela, a mira cresce e a espessura não. Isso é um defeito ou o comportamento esperado?
Atividade 2 — Preenchido × Wireframe
Objetivo. Acrescentar um botão que alterne a mesma geometria entre preenchida e apenas com suas arestas, sem alterar nenhum vértice.
Ponto de partida. Janela com o triângulo preenchido e uma barra de botões vazia.
Roteiro
Passo 1 — Coloque o botão na tela. Crie o botão e adicione-o ao layout da barra. Rode: ele deve aparecer, mesmo sem fazer nada ainda. Escolha um rótulo que descreva o que vai acontecer ao ser clicado, não o estado atual.
Passo 2 — Decida onde mora o estado. A cena tem dois modos possíveis. Esse dado pertence ao canvas ou à janela? Antes de escrever, responda: se amanhã existirem dois canvas na mesma janela, sua escolha ainda funciona?
Passo 3 — Ligue o botão ao estado.
Conecte o sinal do botão a um método que inverta o modo. Consulte gui2d.py
para lembrar qual sinal um QPushButton emite.
Cuidado com a armadilha central desta atividade: esse método não pode chamar funções
gl*nempaintGL()diretamente. Reveja a seção "onde é seguro chamargl*" desta aula e use a chamada correta.
Passo 4 — Aplique o modo no desenho.
Em paintGL, informe ao OpenGL como os polígonos devem ser rasterizados.
Procure por glPolygonMode(face, modo) na referência do OpenGL 2.1 e
descubra: que valores o parâmetro face aceita e qual serve aqui? Que
constante corresponde a cada um dos dois modos?
Passo 5 — Teste o estado que ficou para trás.
Implemente só o ramo do wireframe (sem o else). Clique para ativar e
clique de novo para desativar. O triângulo voltou a ser preenchido? Explique o
que você observou e conserte.
Passo 6 — Feche o ciclo com o usuário. O rótulo escolhido no Passo 1 anuncia o que vai acontecer — logo, ele precisa mudar a cada clique. Faça isso, e use a barra de status para informar o modo atual. Depois pergunte-se: se amanhã você acrescentar um item de menu que alterna o mesmo modo, o rótulo continua correto? Se não, o que precisa mudar na estrutura do código?
Checklist de conclusão
- O botão alterna entre os dois modos, indefinidamente.
- Nenhuma função
gl*é chamada fora deinitializeGL,resizeGLoupaintGL. - Os vértices do triângulo não foram alterados.
- O rótulo do botão nunca contradiz o que está na tela.
Para responder
Registre as respostas em um comentário no topo do arquivo:
- O que acontece se você aplicar o botão desta atividade sobre a mira da
Atividade 1? Teste e explique o resultado. O que isso revela sobre o
alcance de
glPolygonMode? - Qual a diferença entre alternar o modo com
glPolygonModee alternar trocando a primitiva deGL_TRIANGLESparaGL_LINE_LOOP? Existe alguma situação em que as duas abordagens produzem resultados diferentes? - Por que
glPolygonModeé chamado dentro depaintGL, e não uma única vez eminitializeGL?
Exercícios
Questões dissertativas
Explique o que significa dizer que o OpenGL do modo imediato é uma 'máquina de estados'. Use glColor3f como exemplo.
Dado o mesmo conjunto de 6 vértices, descreva a figura resultante com GL_LINES, GL_LINE_STRIP e GL_LINE_LOOP. Quantos segmentos cada uma gera?
Por que a definição da projeção com gluOrtho2D precisa levar em conta a proporção (aspect ratio) da janela? O que acontece se ela for ignorada?
O que caracteriza um polígono convexo? Por que GL_POLYGON não funciona corretamente com polígonos côncavos, e como se desenha uma forma côncava em OpenGL?
Um colega escreveu um slot de menu que chama glColor3f e glBegin diretamente para trocar a cor do desenho, e o programa às vezes não redesenha nada. Explique o erro e a forma correta de implementar.
Questões objetivas
1. Um trecho emite 8 vértices entre glBegin(GL_TRIANGLES) e glEnd(). Quantos triângulos são desenhados?
- a)8 triângulos
- b)6 triângulos
- c)2 triângulos, e 2 vértices são descartados
- d)3 triângulos, com o último incompleto
- e)Nenhum — o OpenGL gera erro por número inválido de vértices
2. Por que glPointSize e glLineWidth devem ser chamados FORA do bloco glBegin/glEnd?
- a)Porque só funcionam depois de glEnd
- b)Porque entre glBegin e glEnd apenas alguns comandos são válidos; os demais geram GL_INVALID_OPERATION
- c)Porque eles alteram a matriz de projeção
- d)Porque exigem que o depth test esteja habilitado
- e)Não há restrição: podem ser chamados em qualquer lugar
3. Em uma janela de 800×400 pixels, qual chamada preserva a proporção das formas mantendo 50 unidades de mundo no eixo mais curto?
- a)gluOrtho2D(-50, 50, -50, 50)
- b)gluOrtho2D(-100, 100, -50, 50)
- c)gluOrtho2D(-50, 50, -100, 100)
- d)gluOrtho2D(-25, 25, -50, 50)
- e)gluOrtho2D(0, 800, 0, 400)
4. Qual é a forma correta de fazer um item de menu trocar a cor do desenho em um QOpenGLWidget?
- a)Chamar glColor3f diretamente dentro do slot conectado ao menu
- b)Alterar um atributo do canvas com a nova cor e chamar update()
- c)Recriar o QOpenGLWidget a cada troca de cor
- d)Chamar paintGL() diretamente a partir do slot
- e)Chamar glClearColor no slot e aguardar o próximo evento de resize
5. O mouse é clicado no ponto (100, 30) de um canvas com 400 pixels de altura. Qual a coordenada Y equivalente na convenção do OpenGL?
- a)30
- b)130
- c)370
- d)400
- e)-30
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)
- The Khronos Group. OpenGL 2.1 Reference Pages — glBegin. 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)
- HEARN, D.; BAKER, M. P.; CARITHERS, W. R. Computer Graphics with OpenGL. 4. ed. Pearson Prentice Hall, 2011. (004.92 H436co)
- Qt for Python (PySide6). Layout Management e QMainWindow. Disponível em: doc.qt.io/qtforpython-6
- Qt for Python (PySide6). Signals and Slots. Disponível em: doc.qt.io/qtforpython-6/tutorials/basictutorial/signals_and_slots.html