¿Cuál es el estado del arte para la representación de texto en OpenGL a partir de la versión 4.1? [cerrado]

199

Ya hay una serie de preguntas sobre la representación de texto en OpenGL, como:

Pero sobre todo lo que se discute es renderizar quads texturizados utilizando la tubería de función fija. Seguramente los sombreadores deben hacer una mejor manera.

No estoy realmente preocupado por la internacionalización, la mayoría de mis cadenas serán etiquetas de marca de trama (fecha y hora o puramente numéricas). Pero los gráficos se volverán a representar a la frecuencia de actualización de la pantalla y podría haber bastante texto (no más de unos pocos miles de glifos en la pantalla, pero suficiente para que el diseño acelerado por hardware sería bueno).

¿Cuál es el enfoque recomendado para la representación de texto usando OpenGL moderno? (Citar el software existente usando el enfoque es una buena evidencia de que funciona bien)

  • Sombreadores de geometría que aceptan, por ejemplo, posición y orientación y una secuencia de caracteres y emiten quads texturizados
  • Sombreadores de geometría que representan fuentes vectoriales
  • Como arriba, pero usando sombreadores de teselación en su lugar
  • Un sombreador de cómputo para hacer rasterización de fuentes
Ben Voigt
fuente
10
No puedo responder sobre el estado del arte, estar orientado principalmente a OpenGL ES hoy en día, pero teselar un TTF utilizando el tesselator GLU y enviarlo como geometría a través de la antigua tubería de funcionalidad fija con kerning calculado en la CPU dio buenos resultados visuales en anti -alias de hardware y buen rendimiento en todos los ámbitos, incluso hace casi una década. Por lo tanto, no solo con los sombreadores puede encontrar una forma 'mejor' (dependiendo de sus criterios, por supuesto). FreeType puede escupir límites de glifos de Bezier e información de interletraje, para que pueda trabajar en vivo desde un TTF en tiempo de ejecución.
Tommy
QML2 (de Qt5) hace algunos trucos interesantes con OpenGL y los campos de distancia al representar texto: blog.qt.digia.com/blog/2012/08/08/native-looking-text-in-qml-2
mlvljr
Así que no lo vuelvo a perder, aquí hay una biblioteca que implementa el método de campo de distancia de Valve. code.google.com/p/glyphy No lo he probado. También vale la pena echarle un vistazo: code.google.com/p/signed-distance-field-font-generator
Timmmm
77
Este "fuera de tema" es la maldición del desbordamiento de la pila. ¿seriamente?
Nikolaos Giotis
1
una versión más ingenua de "cómo hacerlo": stackoverflow.com/questions/8847899/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

202

La representación de contornos, a menos que represente solo una docena de caracteres en total, sigue siendo un "no ir" debido a la cantidad de vértices necesarios por carácter para aproximar la curvatura. Aunque en su lugar ha habido enfoques para evaluar curvas bezier en el sombreador de píxeles, estos sufren de no ser fácilmente suavizados, lo cual es trivial usando un quad con textura de mapa de distancia, y evaluar curvas en el sombreador aún es computacionalmente mucho más costoso de lo necesario.

La mejor compensación entre "rápido" y "calidad" sigue siendo quads texturizados con una textura de campo de distancia firmada. Es un poco más lento que usar un quad con textura normal, pero no tanto. La calidad, por otro lado, está en un estadio completamente diferente. Los resultados son realmente sorprendentes, es lo más rápido posible, y los efectos como el brillo también son trivialmente fáciles de agregar. Además, la técnica se puede degradar muy bien a hardware antiguo, si es necesario.

Vea el famoso documento de Valve para la técnica.

La técnica es conceptualmente similar a cómo funcionan las superficies implícitas (metabolas y demás), aunque no genera polígonos. Se ejecuta completamente en el sombreador de píxeles y toma la distancia muestreada de la textura como una función de distancia. Todo lo que está por encima de un umbral elegido (generalmente 0.5) está "dentro", todo lo demás está "fuera". En el caso más simple, en hardware de 10 años sin capacidad de sombreado, establecer el umbral de prueba alfa en 0.5 hará exactamente eso (aunque sin efectos especiales y antialiasing).
Si uno quiere agregar un poco más de peso a la fuente (falso en negrita), un umbral un poco más pequeño hará el truco sin modificar una sola línea de código (solo cambie su uniforme "font_weight"). Para un efecto de brillo, uno simplemente considera todo lo que está por encima de un umbral como "dentro" y todo lo que está por encima de otro umbral (más pequeño) como "fuera, pero en brillo", y los LERP entre los dos. Antialiasing funciona de manera similar.

Al usar un valor de distancia con signo de 8 bits en lugar de un solo bit, esta técnica aumenta la resolución efectiva de su mapa de textura 16 veces en cada dimensión (en lugar de blanco y negro, se utilizan todos los tonos posibles, por lo tanto tenemos 256 veces el información utilizando el mismo almacenamiento). Pero incluso si amplía mucho más allá de 16x, el resultado todavía parece bastante aceptable. Las líneas rectas largas eventualmente se volverán un poco onduladas, pero no habrá artefactos de muestreo típicos "en bloque".

Puede usar un sombreador de geometría para generar los quads a partir de puntos (reducir el ancho de banda del bus), pero sinceramente, las ganancias son bastante marginales. Lo mismo es cierto para la representación de caracteres instanciada como se describe en GPG8. La sobrecarga de la instancia solo se amortiza si tiene mucho texto para dibujar. Las ganancias, en mi opinión, no guardan relación con la complejidad añadida y la no degradabilidad. Además, está limitado por la cantidad de registros constantes o tiene que leer desde un objeto de búfer de textura, que no es óptimo para la coherencia de la caché (¡y la intención era optimizar para empezar!).
Un búfer de vértices simple y simple es igual de rápido (posiblemente más rápido) si programa la carga con un poco de anticipación y se ejecutará en cada hardware construido durante los últimos 15 años. Y no se limita a un número particular de caracteres en su fuente, ni a un número particular de caracteres para representar.

Si está seguro de que no tiene más de 256 caracteres en su fuente, vale la pena considerar las matrices de texturas para quitar el ancho de banda del bus de una manera similar a la generación de quads desde puntos en el sombreador de geometría. Cuando se usa una textura array, las coordenadas de textura de todos los quads tienen idéntico, constante sy tcoordenadas y sólo difieren en la rcoordenada, que es igual al índice de carácter para render.
Pero al igual que con las otras técnicas, las ganancias esperadas son marginales a costa de ser incompatibles con el hardware de la generación anterior.

Hay una herramienta útil de Jonathan Dummer para generar texturas de distancia: página de descripción

Actualización:
como se señaló más recientemente en la extracción programable de vértices (D. Rákos, "OpenGL Insights", págs. 239), no existe una latencia o sobrecarga adicional significativa asociada con la extracción programática de datos de vértices desde el sombreador en las generaciones más recientes de GPU, en comparación con hacer lo mismo con la función fija estándar.
Además, las últimas generaciones de GPU tienen más y más cachés L2 de propósito general de tamaño razonable (por ejemplo, 1536 kB en nvidia Kepler), por lo que uno puede esperar que el problema de acceso incoherente al extraer compensaciones aleatorias para las esquinas cuádruples de una textura de búfer sea menos problema.

Esto hace que la idea de extraer datos constantes (como los tamaños de quad) de una textura de búfer sea más atractiva. Una implementación hipotética podría reducir las transferencias de memoria y PCIe, así como la memoria de la GPU, al mínimo con un enfoque como este:

  • Cargue solo un índice de caracteres (uno por carácter que se mostrará) como la única entrada a un sombreador de vértices que pasa este índice y gl_VertexID, y amplifíquelo a 4 puntos en el sombreador de geometría, aún teniendo el índice de caracteres y la identificación del vértice (esto será "gl_primitiveID disponible en el sombreador de vértices") como los únicos atributos, y capturará esto a través de la retroalimentación de transformación.
  • Esto será rápido, porque solo hay dos atributos de salida (cuello de botella principal en GS), y está cerca de "no-op" de lo contrario en ambas etapas.
  • Ate una textura de búfer que contenga, para cada carácter en la fuente, las posiciones de vértice del quad texturizado en relación con el punto base (estas son básicamente las "métricas de fuente"). Estos datos se pueden comprimir a 4 números por quad almacenando solo el desplazamiento del vértice inferior izquierdo y codificando el ancho y la altura del cuadro alineado al eje (suponiendo que los medios flotadores, serán 8 bytes de buffer constante por carácter - una fuente típica de 256 caracteres podría caber completamente en 2 KB de caché L1).
  • Establecer un uniforme para la línea de base
  • Enlace una textura de amortiguación con desplazamientos horizontales. Estos podrían probablemente incluso se calcularán sobre la GPU, pero es mucho más fácil y más eficiente para ese tipo de cosas en la CPU, ya que es una operación estrictamente secuencial y no es en absoluto trivial (piense en el ajuste entre caracteres). Además, necesitaría otro pase de retroalimentación, que sería otro punto de sincronización.
  • Renderice los datos generados previamente desde el búfer de retroalimentación, el sombreador de vértices extrae el desplazamiento horizontal del punto base y los desplazamientos de los vértices de las esquinas de los objetos del búfer (usando la identificación primitiva y el índice de caracteres). La ID de vértice original de los vértices enviados ahora es nuestra "ID primitiva" (recuerde que el GS convirtió los vértices en quads).

De esta forma, lo ideal sería reducir el ancho de banda de vértice requerido en un 75% (amortizado), aunque solo sería capaz de representar una sola línea. Si uno quisiera poder renderizar varias líneas en una llamada de dibujo, necesitaría agregar la línea base a la textura del búfer, en lugar de usar un uniforme (haciendo que el ancho de banda aumente más pequeño).

Sin embargo, incluso suponiendo una reducción del 75%, ya que los datos de vértice para mostrar cantidades de texto "razonables" solo están en algún lugar alrededor de 50-100 kB (que es prácticamente cero)a una GPU o un bus PCIe) - Todavía dudo que la complejidad añadida y la pérdida de compatibilidad con versiones anteriores realmente valga la pena. La reducción de cero en un 75% sigue siendo solo cero. Es cierto que no he probado el enfoque anterior, y se necesitaría más investigación para hacer una declaración verdaderamente calificada. Pero aún así, a menos que alguien pueda demostrar una diferencia de rendimiento realmente sorprendente (¡usando cantidades de texto "normales", no miles de millones de caracteres!), Mi punto de vista sigue siendo que para los datos de vértice, un búfer de vértice simple y simple es justificadamente suficiente ser considerado parte de una "solución de vanguardia". Es simple y directo, funciona y funciona bien.

Después de hacer referencia a " OpenGL Insights " arriba, vale la pena señalar también el capítulo "Representación de formas 2D por campos de distancia" de Stefan Gustavson que explica la representación de campos de distancia con gran detalle.

Actualización 2016:

Mientras tanto, existen varias técnicas adicionales que tienen como objetivo eliminar los artefactos de redondeo de esquinas que se vuelven perturbadores con aumentos extremos.

Un enfoque simplemente usa campos de pseudodistancia en lugar de campos de distancia (la diferencia es que la distancia es la distancia más corta, no al contorno real, sino al contorno o una línea imaginaria que sobresale sobre el borde). Esto es algo mejor, y funciona a la misma velocidad (sombreador idéntico), usando la misma cantidad de memoria de textura.

Otro enfoque utiliza la mediana de tres en una textura de tres canales, detalles e implementación disponibles en github . Esto tiene como objetivo ser una mejora sobre los hacks utilizados anteriormente para abordar el problema. Buena calidad, ligeramente, casi no notablemente, más lenta, pero utiliza tres veces más memoria de textura. Además, los efectos adicionales (p. Ej., Resplandor) son más difíciles de acertar.

Por último, almacenar las curvas reales de Bezier que componen los personajes y evaluarlos en un sombreador de fragmentos se ha vuelto práctico , con un rendimiento ligeramente inferior (pero no tanto como un problema) y resultados sorprendentes incluso con los aumentos más altos.
Demostración de WebGL que muestra un PDF grande con esta técnica en tiempo real disponible aquí .

Damon
fuente
1
Se ven bastante bien (incluso con un filtrado ingenuo y en ausencia de mipmapping, ya que tiene texturas muy pequeñas y los datos se interpolan muy bien). Personalmente, creo que incluso se ven mejor que lo "real" en muchos casos, porque no hay rarezas como insinuaciones, que a menudo producen cosas que percibo como "extrañas". Por ejemplo, el texto más pequeño no se pone en negrita repentinamente por ninguna razón obvia, ni aparece en los límites de píxeles, efectos que a menudo se ven con fuentes "reales". Puede haber razones históricas para eso (pantallas en blanco y negro de 1985), pero hoy, está fuera de mi comprensión por qué tiene que ser así.
Damon
2
Funciona y se ve genial, ¡gracias por compartir! Para aquellos que desean una fuente de sombreador de fragmentos HLSL, consulte aquí . Puede adaptar esto para GLSL reemplazando la clip(...)línea con if (text.a < 0.5) {discard;}(o text.a < threshold). HTH
Ingeniero
1
Gracias por la actualización. Desearía poder votar de nuevo.
Ben Voigt
2
@NicolBolas: Parece que no has leído con mucho cuidado. Ambas preguntas se explican en la respuesta. Kepler se da como un ejemplo de "última generación", no hay un segundo pase (y se explica por qué), y afirmo que no creo que la técnica hipotética de ahorro de ancho de banda sea notablemente más rápida o que valga la pena. Sin embargo, creer no significa nada, uno debería tratar de saberlo (no lo he hecho, ya que no considero dibujar cantidades "normales" de texto como un cuello de botella de ninguna manera). Se podría , sin embargo, que valga la pena en una cuando uno está desesperado sobre el ancho de banda y tiene cantidades "anormales" de texto.
Damon
3
@ NicolasBolas: Tienes razón sobre esa oración, lo siento. De hecho, es un poco engañoso. En el párrafo anterior, escribí: "Uno probablemente podría incluso generar esto en la GPU, pero eso requeriría comentarios e ... isnogud". - pero luego continuó por error con "los datos generados desde el búfer de retroalimentación" . Corregiré esto. En realidad, volveré a escribir todo el fin de semana, por lo que es menos ambiguo.
Damon
15

http://code.google.com/p/glyphy/

La principal diferencia entre GLyphy y otros renderizadores OpenGL basados ​​en SDF es que la mayoría de los otros proyectos muestrean el SDF en una textura. Esto tiene todos los problemas habituales que tiene el muestreo. Es decir. distorsiona el contorno y es de baja calidad. GLyphy en cambio representa el SDF usando vectores reales enviados a la GPU. Esto da como resultado una representación de muy alta calidad.

La desventaja es que el código es para iOS con OpenGL ES. Probablemente voy a hacer un puerto Windows / Linux OpenGL 4.x (aunque espero que el autor agregue documentación real).

Nombre para mostrar
fuente
3
Cualquier persona interesada en GLyphy probablemente debería ver la charla del autor en Linux.conf.au 2014: youtube.com/watch?v=KdNxR5V7prk
Fizz
14

La técnica más extendida sigue siendo los quads texturizados. Sin embargo, en 2005 LORIA desarrolló algo llamado texturas vectoriales, es decir, renderizando gráficos vectoriales como texturas en primitivas. Si uno usa esto para convertir fuentes TrueType u OpenType en una textura vectorial, obtendrá esto:

http://alice.loria.fr/index.php/publications.html?Paper=VTM@2005

datenwolf
fuente
2
¿Conoces alguna implementación que use esta técnica?
luke
2
No (como en el grado de producción), pero el artículo de Kilgard (vea mi respuesta a continuación para el enlace) tiene una breve crítica, que resumo como: aún no es práctico. Ha habido más investigación en el área; El trabajo más reciente citado por Kilgard incluye research.microsoft.com/en-us/um/people/hoppe/ravg.pdf y uwspace.uwaterloo.ca/handle/10012/4262
Fizz
9

Me sorprende que el bebé de Mark Kilgard, NV_path_rendering (NVpr), no haya sido mencionado por ninguno de los anteriores. Aunque sus objetivos son más generales que la representación de fuentes, también puede representar texto de fuentes y con interletraje. Ni siquiera requiere OpenGL 4.1, pero en este momento es una extensión solo para proveedores / Nvidia. Básicamente, convierte las fuentes en rutas glPathGlyphsNVque dependen de la biblioteca freetype2 para obtener las métricas, etc. Luego, también puede acceder a la información de interletraje glGetPathSpacingNVy utilizar el mecanismo de representación de ruta general de NVpr para mostrar el texto usando las fuentes "convertidas" de ruta. (Lo pongo entre comillas, porque no hay una conversión real, las curvas se usan como están).

los demostración grabada de las capacidades de fuente de NVpr desafortunadamente no es particularmente impresionante. (Tal vez alguien debería hacer uno en la línea de la demostración SDF mucho más elegante que uno puede encontrar en los intertubos ...)

La charla de presentación de la API NVpr 2011 para la parte de fuentes comienza aquí y continúa en la siguiente parte ; es un poco desafortunado cómo se divide esa presentación.

Materiales más generales sobre NVpr:

  • Nvidia NVpr hub , pero parte del material en la página de destino no es el más actualizado
  • Documento de Siggraph 2012 para los cerebros del método de representación de rutas, llamado "plantilla, luego cubierta" (StC); El documento también explica brevemente cómo funciona la tecnología competitiva como Direct2D. Los bits relacionados con la fuente han sido relegados a un anexo del documento . También hay algunos extras como videos / demos .
  • Presentación de GTC 2014 de para un estado de actualización; en pocas palabras: ahora es compatible con Skia de Google (Nvidia contribuyó con el código a finales de 2013 y 2014), que a su vez se usa en Google Chrome y [independientemente de Skia, creo] en una versión beta de Adobe Illustrator CC 2014
  • la documentación oficial en el registro de extensión OpenGL
  • La USPTO ha otorgado al menos cuatro patentes a Kilgard / Nvidia en relación con NVpr, de las cuales probablemente debería estar al tanto, en caso de que quiera implementar StC usted mismo: US8698837 , US8698808 , US8704830 y US8730253 . Tenga en cuenta que hay algo así como 17 documentos más de la USPTO conectados a esto como "también publicados como", la mayoría de los cuales son solicitudes de patentes, por lo que es muy posible que se otorguen más patentes.

Y dado que la palabra "plantilla" no produjo ningún resultado en esta página antes de mi respuesta, parece que el subconjunto de la comunidad SO que participó en esta página en la medida en que, a pesar de ser bastante numerosa, no estaba al tanto de la libre de teselación, el búfer de plantilla métodos basados ​​para la representación de ruta / fuente en general. Kilgard tiene una publicación de preguntas frecuentes en el foro opengl que puede iluminar cómo los métodos de representación de ruta sin teselación difieren de los gráficos 3D estándar, aunque todavía están usando una GPU [GP]. (NVpr necesita un chip compatible con CUDA).

Para la perspectiva histórica, Kilgard es también el autor del clásico. "Una API simple basada en OpenGL para Text Mapped Text", SGI, 1997 , que no debe confundirse con el NVpr basado en stencil que debutó en 2011.


La mayoría, si no todos los métodos recientes discutidos en esta página, incluidos los métodos basados ​​en plantillas como NVpr o los métodos basados ​​en SDF como GLyphy (que no discuto aquí más porque otras respuestas ya lo cubren) tienen sin embargo una limitación: son Adecuado para la visualización de texto grande en monitores convencionales (~ 100 DPI) sin irregularidades en cualquier nivel de escala, y también se ven bien, incluso a tamaño pequeño, en pantallas de alta resolución tipo retina. Sin embargo, no proporcionan completamente lo que Direct2D + DirectWrite de Microsoft le brinda, es decir, insinuación de pequeños glifos en las pantallas principales. (Para una encuesta visual de sugerencias en general, vea esta página de la tipoteca, por ejemplo. Un recurso más detallado está en antigrain.com ).

No estoy al tanto de ninguna cosa abierta y productiva basada en OpenGL que pueda hacer lo que Microsoft puede hacer con sugerencias en este momento. (Admito ignorancia a los componentes internos OS X GL / Quartz de Apple, porque que yo sepa, Apple no ha publicado cómo hacen las fuentes basadas en GL / renderización de rutas. Parece que OS X, a diferencia de MacOS 9, no hacer insinuaciones, lo que molesta a algunas personas .) De todos modos, hay un artículo de investigación de 2013 que aborda las insinuaciones a través de sombreadores OpenGL escritos por Nicolas P. Rougier de INRIA; Probablemente valga la pena leerlo si necesita hacer sugerencias desde OpenGL. Si bien puede parecer que una biblioteca como freetype ya hace todo el trabajo cuando se trata de insinuar, en realidad no es así por la siguiente razón, que estoy citando en el documento:

La biblioteca FreeType puede rasterizar un glifo usando el suavizado de subpíxeles en modo RGB. Sin embargo, esto es solo la mitad del problema, ya que también queremos lograr un posicionamiento de subpíxeles para una colocación precisa de los glifos. Mostrar el quad con textura en coordenadas de píxel fraccional no resuelve el problema, ya que solo da como resultado la interpolación de texturas a nivel de píxel completo. En cambio, queremos lograr un cambio preciso (entre 0 y 1) en el dominio de subpíxeles. Esto se puede hacer en un fragment shader [...].

La solución no es exactamente trivial, por lo que no voy a tratar de explicarlo aquí. (El documento es de acceso abierto).


Otra cosa que aprendí del artículo de Rougier (y que Kilgard no parece haber considerado) es que los poderes de fuente que existen (Microsoft + Adobe) han creado no uno sino dos métodos de especificación de interletraje. El viejo se basa en el llamado núcleo de condensación mesa y se apoya en freetype. El nuevo se llama GPOS y solo es compatible con bibliotecas de fuentes más nuevas como HarfBuzz o pango en el mundo del software libre. Dado que NVpr no parece admitir ninguna de esas bibliotecas, el kerning podría no funcionar de forma inmediata con NVpr para algunas fuentes nuevas; hay algunos de los que aparentemente están en la naturaleza, de acuerdo con esta discusión del foro .

Finalmente, si necesita hacer un diseño de texto complejo (CTL) , parece que actualmente no tiene suerte con OpenGL ya que parece que no existe una biblioteca basada en OpenGL para eso. (DirectWrite, por otro lado, puede manejar CTL). Hay bibliotecas de código abierto como HarfBuzz que pueden procesar CTL, pero no sé cómo podría hacer que funcionen bien (como al usar los métodos basados ​​en stencil) a través de OpenGL. Probablemente tenga que escribir el código de pegamento para extraer los contornos reformados y alimentarlos como soluciones basadas en NVpr o SDF.

Efervescencia
fuente
44
No mencioné NV_path_rendering porque es una extensión, un proveedor exclusivo para empeorar las cosas. Normalmente trato de dar respuestas solo para técnicas que son universalmente aplicables.
datenwolf
1
Bueno, puedo estar de acuerdo con eso hasta cierto punto. El método en sí mismo ("plantilla, luego cubierta") no es realmente difícil de implementar directamente en OpenGL, pero tendrá una alta sobrecarga de comandos si se hace ingenuamente de esa manera, ya que los intentos previos basados ​​en la plantilla terminaron. Skia [a través de Ganesh] probó una solución basada en una plantilla en el punto, pero se rindió, según Kilgrad. La forma en que Nvidia lo implementa, una capa a continuación, utilizando las capacidades de CUDA, lo hace funcionar. Puede intentar "Mantle" StC usted mismo usando un montón de extensiones EXT / ARB. Pero tenga en cuenta que Kilgard / Nvidia tienen dos solicitudes de patente en NVpr.
Fizz
3

Creo que su mejor opción sería mirar en gráficos de El Cairo con el backend de OpenGL.

El único problema que tuve al desarrollar un prototipo con 3.3 núcleos fue el uso de funciones obsoletas en el backend de OpenGL. Fue hace 1-2 años, por lo que la situación podría haber mejorado ...

De todos modos, espero que en el futuro los controladores de gráficos de escritorio opengl implementen OpenVG.

Orhun
fuente