2 poin oleh GN⁺ 2023-10-27 | 1 komentar | Bagikan ke WhatsApp
  • Berangkat dari animasi CSS border-radius sederhana di beranda, implementasi animasi blob yang halus secara langsung dengan beberapa baris fragment shader
  • Shader adalah program kecil di GPU yang mengubah koordinat piksel menjadi warna; berkat eksekusi paralel ia cepat, tetapi punya banyak batasan dalam berbagi state dan abstraksi tingkat tinggi
  • Contoh dasar GLSL memetakan koordinat vUv ke nilai RGBA gl_FragColor, sekaligus memperkenalkan sintaks seperti varying, uniform, tipe vektor, dan swizzling
  • Lingkaran dan blob dibuat dengan distance(), step(), smoothstep(), serta Signed Distance Function (SDF), lalu beberapa SDF digabungkan dengan min() atau smooth minimum
  • Dengan u_time, gerakan bola dibuat; jika u_mouse ditambahkan ke array pusat, shader berkembang menjadi shader interaktif yang sebagian dapat dimanipulasi pengguna dengan mouse

Apa yang Dilakukan Shader dan Batasannya

  • Shader adalah program kecil yang dijalankan di GPU, dan setidaknya menerima koordinat piksel sebagai input lalu menghasilkan warna
  • Dalam gim video, shader digunakan untuk efek visual seperti pencahayaan, efek khusus, dan rendering bergaya kartun, serta menjadi fondasi penting grafis gim modern
  • Kunci kecepatannya ada pada paralelisasi, yaitu dijalankan secara bersamaan di banyak piksel
  • Pengantar ini berfokus pada OpenGL Shading Language yang ramah browser, yaitu GLSL
  • Sebagai kompensasi atas performa, shader harus ditulis secara kecil dan low-level
    • Sulit mengandalkan abstraksi tingkat tinggi atau impor library
    • Karena sifat eksekusi paralel, shader bekerja secara stateless dan tanpa memori, sehingga tidak memungkinkan penyimpanan atau berbagi data antar-piksel

Contoh GLSL Pertama: Mengubah Koordinat Menjadi Warna

  • Shader mengubah koordinat ternormalisasi menjadi warna RGBA
    • Koordinat biasanya dinormalisasi di antara 0 dan 1
    • (0, 0) adalah kiri bawah, (1, 1) adalah kanan atas
    • Koordinat ini secara konvensi disebut st atau uv
  • Contoh paling sederhana adalah gradien: semakin besar koordinat x, semakin meningkat warna merah; semakin besar koordinat y, semakin meningkat warna hijau
varying vec2 vUv;

void main() {
  vec2 st = vUv;
  gl_FragColor = vec4(st.x, st.y, 0.0, 1.0);
}
  • Gradien biru dibuat dengan memasukkan koordinat x ke kanal biru, seperti gl_FragColor = vec4(0.0, 0.0, st.x, 1.0);
  • Elemen penting dalam sintaksnya adalah sebagai berikut
    • varying: input yang nilainya berbeda untuk setiap piksel
    • uniform: input yang sama untuk semua piksel
    • vec2, vec3, vec4, mat2, mat3: tipe vektor dan matriks yang tipenya dinyatakan eksplisit seperti C
    • swizzling: notasi untuk mengambil sebagian vektor, seperti vec4(1, 2, 3, 4).xy
    • gl_FragColor: output yang menentukan warna tiap piksel di akhir main()

Membuat Lingkaran dengan distance(), step(), dan smoothstep()

  • Bahkan saat menggambar bentuk tegas seperti lingkaran, yang digunakan bukan fungsi seperti drawCircle(), melainkan jarak matematis
  • Jarak antara piksel saat ini dan pusat lingkaran dapat dihitung dengan distance(vec2 p1, vec2 p2)
  • Jika jarak dipetakan langsung menjadi warna, hasilnya adalah gradien melingkar; lingkaran solid dibuat dengan step(float threshold, float value)
    • Jika jarak melewati nilai ambang, hasilnya 1
    • Jika tidak, hasilnya 0
  • step() memiliki transisi tajam, sehingga dapat menimbulkan aliasing di tepi lingkaran
  • Tepi yang lebih halus dapat dibuat dengan smoothstep(float t_start, float t_end, float x)

Merepresentasikan Bentuk dengan Signed Distance Function

  • Signed Distance Function (SDF) merepresentasikan seberapa jauh sebuah titik di ruang dari suatu bentuk dengan jarak bertanda
    • Bagian dalam bentuk bernilai negatif
    • Bagian luar bernilai positif
    • Batasnya bernilai 0
  • SDF untuk lingkaran dapat dibuat dengan mengurangkan radius dari jarak relatif terhadap pusat
float circleSDF(vec2 p, float r) {
    return length(p) - r;
}
  • Dengan menggeser titik berdasarkan posisi piksel uv, kita dapat menghitung jarak terhadap lingkaran yang berada di posisi mana pun dalam ruang UV
  • Jika d < 0.0, piksel berada di dalam lingkaran sehingga dapat diwarnai dengan warna lain
  • Bentuk SDF 2D yang lebih beragam dapat dilihat di daftar komprehensif milik Inigo Quilez

Menggabungkan Beberapa SDF untuk Membuat Blob

  • SDF memudahkan pembuatan bentuk baru melalui operasi boolean
  • Union dari dua SDF cukup dibuat dengan mengambil min() dari kedua jarak
    • Jika salah satu dari dua bentuk berada di bagian dalam, jarak minimum menjadi negatif
    • Jika keduanya berada di luar, jarak minimum menjadi positif
  • Alasan menggunakan 1. - smoothstep() adalah karena step() dan smoothstep() menghasilkan 1 untuk jarak yang lebih besar dari nilai ambang, yaitu di luar bentuk
  • min() sederhana menciptakan diskontinuitas tajam di titik pertemuan dua lingkaran
  • Untuk mencampurnya secara halus seperti blob, gunakan smooth minimum
    • Argumen tambahan k mengendalikan tingkat kehalusannya
    • Contoh ini menggunakan fungsi polynomial smooth min
float smin(float a, float b, float k)
{
    float h = max( k-abs(a-b), 0.0 )/k;
    return min( a, b ) - h*h*k*(1.0/4.0);
}

Membuat Animasi Metaball dengan Uniform Waktu

  • Shader dapat menerima nilai arbitrer sebagai uniform, mirip slider
  • Animasi dibuat dengan memasukkan u_time dari JavaScript sebagai input shader, lalu menggunakannya dalam perhitungan koordinat pusat lingkaran
  • Secara default, shader diperbarui 60 kali per detik dengan nilai u_time baru, menghasilkan gerakan yang halus
  • Pusat lingkaran dapat dibuat berayun dengan fungsi periodik seperti sin dan cos
  • Saat menggabungkan beberapa lingkaran sebagai metaball, koordinat pusat disimpan dalam array dan SDF diakumulasi dengan loop
vec2 centers[4] = vec2[4](c1,c2,c3,c4);

float d = 99.;

for (int i = 0; i < 4; i++) {
  vec2 c = centers[i];
  float sdf = circleSDF(uv, c, .1*u_slider);
  d = smin(d, sdf, K);
}
  • Blob yang dihasilkan berfungsi dengan baik, tetapi masih satu warna, sehingga kemudian ditambahkan warna dan interaksi

Interaksi Akhir yang Dikendalikan Mouse

  • Tahap akhir adalah menerima koordinat mouse sebagai uniform u_mouse agar pengguna dapat mengendalikan langsung salah satu bola di dalam blob
uniform vec2 u_mouse;
  • Jika koordinat mouse ditambahkan ke array pusat bola, interaksi masuk hanya dengan satu baris
vec2 centers[5] = vec2[5](c1,c2,c3,c4,u_mouse);
  • Shader akhir membalik sumbu y mouse agar sesuai dengan koordinat kanvas, lalu menggabungkan empat pusat yang bergerak bersama pusat dari mouse
  • Warna dikombinasikan dengan beberapa kali menggunakan mix(colorA, colorB, percent)
    • Jika percent digunakan seperti boolean, perilakunya mirip if/else
    • Nilai metaball, jarak titik pusat, shine, dan perhitungan membrane tercermin dalam warna akhir
  • Jika memahami prinsip yang sama, Anda juga dapat lebih memahami cara kerja internal editor shader berbasis node seperti Blender’s shader nodes atau Unity’s Shader Graph

Materi Lain yang Layak Dilihat

1 komentar

 
GN⁺ 2023-10-27
Komentar Hacker News
  • Akhirnya memberanikan diri untuk menulis di internet dan menunjukkan diri. Sudah lama ingin belajar shader, dan kupikir akan bagus jika proses belajarnya didokumentasikan lalu dibagikan ke orang lain

    • Sangat suka karena ditulis di blog pribadi dan dibuat menjadi tulisan interaktif. Ini berbeda dari feed Notion/Medium/Dev yang payah, yang hanya menghidupi algoritma Google atau tempel-salin AI
      Blog yang jujur sedang mati perlahan tanpa terlihat, di bawah bayang-bayang algoritma mahakuasa, dan rasanya internet sudah sampai di tengah-tengah Star Wars Episode IV. Umpan balikku cuma satu: terima kasih, dan semoga menulis lebih banyak lagi
    • Selamat datang di internet, superMayo!
      Jika ingin melihat apa yang bisa dilakukan para ahli dengan shader, aku ingin memperkenalkan Inigo Quilez dan seni shader buatannya: https://www.youtube.com/watch?v=BFld4EBO2RE
      Tambahan: tadi aku tidak melihat bahwa kamu adalah penulisnya. Tulisannya sangat bagus, dan aku memang sedang mencari tutorial yang membahas seni pemrograman shader dengan cara yang lebih mudah diakses dan interaktif
    • Jika bersedia memakai ekstensi OES_standard_derivatives yang didukung dengan baik atau WebGL 2, anti-aliasing bisa ditingkatkan. Alih-alih asal memilih konstanta seperti smoothstep(0.0f, 0.01f, dist);, cukup gunakan smoothstep(fwidth(dist), -fwidth(dist), dist);
    • Mengingat shader adalah medium yang sepenuhnya visual, tulisan ini terasa seperti salah satu cara terbaik untuk memperkenalkan shader. Fakta bahwa kode interaktifnya langsung di-embed di dalam tulisan, alih-alih dipisahkan menjadi tautan latihan, terasa sangat pas
  • Tulisannya cukup bagus, tetapi terlalu menyepelekan masalah inti shader.
    Shader adalah kerumitan yang sebenarnya tidak diinginkan oleh sebagian besar program dan aplikasi. 3D menyukai segitiga dan GPU juga cocok dengan abstraksi itu, dan shader berguna untuk interpolasi di atas segitiga-segitiga tersebut
    Namun, di hampir semua hal yang bukan 3D, segitiga kurang cocok. Rendering 2D menginginkan path, rendering font menginginkan path atau pixmap, GUI jauh lebih cocok dengan path dan pixmap, compositor menginginkan pixmap, dan decoder video menginginkan pixmap serta rendering paralel
    Yang diinginkan sisi non-3D adalah pixmap persegi panjang dan akses komputasi langsung ke pixmap itu, tetapi GPU tidak terlalu menyukainya dan shader juga tidak terlalu cocok untuk itu

    • Sebagai orang yang pernah menulis renderer kurva 2D, aku membutuhkan komputasi paralel dan bandwidth tinggi, dan GPU menyediakannya. Terutama pada antarmuka baru yang mendukung scatter-write, meski aku tidak tahu seberapa luas dukungannya di browser
      Memang benar bahwa ini bukan bentuk yang diinginkan di level lebih tinggi, tetapi cukup baik sebagai fondasi untuk membangun abstraksi level lebih tinggi. Bisa saja didukung langsung oleh hardware, tetapi keuntungannya tidak begitu jelas. Tidak ada yang mengeluh bahwa CPU tidak mendukung loop for di tingkat arsitektur
      Setelah kubaca lagi, aku kurang paham masalah apa yang sebenarnya dimaksud tentang GPU. Pipeline pemrosesan vertex bisa diabaikan sepenuhnya, cukup gambar satu quad layar penuh atau gunakan compute shader, dan GPU sangat bagus dalam menangani itu. Tulisan yang ditautkan juga membahas jenis seperti ini
    • Dari sisi sebaliknya, sebagian besar program tidak benar-benar membutuhkan GPU untuk mendapatkan hasil yang diinginkan. Sementara itu, 3D hampir pasti membutuhkan paralelisme begitu skala kerjanya melewati tingkat yang sangat kecil
      Setidaknya begitulah cara berpikirnya pada era 2000-an. Seiring perangkat lunak makin kompleks, GPU memang bisa menjadi optimisasi yang diinginkan, tetapi pipeline GPU selalu ketat dan tertutup, dan pergeseran paradigma dari pemrograman single-core ke multi-core sering kali menuntut algoritma yang berbeda
      Pemrograman GPU umum memang membebaskan kita dari keterikatan pada segitiga, tetapi untuk menikmati paralelismenya, tetap dibutuhkan pendekatan yang sepenuhnya berbeda
    • Aku tidak paham bagian mana dari pixel/fragment shader yang katanya tidak bisa dipetakan dengan baik ke pixmap
  • Keren! Belakangan ini aku sedang cukup dalam menyusuri rabbit hole SDF. Senang melihat tautan ke situs iq; materinya memang sangat bagus
    Rasanya mustahil tidak menautkan shader “happy bouncing”-nya. Menurutku pribadi ini menakjubkan: https://www.shadertoy.com/view/3lsSzf
    Ada juga video YouTube berdurasi 6 jam yang menunjukkan proses pembuatannya. Kodenya padat dan mantap, sekitar 500 baris

    • Inigo Quilez itu seperti penyihir, dan komunitas shader benar-benar berutang banyak kepadanya
  • Aku sudah beberapa kali mencoba tertarik pada topik ini, tetapi tidak pernah menemukan pintu masuk yang mudah diakses, dan baru menemukannya lewat tulisan pengantar ini. Aku suka karena pendekatannya benar-benar menyenangkan dan playful, dan aku tidak sabar membaca tulisan berikutnya

  • Koreksi kecil saja, tetapi saat menyebut cel shading, tertulis cell. Istilah ini berasal dari cel yang digunakan dalam animasi gambar tangan dan dari tone kuantisasi pada shading tersebut

  • Sangat bagus. Sebagai seseorang yang tadinya seniman lalu menjadi programmer, kadang aku tergoda untuk mendalami pemrograman grafis
    Aku pernah menulis beberapa shader yang sangat dasar, tetapi begitu masuk ke matematika, yang sebenarnya cukup awal, aku langsung mentok. Aku kuliah seni, bukan ilmu komputer, jadi kemampuan matematikaku praktis nyaris tidak ada
    Bagaimanapun, ini ditulis dengan baik dan aku sangat menyukai tulisannya

  • Tulisan pengantar yang luar biasa, dan semoga terus berlanjut. Tulisan seperti ini terlalu sering dimulai dengan pembuka yang keren lalu berhenti begitu saja

  • Saya belum pernah bekerja dengan shader, jadi ini mungkin pertanyaan yang sangat mendasar. Dalam satu frame game, apakah pada dasarnya shader yang menggambar semuanya?
    Atau apakah ada bentuk dasar seperti segitiga, persegi, lingkaran, lalu shader menambahkan hal-hal seperti bayangan atau anti-aliasing di atasnya?
    Dari contohnya, terlihat seperti kita bisa membuat shader yang menggambar objek apa pun dalam sebuah scene, lalu menggabungkan shader lain untuk mendapatkan bayangan dan pencahayaan. Dari pengalaman saya yang sangat terbatas, saya menggambar bentuk, bukan menggambar dengan shader. Saya selama ini mengira shader tidak menggambar objek itu sendiri

    • Di bawah ini adalah penjelasan yang disederhanakan tentang proses forward rendering klasik, dan apa yang disebut deferred rendering sedikit berbeda
      GPU mengubah bentuk vektor abstrak seperti segitiga, yang didefinisikan oleh tiga vertex dan data seperti normal yang terkait ke tiap vertex, menjadi aliran fragmen, satu untuk tiap piksel yang dicakup bentuk itu di buffer output, atau lebih jika menggunakan multisampling. Bagian ini seluruhnya ditangani oleh perangkat keras
      Fragmen terdiri dari koordinat piksel dan data yang diberikan pengguna. Datanya bisa berupa uniform yang konstan, atau varying yaitu data vertex yang disebut sebelumnya yang diinterpolasikan ke seluruh permukaan segitiga. Interpolasi ini juga ditangani perangkat keras dan tidak bisa diprogram
      Fragment shader menerima fragmen sebagai input, menghitung warna berdasarkan data itu, lalu setelah beberapa tahap lagi mengeluarkannya sebagai warna piksel yang sesuai di layar atau buffer offscreen. Bisa berupa warna polos atau perhitungan pencahayaan yang kompleks
      Dalam rendering GPU, semua ini berlangsung secara paralel dalam skala besar sehingga sangat banyak fragmen diproses sekaligus. Shader adalah fungsi murni tanpa status, jadi hanya bisa mengakses input dan efek yang bisa dilakukannya juga hanya mengembalikan beberapa hal seperti warna dan nilai kedalaman
      Singkatnya, perangkat keras GPU menghitung piksel mana yang harus diisi untuk menggambar tiap segitiga, dan fragment shader menentukan nilai warna untuk masing-masing piksel itu
    • Shader memang melakukan seluruh proses menggambar, tetapi terbagi ke beberapa tahap. Saya tidak akan menjelaskan seluruh graphics pipeline[1], tetapi sebagian besar dari apa yang secara longgar disebut orang sebagai “shader” untuk efek pencahayaan, lebih dari 90%-nya, ada di tahap fragment/pixel shader milik renderer
      Sebelum fragment shader menggambar “di atas” scene, ada tahap lain seperti vertex dan tessellation yang menggambar bentuk dasar
      Bahkan untuk fragment shader pun, masih ada jauh lebih banyak hal daripada yang sudah dijelaskan. Misalnya deferred rendering[2], dan itu sendiri kalau dibahas juga topik besar
      1: https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipe...
      2: https://learnopengl.com/Advanced-Lighting/Deferred-Shading
    • Ya, warna setiap piksel pada akhirnya memang ditentukan oleh program shader, tetapi seperti dugaan Anda, kenyataannya lebih rumit dari itu
      Ada graphics pipeline yang merupakan campuran tahap perangkat keras fungsi tetap dan tahap yang bisa diprogram. Pada level tinggi, 1) GPU menerima kumpulan segitiga 3D dari CPU, 2) vertex shader mengubah vertex segitiga 3D menjadi vertex segitiga 2D dengan koordinat piksel sehingga menjadi datar, 3) GPU melakukan rasterisasi segitiga 2D untuk menentukan secara tepat piksel mana yang tercakup segitiga, 4) untuk tiap piksel yang tercakup, pixel shader dijalankan untuk menentukan warna piksel, dan 5) warna piksel hasilnya disimpan di frame buffer. Pada tahap ini warnanya juga bisa di-blend dengan warna yang sudah ada
      Pipeline ini diulang berkali-kali dengan mesh segitiga dan shader yang berbeda sampai seluruh frame selesai digambar
    • Secara umum, pemahaman Anda sudah benar. Setahu saya, semua jenis kode yang berjalan di GPU disebut shader. Misalnya operasi data khusus juga disebut compute shader, tetapi sifatnya berbeda
      Tugas seperti warna, bayangan, shading, efek gambar, dan pemrosesan gambar umum semuanya dilakukan sebagai komputasi paralel yang menggabungkan banyak array data. Ini mencakup vertex dan atributnya, tekstur sumber, fungsi yang sudah dipra-hitung, tekstur target, buffer, dan sebagainya
      Misalnya, untuk mendapatkan cahaya dan bayangan, shader harus bisa mengakses hal-hal seperti posisi dan arah spotlight, mungkin sebagai variabel global. Pencahayaan komposit sering diperoleh dengan menggabungkan beberapa shader pass. Misalnya satu pass dasar untuk pencahayaan global dan satu pass untuk tiap cahaya, lalu tiap pass secara harfiah menambahkan cahaya
      Sekarang, agar tidak menambahkan cahaya pada piksel yang tertutup dari sumber cahaya, yaitu bayangan, teknik yang paling umum adalah menggunakan Z-buffer. Pada dasarnya ini adalah tekstur floating-point. Karena kita ingin tahu seberapa jauh tiap cahaya menjangkau scene, sebelum menerapkan semua pencahayaan kita menyiapkan satu shader pass tunggal yang menggabungkan seluruh geometri solid scene, menggunakan posisi dan arah cahaya seperti transformasi kamera, dan memakai shader khusus yang hanya menulis jarak ke objek ke Z-buffer
      Setelah itu, setiap kali ingin tahu apakah cahaya mencapai suatu titik di ruang, setelah sedikit perhitungan geometri kita melakukan sampling pada Z-buffer ini, lalu membandingkan nilai yang tersimpan pada arah tersebut dengan jarak titik itu. Ini bisa penuh bug dan kesalahan presisi juga umum terjadi. Engine yang matang biasanya sudah menangani ini, tetapi tetap memungkinkan Anda memodifikasi prosesnya
      Sisanya pada dasarnya hanyalah variasi dari topik ini. Deferred rendering adalah cara merender data ke tekstur perantara alih-alih warna, lalu memprosesnya nanti untuk mendapatkan warna. Efek blur dilakukan sebagai konvolusi 2D pada tekstur render, misalnya dengan kernel Gaussian. Tessellation shader berkaitan dengan pembuatan geometri baru dari vertex shader. Bahkan menggambar teks pun dilakukan melalui atlas font dan persegi panjang kecil
    • Dengan fragment, yaitu pixel shader, Anda bisa menggambar objek apa pun. Sebab, terlepas dari teknologinya, Anda bisa memakai teknik matematika tertentu untuk menggambar bentuk, misalnya SDF atau trigonometri
      Karena itu, para artis berbakat mendorong batas kemampuan di dalam fragment shader sambil bergulat dengan kompromi performa
      Fragment shader lebih umum dipakai untuk efek filter layar penuh, misalnya koreksi warna
      Shader juga digunakan untuk membuat tekstur dan material objek dasar. Para material artist sering menghasilkan tekstur dengan matematika shader
      Banyak efek visual dibuat dengan penggunaan shader yang kreatif
      Shader dijalankan di GPU dengan cara paralel seperti gelombang. Sangat banyak thread berjalan dalam satu wave pada data yang sama
      Dalam beberapa kasus, shader bisa jauh lebih cepat daripada kode bercabang di CPU. Shader juga bisa mengakses sebagian data rendering dengan lebih mudah

Jadi, ini adalah ruang yang cocok untuk membuat efek khusus yang kreatif. Dalam game, objek dengan detail permukaan yang tinggi adalah target umum untuk memindahkan detail tersebut ke shader. Contohnya permukaan laut, mesh tessellation, dan masih banyak kegunaan lain karena GPU kuat dan fleksibel.

  • Jika ada yang melihat gambar tampak seperti noise yang berkedip, saya mengatasinya dengan menyalin gambar dari browser lalu menempelkannya ke tempat lain. Setelah itu gambarnya bisa terlihat dengan benar.
    Ini tautan Imgur. Gambar pertama adalah tangkapan layar dari yang terlihat di browser, dan sisanya adalah gambar sebenarnya setelah ditempel ke Imgur.
    https://imgur.com/a/F4203rz

    • Aneh. Browser apa yang dipakai?
  • Pemahaman saya tentang shader seperti ini.
    Saat menggambar garis di CPU, itu adalah fungsi yang menelusuri setiap piksel di antara titik A dan titik B lalu menggambarnya satu per satu secara berurutan. Ada tepat sejumlah langkah sesuai jumlah piksel pada garis, dan dijalankan satu kali.
    Saat menggambar garis di GPU, itu adalah fungsi yang memeriksa apakah piksel tersebut berada di atas garis, lalu jika ya maka digambar. Fungsi ini dijalankan secara bersamaan pada semua piksel di layar, termasuk piksel yang jauh dari garis.
    Apakah ini benar?

    • Tidak persis. Pertama, GPU tidak bisa menjalankan pixel shader untuk semua piksel secara bersamaan. Layar biasa punya sekitar 2 juta piksel, tetapi thread eksekusi simultan pada GPU paling banyak hanya beberapa ribu, jadi sebenarnya menggambar dalam potongan berukuran beberapa ribu piksel.
      Kedua, GPU juga tidak perlu menjalankan pixel shader untuk seluruh layar. Dengan menggunakan segitiga, shader bisa dijalankan hanya untuk bentuk arbitrer yang diinginkan. Jadi cara yang efisien untuk menggambar garis adalah mengirim dua segitiga ke GPU yang sesuai dengan geometri garis yang diinginkan, lalu menjalankan pixel shader hanya pada piksel yang ditutupi segitiga tersebut. Jauh lebih efisien.