Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Shaders

Panduan Permulaan untuk Pengecaman Grafik-Grafik: Bahagian 3

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called A Beginner's Guide to Coding Graphics Shaders.
A Beginner's Guide to Coding Graphics Shaders: Part 2

Malay (Melayu) translation by Meyria (you can also view the original English article)

Setelah menguasai asas-asas shader, kami mengambil pendekatan tangan untuk memanfaatkan kuasa GPU untuk mewujudkan pencahayaan dinamik yang dinamik.

Bahagian pertama siri ini meliputi asas-asas grafik shader. Bahagian kedua menerangkan prosedur am untuk menetapkan shaders untuk dijadikan rujukan untuk platform yang anda pilih. Dari sini di luar, kita akan menangani konsep umum pada pemayang grafik tanpa menganggap platform tertentu. (Untuk kemudahan, semua contoh kod akan tetap menggunakan JavaScript/WebGL.)

Sebelum pergi lagi, pastikan anda mempunyai cara untuk menjalankan shader yang anda selesa dengan. (JavaScript/WebGL mungkin paling mudah, tetapi saya menggalakkan anda untuk mencuba mengikuti di platform kegemaran anda!)

Matlamat

Pada akhir tutorial ini, anda bukan sahaja dapat membanggakan pemahaman yang mantap tentang sistem pencahayaan, tetapi anda akan membina sendiri dari awal.

Berikut adalah hasil akhir (klik untuk togol lampu):

Anda boleh menanggalkan dan mengeditnya di CodePen.

Walaupun banyak enjin permainan menawarkan sistem pencahayaan yang sedia ada, memahami bagaimana ia dibuat dan bagaimana untuk mencipta anda sendiri memberikan anda lebih banyak fleksibiliti dalam mewujudkan rupa unik yang sesuai dengan permainan anda. Kesan Shader tidak semestinya kosmetik semata-mata, mereka boleh membuka pintu kepada mekanik permainan baru yang menarik!

Chroma adalah contoh yang hebat ini; watak pemain boleh berjalan di sepanjang bayang-bayang dinamik yang dibuat dalam masa nyata:

KBermula: Adegan Awal Kami

Kami akan melangkau banyak persediaan awal, kerana inilah tutorial sebelumnya yang dibuat secara eksklusif. Kami akan bermula dengan shader serpihan mudah yang memberikan tekstur kami:

Anda boleh menanggalkan dan mengeditnya di CodePen.

Tiada apa yang terlalu mewah berlaku di sini. Kod JavaScript kami sedang menubuhkan adegan kami dan menghantar tekstur untuk menghasilkan, bersama dengan dimensi skrin kami, kepada shader.

Dalam kod GLSL kami, kami mengisytiharkan dan menggunakan pakaian seragam ini:

Kami pastikan anda menormalkan koordinat pixel kami sebelum menggunakannya untuk menarik tekstur.

Hanya untuk memastikan anda memahami segala-galanya yang berlaku di sini, inilah cabaran hangat:

Cabaran: Bolehkah anda membuat tekstur sambil mengekalkan nisbah aspek utuhnya? (Pergi sendiri dengan ini, kami akan berjalan melalui penyelesaian di bawah.)

Ia sepatutnya agak jelas mengapa ia diregangkan, tetapi di sini ada beberapa petunjuk: Lihat garis di mana kita menormalkan koordinat kami:

Kami membahagikan vec2 dengan vec2, yang sama seperti membahagikan setiap komponen secara individu. Dalam erti kata lain, di atas bersamaan dengan:

Kami membahagikan x dan y kami dengan nombor yang berlainan (lebar dan ketinggian skrin), jadi secara semula jadi akan diregangkan.

Apa yang akan berlaku jika kita membahagikan kedua-dua x dan y gl_FragCoord hanya dengan x res? Atau bagaimana pula dengan y?

Untuk kepentingan kesederhanaan, kami akan mengekalkan kod normal kami seperti-untuk tutorial lain, tetapi ia adalah baik untuk memahami apa yang berlaku di sini!

Langkah 1: Menambah Sumber Ringan

Sebelum kita boleh berbuat apa-apa, kita perlu mempunyai sumber cahaya. "Sumber cahaya" adalah tidak lebih dari satu titik yang kami hantar ke shader kami. Kami akan membina pakaian seragam baru untuk perkara ini:

Kami mencipta vektor dengan tiga dimensi kerana kami ingin menggunakan x dan y sebagai kedudukan cahaya pada skrin, dan z sebagai radius.

Mari kita tetapkan beberapa nilai untuk sumber cahaya kami dalam JavaScript:

Kami berhasrat menggunakan jejari sebagai peratusan dimensi skrin, jadi 0.2 ialah 20% daripada skrin kami. (Tiada apa yang istimewa mengenai pilihan ini. Kita boleh menetapkan ini kepada saiz dalam piksel. Nombor ini tidak bermakna apa-apa sehingga kita melakukan sesuatu dengan kod GLSL kita.)

Untuk mendapatkan kedudukan tetikus dalam JavaScript, kami hanya menambah pendengar acara:

Sekarang mari kita tulis beberapa kod shader untuk menggunakan titik cahaya ini. Kami akan mulakan dengan tugas yang mudah: Kami mahu setiap piksel dalam julat cahaya kami dapat dilihat, dan segala-galanya harus menjadi hitam.

Menterjemahkannya ke dalam GLSL mungkin kelihatan seperti ini:

Apa yang telah kami lakukan di sini ialah:

  • Mengisytiharkan pemboleh ubah seragam cahaya kami.
  • Menggunakan fungsi jarak terbina dalam untuk mengira jarak antara kedudukan cahaya dan kedudukan piksel semasa.
  • Semak jika jarak ini (dalam piksel) lebih besar daripada 20% lebar skrin; jika ya, kita kembali warna piksel itu, kalau tidak kita kembali hitam.
Anda boleh menanggalkan dan mengeditnya di CodePen.

Uh oh! Sesuatu yang kelihatan dengan bagaimana cahaya mengikuti tetikus.

Cabaran: Bolehkah anda membetulkannya? (Sekali lagi, pergi sendiri sebelum kita berjalan di bawahnya.)

Memperbaiki Gerakan Cahaya

Anda mungkin ingat dari tutorial pertama dalam siri ini bahawa sumbu-y di sini dibalikkan. Anda mungkin tergoda untuk melakukan:

Mana yang bunyi matematik, tetapi jika anda melakukannya, shader anda tidak akan dikompilasi! Masalahnya ialah pemboleh ubah seragam tidak boleh diubah. Untuk melihat mengapa, ingat bahawa kod ini berjalan untuk setiap piksel tunggal selari. Bayangkan semua teras prosesor yang cuba mengubah pemboleh ubah tunggal pada masa yang sama. Tidak baik!

Kita boleh membetulkannya dengan membuat pemboleh ubah baru dan bukannya cuba menyunting pakaian seragam kita. Atau lebih baik lagi, kita boleh melakukan langkah ini sebelum menyerahkannya kepada shader:

Anda boleh menanggalkan dan mengeditnya di CodePen.

Kami sekarang telah berjaya menentukan julat pemandangan kami yang kelihatan. Ia kelihatan sangat tajam, walaupun ....

Menambah Kecerunan

Daripada sekadar memotong ke hitam apabila kita berada di luar jangkauan, kita boleh mencuba kecerunan lancar ke arah tepi. Kita boleh melakukan ini dengan menggunakan jarak yang kita sudah dikira.

Daripada menetapkan semua piksel di dalam julat yang kelihatan kepada warna tekstur, seperti:

Kita boleh membiak bahawa dengan faktor jarak:

Anda boleh menanggalkan dan mengeditnya di CodePen.

Ini berfungsi kerana dist ialah jarak dalam piksel antara piksel semasa dan sumber cahaya. Istilah (light.z * res.x) adalah panjang jari-jari. Oleh itu, apabila kita melihat pixel tepat pada sumber cahaya, dist adalah 0, jadi kita akhirnya mendarabkan warna dengan 1, iaitu warna penuh.

Diagram ini, dist dikira untuk beberapa piksel sewenang-wenangnya. dist adalah berbeza bergantung pada pixel yang kita berada, sedangkan light.z * res.x adalah malar.

Apabila kita melihat piksel di pinggir bulatan, dist adalah sama dengan panjang jejari, jadi kita akhirnya mendarabkan warna dengan 0, yang berwarna hitam.

Langkah 2: Menambah Kedalaman

Setakat ini kami tidak melakukan lebih banyak daripada membuat topeng gradien untuk tekstur kami. Semuanya masih terlihat datar. Untuk memahami cara memperbaikinya, mari kita lihat apa yang sedang dilakukan sistem pencahayaan kita saat ini, dibandingkan dengan apa yang sepatutnya dilakukan.

Dalam senario di atas, anda akan mengharapkan A menjadi yang paling dinyalakan, memandangkan sumber cahaya kami dihidupkan secara langsung, dengan B dan C menjadi gelap, kerana hampir tiada sinaran cahaya sebenarnya memukul pihak.

Walau bagaimanapun, inilah yang kita lihat sistem cahaya sekarang:

Mereka semua diperlakukan sama, kerana satu-satunya faktor yang kita ambil perhatian adalah jarak pada pesawat xy. Sekarang, anda mungkin berfikir bahawa semua yang kita perlukan sekarang adalah ketinggian setiap mata, tetapi itu tidak betul. Untuk melihat mengapa, pertimbangkan senario ini:

A ialah bahagian atas blok kami, dan B dan C adalah sisinya. D adalah satu lagi tanah yang berdekatan. Kita dapat melihat bahawa A dan D sepatutnya paling terang, dengan D menjadi sedikit lebih gelap kerana lampu itu mencapai sudut. B dan C, sebaliknya, harus sangat gelap, kerana hampir tidak ada cahaya yang menjangkau mereka, kerana mereka menghadapi jauh dari sumber cahaya.

Ia bukan ketinggian sehinggalah arah yang menghadap ke permukaan yang kita perlukan. Ini dikenali sebagai permukaan biasa.

Tetapi bagaimana kita menyampaikan maklumat ini kepada shader? Kita tidak boleh menghantar ribuan nombor raksasa untuk setiap pixel tunggal, bolehkah kita? Sebenarnya, kita sudah berbuat demikian! Kecuali kita tidak menyebutnya array, kita menyebutnya tekstur.

Inilah peta biasa; ia hanya imej di mana nilai r, g dan b setiap piksel mewakili arah dan bukan warna.

Example normal map

Di atas adalah peta biasa yang biasa. Jika kita menggunakan pemetik warna, kita dapat melihat bahawa lalai, arah "rata" diwakili oleh warna (0.5, 0.5, 1) (warna biru yang mengambil majoriti imej). Inilah arah yang menunjuk ke arah lurus. Nilai x, y dan z dipetakan ke nilai r, g dan b.

Sisi miring di sebelah kanan menunjuk ke kanan, jadi nilai xnya lebih tinggi; nilai x juga nilai merah, itulah sebabnya ia kelihatan lebih merah/merah jambu. Perkara yang sama berlaku untuk semua pihak yang lain.

Ia kelihatan lucu kerana ia tidak dimaksudkan untuk diberikan; ia dibuat semata-mata untuk mengekod nilai-nilai normal permukaan ini.

Oleh itu, mari kita memuatkan peta normal ini untuk menguji dengan:

Dan tambahnya sebagai salah satu pemboleh ubah seragam kami:

Untuk menguji bahawa kami telah memuatkannya dengan betul, mari kita cuba menjadikannya bukan tekstur kita dengan menyunting kod GLSL kami (ingat, kami menggunakannya sebagai tekstur latar belakang, dan bukannya peta normal pada ketika ini):

Anda boleh menanggalkan dan mengeditnya di CodePen.

Langkah Tiga: Menggunakan Model Lampu

Sekarang kita mempunyai data biasa permukaan kita, kita perlu melaksanakan model pencahayaan. Dalam erti kata lain, kita perlu memberitahu permukaan kita bagaimana untuk mengambil kira semua faktor yang kita perlu untuk mengira kecerahan akhir.

Model Phong adalah yang paling mudah yang kita boleh laksanakan. Begini bagaimana ia berfungsi: Memandangkan permukaan dengan data normal seperti ini:

Kami hanya mengira sudut antara sumber cahaya dan permukaan biasa:

Lebih kecil sudut ini, lebih cerah piksel.

Ini bermakna bahawa piksel langsung di bawah sumber cahaya, di mana perbezaan sudut adalah 0, akan menjadi paling terang. Piksel paling gelap ialah mereka yang menunjukkan arah yang sama dengan sinar cahaya (yang akan menjadi seperti bahagian bawah objek)

Sekarang mari kita laksanakan ini.

Oleh kerana kami menggunakan peta normal yang mudah untuk diuji, mari kita tetapkan tekstur kami kepada warna pepejal supaya kami dapat dengan mudah mengetahui sama ada ia berfungsi.

Jadi, bukannya:

Mari kita buatnya putih padat (atau mana-mana warna yang anda suka):

Ini adalah GLSL untuk membuat vec4 dengan semua komponen bersamaan dengan 1.0.

Inilah yang kelihatan seperti algoritma kami:

  1. Dapatkan vektor normal pada piksel ini.
  2. Dapatkan vektor arah cahaya.
  3. Biasakan vektor kita.
  4. Hitung sudut di antara mereka.
  5. Keluarkan warna terakhir dengan faktor ini.

1. Dapatkan Vektor Normal di Piksel Ini

Kita perlu tahu arah mana permukaan yang dihadapi supaya kita dapat mengira berapa banyak cahaya yang seharusnya mencapai pixel ini. Arah ini disimpan dalam peta normal kami, jadi mendapatkan vektor normal kami hanya bermaksud mendapatkan warna piksel semasa tekstur biasa:

Oleh kerana nilai alfa tidak mewakili apa-apa dalam peta normal, kita hanya memerlukan tiga komponen pertama.

2. Dapatkan Vektor Arah Cahaya

Sekarang kita perlu tahu di mana arah cahaya kita menunjuk. Kita boleh bayangkan permukaan cahaya kita adalah lampu suluh yang dipegang di hadapan skrin, di lokasi tetikus kita, jadi kita boleh mengira vektor arah cahaya dengan hanya menggunakan jarak antara sumber cahaya dan piksel:

Ia perlu mempunyai koordinat z juga (untuk dapat mengira sudut terhadap vektor normal permukaan 3-dimensi). Anda boleh bermain-main dengan nilai ini. Anda akan mendapati bahawa lebih kecil itu, kontras yang lebih tajam adalah antara kawasan terang dan gelap. Anda boleh memikirkan ini sebagai ketinggian yang anda memegang lampu suluh anda di atas tempat kejadian; semakin jauh, cahaya yang lebih teragih diagihkan.

3. Menetapkan Normal Vektor kami

Kini untuk menormalkan:

Kami menggunakan fungsi terbina dalam untuk menormalkan untuk memastikan kedua vektor kami mempunyai panjang 1.0. Kita perlu melakukan ini kerana kita akan menghitung sudut menggunakan produk dot. Sekiranya anda sedikit kabur mengenai cara kerja ini, anda mungkin mahu menyusun beberapa algebra linear anda. Untuk keperluan kami, anda hanya perlu tahu bahawa produk titik akan mengembalikan kosina sudut antara dua vektor sama panjangnya.

4. Kira Sudut Antara Vektor Kami

Mari maju dan lakukannya dengan fungsi titik terbina dalam:

Saya panggil ia meresap hanya kerana ini adalah istilah ini yang dipanggil dalam model pencahayaan Phong, kerana bagaimana ia menentukan berapa banyak cahaya yang mencapai permukaan adegan kita.

5. Melipatgandakan Warna Akhir oleh Faktor Ini

Itu sahaja! Sekarang, teruskan dan tambahkan warna anda dengan istilah ini. Saya pergi ke hadapan dan mencipta pembolehubah yang dikenali sebagai distanceFactor supaya persamaan kita kelihatan lebih mudah dibaca:

Dan kami mendapat model lampu kerja! (Anda mungkin mahu mengembangkan radius cahaya anda untuk melihat kesannya lebih jelas.)

Anda boleh menanggalkan dan mengeditnya di CodePen.

Hmm, sesuatu seolah-olah sedikit. Rasanya seperti cahaya kita dimiringkan entah bagaimana.

Mari kita menyemak semula matematik kita selama ini. Kami mempunyai vektor cahaya ini:

Yang kita tahu akan memberi kita (0, 0, 60) apabila cahaya berada di atas pixel ini. Selepas kita menormalkannya, ia akan menjadi (0, 0, 1).

Ingatlah bahawa kita mahu normal yang menunjuk terus ke arah cahaya untuk mempunyai kecerahan maksimum. Permukaan lalai kita biasa, menunjuk ke atas, adalah (0.5, 0.5, 1).

Cabaran: Bolehkah anda melihat penyelesaian sekarang? Bolehkah anda melaksanakannya?

Masalahnya adalah bahawa anda tidak boleh menyimpan nombor negatif sebagai nilai warna dalam tekstur. Anda tidak boleh menandakan vektor yang menunjuk ke kiri sebagai (-0.5, 0, 0). Jadi, orang yang membuat peta normal perlu menambah 0.5 kepada semua. (Atau, dalam istilah yang lebih umum, mereka perlu mengalihkan sistem koordinat mereka). Anda perlu sedar tentang ini untuk mengetahui bahawa anda harus menolak 0.5 dari setiap piksel sebelum menggunakan peta.

Inilah yang kelihatan seperti demo selepas menolak 0.5 dari x dan y vektor normal kami:

Anda boleh menanggalkan dan mengeditnya di CodePen.

Terdapat satu pembetulan terakhir yang perlu kita buat. Ingat bahawa produk titik mengembalikan kosinus sudut. Ini bermakna output kami diapit antara -1 dan 1. Kami tidak mahu nilai negatif dalam warna kami, dan sementara WebGL nampaknya secara automatik membuang nilai-nilai negatif ini, anda mungkin mendapat tingkah laku pelik di tempat lain. Kita boleh menggunakan fungsi max terbina dalam untuk menyelesaikan masalah ini, dengan mengubahnya:

Ke dalam ini:

Sekarang anda mempunyai model lampu kerja!

Anda boleh meletakkan tekstur batu, dan anda boleh mencari peta sebenar dalam repo GitHub untuk siri ini (atau, secara langsung, di sini):

Kami hanya perlu menukar satu baris JavaScript, dari:

Untuk

Dan satu garisan GLSL, dari:

Dan anda akan melihat beberapa kacang jeli yang lazat, merentang di skrin kami:

Dan inilah hasil akhir:

Anda boleh menanggalkan dan mengeditnya di CodePen.

Tips Pengoptimuman

GPU sangat berkesan dalam apa yang ia lakukan, tetapi mengetahui apa yang boleh memperlahankannya adalah sangat berharga. Berikut adalah beberapa petua mengenai perkara itu:

Cabang

Satu perkara tentang shaders adalah bahawa ia umumnya lebih baik untuk mengelakkan cawangan apabila mungkin. Walaupun anda jarang perlu bimbang tentang sekumpulan if pernyataan pada mana-mana kod yang anda tulis untuk CPU, mereka boleh menjadi hambatan utama bagi GPU.

Untuk mengetahui mengapa, ingat lagi bahawa kod GLSL anda berjalan pada setiap piksel pada skrin selari. Kad grafik boleh membuat banyak pengoptimuman berdasarkan fakta bahawa semua piksel perlu menjalankan operasi yang sama. Sekiranya terdapat sekumpulan if kenyataan, bagaimanapun, beberapa pengoptimuman mungkin akan gagal, kerana piksel yang berbeza akan menjalankan kod yang berbeza sekarang. Sama ada atau tidak if kenyataan benar-benar memperlahankan sesuatu, ia bergantung kepada pelaksanaan hardware dan graphics card tertentu, tetapi ia adalah perkara yang baik untuk diingat ketika cuba mempercepatkan shader anda.

Renderan Tertunda

Ini adalah konsep yang sangat berguna apabila berurusan dengan pencahayaan. Bayangkan jika kita mahu mempunyai dua sumber cahaya, atau tiga, atau sedozen; kita perlu mengira sudut antara setiap permukaan yang normal dan setiap titik cahaya. Ini akan cepat memperlahankan shader kami untuk merangkak. Renderan ditunda adalah satu cara untuk mengoptimumkan bahawa dengan memisahkan kerja shader kami menjadi berbilang pas. Berikut adalah artikel yang masuk ke butiran mengenai apa yang dimaksudkan. Saya akan memetik bahagian yang relevan untuk tujuan kami di sini:

Pencahayaan adalah sebab utama untuk pergi satu laluan berbanding yang lain. Dalam saluran paip penyampaian yang standard, pengiraan pencahayaan perlu dilakukan pada setiap puncak dan pada setiap serpihan dalam adegan yang kelihatan, untuk setiap cahaya di tempat kejadian.

Sebagai contoh, bukannya menghantar pelbagai titik cahaya, anda mungkin akan menarik mereka semua ke tekstur, sebagai lingkaran, dengan warna pada setiap piksel yang mewakili keamatan cahaya. Dengan cara ini, anda akan dapat mengira kesan gabungan semua lampu di tempat kejadian anda, dan hanya menghantar tekstur akhir (atau penampan seperti yang kadang-kadang dipanggil) untuk mengira pencahayaan dari.

Pembelajaran untuk memecah kerja ke dalam pelbagai pas untuk shader adalah teknik yang sangat berguna. Kesan blur menggunakan idea ini untuk mempercepatkan shader, contohnya, serta kesan seperti shader/shader shader. Ia keluar dari skop tutorial ini, tetapi kita mungkin akan kembali ke teknik dalam tutorial masa depan!

Langkah Seterusnya

Sekarang bahawa anda mempunyai shader pencahayaan yang bekerja, berikut adalah beberapa perkara yang perlu dicuba dan dimainkan:

  • Cuba bervariasi ketinggian (nilai z) vektor cahaya untuk melihat kesannya
  • Cuba bervariasi keamatan cahaya. (Anda boleh melakukannya dengan mendarabkan istilah meresap anda dengan faktor.)
  • Tambah istilah ambien kepada persamaan cahaya anda. (Ini pada dasarnya bermakna memberi nilai minimum, supaya kawasan gelap tidak akan menjadi hitam. Ini membantu menjadikannya lebih realistik kerana perkara-perkara dalam kehidupan sebenar masih diterangi walaupun tidak ada cahaya langsung yang memukul mereka)
  • Cuba laksanakan beberapa shader dalam tutorial WebGL ini. Ia dilakukan dengan Babylon.js bukannya Three.js, tetapi anda boleh melangkau ke bahagian GLSL. Khususnya, teduhan sel dan teduhan Phong mungkin menarik minat anda.
  • Dapatkan inspirasi dari demo di GLSL Sandbox dan ShaderToy

Rujukan

Tekstur batu dan peta biasa yang digunakan dalam tutorial ini diambil dari OpenGameArt:

http://opengameart.org/content/50-free-textures-4-normalmaps

Terdapat banyak program yang boleh membantu anda membuat peta normal. Sekiranya anda berminat untuk mempelajari lebih lanjut mengenai cara membuat peta normal anda sendiri, artikel ini boleh membantu.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.