Ha llegado otro fin de semana, lo que significa que estoy escribiendo otras dos docenas de líneas de código y hago una o dos ilustraciones. En artículos anteriores, he explicado cómo
hacer el trazado de rayos e incluso
hacer explotar cosas . Esto puede sorprenderlo, pero los gráficos por computadora son bastante fáciles: incluso un par de cientos de líneas de C ++ pueden producir algunas imágenes muy emocionantes.
El tema de hoy es la visión binocular, y ni siquiera romperemos la barrera de las 100 líneas al hacerlo. Como podemos dibujar escenas en 3D, sería una tontería ignorar los pares estéreo, así que hoy haremos algo como esto:

La pura locura de
los creadores de
Magic Carpet todavía me sorprende. ¡Para aquellos que no lo saben, este juego te permitió hacer un render 3D en modo anaglifo y estereograma
desde el menú de configuración principal ! Eso fue salvaje para mí.
Paralaje
Entonces, comencemos. Para empezar, ¿qué hace que nuestro aparato de visión perciba profundidad en los objetos? Hay un término inteligente "paralaje". Centrémonos en la pantalla. Todo lo que está dentro del plano de la pantalla es registrado por nuestro cerebro como un objeto. Pero si una mosca vuela entre nuestros ojos y la pantalla, entonces el cerebro la percibe como dos objetos. La araña detrás de la pantalla también se duplicará.

Nuestro cerebro es muy eficiente para analizar imágenes ligeramente diferentes. Utiliza
la disparidad binocular para obtener información sobre la profundidad de las imágenes 2D que provienen de la retina utilizando la
estereopsis . Bueno, ¡atornille las grandes palabras y permítanos dibujar imágenes!
Imaginemos que nuestro monitor es una ventana al mundo virtual :)

Nuestra tarea es dibujar dos imágenes de lo que vemos a través de esa "ventana", una para cada ojo. En la imagen de arriba, el "sándwich" rojo-azul. Olvidemos por ahora cómo enviar estas imágenes a nuestro cerebro, en esta etapa solo necesitamos guardar dos archivos separados. En particular, quiero saber cómo obtener estas imágenes con
mi pequeño raytracer .
Supongamos que el ángulo no cambia y es el vector (0,0, -1). Supongamos que podemos mover la cámara al área entre los ojos, pero entonces ¿qué? Un pequeño detalle: la
visión desde nuestra "ventana" es asimétrica. Pero nuestro rastreador de rayos solo puede generar un tronco simétrico:

¿Y ahora qué hacemos? Truco :)
Podemos renderizar imágenes un poco más anchas de las que necesitamos y luego simplemente recortar las partes adicionales:

Anaglifo
Creo que hemos cubierto el mecanismo de representación básico, y ahora abordamos el problema de entregar la imagen a nuestro cerebro. La forma más sencilla es este tipo de gafas:

Hacemos dos renders en escala de grises y asignamos imágenes izquierda y derecha a los canales rojo y azul, respectivamente. Esto es lo que obtenemos:

El vidrio rojo corta un canal, mientras que el vidrio azul corta el otro. Combinados, los ojos obtienen una imagen diferente y la percibimos en 3D.
Aquí están las modificaciones al commit principal del tinyraytracer. Los cambios incluyen el posicionamiento de la cámara para el montaje de los ojos y los canales.
Renders de anaglifo es una de las formas más antiguas de ver imágenes estéreo (generadas por computadora). Tiene muchas desventajas, por ejemplo, mala transmisión del color. Pero por otro lado, estos son muy fáciles de crear en casa.
Si no tiene un compilador en su computadora, no hay problema. Si tiene una cuenta guithub, puede ver, editar y ejecutar el código (sic!) Con un solo clic en su navegador.

Cuando abre este enlace, gitpod crea una máquina virtual para usted, inicia VS Code y abre un terminal en la máquina remota. En el historial de comandos (haga clic en la consola y presione la tecla arriba) hay un conjunto completo de comandos que le permite compilar el código, ejecutarlo y abrir la imagen resultante.
Estereoscopio
Con los teléfonos inteligentes convirtiéndose en la corriente principal, recordamos la invención del siglo XIX llamada estereoscopio. Hace un par de años, Google sugirió usar dos lentes (que, desafortunadamente, es difícil de crear en casa, debe comprarlo), un poco de cartón (disponible en cualquier lugar) y un teléfono inteligente (en su bolsillo) para crear algo creíble Gafas de realidad virtual.

Son abundantes en AliExpress y cuestan como $ 3 por par. En comparación con el renderizado de anaglifos, ni siquiera tenemos mucho que hacer: solo tome dos fotos y colóquelas una al lado de la otra.
Aquí está el compromiso .

Estrictamente hablando, dependiendo de la lente, es posible que necesitemos
corregir la distorsión de la lente , pero no me molesté con eso, ya que se veía bien de todos modos. Pero si realmente necesitamos aplicar la distorsión previa del barril que compensa la distorsión natural de la lente, así es como se ve mi teléfono inteligente y mis gafas:

Aquí está el enlace gitpod:

Autostereogramas
¿Y qué hacemos si no queremos usar ningún equipo adicional? Entonces solo hay una opción: entrecerrar los ojos. La imagen anterior, honestamente, es suficiente para ver en estéreo, solo entrecierra los ojos (cruzando los ojos o colocándolos en la pared). Aquí hay un esquema que nos dice cómo ver la ilustración anterior. Dos líneas rojas muestran las imágenes percibidas por la retina izquierda, dos azules, la retina derecha.

Si nos enfocamos en la pantalla, entonces cuatro imágenes se combinan en dos. Si cruzamos los ojos o nos enfocamos en un objeto lejano, es posible alimentar al cerebro con "tres" imágenes. Las imágenes centrales se superponen, lo que crea el efecto estéreo.
Diferentes personas usan diferentes métodos: por ejemplo, no puedo cruzar los ojos, pero los muro fácilmente. Es importante que el autoestereograma creado para un determinado método solo se vea con ese método, de lo contrario obtendremos un mapa de profundidad invertido (¿recuerda paralaje positivo y negativo?). El problema es que es difícil cruzar mucho los ojos, por lo que solo funciona en imágenes pequeñas. Pero, ¿y si queremos unos más grandes? Sacrifiquemos los colores por completo y concéntrese solo en la parte de percepción de profundidad. Aquí está la imagen que esperamos obtener al final del artículo:

Este es un autoestereograma con ojos de pared. Para aquellos que prefieren el otro método,
aquí hay una imagen para eso . Si no está acostumbrado a los autoestereogramas, pruebe diferentes condiciones: pantalla completa, imagen más pequeña, brillo, oscuridad. Nuestro objetivo es tapar nuestros ojos para que las dos franjas verticales cercanas se superpongan. Lo más fácil es enfocarse en la parte superior izquierda de la imagen, ya que es simple. Personalmente, abro la imagen en pantalla completa. ¡No olvides quitar también el cursor del mouse!
No te detengas ante un efecto 3D incompleto. Si ve vagamente formas redondeadas, y el efecto 3D es débil, la ilusión es incompleta. Se supone que las esferas "saltan" de la pantalla hacia el espectador, el efecto tiene que ser estable y sostenible. La estereopsis tiene una gisteresis: una vez que obtiene una imagen estable, se vuelve más detallada cuanto más la observa. Cuanto más lejos estén los ojos de la pantalla, mayor será el efecto de percepción de profundidad.
Este estereograma se dibujó utilizando un método sugerido hace 25 años en este artículo: "
Visualización de imágenes en 3D: algoritmos para estereogramas de puntos aleatorios de una sola imagen ".
Comencemos
El punto de partida para renderizar autostereograms es el mapa de profundidad (ya que abandonamos los colores).
Este commit dibuja la siguiente imagen:

Los planos más cercanos y más lejanos establecen nuestra profundidad: el punto más alejado de mi mapa tiene la profundidad de 0, mientras que el más cercano tiene la profundidad de 1.
Los principios centrales
Digamos que nuestros ojos están a distancia de la pantalla. Ponemos nuestro plano lejano (imaginario) (z = 0) a la misma distancia "detrás" de la pantalla. Elegimos la variable μ, que determina la ubicación del plano cercano (z = 1), que estará a una distancia μd del plano lejano. Para mi código elegí μ = ⅓. En general, todo nuestro "mundo" vive a una distancia de d-μd a d detrás de la pantalla. Digamos que sabemos la distancia entre los ojos (en píxeles, elegí 400 píxeles):

Si observamos el punto rojo, entonces dos píxeles marcados en verde deberían tener el mismo color en el estereograma. ¿Cómo calcular la distancia entre ellos? Fácil Si el punto proyectado actual tiene la profundidad de z, entonces el paralaje dividido por la distancia entre los ojos es igual a la fracción entre las profundidades correspondientes: p / e = (d-dμz) / (2d-dμz). Por cierto, ¡tenga en cuenta que d se simplifica y no aparece en ningún otro lugar! Entonces p / e = (1-μz) / (2-μz), lo que significa que el paralaje es igual a p = e * (1-μz) / (2-μz) píxeles.
La idea principal detrás del autoestereograma es: revisamos todo el mapa de profundidad, para cada valor de profundidad determinamos qué píxeles tendrán el mismo color y lo asignamos a nuestro sistema de restricciones. Luego comenzamos desde la imagen aleatoria e intentamos satisfacer todas las restricciones que hemos establecido previamente.
Preparando la imagen fuente
Aquí preparamos la imagen que luego estará limitada por restricciones de paralaje.
Aquí está el commit , y dibuja esto:

Tenga en cuenta que los colores son en su mayoría aleatorios, aparte del canal rojo donde pongo rand () * sin para crear un patrón periódico. Las franjas están separadas por 200 píxeles, que es (dado μ = 1/3 y e = 400) el valor de paralaje máximo en nuestro mundo (el plano lejano). El patrón no es técnicamente necesario, pero ayudará a enfocar los ojos.
Representación de autoestereograma
En realidad, el código completo que dibuja el autoestereograma se ve así:
int parallax(const float z) { const float eye_separation = 400.;
Aquí está el commit , la función int parallax (const float z) nos da la distancia entre píxeles del mismo color para el valor de profundidad actual. Representamos el estereograma línea por línea, ya que las líneas son independientes entre sí (no tenemos paralaje vertical). El bucle principal simplemente itera a través de cada línea; cada vez que comienza con un conjunto ilimitado de píxeles, y luego para cada píxel agrega una restricción de igualdad. Al final, nos da una cierta cantidad de grupos de píxeles del mismo color. Por ejemplo, los píxeles con índices a la izquierda y a la derecha deben terminar idénticos.
¿Cómo almacenar este conjunto de restricciones? La forma más simple es la
estructura de datos union-find . No entraré en detalles, solo iré a Wikipedia, son literalmente tres líneas de código. El punto principal es que para cada grupo hay un cierto píxel "raíz" responsable del grupo. El píxel raíz mantiene su color inicial, y todos los demás píxeles del clúster deben actualizarse:
for (size_t i=0; i<width; i++) {

Conclusión
Eso es todo. Veinte líneas de código y nuestro autoestereograma está listo para que rompas los ojos sobre él. Por cierto, si nos esforzamos lo suficiente, es posible transmitir información de color.
No cubrí otros sistemas estereoscópicos como
los sistemas 3D polarizados , ya que son mucho más caros de fabricar. Si me perdí algo, ¡no dudes en corregirme!