pixiv to internetowa społeczność dla ilustratorów i entuzjastów ilustracji, którzy mogą się ze sobą komunikować za pomocą swoich treści. Pozwala ona użytkownikom publikować własne ilustracje. W maju 2023 r. aplikacja miała ponad 84 miliony użytkowników na całym świecie oraz ponad 120 milionów utworzonych prac.
pixiv Sketch to jedna z usług oferowanych przez pixiv. Służy do tworzenia grafik w witrynie za pomocą palców lub rysika. Aplikacja obsługuje wiele funkcji do tworzenia niesamowitych ilustracji, w tym różne rodzaje pędzli, warstw i wypełniania, a także umożliwia prowadzenie transmisji na żywo z procesu tworzenia rysunku.
W tym studium przypadku przyjrzymy się, jak firma Pixiv Sketch poprawiła wydajność i jakość swojej aplikacji internetowej dzięki wykorzystaniu nowych funkcji platformy internetowej, takich jak WebGL, WebAssembly czy WebRTC.
Dlaczego warto tworzyć aplikację do szkicowania w internecie?
pixiv Sketch został po raz pierwszy opublikowany w wersji internetowej i na iOS w 2015 roku. Docelowa grupa odbiorców wersji internetowej to głównie użytkownicy komputerów PC, którzy nadal korzystają z tej platformy, która jest nadal najważniejszą platformą dla społeczności ilustratorów.
Oto 2 główne powody, dla których zespół pixiv zdecydował się opracować wersję internetową zamiast aplikacji na komputer:
- Tworzenie aplikacji na systemy Windows, Mac, Linux i inne jest bardzo kosztowne. Witryna jest dostępna w dowolnej przeglądarce na komputerze.
- Internet ma największy zasięg na wszystkich platformach. Z internetu można korzystać na komputerach, urządzeniach mobilnych i w każdym systemie operacyjnym.
Technologia
pixiv Sketch oferuje użytkownikom wiele różnych pędzli. Przed przyjęciem WebGL dostępny był tylko 1 typ pędzla, ponieważ płótno 2D było zbyt ograniczone, aby odwzorować złożoną fakturę różnych pędzli, np. chropowate krawędzie ołówka oraz zmienną szerokość i intensywność koloru zależną od nacisku podczas rysowania.
Typy pędzli używanych w WebGL
Dzięki zastosowaniu WebGL udało się jednak dodać więcej szczegółów do pędzli i zwiększyć liczbę dostępnych pędzli do 7.
W ramach kontekstu 2D canvas można było rysować tylko linie o prostej fakturze o równomiernie rozłożonej szerokości, jak na tym zrzucie ekranu:
Te linie zostały narysowane przez tworzenie ścieżek i rysowanie pociągnięć, ale WebGL odtwarza je za pomocą punktów sprite’ów i shaderów, jak widać w tych przykładach kodu
Ten przykład pokazuje shader wierzchołkowy.
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;
}
Poniżej znajduje się przykładowy kod modułu do cieniowania fragmentów.
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);
}
Użycie sprite'ów punktowych ułatwia zmianę grubości i cieniowania w zależności od siły nacisku. Pozwala to wyrazić następujące silne i słabe linie:
Ponadto implementacje korzystające ze sprite’ów punktowych mogą teraz dołączać tekstury za pomocą osobnego shadera, co umożliwia wydajne reprezentowanie pędzli z teksturami, takimi jak ołówek i pisak.
Obsługa rysika w przeglądarce
Korzystanie z cyfrowego piórka stało się bardzo popularne w środowisku artystów cyfrowych. Nowoczesne przeglądarki obsługują interfejs PointerEvent API, który umożliwia użytkownikom korzystanie z rysika na urządzeniu. Za pomocą PointerEvent.pressure
możesz mierzyć siłę nacisku pióra, a PointerEvent.tiltX
i PointerEvent.tiltY
– do mierzenia kąta między rysikiem a urządzeniem.
Aby wykonać uderzenie pędzla za pomocą punktu sprite, musisz interpolować PointerEvent
i przekształcić go w bardziej szczegółową sekwencję zdarzeń. W PointerEvent orientację rysika można uzyskać w postaci współrzędnych biegunowych, ale pixiv Sketch przekształca je w wektor reprezentujący orientację rysika przed ich użyciem.
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);
}
Wiele warstw rysunku
Warstwy to jeden z najbardziej unikalnych pojęć w rysowaniu cyfrowym. Umożliwiają one rysowanie różnych elementów ilustracji na sobie nawzajem i edytowanie warstw pojedynczo. pixiv Sketch udostępnia funkcje warstw podobnie jak inne cyfrowe aplikacje do rysowania.
Zazwyczaj warstwy można stosować, używając kilku elementów <canvas>
z operacjami drawImage()
i kompozytowania. Jest to jednak problematyczne, ponieważ w kontekście obrazu 2D nie ma innego wyjścia, jak użyć trybu kompozycji CanvasRenderingContext2D.globalCompositeOperation
, który jest wstępnie zdefiniowany i w dużej mierze ogranicza skalowalność. Dzięki użyciu WebGL i pisania shadera pozwala deweloperom używać trybów kompozycji, które nie są wstępnie zdefiniowane przez interfejs API. W przyszłości pixiv Sketch wprowadzi funkcję warstw za pomocą WebGL, aby zapewnić większą skalowalność i elastyczność.
Oto przykładowy kod tworzenia warstwy:
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);
}
}
Malowanie dużych obszarów za pomocą funkcji wiadra
Aplikacje Pixiv Sketch na iOS i Androida już miały tę funkcję, ale w wersji internetowej nie. Wersja aplikacji funkcji zasobnika została wdrożona w C++.
Bazując na kodzie źródłowym w języku C++, zespół pixiv Sketch wykorzystał Emscripten i asm.js do zaimplementowania funkcji zbiornika w wersji internetowej.
bfsQueue.push(startPoint);
while (!bfsQueue.empty()) {
Point point = bfsQueue.front();
bfsQueue.pop();
/* ... */
bfsQueue.push(anotherPoint);
}
Użycie pliku asm.js umożliwiło wydajniejsze rozwiązanie. Porównując czas wykonania czystego JavaScriptu z asm.js, można zauważyć, że czas wykonania kodu asm.js jest krótszy o 67%. Zakładamy, że będzie to jeszcze lepsze w przypadku korzystania z WASM.
Szczegóły testu:
- Jak: maluj obszar o wymiarach 1180 x 800 pikseli za pomocą funkcji zasobnika
- Urządzenie testowe: MacBook Pro (M1 Max)
Czas wykonania:
- Czysty JavaScript: 213,8 ms
- asm.js: 70,3 ms
Dzięki Emscriptenowi i asm.js zespół pixiv Sketch mógł opublikować funkcję zasobnika, używając bazy kodu z wersji aplikacji na daną platformę.
Transmisja na żywo podczas rysowania
Pixiv Sketch umożliwia transmitowanie na żywo podczas rysowania za pomocą aplikacji internetowej pixiv Sketch LIVE. Korzysta on z interfejsu API WebRTC, łącząc ścieżkę dźwiękową z mikrofonu uzyskaną z getUserMedia()
i ścieżkę wideo MediaStream
pobraną z elementu <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());
Podsumowanie
Dzięki nowym interfejsom API, takim jak WebGL, WebAssembly i WebRTC, możesz tworzyć złożone aplikacje na platformie internetowej i skalować je na dowolne urządzenie. Więcej informacji o technologiach opisanych w tym przypadku znajdziesz pod tymi linkami:
- WebGL
- Zapoznaj się też z WebGPU, następcą WebGL.
- WebAssembly
- WebRTC
- Oryginalny artykuł w języku japońskim