Cómo renderizar texto en WebVR

En detalle

Visita el sitio

Dentro de (https://with.in/), hay una plataforma para narrar historias en realidad virtual. Por lo tanto, cuando el equipo escuchó sobre WebVR en 2015, nos interesamos inmediatamente por su potencial. Hoy en día, ese interés se manifiesta en un subdominio único de nuestra plataforma web: https://vr.with.in/. Cualquier persona que tenga un navegador habilitado para RV puede visitar el sitio, hacer clic en un botón y usar visores para sumergirse en nuestra cartera de películas de RV.

En la actualidad, Chrome incluye, entre otros, Chrome en Daydream View. Para obtener información sobre el dispositivo y la pantalla de cabeza, consulta https://webvr.info/.

Al igual que otros entornos de renderización específicos de realidad virtual, la Web se basa principalmente en una representación tridimensional de una escena. Esta escena tiene una cámara, tu perspectiva y una variedad de objetos. Para ayudar a administrar esta escena, la cámara y los objetos, usamos una biblioteca llamada Three.js que aprovecha el elemento <canvas> a fin de mostrar la renderización en la GPU de la computadora. Existen muchos complementos útiles de Three.js que permiten que tu escena sea visible en WebVR. Los dos principales son THREE.VREffect, que permite crear un viewport para cada ojo, y THREE.VRControls, que permite traducir la perspectiva (p. ej., la rotación y la posición de la pantalla montada en la cabeza) a tu escena de manera convincente. Hay muchos ejemplos de cómo implementarlo. Consulta los ejemplos de WebVR de Three.js para conocer las formas de comenzar.

A medida que adentramos en nuestra exploración de WebVR, encontramos un problema. Si observamos el contenido de la web, el texto es una parte integral. Aunque la mayoría de nuestro contenido está basado en videos, si vas al texto Dentro del sitio, rodea el contenido. La interfaz de usuario y la información adicional sobre una película o películas relacionadas se crean con texto. Además, todo este texto se crea en el DOM. Nuestras exploraciones de WebVR y https://vr.with.in/ están todas en <canvas>.

Texto que se usa en WebVR Texto que se usa en WebVR
Texto usado en WebVR para vr.with.in

¿Cuáles son mis opciones?

Por suerte, estamos trabajando para que esto sea posible. De hecho, en nuestra investigación, descubrimos varias formas eficaces de renderizar texto en un entorno tridimensional en un elemento <canvas>. A continuación, se muestra una matriz de algunas que encontramos marcadas con ventajas y desventajas para cada una:

Resolución independiente Características tipográficas Rendimiento Facilidad de implementación
Texto del lienzo 2D
Texto vectorial triangulado
Texto 3D eliminado
Texto del mapa de bits de campo de distancia firmado

Nuestra decisión: fuente de mapa de bits SDF

El lienzo 2D con ctx.fillText() puede ajustar el texto, el interlineado y la altura de línea, pero el desbordamiento se corta y el texto se ve borroso si te acercas mucho más. Podrías aumentar el tamaño de la textura del lienzo, pero podrías alcanzar un límite superior en cuanto al tamaño o el rendimiento de la textura podría verse afectado si la textura es demasiado grande.

El texto extruido en 3D es, en esencia, lo mismo que el texto vectorial triangulado, pero con profundidad y posiblemente un bisel, por lo que tiene al menos el doble de geometría. Cualquiera de ellos podría funcionar en pequeñas dosis para títulos o logotipos, pero no tendría un rendimiento tan bueno para grandes cantidades de texto y ninguna tiene características tipográficas.

Flujo de trabajo de fuente a SDF de mapa de bits
Flujo de trabajo de mapa de bits de fuente a SDF

Las fuentes de mapa de bits usan un cuadrante (dos triángulos) por carácter, por lo que usan menos geometría y tienen un mejor rendimiento que los vectores triangulados. Siguen se basan en tramas, ya que usan un objeto de mapa de texturas, pero con un sombreador de SDF, básicamente no dependen de la resolución, por lo que se ven más bonitos que una textura de lienzo 2D. El texto de tres bmfont de Matt DesLauriers también incluye funciones tipográficas confiables para el ajuste de texto, el espaciado entre letras, la altura de línea y la alineación. El desbordamiento no se corta. El tamaño de la fuente se controla a través de la escala. Elegimos esta opción porque nos dio las mejores opciones de diseño sin perder el buen rendimiento. Lamentablemente, no fue tan fácil de implementar, por lo que seguiremos con los pasos para ayudar a otros desarrolladores que trabajan en WebVR.

1. Cómo generar una fuente de mapa de bits (.png + .fnt)

Interfaz de Hiero
Interfaz Hiero
Salida de Hiero (archivo .fnt y PNG de mapa de bits) Salida de Hiero (archivo .fnt y PNG de mapa de bits)
Salida de Hiero (archivo .fnt y mapa de bits PNG)

Hiero es una herramienta de empaquetado de fuentes de mapas de bits que se ejecuta con Java. En la documentación de Hiero, en realidad, no se explica cómo ejecutarlo sin pasar por un proceso de compilación complicado. Primero, instala Java si aún no lo has hecho. Luego, si haces doble clic en runnable-hiero.jar no abre Hiero, intenta ejecutarlo con este comando en la consola:

java -jar runnable-hiero.jar

Una vez que se ejecute Hiero, abre una fuente de escritorio .ttf o .otf, ingresa los caracteres adicionales que quieras incluir, cambia el procesamiento a Java para habilitar los efectos, aumenta el tamaño para que los caracteres ocupen todo el cuadrado de la caché de glifos, agrega un efecto de campo de distancia y ajusta la escala y la expansión del campo de distancia. El valor de escala es como una resolución. Cuanto más alta sea, menos desenfocada estará, pero más tiempo le tomará a Hiero renderizar la vista previa. Luego, guarda la fuente del mapa de bits. Genera una fuente de mapa de bits que consta de una imagen .png y un archivo de descripción de fuente AngelCode .fnt.

2. Convierte AngelCode a JSON

Ahora que se generó la fuente de mapa de bits, debemos cargarla en nuestra app de JavaScript con el paquete load-bmfont npm de Matt DesLauriers.

Podríamos usar navegadores de load-bmfont y usarlos en el frontend, pero, en su lugar, ejecutaremos load-bmfont.js con Node para convertir y guardar el AngelCode .fnt de Hiero en un archivo.json:

npm install
node load-bmfont.js
Ejemplo de JSON de salida
Ejemplo de JSON de salida

Ahora podemos omitir la carga-bmfont y solo hacer una solicitud XHR (XMLHttpRequest) en el archivo de fuentes .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. Browserify tres-bmfont-text

Una vez que hayamos cargado la fuente, el texto de tres bmfont de Matt se encargará del resto. Como no usamos Node para nuestra app, vamos a browserify tres-bmfont-text.js en un three-bmfont-text-bundle.js utilizable.

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

4. Sombreador de SDF

Ajusta los controles deslizantes de afwidth y threshold en vr.with.in/archive/text-sdf-bitmap/ para ver el efecto del sombreador de campos de distancia firmado.

5. Uso

Para mayor comodidad, creé una clase de wrapper de TextBitmap para el texto de tres bmfont-text en buscado.

Text-sdf-bitmap en acción
Text-sdf-bitmap en acción
<script src="three-bmfont-text-bundle.js"></script>
<script src="sdf-shader.js"></script>
<script src="text-bitmap.js"></script>

Crea una solicitud XHR para el archivo de fuentes .json y crea un objeto de texto en la devolución de llamada:

var bmtext = new TextBitmap({ options });

Para cambiar el texto, haz lo siguiente:

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

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

El .png de la fuente del mapa de bits se carga con THREE.TextureLoader en text-bitmap.js

TextBitmap también incluye una caja de colisiones invisible para interacciones con raycast de tres.js mediante un mouse, una cámara o controles de movimiento manuales, como Oculus Touch o los controladores Vive. El tamaño de la caja de colisiones se actualiza automáticamente cuando cambias las opciones de texto.

Se agrega bmtext.group a la escena tres.js. Si necesitas acceder a los objetos secundarios o Object3D, el gráfico de escena del texto se verá de la siguiente manera:

Diagrama del sistema de archivos

6. Unminifica JSON y modifica xoffsets

Dentro de GIF de texto

Si tu kerning no se ve bien, es posible que debas editar los xoffsets en el archivo json. Pega el json en Jsbeautifier.org para obtener una versión unminificada del archivo.

El xoffset es, en esencia, el interletraje global para un carácter. El interlineado es para dos caracteres específicos que aparecen uno al lado del otro. Los valores predeterminados del array del interletraje no hacen ninguna diferencia y sería demasiado tedioso editarlos, por lo que puedes vaciar ese array para disminuir el tamaño del archivo de JSON. Luego, edita los xoffsets para el interletraje.

Primero, deberás descubrir qué caracteres van con qué ID de carácter en el archivo json. En three-bmfont-text-bundle.js, inserta console.log después de la línea 240:

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

Luego, escribe en el campo de texto dat.gui en https://vr.with.in/archive/text-sdf-bitmap/ y revisa la consola para encontrar el ID correspondiente de un carácter.

Por ejemplo, en la fuente de mapa de bits, "j" siempre está demasiado a la derecha. Su ID de carácter es 106. Por lo tanto, busca "id": 106 en el JSON y cambia su xoffset de -1 a -10.

7. Diseño

Si tienes varios bloques de texto y deseas que fluyan de arriba abajo como si fuera HTML, todo debe posicionarse manualmente, de manera similar al posicionamiento absoluto de todos los elementos del dominio con CSS. ¿Te imaginas hacer esto en CSS?

    * { position: absolute; }

Así es el diseño de texto en 3D. En la vista de detalles, el título, el autor, la descripción y la duración son cada uno un objeto TextBitmap nuevo con sus propios estilos, color, escala, etcétera:

Diseño 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;

De esta manera, se supone que el origen local de cada grupo TextBitmap está alineado de forma vertical con la parte superior de la malla TextBitmap (consulta el enfoque en la actualización de text-bitmap.js). Si luego cambias el texto de alguno de esos objetos y la altura del objeto cambia, también deberás volver a calcular esas posiciones. Aquí, solo se modifica la posición Y del texto, pero una oportunidad de trabajar en 3D es que podemos desplazar y extraer el texto en la dirección z, así como rotar alrededor de los ejes x, y y z.

Conclusión

El texto y el diseño en WebVR tienen un largo camino por recorrer antes de que sean tan fáciles de usar y tan populares como HTML y CSS. Pero existen soluciones que funcionan, y en WebVR puedes hacer mucho más que con una página web HTML tradicional. WebVR existe hoy. Es probable que mañana haya mejores herramientas. Hasta entonces, pruébalo y experimenta. Desarrollar sin un framework ubicuo conduce a proyectos más únicos, y eso es emocionante.