Pseudo Lens Flare

Bonjour, Habr! Je vous présente la traduction de l'article «Pseudo Lens Flare» de John Chapman.

image

La lumière parasite (lens flare) est un artefact photographique résultant de la diffusion et de la réfraction de la lumière dans un système de lentilles. Bien qu'il s'agisse d'un artefact, il existe de nombreuses raisons d'utiliser la lumière parasite dans l'infographie:

  • il augmente la luminosité perçue et la plage dynamique visible de l'image.
  • la lumière parasite se retrouve souvent sur les photographies, son absence peut donc être frappante
  • il peut jouer un rôle important dans le style ou le drame, ou il peut faire partie du gameplay dans les jeux (imaginez l'éblouissement aveuglant un joueur)

Traditionnellement, la lumière parasite en temps réel a été mise en œuvre à l'aide de technologies basées sur les sprites. Bien que les sprites donnent des résultats facilement contrôlables et très réalistes, ils doivent être placés explicitement et nécessitent des données d'occlusion pour s'afficher correctement. Je décrirai ici un effet d'espace d'écran simple et relativement bon marché qui crée une pseudo- lumière parasite à partir du tampon de couleur d'entrée. Il n'est pas basé sur la physique, donc le résultat est légèrement différent de celui photoréaliste, mais il peut être utilisé en combinaison avec (ou en remplacement) pour des effets traditionnels à base de sprite.

Algorithme


Se compose de 4 étapes:

  1. Sous-échantillonnage / seuil.
  2. Génération d'éléments de lumière parasite .
  3. Flou
  4. Haut de gamme / mélange avec l'image d'origine.

1. Sous-échantillonnage / seuil


Sous - échantillonnage - optimisation pour réduire le coût des étapes suivantes. De plus, nous voulons sélectionner un sous-ensemble des pixels les plus brillants de l'image d'origine. L'utilisation de l' échelle / biais (échelle / biais) offre un moyen flexible d'y parvenir:

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; } 

image

Le réglage de l' échelle / du biais est le principal moyen d'ajuster l'effet; les meilleurs paramètres dépendront de la plage dynamique du tampon de couleur, ainsi que de la finesse du résultat. En raison du fait que la technique est une approximation, la subtilité est plus susceptible de mieux paraître.

2. Génération d'éléments de lumière parasite


Les éléments parasites de l'objectif ont tendance à tourner autour du centre de l'image. En simulant cet effet, nous pouvons étendre le résultat de l'étape précédente horizontalement / verticalement. Ceci est facile à faire au stade de la génération d'élément en développant les coordonnées de texture:

 vec2 texcoord = -vTexcoords + vec2(1.0); 

Ce n'est pas nécessaire; la génération d'éléments fonctionne très bien avec et sans elle. Cependant, le résultat du changement des coordonnées de texture permet de séparer visuellement l'effet de lumière parasite de l'image d'origine.

Fantômes


Les « fantômes » (fantômes) sont des reflets répétitifs qui reflètent les zones lumineuses du tampon de couleur, se déployant par rapport au centre de l'image. L'approche que j'ai choisie de générer consiste à obtenir un vecteur du pixel actuel au centre de l'écran, puis à effectuer plusieurs sélections le long de ce vecteur.

image

 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; } 

Notez que j'utilise fract () pour m'assurer que les coordonnées de texture s'enroulent; de manière équivalente, vous pouvez utiliser le mode d' habillage GL_REPEAT pour la texture.

Voici le résultat:

image

Vous pouvez améliorer le résultat en autorisant uniquement les zones claires proches du centre de l'image à générer des fantômes. Nous pouvons y parvenir en ajoutant des poids qui diminueront à partir du centre pour les échantillons:

 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 fonction de poids est aussi simple que possible - linéaire. La raison pour laquelle nous calculons le poids à l'intérieur de la boucle est que les zones lumineuses au centre de l'image d'entrée peuvent «projeter» des fantômes vers les bordures, mais les zones lumineuses aux frontières ne peuvent pas projeter des fantômes vers le centre.

image

La dernière amélioration est le changement de couleur radial du fantôme, conformément à la texture 1D:

image

Il est appliqué après le cycle pour affecter la couleur finale du fantôme:

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

HALOS (halos)


Si nous prenons le vecteur au centre de l'image, comme dans le calcul du fantôme , mais fixons la longueur du vecteur, nous obtenons un effet différent: l'image originale est déformée radialement:

image
On peut l'utiliser pour créer un «halo» en multipliant le poids par un échantillon, limitant ainsi la contribution de l'image déformée à un anneau dont le rayon est contrôlé par 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; 

image

DISTORSION CHROMATIQUE (distorsion des couleurs)


Certains reflets ont une distorsion de couleur causée par des variations de la réfraction de la lumière à différentes longueurs d'onde. Nous pouvons simuler cela en créant une fonction qui sélectionne les canaux rouge, vert et bleu séparément avec des décalages légèrement différents le long du vecteur échantillon:

 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 ); } 

Il peut être utilisé en remplacement direct de l'appel de texture () dans la liste précédente. Je calcule la direction et la distorsion comme suit:

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

Bien que la fonction d'extraction soit simple, elle coûte x3 échantillons de la texture, bien qu'ils devraient tous être compatibles avec le cache, sauf si vous définissez uDistortion sur une valeur gigantesque.

Avec la génération d'éléments, tout. Voici le résultat:

image

3. Flou


Sans flou, les éléments parasites de l'objectif (en particulier les fantômes) ont tendance à préserver l'apparence de l'image. En ajoutant du flou aux éléments de la lumière parasite , nous affaiblissons les hautes fréquences et réduisons ainsi le contraste avec l'image d'entrée, ce qui nous aide à vendre l'effet.

image

Je ne dirai pas comment faire un flou; Vous pouvez le lire sur diverses ressources Internet (flou gaussien).

4. Haut de gamme / mélange avec l'image d'origine


Donc, nous avons nos éléments de flare , bien flous. Comment pouvons-nous les combiner avec l'image source d'origine? Il existe plusieurs considérations importantes concernant l'ensemble du pipeline de rendu:

  • Tout flou de mouvement ou profondeur de champ subséquent doit être appliqué avant de combiner avec la lumière parasite , de sorte que les éléments de lumière parasite ne participeront pas à ces effets.
  • La lumière parasite doit être appliquée avant toute cartographie tonale . Cela a un sens physique, car la cartographie tonale imite la réponse du film / CMOS à la lumière entrante, dont la lumière parasite est un composant.

Dans cet esprit, il y a deux choses que nous pouvons faire à ce stade pour améliorer le résultat:

LENS DIRT


Tout d'abord, vous devez modifier les éléments de la lumière parasite avec une texture sale en pleine résolution (ce qui est largement utilisé dans Battlefield 3):

image

 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 clé de tout cela est la texture très sale de la lentille. Si le contraste est faible, les formes de lumière parasite ont tendance à dominer le résultat. À mesure que le contraste augmente, les éléments de la lumière parasite sont étouffés, ce qui donne un aspect esthétique différent et cache également certains défauts.

DIFFRACTION STARBURST


Comme amélioration supplémentaire, nous pouvons utiliser la texture Starburst en l'ajoutant à la saleté de la lentille :

image
En tant que texture, Starburst n'a pas l'air très bien. Néanmoins, nous pouvons passer la matrice de transformation au shader, ce qui nous permettra de faire pivoter / déformer Starburst à chaque image et d'obtenir l'effet dynamique souhaité:

 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 matrice de transformation uLensStarMatrix est basée sur la valeur obtenue à partir de l'orientation de la caméra comme suit:

 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)); 

Il existe d'autres façons d'obtenir la valeur du camrot; plus important encore, il devrait changer en continu lorsque l'appareil photo est tourné. La matrice elle-même est construite comme suit:

 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; 

Les matrices d' échelle et de biais nécessitent des décalages d'origine de la texture afin que nous puissions faire pivoter l' étoile par rapport au centre de l'image.

Conclusion


Alors maintenant tout! Cette méthode montre comment un post-processus relativement simplifié donne une lumière parasite d'aspect décent. Il n'est pas entièrement photoréaliste, mais s'il est utilisé correctement, il peut produire d'excellents résultats.

image



UPD
L'auteur a également publié un article avec des optimisations mineures.
Le code source peut être vu ici et ici .

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


All Articles