Sombras

Para esta ocasión, se le va a agregar sombras a la animación hecha del carro en el parque. Partimos de lo hecho en el ejemplo anterior. El resultado final se puede encontrar en codepen. Se recomienda ver el código por Github y no por el codepen ya que todo se mezclo en un solo archivo.

Para agregarle sombras al ejemplo se utilizo un shadow map. Básicamente, la idea es hacer un primer pasado por la escena a dibujar para calcular la distancia minima que hay desde el objeto que emite luz, hacia todas las direcciones donde esta luz se proyecta. Luego, se hace una segunda pasada por la escena donde sí se dibujan los objetos. Al dibujar los objetos verificamos si la distancia entre la posición del vértice actual y el objeto que emite luz es menor o igual a la distancia minima reportada. Si es menor o igual, significa que este vértice no necesita sombra, de lo contrario, sí necesita.

Para implementar la primera pasada creamos un nuevo frame buffer y dibujamos sobre este la escena en vez de dibujarla en el buffer que pinta sobre el canvas. Es útil hacer esta operación ya que al dibujar sobre el frame buffer obtenemos una textura que codifica la distancia al objeto más cercano desde la cámara que estemos utilizando.


El llamado a Utils se encarga de crear el frame buffer y añadirle las texturas apropiadas y el buffer de profundidad para que este funcione bien.

En el drawScene utilizamos este buffer como se evidencia a continuación:


Nótese que estamos utilizando dos escenas distintas, una llamada shadowScene, que dibuja al buffer nuevo, y otra llamada scene, que dibuja en el buffer original. Aunque estas escenas pintan los mismos objetos, la diferencia entre ellas es que la shadowScene pinta todo desde la perspectiva desde los objetos que emiten luz (e.g. sol) mientras que el scene pinta todo desde una cámara longshot.

Adicionalmente, el vertex y fragment shader de la shadowScene es más sencillo ya que solo se encarga de cargar la distancia más corta entre el objeto que emite luz hacia los demás puntos.


Sin embargo, también se hicieron cambios en el vertex y fragment shader de la escena normal que teníamos del ejemplo pasado. Se tuvo que agregar lógica para poder determinar, utilizando la textura que se renderizó en el shadowScene, si un vértice se encuentra en un lugar que debe tener sombra (y luego aplicarle esta sombra).

Al vertex shader anterior se le agrega otra variable de tipo varying que determina la posición de un objeto relativo al objeto que emite luz.


Por otro lado, al fragment shader se le agregó un método que determina si un vértice está en sombra utilizando la textura que resultó del shadow map.


Se destaca la suma de 0.00004 en el if que se hace para compensar los errores en precisión decimal.

Este método es utilizado en el main para determinar si hay sombra en un vértice. Si sí existe, le asignamos un valor más oscuro de luz que la luz de ambiente.


La luz de ambiente que utilizamos en el ejemplo tiene un peso de vec3(0.3, 0.3, 0.3).

El resto de cambios que se le aplicaron al ejemplo son minucias de implementación que se encargan de agregar nuevas variables, aplicar perspectivas a matrices para obtener el lookAt desde los puntos de luz, entre otras cosas.

Bitácora de Tiempo 

Para este ejercicio se dispusieron 8 horas y se gastaron las 8 horas. La mayoría del tiempo se dedico en comprender bien como funcionaba un shadow map y como poder implementarlo en el ejemplo. Se destaca que no hay muchos recursos que traten sobre este tema en WebGL. Se hizo un debug extenso ya que la sombra que se estaba dibujando no resultaba como se esperaba. Adicionalmente, también me di cuenta que debo hacer un refactor importante para poder seguir reutilizando las primitivas que he creado. Cada cambio en el fragment o vertex shader me ha resultado costoso en tiempo de implementación ya que tengo que tocar casi que todas mis clases que dibujan para pasar los nuevos atributos que estos programas esperan.

Comentarios

Entradas populares de este blog

Texturas

Ejemplo Processing P5.js

Sprint 4 - Proyecto Final