- Membuat animasi Rick hanya dengan 240 baris kode GLSL, tanpa gambar atau library; shader GPU menghitung warna tiap piksel seiring waktu
- Intinya adalah
color_for_pixel(pixel, time) dijalankan untuk semua piksel, dan signed distance field (SDF) merepresentasikan bagian dalam/luar serta kontur bentuk sebagai nilai jarak
- Wajah dibangun dengan menggabungkan bentuk SDF seperti
round_rect(), circle(), star(), bezier(), parabola() melalui translasi, skala, mirroring, pengulangan, dan union
- Gelombang rambut, pengulangan gigi, kontur bibir, kedipan, gerak pupil, dan goyangan kepala ditambahkan bertahap dengan domain warping,
mod(), sin(time), dan noise()
- Versi akhir mencakup latar portal, tetapi editor tidak memiliki ekspor video sehingga memerlukan workflow macOS terpisah memakai
glslviewer dan ffmpeg
Struktur shader yang menghitung warna per piksel
- Animasi ditulis dengan OpenGL Shading Language(GLSL)
- Contoh dapat langsung dijalankan dan dimodifikasi di editor live coding pada halaman tersebut
- Entry point dasarnya adalah fungsi
color_for_pixel(vec2 pixel, float time)
- GPU menjalankan fungsi ini untuk setiap piksel pada pratinjau
- Pertanyaan yang dijawab fungsi ini adalah “warna apa yang seharusnya dimiliki piksel ini pada waktu ini”
- Contoh sederhana memasukkan nilai
pixel.x, pixel.y, dan time ke kanal warna untuk memvisualisasikan posisi piksel dan perubahan waktu
time adalah detik yang berlalu sejak penyuntingan terakhir dan terus bertambah
Cara menggambar bentuk dengan SDF
- Lingkaran dapat digambar dengan menghitung jarak dari titik asal ke piksel menggunakan
length(pixel), lalu membandingkannya dengan jari-jari
- Fungsi
circle() tidak sekadar mengembalikan bool yang menunjukkan dalam/luar, tetapi mengembalikan jarak ke keliling
- Di dalam bentuk bernilai negatif
- Di luar bentuk bernilai positif
- Batasnya bernilai mendekati 0
- Fungsi seperti ini disebut fungsi signed distance field (SDF)
- Dengan memakai nilai jarak, isian dan garis kontur dapat ditangani dengan cara yang sama
- Jika
dist < -0.01, warna bagian dalam
- Jika
dist < 0.0, kontur hitam
- Beberapa bentuk SDF dapat digabungkan dengan
min()
- Jika kepala dan telinga Rick diberi kontur secara terpisah, akan muncul garis yang tidak perlu di antara keduanya
- Dengan menggabungkan dua nilai jarak memakai
min(), hanya tersisa kontur union kepala dan telinga
- Ada juga metode penggabungan lain; sebagai referensi ditautkan materi Inigo Quilez tentang 2D distance functions dan kombinasi primitive
Bentuk-bentuk penyusun wajah Rick
- Bentuk kepala dibuat dengan
round_rect(), dan telinga juga ditambahkan sebagai round_rect() terpisah
- Gambar Rick dari poster musim 1 ditumpangkan secara berkedip di atas pratinjau untuk mencocokkan angka-angka
- Banyak angka merupakan nilai yang ditemukan lewat proses coba-coba ini
- Nilai warna ditemukan dengan tool eyedropper di editor gambar
- Mata tersusun dari SDF lingkaran dan
star() 6 titik
- Bola mata sedikit dipanjangkan ke arah vertikal dengan transformasi koordinat seperti
pixel.y *= .93
- Pupil dibuat dari SDF bintang 6 titik dengan mengurangi nilai jarak kecil agar sudutnya membulat
- Dua mata dibuat tanpa menduplikasi kode, dengan mirroring kiri-kanan memakai
pixel.x = abs(pixel.x)
- Hidung, mulut, dan alis digambar dengan
bezier()
- Rambut dimulai dari
star() 11 titik yang dipanjangkan secara vertikal
- Urutan kode yang menggambar bentuk juga memengaruhi hasil
- Warna yang dikembalikan lebih dulu menjadi bentuk yang terlihat di depan
- Gigi dan lidah hanya digambar di dalam conditional bentuk mulut, sehingga tidak keluar dari mulut
Teknik menyempurnakan rambut, gigi, dan garis dekoratif
- Rambut berbentuk bintang yang kaku diubah menjadi seperti gelombang dengan domain warping
- Domain warping adalah cara menggoyang koordinat dengan offset acak berbasis posisi piksel
- Pada posisi yang sama diterapkan offset yang sama, sehingga dapat menghasilkan distorsi yang konsisten seiring waktu
- Sebagai materi terkait, ditautkan domain warping dari Inigo Quilez
- Gigi dibuat satu buah dengan SDF
parabola(), lalu diulang secara horizontal memakai mod()
mod(pixel.x, width) membuat koordinat x kembali mulai dari 0 pada setiap interval tertentu, sehingga bentuk yang sama berulang
pixel.y = abs(pixel.y)-.06 melakukan mirroring gigi atas dan bawah
- Offset y berbasis
pow(pixel.x, 2.) menyesuaikan gigi dengan kurva senyum
- Kondisi seperti
abs(pixel.x+.06) < .194 membatasi pengulangan tak hingga
- Bibir dan garis bawah mata digambar dengan mendorong garis kontur SDF ke arah luar
- Kontur biasa adalah
abs(distance_to_shape) < thickness
- Kontur yang digeser ke luar adalah
abs(distance_to_shape - outset) < thickness
- Garis bawah mata ditambah kondisi posisi agar hanya terlihat pada area tertentu di bawah mata
Cara membuat gerakan dengan waktu
- Animasi paling sederhana dilakukan dengan memasukkan
sin(time) ke kode
sin() membungkus nilai time yang terus bertambah ke rentang -1 hingga 1 untuk membuat animasi berulang
- Rentangnya disesuaikan dengan skala dan offset seperti
sin(time)*.5 + .5
- Rotasi kepala Rick, rotasi lidah, dan tinggi alis bergerak dengan cara ini
- Fungsi
rotateAt() ditambahkan untuk perhitungan rotasi
- Kedipan diimplementasikan dengan mengganti objek yang digambar berdasarkan waktu
- Jika
mod(time, 2.) < .09, gambar mata tertutup
- Jika tidak, gambar mata terbuka dan pupil
noise() digunakan untuk gerakan pupil yang tidak beraturan
- Agar mata tidak terus bergerak halus,
round(time) diterapkan lalu diteruskan ke noise()
- Rambut bergerak lebih lentur dengan time domain warping
- Alih-alih mendistorsi ruang, nilai waktu ditunda berdasarkan posisi
- Semakin dekat ke ujung rambut, penundaan waktu berbeda-beda, sehingga keseluruhan rambut tidak berputar kaku melainkan melengkung
Latar portal dan penyelesaian
- Versi final diselesaikan dengan menambahkan efek portal di belakang Rick
- Efek portal didasarkan pada efek yang dibuat pengguna ShaderToy valena, dan catatan kaki menyebutkan bahwa ini adalah versi yang dipersingkat oleh FabriceNeyret2
- Kode memprioritaskan keterbacaan dibanding performa
- Hasil keseluruhan merangkum kombinasi bentuk, SDF, warping, pengulangan, transisi berbasis waktu, dan gerakan berbasis noise yang dibutuhkan untuk animasi shader 2D ke dalam satu contoh
Workflow ekspor video
- Editor pada halaman tersebut belum dapat mengekspor animasi sebagai video
- Sebagai solusi sementara, disajikan workflow macOS yang menggunakan glslviewer dan ffmpeg
- Contoh instalasi dependency adalah sebagai berikut
brew install glslviewer ffmpeg
- Skrip ekspor membuat direktori sementara dan menjalankan
glslViewer dalam mode headless
- Resolusi:
1920x1080
- Sekuens: dari detik
0 hingga detik 7
- Frame rate:
60
- File hasil:
animation.mp4
- Contoh live coding lokal menggunakan perintah berikut
glslViewer shader.frag -w 575 -h 324 --noncurses -x 0 -y 0
Super sampling dan titik awal tulisan
- Tepi bentuk terlihat halus karena super sampling diterapkan di belakang layar
- Super sampling bekerja dengan memanggil
color_for_pixel() pada 9 posisi di dalam satu piksel layar lalu menampilkan rata-ratanya
- Jika memakai
#version 300 es, mode “pro” editor aktif dan super sampling otomatis dihapus
- Titik awal tulisan ini adalah video yang dipublikasikan 8 bulan sebelumnya, I Made a 3D Modeler, in C, in a Week
- Video itu memuat animasi yang menjelaskan algoritma marching cubes
- Ia merasa animasi tersebut sulit dibuat secara akurat dan cepat dengan program animasi biasa, sehingga mulai membuatnya dengan kode
- Setelah itu, orang-orang bertanya bagaimana animasi tersebut dibuat, lalu ia merangkumnya dalam tulisan ini
1 komentar
Opini Hacker News
Pada dasarnya caranya mengganti fungsi
stepdenganaastep: https://github.com/glslify/glsl-aastepIni adalah cara kerja yang padat dan iteratif, sangat berbeda dari pengembangan web, protokol, dan aplikasi yang akrab bagi banyak orang
Mengubah satu
floatlalu menekanshift-enterdan langsung melihat hasilnya terasa cukup memuaskancanvasatau abstraksi seperti p5.js di atasnya juga terasa seperti iniSaya tidak yakin apakah yang dimaksud pemrograman grafis secara umum, atau lebih spesifik pekerjaan shader GPU
Ada juga playlist YouTube terkait dari Inigo Quilez: https://www.youtube.com/watch?v=0ifChJ0nJfM&list=PL0EpikNmjs...
Pemrograman shader benar-benar dunia lain, dan artikelnya juga sangat keren
Saya penasaran bagaimana rasanya jika melakukan ini di Vulkan atau WebGPU/WebGL
Untuk Vulkan, tepatnya bentuknya dari GLSL ke SPIR-V
WebGPU yang berjalan di browser secara teknis tidak bisa memakai GLSL secara langsung, tetapi implementasi WebGPU native bisa menerima GLSL, dan konversi juga memungkinkan
Atau cukup gunakan WGSL; itu hampir sama dengan GLSL, kecuali sintaksnya terinspirasi Rust alih-alih sintaks bergaya C
Membuat 240 baris dengan nilai desimal yang tepat lewat trial-and-error sepertinya akan memakan cukup banyak waktu
Pencarian biner tetap cepat meski dilakukan manual
Uniform diteruskan ke shader dan bisa diperbarui tanpa perlu kompilasi ulang