Membuat efek swapping palet retro-style di OpenGL

37

Saya sedang mengerjakan game mirip Megaman di mana saya perlu mengubah warna piksel tertentu saat runtime. Untuk referensi : di Megaman ketika Anda mengubah senjata yang Anda pilih maka palet karakter utama berubah untuk mencerminkan senjata yang dipilih. Tidak semua warna sprite berubah, hanya yang tertentu yang berubah .

Efek semacam ini biasa dan cukup mudah dilakukan pada SEN karena programmer memiliki akses ke palet dan pemetaan logis antara piksel dan indeks palet. Pada perangkat keras modern, ini sedikit lebih menantang karena konsep paletnya tidak sama.

Semua tekstur saya 32-bit dan tidak menggunakan palet.

Ada dua cara yang saya tahu untuk mencapai efek yang saya inginkan, tetapi saya ingin tahu apakah ada cara yang lebih baik untuk mencapai efek ini dengan mudah. Dua opsi yang saya tahu adalah:

  1. Gunakan shader dan tulis beberapa GLSL untuk melakukan perilaku "swapping palet".
  2. Jika shader tidak tersedia (katakanlah, karena kartu grafis tidak mendukungnya) maka dimungkinkan untuk mengkloning tekstur "asli" dan menghasilkan versi yang berbeda dengan perubahan warna yang sudah diterapkan sebelumnya.

Idealnya saya ingin menggunakan shader karena tampaknya mudah dan memerlukan sedikit kerja tambahan yang bertentangan dengan metode duplikat-tekstur. Saya khawatir duplikat tekstur hanya untuk mengubah warna di dalamnya membuang-buang VRAM - haruskah saya tidak khawatir tentang itu?

Sunting : Saya akhirnya menggunakan teknik jawaban yang diterima dan inilah shader saya untuk referensi.

uniform sampler2D texture;
uniform sampler2D colorTable;
uniform float paletteIndex;

void main()
{
        vec2 pos = gl_TexCoord[0].xy;
        vec4 color = texture2D(texture, pos);
        vec2 index = vec2(color.r + paletteIndex, 0);
        vec4 indexedColor = texture2D(colorTable, index);
        gl_FragColor = indexedColor;      
}

Kedua tekstur 32-bit, satu tekstur digunakan sebagai tabel pencarian yang mengandung beberapa palet yang semuanya berukuran sama (dalam kasus saya 6 warna). Saya menggunakan saluran merah pixel sumber sebagai indeks ke tabel warna. Ini bekerja seperti pesona untuk mencapai swapping palet seperti Megaman!

Zack Manusia
sumber

Jawaban:

41

Saya tidak akan khawatir membuang-buang VRAM untuk beberapa tekstur karakter.

Bagi saya menggunakan opsi Anda 2. (dengan tekstur yang berbeda atau offset UV yang berbeda jika cocok) adalah caranya: lebih fleksibel, didorong data, lebih sedikit dampak pada kode, lebih sedikit bug, lebih sedikit kekhawatiran.


Ini dikesampingkan, jika Anda mulai mengumpulkan banyak karakter dengan banyak animasi sprite dalam memori, mungkin Anda bisa mulai menggunakan apa yang direkomendasikan oleh OpenGL , palet do-it-yourself:

Tekstur palet

Dukungan untuk ekstensi EXT_paletted_texture telah dijatuhkan oleh vendor GL utama. Jika Anda benar-benar membutuhkan tekstur palet pada perangkat keras baru, Anda dapat menggunakan shader untuk mencapai efek itu.

Contoh shader:

//Fragment shader
#version 110
uniform sampler2D ColorTable;     //256 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;

void main()
{
  //What color do we want to index?
  vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
  //Do a dependency texture read
  vec4 texel = texture2D(ColorTable, myindex.xy);
  gl_FragColor = texel;   //Output the color
}

Ini hanyalah pengambilan sampel dalam ColorTable(palet dalam RGBA8), menggunakan MyIndexTexture(tekstur persegi 8 bit dalam warna yang diindeks). Hanya mereproduksi cara kerja palet gaya retro.


Contoh yang dikutip di atas menggunakan dua sampler2D, di mana sebenarnya bisa menggunakan satu sampler1D+ satu sampler2D. Saya menduga ini untuk alasan kompatibilitas ( tidak ada tekstur satu dimensi di OpenGL ES ) ... Tapi tidak masalah, untuk desktop OpenGL ini dapat disederhanakan menjadi:

uniform sampler1D Palette;             // A palette of 256 colors
uniform sampler2D IndexedColorTexture; // A texture using indexed color
varying vec2 TexCoord0;                // UVs

void main()
{
    // Pick up a color index
    vec4 index = texture2D(IndexedColorTexture, TexCoord0);
    // Retrieve the actual color from the palette
    vec4 texel = texture1D(Palette, index.x);
    gl_FragColor = texel;   //Output the color
}

Paletteadalah tekstur satu dimensi dari warna "nyata" (mis. GL_RGBA8), dan IndexedColorTexturemerupakan tekstur dua dimensi dari warna yang diindeks (biasanya GL_R8, yang memberi Anda 256 indeks). Untuk membuat itu, ada beberapa alat authoring dan format file gambar di luar sana yang mendukung gambar palet, itu harus dilakukan untuk menemukan yang sesuai dengan kebutuhan Anda.

Laurent Couvidou
sumber
2
Jawaban persis yang akan saya berikan, hanya lebih detail. Akan +2 jika saya bisa.
Ilmari Karonen
Saya mengalami beberapa masalah agar ini berfungsi untuk saya, terutama karena saya tidak mengerti bagaimana indeks dalam warna ditentukan - dapatkah Anda mengembangkannya sedikit lebih lama?
Zack The Human
Anda mungkin harus membaca tentang warna yang diindeks . Tekstur Anda menjadi array 2D indeks, yaitu indeks yang sesuai dengan warna dalam palet (biasanya tekstur satu dimensi). Saya akan mengedit jawaban saya untuk menyederhanakan contoh yang diberikan oleh situs web OpenGL.
Laurent Couvidou
Maaf, saya tidak jelas dalam komentar asli saya. Saya akrab dengan warna yang diindeks dan bagaimana cara kerjanya, tapi saya tidak jelas bagaimana mereka bekerja dalam konteks shader atau tekstur 32-bit (karena itu tidak diindeks - kan?).
Zack The Human
Nah, masalahnya adalah di sini Anda memiliki satu palet 32-bit yang berisi 256 warna, dan satu tekstur indeks 8-bit (mulai dari 0 hingga 255). Lihat hasil edit saya;)
Laurent Couvidou
10

Ada 2 cara yang bisa saya pikirkan untuk melakukan ini.

Cara # 1: GL_MODULATE

Anda dapat membuat sprite dalam nuansa abu-abu, dan GL_MODULATEtekstur saat digambar dengan satu warna solid.

Sebagai contoh,

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Ini tidak memungkinkan Anda banyak fleksibilitas namun, pada dasarnya Anda hanya bisa lebih terang dan lebih gelap dari warna yang sama.

Cara # 2: ifpernyataan (buruk untuk kinerja)

Hal lain yang bisa Anda lakukan adalah mewarnai tekstur Anda dengan beberapa nilai kunci yang melibatkan R, G dan B. SO misalnya, warna pertama adalah (1,0,0), warna ke-2 adalah (0,1,0), ke-3 warna adalah (0,0,1).

Anda mendeklarasikan beberapa seragam suka

uniform float4 color1 ;
uniform float4 color2 ;
uniform float4 color3 ;

Kemudian dalam shader, warna pixel terakhir adalah sesuatu seperti

float4 pixelColor = texel.r*color1 + texel.g*color2 + texel.b*color3 ;

color1, color2, color3, Yang seragam sehingga mereka dapat diatur sebelum berjalan shader (tergantung pada apa yang "baju" Megaman dalam), maka shader hanya melakukan sisanya.

Anda juga bisa menggunakan ifpernyataan jika Anda membutuhkan lebih banyak warna, tetapi Anda harus memastikan pemeriksaan kesetaraan berfungsi dengan benar (tekstur biasanya R8G8B8antara 0 dan 255 masing-masing dalam file tekstur, tetapi dalam shader, Anda memiliki rgba float)

Cara # 3: Hanya menyimpan gambar berwarna secara berbeda di peta sprite

masukkan deskripsi gambar di sini

Ini mungkin cara termudah, jika Anda tidak mencoba menghemat memori

bobobobo
sumber
2
# 1 bisa rapi, tetapi # 2 dan # 3 adalah nasihat yang sangat buruk. GPU mampu mengindeks dari peta (yang pada dasarnya adalah palet) dan melakukannya lebih baik daripada kedua saran tersebut.
Skrylar
6

Saya akan menggunakan shader. Jika Anda tidak ingin menggunakannya (atau tidak bisa) saya akan menggunakan satu tekstur (atau bagian dari tekstur) per warna dan hanya menggunakan putih bersih. Ini akan memungkinkan Anda untuk mewarnai tekstur (misalnya melalui glColor()) tanpa Anda harus khawatir tentang membuat tekstur tambahan untuk warna. Anda bahkan dapat menukar warna dalam pelarian tanpa pekerjaan tekstur tambahan.

Mario
sumber
Ini ide yang bagus. Sebenarnya saya sudah memikirkan hal ini beberapa waktu lalu, tetapi itu tidak terlintas ketika saya memposting pertanyaan ini. Ada beberapa kompleksitas tambahan dengan ini karena setiap sprite yang menggunakan jenis tekstur-komposit ini akan membutuhkan logika gambar yang lebih kompleks. Terima kasih atas saran yang luar biasa ini!
Zack The Human
Ya, Anda juga membutuhkan x pass per sprite, yang dapat menambah kompleksitas. Mengingat perangkat keras saat ini biasanya mendukung setidaknya pixel shader dasar (bahkan netbook GPU), saya akan menggunakannya. Ini bisa rapi jika Anda hanya ingin mewarnai hal-hal tertentu, misalnya bar atau ikon kesehatan.
Mario
3

"Tidak semua warna sprite berubah, hanya yang tertentu yang berubah."

Game lama melakukan trik palet karena hanya itu yang mereka miliki. Saat ini, Anda dapat menggunakan banyak tekstur dan menggabungkannya dengan berbagai cara.

Anda dapat membuat tekstur topeng abu-abu terpisah yang Anda gunakan untuk memutuskan piksel mana yang akan berubah warna. Area putih pada tekstur menerima modifikasi warna penuh, dan area hitam tetap tidak berubah.

Dalam languague shader:

vec3 baseColor = tekstur (BaseSampler, att_TexCoord) .rgb;
jumlah float = tekstur (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, jumlah);

Atau Anda dapat menggunakan fungsi multi-tekstur tetap dengan berbagai mode penggabung tekstur.

Jelas ada banyak cara berbeda yang bisa Anda lakukan tentang ini; Anda dapat menempatkan masker untuk warna berbeda di masing-masing saluran RGB, atau memodulasi warna dengan menggeser warna dalam ruang warna HSV.

Pilihan lain, mungkin lebih sederhana, adalah menggambar sprite menggunakan tekstur terpisah untuk setiap komponen. Satu tekstur untuk rambut, satu untuk baju zirah, satu untuk sepatu bot, dll. Masing-masing warna tintable secara terpisah.

ccxvii
sumber
2

Pertama-tama ini umumnya disebut sebagai bersepeda (bersepeda palet), sehingga dapat membantu Google-fu Anda.

Ini akan tergantung pada efek apa yang ingin Anda hasilkan dan jika Anda berurusan dengan konten 2D statis atau barang 3D dinamis (yaitu apakah Anda hanya ingin berurusan dengan spites / tekstur, atau membuat adegan 3D secara keseluruhan kemudian menukar pallet itu). Juga jika Anda membatasi diri Anda pada sejumlah warna atau menggunakan warna penuh.

Pendekatan yang lebih lengkap, menggunakan shader, akan benar-benar menghasilkan gambar yang diindeks warna dengan tekstur palet. Buat tekstur tetapi alih-alih memiliki warna, miliki warna yang mewakili indeks untuk dicari kemudian memiliki tekstur warna lain yaitu piring. Misalnya merah bisa menjadi koordinat t tekstur, dan hijau bisa menjadi koordinat s. Gunakan shader untuk melakukan pencarian. Dan menghidupkan / memodifikasi warna tekstur palet itu. Ini akan cukup dekat dengan efek retro tetapi hanya akan berfungsi untuk konten 2D statis seperti sprite atau tekstur. Jika Anda melakukan grafik retro asli maka Anda mungkin dapat menghasilkan sprite warna yang diindeks dan menulis sesuatu untuk mengimpornya dan palet.

Anda juga akan ingin berolahraga jika Anda akan menggunakan palet 'global'. Seperti berapa lama perangkat keras akan bekerja, lebih sedikit memori untuk palet karena Anda hanya perlu satu tetapi lebih sulit untuk menghasilkan karya seni Anda karena Anda harus menyinkronkan palet di semua gambar) atau memberikan setiap gambar itu palet sendiri. Itu akan memberi Anda lebih banyak fleksibilitas tetapi menggunakan lebih banyak memori. Tentu saja Anda bisa melakukan keduanya, juga Anda hanya bisa menggunakan palet untuk hal-hal yang Anda sukai. Juga tentukan sejauh mana Anda tidak ingin membatasi warna Anda, game-game lama akan menggunakan 256 warna misalnya. Jika Anda menggunakan tekstur maka Anda akan mendapatkan jumlah piksel sehingga 256 * 256 akan memberi Anda

Jika Anda hanya ingin efek palsu yang murah, seperti air yang mengalir, Anda bisa membuat tekstur ke-2 yang mengandung topeng abu-abu di area yang ingin Anda modifikasi, kemudian gunakan tekstur yang sudah ditata untuk mengubah rona / saturasi / kecerahan dengan jumlah dalam tekstur topeng abu-abu. Anda bisa menjadi lebih malas dan hanya mengubah rona / kecerahan / saturasi apa pun dalam rentang warna.

Jika Anda memang membutuhkannya dalam 3D penuh, Anda dapat mencoba menghasilkan gambar yang diindeks dengan cepat tetapi akan lambat karena Anda harus mencari palet, Anda juga kemungkinan akan terbatas dalam kisaran warna.

Kalau tidak, Anda bisa memalsukannya di shader, jika warna == bertukar warna, tukar. Itu akan lambat dan hanya akan bekerja dengan sejumlah warna swapping yang terbatas tetapi seharusnya tidak menekankan perangkat keras saat ini, terutama jika Anda hanya berurusan dengan grafis 2D dan Anda selalu dapat menandai sprite yang memiliki swapping warna dan menggunakan shader yang berbeda.

Jika tidak benar-benar memalsukan mereka, buatlah sekelompok tekstur animasi untuk setiap negara bagian.

Berikut ini adalah demo hebat bersepeda palet yang dilakukan dalam HTML5 .

David C. Bishop
sumber
0

Saya percaya jika cukup cepat untuk ruang warna [0 ... 1] untuk dibagi menjadi dua:

if (color.r > 0.5) {
   color.r = (color.r-0.5) * uniform_r + uniform_base_r;
} else {
  color.r *=2.0;
} // this could be vectorised for all colors

Dengan cara ini nilai warna antara 0 dan 0,5 dicadangkan untuk nilai warna normal; dan 0,5 - 1,0 dicadangkan untuk nilai "termodulasi", di mana 0,5..1.0 memetakan ke rentang apa pun yang dipilih oleh pengguna.

Hanya resolusi warna yang terpotong dari 256 nilai per piksel hingga 128 nilai.

Mungkin seseorang dapat menggunakan fungsi builtin seperti max (color-0.5f, 0.0f) untuk menghapus ifs sepenuhnya (menukarkannya ke multiplikasi dengan nol atau satu ...).

Aki Suihkonen
sumber