"""Projeção paralela ortográfica x projeção em perspectiva, lado a lado. Aula 05 — Câmera Sintética e Projeção em Perspectiva. Exemplo em Python com PySide6 e PyOpenGL (pipeline fixed-function). Execute com: python projecoes_comparacao.py A MESMA cena — um bloco perto da câmera e outro, do mesmo tamanho real, bem mais longe e ligeiramente ao lado para não ser encoberto — é desenhada duas vezes na mesma janela, em dois ``glViewport`` lado a lado: à esquerda com ``glOrtho``, à direita com ``gluPerspective``. Repare no que muda e no que não muda: * na projeção ORTOGRÁFICA os dois blocos aparecem do MESMO tamanho na tela, porque as projetantes são paralelas — a distância à câmera não entra na conta; * na projeção em PERSPECTIVA o bloco distante aparece MENOR, porque as projetantes convergem para o centro de projeção, como no olho humano. Teclas: Esc fecha Ideia para experimentar: mude ``DISTANCIA_BLOCO_LONGE`` e observe que, na metade ortográfica, o tamanho do bloco na tela não se altera — só a sua posição. """ from __future__ import annotations import sys from PySide6.QtCore import Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtWidgets import QApplication from PySide6.QtOpenGLWidgets import QOpenGLWidget from OpenGL.GL import * from OpenGL.GLU import gluPerspective, gluLookAt # Os dois blocos têm o MESMO tamanho real (semi-lado 0.6). Eles ficam em # profundidades diferentes e recebem um pequeno deslocamento lateral para # que o bloco próximo não encubra o distante. DISTANCIA_BLOCO_PERTO = -4.0 DISTANCIA_BLOCO_LONGE = -10.0 DESLOCAMENTO_X = 0.65 SEMI_LADO = 0.6 OLHO = (0.0, 0.0, 0.0) ALVO = (0.0, 0.0, -1.0) UP = (0.0, 1.0, 0.0) # Limites da window ortográfica — escolhidos para que o bloco perto # apareça do mesmo tamanho, em pixels, que na metade em perspectiva. ORTHO_META_ALTURA = 1.5 class ProjecoesComparacao(QOpenGLWidget): """Duas metades da mesma janela, duas projeções, uma cena.""" def __init__(self, parent=None): super().__init__(parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self._largura, self._altura = 1, 1 def initializeGL(self) -> None: glClearColor(0.10, 0.11, 0.13, 1.0) glEnable(GL_DEPTH_TEST) def resizeGL(self, w: int, h: int) -> None: dpr = self.devicePixelRatioF() self._largura = max(1, round(w * dpr)) self._altura = max(1, round(h * dpr)) def paintGL(self) -> None: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) meia_largura = self._largura // 2 self._desenhar_metade( x0=0, largura=meia_largura, altura=self._altura, ortografica=True ) self._desenhar_metade( x0=meia_largura, largura=self._largura - meia_largura, altura=self._altura, ortografica=False, ) def _desenhar_metade(self, x0, largura, altura, ortografica: bool) -> None: # Cada metade é seu próprio viewport: o recorte de tela e a # projeção são configurados de novo a cada quadro porque dependem # do tamanho da janela, como em zoom_pan.py (Aula 03). glViewport(x0, 0, largura, altura) aspecto = largura / float(altura) glMatrixMode(GL_PROJECTION) glLoadIdentity() if ortografica: meia_h = ORTHO_META_ALTURA meia_w = meia_h * aspecto glOrtho(-meia_w, meia_w, -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() @staticmethod def _desenhar_cena() -> None: ProjecoesComparacao._desenhar_bloco( -DESLOCAMENTO_X, 0.0, DISTANCIA_BLOCO_PERTO, SEMI_LADO, (0.86, 0.35, 0.35), ) ProjecoesComparacao._desenhar_bloco( DESLOCAMENTO_X, 0.0, DISTANCIA_BLOCO_LONGE, SEMI_LADO, (0.35, 0.65, 0.86), ) @staticmethod def _desenhar_bloco(cx: float, cy: float, cz: float, s: float, cor) -> None: x0, x1 = cx - s, cx + s y0, y1 = cy - s, cy + s z0, z1 = cz - s, cz + s faces = [ ((x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)), ((x1, y0, z0), (x0, y0, z0), (x0, y1, z0), (x1, y1, z0)), ((x0, y1, z1), (x1, y1, z1), (x1, y1, z0), (x0, y1, z0)), ((x0, y0, z0), (x1, y0, z0), (x1, y0, z1), (x0, y0, z1)), ((x1, y0, z1), (x1, y0, z0), (x1, y1, z0), (x1, y1, z1)), ((x0, y0, z0), (x0, y0, z1), (x0, y1, z1), (x0, y1, z0)), ] glColor3f(*cor) glBegin(GL_QUADS) for face in faces: for v in face: glVertex3f(*v) glEnd() def keyPressEvent(self, event) -> None: if event.key() == Qt.Key.Key_Escape: self.close() def main() -> int: fmt = QSurfaceFormat() fmt.setVersion(2, 1) fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile) fmt.setDepthBufferSize(24) QSurfaceFormat.setDefaultFormat(fmt) app = QApplication(sys.argv) janela = ProjecoesComparacao() janela.setWindowTitle("Ortográfica x perspectiva") janela.resize(900, 480) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())