Mudanças entre as edições de "Perspectiva ou Projeção Cônica"

De Aulas
 
Linha 1: Linha 1:
 
 
 
 
 
 
 
Afluentes: [[Computação Gráfica]]
 
Afluentes: [[Computação Gráfica]]
  

Edição atual tal como às 09h18min de 16 de maio de 2025

Afluentes: Computação Gráfica

Introdução

Para representarmos um espaço tridimensional — tal como percebido pelos olhos humanos — em um plano bidimensional, como a tela do computador, recorremos a uma das mais importantes descobertas do mundo das artes: a perspectiva.

Desenvolvida a partir de análises visuais e necessidades construtivas, a projeção em perspectiva surge durante o Renascimento, na busca por soluções geométricas para a construção da cúpula da Catedral de Florença, pelo arquiteto italiano Filippo Brunelleschi (1377–1446).

Essa técnica introduziu o realismo na pintura e no desenho, oferecendo uma maneira sistemática de representar a profundidade — ou melhor, a ilusão dela — sobre superfícies planas.

A projeção perspectiva produz uma imagem visualmente realista, embora não preserve as medidas reais dos objetos, pois executa uma transformação dentro do espaço tridimensional para representar a cena a partir de um ponto de observação a uma distância finita.

Na projeção perspectiva, as coordenadas dos pontos projetados são obtidas pela intersecção dos raios projetores com o plano de projeção, conforme ilustrado na Figura 1.

Figura 1: Representação da Visualização em Perspectiva.

Termos

Sendo que:

  • (x, y, z): coordenadas de um ponto no espaço 3D (a cena que está sendo projetada).
  • f: a distância focal, que representa o quão longe está o plano da imagem do centro da projeção (o "olho" da câmera).
  • (x′,y′): coordenadas do ponto projetado no plano da imagem, ou seja, no plano 2D.
  • z é a distância do observador até o ponto original;

Ao aplicarmos o ponto do objeto no plano da imagem, iremos criar o ponto de projeção. Dessa forma, estamos aplicando uma transformação do espaço tridimensional para o espaço bidimensional.

Interpretação

Imagine que a câmera está olhando na direção do eixo Z, com o plano de projeção paralelo ao plano XY, colocado a uma distância f do centro (em z = f). O ponto 3D é então projetado em linha reta até esse plano.

Dessa forma, temos que x' dividido por x é igual a f dividido por x que implica em x' é igual a f dividido por z, multiplicado por x.

Logo, para encontrar o x' que é a representação no plano do x do espaço 3D, usamos a fórmula x' = f dividido por z, multiplicado por x:

E o mesmo se aplica a y ou seja, y' é igual a f dividido por z, multiplicado por y:

O z aqui é crucial — ele representa a profundidade. Quanto mais longe o objeto estiver (maior z), menor ele aparecerá na imagem — exatamente como nossos olhos percebem o mundo. Uma verdadeira dança de escala baseada na profundidade.

Formalizando mais...

Podemos ver essa projeção como uma transformação projetiva que leva um ponto 3D para 2D, ignorando a componente de profundidade na imagem. Em coordenadas homogêneas:

Para aplicarmos, fazemos uma operação de matrizes. Temos que a matriz vertical de três linhas e uma coluna, contendo x', y' e 1 é igual a matriz de três por quatro contendo na primeira linha: f, 0, 0 e 0, na segunda linha: 0, f, 0 e 0 e na terceira linha: 0, 0, 1 e 0, multiplicado pela matriz vertical de quatro por um contendo z, y, z e 1 da imagem 3D. Isso implica nas fórmulas para gerar x' e y' apresentadas anteriormente.

Essa abordagem é usada em gráficos computacionais, visão computacional, realidade aumentada, renderização 3D, etc.

E para implementar?

Tendo as equações já simplificadas, podemos agora aplicá-las na programação.

O exemplo a seguir apresenta, em forma de algoritmo, a função de transformação do espaço euclidiano tridimensional para sua projeção cônica na tela do nosso dispositivo computacional.

Distancia = 300
Elemento2 = Elemento

Para i = 0 até Quantidade_de_pontos fazer:
    Elemento2[i].x = (Distancia / Elemento[i].z) * Elemento[i].x
    Elemento2[i].y = (Distancia / Elemento[i].z) * Elemento[i].y
Fim para

Ou, em Python:

DISTANCIA = 300

def projetar_ponto(ponto):
    x, y, z = ponto
    fator = DISTANCIA / z
    return (fator * x, fator * y)

elemento = [(x, y, z) for x, y, z in ...]  # seus pontos 3D
elemento2 = [projetar_ponto(p) for p in elemento]

Com isso, criamos um novo conjunto de pontos Elemento2, que representa a projeção do objeto original (Elemento) no plano de visualização.

A partir deste ponto, não exibiremos mais o objeto original, mas sim sua representação projetada.


O resultado dessa transformação, aplicada a um cubo originalmente situado no espaço euclidiano (Figura 2), é apresentado na Figura 3, agora sob uma perspectiva central.

Figura 2: Imagem do cubo isométrico (espaço euclidiano).
Figura 3: Imagem do cubo em perspectiva.

Implementação

Script cubo3d.py.

from __future__ import annotations
import sys
import math
import pygame

# Configuração
SPEED: float = 0.1
WIDTH: int = 800
HEIGHT:  int = 600
Color_screen: pygame.Color = (255, 255, 255)
Color_line: pygame.Color = (0, 0, 0)
CONICAL: bool = False
DISTANCE: float = 300


# -------------------------
# Classe Point
# -------------------------
class Point:
    def __init__(self, x: float, y: float, z: float):
        self.x: float = x                               # vetor x
        self.y: float = y                               # vetor y
        self.z: float = z                               # vetor z

    def copy(self) -> Point:                            # Cria uma copia do ponto
        return Point(self.x, self.y, self.z)


# -------------------------
# Classe Cube
# -------------------------
class Cube:
    def __init__(self, screen):
        self.screen: pygame.display = screen            # Referencia a screen do pygame
        self.points: list[Point] = [                    # Criacao dos pontos do cubo
            Point(0, 0, 0),
            Point(-50, -50, -50),
            Point(+50, -50, -50),
            Point(+50, +50, -50),
            Point(-50, +50, -50),
            Point(-50, -50, +50),
            Point(+50, -50, +50),
            Point(+50, +50, +50),
            Point(-50, +50, +50),
        ]
        self.translate(0, 0, 300)                        # Move para uma posicao inicial longe da tela

    def translate(self, tx, ty, tz) -> None:             # Translada o cubo
        for p in self.points:                            # Para cada ponto executa a matriz de translacao
            p.x += tx                                    # Soma x a tx
            p.y += ty                                    # Soma y a ty
            p.z += tz                                    # Soma z a tz

    def scale(self, sx, sy, sz) -> None:                 # Executa a escala no cubo
        pivot = self.points[0].copy()                    # Cria uma copia do ponto pivo
        self.translate(-pivot.x, -pivot.y, -pivot.z)     # Translada para a origem com referencia ao pivo
        for p in self.point:                             # Executa a matriz de escala para cada ponto
            p.x *= sx
            p.y *= sy
            p.z *= sz
        self.translate(pivot.x, pivot.y, pivot.z)        # Translada da origem de volta ao pivo original

    def rotate(self, angle, axis):                       # Executa a rotacao do cubo
        angle = math.radians(angle)                      # Converter para radianos
        pivot = self.points[0].copy()                    # Faz uma copia do ponto pivo
        self.translate(-pivot.x, -pivot.y, -pivot.z)     # Translada para a origem com referência ao pivo
        for p in self.points:                            # Executa a matriz de rotacao em cada ponto e cada vetor
            x, y, z = p.x, p.y, p.z
            if axis == "x":
                p.y = y * math.cos(angle) - z * math.sin(angle)
                p.z = z * math.cos(angle) + y * math.sin(angle)
            elif axis == "y":
                p.x = x * math.cos(angle) - z * math.sin(angle)
                p.z = z * math.cos(angle) + x * math.sin(angle)
            elif axis == "z":
                p.x = x * math.cos(angle) - y * math.sin(angle)
                p.y = y * math.cos(angle) + x * math.sin(angle)
        self.translate(pivot.x, pivot.y, pivot.z)

    # Executa visualizacao da projecao conica / perspectiva se ativa
    def perspective(self, p) -> Point:
        if CONICAL and p.z != 0:
            scale = DISTANCE / p.z
            return Point(p.x * scale, p.y * scale, p.z)
        return Point(p.x, p.y, p.z)

    def line(self, p1, p2) -> None:
        pygame.draw.line(self.screen, Color_line, (p1.x, p1.y), (p2.x, p2.y))

    # Desenha o cubo
    def draw(self) -> None:
        p = []
        for i in range(9):
            point = self.perspective(self.points[i])
            point.x += WIDTH / 2
            point.y += HEIGHT / 2
            p.append(point)

        self.line(p[0], p[0])  # draw point over pivot
        for i in range(3):
            self.line(p[i + 1], p[i + 2])
            self.line(p[i + 5], p[i + 5 + 1])
            self.line(p[i + 1], p[i + 5])
        self.line(p[4], p[1])
        self.line(p[8], p[4])
        self.line(p[5], p[8])

    def change(self, move):
        if move == "translate_left":
            self.translate(-SPEED, 0, 0)
        elif move == "translate_right":
            self.translate(SPEED, 0, 0)
        elif move == "translate_up":
            self.translate(0, -SPEED, 0)
        elif move == "translate_down":
            self.translate(0, SPEED, 0)
        elif move == "translate_front":
            self.translate(0, 0, SPEED)
        elif move == "translate_back":
            self.translate(0, 0, -SPEED)
        elif move == "rotate_y_left":
            self.rotate(-SPEED, "y")
        elif move == "rotate_y_right":
            self.rotate(SPEED, "y")
        elif move == "rotate_x_up":
            self.rotate(SPEED, "x")
        elif move == "rotate_x_down":
            self.rotate(-SPEED, "x")
        elif move == "rotate_z_right":
            self.rotate(SPEED, "z")
        elif move == "rotate_z_left":
            self.rotate(-SPEED, "z")


def show_help(screen, font):
    help = [
        font.render("ESC: Fecha o programa", True, (200, 200, 200)),
        font.render("A, D: Rotaciona no eixo X", True, (200, 200, 200)),
        font.render("S, W: Rotaciona mo eixo Y", True, (200, 200, 200)),
        font.render("Z, X: Rotaciona mo eixo Z", True, (200, 200, 200)),
        font.render("Setas ESQUERDA, DIREITA: Translada mo eixo X", True, (200, 200, 200)),
        font.render("Setas CIMA, BAIXO: Translada mo eixo Y", True, (200, 200, 200)),
        font.render("Q, E: Translada no eixo Z", True, (200, 200, 200)),
        font.render("C: Liga/desliga a projeção cônica", True, (200, 200, 200)),
    ]
    help_pos_y = 10
    for i in range(len(help)):
        screen.blit(help[i], (10, help_pos_y))
        help_pos_y += 25


def main():
    global CONICAL
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    font = pygame.font.SysFont(None, 22)
    cube = Cube(screen)
    cube.draw()
    move = ""
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit(0)
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    move = "translate_left"
                elif event.key == pygame.K_RIGHT:
                    move = "translate_right"
                elif event.key == pygame.K_DOWN:
                    move = "translate_down"
                elif event.key == pygame.K_UP:
                    move = "translate_up"
                elif event.key == pygame.K_q:
                    move = "translate_back"
                elif event.key == pygame.K_e:
                    move = "translate_front"
                elif event.key == pygame.K_a:
                    move = "rotate_y_left"
                elif event.key == pygame.K_d:
                    move = "rotate_y_right"
                elif event.key == pygame.K_w:
                    move = "rotate_x_up"
                elif event.key == pygame.K_s:
                    move = "rotate_x_down"
                elif event.key == pygame.K_x:
                    move = "rotate_z_right"
                elif event.key == pygame.K_z:
                    move = "rotate_z_left"
                elif event.key == pygame.K_c:
                    CONICAL = not CONICAL
                elif event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit(0)
            elif event.type == pygame.KEYUP:
                move = ""
        cube.change(move)

        screen.fill(Color_screen)
        cube.draw()
        show_help(screen, font)

        pygame.display.flip()


if __name__ == "__main__":
    main()