Ejercicio 1 - Transparencia

El cálculo de la transparencia en OpenGL lo realiza la GPU de forma automática. Sin embargo, el programador aún debe realizar varias tareas. Descarga transparencia.zip antes de continuar.

La primera de las tareas es indicar la función que OpenGL debe utilizar para realizar el cálculo de la transparencia. Se puede considerar que esto solo es necesario realizarlo una única vez. Así que su sitio podría perfectamente ser la función initRendering().

Ejercicio 3 Opcional - Combina transparencia y sombras

Trata de por ejemplo combinar los dos métodos vistos en esta práctica, o aplica alguno de los métodos a tu proyecto actual. No será fácil, pero el resultado va a valer la pena, garantizado.

Realismo Visual

Informática Gráfica

José Ribelles

Departamento de Lenguajes y Sistemas Informáticos

Universitat Jaume I

Añadir iluminación y texturas ha supuesto un incremento muy importante en el realismo visual de la escena. En esta práctica vamos a tratar de mejorarlo aún más incorporando objetos transparentes y sombras.

Última revisión: 21/11/2016

La primera forma (izquierda en la imagen) consiste en dibujar el objeto sin más. Si mueves la cámara verás que hay veces que se observan resultados extraños en la superficie del objeto. ¿Entiendes por qué?

La segunda forma (centro en la imagen) consiste en dibujar el objeto habiendo habilitado previamente la eliminación de las caras de detrás, proceso que realizará automáticamente la GPU en la etapa de Rasterización.

Y la tercera forma (derecha en la imagen) consiste en dibujar dos veces el objeto, ambas con la eliminación de caras habilitada. La primera solicitando la eliminación de las caras de delante, y la segunda solicitando la eliminación de las caras de detrás.

Juega un poco con el ejemplo y observa las diferencias. Prueba también a colocar la cámara para ver a través de todos los objetos transparentes. Comenta los resultados con tus compañeros y con tu profesor.

Ejercicio 2 - Sombras

El método que trata esta sección propone dibujar dos veces la escena. Descarga sombras.zip antes de continuar.

El primer dibujado se realiza viendo lo que ve la fuente de luz, por lo que sólo tiene sentido si pensamos en una fuente de luz tipo foco. Como resultado de este primer render, se almacena la profundidad de cada fragmento en una textura. Este es el mecanismo que OpenGL proporciona para poder reutilizar el resultado en un nuevo dibujado, y se emplea en técnicas muy diferentes.

Con la textura almacenando la profundidad de los elementos de la escena vistos desde la posición de la fuente de luz, se dibuja la escena de nuevo pero esta vez vista desde la cámara establecida por el usuario. La idea es oscurecer o no cada fragmento dependiendo del resultado de la comparación entre la profundidad del fragmento y la almacenada en la textura.

Dos Shaders

El primer dibujado tiene como objetivo conocer la profundidad desde la fuente de luz. Por lo que necesitamos un Shader muy sencillito en el que en el procesador de vértices únicamente se opere el vértice por sus matrices correspondientes, y en el de fragmentos se almacene como valor de color del fragmento su profunidad (gl_FragColor = vec4(gl_FragCoord.z)).

El segundo Shader tiene como objetivo dibujar la escena que el usuario va a ver. Pero además, debe de para cada vértice obtener su posición respecto a la cámara ubicada en el foco, y para cada fragmento hacer la comparación de su profundidad con la almacenada en la textura.

Así, en el shader de fragmentos, además del proceso habitual que un vértice sufre y que ya conoces (gl_Position = projectionMatrix * modelViewMatrix * VertexPosition), también hay que obtener el resultado de operar el vértice por las mismas matrices pero correspondientes a la posición y orientación del foco de luz y a su propio volumen de la vista  (posFromLight = projectionLightMatrix * modelViewLightMatrix * VertexPosition). La variable posFromLight será de tipo varying para que la GPU interpole su valor para cada fragmento.

Comprueba que en dicha función se ha codificado el operador over gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);. Otra posibilidad interesante es utilizar gl.blendFunc(gl.SRC_ALPHA, gl.ONE);. Prueba ambas y observa las diferencias.

El factor de transparencia se suministra habitualmente mediante la cuarta componente de Kd ya que en realidad se trata de una propiedad más del material. Sin embargo, en el ejemplo se ha definido una variable uniform en el Shader de fragmentos  que controla dicho factor para todos los objetos transparentes de la escena. La página Web muestra un controlador de rango que te permite jugar con el factor de transparencia haciéndolo variar entre cero y uno. Finalmente, la componente alpha de gl_FragColor (la cuarta componente) debe contener el factor de transparencia definitivo.

La imagen anterior muestra una escena con un tablero de aspecto sólido, y un objeto transparente formado por un cilindro y un cono. Dicho objeto se ha dibujado tres veces, cada una de ellas de manera diferente. Comprueba en el código que antes de dibujarlos se habilita el cálculo de la transparencia en la GPU (gl.enable(gl.BLEND)) y que se impide la actualización del buffer de profundidad pero no de la realización del test (gl.depthMask(false)). Por supuesto, después de dibujar los objetos transparentes se realizan las operaciones contrarias (gl.disable(gl.BLEND) y gl.depthMask(true)).

(placeholder)

Finalmente se consigue así que cada fragmento conozca su profundidad tanto para la escena que se está dibujando como para la posición de la cámara ubicada en la fuente de luz. Utilizando este segundo valor de profundidad, y comparándolo con el almacenado en la textura, sabremos si el fragmento correspondiente está en sombra o no.

Framebuffer Object

Para poder dibujar y que el resultado no sea visible al usuario, hay que crear lo que OpenGL denomina objeto Framebuffer (FBO). Excepto ese detalle (que el contenido no es visible al usuario), no hay ninguna otra diferencia respecto al framebuffer que existe por defecto (el que sí que se muestra al usuario). De esta manera, el primer dibujado se realiza contra un nuevo FBO. Mientras que el segundo dibujado se realiza contra el framebuffer por defecto. La creación de un FBO en nuestro caso requiere de un buffer de profundidad y de un objeto textura. La función initFramebufferObject() contiene la secuencia de instrucciones necesarias.

Sin embargo, hay dos tareas que la GPU realiza de forma fija a la variable gl_Position y que no hace a la variable posFromLight, que son la división perspectiva y la normalización en el rango [0..1]. Observa estas operaciones en el código.

Juega un poco con el ejemplo y trata de encontrar sus puntos débiles. Prueba por ejemplo a utilizar una textura más pequeña. ¿Sabrías por ejemplo cómo visualizar la profundidad (la imagen en grises un poco más arriba)? Comenta los resultados que obtengas con tus compañeros y con tu profesor.

(placeholder)