← CC2018

Raycasting

Semestre 02, 2026

Introducción

  • El raycasting es una técnica fundamental para simular escenas tridimensionales en entornos 2D.
  • Lanzamos rayos desde un observador hacia el entorno y medimos qué encuentran y a qué distancia.
  • Exploraremos el concepto de campo de visión (FOV) y cómo influye en lo que percibe el observador.

Raycasting

Definición

Técnica de proyección en la que se lanzan rayos desde un punto (usualmente el jugador) hacia el entorno.

Cada rayo verifica colisiones con objetos o paredes para saber qué es visible.
Y también a qué distancia está lo que golpea.
A diferencia de un motor 3D completo, simula perspectiva 3D con operaciones 2D.

Aplicaciones

Motores de juegos tempranos como Wolfenstein 3D (1992).
Motores de colisión simples para juegos y simulaciones.
Análisis de línea de visión en IA y juegos de estrategia.
Wolfenstein 3D, un motor de raycasting clásico

Proceso de raycasting

Una columna vertical de la pantalla a la vez.

1Lanzar el rayo
  • Por cada columna vertical de la pantalla se lanza un rayo.
2Buscar la intersección
  • Se calcula dónde el rayo choca con el entorno (muro u obstáculo).
3Medir la distancia
  • Distancia desde el jugador hasta el punto de intersección.

Dibujar la columna

4Calcular la altura
  • Con base en la distancia: cuanto más lejos, más pequeña.
5Pintar
  • Se dibuja la columna con una textura o color correspondiente.
Idea clave

Nunca dibujamos geometría 3D. Repetimos un cálculo 2D sencillo una vez por columna y el resultado se percibe como profundidad.

Ver todo el campo de visión

  • Un solo rayo nos da información únicamente de lo que hay directamente al frente.
  • Para representar todo lo que abarca la mirada necesitamos más rayos.
  • A ese concepto le llamamos field of view (campo de visión).
Un solo rayo solo ve al frente

Campo de visión

  • El FOV es la dirección hacia donde el jugador está mirando y cuánto abarca.
  • Para simplificar los cálculos se mide en radianes, como \(\frac{\pi}{3} = 60°\).
  • Simulamos un rayo que parte del jugador y avanza en línea recta en la dirección del ángulo hasta encontrar una pared.
El ángulo de visión del jugador

FOV bajo y FOV alto

FOV bajo (p. ej. 40°): mayor "zoom", menor percepción de periferia.
FOV alto (p. ej. 100°): mayor distorsión, pero mejor panorama.
60° es un valor cómodo, cercano a la visión enfocada del ojo humano.
Es el mismo parámetro que ajusta el "zoom" de una cámara.

Cálculo de la distancia

  • El plano de proyección es perpendicular a la dirección de vista del jugador.
  • Cada rayo intersecta ese plano a cierta distancia, y esa distancia determina la deformación.
  • Lo que nos interesa es la distancia \(d\) hasta la pared, con identidades trigonométricas sencillas.
Componentes del punto de impacto $$ x = d\cos(a), \qquad y = d\sin(a) $$
Distancia del rayo al plano de proyección

Corrección de ojo de pez

Fisheye

Los rayos de los bordes viajan más lejos que los del centro para tocar la misma pared recta. Si usamos esa distancia tal cual, la pared se ve curvada.

La corrección proyecta la distancia sobre la dirección de vista, con \(\beta\) el ángulo del rayo respecto al centro:

$$ d_{\perp} = d \cdot \cos(\beta) $$
Pared recta a distancia real 5. Un rayo a \(\beta = 30°\) mide \(5 / \cos 30° = 5.77\).
Corregido: \(5.77 \cdot \cos 30° = 5.0\). La pared vuelve a verse plana.

Usando el FOV

  • Para simular lo que el jugador ve, lanzamos múltiples rayos que abarcan el ángulo total del FOV.
  • El primer rayo sale en la dirección \(a - \frac{fov}{2}\).
  • Incrementamos el ángulo con cada rayo sucesivo hasta \(a + \frac{fov}{2}\).

for i in 0..5 {
    let angle = a - (fov / 2.0)
        + i as f32 * (fov / 5.0);
    // lanzar rayo en esa dirección
}

Cinco rayos, con números

Datos

Vista a \(a = 90°\), \(fov = 60°\), 5 rayos. El primero sale en \(90° - 30° = 60°\) y el paso es \(60°/5 = 12°\).

\(60°\) — extremo izquierdo del FOV.
\(72°\)
\(84°\) — cerca del centro.
\(96°\)
\(108°\) — hacia el extremo derecho.

Un rayo por columna

Múltiples rayos abarcando el FOV
En la práctica

El número de rayos es el ancho de la pantalla: con \(n\) rayos dibujamos \(n\) columnas, una imagen completa.

Ventajas

Simplicidad: no requiere geometría tridimensional completa.
Velocidad: muy rápido para entornos 2D con simulación 3D.
Bajo consumo de recursos: ideal para sistemas embebidos o retro.
Estética retro reconocible de los primeros FPS.

Limitaciones

No puede renderizar superficies inclinadas ni objetos 3D complejos.
Solo simula muros verticales.
Dificultad para manejar luces y sombras realistas.
Para 3D real hace falta ray tracing o el pipeline de rasterización.

Estacas

  • En raycasting no dibujamos 3D real: simulamos profundidad con columnas verticales llamadas estacas (stakes).
  • Se lanza un rayo por cada columna del framebuffer; con \(n\) rayos dibujamos \(n\) estacas.
  • Mientras más cerca esté la pared, más alta se dibuja la estaca.
Estacas verticales que simulan paredes

Altura desde la distancia


// hh = mitad de la altura del viewport
let stake_height = hh / intersect.d;
Relación inversa

La altura es inversamente proporcional a la distancia: el doble de lejos, la mitad de alto. Eso da la perspectiva.

La altura de la estaca depende de la distancia

Centrar la estaca


let stake_top    = hh - stake_height / 2.0;
let stake_bottom = hh + stake_height / 2.0;
  • \(hh\) es la mitad de la pantalla: el horizonte.
  • La estaca crece hacia arriba y hacia abajo desde ahí.
  • Dividimos la pantalla en dos mitades para que quede centrada.
La estaca centrada en el viewport

Alturas, con números

Pantalla de 900 px

\(hh = 450\). La altura es \(stake = hh / d\), con la distancia en unidades de celda.

\(d = 1\): \(stake = 450\). Pared muy cerca, casi llena la pantalla.
\(d = 2\): \(stake = 225\). Centrada entre 337.5 y 562.5.
\(d = 5\): \(stake = 90\). Pared lejana, columna corta.

Texturas

  • Para que una pared se vea realista, en vez de un color plano usamos una textura: una imagen asignada a la superficie.
  • Necesitamos dos coordenadas de textura: \(tx\) (columna) y \(ty\) (fila).
  • \(tx\) depende de dónde chocó el rayo dentro del bloque; \(ty\), del pixel de la estaca.
Muestreo de textura sobre una pared

Coordenada horizontal tx


let hitx = x - i * block_size;  // offset en el bloque
let tx = (maxhit * 128) / block_size;  // 128 = ancho tex.
Idea

\(maxhit\) es el punto exacto (en píxeles) donde chocó el rayo dentro del bloque; se reescala al ancho de la textura.

Ejemplo

Impacto al centro del bloque: \(maxhit = 32\), \(block = 64\).

tx = (32 * 128) / 64
tx = 64

Usamos la columna 64 de la imagen de la pared.

Coordenada vertical ty


let ty = (y - stake_top)
    / (stake_bottom - stake_top)
    * texture_height;
  • \(y\) es el pixel actual de la estaca que se está pintando.
  • Se normaliza entre \(stake\_top\) y \(stake\_bottom\): un valor de 0 a 1.
  • Se multiplica por el alto de la textura para elegir la fila.
  • Con \(tx\) y \(ty\) se muestrea la textura y se pinta el pixel.

Sprites

  • Los sprites son objetos 2D (enemigos, objetos) que siempre miran hacia el jugador.
  • Son imágenes planas orientadas según la posición del jugador (billboards).
  • Generan el efecto de que el objeto está integrado al entorno 3D.
Sprites que miran al jugador

Cómo dibujar un sprite

1Posición relativa
  • Vector del sprite respecto al jugador \((x, y)\).
2Ángulo con atan2
  • La arcotangente da el ángulo hacia el sprite.
3¿Dentro del FOV?
  • Verificar si el sprite cae en el campo de visión.
4Distancia y tamaño
  • Más lejos, más pequeño (igual que las estacas).
5Z-buffer
  • Dibujar solo si está delante de las paredes.

Un mundo 3D sencillo y convincente

  • El raycasting simula profundidad con operaciones 2D: rayos, distancias y estacas.
  • Con FOV, corrección de fisheye, texturas y sprites logramos una escena creíble.
  • Ray tracing extiende la idea del rayo al 3D real y a la simulación física de la luz.
Cita

"Un mundo 3D no necesita ser complicado para ser convincente."