pixiv è un servizio di community online che consente a illustratori e appassionati di illustrazioni di comunicare tra loro tramite i loro contenuti. Consente agli utenti di pubblicare le proprie illustrazioni. Ha oltre 84 milioni di utenti in tutto il mondo e più di 120 milioni di opere d'arte pubblicate a maggio 2023.
pixiv Sketch è uno dei servizi forniti da pixiv. Viene utilizzato per disegnare opere d'arte 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 a secchiello, e consente anche di trasmettere in live streaming il processo di disegno.
In questo case study, esamineremo 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 disegno sul web?
pixiv Sketch è stato rilasciato per la prima volta sul web e su iOS nel 2015. Il pubblico di destinazione per la versione web era principalmente il desktop, che è ancora la piattaforma più importante utilizzata dalla community di illustratori.
Ecco i due motivi principali per cui pixiv ha scelto di sviluppare una versione web anziché un'app per computer:
- La creazione di app per Windows, Mac, Linux e altri sistemi operativi è molto costosa. 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 tutti i sistemi operativi.
Tecnologia
pixiv Sketch offre agli utenti una serie di pennelli diversi tra cui scegliere. Prima dell'adozione di WebGL, esisteva un solo tipo di pennello perché il canvas 2D era troppo limitato per rappresentare la complessa texture di pennelli diversi, come i bordi ruvidi di una matita e l'intensità di larghezza e colore che cambia in base alla pressione dello schizzo.
Tipi di pennelli creativi che utilizzano WebGL
Tuttavia, con l'adozione di WebGL, è stato possibile aggiungere più varietà nei dettagli del pennello e aumentare il numero di pennelli disponibili a sette.

Utilizzando il contesto del canvas 2D, era possibile disegnare solo linee con una semplice texture con larghezza distribuita uniformemente, come nello screenshot seguente:

Queste linee sono state disegnate creando tracciati e disegnando tratti, ma WebGL le riproduce utilizzando sprite di punti e shader, come mostrato negli esempi di codice seguenti.
L'esempio seguente mostra uno shader dei vertici.
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;
}
Il seguente esempio mostra un codice campione per uno shader di framenti.
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'utilizzo di point sprite consente di variare facilmente lo spessore e l'ombreggiatura in risposta alla pressione del disegno, consentendo di esprimere le seguenti linee forti e deboli, come queste:


Inoltre, le implementazioni che utilizzano gli sprite punto ora possono allegare texture utilizzando uno shader separato, consentendo una rappresentazione efficiente dei pennelli con texture come matita e pennarello.
Supporto dello 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 utilizza PointerEvent.tiltX, PointerEvent.tiltY per
misurare l'angolo della penna rispetto al dispositivo.
Per eseguire tratti di pennello con uno sprite punto, il PointerEvent deve
essere interpolato e convertito in una sequenza di eventi più granulare. In
PointerEvent, l'orientamento dello stilo può essere ottenuto sotto forma di coordinate
polari, ma pixiv Sketch le converte in un vettore che rappresenta l'orientamento
dello stilo prima di utilizzarle.
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 del disegno digitale. Consentono agli utenti di disegnare diversi elementi di un'illustrazione uno sopra l'altro e di apportare modifiche livello per livello. pixiv Sketch fornisce funzioni di livello simili a quelle di altre app di disegno digitale.
Convenzionalmente, è possibile implementare i livelli utilizzando diversi elementi <canvas>
con drawImage() e operazioni di composizione. Tuttavia, questo è
problematico perché con il contesto del canvas 2D, non c'è altra scelta se non quella di utilizzare
la modalità di composizione CanvasRenderingContext2D.globalCompositeOperation, che è predefinita e limita in gran parte la scalabilità. Utilizzando
WebGL e scrivendo lo shader, gli sviluppatori possono utilizzare modalità di composizione
non predefinite dall'API. In futuro, pixiv Sketch implementerà la funzionalità dei livelli utilizzando WebGL per una maggiore scalabilità e flessibilità.
Ecco il codice campione 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);
}
}
Dipingere un'ampia area con la funzione Secchiello
Le app per iOS e Android di pixiv Sketch fornivano già la funzionalità di bucket, ma la versione web no. La versione dell'app della funzione bucket è stata implementata in C++.
Con il codebase già disponibile in C++, pixiv Sketch ha utilizzato Emscripten e asm.js per implementare la funzione secchiello 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 di creare una soluzione efficiente. Se si confronta il tempo di esecuzione di JavaScript puro con quello di asm.js, il tempo di esecuzione utilizzando asm.js si riduce del 67%. Si prevede che sarà ancora migliore quando si utilizza WASM.
Dettagli del test:
- Come: colora l'area 1180x800 px con la funzione Secchiello
- 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 è riuscito a rilasciare correttamente la funzionalità di bucket riutilizzando il codebase della versione dell'app specifica per la piattaforma.
Live streaming mentre disegni
pixiv Sketch offre la funzionalità di live streaming durante il disegno 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 ridimensionarla su qualsiasi dispositivo. Per saperne di più sulle tecnologie introdotte in questo case study, visita i seguenti link:
- WebGL
- Dai un'occhiata anche a WebGPU, il successore di WebGL.
- WebAssembly
- WebRTC
- Articolo originale in giapponese