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 s
e t
coordenadas e diferem apenas na r
coordenada, 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_VertexID
e 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 .