Profundidad de campo

Programando un motor 3D desde cero - Brakeza3D

Profundidad de campo

Buenas a tod@s! En el último post hablamos de los shaders y de las posibilidades que estos nos ofrecen. Hoy vamos a hablar de uno de los efectos más habituales para mejorar las sensaciones de nuestro motor de renderizado: La profundidad de campo (depth of field en inglés o simplemente DOF). Es una técnica ampliamente utilizada en la programación de videojuegos para difuminar partes fuera del foco.

La profundidad de campo es una característica que podríamos implementar totalmente acoplada al render, es decir, en fase de rasterización, pero que también es posible conseguir mediante un shader. Utilizaremos este último camino al ser una vía menos intrusiva y nos beneficiaremos de la naturaleza del shader, al ser un proceso puramente calculado en GPU.

¿En qué consiste la profundidad de campo?

Cualquier aficionado a la fotografía, habrá jugado con la profundidad de campo. Una imagen vale más que mil palabras:

Como podéis observar entre ambas imágenes, la diferencia radica en que el fondo está desenfocado, ganando protagonismo la cara del niño.  Podréis encontrar este efecto en incontables videojuegos:

Es por tanto un recurso más a nuestra disposición, quedando a nuestra elección cuando, donde y con qué intensidad lo utilizaremos.

¿Cómo implementamos el campo de profundidad?

La implementación del campo de profundidad mediante un shader, no es compleja, siempre y cuando ya dispongamos de algunas herramientas previamente: El buffer de profundidad (Z-Buffer) y el frustum.

Os recomiendo repasar el concepto de Z-Buffer si genera dudas: Por resumirlo brevemente, el Z-Buffer al final nos estaba aportando un buffer donde almacenamos la profundidad de cada pixel renderizado en pantalla. Si interpretásemos el Z-Buffer como una imagen, sería un degradado de este estilo:

Z Buffer image

Si ya disponemos por tanto de este buffer en cada frame, podemos proporcionar este buffer al shader en cuestión. Esto por un lado.

Por otro lado el frustum, representada mediante una pirámide truncada (por los planos near y far) que limita la geometría a renderizar de toda la escena.

Os recomiendo visitar los enlaces de ambos temas, si generan dudas. Continuamos!

¿Qué parámetros conforman un campo de profundidad?

No demasiados, al menos los que yo he utilizado para articular el DOF son:

  • focusPlaneDepth: A qué distancia de profundidad se situa el plano enfocado.
  • focusRange: Define el “grosor” del plano enfocado.
  • blurSize: Factor de difuminación.
  • intensity: Intensidad global (rango 0-1).

¿Qué estrategia de difuminación utilizo?

La buena noticia es que tenemos libertad para elegir. Existen distintos algoritmos de difuminación, en mi caso he optado por el “desenfoque de caja“. El desenfoque de caja se consigue promediando el color del pixel en cuestión con sus píxeles vecinos.

Cuanto mayor sea la caja, más difuminado, pero más coste de procesamiento.

Lógica del Shader

Al final podréis encontrar el código completo del shader en OpenCL, pero esta es su lógica principal:

  1. Se obtiene la profundidad del píxel actual a partir del buffer de profundidad (z-buffer)

  2. Se calcula la diferencia de profundidad entre el píxel actual y el plano de enfoque especificado.

  3. Se determina la cantidad de desenfoque necesario para el píxel actual basado en la diferencia de profundidad y el rango de enfoque. 

  4. Aplicamos al pixel el difuminado de caja, en funcion de la cantidad aportada en el punto anterior.

Podéis encontrar aquí el código completo del shader aquí.

Resultados

A continuación algunos ejemplos del resultado real en Brakeza3D:

Resumen

Presentamos la profundidad de campo y su implementación mediante un Shader. Con la profundidad de campo podremos captar la atención del jugador hacia objetos específicos, relegando el resto de la escena a un segundo lugar. Es un recurso visual más a tener en cuenta.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *