Renderização de texto na WebVR

Nos detalhes

Ver o site

A Dentro (https://with.in/), há uma plataforma para contar histórias em realidade virtual. Quando a equipe descobriu a WebVR em 2015, ficou imediatamente interessada no potencial dela. Hoje, esse interesse se manifesta em um subdomínio exclusivo da nossa plataforma da Web, https://vr.with.in/. Qualquer pessoa com um navegador compatível com RV pode acessar o site, clicar em um botão e colocar um fone de ouvido para participar do nosso portfólio de filmes de RV.

Hoje, isso inclui o Chrome no Daydream View, entre outros. Para saber mais sobre seu dispositivo e headset, consulte https://webvr.info/.

Assim como outros ambientes de renderização específicos de realidade virtual, a Web depende predominantemente de uma representação tridimensional de um cenário. Essa cena tem uma câmera, sua perspectiva e qualquer número de objetos. Para ajudar a gerenciar esse cenário, câmera e objetos, usamos uma biblioteca chamada Three.js, que aproveita o elemento <canvas> para gerar a renderização na GPU do computador. Existem muitos complementos úteis da Three.js para tornar sua cena visível na WebVR. Os dois principais são THREE.VREffect para criar uma janela de visualização para cada olho e THREE.VRControls para traduzir a perspectiva (por exemplo, a rotação e a posição da tela montada na cabeça) de maneira convincente para a cena. Há muitos exemplos de como implementar isso. Confira os exemplos da WebVR do three.js (em inglês) para maneiras de começar.

À medida que progredimos na exploração da WebVR, encontramos um problema. Ao observar o conteúdo da Web, o texto é parte integrante dela. Embora a maior parte do nosso conteúdo seja baseada em vídeo, quando você acessa o texto No site ao redor dele, a interface do usuário e as informações adicionais sobre um filme ou filmes relacionados são criadas com texto. Além disso, todo esse texto é criado no DOM. Nossas explorações da WebVR e a página https://vr.with.in/ estão todas em <canvas>.

Texto usado na WebVR Texto usado na WebVR
Texto usado na WebVR para vr.with.in

Quais são minhas opções?

Felizmente, há muito trabalho a ser feito para que isso seja possível. Na nossa pesquisa, encontramos várias maneiras eficazes de renderizar texto em um ambiente tridimensional em um elemento <canvas>. Abaixo está uma matriz com algumas que encontramos marcadas com prós e contras para cada uma:

Resolução independente Atributos tipográficos Performance Facilidade de implementação
Texto da tela 2D Sim Sim Sim
Texto vetorial triangular Sim Sim
Texto 3D extruído Sim
Texto de bitmap do campo de distância assinada Sim Sim Sim

Nossa decisão: fonte de bitmap do SDF

A tela 2D com ctx.fillText() pode fazer ajuste de texto, espaçamento entre letras e altura da linha, mas o estouro é cortado, e o texto fica desfocado se você aumentar muito o zoom. Você pode aumentar o tamanho da textura da tela, mas atingir um limite superior no tamanho da textura, ou o desempenho poderá ser afetado se a textura for muito grande.

O texto 3D extrudado é basicamente o mesmo que o texto vetorial triangulado, mas com profundidade e possivelmente um chanfro, para que tenha pelo menos o dobro da geometria. Qualquer uma dessas opções pode funcionar em pequenas doses para títulos ou logotipos, mas não vai funcionar tão bem para grandes quantidades de texto e nenhuma delas tem recursos tipográficos.

Fluxo de trabalho de bitmap de fonte para SDF
Fluxo de trabalho de bitmap de fonte para SDF

As fontes de bitmap usam um quad (dois triângulos) por caractere. Portanto, elas usam menos geometria e têm um desempenho melhor do que os vetores triangulados. Eles ainda são baseados em varredura, porque usam um sprite de mapa de textura. No entanto, com um sombreador SDF, eles são basicamente independentes da resolução, ou seja, têm uma aparência melhor do que uma textura de tela 2D. O três-bmfont-text do Matt DesLauriers também inclui recursos tipográficos confiáveis para ajuste de texto, espaçamento de letras, altura e alinhamento da linha. O estouro não é cortado. O tamanho da fonte é controlado por escala. Escolhemos essa rota porque ela nos deu as melhores opções de design com desempenho. Infelizmente, não foi tão fácil de implementar. Por isso, passaremos pelas etapas na esperança de ajudar outros desenvolvedores que trabalham na WebVR.

1. Gerar uma fonte de bitmap (.png + .fnt)

Interface Hiero
Interface do Hiero
Saída hiero (arquivo Bitmap PNG e .fnt) Saída hiero (arquivo Bitmap PNG e .fnt)
Saída do Hiero (arquivo Bitmap PNG e .fnt)

O Hiero é uma ferramenta de empacotamento de fontes de bitmap que é executada com Java. A documentação do Hiero não explica como executá-lo sem passar por um processo de build complicado. Primeiro, instale o Java, caso ainda não tenha feito isso. Em seguida, se você clicar duas vezes no runnable-hiero.jar não abrir o Hiero, tente executá-lo com este comando no console:

java -jar runnable-hiero.jar

Quando o Hiero estiver em execução, abra uma fonte para computadores .ttf ou .otf, insira os caracteres extras que quiser incluir, altere a renderização para Java para ativar efeitos, aumente o tamanho para que seus caracteres preencham todo o quadrado do cache de glifos, adicione um efeito de campo de distância, ajuste a escala e a propagação do campo de distância. O valor de escala é como uma resolução. Quanto maior ela for, menos desfocada será, mas mais tempo vai levar para o Hiero renderizar a visualização. Em seguida, salve a fonte do bitmap. Ele gera uma fonte de bitmap que consiste em uma imagem .png e um arquivo de descrição de fonte .fnt do AngelCode.

2. Converter AngelCode em JSON

Agora que a fonte de bitmap foi gerada, precisamos carregá-la no nosso app JavaScript com o pacote npm load-bmfont de Matt DesLauriers.

Podemos aplicar o navegador load-bmfont e usar isso no front-end, mas vamos executar load-bmfont.js com o Node para converter e salvar o .fnt AngelCode do Hiero em um arquivo.json:

npm install
node load-bmfont.js
Exemplo de JSON de saída
Exemplo de JSON de saída

Agora podemos ignorar load-bmfont e fazer uma solicitação XHR (XMLHttpRequest) no arquivo de fonte .json.

var r = new XMLHttpRequest();
r.open('GET', 'fonts/roboto/bitmap/roboto-bold.json');

r.onreadystatechange = function() {
    if (r.readyState === 4 && r.status === 200) {
    setup(JSON.parse(r.responseText));
    }
};

r.send();

function setup(font) {
    // pass font into TextBitmap object
}

3. Navegador de texto de três bmfonts

Depois que a fonte for carregada, o três-bmfont-text de Matt vai cuidar do restante. Como não estamos usando o Node em nosso próprio app, vamos browser three-bmfont-text.js em um three-bmfont-text-bundle.js

npm install -g browserify
browserify three-bmfont-text.js -o three-bmfont-text-bundle.js

4. Sombreador SDF

Ajuste os controles deslizantes afwidth e threshold em vr.with.in/archive/text-sdf-bitmap/ para conferir o efeito do sombreador de campo de distância assinada.

5. Uso

Por conveniência, criei uma classe de wrapper TextBitmap para o texto três-bmfont-text do navegador.

Text-sdf-bitmap em ação
Text-sdf-bitmap em ação
<script src="three-bmfont-text-bundle.js"></script>
<script src="sdf-shader.js"></script>
<script src="text-bitmap.js"></script>

Crie uma solicitação XHR para o arquivo de fonte .json e crie um objeto de texto no callback:

var bmtext = new TextBitmap({ options });

Para alterar o texto:

bmtext.text = 'The quick brown fox jumps over the lazy dog.';

scene.add( bmtext.group );
hitBoxes.push( bmtext.hitBox );

O arquivo .png da fonte de bitmap é carregado com THREE.TextureLoader em text-bitmap.js.

O TextBitmap também inclui uma hitbox invisível para interação com o raycast três.js por meio de controles de movimento rastreados por mouse, câmera ou manualmente, como o Oculus Touch ou os controles do Vive. O tamanho da hitbox é atualizado automaticamente quando você muda as opções de texto.

O Bmtext.group foi adicionado à cena três.js. Caso você precise acessar os filhos ou do Object3D, o gráfico de cena para o texto vai ter esta aparência:

Diagrama do sistema de arquivos

6. Cancelar a redução de JSON e modificar os deslocamentos x

No GIF do texto

Caso seu kerning fique com a impressão errada, talvez seja necessário editar os xoffsets no json. Cole o JSON em Jsbeautifier.org para ver uma versão não reduzida do arquivo.

O xoffset é essencialmente kerning global para um caractere. O Kerning é para dois caracteres específicos que aparecem um ao lado do outro. Os valores padrão na matriz de kerning não fazem diferença e seria muito tedioso editar, então você pode esvaziar essa matriz para diminuir o tamanho do arquivo json. Em seguida, edite os deslocamentos X para kerning.

Primeiro, você terá que descobrir quais caracteres vão com cada ID de caractere no json. Em three-bmfont-text-bundle.js, insira console.log após a linha 240:

    var id = text.charCodeAt(i)
    // console.log(id);

Em seguida, digite no campo de texto dat.gui em https://vr.with.in/archive/text-sdf-bitmap/ e confira o console para encontrar o ID correspondente de um caractere.

Por exemplo, em nossa fonte de bitmap, "j" está consistentemente muito à direita. O ID de caracteres é 106. Portanto, encontre "id": 106 no json e altere o xoffset de -1 para -10.

7. Layout

Se você tiver vários blocos de texto e quiser que ele flua de cima para baixo, como HTML, tudo precisa ser posicionado manualmente, semelhante ao posicionamento absoluto de cada elemento DOM com CSS. Você consegue imaginar fazer isso no CSS?

    * { position: absolute; }

É assim que é o layout de texto em 3D. Na visualização de detalhes: título, autor, descrição e duração são novos objetos TextBitmap com os próprios estilos, cor, escala etc.:

Layout 3D
author.group.position.y = title.group.position.y - title.height - padding;
description.group.position.y = author.group.position.y - author.height - padding;
duration.group.position.y = description.group.position.y - description.height - padding;

Isso pressupõe que a origem local de cada grupo TextBitmap esteja verticalmente alinhada à parte superior da malha TextBitmap. Consulte a centralização na atualização de text-bitmap.js. Se você alterar o texto de qualquer um desses objetos posteriormente e a altura do objeto mudar, também será necessário recalcular essas posições. Aqui, apenas a posição y do texto é modificada, mas uma oportunidade de trabalhar em 3D é que podemos empurrar e extrair o texto na direção z, além de girar em torno dos eixos x, y e z.

Conclusão

O texto e o layout na WebVR têm um longo caminho a percorrer antes de serem tão fáceis e tão ampliados quanto HTML e CSS. Mas existem soluções funcionais e você pode fazer muito mais na WebVR do que em uma página HTML tradicional. A WebVR existe hoje. Provavelmente haverá ferramentas melhores amanhã. Até lá, faça um teste. Desenvolver sem uma estrutura onipresente leva a projetos mais únicos, e isso é empolgante.