2 poin oleh GN⁺ 2025-02-08 | 1 komentar | Bagikan ke WhatsApp
  • 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

 
GN⁺ 2025-02-08
Opini Hacker News
  • Jika ingin mendapatkan tepi antialiasing yang halus dari SDF tanpa pass supersampling kedua, cukup gunakan turunan standar di SDF
    Pada dasarnya caranya mengganti fungsi step dengan aastep: https://github.com/glslify/glsl-aastep
  • Benar-benar keren. Developer shader rasanya seperti berada di dimensi lain
    Ini adalah cara kerja yang padat dan iteratif, sangat berbeda dari pengembangan web, protokol, dan aplikasi yang akrab bagi banyak orang
    Mengubah satu float lalu menekan shift-enter dan langsung melihat hasilnya terasa cukup memuaskan
    • Saya penasaran apakah bekerja dengan JavaScript canvas atau abstraksi seperti p5.js di atasnya juga terasa seperti ini
      Saya tidak yakin apakah yang dimaksud pemrograman grafis secara umum, atau lebih spesifik pekerjaan shader GPU
  • Artikel yang sangat tersusun baik sebagai pengantar GLSL
    Ada juga playlist YouTube terkait dari Inigo Quilez: https://www.youtube.com/watch?v=0ifChJ0nJfM&list=PL0EpikNmjs...
  • Cara membuat gambar referensi berkedip di atas pratinjau untuk membandingkannya dengan aslinya saat mengubah kode persis sama dengan metode yang dipakai dalam animasi gambar tangan
    Pemrograman shader benar-benar dunia lain, dan artikelnya juga sangat keren
  • Strukturnya sangat bagus sebagai pengantar GLSL
    Saya penasaran bagaimana rasanya jika melakukan ini di Vulkan atau WebGPU/WebGL
    • Hampir sama. Vulkan dan WebGL sama-sama bisa memakai GLSL secara langsung
      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
  • Fakta bahwa butuh 8 bulan untuk membuat animasi ini menunjukkan ketekunan yang luar biasa
  • Saya penasaran apakah proses pengembangannya berupa iterasi terus-menerus menyesuaikan nilai pecahan, atau editor seperti apa yang dipakai
    Membuat 240 baris dengan nilai desimal yang tepat lewat trial-and-error sepertinya akan memakan cukup banyak waktu
    • Saya hanya memakai editor kode yang tertanam di halaman itu
      Pencarian biner tetap cepat meski dilakukan manual
    • Saat mengerjakan hal seperti ini, biasanya kita mengambil satu slider atau nilai input lalu menghubungkannya ke uniform
      Uniform diteruskan ke shader dan bisa diperbarui tanpa perlu kompilasi ulang