La forma más rápida de renderizar líneas con AA, variando el grosor en DirectX

14

Así que estoy haciendo un desarrollo de DirectX, usando SharpDX bajo .NET para ser exactos (pero las soluciones API de DirectX / C ++ son aplicables). Estoy buscando la forma más rápida de representar líneas en una proyección ortogonal (por ejemplo, simulando un dibujo lineal 2D para aplicaciones científicas) usando DirectX.

A continuación se muestra una captura de pantalla de los tipos de parcelas que intento representar: ingrese la descripción de la imagen aquí

No es raro que este tipo de parcelas tengan líneas con millones de segmentos, de espesor variable, con o sin antialiasing por línea (o pantalla completa AA activada / desactivada). Necesito actualizar los vértices de las líneas con mucha frecuencia (por ejemplo, 20 veces / segundo) y descargar la mayor cantidad posible a la GPU.

Hasta ahora he intentado:

  1. La representación de software, por ejemplo, GDI + en realidad no es un mal rendimiento, pero obviamente es pesado en la CPU
  2. Direct2D API: más lento que GDI, especialmente con Antialiasing en
  3. Direct3D10 usando este método para emular AA usando colores de vértice y teselación en el lado de la CPU. También lento (lo perfilé y se pasa el 80% del tiempo calculando las posiciones de los vértices)

Para el tercer método, estoy usando Vertex Buffers para enviar una franja triangular a la GPU y actualizar cada 200 ms con nuevos vértices. Estoy obteniendo una frecuencia de actualización de alrededor de 5FPS para 100,000 segmentos de línea. ¡Necesito millones idealmente!

Ahora estoy pensando que la forma más rápida sería hacer la teselación en la GPU, por ejemplo, en un sombreador de geometría. Podría enviar los vértices como una lista de líneas o empaquetar en una textura y desempaquetar en un sombreador de geometría para crear los quads. O simplemente envíe puntos sin procesar a un sombreador de píxeles e implemente el dibujo lineal de Bresenham completamente en un sombreador de píxeles. Mi HLSL es un modelo oxidado y sombreado 2 de 2006, así que no sé qué cosas locas pueden hacer las GPU modernas.

Entonces la pregunta es: ¿Alguien ha hecho esto antes y tiene alguna sugerencia que probar? - ¿Tiene alguna sugerencia para mejorar el rendimiento actualizando rápidamente la geometría (por ejemplo, una nueva lista de vértices cada 20 ms)?

ACTUALIZACIÓN 21 de enero

Desde entonces he implementado el método (3) anterior usando sombreadores Geometry usando LineStrip y Dynamic Vertex Buffers. Ahora obtengo 100FPS en 100k puntos y 10FPS en 1,000,000 puntos. Esta es una gran mejora, pero ahora tengo una tasa de relleno y un cálculo limitados, así que pensé en otras técnicas / ideas.

  • ¿Qué pasa con la instalación de hardware de una geometría de segmento de línea?
  • ¿Qué pasa con Sprite Batch?
  • ¿Qué pasa con otros métodos orientados (sombreador de píxeles)?
  • ¿Puedo sacrificar eficientemente la GPU o la CPU?

Sus comentarios y sugerencias son muy apreciados!

Dr. ABT
fuente
1
¿Qué pasa con AA nativo en Direct3D?
API-Beast el
55
¿Puedes mostrar algunas curvas de ejemplo que deseas trazar? Menciona un millón de vértices, pero su pantalla probablemente no tenga mucho más que un millón de píxeles , ¿es realmente necesaria esa cantidad? Me parece que no necesitará esa densidad de datos completa en todas partes. ¿Has considerado LODs?
Sam Hocevar 01 de
1
El segundo enfoque que vinculó parece bueno, ¿está utilizando un búfer de vértices dinámico que actualiza o crea uno nuevo cada vez?
1
Probablemente debería ir con un búfer de vértices dinámico (no mucho trabajo, solo dígale a su búfer de vértices que sea dinámico, asigne el búfer, copie sus datos, desasigne), pero si su cuello de botella es la generación de vértices en primer lugar, eso probablemente ganó Ayuda mucho ahora. No piense que hay algo loco en usar un GS para aligerar la carga en la CPU
1
Si el número de vértices termina siendo demasiado grande para la GPU, aún podría intentar reducir el muestreo de sus entradas; realmente no necesita cientos de millones de segmentos de línea para una sola curva cuando su pantalla tendrá unos pocos miles de píxeles de ancho a lo sumo.

Respuestas:

16

Si solo va a representar Y = f(X)gráficos, le sugiero que pruebe el siguiente método.

Los datos de la curva se pasan como datos de textura , haciéndolos persistentes y permitiendo actualizaciones parciales, glTexSubImage2Dpor ejemplo. Si necesita desplazarse, incluso podría implementar un búfer circular y solo actualizar algunos valores por fotograma. Cada curva se representa como un quad de pantalla completa y el sombreador de píxeles realiza todo el trabajo.

El contenido de textura de un componente podría verse así:

+----+----+----+----+
| 12 | 10 |  5 | .. | values for curve #1
+----+----+----+----+
| 55 | 83 | 87 | .. | values for curve #2
+----+----+----+----+

El trabajo del sombreador de píxeles es el siguiente:

  • encontrar la coordenada X del fragmento actual en el espacio del conjunto de datos
  • tomar por ejemplo. los 4 puntos de datos más cercanos que tienen datos; por ejemplo, si el valor X es 41.3, elegiría 40,41 , 42y 43.
  • consultar la textura para los valores de 4 Y (asegúrese de que la muestra no realice interpolaciones de ningún tipo)
  • convertir el X,Y pares al espacio de la pantalla
  • calcular la distancia desde el fragmento actual a cada uno de los tres segmentos y cuatro puntos
  • usar la distancia como un valor alfa para el fragmento actual

Es posible que desee sustituir 4 con valores más grandes según el nivel de zoom potencial.

He escrito un sombreador GLSL muy rápido y sucio que implementa esta característica . Puedo agregar la versión HLSL más tarde, pero deberías poder convertirla sin demasiado esfuerzo. El resultado se puede ver a continuación, con diferentes tamaños de línea y densidades de datos:

curvas

Una ventaja clara es que la cantidad de datos transferidos es muy baja, y el número de llamadas es solo uno.

sam hocevar
fuente
Esa es una técnica bastante buena: he hecho GPGPU antes, así que empacar texturas con datos es algo que sé, pero no es algo que consideré para esto. ¿Cuál es el tamaño más grande (por ejemplo, el mayor conjunto de datos que puede cargar?). Descargaré tu código si no te molesta y jugaré con él. Estoy ansioso por ver el rendimiento.
Dr. ABT
@ Dr.ABT El tamaño del conjunto de datos solo está limitado por el tamaño máximo de textura. No veo por qué millones de puntos no funcionarían dado un diseño de datos adecuado. Tenga en cuenta que mi código ciertamente no es de calidad de producción, pero no dude en ponerse en contacto conmigo en privado si hay algún problema.
sam hocevar
Saludos @SamHocevar - Lo intentaré. ¡Ha pasado un tiempo desde que compilé un ejemplo de OpenGL! :-)
Dr. ABT
Marcado como respuesta, ya que aunque no estoy usando la carga de textura, presentaste un ejemplo real de OpenGL que podría dibujar líneas en un sombreador de píxeles y ponernos en la dirección correcta. :)
Dr. ABT
4

Hubo un capítulo de GPU Gems sobre la representación de líneas antialias: Líneas prefiltradas rápidas . La idea básica es representar cada segmento de línea como un quad y calcular, en cada píxel, una función gaussiana de la distancia del centro de píxeles desde el segmento de línea.

Esto significa dibujar cada segmento de línea en el gráfico como un quad separado, pero en D3D11 ciertamente podría usar un sombreador de geometría y / o instancias para generar los quads, reduciendo la cantidad de datos que se transferirán a la GPU solo a los puntos de datos sí mismos. Probablemente configuré los puntos de datos como un StructuredBuffer para que los lea el sombreador de vértices / geometría, luego haría una llamada de dibujo especificando el número de segmentos a dibujar. No habría ningún buffer de vértice real; el sombreador de vértices simplemente usaría SV_VertexID o SV_InstanceID para determinar qué puntos de datos mirar.

Nathan Reed
fuente
Hola, gracias. Sí, conozco ese artículo, desafortunadamente no hay código fuente :-( La premisa de GeoShader + FragShader parece ser un ganador para este tipo de trabajo. Llevar los datos a la GPU de manera eficiente será ¡la parte difícil!
Dr. ABT
Hola @NathanReed volviendo a esto: si utilicé Geometry Shader o instancia para dibujar quads, entonces Point1 / Point2 son vértices opuestos de un quad, ¿cómo puedo calcular la distancia a la línea entre Pt1 y Pt2 en el Pixel Shader? ¿El sombreador de píxeles tiene conocimiento de la geometría de entrada?
Dr. ABT
@ Dr.ABT Los parámetros de la línea matemática deben enviarse al sombreador de píxeles a través de interpoladores. El sombreador de píxeles no tiene acceso directo a la geometría.
Nathan Reed
Ya veo, ¿se puede hacer esto enviando salidas desde GeometryShader o instancing? Para obtener crédito adicional (si está interesado), agregué una Q aquí: gamedev.stackexchange.com/questions/47831/…
Dr. ABT
@ Dr.ABT Es exactamente de la misma manera que las coordenadas de textura, los vectores normales y todo ese tipo de cosas se envían normalmente al sombreador de píxeles, escribiendo salidas del sombreador de vértices / geometría que se interpolan y se ingresan al sombreador de píxeles.
Nathan Reed
0

@ Dr.ABT - Disculpas por esto es una pregunta, no una respuesta. No encontré una forma de preguntar en la respuesta de Sam Hocevar anterior.

¿Podría compartir más detalles sobre cómo finalmente implementó las líneas para su gráfico? Tengo la misma necesidad de una aplicación WPF. Gracias de antemano por cualquier detalle o código que pueda compartir sobre esto.

ErcGeek
fuente
como saben que no es una respuesta por favor, edite y lo puso como un comentario
MephistonX
Estaba tratando de hacer eso, pero no hay un botón "agregar comentario" en la publicación original.
ErcGeek
Hola ErcGeek, lo siento, no puedo compartir la solución final ya que esta información es propiedad de mi empresa. Aunque los ejemplos y sugerencias anteriores nos ponen en el camino correcto. ¡Todo lo mejor!
Dr. ABT
No puedes publicar comentarios antes de alcanzar una cierta reputación. Y todos estos votos negativos seguramente no le brindarán esa oportunidad.
Mathias Lykkegaard Lorenzen
El chico no debería ser penalizado por querer saber el resultado final y pedirlo de la única manera disponible para él. Votó la respuesta.
David Ching