Aprende OpenGL. Lección 5.9 - Representación retrasada


En artículos anteriores, utilizamos iluminación directa (renderizado hacia adelante o sombreado hacia adelante) . Este es un enfoque simple en el que dibujamos un objeto teniendo en cuenta todas las fuentes de luz, luego dibujamos el siguiente objeto junto con toda la iluminación, y así sucesivamente para cada objeto. Es bastante simple de entender e implementar, pero al mismo tiempo resulta bastante lento desde el punto de vista del rendimiento: para cada objeto debe clasificar todas las fuentes de luz. Además, la iluminación directa funciona de manera ineficiente en escenas con una gran cantidad de objetos superpuestos entre sí, ya que la mayoría de los cálculos del sombreador de píxeles no son útiles y se sobrescribirán con valores para objetos más cercanos.


La iluminación diferida o el sombreado diferido o la representación diferida omiten este problema y cambian drásticamente la forma en que dibujamos los objetos. Esto brinda nuevas oportunidades para optimizar significativamente las escenas con una gran cantidad de fuentes de luz, lo que le permite dibujar cientos e incluso miles de fuentes de luz a una velocidad aceptable. A continuación se muestra una escena con 1847 fuentes puntuales de luz dibujadas con iluminación diferida (imagen cortesía de Hannes Nevalainen). Algo como esto sería imposible con un cálculo directo de la iluminación:


img1



La idea de la iluminación diferida es que diferimos las partes más complejas computacionalmente (como la iluminación) para más adelante. La iluminación diferida consta de dos pasos: en el primer paso, el paso de geometría (paso de geometría) , se dibuja toda la escena y se almacena diversa información en un conjunto de texturas llamado G-buffer. Por ejemplo: posiciones, colores, normales y / o reflejo de superficie para cada píxel. La información gráfica almacenada en el G-buffer se usa luego para calcular la iluminación. El siguiente es el contenido del G-buffer para un cuadro:


img2


En el segundo pase, llamado pase de iluminación, usamos las texturas del G-buffer cuando dibujamos el rectángulo de pantalla completa. En lugar de usar sombreadores de vértices y fragmentos por separado para cada objeto, dibujamos la escena completa píxel por píxel. El cálculo de la iluminación permanece exactamente igual que con un pase directo, pero tomamos los datos necesarios solo del G-buffer y los sombreadores variables (uniformes) , y no del sombreador de vértices.


La imagen a continuación muestra bien el proceso de dibujo general.


img3


La principal ventaja es que la información almacenada en el G-buffer pertenece a los fragmentos más cercanos que no están ocultos por nada: la prueba de profundidad solo los deja. Gracias a esto, calculamos la iluminación para cada píxel solo una vez, sin hacer demasiado trabajo. Además, la iluminación diferida nos brinda oportunidades para nuevas optimizaciones, lo que nos permite utilizar muchas más fuentes de luz que en la iluminación directa.


Sin embargo, hay un par de inconvenientes: el G-buffer almacena una gran cantidad de información sobre la escena. Además, los datos del tipo de posición deben almacenarse con alta precisión, como resultado, el búfer G ocupa bastante espacio en la memoria. Otro inconveniente es que no podremos usar objetos translúcidos (ya que el búfer almacena información solo para la superficie más cercana) y el suavizado como MSAA tampoco funcionará. Existen varias soluciones para resolver estos problemas, se analizan al final del artículo.


(Note lane. - El G-buffer ocupa mucho espacio en la memoria. Por ejemplo, para una pantalla de 1920 * 1080 y con 128 bits por píxel, el búfer tomará 33mb. Hay requisitos crecientes para el ancho de banda de la memoria, hay muchos más datos escritos y leídos)


G-buffer


G-buffer se refiere a las texturas utilizadas para almacenar información relacionada con la iluminación utilizada en la última pasada de representación. Veamos qué información necesitamos para calcular la iluminación para el renderizado directo:


  • Vector de posición 3D: se utiliza para averiguar la posición del fragmento en relación con la cámara y las fuentes de luz.
  • Color difuso del fragmento (reflectividad para rojo, verde y azul, en general, color).
  • Vector normal 3d (para determinar a qué ángulo cae la luz en la superficie)
  • flotador para almacenar el componente espejo
  • La posición de la fuente de luz y su color.
  • Posición de la cámara.

Usando estas variables, podemos calcular la cobertura usando el modelo Blinn-Fong que ya conocemos. El color y la posición de la fuente de luz, así como la posición de la cámara pueden ser variables comunes, pero el resto de los valores serán diferentes para cada fragmento de imagen. Si pasamos exactamente los mismos datos al paso final de iluminación diferida, que usaríamos para un pase directo, obtendremos el mismo resultado, a pesar de que dibujaremos fragmentos en un rectángulo 2D regular.


OpenGL no tiene restricciones sobre lo que podemos almacenar en una textura, por lo que tiene sentido almacenar toda la información en una o más texturas del tamaño de una pantalla (llamada G-buffer) y usarlas todas en el pase de iluminación. Dado que el tamaño de las texturas y la pantalla es el mismo, obtenemos los mismos datos de entrada que en la iluminación directa.


En pseudocódigo, la imagen general se ve así:


while(...) // render loop { // 1.  :  /    g- glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); gBufferShader.use(); for(Object obj : Objects) { ConfigureShaderTransformsAndUniforms(); obj.Draw(); } // 2.  :  g-     glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT); lightingPassShader.use(); BindAllGBufferTextures(); SetLightingUniforms(); RenderQuad(); } 

Información necesaria para cada píxel: vector de posición , vector normal , vector de color y valor para el componente espejo . En el paso geométrico, dibujamos todos los objetos en la escena y guardamos todos estos datos en el G-buffer. Podemos usar múltiples objetivos de renderizado para llenar todos los buffers en un sorteo, este enfoque se discutió en el artículo anterior sobre la implementación del brillo: Bloom , traducción en el centro


Para el pase geométrico, cree un framebuffer con el nombre obvio gBuffer, al que le agregaremos varios buffers de color y un buffer de profundidad. Para almacenar posiciones y valores normales, es preferible utilizar una textura con alta precisión (valores flotantes de 16 o 32 bits para cada componente), almacenaremos el color difuso y los valores especulares en la textura de forma predeterminada (precisión de 8 bits por componente).


 unsigned int gBuffer; glGenFramebuffers(1, &gBuffer); glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); unsigned int gPosition, gNormal, gColorSpec; //   glGenTextures(1, &gPosition); glBindTexture(GL_TEXTURE_2D, gPosition); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0); //   glGenTextures(1, &gNormal); glBindTexture(GL_TEXTURE_2D, gNormal); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0); //    +    glGenTextures(1, &gAlbedoSpec); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0); //  OpenGL,        unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments); //           . [...] 

Como usamos varios objetivos de representación, debemos decirle explícitamente a OpenGL a qué búferes del GBuffer adjunto vamos a dibujar en glDrawBuffers() . También vale la pena señalar que almacenamos posiciones y las normales tienen 3 componentes cada una, y las almacenamos en texturas RGB. Pero al mismo tiempo, inmediatamente colocamos la misma textura RGBA tanto en el color como en el coeficiente de reflexión especular; gracias a esto, usamos un buffer menos. Si su implementación de renderizado diferido se vuelve más compleja y utiliza más datos, puede encontrar fácilmente nuevas formas de combinar datos y organizarlos en texturas.


En el futuro, debemos procesar los datos en el G-buffer. Si cada objeto tiene color, normal y coeficiente de reflexión especular, podemos escribir algo como el siguiente sombreador:


 #version 330 core layout (location = 0) out vec3 gPosition; layout (location = 1) out vec3 gNormal; layout (location = 2) out vec4 gAlbedoSpec; in vec2 TexCoords; in vec3 FragPos; in vec3 Normal; uniform sampler2D texture_diffuse1; uniform sampler2D texture_specular1; void main() { //       G- gPosition = FragPos; //          G- gNormal = normalize(Normal); //   gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb; //       gAlbedoSpec.a = texture(texture_specular1, TexCoords).r; } 

Como utilizamos varios objetivos de representación, con la ayuda del layout indicamos qué y en qué búfer del framebuffer actual representamos. Tenga en cuenta que no almacenamos el coeficiente espejo en un búfer separado, ya que podemos almacenar el valor flotante en el canal alfa de uno de los búferes.


Tenga en cuenta que al calcular la iluminación es extremadamente importante almacenar todas las variables en el mismo espacio de coordenadas, en este caso almacenamos (y realizamos cálculos) en el espacio del mundo.

Si ahora procesamos varios nanosuits en un G-buffer y dibujamos su contenido proyectando cada buffer en un cuarto de la pantalla, veremos algo como esto:


img4


Intente visualizar la posición y los vectores normales y asegúrese de que sean correctos. Por ejemplo, los vectores normales que apuntan a la derecha serán rojos. Del mismo modo con los objetos ubicados a la derecha del centro de la escena. Una vez que esté satisfecho con el contenido del G-buffer, pasemos a la siguiente parte: el paso de la iluminación.


Paso de iluminación


Ahora que tenemos una gran cantidad de información en el G-buffer, podemos calcular completamente la iluminación y los colores finales para cada píxel del G-buffer, utilizando su contenido como entrada para algoritmos de cálculo de iluminación. Dado que los valores del G-buffer representan solo fragmentos visibles, realizaremos cálculos complejos de iluminación exactamente una vez para cada píxel. Debido a esto, la iluminación diferida es bastante efectiva, especialmente en escenas complejas, en las que, cuando se procesa directamente para cada píxel, a menudo es necesario calcular la iluminación varias veces.


Para el paso de la iluminación, vamos a representar un rectángulo de pantalla completa (un poco como el efecto de procesamiento posterior) y realizar un cálculo lento de la iluminación para cada píxel.


 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gPosition); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gNormal); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); //         shaderLightingPass.use(); SendAllLightUniformsToShader(shaderLightingPass); shaderLightingPass.setVec3("viewPos", camera.Position); RenderQuad(); 

Vinculamos todas las texturas de G-buffer necesarias antes de renderizar y, además, establecemos los valores variables relacionados con la iluminación en el sombreador.


El sombreador de pasaje de fragmentos es muy similar al que usamos en las lecciones de la reunión. Fundamentalmente nuevo es la forma en que obtenemos información para la iluminación directamente desde el G-buffer.


 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D gPosition; uniform sampler2D gNormal; uniform sampler2D gAlbedoSpec; struct Light { vec3 Position; vec3 Color; }; const int NR_LIGHTS = 32; uniform Light lights[NR_LIGHTS]; uniform vec3 viewPos; void main() { //    G- vec3 FragPos = texture(gPosition, TexCoords).rgb; vec3 Normal = texture(gNormal, TexCoords).rgb; vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb; float Specular = texture(gAlbedoSpec, TexCoords).a; //     vec3 lighting = Albedo * 0.1; //    vec3 viewDir = normalize(viewPos - FragPos); for(int i = 0; i < NR_LIGHTS; ++i) { //   vec3 lightDir = normalize(lights[i].Position - FragPos); vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color; lighting += diffuse; } FragColor = vec4(lighting, 1.0); } 

El sombreador de iluminación acepta 3 texturas que contienen toda la información registrada en el pasaje geométrico y en la que consiste el G-buffer. Si tomamos la entrada para la iluminación a partir de texturas, obtenemos exactamente los mismos valores que con el renderizado directo normal. Al comienzo del sombreador de fragmentos, obtenemos los valores relacionados con las variables de iluminación simplemente leyendo la textura. Tenga en cuenta que obtenemos tanto el color como el coeficiente de reflexión especular de una textura: gAlbedoSpec .


Dado que para cada fragmento hay valores (así como variables de sombreado uniformes) necesarios para calcular la iluminación de acuerdo con el modelo Blinn-Fong, no necesitamos cambiar el código de cálculo de iluminación. Lo único que se ha cambiado es la forma de obtener los valores de entrada.


Comenzar una demostración simple con 32 fuentes de luz pequeñas se parece a esto:


img5


Una de las desventajas de la iluminación diferida es la imposibilidad de mezclar, ya que todos los g-buffers para cada píxel contienen información sobre una sola superficie, mientras que la mezcla usa combinaciones de varios fragmentos. (Mezcla) , traducción . Otra desventaja de la iluminación diferida es que te obliga a usar un método común para calcular la iluminación de todos los objetos; aunque esta limitación de alguna manera se puede eludir agregando información de material al g-buffer.


Para hacer frente a estas deficiencias (especialmente la falta de mezcla), a menudo dividen el renderizado en dos partes: renderizado con iluminación diferida, y la segunda parte con renderizado directo destinado a aplicar algo a la escena o usar sombreadores que no son compatibles con la iluminación diferida. (Nota de los ejemplos: agregar humo translúcido, fuego, vidrio) Para ilustrar el trabajo, dibujaremos las fuentes de luz como pequeños cubos usando renderizado directo, ya que los cubos de iluminación requieren un sombreador especial (brillan uniformemente en el mismo color).


Combina renderizado diferido con directo.


Suponga que queremos dibujar cada fuente de luz en forma de cubo 3D con un centro que coincida con la posición de la fuente de luz y que emita luz con el color de la fuente. La primera idea que viene a la mente es renderizar cubos directamente para cada fuente de luz sobre los resultados de renderización diferidos. Es decir, dibujamos cubos como de costumbre, pero solo después del renderizado diferido. El código se verá así:


 //    [...] RenderQuad(); //          shaderLightBox.use(); shaderLightBox.setMat4("projection", projection); shaderLightBox.setMat4("view", view); for (unsigned int i = 0; i < lightPositions.size(); i++) { model = glm::mat4(); model = glm::translate(model, lightPositions[i]); model = glm::scale(model, glm::vec3(0.25f)); shaderLightBox.setMat4("model", model); shaderLightBox.setVec3("lightColor", lightColors[i]); RenderCube(); } 

Estos cubos renderizados no tienen en cuenta los valores de profundidad del renderizado diferido y, como resultado, siempre se dibujan sobre los objetos ya renderizados: esto no es lo que buscamos.


img6


Primero necesitamos copiar la información de profundidad del pasaje geométrico en el búfer de profundidad, y solo después de eso dibujar los cubos luminosos. Por lo tanto, los fragmentos de cubos luminosos se dibujarán solo si están más cerca que los objetos ya dibujados.


Podemos copiar el contenido del framebuffer a otro framebuffer usando la función glBlitFramebuffer . Ya hemos usado esta función en el ejemplo de suavizado : ( suavizado ), traducción . La función glBlitFramebuffer copia la parte especificada por el usuario del framebuffer en la parte especificada de otro framebuffer.


Para los objetos dibujados en el pasaje de iluminación diferido, guardamos la profundidad en el g-buffer del objeto framebuffer. Si simplemente copiamos el contenido del búfer de profundidad del búfer g al búfer de profundidad predeterminado, los cubos luminosos se dibujarán como si toda la geometría de la escena se dibujara usando un pase de renderizado directo. Como se explicó brevemente en el ejemplo anti-aliasing, necesitamos establecer framebuffers para leer y escribir:


 glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); //   - glBlitFramebuffer( 0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST ); glBindFramebuffer(GL_FRAMEBUFFER, 0); //        [...] 

Aquí copiamos todo el contenido del búfer de profundidad de framebuffer al búfer de profundidad predeterminado (si es necesario, puede copiar los búferes de color o el búfer stensil de la misma manera). Si ahora renderizamos los cubos brillantes, se dibujarán como si la geometría de la escena fuera real (aunque se dibuja como simple).


img7


El código fuente de la demostración se puede encontrar aquí .


Con este enfoque, podemos combinar fácilmente la representación diferida con la representación directa. Esto es excelente, ya que podemos aplicar mezclas y dibujar objetos que requieren sombreadores especiales que no son aplicables para el renderizado diferido.


Más fuentes de luz


La iluminación diferida a menudo se elogia por poder atraer una gran cantidad de fuentes de luz sin una disminución significativa en el rendimiento. La iluminación retrasada por sí sola no nos permite dibujar una gran cantidad de fuentes de luz, ya que todavía tenemos que calcular la contribución de todas las fuentes de luz para cada píxel. Para dibujar una gran cantidad de fuentes de luz, se utiliza una optimización muy hermosa, aplicable al renderizado diferido, el área de acción de las fuentes de luz. (volúmenes ligeros)


Por lo general, cuando dibujamos fragmentos en una escena altamente iluminada, tenemos en cuenta la contribución de cada fuente de luz en la escena, independientemente de su distancia al fragmento. Si la mayoría de las fuentes de luz nunca afectarán el fragmento, ¿por qué estamos perdiendo el tiempo calculando para ellas?


La idea del alcance de la fuente de luz es encontrar el radio (o volumen) de la fuente de luz, es decir, el área en la que la luz puede alcanzar la superficie. Como la mayoría de las fuentes de luz utilizan algún tipo de atenuación, podemos encontrar la distancia máxima (radio) que puede alcanzar la luz. Después de eso, realizamos cálculos de iluminación complejos solo para aquellas fuentes de luz que afectan a este fragmento. Esto nos salva de una gran cantidad de cálculos, ya que solo calculamos la iluminación donde se necesita.


Con este enfoque, el truco principal es determinar el tamaño del área de acción de la fuente de luz.


Cálculo del alcance de una fuente de luz (radio)


Para obtener el radio de la fuente de luz, debemos resolver la ecuación de amortiguación para el brillo, que consideramos oscuro: puede ser 0.0 o algo un poco más iluminado, pero aún oscuro: por ejemplo, 0.03. Para demostrar cómo calcular el radio, usaremos una de las funciones de atenuación más complejas y comunes del ejemplo del lanzador de luz


F l i g h t = f r a c I K c + K ld + K qd 2 


Queremos resolver esta ecuación para el caso cuando F l i g h t = 0.0 , es decir, cuando la fuente de luz es completamente oscura. Sin embargo, esta ecuación nunca alcanzará el valor exacto de 0.0, por lo que no hay solución. Sin embargo, podemos resolver la ecuación de brillo para un valor cercano a 0.0, que puede considerarse prácticamente oscuro. En este ejemplo, consideramos aceptable el valor de brillo en  f r a c 5 256 - dividido por 256, ya que el framebuffer de 8 bits puede contener 256 valores de brillo diferentes.


La función de atenuación seleccionada se vuelve casi oscura a una distancia de rango, si la limitamos a un brillo inferior a 5/256, entonces el rango de la fuente de luz será demasiado grande, esto no es tan efectivo. Idealmente, una persona no debería ver un borde repentino de luz de una fuente de luz. Por supuesto, esto depende del tipo de escena, un valor mayor del brillo mínimo proporciona áreas de acción más pequeñas de las fuentes de luz y aumenta la eficiencia de los cálculos, pero puede conducir a artefactos notables en la imagen: la iluminación se interrumpirá abruptamente en los bordes del área de acción de la fuente de luz.

La ecuación de atenuación que debemos resolver se convierte en:


 frac5256= fracImaxAtenuación


Aqui Imax - El componente más brillante de la luz (de los canales r, g, b). Usaremos el componente más brillante, ya que los otros componentes darán una restricción más débil en el alcance de la fuente de luz.


Seguimos resolviendo la ecuación:


 frac5256 cdotAtenuación=Imax


Atenuación=Imax cdot frac2565


Kc+Kl cdotd+Kq cdotd2=Imax cdot frac2565


Kc+Kl cdotd+Kq cdotd2Imax cdot frac2565=0


La última ecuación es una ecuación cuadrática en la forma ax2+bx+c=0 con la siguiente solución:


x= fracKl+ sqrtK2l4Kq(KcImax frac2565)2Kq


Obtuvimos una ecuación general que nos permite sustituir los parámetros (atenuación constante, coeficientes lineales y cuadráticos) para encontrar x, el radio de la fuente de luz.


 float constant = 1.0; float linear = 0.7; float quadratic = 1.8; float lightMax = std::fmaxf(std::fmaxf(lightColor.r, lightColor.g), lightColor.b); float radius = (-linear + std::sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * lightMax))) / (2 * quadratic); 

La fórmula devuelve un radio entre aproximadamente 1.0 y 5.0 dependiendo del brillo máximo de la fuente de luz.


Encontramos este radio para cada fuente de luz en el escenario y lo usamos para tener en cuenta solo aquellas fuentes de luz dentro de las cuales se encuentra dentro del alcance de cada fragmento. A continuación se muestra un paso de iluminación renovado que tiene en cuenta las áreas de acción de las fuentes de luz. Tenga en cuenta que este enfoque se implementa solo con fines educativos y no es adecuado para un uso práctico (pronto discutiremos por qué).


 struct Light { [...] float Radius; }; void main() { [...] for(int i = 0; i < NR_LIGHTS; ++i) { //         float distance = length(lights[i].Position - FragPos); if(distance < lights[i].Radius) { //     [...] } } } 

El resultado es exactamente el mismo que antes, pero ahora para cada fuente de luz, su efecto se tiene en cuenta solo dentro del área de su acción.


El código final es una demostración. .


La aplicación real del alcance de la fuente de luz.


El sombreador de fragmentos que se muestra arriba no funcionará en la práctica y solo sirve para ilustrar cómo podemos deshacernos de los cálculos de iluminación innecesarios. En realidad, la tarjeta de video y el lenguaje de sombreado GLSL optimizan muy mal los bucles y las ramas. La razón de esto es que la ejecución del sombreador en la tarjeta de video se realiza en paralelo para diferentes píxeles, y muchas arquitecturas imponen la limitación de que en la ejecución en paralelo diferentes hilos deben calcular el mismo sombreador. A menudo, esto lleva al hecho de que el sombreador en ejecución siempre calcula todas las ramas para que todos los sombreadores funcionen al mismo tiempo. (Note lane. Esto no afecta el resultado de los cálculos, pero puede reducir el rendimiento del sombreador). Debido a esto, puede resultar que nuestra verificación de radio sea inútil: ¡calcularemos la iluminación para todas las fuentes!


Un enfoque adecuado para usar el alcance de la luz es representar esferas con un radio como el de una fuente de luz. El centro de la esfera coincide con la posición de la fuente de luz, de modo que la esfera contiene dentro de sí misma el rango de acción de la fuente de luz. Aquí hay un pequeño truco: utilizamos básicamente el mismo sombreador de fragmentos diferidos para dibujar una esfera. Al dibujar una esfera, el sombreador de fragmentos se llama específicamente para aquellos píxeles que se ven afectados por la fuente de luz, renderizamos solo los píxeles necesarios y omitimos todos los demás. Ilustración en la imagen a continuación:


img8


, . , , . _*__
_ + __ , .


: ( ) , , , - ( ). stenil .


, , , . ( ) : c (deferred lighting) (tile-based deferred shading) . MSAA. .


vs


( ) - , , . , — , MSAA, .


( ), ( g- ..) . , .


: , , , . , , , . . parallax mapping, , . , .


Enlaces de sitio



PS - . , !

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


All Articles