O que é o estado da arte para renderização de texto no OpenGL a partir da versão 4.1? [fechadas]

199

Já existem várias perguntas sobre a renderização de texto no OpenGL, como:

Mas principalmente o que é discutido é renderizar quads texturizados usando o pipeline de função fixa. Certamente shaders devem fazer um caminho melhor.

Não estou realmente preocupado com a internacionalização, a maioria das minhas seqüências de caracteres serão marcações de plotagem (data e hora ou puramente numéricas). Mas os gráficos serão renderizados novamente na taxa de atualização da tela e poderá haver bastante texto (não mais do que alguns milhares de glifos na tela, mas o suficiente para que o layout acelerado por hardware seja bom).

Qual é a abordagem recomendada para renderização de texto usando o OpenGL moderno? (Citar o software existente usando a abordagem é uma boa evidência de que funciona bem)

  • Shaders de geometria que aceitam, por exemplo, posição e orientação e uma sequência de caracteres e emitem quadris texturizados
  • Shaders de geometria que renderizam fontes de vetor
  • Como acima, mas usando shaders de mosaico em vez disso
  • Um sombreador de computação para fazer a rasterização de fontes
Ben Voigt
fonte
10
Eu não sou capaz de responder sobre o estado da arte, sendo principalmente orientado para o OpenGL ES hoje em dia, mas o mosaico de um TTF usando o tesselator GLU e o envio como geometria através do antigo pipeline de funcionalidade fixa com o kerning calculado na CPU deu bons resultados visuais no anti - aliasing hardware e bom desempenho em toda a linha, mesmo quase uma década atrás. Portanto, não é apenas com shaders que você pode encontrar uma maneira 'melhor' (dependendo dos seus critérios, é claro). O FreeType pode cuspir limites de glifo de Bezier e informações de kerning, para que você possa trabalhar ao vivo a partir de um TTF em tempo de execução.
Tommy
O QML2 (do Qt5) faz alguns truques interessantes com o OpenGL e os campos de distância ao renderizar o texto: blog.qt.digia.com/blog/2012/08/08/native-looking-text-in-qml-2
mlvljr
Portanto, não perco novamente, aqui está uma biblioteca que implementa o método de campo de distância da Valve. code.google.com/p/glyphy Eu não tentei. Talvez também valha uma olhada: code.google.com/p/signed-distance-field-font-generator
Timmmm
7
esse "fora de tópico" é a maldição do estouro de pilha. seriamente?
Nikolaos Giotis
1
uma versão mais ingênua do "como fazê-lo": stackoverflow.com/questions/8847899/…
Ciro Santilli #

Respostas:

202

Os contornos de renderização, a menos que você renderize apenas uma dúzia de caracteres no total, permanece "não aprovado" devido ao número de vértices necessários por caractere para aproximar a curvatura. Embora existam abordagens para avaliar curvas de bezier no shader de pixel, elas sofrem por não serem facilmente suavizadas, o que é trivial usando um quad com textura de mapa de distância, e a avaliação de curvas no shader ainda é computacionalmente muito mais cara do que o necessário.

O melhor compromisso entre "rápido" e "qualidade" ainda são quads texturizados com uma textura de campo de distância assinada. É muito mais lento do que usar um quad texturizado normal, mas não tanto. A qualidade, por outro lado, está em um estádio completamente diferente. Os resultados são realmente impressionantes, é o mais rápido possível e efeitos como brilho também são fáceis de adicionar. Além disso, a técnica pode ser rebaixada para hardware mais antigo, se necessário.

Veja o famoso artigo da Valve para a técnica.

A técnica é conceitualmente semelhante à maneira como as superfícies implícitas (metaballs e outras) funcionam, embora não gere polígonos. Ele roda inteiramente no pixel shader e toma a distância amostrada da textura como uma função de distância. Tudo acima do limite escolhido (geralmente 0,5) está "dentro", todo o resto está "fora". No caso mais simples, em um hardware sem capacidade de sombreador de 10 anos, definir o limite do teste alfa como 0,5 fará exatamente isso (embora sem efeitos especiais e antialiasing).
Se alguém quiser adicionar um pouco mais de peso à fonte (negrito falso), um limite um pouco menor fará o truque sem modificar uma única linha de código (apenas altere seu uniforme "font_weight"). Para um efeito de brilho, simplesmente se considera tudo acima de um limite como "dentro" e tudo acima de outro limiar (menor) como "fora, mas no brilho" e LERPs entre os dois. O antialiasing funciona da mesma forma.

Ao usar um valor de distância sinalizada de 8 bits em vez de um único bit, essa técnica aumenta em 16 vezes a resolução efetiva do seu mapa de textura em cada dimensão (em vez de preto e branco, todos os tons possíveis são usados, portanto, temos 256 vezes mais informações usando o mesmo armazenamento). Mas mesmo se você ampliar muito além de 16x, o resultado ainda parecerá aceitável. As longas linhas retas acabarão se tornando um pouco complicadas, mas não haverá artefatos de amostragem "em blocos" típicos.

Você pode usar um sombreador de geometria para gerar os quadríceps a partir de pontos (reduzir a largura de banda do barramento), mas honestamente os ganhos são bastante marginais. O mesmo vale para a renderização instanciada de caracteres, conforme descrito no GPG8. A sobrecarga da instância é amortizada apenas se você tiver muito texto para desenhar. Os ganhos, em minha opinião, não têm relação com a complexidade adicional e a não degradabilidade. Além disso, você está limitado pela quantidade de registros constantes ou precisa ler um objeto de buffer de textura, o que não é ideal para a coerência do cache (e a intenção era otimizar para começar!).
Um buffer de vértice antigo simples e simples é tão rápido (possivelmente mais rápido) se você agendar o upload um pouco antes e executar em todos os hardwares construídos nos últimos 15 anos. E não se limita a nenhum número específico de caracteres na sua fonte, nem a um número específico de caracteres para renderizar.

Se você tiver certeza de que não possui mais de 256 caracteres em sua fonte, vale a pena considerar as matrizes de textura para reduzir a largura de banda do barramento de maneira semelhante à geração de quads a partir de pontos no sombreador de geometria. Ao usar uma textura de matriz, as coordenadas de textura de todos os quadriláteros têm idênticas, constantes se tcoordenadas e diferem apenas na rcoordenada, que é igual ao índice de caracteres a ser renderizado.
Mas, como nas outras técnicas, os ganhos esperados são marginais ao custo de serem incompatíveis com o hardware da geração anterior.

Existe uma ferramenta útil de Jonathan Dummer para gerar texturas de distância: página de descrição

Atualização:
Como apontado mais recentemente em Programmable Vertex Pulling (D.Rákos, "OpenGL Insights", pp. 239), não há latência ou sobrecarga extra significativa associada à extração de dados de vértice programaticamente do shader nas novas gerações de GPUs, em comparação a fazer o mesmo usando a função fixa padrão.
Além disso, as últimas gerações de GPUs têm cada vez mais caches L2 de uso geral de tamanho razoável (por exemplo, 1536kiB na nvidia Kepler), portanto, pode-se esperar que o problema de acesso incoerente ao extrair desvios aleatórios para os cantos quádruplos de uma textura de buffer seja menos problema.

Isso torna mais atraente a ideia de extrair dados constantes (como tamanhos de quad) de uma textura de buffer. Uma implementação hipotética poderia, portanto, reduzir o PCIe e as transferências de memória, bem como a memória da GPU, ao mínimo com uma abordagem como esta:

  • Carregue apenas um índice de caracteres (um por caractere a ser exibido) como a única entrada para um sombreador de vértice que passa nesse índice gl_VertexIDe amplifique-o para 4 pontos no sombreador de geometria, ainda tendo o índice de caracteres e o ID do vértice (este será "gl_primitiveID disponibilizado no sombreador de vértice") como os únicos atributos e capture isso por meio do feedback de transformação.
  • Isso será rápido, porque existem apenas dois atributos de saída (gargalo principal no GS), e é quase "no-op" caso contrário, nos dois estágios.
  • Vincule uma textura de buffer que contenha, para cada caractere na fonte, as posições de vértice do quad texturizado em relação ao ponto base (essas são basicamente as "métricas da fonte"). Esses dados podem ser compactados para 4 números por quad, armazenando apenas o deslocamento do vértice inferior esquerdo e codificando a largura e a altura da caixa alinhada ao eixo (assumindo meias flutuações, serão 8 bytes de buffer constante por caractere - uma fonte típica de 256 caracteres pode caber completamente no cache 2kiB do L1).
  • Definir um uniforme para a linha de base
  • Vincule uma textura de buffer com deslocamentos horizontais. Estes poderiam provavelmente ainda ser calculado com GPU, mas é muito mais fácil e mais eficiente para esse tipo de coisa na CPU, uma vez que é uma operação estritamente sequencial e não em todos trivial (pense kerning). Além disso, seria necessário outro passe de feedback, que seria outro ponto de sincronização.
  • Renderize os dados gerados anteriormente a partir do buffer de feedback, o sombreador de vértice puxa o deslocamento horizontal do ponto base e as compensações dos vértices de canto dos objetos do buffer (usando a identificação primitiva e o índice de caracteres). O ID de vértice original dos vértices enviados agora é nosso "ID primitivo" (lembre-se de que o GS transformou os vértices em quads).

Dessa maneira, seria possível reduzir idealmente a largura de banda de vértice necessária em 75% (amortizado), embora apenas pudesse renderizar uma única linha. Se alguém quiser renderizar várias linhas em uma chamada de desenho, será necessário adicionar a linha de base à textura do buffer, em vez de usar um uniforme (diminuindo o ganho de largura de banda).

No entanto, mesmo assumindo uma redução de 75% - uma vez que os dados do vértice para exibir quantidades "razoáveis" de texto estão apenas em torno de 50 a 100 kB (o que é praticamente zeropara uma GPU ou um barramento PCIe) - ainda duvido que a complexidade adicionada e a perda de compatibilidade com versões anteriores valham a pena. Reduzir o zero em 75% ainda é apenas zero. Eu reconhecidamente não tentei a abordagem acima, e seriam necessárias mais pesquisas para fazer uma declaração verdadeiramente qualificada. Ainda assim, a menos que alguém possa demonstrar uma diferença de desempenho verdadeiramente impressionante (usando quantidades "normais" de texto, e não bilhões de caracteres!), Meu ponto de vista permanece: para os dados de vértice, um buffer de vértice simples e simples é justificadamente bom o suficiente ser considerado parte de uma "solução de última geração". É simples e direto, funciona e funciona bem.

Depois de já ter mencionado o " OpenGL Insights " acima, vale destacar também o capítulo "Renderização de formas 2D por campos a distância", de Stefan Gustavson, que explica detalhadamente a renderização de campos a distância.

Atualização 2016:

Enquanto isso, existem várias técnicas adicionais que visam remover os artefatos de cantos arredondados que se tornam perturbadores com ampliações extremas.

Uma abordagem simplesmente usa campos de pseudo-distância em vez de campos de distância (a diferença é que a distância é a menor distância não do contorno real, mas do contorno ou de uma linha imaginária que se projeta sobre a borda). Isso é um pouco melhor e é executado na mesma velocidade (sombreador idêntico), usando a mesma quantidade de memória de textura.

Outra abordagem usa a mediana de três em detalhes de textura de três canais e implementação disponível no github . Isso visa ser uma melhoria em relação aos e-ou hacks usados ​​anteriormente para solucionar o problema. Boa qualidade, levemente, quase não notavelmente, mais lenta, mas usa três vezes mais memória de textura. Além disso, efeitos extras (por exemplo, brilho) são mais difíceis de acertar.

Por fim, armazenar as curvas bezier reais que compõem os personagens e avaliá-las em um shader de fragmento se tornou prático , com desempenho ligeiramente inferior (mas não tanto que isso é um problema) e resultados impressionantes, mesmo com ampliações mais altas.
Demonstração WebGL que renderiza um PDF grande com esta técnica em tempo real disponível aqui .

Damon
fonte
1
Eles parecem muito bons (mesmo com filtragem ingênua e na ausência de mipmapping, pois você tem texturas muito pequenas e os dados interpolam muito bem). Pessoalmente, acho que eles até parecem melhores do que a coisa "real" em muitos casos, porque não há esquisitices como dicas, que geralmente produzem coisas que considero "estranhas". Por exemplo, texto menor não fica subitamente em negrito, sem motivo óbvio, nem aparece nos limites dos pixels - efeitos que você costuma ver com fontes "reais". Pode haver razões históricas para isso (exibição em preto e branco), mas hoje está além da minha compreensão por que tem que ser assim.
Damon
2
Funciona e parece ótimo, obrigado por compartilhar! Para quem deseja fonte de shader HLSL, veja aqui . Você pode adaptar isso ao GLSL substituindo a clip(...)linha por if (text.a < 0.5) {discard;}(ou text.a < threshold). HTH.
Engenheiro
1
Obrigado pela atualização. Eu gostaria de poder votar novamente.
Ben Voigt
2
@ NicolBolas: Você parece não ter lido com muito cuidado. Ambas as perguntas são explicadas na resposta. Kepler é dado como um exemplo de "última geração", não existe uma segunda passagem (e é explicado o porquê), e afirmo que não acredito que a técnica de economia de largura de banda hipotética seja visivelmente mais rápida ou que valha a pena. No entanto, crença não significa nada - seria preciso tentar saber (não considero, pois não considero desenhar quantidades "normais" de texto um gargalo de qualquer maneira). Ele poderia , no entanto, ser útil em um quando se está desesperado sobre largura de banda e tem quantidades "anormais" de texto.
Damon
3
@ NicolBolas: Você está certo sobre essa frase, desculpe. É realmente um pouco enganador. No parágrafo anterior, escrevi "Provavelmente, é possível gerar isso na GPU, mas isso exigiria feedback e ... isnogud". - mas continuou erroneamente com "os dados gerados a partir do buffer de feedback" . Eu vou corrigir isso. Na verdade, vou reescrever a coisa completa no fim de semana, por isso é menos ambígua.
Damon
15

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

A principal diferença entre o GLyphy e outros renderizadores OpenGL baseados em SDF é que a maioria dos outros projetos mostra o SDF em uma textura. Isso tem todos os problemas usuais que a amostragem tem. Ou seja. distorce o contorno e é de baixa qualidade. GLyphy representa o SDF usando vetores reais enviados à GPU. Isso resulta em renderização de alta qualidade.

A desvantagem é que o código é para iOS com o OpenGL ES. Provavelmente vou criar uma porta Windows / Linux OpenGL 4.x (espero que o autor adicione alguma documentação real).

Nome em Exibição
fonte
3
Qualquer pessoa interessada no GLyphy provavelmente deveria assistir à palestra do autor em Linux.conf.au 2014: youtube.com/watch?v=KdNxR5V7prk
Fizz
14

A técnica mais difundida ainda é quads texturizados. No entanto, em 2005, o LORIA desenvolveu algo chamado texturas vetoriais, ou seja, renderização de gráficos vetoriais como texturas em primitivas. Se alguém usa isso para converter fontes TrueType ou OpenType em uma textura vetorial, você obtém o seguinte:

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

datenwolf
fonte
2
Você conhece alguma implementação usando essa técnica?
quer
2
Não (como no nível de produção), mas o artigo de Kilgard (veja minha resposta abaixo no link) tem uma breve crítica, que eu sumario como: ainda não prático. Houve mais pesquisas na área; trabalho mais recente citado por Kilgard inclui research.microsoft.com/en-us/um/people/hoppe/ravg.pdf e uwspace.uwaterloo.ca/handle/10012/4262
Fizz
9

Estou surpreso que o bebê de Mark Kilgard, NV_path_rendering (NVpr), não tenha sido mencionado por nenhum dos itens acima. Embora seus objetivos sejam mais gerais que a renderização de fontes, ele também pode renderizar texto de fontes e com o kerning. Nem sequer requer o OpenGL 4.1, mas é uma extensão apenas de fornecedor / Nvidia no momento. Basicamente, transforma fontes em caminhos usando o glPathGlyphsNVque depende da biblioteca freetype2 para obter as métricas, etc. Em seguida, você também pode acessar as informações de kerning glGetPathSpacingNVe usar o mecanismo geral de renderização de caminho do NVpr para exibir texto usando as fontes "convertidas" do caminho. (Coloquei isso entre aspas, porque não há conversão real, as curvas são usadas como estão.)

A demonstração gravada para recursos de fonte de NVpr infelizmente não é particularmente impressionante. (Talvez alguém deva fazer um na linha da demo SDF muito mais sofisticada que se pode encontrar nos intertubos ...)

A palestra de apresentação da API NVpr 2011 para a parte de fontes começa aqui e continua na próxima parte ; é um pouco lamentável como essa apresentação é dividida.

Materiais mais gerais sobre NVpr:

  • Nvidia NVpr hub , mas algum material na página de destino não é o mais atualizado
  • Documento da Siggraph 2012 para os cérebros do método de renderização de caminho, chamado "stencil, then cover" (StC); o documento também explica brevemente como a tecnologia concorrente como o Direct2D funciona. Os bits relacionados à fonte foram relegados a um anexo do artigo . Existem também alguns extras, como vídeos / demos .
  • Apresentação do GTC 2014 para um status de atualização; em poucas palavras: agora é suportado pelo Skia do Google (a Nvidia contribuiu com o código no final de 2013 e 2014), que por sua vez é usado no Google Chrome e [independentemente do Skia, acho] em uma versão beta do Adobe Illustrator CC 2014
  • a documentação oficial no registro de extensão OpenGL
  • O USPTO concedeu pelo menos quatro patentes à Kilgard / Nvidia em conexão com o NVpr, das quais você provavelmente deve estar ciente, caso queira implementar o StC por si mesmo: US8698837 , US8698808 , US8704830 e US8730253 . Observe que existem mais 17 documentos USPTO conectados a isso como "também publicados como", a maioria dos quais são pedidos de patentes, portanto é perfeitamente possível que mais patentes possam ser concedidas a partir deles.

E como a palavra "estêncil" não produziu nenhum acerto nesta página antes da minha resposta, parece que o subconjunto da comunidade SO que participou desta página na medida em que, apesar de bastante numeroso, não tinha conhecimento de buffer de estêncil, sem estêncil métodos baseados na renderização de caminho / fonte em geral. Kilgard tem uma postagem do tipo FAQ no fórum opengl, que pode esclarecer como os métodos de renderização de caminho sem mosaicos diferem dos gráficos 3D comuns, mesmo que eles ainda estejam usando uma GPU [GP]. (O NVpr precisa de um chip compatível com CUDA.)

Para uma perspectiva histórica, Kilgard também é o autor do clássico "Uma API simples baseada em OpenGL para Textos Mapeados em Texturas", SGI, 1997 , que não deve ser confundida com o NVpr baseado em estêncil que estreou em 2011.


A maioria, se não todos os métodos recentes discutidos nesta página, incluindo métodos baseados em estêncil, como NVpr ou SDF, como GLyphy (que não discutirei mais aqui porque outras respostas já o abordam) têm, no entanto, uma limitação: eles são adequado para exibição de texto grande em monitores convencionais (~ 100 DPI) sem recortes em qualquer nível de escala e também tem uma aparência agradável, mesmo em tamanho pequeno, em telas com retina com alto DPI. No entanto, eles não fornecem totalmente o que o Direct2D + DirectWrite da Microsoft oferece, ou seja, dicas de pequenos glifos nos monitores convencionais. (Para uma pesquisa visual de dicas em geral, consulte esta página de tipoteca, por exemplo. Um recurso mais detalhado está em antigrain.com .)

Não conheço nenhum material aberto e produtivo baseado em OpenGL que possa fazer o que a Microsoft pode sugerir no momento. (Admito desconhecimento dos componentes internos do OS X GL / Quartz da Apple, porque, até onde eu sei, a Apple não publicou como eles fazem coisas de renderização de fontes / caminhos com base em GL. Parece que o OS X, ao contrário do MacOS 9, não de qualquer forma, o que incomoda algumas pessoas .) De qualquer forma, existe um artigo de pesquisa de 2013 que aborda dicas através de shaders OpenGL escritos por Nicolas P. Rougier, do INRIA; provavelmente vale a pena ler se você precisar dar dicas do OpenGL. Embora possa parecer que uma biblioteca como o freetype já faça todo o trabalho quando se trata de sugestões, isso não é verdade pelo seguinte motivo, que estou citando no artigo:

A biblioteca FreeType pode rasterizar um glifo usando a suavização de serrilhado de sub-pixel no modo RGB. No entanto, isso é apenas metade do problema, pois também queremos obter o posicionamento sub-pixel para um posicionamento preciso dos glifos. A exibição do quad texturizado em coordenadas fracionárias de pixels não resolve o problema, pois resulta apenas na interpolação de texturas no nível de pixel inteiro. Em vez disso, queremos obter uma mudança precisa (entre 0 e 1) no domínio do subpixel. Isso pode ser feito em um shader de fragmento [...].

A solução não é exatamente trivial, então não vou tentar explicá-la aqui. (O documento é de acesso aberto.)


Outra coisa que aprendi no artigo de Rougier (e que Kilgard parece não ter considerado) é que os poderes de fonte que são (Microsoft + Adobe) criaram não um, mas dois métodos de especificação de kerning. A antiga é baseada na chamada tabela kern e é suportada pelo tipo livre. O novo é chamado GPOS e é suportado apenas por bibliotecas de fontes mais recentes, como HarfBuzz ou pango, no mundo do software livre. Como o NVpr parece não suportar nenhuma dessas bibliotecas, o kerning pode não funcionar imediatamente com o NVpr para algumas novas fontes; existem alguns aparentemente em estado selvagem, de acordo com esta discussão no fórum .

Por fim, se você precisar criar um layout de texto complexo (CTL), parece que está sem sorte no momento com o OpenGL, pois nenhuma biblioteca baseada em OpenGL parece existir para isso. (Por outro lado, o DirectWrite pode manipular CTL.) Existem bibliotecas de código aberto, como o HarfBuzz, que podem renderizar CTL, mas não sei como você faria com que elas funcionassem bem (como no uso dos métodos baseados em estêncil) via OpenGL. Você provavelmente teria que escrever o código da cola para extrair os contornos remodelados e alimentá-los em soluções baseadas em NVpr ou SDF como caminhos.

Efervescer
fonte
4
Eu não mencionei NV_path_rendering porque é uma extensão, um fornecedor proprietário para piorar a situação. Normalmente, tento dar respostas apenas para técnicas universalmente aplicáveis.
datenwolf
1
Bem, eu posso concordar com isso até certo ponto. O método em si ("estêncil, em seguida, capa") não é realmente difícil de implementar diretamente no OpenGL, mas terá uma sobrecarga de comando alta se for feito de maneira ingênua dessa maneira, pois as tentativas anteriores baseadas em estêncil acabaram. A Skia [via Ganesh] tentou uma solução baseada em estêncil no momento, mas desistiu, de acordo com Kilgrad. A maneira como é implementada pela Nvidia, uma camada abaixo, usando os recursos CUDA, faz com que ela funcione. Você pode tentar "Mantle" StC você mesmo usando várias extensões EXT / ARB. Mas lembre-se de que a Kilgard / Nvidia tem dois pedidos de patente no NVpr.
Fizz
3

Eu acho que sua melhor aposta seria procurar gráficos cairo com o back-end do OpenGL.

O único problema que tive ao desenvolver um protótipo com o núcleo 3.3 foi o uso obsoleto da função no back-end do OpenGL. Foi há 1-2 anos, então a situação pode ter melhorado ...

De qualquer forma, espero que, no futuro, os drivers gráficos de desktop opengl implementem o OpenVG.

Orhun
fonte