7 days of WordPress themes, graphics & videos - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Game Development
  2. Programming

Buat Penembak Neon Vektor di XNA: Bloom dan Black Holes

Read Time: 13 mins
This post is part of a series called Cross-Platform Vector Shooter: XNA.
Make a Neon Vector Shooter in XNA: More Gameplay
Make a Neon Vector Shooter in XNA: Particle Effects

Indonesian (Bahasa Indonesia) translation by Sap (you can also view the original English article)

Dalam seri tutorial ini, saya akan menunjukkan cara membuat tongkat shooter neon, seperti Geometri Wars, di XNA. Tujuan dari tutorial ini bukan untuk meninggalkan Anda dengan replika Perang Geometri yang sama persis, melainkan untuk membahas elemen-elemen penting yang memungkinkan Anda membuat varian berkualitas tinggi sendiri.


Overview

Dalam seri sejauh ini, kita telah mengatur basic gameplay untuk tongkat shooter neon, Shape Blaster. Dalam tutorial ini kita akan membuat tampilan neon tanda tangan dengan menambahkan filter pasca-pemrosesan bloom.

Efek sederhana seperti ini atau efek partikel dapat membuat permainan jauh lebih menarik tanpa membutuhkan perubahan pada gameplay. Penggunaan efek visual yang efektif merupakan pertimbangan penting dalam game manapun. Setelah menambahkan filter bloom, kita juga akan menambahkan black hole kedalam game.


Efek Paska Proses Bloom

Bloom menggambarkan efek yang Anda lihat ketika Anda melihat suatu objek dengan cahaya terang di belakangnya dan cahaya tampak memudar di atas objek. Dalam Shape Blaster, efek bloom akan membuat garis-garis terang dari kapal dan partikel terlihat seperti lampu neon yang terang dan bersinar.

Sunlight blooming through the treesSunlight blooming through the treesSunlight blooming through the trees
Sinar matahari menyusup diantara pepohonan

Untuk menerapkan bloom dalam sebuah game, kita harus menjadikan adegan sebagai target render, dan kemudian menerapkan filter bloom ke target render tersebut.

Bloom bekerja dalam tiga langkah:

  1. Ekstrak bagian terang dari gambar.
  2. Memburamkan bagian yang terang.
  3. Kombinasikan gambar buram dengan gambar asli saat melakukan beberapa penyesuaian kecerahan dan saturasi.

Setiap langkah ini membutuhkan shader - pada dasarnya program singkat yang berjalan pada kartu grafis Anda. Shaders di XNA ditulis dalam bahasa khusus yang disebut High-Level Shader Language (HLSL). Gambar contoh di bawah ini menunjukkan hasil dari setiap langkah.

Initial imageInitial imageInitial image
Gambar awal
The bright areas extracted from the imageThe bright areas extracted from the imageThe bright areas extracted from the image
Area terang diekstraksi dari gambar
The bright areas after blurringThe bright areas after blurringThe bright areas after blurring
Area terang setelah buram
The final result after recombining with the original imageThe final result after recombining with the original imageThe final result after recombining with the original image
Hasil akhir setelah mengkombinasikan dengan gambar asli

Menambahkan Bloom ke Shape Blaster

Untuk filter bloom, kita akan menggunakan Sampel XNA Bloom Postprocess.

Mengintegrasikan sampel bloom dengan proyek sangatlah mudah. Pertama, temukan dua file kode dari contoh, BloomComponent.cs dan BloomSettings.cs, dan tambahkan ke proyek ShapeBlaster. Juga tambahkan BloomCombine.fx, BloomExtract.fx, dan GaussianBlur.fx ke proyek pipeline konten.

Di GameRoot, tambahkan pernyataan using untuk namespace BloomPostprocess dan tambahkan variabel anggota BloomComponent.

Di konstruktor GameRoot, tambahkan baris berikut.

Akhirnya, pada awal GameRoot.Draw(), tambahkan baris berikut.

Itu dia. Jika Anda menjalankan gim ini sekarang, Anda akan melihat bloom yang terjadi.

Saat Anda memakai bloom.BeginDraw(), ia akan mengalihkan pemakaian gambar berikutnya ke target render yang akan diterapkan. Saat Anda memakai base.Draw() di akhir metode GameRoot.Draw(), metode Draw() BloomComponent dipakai. Di sinilah bloom diterapkan dan adegan ditarik ke buffer belakang. Oleh karena itu, apa pun yang perlu mekar diterapkan harus ditarik antara panggilan untuk bloom.BeginDraw() dan base.Draw().

Tip: Jika Anda ingin menggambar sesuatu tanpa bloom (misalnya, user interface), gambarlah setelah panggilan ke base.Draw().

Anda dapat men-tweak pengaturan mekar sesuai dengan keinginan Anda. Saya telah memilih nilai-nilai berikut:

  • 0.25 untuk ambang bloom. Ini berarti setiap bagian dari gambar yang kurang dari seperempat kecerahan penuh tidak akan berkontribusi untuk bloom.
  • 4 untuk jumlah blur. Untuk yang cenderung matematis, ini adalah standar deviasi dari Gaussian blur. Nilai yang lebih besar akan mengaburkan cahaya yang mekar lebih banyak. Namun, perlu diingat bahwa blur shader diatur untuk menggunakan sejumlah sampel tetap, terlepas dari jumlah blur. Jika Anda menetapkan nilai ini terlalu tinggi, blur akan melampaui radius dari mana sampel shader, dan artefak akan muncul. Idealnya, nilai ini seharusnya tidak lebih dari sepertiga dari radius pengambilan sampel Anda untuk memastikan kesalahannya dapat diabaikan.
  • 2 untuk intensitas bloom, yang menentukan seberapa kuat bloom mempengaruhi hasil akhir.
  • 1 untuk intensitas dasar, yang menentukan seberapa kuat gambar asli mempengaruhi hasil akhir.
  • 1.5 untuk saturation bloom. Ini menyebabkan cahaya di sekitar objek terang memiliki warna yang lebih jenuh daripada objek itu sendiri. Nilai tinggi dipilih untuk mensimulasikan tampilan lampu neon. Jika Anda melihat pusat cahaya neon yang terang, ia terlihat hampir putih, sementara cahaya di sekitarnya lebih berwarna.
  • 1 untuk saturasi dasar. Nilai ini mempengaruhi saturasi gambar dasar.
Without bloomWithout bloomWithout bloom
Tanpa bloom
With bloomWith bloomWith bloom
Dengan bloom

Bloom Dibawah Hood

Filter bloom diterapkan di kelas BloomComponent. Komponen mekar dimulai dengan membuat dan memuat sumber daya yang diperlukan dalam metode LoadContent(). Di sini, ia memuat tiga shader yang diperlukan dan menciptakan tiga target render.

Target render pertama, sceneRenderTarget, adalah untuk memegang adegan bahwa mekar akan diterapkan. Dua lainnya, renderTarget1 dan renderTarget2, digunakan untuk menahan sementara hasil perantara antara setiap render pass. Target-target ini dibuat setengah dari resolusi permainan untuk mengurangi biaya kinerja. Ini tidak mengurangi kualitas akhir dari bloom, karena kita akan mengaburkan gambar bloom.

Bloom membutuhkan empat lintasan perenderan, seperti yang ditunjukkan dalam diagram ini:

Render target diagramRender target diagramRender target diagram

Di XNA, kelas Effect merangkum shader. Anda menulis kode untuk shader dalam file terpisah, yang Anda tambahkan ke pipeline konten. Ini adalah file dengan ekstensi .fx yang kami tambahkan sebelumnya. Anda memuat shader ke objek Effect dengan memanggil metode Content.Load<effect>() di LoadContent(). Cara termudah untuk menggunakan shader dalam game 2D adalah dengan mengirimkan objek Effect sebagai parameter ke SpriteBatch.Begin().

Ada beberapa jenis shader, tetapi untuk filter bloom kita hanya akan menggunakan pixel shaders (kadang-kadang disebut shader fragment). Shader piksel adalah program kecil yang berjalan sekali untuk setiap piksel yang Anda gambar dan menentukan warna piksel. Kita akan memeriksa masing-masing shader yang digunakan.

BloomExtract Shader

BloomExtract shader adalah yang paling sederhana dari tiga shader. Tugasnya adalah mengekstrak area gambar yang lebih terang dari beberapa ambang lalu menskala ulang nilai warna untuk menggunakan rentang warna penuh. Nilai apa pun di bawah ambang akan menjadi hitam.

Kode shader penuh ditunjukkan di bawah ini.

Jangan khawatir jika Anda tidak terbiasa dengan HLSL. Mari kita periksa bagaimana ini bekerja.

Bagian pertama ini mendeklarasikan sampler tekstur yang disebut TextureSampler. SpriteBatch akan mengikat tekstur ke sampler ini saat menggambar dengan shader ini. Menentukan yang mendaftar untuk terikat merupakan opsional. Kita menggunakan sampler untuk mencari piksel dari tekstur terikat.

BloomThreshold adalah parameter yang bisa kita atur dari kode C# kita.

Ini adalah deklarasi fungsi shader piksel kita yang mengambil koordinat tekstur sebagai input dan mengembalikan warna. Warnanya dikembalikan sebagai float4. Ini adalah kumpulan dari empat pelampung, mirip dengan Vector4 di XNA. Mereka menyimpan komponen warna merah, hijau, biru, dan alfa dari warna sebagai nilai antara nol dan satu.

TEXCOORD0 dan COLOR0 disebut semantik, dan mereka menunjukkan kepada compiler bagaimana parameter texCoord dan nilai kembalinya digunakan. Untuk setiap output piksel, texCoord akan berisi koordinat titik yang sesuai dalam tekstur input, dengan (0, 0) sebagai sudut kiri atas dan (1, 1) adalah kanan bawah.

Di sinilah semua pekerjaan nyata dilakukan. Ini mengambil warna pixel dari tekstur, mengurangi BloomThreshold dari setiap komponen warna, dan kemudian menskalakannya kembali sehingga nilai maksimumnya adalah satu. Fungsi saturate() kemudian menjepit komponen warna antara nol dan satu.

Anda mungkin memperhatikan bahwa c dan BloomThreshold yang tidak jenis yang sama, karena c adalah float4 dan BloomThreshold adalah float. HLSL memungkinkan Anda untuk melakukan operasi dengan jenis-jenis yang berbeda ini dengan mengubah float menjadi float4 dengan semua komponen yang sama. (c - BloomThreshold) secara efektif menjadi:

Sisa shader hanya menciptakan teknik yang menggunakan fungsi pixel shader, dikompilasi untuk shader model 2.0.

Shader GaussianBlur

Gaussian blur mengaburkan gambar menggunakan fungsi Gaussian. Untuk setiap piksel dalam gambar output, kita menjumlahkan piksel dalam gambar input yang ditimbang oleh jaraknya dari piksel target. Piksel di dekatnya berkontribusi besar terhadap warna akhir sementara piksel jauh berkontribusi sangat sedikit.

Karena piksel jauh membuat kontribusi yang dapat diabaikan dan karena pencarian tekstur mahal, kita hanya mengambil sampel piksel dalam radius pendek, bukan sampling seluruh tekstur. Shader ini akan mengambil sampel titik-titik dalam 14 piksel dari piksel saat ini.

Penerapan yang naif dapat mengambil sampel semua titik dalam persegi di sekitar piksel saat ini. Namun, ini bisa jadi mahal. Dalam contoh ini, kita harus mengambil sampel dalam kotak 29x29 (14 poin di kedua sisi piksel tengah, ditambah piksel tengah). Itu total 841 sampel untuk setiap piksel dalam gambar. Untungnya, ada metode yang lebih cepat. Ternyata melakukan 2D Gaussian blur sama dengan memburamkan gambar secara horizontal, dan kemudian mengaburkannya lagi secara vertikal. Masing-masing satu dimensi kabur hanya membutuhkan 29 sampel, mengurangi total menjadi 58 sampel per pixel.

Satu lagi trik digunakan untuk lebih meningkatkan efisiensi blur. Saat Anda memberi tahu GPU untuk mengambil sampel di antara dua piksel, itu akan mengembalikan perpaduan dua piksel tanpa biaya kinerja tambahan. Karena blur kita memadukan piksel bersama-sama, ini memungkinkannya untuk mengambil sampel dua piksel sekaligus. Ini memotong jumlah sampel yang dibutuhkan hampir setengahnya.

Di bawah ini adalah bagian yang relevan dari shader GaussianBlur.

Shader sebenarnya cukup sederhana; hanya mengambil array offset dan array yang sesuai bobot dan menghitung penjumlahan seimbang. Semua matematika kompleks sebenarnya dalam kode C# yang mengisi susunan offset dan berat. Ini dilakukan dalam metode SetBlurEffectParameters() dan ComputeGaussian() dari kelas BloomComponent. Ketika melakukan blur lulus horisontal, SampleOffsets akan diisi hanya dengan offset horizontal (komponen y semuanya nol), dan tentu saja kebalikannya adalah benar untuk pass vertikal.

Shader BloomCombine

Shader BloomCombine melakukan beberapa hal sekaligus. Ini menggabungkan tekstur mekar dengan tekstur asli sementara juga menyesuaikan intensitas dan saturasi setiap tekstur.

Shader dimulai dengan mendeklarasikan dua samplers tekstur dan empat parameter float.

Satu hal yang perlu diperhatikan adalah bahwa SpriteBatch akan secara otomatis mengikat tekstur yang Anda berikan ketika memanggil SpriteBatch.Draw() ke sampler pertama, tetapi tidak akan secara otomatis mengikatkan apa pun ke sampler kedua. Sampler kedua diatur secara manual di BloomComponent.Draw() dengan baris berikut.

Selanjutnya kita memiliki fungsi pembantu yang mengatur saturasi warna.

Fungsi ini mengambil warna dan nilai saturasi dan mengembalikan warna baru. Melewati saturasi 1 daun warna tidak berubah. Melewati 0 akan menghasilkan warna abu-abu, dan nilai kelulusan yang lebih besar dari satu akan mengembalikan warna dengan saturasi yang meningkat. Melewatkan nilai negatif benar-benar di luar penggunaan yang dimaksudkan, tetapi akan membalikkan warna jika Anda melakukannya.

Fungsi ini bekerja dengan terlebih dahulu menemukan luminositas warna dengan mengambil jumlah tertimbang berdasarkan sensitivitas mata kita terhadap cahaya merah, hijau dan biru. Ini kemudian secara linear interpolasi antara abu-abu dan warna asli dengan jumlah kejenuhan yang ditentukan. Fungsi ini disebut dengan fungsi pixel shader.

Sekali lagi, shader ini cukup mudah. Jika Anda bertanya-tanya mengapa gambar dasar perlu digelapkan di area dengan mekar yang cerah, ingatlah bahwa menambahkan dua warna bersama-sama meningkatkan kecerahan dan komponen warna apa pun yang menambahkan hingga nilai yang lebih besar dari satu (kecerahan penuh) akan terpotong menjadi satu. Karena gambar bloom mirip dengan gambar dasar, ini akan menyebabkan banyak gambar yang memiliki lebih dari 50% kecerahan menjadi maksimal. Menggelapkan gambar dasar memetakan semua warna kembali ke kisaran warna yang dapat kita tampilkan dengan benar.


Black Holes

Salah satu musuh paling menarik di Geometry Wars adalah black hole. Mari periksa bagaimana kita bisa membuat sesuatu yang mirip dalam Shape Blaster. Kita akan menciptakan fungsi dasar sekarang, dan akan mengunjungi musuh di tutorial berikutnya untuk menambahkan efek partikel dan interaksi partikel.

A black hole with orbiting particles
Black hole dengan partikel yang mengorbit

Fungsi dasar

Lubang hitam akan menarik player ship, musuh di dekatnya, dan (setelah tutorial berikutnya), tetapi akan mengusir peluru.

Ada banyak fungsi yang bisa kita gunakan untuk daya tarik atau tolakan. Yang paling sederhana adalah menggunakan gaya konstan sehingga black hole menarik dengan kekuatan yang sama terlepas dari jarak objek. Pilihan lain adalah memiliki gaya meningkat secara linear dari nol pada jarak maksimum tertentu, untuk kekuatan penuh untuk objek langsung di atas black hole.

Jika kita ingin memodelkan gravitasi secara lebih realistis, kita dapat menggunakan kuadrat terbalik dari jarak, yang berarti gaya gravitasi sebanding dengan \(1 / distance^2\). Kita sebenarnya akan menggunakan masing-masing dari tiga fungsi ini untuk menangani objek yang berbeda. Peluru akan ditolak dengan kekuatan konstan, musuh dan kapal pemain akan tertarik dengan gaya linier, dan partikel akan menggunakan fungsi kuadrat terbalik.

Kita akan membuat kelas baru untuk black hole. Mari mulai dengan fungsi dasar.

Black hole memerlukan sepuluh tembakan untuk terbunuh. Kita menyesuaikan skala sprite sedikit untuk membuatnya berdenyut. Jika Anda memutuskan bahwa menghancurkan black hole juga harus memberikan poin, Anda harus melakukan penyesuaian serupa dengan kelas BlackHole seperti yang kita lakukan dengan kelas musuh.

Selanjutnya kita akan membuat black hole benar-benar menerapkan gaya pada entitas lain. Kita membutuhkan metode pembantu kecil dari EntityManager.

Metode ini dapat dibuat lebih efisien dengan menggunakan skema pembagian spasial yang lebih rumit, tetapi untuk jumlah entitas yang akan kita miliki, tidak apa-apa seperti itu. Sekarang kita dapat membuat black hole berlaku secara paksa dalam metode Update() mereka.

Black hole hanya mempengaruhi entitas dalam radius yang dipilih (250 piksel). Peluru dalam radius ini memiliki gaya tolak konstan yang diterapkan, sementara yang lainnya memiliki gaya tarik linear yang diterapkan.

Kita perlu menambahkan penanganan tabrakan untuk lubang hitam ke EntityManager. Tambahkan List<> untuk black hole seperti yang kita lakukan untuk jenis entitas lain, dan tambahkan kode berikut di EntityManager.HandleCollisions().

Akhirnya, buka kelas EnemySpawner dan buatlah beberapa black hole. Saya membatasi jumlah maksimum lubang hitam menjadi dua, dan memberi peluang 1 banding 600 lubang hitam untuk menghasilkan setiap bingkai.


Kesimpulan

Kita telah menambahkan bloom menggunakan berbagai shader, dan black hole menggunakan berbagai rumus kekuatan. Shape Blaster mulai terlihat bagus. Di bagian selanjutnya, kita akan menambahkan beberapa efek partikel atas yang gila.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Scroll to top
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.