Apprenez OpenGL. Leçon 5.7 - HDR


Lors de l'écriture dans le tampon d'image, les valeurs de la luminosité des couleurs sont réduites à l'intervalle de 0,0 à 1,0. Pour cette raison, à première vue inoffensif, nous devons toujours choisir des valeurs d'éclairage et de couleurs qui correspondent à cette restriction. Cette approche fonctionne et donne des résultats décents, mais que se passe-t-il si nous rencontrons une zone particulièrement lumineuse avec beaucoup de sources de lumière vive et que la luminosité totale dépasse 1,0? Par conséquent, toutes les valeurs supérieures à 1,0 seront converties en 1,0, ce qui n'est pas très joli:



Étant donné que les valeurs de couleur sont réduites à 1,0 pour un grand nombre de fragments, de grandes zones de l'image sont remplies de la même couleur blanche, un nombre important de détails de l'image sont perdus et l'image elle-même commence à paraître anormale.


La solution à ce problème peut être de réduire la luminosité des sources lumineuses afin qu'il n'y ait pas de fragments plus lumineux que 1.0 sur la scène: ce n'est pas la meilleure solution, ce qui oblige à utiliser des valeurs d'éclairage irréalistes. La meilleure approche consiste à permettre aux valeurs de luminosité de dépasser temporairement la luminosité de 1,0 et à l'étape finale de changer les couleurs afin que la luminosité revienne dans la plage de 0,0 à 1,0, mais sans perte de détails de l'image.


L'écran de l'ordinateur est capable d'afficher des couleurs avec une luminosité allant de 0,0 à 1,0, mais nous n'avons pas une telle limitation lors du calcul de l'éclairage. En permettant aux couleurs du fragment d'être plus lumineuses que l'unité, nous obtenons une plage de luminosité beaucoup plus élevée pour le travail - HDR (plage dynamique élevée) . Avec hdr, les choses brillantes paraissent brillantes, les choses sombres peuvent être vraiment sombres, et ce faisant, nous verrons les détails.



Initialement, une plage dynamique élevée était utilisée en photographie: le photographe a pris plusieurs photographies identiques de la scène avec différentes expositions, capturant des couleurs de presque n'importe quelle luminosité. La combinaison de ces photos forme une image hdr dans laquelle la plupart des détails deviennent visibles en raison de la combinaison d'images avec différentes pertes d'exposition. Par exemple, ci-dessous sur l'image de gauche, des fragments fortement éclairés de l'image sont clairement visibles (regardez la fenêtre), mais ces détails disparaissent lorsque vous utilisez une exposition élevée. Cependant, l'exposition élevée rend les détails sur les zones sombres de l'image qui n'étaient pas visibles auparavant.



Ceci est similaire au fonctionnement de l'œil humain. Avec un manque de lumière, l'œil s'adapte, de sorte que les détails sombres deviennent clairement visibles, et de même pour les zones claires. On peut dire que l'œil humain dispose d'un contrôle d'exposition automatique, en fonction de la luminosité de la scène.


Le rendu HDR fonctionne de la même manière. Nous autorisons lors du rendu à utiliser une large plage de valeurs de luminosité pour collecter des informations sur les détails clairs et sombres de la scène, et à la fin, nous reconvertirons les valeurs de la plage HDR en LDR (faible plage dynamique, plage de 0 à 1). Cette transformation est appelée cartographie des tons , il existe un grand nombre d'algorithmes visant à conserver la plupart des détails de l'image lors de la conversion en LDR. Ces algorithmes ont souvent un paramètre d'exposition qui leur permet de mieux montrer les zones claires ou sombres de l'image.


L'utilisation du HDR lors du rendu nous permet non seulement de dépasser la plage LDR de 0 à 1 et d'enregistrer plus de détails sur l'image, mais permet également d'indiquer la luminosité réelle des sources lumineuses. Par exemple, le soleil a une luminosité beaucoup plus grande que quelque chose comme une lampe de poche, alors pourquoi ne pas régler le soleil sur cela (par exemple, lui donner une luminosité de 10,0)? Cela nous permettra de mieux régler l'éclairage de la scène avec des paramètres de luminosité plus réalistes, ce qui serait impossible avec un rendu LDR et une plage de luminosité de 0 à 1.


Étant donné que l'affichage n'affiche une luminosité que de 0 à 1, nous sommes obligés de reconvertir la plage de valeurs HDR utilisée en plage du moniteur. Une simple mise à l'échelle de la plage ne sera pas une bonne solution, car les zones claires commenceront à dominer l'image. Cependant, nous pouvons utiliser diverses équations ou courbes pour convertir les valeurs HDR en LDR, ce qui nous donnera un contrôle total sur la luminosité de la scène. Cette transformation est appelée mappage des tons et est la dernière étape du rendu HDR.


Tampons de cadre à virgule flottante


Pour implémenter le rendu HDR, nous avons besoin d'un moyen d'empêcher les valeurs d'être portées à une plage de 0 à 1 à partir du shader de fragment. Si le tampon d'images utilise le format à virgule fixe normalisé (GL_RGB) pour les tampons de couleurs, OpenGL limite automatiquement les valeurs avant de les enregistrer dans le tampon d'images. Cette restriction s'applique à la plupart des formats de tampon d'image, à l'exception des formats à virgule flottante.


Pour stocker des valeurs qui se trouvent en dehors de la plage [0.0..1.0], nous pouvons utiliser un tampon de couleur avec les formats suivants: GL_RGB16F, GL_RGBA16F, GL_RGB32F or GL_RGBA32F . C'est génial pour le rendu hdr. Un tel tampon sera appelé tampon de trame à virgule flottante.


La création d'un tampon à virgule flottante diffère d'un tampon standard uniquement en ce qu'il utilise un format interne différent:


 glBindTexture(GL_TEXTURE_2D, colorBuffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); 

Le framebuffer OpenGL utilise par défaut seulement 8 bits pour stocker chaque couleur. Dans le tampon d' GL_RGB32F à GL_RGB32F GL_RGBA32F formats GL_RGB32F ou GL_RGBA32F , 32 bits sont utilisés pour stocker chaque couleur - 4 fois plus. Si une précision très élevée n'est pas requise, le format GL_RGBA16F sera tout à fait suffisant.


Si un tampon à virgule flottante est attaché au tampon de trame pour la couleur, nous pouvons y rendre la scène en tenant compte du fait que les valeurs de couleur ne seront pas limitées à la plage de 0 à 1. Dans le code de cet article, nous rendons d'abord la scène au tampon de trame à virgule flottante, puis afficher le contenu tampons de couleur sur un rectangle demi-écran. Cela ressemble à ceci:


 glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // [...]    hdr glBindFramebuffer(GL_FRAMEBUFFER, 0); //  hdr    2     hdrShader.use(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, hdrColorBufferTexture); RenderQuad(); 

Ici, les valeurs de couleur contenues dans le tampon de couleur peuvent être supérieures à 1. Pour cet article, une scène a été créée avec un grand cube allongé qui ressemble à un tunnel avec quatre sources lumineuses ponctuelles, l'une d'entre elles est située à l'extrémité du tunnel et a une grande luminosité.


 std::vector<glm::vec3> lightColors; lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f)); lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f)); lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f)); lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f)); 

Le rendu dans le tampon à virgule flottante est exactement le même que si nous rendions la scène dans un tampon d'images normal. La seule nouveauté est le shader hdr fragmenté, qui traite de l'ombrage simple d'un rectangle plein écran avec les valeurs d'une texture, qui est un tampon de couleurs à virgule flottante. Pour commencer, écrivons un shader simple qui transfère les données d'entrée inchangées:


 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D hdrBuffer; void main() { vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; FragColor = vec4(hdrColor, 1.0); } 

Nous prenons l'entrée de la virgule flottante du tampon de couleur et nous l'utilisons comme valeur de sortie du shader. Cependant, étant donné que le rectangle 2D est rendu dans le framebuffer par défaut, les valeurs de sortie du shader seront limitées à un intervalle de 0 à 1, malgré le fait qu'à certains endroits, les valeurs sont supérieures à 1.



Il devient évident que des valeurs de couleur trop grandes à la fin du tunnel sont limitées à l'unité, car une partie importante de l'image est complètement blanche et nous perdons des détails d'image plus brillants que l'unité. Puisque nous utilisons les valeurs HDR directement en tant que LDR, cela équivaut à ne pas avoir de HDR. Pour résoudre ce problème, nous devons afficher les différentes valeurs de couleur dans la plage de 0 à 1 sans perdre aucun détail dans l'image. Pour ce faire, appliquez une compression tonale.


Compression de tonalité


La compression des tons est la conversion des valeurs de couleur pour les adapter dans la plage de 0 à 1 sans perdre les détails de l'image, souvent en combinaison avec donner à l'image la balance des blancs souhaitée.


L'algorithme de mappage de tons le plus simple est connu sous le nom d'algorithme de mappage de tons Reinhard . Il affiche toutes les valeurs HDR dans la plage LDR. Ajoutez cet algorithme au shader de fragment précédent et appliquez également la correction gamma (et l'utilisation de textures SRGB).


 void main() { const float gamma = 2.2; vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; //   vec3 mapped = hdrColor / (hdrColor + vec3(1.0)); // - mapped = pow(mapped, vec3(1.0 / gamma)); FragColor = vec4(mapped, 1.0); } 

Remarque trans. - pour les petites valeurs de x, la fonction x / (1 + x) se comporte approximativement comme x, pour les grands x elle tend vers l'unité. Graphique de fonction:


Avec la compression de tonalité Reinhardt, nous ne perdons plus de détails dans les zones claires de l'image. L'algorithme préfère les zones claires, rendant les zones sombres moins distinctes.



Ici, vous pouvez à nouveau voir les détails à la fin de l'image, tels que la texture du bois. Avec cet algorithme relativement simple, nous pouvons voir clairement toutes les couleurs de la gamme HDR et pouvons contrôler l'éclairage de la scène sans perdre les détails de l'image.


Il convient de noter que nous pouvons utiliser la compression tonale directement à la fin de notre shader pour calculer l'éclairage, et nous n'avons alors pas du tout besoin d'un tampon de trame à virgule flottante. Cependant, dans les scènes plus complexes, vous rencontrerez souvent la nécessité de stocker des valeurs HDR intermédiaires dans des tampons à virgule flottante, donc cela vous sera utile.

Une autre caractéristique intéressante de la compression de tonalité est l'utilisation d'un paramètre d'exposition. Vous vous souvenez peut-être que dans les images au début de l'article, divers détails étaient visibles à différentes valeurs d'exposition. Si nous avons une scène dans laquelle le jour et la nuit changent, il est logique d'utiliser une faible exposition pendant la journée et haute la nuit, ce qui est similaire à l'adaptation de l'œil humain. Avec ce paramètre d'exposition, nous pouvons configurer des paramètres d'éclairage qui fonctionneront jour et nuit dans différentes conditions d'éclairage.


Un algorithme de compression tonale relativement simple avec exposition ressemble à ceci:


 uniform float exposure; void main() { const float gamma = 2.2; vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; //     vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); // - mapped = pow(mapped, vec3(1.0 / gamma)); FragColor = vec4(mapped, 1.0); } 

Remarque par: ajouter un graphique pour cette fonction avec les expositions 1 et 2:


Ici, nous avons défini une variable pour l'exposition, qui est 1 par défaut et nous permet de choisir plus précisément l'équilibre entre la qualité d'affichage des zones sombres et lumineuses de l'image. Par exemple, avec une grande exposition, nous voyons beaucoup plus de détails dans les zones sombres de l'image. À l'inverse, une faible exposition rend les zones sombres indiscernables, mais vous permet de mieux voir les zones claires de l'image. Ci-dessous sont des images d'un tunnel avec différents niveaux d'exposition.



Ces images montrent clairement les avantages du rendu hdr. À mesure que le niveau d'exposition change, nous voyons plus de détails de la scène qui seraient perdus dans le rendu normal. Prenons l'exemple du bout du tunnel - avec une exposition normale, la texture de l'arbre est à peine visible, mais avec une exposition faible, la texture est parfaitement visible. De même, à forte exposition, les détails dans les zones sombres sont très clairement visibles.


Le code source de la démo est ici.


Plus de HDR


Ces deux algorithmes de compression de tonalité qui ont été montrés ne sont qu'une petite partie parmi un grand nombre d'algorithmes plus avancés, chacun ayant ses propres forces et faiblesses. Certains algorithmes mettent mieux en valeur certaines couleurs / luminosités, certains algorithmes montrent des zones sombres et lumineuses en même temps, donnant des images plus colorées et plus détaillées. Il existe également de nombreuses méthodes connues sous le nom d'ajustement automatique de l'exposition ou d' adaptation oculaire . Ils déterminent la luminosité de la scène dans l'image précédente et modifient (lentement) le paramètre d'exposition, de sorte que la scène sombre devient lentement plus lumineuse et la lumière - plus sombre: semblable à l'habituation de l'œil humain.


Les avantages réels du HDR sont mieux visibles sur les scènes grandes et complexes avec des algorithmes d'éclairage sérieux. À des fins de formation, cet article a utilisé la scène la plus simple possible, car la création d'une grande scène peut être difficile. Malgré la simplicité de la scène, certains avantages du rendu hdr sont visibles: dans les zones sombres et lumineuses de l'image, les détails ne sont pas perdus, car ils sont enregistrés à l'aide de la compression des tons, l'ajout de plusieurs sources de lumière n'entraîne pas l'apparition de zones blanches et les valeurs n'ont pas à tenir dans LDR gamme.


De plus, le rendu HDR rend également certains effets intéressants plus crédibles et réalistes. L'un de ces effets est la floraison, dont nous parlerons dans un prochain article .


Ressources supplémentaires:



PS Nous avons un télégramme conf pour la coordination des transferts. Si vous avez un sérieux désir d'aider à la traduction, alors vous êtes les bienvenus!

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


All Articles