pixiv è un servizio di community online per illustratori e appassionati di illustrazione che vogliono comunicare tra loro tramite i contenuti. Consente alle persone di pubblicare le proprie illustrazioni. Hanno più di 84 milioni di utenti in tutto il mondo e più di 120 milioni di opere d'arte pubblicate a partire da maggio 2023.
pixiv Sketch è uno dei servizi forniti da pixiv. Viene utilizzato per disegnare artwork sul sito web, utilizzando le dita o gli stili. Supporta una serie di funzionalità per disegnare illustrazioni straordinarie, tra cui numerosi tipi di pennelli, livelli e pittura con secchiello, e consente inoltre alle persone di trasmettere in live streaming il processo di disegno.
In questo caso studio, vedremo in che modo pixiv Sketch ha migliorato le prestazioni e la qualità della propria app web utilizzando alcune nuove funzionalità della piattaforma web come WebGL, WebAssembly e WebRTC.
Perché sviluppare un'app di schizzi sul web?
pixiv Sketch è stato rilasciato per la prima volta sul web e su iOS nel 2015. Il pubblico di destinazione della versione web era principalmente costituito da utenti di computer, che rappresentano ancora la piattaforma più utilizzata dalla community di illustratori.
Ecco i due motivi principali per cui pixiv ha scelto di sviluppare una versione web anziché un'app desktop:
- Creare app per Windows, Mac, Linux e altri sistemi operativi è molto costoso. Il web raggiunge qualsiasi browser sul computer.
- Il web ha la migliore copertura su tutte le piattaforme. Il web è disponibile su computer e dispositivi mobili e su ogni sistema operativo.
Tecnologia
Pixiv Sketch offre una serie di diversi pennelli tra cui gli utenti possono scegliere. Prima di adottare WebGL, esisteva un solo tipo di pennello poiché la tela 2D era troppo limitata per rappresentare la trama complessa di diversi pennelli, come i bordi ruvidi di una matita e la larghezza e l'intensità del colore diverse che cambiano in base alla pressione dello schizzo.
Tipi di creatività di pennelli usando WebGL
Tuttavia, con l'adozione di WebGL, è stato possibile aggiungere più varietà ai dettagli dei pennelli e aumentare il numero di pennelli disponibili a sette.
Utilizzando il contesto della tela 2D, era possibile disegnare solo linee con una trama semplice con larghezza uniformemente distribuita, come nello screenshot seguente:
Queste linee sono state disegnate creando percorsi e tratti disegnando, ma WebGL lo riproduce utilizzando sprite di punti e Shar, come mostrato nei seguenti esempi di codice
L'esempio seguente mostra un vertex shader.
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;
}
L'esempio seguente mostra un codice di esempio per un frammento shader.
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);
}
L'uso degli sprite a punti consente di variare facilmente lo spessore e l'ombreggiatura in risposta alla pressione del tratto, consentendo di esprimere le seguenti linee forti e deboli, ad esempio:
Inoltre, le implementazioni che utilizzano sprite a punti ora possono allegare texture utilizzando un shader separato, consentendo una rappresentazione efficiente dei pennelli con texture come matite e pennarelli.
Supporto per stilo sul browser
L'utilizzo di uno stilo digitale è diventato estremamente popolare tra gli artisti digitali. I browser moderni supportano l'API PointerEvent che consente agli utenti di utilizzare uno stilo sul proprio dispositivo: utilizza PointerEvent.pressure
per misurare la pressione della penna e PointerEvent.tiltX
, PointerEvent.tiltY
per misurare l'angolo della penna rispetto al dispositivo.
Per eseguire tratti con un punto sprite, PointerEvent
deve essere interpolato e convertito in una sequenza di eventi più granulare. In
PointerEvent, l'orientamento del pennino può essere ottenuto sotto forma di coordinate polari, ma Pixiv Sketch lo converte in un vettore che rappresenta l'orientamento del pennino prima di utilizzarlo.
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);
}
Più livelli di disegno
I livelli sono uno dei concetti più unici nel disegno digitale. Consentono agli utenti di sovrapporre diversi elementi di illustrazione e di apportare modifiche livello per livello. pixiv Sketch offre funzioni di livello simili a quelle di altre app di disegno digitale.
Tradizionalmente, è possibile implementare i livelli utilizzando diversi elementi <canvas>
con operazioni di drawImage()
e composizione. Tuttavia, questo è problematico perché, con il contesto della tela 2D, non c'è altra scelta che utilizzare la modalità di composizione CanvasRenderingContext2D.globalCompositeOperation
, che è predefinita e limita notevolmente la scalabilità. L'utilizzo di WebGL e la scrittura dello shader consentono agli sviluppatori di utilizzare modalità di composizione non predefinite dall'API. In futuro, pixiv Sketch implementerà
la funzionalità dei livelli tramite WebGL per ottenere maggiore scalabilità e flessibilità.
Di seguito è riportato il codice di esempio per la composizione dei livelli:
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);
}
}
Pittura di aree di grandi dimensioni con la funzione secchio
Le app pixiv Sketch per iOS e Android fornivano già la funzionalità del bucket, ma la versione web non lo era. La versione dell'app della funzione del bucket è stata implementata in C++.
Con la base di codice già disponibile in C++, pixiv Sketch ha utilizzato Emscripten e asm.js per implementare la funzione del bucket nella versione web.
bfsQueue.push(startPoint);
while (!bfsQueue.empty()) {
Point point = bfsQueue.front();
bfsQueue.pop();
/* ... */
bfsQueue.push(anotherPoint);
}
L'utilizzo di asm.js ha consentito una soluzione con prestazioni elevate. Se confrontiamo il tempo di esecuzione di JavaScript puro con asm.js, il tempo di esecuzione utilizzando asm.js viene ridotto del 67%. Questo dovrebbe essere ancora migliore quando si utilizza WASM.
Dettagli del test:
- Come: colorare un'area 1180x800 px con la funzione bucket
- Dispositivo di test: MacBook Pro (M1 Max)
Tempo di esecuzione:
- JavaScript puro: 213,8 ms
- asm.js::70,3 ms
Utilizzando Emscripten e asm.js, pixiv Sketch è riuscita a rilasciare la funzionalità del bucket riutilizzando la base di codice della versione dell'app specifica per la piattaforma.
Live streaming mentre si disegna
pixiv Sketch offre la funzionalità di live streaming mentre disegni, tramite l'app web Pixiv Sketch LIVE. Questa utilizza l'API WebRTC, combinando la traccia audio del microfono ottenuta da getUserMedia()
e la traccia video MediaStream
recuperata dall'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());
Conclusioni
Grazie alla potenza di nuove API come WebGL, WebAssembly e WebRTC, puoi creare un'app complessa sulla piattaforma web e scalarla su qualsiasi dispositivo. Per saperne di più sulle tecnologie introdotte in questo caso di studio, visita i seguenti link:
- WebGL
- Dai un'occhiata anche a WebGPU, il successore di WebGL
- WebAssembly
- WebRTC
- Articolo originale in giapponese