pixiv 是線上社群服務,插畫家和插畫愛好者可透過內容相互交流。使用者可以自行發布插畫。截至 2023 年 5 月,全球使用者超過 8, 400 萬人,發布的藝術作品超過 1.2 億件。
pixiv Sketch 是 pixiv 提供的服務之一。使用者可透過手指或觸控筆,在網站上繪製藝術作品。支援多種繪製精美插畫的功能,包括多種筆刷、圖層和填色,還可讓使用者直播繪圖過程。
在本案例研究中,我們將瞭解 pixiv Sketch 如何運用 WebGL、WebAssembly 和 WebRTC 等新版網頁平台功能,提升網頁應用程式的效能和品質。
為什麼要在網路上開發繪圖應用程式?
pixiv Sketch 於 2015 年首次在網頁和 iOS 平台推出,網頁版的目標對象主要是桌機使用者,而桌機仍是插畫社群最主要使用的平台。
pixiv 選擇開發網頁版而非電腦應用程式的主要原因有以下兩點:
- 為 Windows、Mac、Linux 等系統建立應用程式的成本非常高。網頁可透過電腦上的任何瀏覽器存取。
- 網頁的觸及範圍最廣,網頁版適用於電腦和行動裝置,以及所有作業系統。
科技
pixiv Sketch 提供多種筆刷供使用者選擇。採用 WebGL 前,由於 2D 畫布的限制,無法描繪不同筆刷的複雜紋理,例如鉛筆的粗糙邊緣,以及會因草圖壓力而改變的寬度和顏色強度,因此只有一種筆刷。
使用 WebGL 的筆刷廣告素材類型
不過,採用 WebGL 後,他們就能在筆刷細節中加入更多變化,並將可用筆刷數量增加到七種。

使用 2D 畫布內容時,只能繪製具有簡單紋理且寬度均勻分布的線條,如下列螢幕截圖所示:

這些線條是透過建立路徑和繪製筆觸所繪製,但 WebGL 會使用點精靈和著色器重現此效果,如下列程式碼範例所示
以下範例示範了頂點著色器。
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;
}
下列範例顯示片段著色器的範例程式碼。
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);
}
使用點精靈可輕鬆根據繪圖壓力調整粗細和陰影,因此可以繪製出粗細不同的線條,例如:


此外,使用點精靈的實作現在可透過獨立著色器附加紋理,有效呈現具有紋理的筆刷,例如鉛筆和麥克筆。
瀏覽器支援觸控筆
數位手寫筆已成為數位藝術家非常愛用的工具。新式瀏覽器支援 PointerEvent API,可讓使用者在裝置上使用觸控筆:使用 PointerEvent.pressure 測量筆壓,並使用 PointerEvent.tiltX、PointerEvent.tiltY 測量筆與裝置的角度。
如要使用點精靈執行筆刷筆觸,必須插補 PointerEvent,並轉換為更精細的事件序列。在 PointerEvent 中,觸控筆的方向可以極座標的形式取得,但 pixiv Sketch 會先將其轉換為代表觸控筆方向的向量,再加以使用。
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);
}
多個繪圖圖層
圖層是數位繪圖中最獨特的概念之一。使用者可以疊加繪製插畫,並逐層編輯。pixiv Sketch 提供的圖層功能與其他數位繪圖應用程式類似。
按照慣例,您可以使用多個 <canvas> 元素搭配 drawImage() 和合成作業,實作圖層。不過,這會造成問題,因為使用 2D 畫布環境時,只能使用 CanvasRenderingContext2D.globalCompositeOperation 合成模式,而這種模式是預先定義的,且大幅限制了可擴充性。開發人員可以透過 WebGL 編寫著色器,使用 API 未預先定義的組合模式。pixiv Sketch 日後會使用 WebGL 實作圖層功能,以提升擴充性和彈性。
以下是圖層組合的程式碼範例:
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);
}
}
使用「油漆桶」功能繪製大面積區域
pixiv Sketch iOS 和 Android 應用程式已提供儲存桶功能,但網頁版沒有。值區函式的應用程式版本是以 C++ 實作。
pixiv Sketch 的程式碼集已採用 C++,因此使用 Emscripten 和 asm.js,將 bucket 函式實作到網頁版中。
bfsQueue.push(startPoint);
while (!bfsQueue.empty()) {
Point point = bfsQueue.front();
bfsQueue.pop();
/* ... */
bfsQueue.push(anotherPoint);
}
使用 asm.js 即可啟用高效能解決方案。比較純 JavaScript 與 asm.js 的執行時間,使用 asm.js 的執行時間縮短了 67%。使用 WASM 時,預期效能會更佳。
測試詳細資料:
- 做法:使用油漆桶函式繪製 1180x800 像素的區域
- 測試裝置:MacBook Pro (M1 Max)
執行時間:
- 純 JavaScript:213.8 毫秒
- asm.js:70.3 毫秒
pixiv Sketch 運用 Emscripten 和 asm.js,重複使用特定平台應用程式版本的程式碼,成功發布了 bucket 功能。
在繪圖時直播
pixiv Sketch 提供繪圖直播功能,使用者可透過 pixiv Sketch LIVE 網頁應用程式進行直播。這項功能會使用 WebRTC API,結合從 getUserMedia() 取得的麥克風音訊軌,以及從 <canvas> 元素擷取的 MediaStream 視訊軌。
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());
結論
有了 WebGL、WebAssembly 和 WebRTC 等新 API,您可以在網頁平台上建立複雜的應用程式,並在任何裝置上擴展應用程式。如要進一步瞭解本個案研究中介紹的技術,請參閱下列連結:
- WebGL
- 也請瞭解 WebGL 的後繼技術 WebGPU
- WebAssembly
- WebRTC
- 日文原文