OpenGL'de retro tarzı bir palet değiştirme efekti oluşturma


37

Çalışma zamanında belirli piksellerin rengini değiştirmem gereken Megaman benzeri bir oyun üzerinde çalışıyorum . Başvuru için : seçtiğiniz silahı değiştirdiğinizde Megaman'da ana karakterin paleti seçilen silahı yansıtacak şekilde değişir. Sprite’ın renklerinin tümü değişmez, sadece bazıları değişir .

Bu tür bir etki, programcının palete ve pikseller ve palet endeksleri arasındaki mantıksal haritaya erişimi olduğundan, NES üzerinde oldukça yaygındı ve yapmak oldukça kolaydı. Modern donanımda, bu biraz daha zordur çünkü paletler aynı değildir.

Dokularımın tümü 32 bit ve palet kullanmıyor.

İstediğim efekti elde etmek için bildiğim iki yol var, ancak bu efekti kolayca elde etmenin daha iyi yolları olup olmadığını merak ediyorum. Bildiğim iki seçenek:

  1. Bir gölgelendirici kullanın ve "palet değiştirme" davranışını gerçekleştirmek için bir miktar GLSL yazın.
  2. Gölgelendiriciler mevcut değilse (örneğin, grafik kartı onları desteklemediğinden), o zaman "orijinal" dokuları klonlamak ve önceden uygulanan renk değişiklikleriyle farklı sürümler oluşturmak mümkündür.

İdeal olarak, bir gölgelendirici kullanmak istiyorum, çünkü basit görünüyor ve çoğaltılmış doku yönteminin aksine çok az ek çalışma gerektiriyor. Dokulardaki renkleri değiştirmek için kopyalayan dokuların VRAM'yi boşa harcadığından endişe ediyorum - bu konuda endişelenmemeli miyim?

Düzenleme : Kabul edilen cevap tekniğini kullanarak bitirdim ve işte referans olarak gölgelendiricim.

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;      
}

Her iki doku da 32-bit'dir, bir doku, hepsi aynı boyutta (benim durumumda 6 renk) birkaç paleti içeren arama tablosu olarak kullanılır. Kaynak pikselin kırmızı kanalını renk tablosuna bir dizin olarak kullanıyorum. Bu Megaman benzeri palet takas başarmak için bir cazibe gibi çalıştı!

Yanıtlar:


41

Birkaç karakter dokusuna VRAM'ı boşa harcamam konusunda endişelenmem.

Bana göre 2. seçeneğinizin kullanılması (eğer farklı dokular veya farklı UV ofsetleri ile uyuyorsa) bunun yoludur: daha esnek, veri odaklı, kod üzerinde daha az etki, daha az hata, daha az endişe.


Bu bir kenara koyun, bellekte tonlarca sprite animasyonu olan tonlarca karakter biriktirmeye başlarsanız, belki OpenGL'nin önerdiği şeyleri kullanmaya başlayabilirsiniz :

Paletli dokular

EXT_paletted_texture uzantısına yönelik destek , ana GL satıcıları tarafından düşürüldü. Yeni donanımda paletli dokulara gerçekten ihtiyacınız varsa, bu efekti elde etmek için gölgelendiriciler kullanabilirsiniz.

Gölgelendirici örneği:

//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
}

Bu basitçe ColorTable( MyIndexTextureindekslenmiş renklerde 8 bitlik kare bir doku ) kullanarak örneklemedir (RGBA8'de bir palet ). Sadece retro tarzı paletlerin çalışma şeklini yeniden üretir.


Yukarıda alıntılanan örnek, sampler2Dgerçekte bir sampler1D+ bir kullanabileceği iki değeri kullanır sampler2D. Bunun uyumluluk nedenleriyle ( OpenGL ES'de tek boyutlu dokular yok) olduğundan şüpheliyim ... Ama boşuna, masaüstü OpenGL için bu basitleştirilebilir:

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
}

Palette"gerçek" renklerin (örneğin GL_RGBA8) tek boyutlu bir dokusudur ve dizine alınmış renklerinIndexedColorTexture iki boyutlu dokularıdır (genellikle , size 256 dizinGL_R8 verir). Bunları oluşturmak için, paletlenmiş görüntüleri destekleyen çok sayıda geliştirme aracı ve görüntü dosyası formatı var, ihtiyaçlarınıza uygun olanı bulmak mümkün olmalı.


2
Tam olarak verdiğim cevap sadece daha ayrıntılı. Yapabilseydim +2 olurdu.
Ilmari Karonen

Bunun benim için çalışmasını sağlama konusunda bazı problemlerim var, çünkü çoğunlukla renkteki indeksin nasıl belirlendiğini anlamadım - muhtemelen biraz daha genişletebilir misiniz?
Zack, İnsan

Muhtemelen indekslenmiş renkleri okumalısınız . Dokunuz 2D bir dizin dizisi haline gelir, bir paletteki renklere karşılık gelen dizinleri vurur (tipik olarak tek boyutlu bir doku). OpenGL web sitesi tarafından verilen örneği basitleştirmek için cevabımı düzenleyeceğim.
Laurent Couvidou

Özür dilerim, orijinal yorumumda net değildim. Dizine alınmış renklere ve nasıl çalıştıklarına aşinayım, ancak gölgelendirici veya 32 bitlik bir doku bağlamında nasıl çalıştıkları konusunda net değilim (bunlar dizine alınmadığından değil mi?).
Zack, İnsan

İşin aslı, burada 256 renk içeren bir 32 bit palete ve 8 bit indisli bir dokuya (0'dan 255'e kadar) sahip olmaktır. Düzenlememe bakın;)
Laurent Couvidou

10

Bunu yapmayı düşündüğüm 2 yol var.

Yol # 1: GL_MODULATE

Sprite'yi gri tonlarda ve GL_MODULATEtek bir düz renkle çizildiğinde dokuyu yapabilirsiniz.

Örneğin,

görüntü tanımını buraya girin

görüntü tanımını buraya girin

Bu size çok fazla esneklik sağlamaz, ancak temelde sadece aynı renk tonunda daha açık ve koyu olabilirsiniz.

Yol # 2: ifİfade (performans için kötü)

Yapabileceğiniz başka bir şey de, dokularınızı R, G ve B gibi bazı anahtar değerlerle renklendirmek. Örneğin, ilk renk (1,0,0), 2. renk (0,1,0), 3. renk. renk (0,0,1).

Gibi birkaç üniforma ilan edersiniz

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

Daha sonra gölgelendiricideki son piksel rengi gibi bir şey

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

color1, color2, color3Onlar gölgelendirici çalışmadan önce ayarlanabilir, böylece üniformalar, o zaman gölgelendirici basitçe gerisini (Ne "takım" megaman bağlı olan).

Daha iffazla renge ihtiyacınız varsa ifadeleri de kullanabilirsiniz , ancak eşitlik kontrollerinin doğru çalışması gerekir (dokuların genellikle R8G8B8doku dosyasında her biri 0 ile 255 arasındadır, ancak gölgelendiricide rgba yüzer)

Yol # 3: Farklı renkli görüntüleri sprite haritasında yedeklemeniz yeterli

görüntü tanımını buraya girin

Hafızayı korumaya çalışmıyorsanız, bu en kolay yol olabilir.


2
# 1 düzgün olabilir, ama # 2 ve # 3 korkunç derecede kötü tavsiye parçalarıdır. GPU, (temelde bir paleti olan) haritalardan indeksleme yeteneğine sahiptir ve bu önerilerin her ikisinden de daha iyisini yapar.
Skrylar

6

Gölgelendirici kullanırdım. Bunları kullanmak istemiyorsanız (veya kullanamıyorsanız), renk başına bir dokuya (veya bir dokuya ait parçaya) gider ve sadece temiz beyaz kullanırım. Bu, glColor()renkler için ek dokular oluşturma konusunda endişelenmenize gerek kalmadan dokuyu (örn. İçinden ) renklendirmenize olanak sağlar. Ek doku çalışması olmadan kaçakta renkleri bile değiştirebilirsiniz.


Bu oldukça iyi bir fikir. Bunu bir süre önce düşünmüştüm ama bu soruyu gönderdiğimde aklıma gelmedi. Bununla ilgili bazı karmaşıklıklar vardır, çünkü bu tür kompozit doku kullanan herhangi bir sprite daha karmaşık bir çizim mantığına ihtiyaç duyacaktır. Bu harika öneri için teşekkürler!
Zack, İnsan

Evet, ayrıca sprite başına x pass'a ihtiyacınız olacak ve bu da oldukça karmaşık bir özellik olabilir. Günümüzün donanımı en azından temel piksel gölgelendiricileri (hatta netbook GPU'ları) destekleyen göz önüne alındığında, bununla giderdim. Sağlık barları veya simgeler gibi sadece belirli şeyleri renklendirmek istiyorsanız, temiz olabilir.
Mario

3

"Sprite’ın renklerinin tümü değişmiyor, sadece bazıları değişiyor."

Eski oyunlar hile paleti yaptı çünkü sahip oldukları tek şey buydu. Bu günlerde, birden fazla doku kullanabilir ve bunları çeşitli şekillerde birleştirebilirsiniz.

Hangi piksellerin renk değiştireceğine karar vermek için kullandığınız ayrı bir gri tonlamalı maske dokusu oluşturabilirsiniz. Dokudaki beyaz alanlar tam renk değişikliğini alır ve siyah alanlar değişmeden kalır.

Gölgelendirici dilinde

vec3 baseColor = doku (BaseSampler, att_TexCoord) .rgb;
şamandıra miktarı = doku (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, miktar);

Veya çeşitli doku birleştirme modları ile sabit fonksiyonlu çoklu dokuyu kullanabilirsiniz.

Bu konuda gidebileceğiniz birçok farklı yol vardır; RGB kanallarının her birine farklı renkler için maskeler koyabilir veya bir HSV renk boşluğunda ton değiştirerek renkleri modüle edebilirsiniz.

Belki daha basit bir seçenek, her bir bileşen için ayrı bir doku kullanarak sprite çizmektir. Biri saç, biri zırh, biri çizmeler, vb. İçin tek bir doku. Her biri ayrı ayrı renklendirilebilir.


2

Öncelikle genellikle bisiklete binme (palet bisikleti) olarak adlandırılır, bu nedenle Google-fu’nuza yardımcı olabilir.

Bu, tam olarak hangi efektleri üretmek istediğinize bağlı olacaktır ve statik 2B içerikle veya dinamik 3B şeylerle uğraşıyorsanız (yani sadece lekeler / dokularla uğraşmak mı yoksa bütün bir 3B sahnesini daha sonra palet takas etmek istiyorsanız). Ayrıca kendinizi bir dizi renkle sınırlandırıyorsanız veya tam renk kullanıyorsanız.

Gölgelendiricileri kullanan daha eksiksiz bir yaklaşım, palet dokusuyla renk dizine alınmış görüntüler üretmek olabilir. Bir doku yapın, ancak renkler yerine, aranacak bir dizini temsil eden bir renge sahip sonra plaka olan başka bir renk dokusuna sahip olun. Mesela kırmızı dokularda koordinat olabilir, yeşiller koordinatta olabilir. Aramayı yapmak için bir gölgelendirici kullanın. Ve bu palet dokusunun renklerini canlandırın / değiştirin. Bu, retro efektine oldukça yakın olabilir, ancak sadece sprite veya textures gibi statik 2D içerikler için işe yarar. Orijinal, retro grafikler yapıyorsanız, gerçek dizine alınmış renkli spritelar üretebilir ve bunları ve paletleri içe aktarmak için bir şeyler yazabilirsiniz.

Eğer 'global' bir palet kullanacaksanız, çalışmak isteyeceksiniz. Eski donanımların nasıl çalışacağı gibi, paleti tüm resimler arasında senkronize etmeniz gerektiğinden veya her resme kendi paletini vermeniz gerektiğinden, resminizi üretmek için yalnızca birine ihtiyaç duyduğunuzdan, paletler için daha az bellek. Bu size daha fazla esneklik verir ama daha fazla bellek kullanır. Tabii ki her ikisini de yapabilirsiniz, aynı zamanda sadece renk döngüsü yaptığınız şeyler için paletler kullanabilirsiniz. Ayrıca, renklerinizi ne kadar sınırlandırmayacağınıza da karar verin, eski oyunlar mesela 256 renk kullanır. Bir doku kullanıyorsanız, o zaman piksel sayısını alacaksınız, böylece 256 * 256

Sadece akan su gibi ucuz bir sahte efekt istiyorsanız, değiştirmek istediğiniz alanı gri tonlamalı bir maske içeren 2. bir doku oluşturabilir, sonra direkleri kullanarak renk tonunu / doygunluğu / parlaklığı değiştirebilirsiniz. gri tonlama maskesi doku. Lazier bile olabilir ve renk aralığındaki herhangi bir şeyin tonunu / parlaklığını / doygunluğunu değiştirebilirsiniz.

Tam 3 boyutlu olarak ihtiyacınız varsa, anında indekslenmiş görüntüyü oluşturmayı deneyebilirsiniz, ancak palete bakmak zorunda kalacağınız için yavaş olacaktır, muhtemelen renk yelpazesiyle sınırlı kalırsınız.

Aksi taktirde, eğer renk == renk değiştirirse, gölgelendiricinin taklit edersiniz. Bu yavaş olacaktır ve yalnızca sınırlı sayıda değiştirme renkleriyle çalışır, ancak günümüzde donanımların hiçbirini vurgulamamalısınız, özellikle sadece 2D grafiklerle uğraşıyorsanız ve hangi sprite renk değişiminin olduğunu her zaman işaretleyebilir ve farklı bir gölgelendirici kullanabilirsiniz.

Aksi taktirde onları gerçekten kandırırsınız, her durum için bir sürü animasyonlu doku yapın.

HTML5'te yapılan harika palet paleti demosu .


0

Renk uzayının [0 ... 1] ikiye bölünmesi için yeterince hızlı olup olmadıklarına inanıyorum:

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

Bu şekilde, 0 ile 0,5 arasındaki renk değerleri normal renk değerleri için ayrılmıştır; ve 0.5 - 1.0, "modüle edilmiş" değerler için ayrılmıştır, burada 0.5..1.0, kullanıcı tarafından seçilen herhangi bir aralık ile eşlenir.

Yalnızca renk çözünürlüğü, piksel başına 256 değerden 128 değere kesilir.

Muhtemelen biri ifs 'i tamamen kaldırmak için max (color-0.5f, 0.0f) gibi yerleşik fonksiyonlar kullanabilir (çarpımlarla sıfır veya bir ile işlem yaparak ...).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.