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. Aplikasi ini memiliki lebih dari 84 juta pengguna di seluruh dunia, dan lebih dari 120 juta karya seni yang diposting mulai Mei 2023.

pixiv Sketch adalah salah satu layanan yang disediakan oleh pixiv. Alat ini digunakan untuk menggambar karya seni di situs, menggunakan jari atau stilus. Aplikasi ini mendukung berbagai fitur untuk menggambar ilustrasi yang luar biasa, termasuk berbagai jenis kuas, lapisan, dan lukisan bucket, serta memungkinkan pengguna melakukan live stream 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 perlu mengembangkan aplikasi sketsa di web?

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

Berikut adalah dua alasan utama Pixiv dalam memilih mengembangkan versi web daripada aplikasi desktop:

  • Membuat aplikasi untuk Windows, Mac, Linux, dan lainnya memerlukan biaya yang sangat mahal. Web menjangkau browser apa pun di desktop.
  • Web memiliki jangkauan terbaik di berbagai platform. Web tersedia di desktop dan seluler, serta di setiap sistem operasi.

Teknologi

pixiv Sketch memiliki sejumlah kuas yang dapat dipilih pengguna. Sebelum menggunakan 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 materi iklan yang menggunakan WebGL

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

Tujuh jenis kuas yang berbeda dalam piksel bervariasi, mulai dari halus hingga kasar, tajam hingga tidak tajam, hingga halus hingga halus.

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

Goresan kuas dengan tekstur sederhana.

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

Contoh berikut menunjukkan shader verteks.

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 kode contoh 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 bayangan sebagai respons terhadap tekanan gambar, sehingga garis kuat dan lemah berikut dapat diekspresikan, seperti ini:

Goresan kuas yang tajam dan merata dengan ujung tipis.

Goresan kuas yang tidak tajam dengan lebih banyak tekanan yang diterapkan di tengah.

Selain itu, implementasi yang menggunakan point sprite kini dapat melampirkan tekstur menggunakan shader terpisah, memungkinkan representasi kuas yang efisien dengan tekstur seperti pensil dan pena spidol.

Dukungan stilus di browser

Penggunaan stilus digital sangat populer di kalangan artis 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 ke perangkat.

Untuk melakukan goresan kuas dengan sprite titik, PointerEvent harus diinterpolasi dan dikonversi menjadi urutan peristiwa yang lebih mendetail. Pada PointerEvent, orientasi stilus dapat diperoleh dalam bentuk koordinat polar, tetapi pixiv Sketch mengonversinya menjadi vektor yang merepresentasikan orientasi stilus 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

{i>Layer<i} adalah salah satu konsep yang paling unik dalam gambar digital. Fungsi ini memungkinkan pengguna menggambar berbagai potongan ilustrasi di atas satu sama lain, dan memungkinkan pengeditan lapisan demi lapisan. pixiv Sketch menyediakan fungsi lapisan seperti halnya aplikasi gambar digital lainnya.

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

Berikut adalah kode contoh 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);
  }
}

Menggambar area besar dengan fungsi ember

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

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

bfsQueue.push(startPoint);

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

Penggunaan asm.js memungkinkan solusi yang berperforma tinggi. Jika dibandingkan dengan waktu eksekusi JavaScript murni versus asm.js, waktu eksekusi menggunakan asm.js dipersingkat sebesar 67%. Hal ini diharapkan akan lebih baik saat menggunakan WASM.

Detail pengujian:

  • Cara: Melukis area 1180x800 piksel 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. Aplikasi ini menggunakan WebRTC API, yang 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 yang kompleks di platform web dan menskalakannya di perangkat apa pun. Anda dapat mempelajari teknologi yang diperkenalkan dalam studi kasus lebih lanjut di link berikut: