pixiv es un servicio comunitario en línea para ilustradores y fanáticos de la ilustración que les permite comunicarse entre sí a través de su contenido. Permite a las personas publicar sus propias ilustraciones. Tiene más de 84 millones de usuarios en todo el mundo y más de 120 millones de obras de arte publicadas a mayo de 2023.
pixiv Sketch es uno de los servicios que proporciona pixiv. Se usa para dibujar obras de arte en el sitio web con dedos o plumas stylus. Admite una variedad de funciones para dibujar ilustraciones increíbles, incluidos varios tipos de pinceles, capas y pintura con cubo, y también permite que las personas transmitan en vivo su proceso de dibujo.
En este caso de éxito, veremos cómo pixiv Sketch mejoró el rendimiento y la calidad de su app web con algunas funciones nuevas de la plataforma web, como WebGL, WebAssembly y WebRTC.
¿Por qué desarrollar una app de bocetos en la Web?
pixiv Sketch se lanzó por primera vez en la Web y en iOS en 2015. Su público objetivo para la versión web eran principalmente las computadoras de escritorio, que sigue siendo la plataforma más importante que usa la comunidad de ilustraciones.
Estos son los dos motivos principales por los que pixiv decidió desarrollar una versión web en lugar de una app para computadoras:
- Crear apps para Windows, Mac, Linux y otros sistemas es muy costoso. La Web llega a cualquier navegador en la computadora de escritorio.
- La Web tiene el mejor alcance en todas las plataformas. La Web está disponible en computadoras de escritorio y dispositivos móviles, y en cualquier sistema operativo.
Tecnología
pixiv Sketch tiene varios pinceles diferentes para que los usuarios elijan. Antes de adoptar WebGL, solo había un tipo de pincel, ya que el lienzo en 2D era demasiado limitado para representar la textura compleja de diferentes pinceles, como los bordes gruesos de un lápiz y la intensidad de ancho y color diferentes que cambia según la presión del boceto.
Tipos de pinceles creativos con WebGL
Sin embargo, con la adopción de WebGL, pudieron agregar más variedades en los detalles de los pinceles y aumentar la cantidad de pinceles disponibles a siete.
Con el contexto de lienzo 2D, solo se podían dibujar líneas con una textura simple con un ancho distribuido de manera uniforme, como en la siguiente captura de pantalla:
Estas líneas se dibujaron creando rutas y trazos, pero WebGL las reproduce con sprites y sombreadores de puntos, como se muestra en los siguientes ejemplos de código.
En el siguiente ejemplo, se muestra un sombreador de vértices.
precision highp float;
attribute vec2 pos;
attribute float thicknessFactor;
attribute float opacityFactor;
uniform float pointSize;
varying float varyingOpacityFactor;
varying float hardness;
// Calculate hardness from actual point size
float calcHardness(float s) {
float h0 = .1 * (s - 1.);
float h1 = .01 * (s - 10.) + .6;
float h2 = .005 * (s - 30.) + .8;
float h3 = .001 * (s - 50.) + .9;
float h4 = .0002 * (s - 100.) + .95;
return min(h0, min(h1, min(h2, min(h3, h4))));
}
void main() {
float actualPointSize = pointSize * thicknessFactor;
varyingOpacityFactor = opacityFactor;
hardness = calcHardness(actualPointSize);
gl_Position = vec4(pos, 0., 1.);
gl_PointSize = actualPointSize;
}
En el siguiente ejemplo, se muestra código de muestra para un sombreador de fragmentos.
precision highp float;
const float strength = .8;
const float exponent = 5.;
uniform vec4 color;
varying float hardness;
varying float varyingOpacityFactor;
float fallOff(const float r) {
// w is for width
float w = 1. - hardness;
if (w < 0.01) {
return 1.;
} else {
return min(1., pow(1. - (r - hardness) / w, exponent));
}
}
void main() {
vec2 texCoord = (gl_PointCoord - .5) * 2.;
float r = length(texCoord);
if (r > 1.) {
discard;
}
float brushAlpha = fallOff(r) * varyingOpacityFactor * strength * color.a;
gl_FragColor = vec4(color.rgb, brushAlpha);
}
El uso de sprites de punto permite variar la grosor y las sombras de manera directa en respuesta a la presión de dibujo, lo que permite expresar las siguientes líneas fuertes y débiles, como las siguientes:
Además, las implementaciones que usan objetos de punto ahora pueden adjuntar texturas con un sombreador independiente, lo que permite una representación eficiente de los pinceles con texturas como el lápiz y el lápiz.
Compatibilidad con la pluma stylus en el navegador
El uso de una pluma stylus digital se ha vuelto muy popular entre los artistas digitales. Los navegadores modernos admiten la API de PointerEvent, que permite a los usuarios usar una pluma stylus en su dispositivo. Usa PointerEvent.pressure
para medir la presión de la pluma y PointerEvent.tiltX
, PointerEvent.tiltY
para medir el ángulo de la pluma con respecto al dispositivo.
Para realizar trazos de pincel con un sprite de punto, se debe interpolar PointerEvent
y convertirlo en una secuencia de eventos más detallada. En el evento de puntero, la orientación de la pluma stylus se puede obtener en forma de coordenadas polares, pero pixiv Sketch las convierte en un vector que representa la orientación de la pluma stylus antes de usarlas.
function getTiltAsVector(event: PointerEvent): [number, number, number] {
const u = Math.tan((event.tiltX / 180) * Math.PI);
const v = Math.tan((event.tiltY / 180) * Math.PI);
const z = Math.sqrt(1 / (u * u + v * v + 1));
const x = z * u;
const y = z * v;
return [x, y, z];
}
function handlePointerDown(event: PointerEvent) {
const position = [event.clientX, event.clientY];
const pressure = event.pressure;
const tilt = getTiltAsVector(event);
interpolateAndRender(position, pressure, tilt);
}
Varias capas de dibujo
Las capas son uno de los conceptos más únicos del dibujo digital. Permiten que los usuarios dibujen diferentes piezas de ilustración una encima de la otra y permiten realizar ediciones capa por capa. En pixiv Sketch, las funciones de capas son similares a las de otras apps de dibujo digital.
De manera convencional, es posible implementar capas usando varios elementos <canvas>
con drawImage()
y operaciones de composición. Sin embargo, esto es problemático porque, con el contexto de lienzo 2D, no hay otra opción que usar el modo de composición CanvasRenderingContext2D.globalCompositeOperation
, que está predefinido y limita en gran medida la escalabilidad. Cuando usas WebGL y escribes el sombreador, los desarrolladores pueden usar modos de composición que la API no predefine. En el futuro, pixiv Sketch implementará la función de capas con WebGL para lograr una mayor escalabilidad y flexibilidad.
Este es el código de muestra para la composición de capas:
precision highp float;
uniform sampler2D baseTexture;
uniform sampler2D blendTexture;
uniform mediump float opacity;
varying highp vec2 uv;
// for normal mode
vec3 blend(const vec4 baseColor, const vec4 blendColor) {
return blendColor.rgb;
}
// for multiply mode
vec3 blend(const vec4 baseColor, const vec4 blendColor) {
return blendColor.rgb * blendColor.rgb;
}
void main()
{
vec4 blendColor = texture2D(blendTexture, uv);
vec4 baseColor = texture2D(baseTexture, uv);
blendColor.a *= opacity;
float a1 = baseColor.a * blendColor.a;
float a2 = baseColor.a * (1. - blendColor.a);
float a3 = (1. - baseColor.a) * blendColor.a;
float resultAlpha = a1 + a2 + a3;
const float epsilon = 0.001;
if (resultAlpha > epsilon) {
vec3 noAlphaResult = blend(baseColor, blendColor);
vec3 resultColor =
noAlphaResult * a1 + baseColor.rgb * a2 + blendColor.rgb * a3;
gl_FragColor = vec4(resultColor / resultAlpha, resultAlpha);
} else {
gl_FragColor = vec4(0);
}
}
Pintura de áreas extensas con la función de cubo
Las apps de pixiv Sketch para iOS y Android ya proporcionaban la función de bucket, pero la versión web no. La versión de la app de la función de bucket se implementó en C++.
Con la base de código ya disponible en C++, pixiv Sketch usó Emscripten y asm.js para implementar la función de bucket en la versión web.
bfsQueue.push(startPoint);
while (!bfsQueue.empty()) {
Point point = bfsQueue.front();
bfsQueue.pop();
/* ... */
bfsQueue.push(anotherPoint);
}
El uso de asm.js permitió obtener una solución de alto rendimiento. Si comparamos el tiempo de ejecución de JavaScript puro con asm.js, el tiempo de ejecución con asm.js se reduce en un 67%. Se espera que esto sea aún mejor cuando se use WASM.
Detalles de la prueba:
- Cómo: Pinta un área de 1180 x 800 px con la función bucket
- Dispositivo de prueba: MacBook Pro (M1 Max)
Tiempo de ejecución:
- JavaScript puro: 213.8 ms
- asm.js: 70.3ms
Con Emscripten y asm.js, pixiv Sketch pudo lanzar correctamente la función de bucket reutilizando la base de código de la versión de la app específica de la plataforma.
Transmitiendo en vivo mientras dibujas
pixiv Sketch ofrece la función de transmitir en vivo mientras dibujas a través de la app web pixiv Sketch LIVE. Para ello, se usa la API de WebRTC, que combina la pista de audio del micrófono obtenida de getUserMedia()
y la pista de video MediaStream
recuperada del elemento <canvas>
.
const canvasElement = document.querySelector('#DrawCanvas');
const framerate = 24;
const canvasStream = canvasElement.captureStream(framerate);
const videoStreamTrack = canvasStream.getVideoTracks()[0];
const audioStream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: {},
});
const audioStreamTrack = audioStream.getAudioTracks()[0];
const stream = new MediaStream();
stream.addTrack(audioStreamTrack.clone());
stream.addTrack(videoStreamTrack.clone());
Conclusiones
Con la potencia de las nuevas APIs como WebGL, WebAssembly y WebRTC, puedes crear una app compleja en la plataforma web y escalarla a cualquier dispositivo. Puedes obtener más información sobre las tecnologías presentadas en este caso de éxito en los siguientes vínculos:
- WebGL
- Consulta también WebGPU, el sucesor de WebGL.
- WebAssembly
- WebRTC
- Artículo original en japonés