Kecanggihan web untuk ilustrator: Cara pixiv menggunakan teknologi web untuk aplikasi menggambar mereka

pixiv adalah layanan komunitas online bagi ilustrator dan penggemar ilustrasi untuk berkomunikasi satu sama lain melalui konten mereka. Fitur ini memungkinkan pengguna memposting ilustrasi mereka sendiri. Mereka memiliki lebih dari 84 juta pengguna di seluruh dunia, dan lebih dari 120 juta karya seni yang diposting hingga Mei 2023.

pixiv Sketch adalah salah satu layanan yang disediakan oleh pixiv. Digunakan untuk menggambar karya seni di situs, menggunakan jari atau stylus. Aplikasi ini mendukung berbagai fitur untuk menggambar ilustrasi yang menakjubkan, termasuk berbagai jenis kuas, lapisan, dan lukisan bucket, serta memungkinkan orang melakukan livestream proses menggambar mereka.

Dalam studi kasus ini, kita akan melihat bagaimana pixiv Sketch meningkatkan performa dan kualitas aplikasi web mereka dengan menggunakan beberapa fitur platform web baru seperti WebGL, WebAssembly, dan WebRTC.

Mengapa mengembangkan aplikasi sketsa di web?

pixiv Sketch pertama kali dirilis di web dan di iOS pada tahun 2015. Target audiens untuk versi web mereka terutama adalah desktop, yang masih menjadi platform utama yang digunakan oleh komunitas ilustrasi.

Berikut dua alasan utama pixiv memilih mengembangkan versi web daripada aplikasi desktop:

  • Membuat aplikasi untuk Windows, Mac, Linux, dan lainnya sangat mahal. Web dapat diakses di browser mana pun di desktop.
  • Web memiliki jangkauan terbaik di seluruh platform. Web tersedia di desktop dan perangkat seluler, serta di setiap sistem operasi.

Teknologi

pixiv Sketch memiliki sejumlah kuas berbeda yang dapat dipilih pengguna. Sebelum mengadopsi WebGL, hanya ada satu jenis kuas karena kanvas 2D terlalu terbatas untuk menggambarkan tekstur kompleks dari berbagai kuas, seperti tepi kasar pensil dan perbedaan lebar serta intensitas warna yang berubah pada tekanan sketsa.

Jenis kuas kreatif menggunakan WebGL

Namun, dengan penerapan WebGL, mereka dapat menambahkan lebih banyak variasi dalam detail kuas dan meningkatkan jumlah kuas yang tersedia menjadi tujuh.

Tujuh kuas berbeda di pixiv, mulai dari halus hingga kasar, tajam hingga tidak tajam, berpiksel hingga halus, dll.

Dengan menggunakan konteks kanvas 2D, hanya garis yang memiliki tekstur sederhana dengan lebar yang didistribusikan secara merata yang dapat digambar, seperti screenshot berikut:

Goresan kuas dengan tekstur sederhana.

Garis ini digambar dengan membuat jalur dan menggambar goresan, tetapi WebGL mereproduksinya menggunakan sprite titik dan shader, yang ditunjukkan dalam contoh kode berikut

Contoh berikut menunjukkan shader vertex.

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;
}

Contoh berikut menunjukkan contoh kode untuk shader fragmen.

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);
}

Penggunaan sprite titik memudahkan untuk memvariasikan ketebalan dan shading sebagai respons terhadap tekanan gambar, sehingga memungkinkan garis tebal dan tipis berikut ditampilkan, seperti ini:

Sapuan kuas yang tajam dan merata dengan ujung tipis.

Goresan kuas yang tidak tajam dengan tekanan yang lebih besar di bagian tengah.

Selain itu, penerapan yang menggunakan sprite titik kini dapat melampirkan tekstur dengan menggunakan shader terpisah, sehingga memungkinkan representasi kuas yang efisien dengan tekstur seperti pensil dan pena ujung runcing.

Dukungan stilus di browser

Penggunaan stylus digital telah menjadi sangat populer di kalangan seniman digital. Browser modern mendukung PointerEvent API yang memungkinkan pengguna menggunakan stilus di perangkat mereka: Gunakan PointerEvent.pressure untuk mengukur tekanan pena, dan gunakan PointerEvent.tiltX, PointerEvent.tiltY untuk mengukur sudut pena terhadap perangkat.

Untuk melakukan sapuan kuas dengan sprite titik, PointerEvent harus diinterpolasi dan dikonversi menjadi urutan peristiwa yang lebih terperinci. Di PointerEvent, orientasi stylus dapat diperoleh dalam bentuk koordinat polar, tetapi pixiv Sketch mengonversinya menjadi vektor yang merepresentasikan orientasi stylus sebelum menggunakannya.

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);
}

Beberapa lapisan gambar

Lapisan adalah salah satu konsep paling unik dalam gambar digital. Fitur ini memungkinkan pengguna menggambar berbagai bagian ilustrasi di atas satu sama lain, dan mengedit lapisan demi lapisan. pixiv Sketch menyediakan fungsi lapisan seperti aplikasi menggambar digital lainnya.

Secara konvensional, lapisan dapat diimplementasikan menggunakan beberapa elemen <canvas> dengan drawImage() dan operasi komposit. Namun, hal ini bermasalah karena dengan konteks kanvas 2D, tidak ada pilihan lain selain menggunakan mode komposisi CanvasRenderingContext2D.globalCompositeOperation, yang telah ditentukan sebelumnya dan sangat membatasi skalabilitas. Dengan menggunakan WebGL dan menulis shader, developer dapat menggunakan mode komposisi yang tidak ditentukan sebelumnya oleh API. Pada masa mendatang, pixiv Sketch akan menerapkan fitur lapisan menggunakan WebGL untuk skalabilitas dan fleksibilitas yang lebih baik.

Berikut adalah contoh kode untuk komposisi lapisan:

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);
  }
}

Mewarnai area besar dengan fungsi ember

Aplikasi iOS dan Android pixiv Sketch sudah menyediakan fitur bucket, tetapi versi web belum. Versi aplikasi fungsi bucket diimplementasikan di C++.

Dengan codebase yang sudah tersedia di C++, pixiv Sketch menggunakan Emscripten dan asm.js untuk menerapkan fungsi bucket ke versi web.

bfsQueue.push(startPoint);

while (!bfsQueue.empty()) {
  Point point = bfsQueue.front();
  bfsQueue.pop();
  /* ... */
  bfsQueue.push(anotherPoint);
}

Penggunaan asm.js memungkinkan solusi berperforma tinggi. Dengan membandingkan waktu eksekusi JavaScript murni dengan asm.js, waktu eksekusi menggunakan asm.js dipersingkat sebesar 67%. Performa ini diperkirakan akan lebih baik lagi saat menggunakan WASM.

Detail pengujian:

  • Cara: Warnai area 1180x800 px dengan fungsi bucket
  • Perangkat pengujian: MacBook Pro (M1 Max)

Waktu eksekusi:

  • JavaScript Murni: 213,8 md
  • asm.js: 70,3 md

Dengan menggunakan Emscripten dan asm.js, pixiv Sketch berhasil merilis fitur bucket dengan menggunakan kembali codebase dari versi aplikasi khusus platform.

Live streaming sambil menggambar

pixiv Sketch menawarkan fitur untuk melakukan live stream saat menggambar, melalui aplikasi web pixiv Sketch LIVE. Fitur ini menggunakan WebRTC API, menggabungkan trek audio mikrofon yang diperoleh dari getUserMedia() dan trek video MediaStream yang diambil dari elemen <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());

Kesimpulan

Dengan kecanggihan API baru seperti WebGL, WebAssembly, dan WebRTC, Anda dapat membuat aplikasi kompleks di platform web dan menskalakannya di perangkat apa pun. Anda dapat mempelajari lebih lanjut teknologi yang diperkenalkan dalam studi kasus ini di link berikut: