"""Hello World 3D — o mínimo para uma janela OpenGL com perspectiva e luz. Ponto de partida para o exercício prático. É a versão enxuta do ``pygl/viewer3d`` (sem câmera orbital, carregamento de OBJ, texturas nem animação): o esqueleto de um ``QOpenGLWidget`` que abre uma janela, define a projeção em **perspectiva** com ``gluPerspective``, liga o **teste de profundidade** e uma **luz**, e desenha um cubo colorido face a face. Execute com: python helloworld3d.py Diferenças em relação ao 2D (compare com ``helloworld2d.py``): * pede-se **buffer de profundidade** (24 bits) e habilita-se ``GL_DEPTH_TEST`` para resolver a oclusão entre as faces; * a projeção é em **perspectiva** (``gluPerspective``), não ortográfica; * há **iluminação** — cada vértice precisa de uma **normal** (``glNormal3f``). Ideias para o exercício: gire o cubo variando os ângulos de ``glRotatef``, troque as cores das faces, adicione mais objetos com ``glPushMatrix`` / ``glPopMatrix``, ou anime a rotação com um ``QTimer``. """ from __future__ import annotations import sys 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 8 vértices do cubo, de -1 a 1 em cada eixo. _VERTS = [ (-1, -1, 1), (-1, 1, 1), ( 1, 1, 1), ( 1, -1, 1), (-1, -1, -1), (-1, 1, -1), ( 1, 1, -1), ( 1, -1, -1), ] # Cada face: (cor RGB, normal, índices dos 4 vértices em sentido anti-horário). _FACES = [ ((1.0, 0.0, 0.0), ( 1.0, 0.0, 0.0), (2, 3, 7, 6)), # +X vermelho ((0.0, 1.0, 0.0), ( 0.0, 1.0, 0.0), (1, 2, 6, 5)), # +Y verde ((0.0, 0.0, 1.0), ( 0.0, 0.0, 1.0), (0, 3, 2, 1)), # +Z azul ((1.0, 0.0, 1.0), ( 0.0, -1.0, 0.0), (3, 0, 4, 7)), # -Y magenta ((1.0, 1.0, 0.0), ( 0.0, 0.0, -1.0), (4, 5, 6, 7)), # -Z amarelo ((0.0, 1.0, 1.0), (-1.0, 0.0, 0.0), (5, 4, 0, 1)), # -X ciano ] class HelloWorld3D(QOpenGLWidget): """Viewport 3D em perspectiva que desenha um cubo iluminado.""" def initializeGL(self) -> None: glClearColor(0.12, 0.12, 0.14, 1.0) glEnable(GL_DEPTH_TEST) # oclusão: faces à frente escondem as de trás glEnable(GL_LIGHTING) # ativa o modelo de iluminação glEnable(GL_LIGHT0) # com uma fonte de luz (LIGHT0) # A cor definida por glColor* passa a valer como material do objeto. glEnable(GL_COLOR_MATERIAL) def resizeGL(self, w: int, h: int) -> None: glViewport(0, 0, w, max(1, h)) # Projeção em perspectiva: campo de visão 45°, proporção da janela, # planos de corte próximo (0.1) e distante (100). glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, w / float(max(1, h)), 0.1, 100.0) glMatrixMode(GL_MODELVIEW) def paintGL(self) -> None: # Limpa cor E profundidade (o buffer de depth também precisa ser limpo). glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_MODELVIEW) glLoadIdentity() # Câmera: posiciona o olho em (4, 3, 5) olhando para a origem. gluLookAt(4.0, 3.0, 5.0, # posição do olho 0.0, 0.0, 0.0, # ponto observado 0.0, 1.0, 0.0) # vetor "para cima" # Uma leve rotação para o cubo não aparecer de frente (mostra o 3D). glRotatef(20.0, 1.0, 0.0, 0.0) glRotatef(30.0, 0.0, 1.0, 0.0) self._draw_cube() def _draw_cube(self) -> None: """Desenha o cubo face a face, com uma cor e uma normal por face.""" glBegin(GL_QUADS) for color, normal, face in _FACES: glColor3f(*color) glNormal3f(*normal) # normal usada pela iluminação for vi in face: glVertex3f(*_VERTS[vi]) glEnd() def main() -> int: # O contexto 3D precisa de buffer de profundidade (24 bits). fmt = QSurfaceFormat() fmt.setVersion(2, 1) # OpenGL 2.1 (fixed-function) # NoProfile é o perfil de compatibilidade, que mantém o fixed-function. fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile) fmt.setDepthBufferSize(24) QSurfaceFormat.setDefaultFormat(fmt) app = QApplication(sys.argv) window = HelloWorld3D() window.setWindowTitle("Hello World 3D — gluPerspective + luz") window.resize(640, 640) window.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())