Profundidad de precisi贸n claramente

La precisi贸n de profundidad es una molestia que cualquier programador de gr谩ficos enfrentar谩 tarde o temprano. Se han escrito muchos art铆culos y trabajos sobre este tema. Y en diferentes juegos y motores, y en diferentes plataformas, puede ver muchos formatos y configuraciones diferentes para el b煤fer de profundidad .

La conversi贸n de profundidad en una GPU parece obvia debido a c贸mo interact煤a con la proyecci贸n en perspectiva, y el estudio de las ecuaciones no aclara la situaci贸n. Para entender c贸mo funciona esto, es 煤til dibujar algunas im谩genes.

imagen

Este art铆culo est谩 dividido en 3 partes:

  1. Tratar茅 de explicar la motivaci贸n para la transformaci贸n de profundidad no lineal .
  2. Presentar茅 varios gr谩ficos que lo ayudar谩n a comprender c贸mo funciona la conversi贸n de profundidad no lineal en diferentes situaciones, de forma intuitiva y visual.
  3. Una discusi贸n de los principales hallazgos de Apretar la precisi贸n del renderizado en perspectiva [Paul Upchurch, Mathieu Desbrun (2012)] con respecto al efecto de los errores de punto flotante redondeados en la precisi贸n de la profundidad.


驴Por qu茅 1 / z?


Un b煤fer de profundidad de GPU de hardware generalmente no almacena una representaci贸n lineal de la distancia entre el objeto y la c谩mara, al contrario de lo que se espera ingenuamente de la primera reuni贸n. En cambio, el b煤fer de profundidad almacena valores inversamente proporcionales a la profundidad del espacio de vista. Quiero describir brevemente la motivaci贸n para tal decisi贸n.

En este art铆culo, usar茅 d para representar los valores almacenados en el b煤fer de profundidad (en el rango [0, 1] para DirectX), y z para representar el espacio de vista de profundidad, es decir. La distancia real de la c谩mara, en unidades mundiales, por ejemplo, metros. En general, la relaci贸n entre ellos tiene la siguiente forma:

imagen

donde a, b son las constantes asociadas con la configuraci贸n cercana y lejana de los planos. En otras palabras, d es siempre una transformaci贸n lineal de 1 / z .

A primera vista, puede parecer que cualquier funci贸n de z puede tomarse como d . Entonces, 驴por qu茅 se ve as铆? Hay dos razones principales para esto.

En primer lugar, 1 / z encaja naturalmente en la proyecci贸n en perspectiva. Y esta es la clase m谩s b谩sica de transformaciones, que garantiza preservar las l铆neas rectas. Por lo tanto, la proyecci贸n en perspectiva es adecuada para la rasterizaci贸n de hardware, ya que los bordes rectos de los tri谩ngulos permanecen rectos en la pantalla. Podemos obtener una transformaci贸n lineal de 1 / z , aprovechando la divisi贸n de perspectiva que la GPU ya realiza:

imagen

Por supuesto, la verdadera fortaleza de este enfoque es que la matriz de proyecci贸n se puede multiplicar con otras matrices, lo que le permite combinar muchas transformaciones en una sola.

La segunda raz贸n es que 1 / z es lineal en el espacio de la pantalla, como se帽al贸 Emil Persson . Esto hace que sea f谩cil interpolar d en el tri谩ngulo durante la rasterizaci贸n, y cosas como los amortiguadores Z jer谩rquicos , el sacrificio Z temprano y el b煤fer de profundidad de compresi贸n .

Brevemente del art铆culo.
Si bien el valor de w (profundidad del espacio de visualizaci贸n) es lineal en el espacio de visualizaci贸n, no es lineal en el espacio de la pantalla. z (profundidad) , no lineal en el espacio de visualizaci贸n, por otro lado lineal en el espacio de la pantalla. Esto se puede verificar f谩cilmente con un simple sombreador DX10:

float dx = ddx(In.position.z); float dy = ddy(In.position.z); return 1000.0 * float4(abs(dx), abs(dy), 0, 0); 

Aqu铆 In.position es SV_Position. El resultado se parece a esto:

imagen

Tenga en cuenta que todas las superficies se ven monocrom谩ticas. La diferencia en z de p铆xel a p铆xel es la misma para cualquier primitiva. Esto es muy importante para la GPU. Una raz贸n es que la interpolaci贸n z es m谩s barata que la interpolaci贸n w . Para z, no hay necesidad de realizar una correcci贸n de perspectiva. Con unidades de hardware m谩s baratas, puede procesar m谩s p铆xeles por ciclo con el mismo presupuesto para transistores. Naturalmente, esto es muy importante para el pase pre-z y el mapa de sombras . Con el hardware moderno, la linealidad en el espacio de la pantalla tambi茅n es una caracter铆stica muy 煤til para las optimizaciones z. Dado que el gradiente es lineal para toda la primitiva, tambi茅n es relativamente f谩cil calcular el rango de profundidad exacto dentro del mosaico para el sacrificio de Hi-z . Tambi茅n significa que es posible la compresi贸n z . Con una constante 螖z en x e y, no necesita almacenar mucha informaci贸n para poder restaurar por completo todos los valores de z en un mosaico, siempre que la primitiva haya cubierto todo el mosaico.

Gr谩ficos de profundidad


Las ecuaciones son complicadas, 隆veamos un par de im谩genes!

imagen

La forma de leer estos cuadros es de izquierda a derecha, luego hacia abajo. Comience con d en el eje izquierdo. Dado que d puede ser una transformaci贸n lineal arbitraria de 1 / z , podemos organizar 0 y 1 en cualquier lugar conveniente del eje. Las marcas indican diferentes valores de tamp贸n de profundidad . Para fines de claridad, model茅 un b煤fer de profundidad normalizado de enteros de 4 bits, por lo que hay 16 marcas espaciadas uniformemente.

El gr谩fico anterior muestra la conversi贸n de profundidad de vainilla "est谩ndar" a D3D y API similares. Puede notar de inmediato c贸mo, debido a la curva 1 / z , se agrupan los valores cercanos al plano cercano y se dispersan los valores cercanos al plano lejano.

Tambi茅n es f谩cil entender por qu茅 cerca de un plano afecta tanto la precisi贸n de profundidad. La distancia cerca del plano conducir谩 a un r谩pido aumento en los valores de d en relaci贸n con los valores de z , lo que conducir谩 a una distribuci贸n de valores a煤n m谩s desigual:

imagen

Del mismo modo, en este contexto, es f谩cil ver por qu茅 mover el plano lejano al infinito no tiene un efecto tan grande. Simplemente significa expandir el rango de d a 1 / z = 0 :

imagen

Pero, 驴qu茅 pasa con la profundidad de punto flotante? Al siguiente gr谩fico se le han agregado marcas correspondientes al formato flotante con 3 bits del exponente y 3 bits de la mantisa:

imagen

Ahora en el rango [0,1] hay 40 valores diferentes, un poco m谩s de 16 valores anteriores, pero la mayor铆a de ellos est谩n agrupados in煤tilmente cerca del plano cercano (m谩s cerca de 0 el flotador tiene mayor precisi贸n), donde realmente no necesitamos mucha precisi贸n.

Ahora, un truco bien conocido es invertir la profundidad, mostrando el plano cercano en d = 1 y el plano lejano en d = 0 :

imagen

Mucho mejor! Ahora, la distribuci贸n cuasi-logar铆tmica de flotaci贸n compensa de alguna manera la no linealidad de 1 / z , mientras que m谩s cerca del plano cercano proporciona una precisi贸n similar al b煤fer de profundidad de enteros, y proporciona una precisi贸n significativamente mayor en otros lugares. La precisi贸n de profundidad se deteriora muy lentamente si te alejas de la c谩mara.

El truco Z invertido puede haberse reinventado de forma independiente varias veces, pero al menos la primera menci贸n fue en el art铆culo SIGGRAPH '99 [Eugene Lapidous y Guofang Jiao (desafortunadamente no est谩 disponible p煤blicamente)]. Y recientemente, Matt Petineo y Brano Kemen lo volvieron a mencionar en el blog, y en un discurso de Emil Persson Creando Vast Game Worlds SIGGRAPH 2012.

Todos los gr谩ficos previos asumieron un rango de profundidad [0.1] despu茅s de la proyecci贸n, lo cual es una convenci贸n en D3D. 驴Qu茅 hay de OpenGL ?

imagen

OpenGL por defecto asume un rango de profundidad [-1, 1] despu茅s de la proyecci贸n. Para formatos enteros, nada cambia, pero para punto flotante toda la precisi贸n se concentra in煤til en el medio. (El valor de profundidad se asigna al rango [0,1] para su posterior almacenamiento en el b煤fer de profundidad, pero esto no ayuda, ya que la asignaci贸n inicial a [-1,1] ya destruy贸 toda la precisi贸n en la mitad lejana del rango). Y debido a la simetr铆a, el truco Z invertida no funcionar谩 aqu铆.

Afortunadamente, en el escritorio OpenGL, esto se puede solucionar utilizando la extensi贸n ARB_clip_control ampliamente compatible (tambi茅n comenzando con OpenGL 4.5, glClipControl es est谩ndar ). Desafortunadamente, GL ES est谩 en vuelo.

El efecto de los errores de redondeo


La conversi贸n 1 / z y la elecci贸n del b煤fer de profundidad flotante vs int es una gran parte de la historia de precisi贸n, pero no todo. Incluso si tiene suficiente precisi贸n de profundidad para representar la escena que est谩 tratando de representar, es f谩cil degradar la precisi贸n con errores aritm茅ticos durante el proceso de conversi贸n de v茅rtices.

Al comienzo del art铆culo, se mencion贸 que Upchurch y Desbrun estudiaron este problema. Propusieron dos recomendaciones principales para minimizar los errores de redondeo:

  1. Usa infinito plano lejano.
  2. Mantenga la matriz de proyecci贸n separada de otras matrices y apl铆quela como una operaci贸n separada en el sombreador de v茅rtices, en lugar de combinarla con la matriz de vista.

Upchurch y Desbrun hicieron estas recomendaciones utilizando un m茅todo anal铆tico basado en el procesamiento de errores de redondeo como peque帽os errores aleatorios presentados en cada operaci贸n aritm茅tica y rastre谩ndolos hasta el primer orden en el proceso de conversi贸n. Decid铆 probar los resultados en la pr谩ctica.

Las fuentes aqu铆 son Python 3.4 y numpy. El programa funciona de la siguiente manera: se genera una secuencia de puntos aleatorios, ordenados por profundidad, ubicados linealmente o logar铆tmicamente entre planos cercanos y lejanos. Luego, los puntos se multiplican por la vista y la proyecci贸n de la matriz y se realiza la divisi贸n en perspectiva, utilizando flotantes de 32 bits, y opcionalmente el resultado final se convierte en un int de 24 bits. Al final, pasa a trav茅s de la secuencia y cuenta cu谩ntas veces 2 puntos vecinos (que inicialmente ten铆an profundidades diferentes) se volvieron id茅nticos, porque ten铆an la misma profundidad o el orden cambi贸 en absoluto. En otras palabras, el programa mide la frecuencia con la que ocurren los errores de comparaci贸n de profundidad, que corresponde a problemas como la lucha Z , en varios escenarios.

Aqu铆 est谩n los resultados para cerca = 0.1, lejos = 10K, con una profundidad lineal de 10K. (Prob茅 el intervalo de profundidad logar铆tmica y otras proporciones cercanas / lejanas, y aunque los n煤meros espec铆ficos variaron, las tendencias generales en los resultados fueron las mismas).

En la tabla, "eq" - dos puntos con la profundidad m谩s cercana obtienen el mismo valor en el b煤fer de profundidad, y "swap" - se intercambian dos puntos con la profundidad m谩s cercana.
Matriz de vista-proyecci贸n compuestaVista separada y matrices de proyecci贸n
float32int24float32int24
Valores Z sin cambios (prueba de control)0% eq
0% de intercambio
0% eq
0% de intercambio
0% eq
0% de intercambio
0% eq
0% de intercambio
Proyecci贸n est谩ndar45% eq
18% de intercambio
45% eq
18% de intercambio
77% eq
0% de intercambio
77% eq
0% de intercambio
Infinito lejos45% eq
18% de intercambio
45% eq
18% de intercambio
76% eq
0% de intercambio
76% eq
0% de intercambio
Z invertido0% eq
0% de intercambio
76% eq
0% de intercambio
0% eq
0% de intercambio
76% eq
0% de intercambio
Infinito + Z invertido0% eq
0% de intercambio
76% eq
0% de intercambio
0% eq
0% de intercambio
76% eq
0% de intercambio
Est谩ndar + estilo GL56% eq
12% de intercambio
56% eq
12% de intercambio
77% eq
0% de intercambio
77% eq
0% de intercambio
Infinito + estilo GL59% eq
10% de intercambio
59% eq
10% de intercambio
77% eq
0% de intercambio
77% eq
0% de intercambio

Pido disculpas por el hecho de que sin un gr谩fico, 隆hay demasiada dimensi贸n aqu铆 y simplemente no puedo construirlo! En cualquier caso, mirando los n煤meros, las siguientes conclusiones son obvias:

  • En la mayor铆a de los casos, no hay diferencia entre el b煤fer de profundidad int y flotante . Errores aritm茅ticos para calcular errores de anulaci贸n de profundidad en la conversi贸n a int. En parte porque float32 e int24 tienen una ULP casi igual (la unidad de menor precisi贸n es la distancia al n煤mero vecino m谩s cercano) en [0.5.1] (dado que float32 tiene una mantisa de 23 bits), por lo que no se agrega un error de conversi贸n en casi todo el rango de profundidad en int.
  • En la mayor铆a de los casos, la separaci贸n de las matrices de vista y proyecci贸n (siguiendo las recomendaciones de Upchurch y Desbrun) mejora el resultado. A pesar de que la tasa de error general no disminuye, los "intercambios" se convierten en valores iguales, y este es un paso en la direcci贸n correcta.
  • El plano lejano infinito cambia ligeramente la frecuencia de los errores. Upchurch y Desbrun predijeron una reducci贸n del 25% en la frecuencia de errores num茅ricos (errores de precisi贸n), pero esto no parece conducir a una disminuci贸n en la tasa de error de comparaci贸n.

Sin embargo, los hallazgos anteriores no son reales en comparaci贸n con la magia Z invertida . Comprobar:

  • La Z invertida con b煤fer de profundidad flotante proporciona una tasa de error cero en la prueba. Ahora, por supuesto, puede obtener algunos errores si contin煤a aumentando el intervalo de valores de profundidad de entrada. Sin embargo, la Z invertida con flotaci贸n es rid铆culamente m谩s precisa que cualquier otra opci贸n.
  • Reversed-Z con buffer de profundidad de enteros es tan bueno como otras opciones de enteros.
  • La Z invertida difumina la distinci贸n entre matrices de vista / proyecci贸n compuestas y separadas, y planos lejanos finitos e infinitos. En otras palabras, con Z invertida puede multiplicar la proyecci贸n con otras matrices y usar cualquier plano lejano que desee, sin comprometer la precisi贸n.

Conclusi贸n


Creo que la conclusi贸n es clara. En cualquier situaci贸n, cuando se trata de la proyecci贸n en perspectiva, solo use el b煤fer de profundidad de flotaci贸n y la Z invertida . Y si no puede usar el b煤fer de profundidad de flotaci贸n, a煤n debe usar Z invertida. Esto no es una panacea para todos los males, especialmente si crea un entorno de mundo abierto con rangos de profundidad extrema. Pero este es un gran comienzo.

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


All Articles