Pseudo destello de lente

Hola Habr! Les presento la traducción del artículo "Pseudo Lens Flare" de John Chapman.

imagen

Destello de lente ( destello de lente) es un artefacto fotográfico que surge de la dispersión y refracción de la luz en un sistema de lente. Aunque es un artefacto, hay muchas razones para usar el destello de lente en los gráficos de computadora:

  • aumenta el brillo percibido y el rango dinámico visible de la imagen.
  • El destello de la lente a menudo se encuentra en las fotografías, por lo que su ausencia puede ser sorprendente.
  • Puede desempeñar un papel importante en el estilo o el drama, o puede ser parte de la jugabilidad en los juegos (imagine el deslumbramiento cegando a un jugador)

Tradicionalmente, el destello de lente en tiempo real se ha implementado utilizando tecnologías basadas en sprites. Aunque los sprites brindan resultados fáciles de controlar y muy realistas, deben colocarse explícitamente y requieren datos de oclusión para visualizarse correctamente. Aquí describiré un efecto de espacio de pantalla simple y relativamente barato que crea un destello de pseudo lente desde el búfer de color de entrada. No se basa en la física, por lo que el resultado es ligeramente diferente del fotorrealista, pero se puede usar en combinación con (o como reemplazo) para los efectos tradicionales basados ​​en sprites.

Algoritmo


Consta de 4 etapas:

  1. Muestra descendente / umbral.
  2. Generación de elementos de destello de lente .
  3. Desenfoque
  4. Mejora / fusión con la imagen original.

1. Muestra descendente / umbral


Reducción de muestreo : optimización para reducir el costo de los pasos posteriores. Además, queremos seleccionar un subconjunto de los píxeles más brillantes en la imagen original. El uso de scale / bias (scale / bias) proporciona una forma flexible de lograr esto:

uniform sampler2D uInputTex; uniform vec4 uScale; uniform vec4 uBias; noperspective in vec2 vTexcoord; out vec4 fResult; void main() { fResult = max(vec4(0.0), texture(uInputTex, vTexcoord) + uBias) * uScale; } 

imagen

El ajuste de escala / sesgo es la forma principal de ajustar el efecto; la mejor configuración dependerá del rango dinámico del búfer de color, así como de qué tan delgado desee ver el resultado. Debido al hecho de que la técnica es una aproximación, es más probable que la sutileza se vea mejor.

2. Generación de elementos de destello de lente.


Los elementos de destello de la lente tienden a girar alrededor del centro de la imagen. Al simular este efecto, podemos expandir el resultado de la etapa anterior horizontal / verticalmente. Esto es fácil de hacer en la etapa de generación de elementos expandiendo las coordenadas de textura:

 vec2 texcoord = -vTexcoords + vec2(1.0); 

Esto no es necesario; la generación de elementos funciona bien con y sin ella. Sin embargo, el resultado de cambiar las coordenadas de textura ayuda a separar visualmente el efecto de reflejo de la lente de la imagen original.

Fantasmas


Los " fantasmas " (fantasmas) son reflejos repetidos que reflejan las áreas brillantes en el búfer de color, desplegándose en relación con el centro de la imagen. El enfoque que elegí generar es obtener un vector desde el píxel actual hasta el centro de la pantalla, y luego hacer varias selecciones a lo largo de este vector.

imagen

 uniform sampler2D uInputTex; uniform int uGhosts; // number of ghost samples uniform float uGhostDispersal; // dispersion factor noperspective in vec2 vTexcoord; out vec4 fResult; void main() { vec2 texcoord = -vTexcoord + vec2(1.0); vec2 texelSize = 1.0 / vec2(textureSize(uInputTex, 0)); // ghost vector to image centre: vec2 ghostVec = (vec2(0.5) - texcoord) * uGhostDispersal; // sample ghosts: vec4 result = vec4(0.0); for (int i = 0; i < uGhosts; ++i) { vec2 offset = fract(texcoord + ghostVec * float(i)); result += texture(uInputTex, offset); } fResult = result; } 

Tenga en cuenta que uso fract () para asegurar que las coordenadas de textura se envuelvan; de manera equivalente, puede usar el modo de ajuste GL_REPEAT para la textura.

Aquí está el resultado:

imagen

Puede mejorar el resultado permitiendo que solo las áreas brillantes más cercanas al centro de la imagen generen fantasmas. Podemos lograr esto agregando pesos que disminuirán desde el centro para las muestras:

 vec4 result = vec4(0.0); for (int i = 0; i < uGhosts; ++i) { vec2 offset = fract(texcoord + ghostVec * float(i)); float weight = length(vec2(0.5) - offset) / length(vec2(0.5)); weight = pow(1.0 - weight, 10.0); result += texture(uInputTex, offset) * weight; } 

La función de peso es lo más simple posible: lineal. La razón por la que calculamos el peso dentro del bucle es porque las áreas brillantes en el centro de la imagen de entrada pueden arrojar fantasmas a los bordes, pero las áreas brillantes en los bordes no pueden arrojar fantasmas al centro.

imagen

La mejora final es el cambio de color radial del fantasma, de acuerdo con la textura 1D:

imagen

Se aplica después del ciclo para afectar el color final del fantasma:

 result *= texture(uLensColor, length(vec2(0.5) - texcoord) / length(vec2(0.5))); 

HALOS (halos)


Si llevamos el vector al centro de la imagen, como en el cálculo fantasma , pero fijamos la longitud del vector, obtenemos un efecto diferente: la imagen original se deforma radialmente:

imagen
Podemos usar esto para crear un "halo" multiplicando el peso por una muestra, limitando así la contribución de la imagen deformada a un anillo cuyo radio está controlado por uHaloWidth :

 // sample halo: vec2 haloVec = normalize(ghostVec) * uHaloWidth; float weight = length(vec2(0.5) - fract(texcoord + haloVec)) / length(vec2(0.5)); weight = pow(1.0 - weight, 5.0); result += texture(uInputTex, texcoord + haloVec) * weight; 

imagen

DISTORCIÓN CROMÁTICA (distorsión de color)


Algunos destellos de lentes tienen una distorsión de color causada por variaciones en la refracción de la luz a diferentes longitudes de onda. Podemos simular esto creando una función que seleccione los canales rojo, verde y azul por separado con desplazamientos ligeramente diferentes a lo largo del vector de muestra:

 vec3 textureDistorted( in sampler2D tex, in vec2 texcoord, in vec2 direction, // direction of distortion in vec3 distortion // per-channel distortion factor ) { return vec3( texture(tex, texcoord + direction * distortion.r).r, texture(tex, texcoord + direction * distortion.g).g, texture(tex, texcoord + direction * distortion.b).b ); } 

Se puede usar como un reemplazo directo para llamar a texture () en la lista anterior. Calculo la dirección y la distorsión de la siguiente manera:

 vec2 texelSize = 1.0 / vec2(textureSize(uInputTex, 0)); vec3 distortion = vec3(-texelSize.x * uDistortion, 0.0, texelSize.x * uDistortion); vec3 direction = normalize(ghostVec); 

Aunque la función de búsqueda es simple, cuesta x3 muestras de la textura, aunque todas deberían ser compatibles con la memoria caché a menos que establezca uDistortion en algún valor gigantesco.

Con la generación de elementos, todo. Aquí está el resultado:

imagen

3. Desenfoque


Sin desenfoque, los elementos de destello de la lente (en particular, los fantasmas) tienden a preservar la apariencia de la imagen. Al agregar desenfoque a los elementos de destello de la lente , debilitamos las altas frecuencias y, por lo tanto, reducimos el contraste con la imagen de entrada, lo que nos ayuda a vender el efecto.

imagen

No diré cómo hacer un desenfoque; Puede leer sobre esto en varios recursos de Internet (desenfoque gaussiano).

4. Mejora / mezcla con la imagen original


Entonces, tenemos nuestros elementos de destello de lente , bien borrosos. ¿Cómo podemos combinarlos con la imagen fuente original? Hay varias consideraciones importantes con respecto a toda la canalización de renderizado:

  • Cualquier desenfoque de movimiento posterior o profundidad de campo se debe aplicar antes de combinar con destello de lente , de modo que los elementos de destello de lente no participen en estos efectos.
  • El destello de la lente debe aplicarse antes de cualquier mapeo de tonos . Esto tiene sentido físico, ya que el mapeo de tonos imita la respuesta de la película / CMOS a la luz entrante, de la cual el destello de la lente es una parte integral.

Con eso en mente, hay un par de cosas que podemos hacer en esta etapa para mejorar el resultado:

DIRECCIÓN DE LENTE


Primero, debe modificar los elementos de destello de lente con una textura sucia en resolución completa (que se usa ampliamente en Battlefield 3):

imagen

 uniform sampler2D uInputTex; // source image uniform sampler2D uLensFlareTex; // input from the blur stage uniform sampler2D uLensDirtTex; // full resolution dirt texture noperspective in vec2 vTexcoord; out vec4 fResult; void main() { vec4 lensMod = texture(uLensDirtTex, vTexcoord); vec4 lensFlare = texture(uLensFlareTex, vTexcoord) * lensMod; fResult = texture(uInputTex, vTexcoord) + lensflare; } 

La clave de esto es la textura muy sucia en las lentes. Si el contraste es bajo, las formas de reflejo de la lente tienden a dominar el resultado. A medida que aumenta el contraste, los elementos de destello de la lente se amortiguan, lo que le da un aspecto estético diferente y también oculta algunos defectos.

DIFRACCIÓN STARBURST


Como una mejora adicional, podemos usar la textura del estallido estelar agregándola a la suciedad de la lente :

imagen
Como textura, el estallido estelar no se ve muy bien. Sin embargo, podemos pasar la matriz de transformación al sombreador, lo que nos permitirá rotar / deformar el estallido estelar en cada cuadro y obtener el efecto dinámico deseado:

 uniform sampler2D uInputTex; // source image uniform sampler2D uLensFlareTex; // input from the blur stage uniform sampler2D uLensDirtTex; // full resolution dirt texture uniform sampler2D uLensStarTex; // diffraction starburst texture uniform mat3 uLensStarMatrix; // transforms texcoords noperspective in vec2 vTexcoord; out vec4 fResult; void main() { vec4 lensMod = texture(uLensDirtTex, vTexcoord); vec2 lensStarTexcoord = (uLensStarMatrix * vec3(vTexcoord, 1.0)).xy; lensMod += texture(uLensStarTex, lensStarTexcoord); vec4 lensFlare = texture(uLensFlareTex, vTexcoord) * lensMod; fResult = texture(uInputTex, vTexcoord) + lensflare; } 

La matriz de transformación uLensStarMatrix se basa en el valor obtenido de la orientación de la cámara de la siguiente manera:

 vec3 camx = cam.getViewMatrix().col(0); // camera x (left) vector vec3 camz = cam.getViewMatrix().col(1); // camera z (forward) vector float camrot = dot(camx, vec3(0,0,1)) + dot(camz, vec3(0,1,0)); 

Hay otras formas de obtener el valor de camrot; Lo más importante, debe cambiar continuamente cuando se gira la cámara. La matriz misma se construye de la siguiente manera:

 mat3 scaleBias1 = ( 2.0f, 0.0f, -1.0f, 0.0f, 2.0f, -1.0f, 0.0f, 0.0f, 1.0f, ); mat3 rotation = ( cos(camrot), -sin(camrot), 0.0f, sin(camrot), cos(camrot), 0.0f, 0.0f, 0.0f, 1.0f ); mat3 scaleBias2 = ( 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, ); mat3 uLensStarMatrix = scaleBias2 * rotation * scaleBias1; 

Las matrices de escala y polarización necesitan desplazamientos de origen de textura para que podamos rotar el estallido estelar en relación con el centro de la imagen.

Conclusión


¡Así que ahora todo! Este método demuestra cómo un proceso posterior relativamente simplificado da un destello de lente de aspecto decente. No es completamente fotorrealista, pero si se usa correctamente, puede producir excelentes resultados.

imagen



UPD
El autor también publicó un artículo con pequeñas optimizaciones.
El código fuente se puede ver aquí y aquí .

Source: https://habr.com/ru/post/439408/


All Articles