Al modificar el sombreador para el próximo juego, me encontré con un artefacto desagradable que solo se manifiesta cuando el hardware MSAA está encendido. En la captura de pantalla del paisaje puede ver algunos píxeles demasiado brillantes. Los valores de color en varios de ellos fueron tan grandes que después de florecer, se convirtieron en "fantasmas" multicolores.
Les traigo a su atención una traducción de un artículo que explica en detalle la razón de este fenómeno y la forma de tratarlo.
Figura 1 - Imágenes correctas (izquierda) e incorrectas (derecha). Presta atención a la barra amarilla en el borde izquierdo de la imagen "incorrecta". Aunque la variable myMixer varía de 0 a 1, de alguna manera va más allá de este rango en la imagen "incorrecta".Considere un sombreador de fragmentos simple con una transformación no lineal simple:
smooth in float myMixer; // . // sqrt . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); // myMixer < 0.0 vec3 color = mix( blue, yellow, a ); // gl_FragColor = vec4( color, 1.0 ); }
¿De dónde vino la franja amarilla a la izquierda en la imagen incorrecta? Para comprender mejor lo que salió mal, primero veamos un caso en el que todo funciona correctamente (casi) siempre.
Esta es una rasterización clásica con una muestra. Los cuadrados grises son píxeles y los puntos amarillos son los centros de los píxeles ubicados en las coordenadas de la ventana de medio entero (por defecto, las coordenadas del píxel inferior izquierdo en gl_FragCoord son (0.5, 0.5) - trans. ).En la imagen de arriba, la línea secante separa el medio espacio de la primitiva. Arriba y a la izquierda de esta línea, la variable myMixer es positiva, y abajo y a la derecha es negativa.La rasterización clásica de una muestra clasifica los píxeles según la pertenencia primitiva y crea fragmentos solo para los píxeles cuyos centros se encuentran dentro de la primitiva. En este ejemplo, se producirán seis fragmentos, que se muestran en la esquina superior izquierda. Los píxeles marcados en color apagado no caen en la primitiva. No se generarán fragmentos para ellos.
El verde indica los puntos en los que se calculará el sombreador de fragmentos. El valor de myMixer se calculará para el centro de cada píxel. Tenga en cuenta que los puntos verdes están arriba ya la izquierda de la línea, por lo que los valores de myMixer en ellos serán positivos. Todos los datos de entrada asociados con vértices (variables variables o variables de entrada / salida) también se interpolarán en estos puntos.Nuestro sombreador simple no utiliza derivadas (explícitas o implícitas, por ejemplo, al muestrear desde una textura con niveles de mip), pero las derivadas dFdx (horizontal) y dFdy (vertical) están marcadas con flechas. Dentro de lo primitivo, están bastante bien definidos y son regulares.
Para resumir: en una sola selección, los fragmentos se generan
solo si el centro del píxel cae "dentro" de la primitiva, los datos del fragmento se calculan para el centro del píxel, la interpolación de datos de vértice y el cálculo del sombreador se realizan solo dentro de la primitiva. Todo es bueno y "correcto". (Casi siempre. Por ahora, omita las inexactitudes de algunas derivadas en píxeles a lo largo del borde de la primitiva).
Entonces, todo es (casi) excelente en rasterización con una sola selección. Pero, ¿qué puede salir mal cuando se activa el muestreo múltiple?
Esta es una clásica rasterización de muestreo múltiple. Los cuadrados grises indican píxeles. Los puntos amarillos son centros de píxeles en coordenadas de medio entero. En los puntos azules, se produce el muestreo. Este ejemplo muestra un diagrama simple de dos muestras rotadas. Todos los argumentos pueden generalizarse para un número arbitrario de muestras.La línea todavía separa el medio espacio de lo primitivo. Arriba ya la izquierda, el valor de myMixer es positivo. Más abajo y a la derecha - negativo.Al rasterizar con muestreo múltiple, el clasificador de píxeles generará un fragmento si
al menos una muestra de píxeles cae dentro de la primitiva.
En este ejemplo, se generarán 10 fragmentos, que se muestran en el semiplano superior izquierdo. Observe los cuatro fragmentos agregados a lo largo de la cara, en los que una muestra cae dentro del primitivo, aunque el centro está afuera. Los píxeles fuera del primitivo todavía están marcados como tenues.
¿Qué sucederá al calcular en el centro del píxel?El sombreador se calculará en puntos verdes
y rojos para cada uno de los fragmentos. Los datos asociados a myMixer se calculan en el centro de cada píxel. En los puntos verdes, estos valores serán positivos, ya que están arriba ya la izquierda del borde. Los puntos rojos están fuera de la primitiva, porque los valores de myMixer en ellos son negativos. En los puntos rojos, los datos asociados se extrapolan en lugar de la interpolación.
En nuestro sombreador, los valores de sqrt (myMixer) no están definidos con un myMixer negativo. Incluso cuando los valores de myMixer registrados por el sombreador de vértices se encuentran en el intervalo de cero a uno, en el sombreador de fragmentos, myMixer puede ir más allá de este intervalo debido a la extrapolación. Por lo tanto, con un myMixer negativo, el resultado del sombreador de fragmentos no está definido.
Todavía estamos considerando calcular el sombreador en el centro de los píxeles, las flechas en la figura muestran dFdx y dFdy. En los fragmentos internos del polígono, están bastante bien definidos porque todos los cálculos se realizan en los centros de píxeles ubicados a intervalos iguales.¿Qué sucederá al calcular en puntos distintos de los centros de los píxeles?Los puntos verdes son los puntos en los que se calculará el sombreador. El valor asociado de myMixer se calcula en el
centroide de cada píxel.
El centroide de un píxel es el centro de gravedad de la intersección del cuadrado del píxel y el interior de la primitiva. Para un píxel totalmente cubierto, el centroide es el centro. Para un píxel parcialmente cubierto, el centroide suele ser diferente del centro.
El estándar OpenGL permite que una implementación seleccione un punto arbitrario en la intersección de un primitivo y un píxel en lugar de un centroide ideal. Por ejemplo, podría ser un punto de muestreo.
En este ejemplo, si el centro se encuentra dentro de la primitiva, los datos de vértice se calculan para el centro. De lo contrario, se calculan en cualquiera de los puntos de muestra que se encuentran dentro de la primitiva. Esto sucede para cuatro píxeles a lo largo del borde. Todos los puntos verdes se encuentran arriba ya la izquierda del borde, por lo que los valores en ellos siempre se interpolan y nunca se extrapolan.
¿Por qué no siempre calcular el sombreador centroide? En general, es más costoso que la informática en el centro. Sin embargo, este no es el factor principal.
Se trata de calcular derivados. Presta atención a las flechas entre los puntos verdes. La distancia entre ellos no es la misma para diferentes pares de puntos. Además, y no es constante para dFdx, y x no es constante para dFdy.
Los derivados son menos precisos cuando se calculan en centroides .
Esto es un compromiso y, por lo tanto, OpenGL, comenzando con GLSL 1.20, ofrece al desarrollador del sombreador una opción entre centro y centroide utilizando el calificador centroide:
centroid in float myMixer; // centroid smooth // . // sqrt . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); // myMixer < 0.0 vec3 color = mix( blue, yellow, a ); // gl_FragColor = vec4( color, 1.0 ); }
¿Cuándo deberías usar el centroide?
- Cuando un valor extrapolado puede conducir a resultados vagos. Preste especial atención a las funciones integradas, cuya descripción dice "el resultado no se define si ..."
- Cuando se usa un valor extrapolado con una función muy no lineal o discontinua. Estos incluyen la función de paso o el cálculo de destellos, especialmente cuando el exponente es lo suficientemente grande.
¿Cuándo no deberías usar un centroide?
- Si necesita derivados exactos. Los derivados pueden ser explícitos (llamada dFdx) o implícitos, por ejemplo, muestras de texturas con niveles mip o con filtrado anisotrópico. En la especificación GLSL, los derivados en centroides se consideran tan inutilizables que se declaran indefinidos. En tales casos, intente escribir:
centroid in float myMixer; // ! smooth in float myCenterMixer; // .
- Si se representa una cuadrícula en la que la mayoría de los límites de las primitivas son internos y siempre están bien definidos. El ejemplo más simple es una tira de 100 triángulos (TRIANGLE_STRIP), en la que solo el primer y el último triángulo están sujetos a extrapolación. El calificador centroide dará como resultado la interpolación en estos dos triángulos a costa de la pérdida de precisión y continuidad en los 98 triángulos restantes.
- Si sabe que los artefactos pueden aparecer por una función indefinida, no lineal o discontinua, pero en la práctica estos artefactos resultan ser casi invisibles. Si el sombreador no ataca, ¡no lo arregles!