OpenGLでレトロスタイルのパレットスワッピングエフェクトを作成する


37

実行時に特定のピクセルの色を変更する必要がある、メガマンのようなゲームに取り組んでいます。参考メガマンでは、選択した武器を変更すると、メインキャラクターのパレットが選択した武器を反映するように変更されます。スプライトのすべての色が変化するわけではなく、特定の色のみが変化します

プログラマーはパレットと、ピクセルとパレットインデックス間の論理マッピングにアクセスできるため、この種の効果はNESで一般的であり、非常に簡単に実行できました。ただし、最新のハードウェアでは、パレットの概念が同じではないため、これはもう少し困難です。

テクスチャはすべて32ビットであり、パレットを使用しません。

私が望む効果を達成するために知っている2つの方法がありますが、この効果を簡単に達成するより良い方法があるかどうか興味があります。私が知っている2つのオプションは次のとおりです。

  1. シェーダーを使用してGLSLを記述し、「パレット交換」動作を実行します。
  2. シェーダーが利用できない場合(たとえば、グラフィックカードがサポートしていないため)、「オリジナル」テクスチャのクローンを作成し、色の変更が事前に適用された異なるバージョンを生成することができます。

理想的には、シェーダーを使用したいと思います。シェーダーは簡単に見えるため、テクスチャの複製方法とは対照的に追加作業がほとんど必要ないためです。テクスチャを複製して色を変更するだけでVRAMが無駄になるのではないかと心配しています。心配する必要はありませんか?

編集:私は最終的に受け入れられた答えのテクニックを使用することになり、これは参考のために私のシェーダーです。

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

両方のテクスチャは32ビットであり、1つのテクスチャはすべて同じサイズ(私の場合は6色)であるいくつかのパレットを含むルックアップテーブルとして使用されます。ソーステーブルの赤チャンネルをカラーテーブルのインデックスとして使用します。これは、メガマンのようなパレット交換を実現するための魅力のように機能しました!

回答:


41

いくつかのキャラクターテクスチャのためにVRAMを無駄にする心配はありません。

私にはあなたのオプション2を使用しています(異なるテクスチャまたは適合する場合は異なるUVオフセットを使用する)が道です:より柔軟で、データ駆動型で、コードへの影響が少なく、バグが少なく、心配が少ないです。


これはさておき、大量のスプライトアニメーションを含む大量のキャラクターをメモリに蓄積し始めたら、多分あなたはOpenGLが推奨する日曜大工のパレットを使い始めることができます:

パレットテクスチャ

EXT_paletted_texture拡張機能のサポートは、主要なGLベンダーによって廃止されました。新しいハードウェアでパレットテクスチャが本当に必要な場合は、シェーダーを使用してその効果を実現できます。

シェーダーの例:

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

これは単純にColorTable(RGBA8のパレット)でMyIndexTexture(インデックス付き色の8ビットの正方形のテクスチャ)をサンプリングしています。レトロスタイルのパレットの動作を再現します。


上記の例ではtwoをsampler2D使用していますが、実際にはone sampler1D+ oneを使用できsampler2Dます。これは互換性の理由によるものだと思われます(OpenGL ESには1次元のテクスチャはありません)。

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は、「実際の」色(たとえばGL_RGBA8)のIndexedColorTexture1次元のテクスチャーであり、インデックス付きの色の 2次元のテクスチャーです(通常GL_R8、256個のインデックスを提供します)。これらを作成するには、パレット画像をサポートするオーサリングツール画像ファイル形式いくつか あります。ニーズに合ったものを見つけることができるはずです。


2
まさに私が与えたであろう答え、より詳細なだけ。できれば+2。
イルマリカロネン

主に色のインデックスがどのように決定されるのか理解していないため、これをうまく機能させるのにいくつかの問題があります-それをもう少し拡張できますか?
ザックザヒューマン

おそらくインデックス付きの色について読んでください。テクスチャは、パレットの色に対応するインデックスの2D配列になります(通常、1次元のテクスチャ)。OpenGL Webサイトで提供されている例を単純化するために、回答を編集します。
ローランクーヴィドゥー

申し訳ありませんが、元のコメントでは明確ではありませんでした。私はインデックス付きの色とその仕組みに精通していますが、シェーダーまたは32ビットテクスチャのコンテキスト内でどのように機能するのか明確ではありません(インデックス化されていないので、そうですか?)。
ザックザヒューマン

さて、ここでは、256色を含む32ビットパレットが1つと、8ビットインデックスのテクスチャ(0から255まで)が1つあります。私の編集を参照してください;)
ローランCouvidou

10

これを行うには、2つの方法があります。

方法1:GL_MODULATE

スプライトをグレーの色合いで作成しGL_MODULATE、テクスチャを単一の単色で描画することができます。

例えば、

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ただし、これでは柔軟性があまり高くありません。基本的に、同じ色合いで明るくすることと暗くすることしかできません。

方法#2:ifステートメント(パフォーマンスに悪い)

もう1つできることは、R、G、Bを含むいくつかのキー値でテクスチャに色を付けることです。たとえば、最初の色は(1,0,0)、2番目の色は(0,1,0)、3番目は色は(0,0,1)です。

次のようないくつかのユニフォームを宣言します

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

シェーダーでは、最終的なピクセルの色は次のようになります

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

color1color2color3、彼らは(「スーツ」、ロックマンがであるものに応じて)、シェーダの実行前に設定することができますので、その後、シェーダは、単に残りを行い制服です。

ifさらに色が必要な場合はステートメントを使用することもできますが、等値チェックが正しく機能する必要があります(テクスチャは通常R8G8B8、テクスチャファイルではそれぞれ0〜255ですが、シェーダではrgbaフロートがあります)

方法#3:スプライトマップに異なる色の画像を重複して保存する

ここに画像の説明を入力してください

あなたがめちゃくちゃメモリを節約しようとしていない場合、これは最も簡単な方法かもしれません


2
#1はきちんとできますが、#2と#3は恐ろしく悪いアドバイスです。GPUはマップ(基本的にはパレット)からインデックスを作成することができ、これらの両方の提案よりも優れています。
Skrylar

6

シェーダーを使用します。それらを使用したくない場合(または使用できない場合)は、色ごとに1つのテクスチャ(またはテクスチャの一部)を使用し、クリーンホワイトのみを使用します。これによりglColor()、色用の追加のテクスチャを作成することを心配することなく、テクスチャに色合いを付けることができます(例:を通して)。追加のテクスチャ作業なしで、実行時に色を交換することもできます。


これはかなり良い考えです。私は実際にしばらく前にこのことを考えていましたが、この質問を投稿したときに気になりませんでした。この種の複合テクスチャを使用するスプライトは、より複雑な描画ロジックを必要とするため、これにはいくつかの余分な複雑さがあります。この素晴らしい提案をありがとう!
ザックザヒューマン

はい、スプライトごとにxパスも必要になるため、かなり複雑になります。通常、少なくとも基本的なピクセルシェーダー(ネットブックGPUを含む)をサポートする今日のハードウェアを考慮すると、代わりにこれらを使用します。ヘルスバーやアイコンなど、特定の項目に色を付けたいだけの場合は便利です。
マリオ

3

「スプライトのすべての色が変化するわけではなく、特定の色だけが変化します。」

古いゲームはパレットトリックでした。最近では、複数のテクスチャを使用してさまざまな方法で組み合わせることができます。

どのピクセルが色を変更するかを決定するために使用するグレースケールマスクテクスチャを個別に作成できます。テクスチャ内の白い領域は完全な色の変更を受け取り、黒い領域は変更されません。

シェーダーラングアグでは:

vec3 baseColor = texture(BaseSampler、att_TexCoord).rgb;
float amount = texture(MaskSampler、att_TexCoord).r;
vec3 color = mix(baseColor、baseColor * ModulationColor、amount);

または、さまざまなテクスチャコンバイナモードで固定機能のマルチテクスチャリングを使用できます。

これについては、明らかにさまざまな方法があります。各RGBチャンネルに異なる色のマスクを配置したり、HSVカラースペースで色合いをシフトして色を変調したりできます。

別の、おそらくより簡単なオプションは、コンポーネントごとに個別のテクスチャを使用して単純にスプライトを描画することです。髪の毛、甲arm、ブーツなどのテクスチャー。それぞれが個別に色付け可能です。


2

まず、一般的にサイクリング(パレットサイクリング)と呼ばれるため、Google-fuに役立つ可能性があります。

これは、どのエフェクトを生成したいか、静的2Dコンテンツまたは動的3Dのものを扱っているかどうかに依存します(つまり、単にスピット/テクスチャを処理するか、3Dシーン全体をレンダリングしてからパレットスワップするか)。また、自分をいくつかの色に制限している場合やフルカラーを使用している場合。

シェーダーを使用するより完全なアプローチは、パレットテクスチャを使用してカラーインデックス付き画像を実際に作成することです。テクスチャを作成しますが、色を使用する代わりに、ルックアップするインデックスを表す色を使用し、次にプレートである色の別のテクスチャを使用します。たとえば、赤はテクスチャのt座標で、緑はs座標です。シェーダーを使用してルックアップを行います。そして、そのパレットテクスチャの色をアニメーション化/変更します。これはレトロ効果に非常に近いですが、スプライトやテクスチャなどの静的な2Dコンテンツでのみ機能します。本物のレトロなグラフィックを使用している場合、実際のインデックス付きカラースプライトを作成し、それらとパレットをインポートするために何かを書くことができるかもしれません。

また、「グローバル」パレットを使用する場合にも解決する必要があります。古いハードウェアがどのように機能するかと同様に、必要なのは1つだけですが、すべての画像間でパレットを同期する必要があるため、アートワークを作成するのが難しいため、パレットのメモリは少なくなります)または各画像に独自のパレットを与えます。これにより、柔軟性が向上しますが、より多くのメモリが使用されます。もちろん、両方を行うこともできますし、カラーサイクリングを行う場合にのみパレットを使用することもできます。また、色をどの程度に制限したくないのかを考えてみてください。古いゲームでは、たとえば256色を使用します。テクスチャを使用している場合は、ピクセル数を取得するため、256 * 256で得られます

流れる水などの安価な偽の効果が必要な場合は、変更する領域をグレースケールマスクを含む2番目のテクスチャを作成し、マストテクスチャを使用して、色相/彩度/明るさをグレースケールマスクテクスチャ。あなたはもっと怠け者になり、色の範囲で何かの色相/明るさ/彩度を変えるだけでした。

フル3Dで必要な場合は、インデックス付き画像をその場で生成できますが、パレットを検索する必要があるため速度が遅くなり、色の範囲も制限される可能性があります。

それ以外の場合は、シェーダーでそれを偽造することができます。色==色を交換する場合は、交換します。これは遅く、限られた数のスワッピングカラーでのみ機能しますが、特に2Dグラフィックスを扱うだけで、どのスプライトにカラースワッピングがあるかを常にマークして、別のシェーダーを使用できる場合は、現在のハードウェアにストレスをかける必要はありません。

それ以外の場合は、それらを実際に偽造して、各状態のアニメーション化されたテクスチャの束を作成します。

これは、HTML5で行われたパレットサイクリングの優れたデモです


0

ifは、色空間[0 ... 1]を2つに分割するのに十分な速さだと思います。

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

このようにして、0〜0.5の色の値は通常の色の値用に予約されています。0.5-1.0は「変調された」値用に予約されており、0.5..1.0はユーザーが選択した任意の範囲にマッピングされます。

カラー解像度のみが、ピクセルあたり256値から128値に切り捨てられます。

おそらくmax(color-0.5f、0.0f)のような組み込み関数を使用して、ifを完全に削除することができます(ゼロまたは1による乗算に変換します...)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.