Aprenda o OpenGL. Lição 5.7 - HDR


Ao gravar no buffer de moldura, os valores do brilho das cores são reduzidos para o intervalo de 0,0 a 1,0. Por esse motivo, à primeira vista, um recurso inofensivo, sempre temos que escolher esses valores para iluminação e cores que se encaixem nessa restrição. Essa abordagem funciona e fornece resultados decentes, mas o que acontece se encontrarmos uma área particularmente brilhante com muitas fontes de luz brilhante e o brilho total exceder 1,0? Como resultado, todos os valores maiores que 1,0 serão convertidos para 1,0, o que não parece muito bom:



Como os valores das cores são reduzidos a 1,0 para um grande número de fragmentos, grandes áreas da imagem são preenchidas com a mesma cor branca, um número significativo de detalhes da imagem é perdido e a própria imagem começa a parecer antinatural.


A solução para esse problema pode ser reduzir o brilho das fontes de luz para que não haja fragmentos mais brilhantes que 1,0 no palco: essa não é a melhor solução, o que força o uso de valores de iluminação irrealistas. A melhor abordagem é permitir que os valores de brilho excedam temporariamente o brilho de 1,0 e, na etapa final, altere as cores para que o brilho retorne ao intervalo de 0,0 a 1,0, mas sem perda de detalhes da imagem.


A tela do computador é capaz de mostrar cores com brilho variando de 0,0 a 1,0, mas não temos essa limitação ao calcular a iluminação. Ao permitir que as cores do fragmento sejam mais brilhantes que a unidade, obtemos uma faixa de brilho muito maior para o trabalho - HDR (alta faixa dinâmica) . Com hdr, as coisas brilhantes parecem brilhantes, as coisas escuras podem ser realmente escuras e, ao fazê-lo, veremos os detalhes.



Inicialmente, uma alta faixa dinâmica foi usada na fotografia: o fotógrafo tirou várias fotografias idênticas da cena com diferentes exposições, capturando cores de quase qualquer brilho. A combinação dessas fotos forma uma imagem hdr na qual a maioria dos detalhes se torna visível devido à combinação de imagens com diferentes perdas de exposição. Por exemplo, abaixo, na imagem esquerda, fragmentos altamente iluminados da imagem são claramente visíveis (observe a janela), mas esses detalhes desaparecem ao usar alta exposição. No entanto, a alta exposição faz os detalhes em áreas escuras da imagem que não eram visíveis antes.



Isso é semelhante ao funcionamento do olho humano. Com a falta de luz, o olho se adapta, para que os detalhes escuros se tornem claramente visíveis, e da mesma forma para áreas claras. Pode-se dizer que o olho humano possui um controle de exposição automático, dependendo do brilho da cena.


A renderização HDR funciona da mesma maneira. Quando renderizamos, permitimos o uso de uma grande variedade de valores de brilho para coletar informações sobre os detalhes claros e escuros da cena e, no final, converteremos os valores da faixa HDR de volta para LDR (baixa faixa dinâmica, faixa de 0 a 1). Essa transformação é chamada de mapeamento de tons ; há um grande número de algoritmos destinados a preservar a maioria dos detalhes da imagem ao converter para LDR. Esses algoritmos geralmente têm uma configuração de exposição que lhes permite mostrar melhor as áreas claras ou escuras da imagem.


O uso do HDR durante a renderização nos permite não apenas exceder o intervalo de LDR de 0 a 1 e salvar mais detalhes da imagem, mas também possibilita indicar o brilho real das fontes de luz. Por exemplo, o sol tem um brilho de luz muito maior do que algo como uma lanterna; então, por que não definir o sol para isso (por exemplo, dê um brilho de 10,0)? Isso nos permitirá ajustar melhor a iluminação da cena com parâmetros de brilho mais realistas, o que seria impossível com a renderização LDR e um intervalo de brilho de 0 a 1.


Como a tela mostra brilho apenas de 0 a 1, somos forçados a converter a faixa de valores HDR usada de volta à faixa do monitor. Simplesmente escalar o alcance não será uma boa solução, pois as áreas brilhantes começarão a dominar a imagem. No entanto, podemos usar várias equações ou curvas para converter os valores HDR em LDR, o que nos dará controle total sobre o brilho da cena. Essa transformação é chamada de mapeamento de tons e é a etapa final na renderização HDR.


Buffers de quadro de ponto flutuante


Para implementar a renderização HDR, precisamos de uma maneira de impedir que valores sejam trazidos para um intervalo de 0 a 1 a partir do shader de fragmento. Se o framebuffer usar o formato de ponto fixo normalizado (GL_RGB) para buffers de cores, o OpenGL limitará automaticamente os valores antes de salvar no framebuffer. Essa restrição se aplica à maioria dos formatos de buffer de quadros, exceto os formatos de ponto flutuante.


Para armazenar valores que estão fora do intervalo [0,0..1,0], podemos usar um buffer de cores com os seguintes formatos: GL_RGB16F, GL_RGBA16F, GL_RGB32F or GL_RGBA32F . Isso é ótimo para renderização hdr. Esse buffer será chamado de buffer de quadro de ponto flutuante.


A criação de um buffer de ponto flutuante difere de um buffer regular apenas no fato de usar um formato interno diferente:


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

O buffer de estrutura do OpenGL, por padrão, usa apenas 8 bits para armazenar cada cor. No buffer de GL_RGB32F ponto flutuante GL_RGBA32F formatos GL_RGBA32F ou GL_RGBA32F , 32 bits são usados ​​para armazenar cada cor - 4 vezes mais. Se uma precisão muito alta não for necessária, o formato GL_RGBA16F será suficiente.


Se um buffer de ponto flutuante estiver anexado ao buffer de moldura da cor, podemos renderizar a cena, levando em consideração que os valores das cores não serão limitados ao intervalo de 0 a 1. No código deste artigo, primeiro renderizamos a cena no buffer de moldura de ponto flutuante e, em seguida, exibimos o conteúdo buffers de cores em um retângulo de meia tela. Parece algo como isto:


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

Aqui, os valores de cores contidos no buffer de cores podem ser maiores que 1. Para este artigo, foi criada uma cena com um grande cubo alongado que se parece com um túnel com quatro fontes pontuais de luz, uma das quais está localizada no final do túnel e possui grande brilho.


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

A renderização para o buffer de ponto flutuante é exatamente a mesma como se estivéssemos renderizando a cena em um buffer de quadro comum. A única novidade é o sombreador hdr fragmentado, que lida com um sombreamento simples de um retângulo de tela cheia com valores de uma textura, que é um buffer de cores de ponto flutuante. Para começar, vamos escrever um shader simples que transfira os dados de entrada inalterados:


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

Pegamos a entrada do ponto flutuante do buffer de cores e a usamos como o valor de saída do shader. No entanto, como o retângulo 2D é renderizado no buffer de quadros por padrão, os valores de saída do sombreador serão limitados a um intervalo de 0 a 1, apesar do fato de que em alguns locais os valores são maiores que 1.



Torna-se óbvio que valores de cores muito grandes no final do túnel são limitados à unidade, pois uma parte significativa da imagem é completamente branca e perdemos detalhes da imagem que são mais brilhantes que a unidade. Como usamos os valores HDR diretamente como LDR, isso equivale a não ter HDR. Para corrigir isso, é preciso exibir os diferentes valores de cores no intervalo de 0 a 1 sem perder nenhum detalhe na imagem. Para fazer isso, aplique a compactação tonal.


Compressão de tom


A compactação de tom é a conversão de valores de cores para ajustá-los no intervalo de 0 a 1 sem perder os detalhes da imagem, geralmente em combinação com o balanço de branco desejado para a imagem.


O algoritmo de mapeamento de tons mais simples é conhecido como algoritmo de mapeamento de tons Reinhard . Ele exibe quaisquer valores de HDR na faixa de LDR. Adicione esse algoritmo ao shader de fragmento anterior e aplique a correção gama (e o uso de texturas 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); } 

Nota trans. - para valores pequenos de x, a função x / (1 + x) se comporta aproximadamente como x, para x grande tende a se unir. Gráfico de funções:


Com a compactação de tom Reinhardt, não perdemos mais os detalhes nas áreas claras da imagem. O algoritmo prefere áreas claras, tornando as áreas escuras menos distintas.



Aqui você pode novamente ver detalhes no final da imagem, como textura de madeira. Com esse algoritmo relativamente simples, podemos ver claramente qualquer cor da faixa HDR e controlar a iluminação da cena sem perder os detalhes da imagem.


Vale a pena notar que podemos usar a compressão tonal diretamente no final do nosso sombreador para calcular a iluminação e, portanto, não precisamos de um buffer de quadros de ponto flutuante. No entanto, em cenas mais complexas, você frequentemente encontrará a necessidade de armazenar valores intermediários de HDR em buffers de ponto flutuante, portanto, isso será útil.

Outra característica interessante da compactação de tom é o uso de um parâmetro de exposição. Lembre-se de que nas imagens no início do artigo vários detalhes eram visíveis em diferentes valores de exposição. Se temos uma cena em que dia e noite mudam, faz sentido usar baixa exposição durante o dia e alta à noite, o que é semelhante à adaptação do olho humano. Com este parâmetro de exposição, podemos configurar parâmetros de iluminação que funcionarão dia e noite sob diferentes condições de iluminação.


Um algoritmo de compactação tonal relativamente simples com exposição é semelhante a este:


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

Nota por: adicione um gráfico para esta função com as exposições 1 e 2:


Aqui, definimos uma variável para a exposição, que é 1 por padrão e nos permite escolher com mais precisão o equilíbrio entre a qualidade da exibição das áreas escuras e brilhantes da imagem. Por exemplo, com uma grande exposição, vemos muito mais detalhes nas áreas escuras da imagem. Por outro lado, a baixa exposição torna as áreas escuras indistinguíveis, mas permite ver melhor as áreas claras da imagem. Abaixo estão as imagens de um túnel com diferentes níveis de exposição.



Essas imagens mostram claramente os benefícios da renderização hdr. À medida que o nível de exposição muda, vemos mais detalhes da cena que seriam perdidos na renderização normal. Tome o fim do túnel como exemplo - com uma exposição normal, a textura da árvore é pouco visível, mas com baixa exposição, a textura é perfeitamente visível. Da mesma forma, em alta exposição, detalhes em áreas escuras são muito claramente visíveis.


O código fonte da demonstração está aqui.


Mais HDR


Esses algoritmos de compressão de dois tons mostrados são apenas uma pequena parte de um grande número de algoritmos mais avançados, cada um com suas próprias forças e fraquezas. Alguns algoritmos enfatizam melhor determinadas cores / brilho, alguns algoritmos mostram áreas escuras e brilhantes ao mesmo tempo, fornecendo imagens mais coloridas e detalhadas. Existem também muitos métodos conhecidos como ajuste automático da exposição ou adaptação ocular . Eles determinam o brilho da cena no quadro anterior e (lentamente) alteram o parâmetro de exposição, para que a cena escura fique mais brilhante e a mais escura: semelhante à habituação do olho humano.


Os benefícios reais do HDR são vistos melhor em cenas grandes e complexas com algoritmos de iluminação sérios. Para fins de treinamento, este artigo usou a cena mais simples possível, pois a criação de uma cena grande pode ser difícil. Apesar da simplicidade da cena, algumas vantagens da renderização hdr são visíveis: nas áreas escuras e brilhantes da imagem, os detalhes não são perdidos, pois são salvos usando a compactação de tons, a adição de várias fontes de luz não leva ao aparecimento de áreas brancas e os valores não precisam se encaixar no LDR alcance.


Além disso, a renderização HDR também torna alguns efeitos interessantes mais críveis e realistas. Um desses efeitos é o bloom, que discutiremos em um artigo futuro.


Recursos adicionais:



PS Temos um telegrama conf para coordenação de transferências. Se você tem um desejo sério de ajudar com a tradução, é bem-vindo!

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


All Articles