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 = -zNeardel 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 posteriormentegluPerspective/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: iteraempty_facecon 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:
(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:
- Seleccionar matriz de proyección.
glLoadIdentity→ matriz identidad.glOrtho→ multiplica con los coeficientes de proyección ortogonal.- Volver a model-view.
gluLookAt→ define la cámara.- 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 siguienteglViewportse 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
zFarde 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/popaí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
colorscon 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\)?
Para \(a=b=c=10\): \(1000 - 512 = 488\), como afirmaba la profesora.
Preguntas de Autoevaluación
- ¿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. - 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?
- ¿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.
- 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? - ¿Por qué
glOrthose llama tras seleccionar la matriz de proyección y NO la model-view? ¿Qué pasaría si te equivocas de pila? - ¿Cuál es el orden de parámetros de
glViewport? ¿En qué sistema de coordenadas se expresan? - Tienes una ventana 1200 × 800 y quieres seis viewports en distribución 3 × 2. Da las llamadas exactas de
glViewportpara cada uno. - 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? - ¿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.
- ¿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.
- En la función
solid_face_xz, ¿por qué se usaglTranslatef(1, 0, -z_size)al final del bucle externo y noglTranslatef(1, 0, 0)? - ¿Cuál es la función de
glPushMatrix/glPopMatrixcuando construyes un árbol como en el ejemplo? ¿Qué pasaría si los omites? - Explica qué hace
density_ortoy por qué es la primitiva adecuada para una copa de árbol pero no para el tronco. - ¿Cómo construirías una pirámide de base 7 × 7 utilizando solo
empty_facey traslaciones? - 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.)
- ¿Por qué en
gluLookAt(0,0,0, 0,0,-1, 0,1,0)y engluLookAt(0,0,0, 0,0,-100, 0,1,0)la imagen es la misma? ¿Qué cambiaría si pones(0,-1,0)como APP?