Skip to content

Clase 7 — Proyección paralela ortogonal, viewports múltiples y actividad grupal

Resumen Ejecutivo

Penúltima clase de OpenGL antes de cerrar el bloque y pasar a visión artificial. Tras introducir el sistema de coordenadas de la vista la semana pasada, se aborda la transformación de proyección: cómo pasar de coordenadas de la vista al plano de proyección que finalmente se ve en el viewport. Se distinguen las dos familias (paralela y perspectiva), se profundiza en la proyección paralela ortogonal (glOrtho), se aprende a definir múltiples viewports con glViewport (clave para la actividad grupal y para el examen), y se cierra con la presentación detallada de la actividad grupal: dibujar una escena estilo Minecraft con cuatro viewports y un kit de funciones de apoyo (solid_face, empty_orto, density_orto, empty_face, empty_pipe, pyramid). ⚠️ EXAMEN: tipos de proyección y sus diferencias, glOrtho, glViewport (definir varios viewports), volumen de recorte y por qué los objetos fuera no se ven, optimización hueco vs sólido.

Conceptos Clave

  • Proyección: paso del 3D al 2D. Convierte vértices en coordenadas de la vista a puntos del plano de proyección.
  • Plano de proyección / plano cercano: plano más cercano a la cámara del volumen de recorte. En OpenGL coincide con la cara Z = -zNear del ortoedro de recorte. ⚠️ EXAMEN
  • Línea de proyección: recta que une cada vértice con el plano de proyección. Su comportamiento define el tipo de proyección.
  • Proyección paralela: las líneas de proyección son paralelas entre sí. ⚠️ EXAMEN: conserva las proporciones relativas entre objetos. Ideal para CAD, planos técnicos, ingeniería. Cero sensación de profundidad.
  • Proyección en perspectiva: las líneas de proyección convergen en un punto (centro de proyección). No conserva proporciones (objetos lejanos parecen más pequeños). Imagen realista. Se verá la próxima clase.
  • Proyección paralela ortogonal (ortográfica): líneas perpendiculares al plano de proyección. ⚠️ EXAMEN
  • Proyección paralela oblicua: líneas paralelas entre sí pero oblicuas al plano. Caballera, gabinete (las usadas hasta ahora en cuadernos para visualizar 3D). Próxima clase.
  • glOrtho(left, right, bottom, top, zNear, zFar): define volumen de recorte y activa proyección paralela ortogonal. ⚠️ EXAMEN
  • Matriz de proyección: pila de matrices propia de OpenGL (distinta de la model-view). Guarda los coeficientes que se aplicarán a cada vértice tras la transformación de la vista. La altera glOrtho (y posteriormente gluPerspective / glFrustum).
  • glViewport(x, y, w, h): define un viewport por su esquina inferior izquierda y su ancho/alto, en coordenadas del dispositivo (píxeles). ⚠️ EXAMEN — múltiples viewports en la actividad grupal y en el examen.
  • Optimización Minecraft: ortoedros huecos ahorran cubos drásticamente. Un cubo de lado 10 sólido = 1 000 cubos / 6 000 caras; el mismo hueco = 488 cubos (≈ –51%). ⚠️ EXAMEN-conceptual
  • Función solid_face: dibuja una superficie (XZ, YZ, XY) rellenando con cubos cuyo color se elige al azar dentro de un rango de tonalidades.
  • Función empty_orto: dibuja un ortoedro hueco con seis caras sólidas.
  • Función density_orto: dibuja un ortoedro denso (cubos dispersos por el volumen, p. ej. copas de árboles).
  • Función empty_face: dibuja un marco rectangular hueco. Base para construir pirámides.
  • Función empty_pipe: ortoedro sin tapas (tronco de árbol).
  • Función pyramid: itera empty_face con tamaños decrecientes y traslaciones en Y.

Desarrollo del Temario

1. Ubicación dentro del pipeline OpenGL

Estamos un paso más allá que la clase 6. Allí se definía el sistema de coordenadas de la vista; aquí se introduce la proyección sobre el plano cercano de ese sistema.

flowchart LR
    A[Coord. modelo] -->|model-view| B[Coord. universo]
    B -->|gluLookAt| C[Coord. vista]
    C -->|glOrtho / gluPerspective| D[Coord. proyección]
    D --> E[Normalización]
    E -->|glViewport| F[Coord. dispositivo]

La pieza nueva es el paso C → D, gobernado por la matriz de proyección (no la model-view).

2. Tipos de proyección

2.1 Paralela vs perspectiva

Paralela Perspectiva
Líneas de proyección Paralelas entre sí Convergen en un punto
Proporciones relativas Se conservan No se conservan
Sensación de profundidad Casi nula Alta
Realismo Bajo Alto
Uso típico CAD, planos técnicos, ingeniería Videojuegos, simulación, render

⚠️ EXAMEN: la afirmación "la proyección paralela conserva las proporciones relativas" es la pregunta más típica de este apartado.

Apunte de Albert (en clase): "la paralela da poca sensación de profundidad". Confirmado: la convergencia en un punto de fuga es lo que aporta profundidad, y la paralela carece de ella.

2.2 Subtipos de la proyección paralela

Ortogonal (ortográfica) Oblicua
Ángulo línea–plano 90° \(\neq\) 90°
Función OpenGL glOrtho Hay que aplicar glOrtho + matriz de cizalla manual
Casos típicos Planos de planta, alzado, perfil Caballera, gabinete

"La ortogonal puede mostrarse en aspecto 3D si la cámara no apunta a un eje principal." Esto es importante: paralela ortogonal NO implica imagen plana, depende del punto de vista.

3. Geometría de la proyección paralela ortogonal

Como las líneas de proyección son perpendiculares al plano de proyección, y el plano de proyección es perpendicular al eje Z-view, las líneas son paralelas al eje Z-view.

Consecuencia: para un vértice \((x_v, y_v, z_v)\), sus coordenadas proyectadas son trivialmente:

\[ x_p = x_v, \quad y_p = y_v, \quad z_p = -\text{zNear} \]

(Z queda fija al plano de proyección; X e Y no cambian).

4. Volumen de recorte en proyección ortogonal

Es un ortoedro alineado con los ejes:

flowchart LR
    direction LR
    A["(left, bottom, -zFar)"] --- B["(right, top, -zNear)"]
  • Plano cercano: \(Z = -\text{zNear}\)es el plano de proyección.
  • Plano lejano: \(Z = -\text{zFar}\).
  • Cualquier vértice fuera del ortoedro no se proyecta.

⚠️ EXAMEN — observación de la profesora: cuando un objeto "no se ve", lo más probable es que esté fuera del volumen de recorte. Solución: ampliar zFar o desplazar la cámara.

5. glOrtho — recordatorio operativo

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(left, right, bottom, top, zNear, zFar);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x0, y0, z0, xref, yref, zref, vx, vy, vz);

Patrón siempre el mismo:

  1. Seleccionar matriz de proyección.
  2. glLoadIdentity → matriz identidad.
  3. glOrtho → multiplica con los coeficientes de proyección ortogonal.
  4. Volver a model-view.
  5. gluLookAt → define la cámara.
  6. Dibujar.

6. glViewport y viewports múltiples ⚠️ EXAMEN

Hasta ahora todo el pipeline acababa en un único viewport (toda la ventana). Ahora aprendemos a partir la ventana en varias regiones, cada una con su propia proyección y/o cámara.

glViewport(x_inferior_izq, y_inferior_izq, ancho, alto);
  • x, y → coordenadas en píxeles del dispositivo, esquina inferior izquierda.
  • Tras llamar a glViewport, todo lo que se dibuje hasta el siguiente glViewport se proyectará en esa región.
  • El sistema de referencia es el de la ventana: (0,0) abajo-izquierda, (W, H) arriba-derecha.

6.1 Reparto de una ventana 900 × 600 en seis viewports

Distribución 3 × 2 = 6 viewports cuadrados de 300 × 300:

+---------+---------+---------+
| (0,300) | (300,300)| (600,300)|
+---------+---------+---------+
| (0, 0)  | (300, 0) | (600, 0) |
+---------+---------+---------+

Cada celda corresponde a una vista distinta de la misma escena (frontal, lateral derecho, lateral izquierdo, posterior, planta, oblicua).

7. Las 5 vistas estándar de un objeto en OpenGL

Asumiendo cámara en (0,0,0) y APP (0,1,0):

Vista Punto de referencia (Pref) Comentario
Frontal (0,0,-1) Mirando a -Z (default OpenGL).
Posterior (0,0,1) Mirando a +Z. Se ve la cara trasera (gris oscuro).
Lateral derecho (-1,0,0) Mirando a -X (visualizamos la cara derecha).
Lateral izquierdo (1,0,0) Mirando a +X.
Planta (cenital) (0,-1,0) Mirando a -Y (mirando hacia abajo).

⚠️ EXAMEN: justificar el signo del eje según la dirección desde la que se mira el objeto.

7.1 Por qué la ortogonal puede verse "en 3D"

Si en lugar de poner la cámara en un eje principal la situamos en un punto oblicuo (por ejemplo (3, 3, 3) mirando al origen), la proyección sigue siendo paralela ortogonal pero percibimos volumen. Es la clave para la actividad grupal: una de las vistas debe ser oblicua para apreciar la escena.

Detalle: si la casa "desaparece" al mover la cámara, lo más probable es que se haya salido del volumen de recorte. Solución del cuaderno: subir zFar de 3 a 7.

8. Actividad grupal — escena Minecraft con 4 viewports

8.1 Objetivo

Dibujar una escena coherente formada por objetos compuestos de cubos unitarios de distintos colores (estética Minecraft, sin texturas). La actividad sustituye la unidad de texturizado, que no entra por tiempo.

8.2 Requisitos

  • Ventana con 4 viewports (proyecciones distintas; al menos una con vista oblicua).
  • Objetos formados íntegramente por cubos unitarios llamando a color_cube.
  • Diseñar el tamaño de cada objeto en cubos antes de codificar.
  • División del trabajo: cada miembro hace al menos un objeto.
  • Coherencia temática (no mezclar barco y árbol y montaña; Navidad y granjas son recurrentes).

8.3 Optimización crítica: hueco vs sólido ⚠️ EXAMEN-conceptual

Cubo de lado 10:

Cubos Polígonos (× 6 caras)
Sólido (todos los huecos rellenos) \(10^3 = 1\,000\) 6 000
Hueco (solo cáscara) \(10^3 - 8^3 = 488\) 2 928

Coste \(\sim 2.05\times\) menos en hueco. Para escenas con varios objetos grandes la diferencia decide entre "ejecuta" y "no ejecuta".

8.4 Diseño con funciones intermedias

Principio: no replicar el bucle de pintado en cada objeto. Definir una biblioteca pequeña de primitivas y construir los objetos llamándolas. Filosofía típica de motores de videojuegos.

8.5 Estructura del callback display

def display():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    # parámetros del volumen de recorte (compartidos por los viewports)
    xmin, xmax = -3, 3
    ymin, ymax = -3, 3
    zN, zF = -3, 3
    viewport_ortho_front(xmin, xmax, ymin, ymax, zN, zF)
    viewport_ortho_back (xmin, xmax, ymin, ymax, zN, zF)
    viewport_ortho_right(xmin, xmax, ymin, ymax, zN, zF)
    viewport_ortho_left (xmin, xmax, ymin, ymax, zN, zF)
    viewport_ortho_top  (xmin, xmax, ymin, ymax, zN, zF)
    viewport_oblicua    (xmin, xmax, ymin, ymax, zN, zF)
    glFlush()

Cada viewport_… encapsula glViewport + glOrtho + gluLookAt + dibujar_escena.

9. Biblioteca de primitivas (kit de la profesora)

9.1 Rangos de color

yellow_range = [yellow1, yellow2, yellow3, yellow4, yellow5]
yellow_light_range = yellow_range[:3]   # 3 más claros
yellow_dark_range  = yellow_range[2:]   # 3 más oscuros
# idem brown, blue, green, red, gray

Cada color tiene 5 tonalidades; rangos light y dark capturan 3 cada uno. Permite jugar con variedad sin sobreplanificar paletas.

9.2 solid_face_xz(x_size, z_size, colors)

Dibuja una cara plana paralela al plano XZ rellena de cubos, eligiendo color al azar de colors.

def solid_face_xz(x_size, z_size, colors):
    if x_size < 2 or z_size < 2:
        return
    glPushMatrix()
    for x in range(x_size):
        for z in range(z_size):
            color = choice(colors)
            color_cube(color)
            glTranslatef(0, 0, 1)        # avanzar en Z dentro de la fila
        glTranslatef(1, 0, -z_size)      # nueva fila, volver al inicio en Z
    glPopMatrix()

Detalles a entender:

  • El bucle externo es en X, el interno en Z → se rellenan filas sobre Z para cada X.
  • glTranslatef(0, 0, 1) avanza un cubo dentro de la fila.
  • glTranslatef(1, 0, -z_size) cambia de fila: avanza uno en X y deshace todo el desplazamiento acumulado en Z.
  • push/pop aísla las traslaciones para no contaminar la matriz model-view del resto de la escena. ⚠️ EXAMEN

Analogía de Mark (clase): "es como si una grúa pusiera 100 cubos en una recta y, para empezar la siguiente, se desplaza un centímetro a la derecha y vuelve atrás para rellenar". Útil para fijar el patrón.

Trucos de uso:

  • Pasar colors con un único elemento → cara monocroma.
  • Pasar el rango completo → estética rugosa Minecraft.
  • Cambiar la semilla del generador aleatorio si se quiere reproducibilidad.

9.3 solid_face_yz y solid_face_xy

Misma filosofía aplicando rotaciones / permutaciones de ejes.

9.4 empty_orto(x, y, z, colors)

Ortoedro hueco. Construido con seis llamadas a solid_face_*, ajustando dimensiones para que las caras no se solapen (cada cara aprovecha que las adyacentes ya están puestas → se dibujan con un cubo menos en la dimensión correspondiente).

9.5 density_face y density_orto(x, y, z, density_pct, colors)

Igual que las solid pero con densidad en tanto por ciento. Útil para copas de árboles donde se quieren cubos dispersos. El uso típico:

density_orto(30, 30, 30, 15, green_range)   # copa

9.6 empty_face(x_size, z_size, colors)

Marco rectangular (cuatro lados de la cara, hueco por dentro). Pieza base para pyramid.

9.7 empty_pipe(x, y, z, colors)

Ortoedro sin tapas (caras superior e inferior abiertas). Implementación: apilar empty_face con la rotación adecuada. Uso típico: tronco de árbol.

9.8 pyramid(size, colors)

Construye una pirámide de base cuadrada. Si size es par lo pasa a impar para que el vértice sea un punto. Itera empty_face con tamaño decreciente subiendo en Y.

def pyramid(size, colors):
    if size % 2 == 0:
        size += 1
    glPushMatrix()
    while size > 0:
        empty_face(size, size, colors)
        glTranslatef(1, 1, 1)   # subir y centrar
        size -= 2
    glPopMatrix()

10. Construcción de un árbol con la biblioteca

def tree(trunk_color, leaves_color):
    glPushMatrix()
    glPushMatrix()
    empty_pipe(2, 5, 2, trunk_color)         # tronco
    glPopMatrix()
    glPushMatrix()
    glTranslatef(-3, 5, -3)
    density_orto(8, 8, 8, 15, leaves_color)  # copa
    glPopMatrix()
    glPopMatrix()

Patrón crucial: cada llamada a una primitiva va envuelta en su propio push/pop. Anidamientos limpios → no se contaminan posiciones entre objetos. ⚠️ EXAMEN.

11. Patrón general para añadir detalles (ojos, bocas, etc.)

Para pintar dos ojos en la cara de un muñeco:

glPushMatrix()
# 1) trasladar al punto donde irá el ojo izquierdo
glTranslatef(x_ojo_izq, y_ojo, z_cara)
# 2) dibujar la cara pequeña (2×2 cubos, monocroma)
solid_face_xy(2, 2, [black])
glPopMatrix()
glPushMatrix()
# 3) repetir para el ojo derecho
glTranslatef(x_ojo_der, y_ojo, z_cara)
solid_face_xy(2, 2, [black])
glPopMatrix()

Conviene esbozar el objeto en papel con coordenadas de los cubos antes de programarlo. Las traslaciones son fáciles si sabes dónde quieres cada pieza.

Ejemplos / Ejercicios resueltos

Ejemplo 1 — Por qué hay que ampliar zFar al mover la cámara

Con glOrtho(-3,3,-3,3,-3,3) y cámara en (3,3,3) mirando al origen, la casa no se ve. Razón: la diagonal del cubo de recorte de lado 6 no llega a contener la casa cuando la cámara se aleja a (3,3,3). Solución: ampliar zFar a 7 (o aumentar el ortoedro completo). ⚠️ EXAMEN: relación entre zFar y la distancia de la cámara al objeto.

Ejemplo 2 — Definir cuatro viewports en una ventana 800 × 600

Para una distribución 2 × 2 (cuadrantes):

def viewport_top_left():
    glViewport(0, 300, 400, 300)
    # ... glOrtho + gluLookAt + dibujar

def viewport_top_right():
    glViewport(400, 300, 400, 300)
    # ...

def viewport_bottom_left():
    glViewport(0, 0, 400, 300)
    # ...

def viewport_bottom_right():
    glViewport(400, 0, 400, 300)
    # ...

Cada viewport puede usar distintos glOrtho y gluLookAt para ofrecer 4 vistas de la misma escena.

Ejemplo 3 — Calcular cubos de un ortoedro

¿Cuántos cubos tiene un ortoedro hueco de tamaño \(a \times b \times c\)?

\[ N_\text{hueco} = a\,b\,c \;-\; (a-2)(b-2)(c-2) \]

Para \(a=b=c=10\): \(1000 - 512 = 488\), como afirmaba la profesora.

Preguntas de Autoevaluación

  1. ¿Qué ocurre con un vértice cuyas coordenadas en la vista caen fuera del volumen de recorte definido por glOrtho? Justifica con un ejemplo de la práctica.
  2. Explica las dos diferencias clave entre proyección paralela y proyección en perspectiva. ¿Cuál usarías para un plano de planta de un edificio? ¿Y para un videojuego de plataformas?
  3. ¿Qué subtipos tiene la proyección paralela? Indica cómo se define cada uno y cuál de ellos hemos estado usando en los cuadernos antes de esta clase.
  4. Dada glOrtho(-3, 3, -3, 3, -3, 3), indica las coordenadas exactas de los seis planos del ortoedro en el sistema de coordenadas de la vista. ¿Qué plano es el plano de proyección?
  5. ¿Por qué glOrtho se llama tras seleccionar la matriz de proyección y NO la model-view? ¿Qué pasaría si te equivocas de pila?
  6. ¿Cuál es el orden de parámetros de glViewport? ¿En qué sistema de coordenadas se expresan?
  7. Tienes una ventana 1200 × 800 y quieres seis viewports en distribución 3 × 2. Da las llamadas exactas de glViewport para cada uno.
  8. Un compañero coloca la cámara en (5,5,5) mirando al origen y ya no ve nada. ¿Cuál es la causa más probable y cómo la corriges?
  9. ¿Por qué en proyección paralela ortogonal se puede ver un objeto "en 3D"? Explica la condición sobre la posición de la cámara.
  10. ¿Cuántos cubos contiene un ortoedro hueco de \(20 \times 10 \times 5\)? Razona aplicando la fórmula y explica por qué interesa hacerlo hueco.
  11. En la función solid_face_xz, ¿por qué se usa glTranslatef(1, 0, -z_size) al final del bucle externo y no glTranslatef(1, 0, 0)?
  12. ¿Cuál es la función de glPushMatrix/glPopMatrix cuando construyes un árbol como en el ejemplo? ¿Qué pasaría si los omites?
  13. Explica qué hace density_orto y por qué es la primitiva adecuada para una copa de árbol pero no para el tronco.
  14. ¿Cómo construirías una pirámide de base 7 × 7 utilizando solo empty_face y traslaciones?
  15. En la actividad grupal, ¿qué porcentaje de la nota está relacionado con la eficiencia (uso de huecos, número de cubos) frente al resultado visual? (Pista: la profesora insiste en que computacionalmente es nefasto pero acepta la complejidad porque no se ha visto texturizado.)
  16. ¿Por qué en gluLookAt(0,0,0, 0,0,-1, 0,1,0) y en gluLookAt(0,0,0, 0,0,-100, 0,1,0) la imagen es la misma? ¿Qué cambiaría si pones (0,-1,0) como APP?