Luces

Ejemplo de utilización de luces en WebGL. El ejercicio que se va a explicar se encuentra aquí. El código se puede encontrar tanto en codepen como en github.

Nos concentraremos en los detalles de luces y obviaremos la implementación de las figuras y del grafo de escena. Solo se mencionara estos componentes cuando sea relevante. La mayoría de efectos de luces que se implementaron se hacen directamente desde el vertex shader y el fragment shader.

Lo único que se necesita para poder realizar los efectos es tener el vector normal de cada vértice dibujado, y la posición de los objetos que emiten luz.

A continuación se explicará el vertex shader suponiendo que ya hemos cargado todas los vectores con las posiciones que emiten luz y los vectores normales a los vertices de cada figura.




Se evidencia de esta primera sección del vertex shader que hemos agregado un número importante de variables. Además de tener la posición del vértice (aVertexPosition) y la coordenada de textura (aTextureCoord), le hemos agregado aVertexNormal, que representa el vector normal al vértice.

Luego, tenemos las matrices a las que ya estamos acostumbrados (uNormalMatrix, uProjectionMatrix, uModelViewMatrix).

Finalmente, tenemos nuevas variables de tipo varying que le vamos a suministrar al fragment shader. Las nuevas son vPosition y vTransformedNormal. La primera nos indica cual es la posición en el world matrix del vértice, mientras que la segunda nos dice cuales son las normales transformadas.

Dentro del main solo le damos valores a estas variables para suministrarlas al fragment shader.

Por otro lado, el fragment shader ahora tiene que hacer muchas más operaciones para poder hacer una implementación apropiada de luces.


Dado que la implementación de este bloque tiene bastantes lineas de código, fragmentaremos la explicación.


Tenemos variables para saber si:
  1. El elemento que estamos dibujando es el sol (uIsSun). Con esta variable sabremos si ignoramos los efectos de sombra para este objeto (el sol emite luz y por ende debe estar completamente iluminado).
  2. El elemento que estamos dibujando es una luz del vehículo (uIsHeadlight). Se justifica igual que uIsSun.
  3. El elemento que estamos dibujando es un poste de luz (uIsLightPole). Se justifica igual que uIsSun.
  4. El vector de posición al sol en nuestro ejemplo (uSunDirectionalVector). Esto nos sirve para determinar en que dirección esta incidiendo la luz en el resto de las figuras.
  5. Las posiciones de las dos luces del vehículo (uHeadlightPosition1 y uHeadlightPosition2), para determinar que elementos son los que iluminan estas linternas.
Después de la declaración de variables comenzamos a operar sobre estas.



En estas primeras lineas conseguimos el color del texel. Definimos que la luz ambiental va a ser de 0.3 en los 3 colores rgb, que la luz direccional va a tener el valor máximo de 1 en cada color rgb, y que la luz del sol está posicionada en el sentido definido por uSunDirectionalVector. También decimos que el peso que le vamos a dar a la luz cuando estemos calculando el color final del pixel es inicialmente igual a la luz ambiental.



Las últimas 7 lineas de código que se observan en la imagen calculan cuanto es la iluminación que recibe el vértice actual basado en la posición del sol. Esto se considera un directional lighting y basta calcularlo utilizando el producto punto entre el vector normal del vértice y el vector de posición del sol.

El siguiente segmento del fragment shader se encarga de determinar si el objeto debe estar iluminado completamente ya que este emite luz. Esto ocurre inicialmente en el primer if que pregunta si el vértice actual hace parte de la figura sol (uIsSun < 0.5).

De no ser el sol, es decir, uIsSun < 0.5 == true, debemos verificar si es de día (es de día cuando el sol está encima de nuestro disco y no por debajo). Si es de día toda la luz que recibe nuestra escena se le atribuye al sol (la luz del carro y de la lampara en el centro están apagadas).

Si es de noche entonces debemos "prender" la luz del carro y la luz del poste. Primero revisamos si el vértice actual hace parte de uno de estos emisores de luz para ver si le asignamos el número máximo de iluminación (uIsHeadlight < 0.5 && uIsLightPole < 0.5).

Luego, vemos si el vértice actual debe ser afectado por la luz del carro o del poste. Para esto calculamos la distancia entre el vértice actual y los distintos emitores de luz. Si en algún punto el vértice está a menos de 0.15 unidades de un emitor de luz, este se verá afectado.

Finalmente, el color del pixel se hace utilizando el color del texel y pesándolo con la luz final obtenida de la suma de los if statements anteriores.

Bitácora de Tiempo

Para este ejercicio se dispuso de al rededor de 20 horas. Las 20 horas fueron utilizadas. Quería desarrollar este ejercicio bien y tenerlo organizado para poder reutilizar componentes en las siguientes tareas y en el proyecto final. Para esta ocasión se organizó el código en primitivas, objetos más elaborados que dependen de estas primitivas, y un grafo de escena que es capaz de dibujar todos estos objetos de forma fácil.

Comentarios

Entradas populares de este blog

Texturas

Ejemplo Processing P5.js

Sprint 4 - Proyecto Final