"""Laboratório de fontes, reflexão e tonalização no OpenGL 2.1. Teclas: 1 pontual, 2 direcional, 3 spot; A ambiente, D difusa, E especular; S flat/Gouraud; N normais face/vértice; I inverte normais; R reinicia; O abertura spot; P expoente spot; [ e ] brilho; setas rotação; J/L câmera; C referencial da luz; Esc fecha. """ from __future__ import annotations import math import sys from PySide6.QtCore import Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtOpenGLWidgets import QOpenGLWidget from PySide6.QtWidgets import QApplication from OpenGL.GL import * from OpenGL.GLU import gluLookAt, gluPerspective SETORES = 18 ANEIS = 10 def ponto_esfera(anel: int, setor: int) -> tuple[float, float, float]: latitude = -math.pi / 2 + math.pi * anel / ANEIS longitude = 2 * math.pi * setor / SETORES cos_lat = math.cos(latitude) return ( cos_lat * math.cos(longitude), math.sin(latitude), cos_lat * math.sin(longitude), ) class LaboratorioIluminacao(QOpenGLWidget): def __init__(self, parent=None): super().__init__(parent) self.reiniciar() self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) def reiniciar(self) -> None: """Estado inicial conhecido para repetir cada experimento.""" self.fonte = 1 self.ambiente = self.difusa = self.especular = True self.smooth = True self.normais_vertices = True self.inverter_normais = False self.abertura_spot = 28.0 self.expoente_spot = 0.0 self.brilho = 48.0 self.rot_x, self.rot_y = -15.0, 25.0 self.camera_angulo = 0.0 self.luz_na_camera = False def initializeGL(self) -> None: glClearColor(0.06, 0.07, 0.10, 1.0) glEnable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) glCullFace(GL_BACK) glFrontFace(GL_CCW) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glEnable(GL_NORMALIZE) # Sem ambiente global residual, A/D/E isolam as parcelas da fonte. glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (0.0, 0.0, 0.0, 1.0)) glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (0.18, 0.52, 0.82, 1.0)) glMaterialfv(GL_FRONT, GL_SPECULAR, (1.0, 1.0, 1.0, 1.0)) def resizeGL(self, w: int, h: int) -> None: dpr = self.devicePixelRatioF() w, h = max(1, round(w * dpr)), max(1, round(h * dpr)) glViewport(0, 0, w, h) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45.0, w / float(h), 0.1, 100.0) glMatrixMode(GL_MODELVIEW) def configurar_fonte(self) -> None: glLightfv(GL_LIGHT0, GL_AMBIENT, (0.16, 0.16, 0.20, 1.0) if self.ambiente else (0.0, 0.0, 0.0, 1.0)) glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.90, 0.82, 0.68, 1.0) if self.difusa else (0.0, 0.0, 0.0, 1.0)) glLightfv(GL_LIGHT0, GL_SPECULAR, (1.0, 1.0, 1.0, 1.0) if self.especular else (0.0, 0.0, 0.0, 1.0)) if self.fonte == 1: glLightfv(GL_LIGHT0, GL_POSITION, (2.5, 2.8, 3.0, 1.0)) glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 180.0) elif self.fonte == 2: glLightfv(GL_LIGHT0, GL_POSITION, (1.0, 1.0, 1.0, 0.0)) glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 180.0) else: glLightfv(GL_LIGHT0, GL_POSITION, (0.0, 3.0, 3.0, 1.0)) glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, (0.0, -0.7, -1.0)) glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, self.abertura_spot) glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, self.expoente_spot) def enviar_normal(self, normal) -> None: sinal = -1.0 if self.inverter_normais else 1.0 glNormal3f(*(sinal * componente for componente in normal)) def desenhar_esfera(self) -> None: glBegin(GL_TRIANGLES) for anel in range(ANEIS): for setor in range(SETORES): a = ponto_esfera(anel, setor) b = ponto_esfera(anel, setor + 1) c = ponto_esfera(anel + 1, setor + 1) d = ponto_esfera(anel + 1, setor) # Nos polos, um dos triângulos teria dois pontos coincidentes. triangulos = [] if anel > 0: triangulos.append((a, c, b)) if anel < ANEIS - 1: triangulos.append((a, d, c)) for triangulo in triangulos: if not self.normais_vertices: u = tuple(triangulo[1][i] - triangulo[0][i] for i in range(3)) v = tuple(triangulo[2][i] - triangulo[0][i] for i in range(3)) normal = ( u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0], ) self.enviar_normal(normal) for ponto in triangulo: if self.normais_vertices: self.enviar_normal(ponto) glVertex3f(*ponto) glEnd() def paintGL(self) -> None: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() if self.luz_na_camera: self.configurar_fonte() angulo = math.radians(self.camera_angulo) gluLookAt(4.5 * math.sin(angulo), 0.4, 4.5 * math.cos(angulo), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) if not self.luz_na_camera: self.configurar_fonte() glMaterialf(GL_FRONT, GL_SHININESS, self.brilho) glShadeModel(GL_SMOOTH if self.smooth else GL_FLAT) glRotatef(self.rot_x, 1.0, 0.0, 0.0) glRotatef(self.rot_y, 0.0, 1.0, 0.0) glScalef(1.25, 1.25, 1.25) self.desenhar_esfera() nomes = {1: "pontual", 2: "direcional", 3: "spot"} self.setWindowTitle( f"fonte={nomes[self.fonte]} | luz={'câmera' if self.luz_na_camera else 'mundo'} | A={self.ambiente} D={self.difusa} " f"E={self.especular} | N={'vértice' if self.normais_vertices else 'face'} " f"I={self.inverter_normais} | spot={self.abertura_spot:.0f}/{self.expoente_spot:.0f} | {'Gouraud' if self.smooth else 'flat'} | brilho={self.brilho:.0f}" ) def keyPressEvent(self, event) -> None: tecla = event.key() if tecla == Qt.Key.Key_Escape: self.close() return if tecla in (Qt.Key.Key_1, Qt.Key.Key_2, Qt.Key.Key_3): self.fonte = {Qt.Key.Key_1: 1, Qt.Key.Key_2: 2, Qt.Key.Key_3: 3}[tecla] elif tecla == Qt.Key.Key_A: self.ambiente = not self.ambiente elif tecla == Qt.Key.Key_D: self.difusa = not self.difusa elif tecla == Qt.Key.Key_E: self.especular = not self.especular elif tecla == Qt.Key.Key_J: self.camera_angulo -= 5.0 elif tecla == Qt.Key.Key_L: self.camera_angulo += 5.0 elif tecla == Qt.Key.Key_C: self.luz_na_camera = not self.luz_na_camera elif tecla == Qt.Key.Key_R: self.reiniciar() elif tecla == Qt.Key.Key_N: self.normais_vertices = not self.normais_vertices elif tecla == Qt.Key.Key_I: self.inverter_normais = not self.inverter_normais elif tecla == Qt.Key.Key_O: valores = (12.0, 28.0, 45.0) self.abertura_spot = valores[(valores.index(self.abertura_spot) + 1) % len(valores)] elif tecla == Qt.Key.Key_P: valores = (0.0, 12.0, 48.0) self.expoente_spot = valores[(valores.index(self.expoente_spot) + 1) % len(valores)] elif tecla == Qt.Key.Key_S: self.smooth = not self.smooth elif tecla == Qt.Key.Key_BracketLeft: self.brilho = max(0.0, self.brilho - 8.0) elif tecla == Qt.Key.Key_BracketRight: self.brilho = min(128.0, self.brilho + 8.0) elif tecla == Qt.Key.Key_Left: self.rot_y -= 5.0 elif tecla == Qt.Key.Key_Right: self.rot_y += 5.0 elif tecla == Qt.Key.Key_Up: self.rot_x -= 5.0 elif tecla == Qt.Key.Key_Down: self.rot_x += 5.0 else: return self.update() 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 = LaboratorioIluminacao() janela.resize(900, 650) janela.show() return app.exec() if __name__ == "__main__": raise SystemExit(main())