ホーム < ゲームつくろー! < プログラマブルシェーダ編 < HLSLシェーダプログラムのリソース化

その8 HLSLシェーダプログラムのリソース化


 テキストで生成されたHLSLシェーダプログラム(エフェクトプログラム)は、配布するプログラムにも添付するものなのでしょうか?これは大いに否です。ノートパッドでさえ見れてしまうエフェクトソースをそのままにするのはあまりに無防備というものでしょう。シェーダプログラムは立派な「プログラム」なのですから、できる限り隠蔽する事が望ましいはずです。

 実行ファイルに普遍的なデータを埋め込む「リソース化」は、そういう隠蔽法として有用です。HLSLプログラムもリソースとして扱えば、ほぼ完璧に隠蔽できます。Windowsはリソース化の手引きを、DirectXはリソース化されたシェーダプログラムについてしっかりとサポートしてくれています。そこで、この章では今後多分確実に考慮する事になるだろうHLSLシェーダプログラムのリソース化について詳しく見ていく事にします。



@ HLSLリソースの作り方のイロハ

 オンラインのMSDNによりますと、リソースとは「アプリケーションの外部で定義され、アプリケーションの内部で使用されるオブジェクトである」と定義されています。もう少し平たく言うと、外部に公開したくないデータを実行ファイルの中に埋め込む事で静的なアクセスと隠蔽をはかることが可能になる方法です。リソースの作り方は決まっていますが、非常に特異な生成方法なため、毛嫌いしている方も結構いるのではないでしょうか。ここでは、リソースの作り方をじっくり説明致しますので、これを機会にリソース好きになってもらえればと思います。

 今回の作成方法はVisual Studio 2005環境で説明しています。それ以外の統合環境を使用されている方は一部操作が異なると思いますが、要領は殆ど一緒です。では、始めましょう。


 まず、リソースの本体であるHLSLプログラムを外部テキストファイルとして作成しておきます。ここでは作成したHLSLファイル名を「TestEffect.fx」としておきましょう。これは、パスの確かな場所に保存しておきます。プロジェクトファイルがあるルートフォルダでも良いですし、フォルダを設けても構いません。ここでは、あえて「Effect」フォルダを設けて、そこに保存する事にします。

 次にVisual Studioの「プロジェクト」→「新しい項目の追加」で「リソースファイル(.rc)」を選びます。他のVS環境でもファイルの追加で同様の作業が出来るはずです:

 作成するリソースファイル名は「Effects.rc」としておきましょう(適当です)。[追加]をクリックすると、統合環境に2つのファイルが追加されます。1つは今作成したEffects.rcファイル、そしてもう1つはResource.hです。

 Resource.hは1つのリソースに割り振る固有のIDを設定する専用のヘッダーファイルです。殆どの環境でリソースを扱うと自動的に作成されます。便利ですが時にやっかいでもあります(^-^;。リソースに振るIDは通常0〜65535までの整数で、プロジェクトの中では重複が許されません。そして、IDには必ず対応するマクロを定義します。今回作成したいリソースはエフェクトファイルであるため、次のような名前でIDを定義をする事にしましょう:

Resource.h定義
#define FXID_TESTEFFECT 101

Resource.hファイル内には自動生成された他のマクロ定義もありますが、通常のヘッダーファイル同様に、マクロはどこに追加しても構いません。このマクロはプログラム内でリソースを読み込む時に利用される事になります。

 続いてEffect.rcリソースファイルファイルを編集します。このファイルにはリソースマクロに対応する外部ファイル名とその埋め込み型(リソースタイプ)を定義します。何だかイメージが沸かなくても、良くわからなくても大丈夫です。VS2005を起動すると、デフォルトでは左側にクラスビューなどのウィンドウが開いていると思います。そこに「ソリューションエクスプローラ」があるはずです。「無いよ〜」という方は[表示]→[ソリューションエクスプローラ]で表示されます。ここに、先ほど作成したEffects.rcファイルが表示されています。これをダブルクリックしても、実はEffect.rcファイルは開きません。このファイルを編集するには、右クリックして「コードの表示」を選択します。すると、画面にEffect.rcファイルの内部が表示されるはずです。

 環境にもよりますが、通常このファイル内にはなにやら沢山書き込まれています。それらは全部無視しまして、次の一行を最後尾に追加してください:

Effects.rcに追加
FXID_TESTEFFECT RCDATA DISCARDABLE "Effect/TestEffect.fx"

これがリソースファイル内ですべきメインの仕事なんです。

 この一行を説明します。最初に先ほど定義したFXID_TESTEFFECTマクロをそのまま書きます。次にリソースタイプを宣言します。ここではリソースタイプが「RCDATA型」である事を示しています。RCDATA型というのはユーザ定義型でして、ファイルの中身がそのまま埋め込まれます。実はDirectXの仕様上、HLSLプログラムをリソースとして読み込む為にはこの型にしなければいけませんので、問答無用でこう定義して下さい。次にある「DISCARDABLE」というのは、「リソースが必要なくなったらメモリから自動的に破棄して下さい」という印です。これは付けなくても動作しますが、付けるとメモリの無駄が無くなります。普通は使用するフラグですね。最後にリソースファイル名を相対パスで明記します。これで、リソースマクロとファイルが関連付けられました。独特な三命法ですが、慣れれば問題なく扱えるようになります。

 これでリソースの準備は終了です。ファイル作成から2ステップ(2行)で終わりますので非常に簡単です。リソースを追加した場合は、Resource.hとEffect.rcに同様な記述を追加すれば良いだけです。では次に、リソース化したHLSLプログラムを読み込む部分に移りましょう。




A HLSLリソースを読み込む

 リソースからエフェクトを読み込む作業は非常に簡単です。

 やる事はID3DXEffectインターフェイスを作成するだけです。前章ではこのインターフェイスを作成するためにD3DXCreateEffectFromFile関数を用いていました。これはファイルからエフェクトを作成する常套手段です。一方でリソースからエフェクトインターフェイスを作成するには、この関数の代わりにD3DXCreateEffectFromResource関数を用います:

D3DXCreateEffectFromResource関数
HRESULT D3DXCreateEffectFromResource(      
    LPDIRECT3DDEVICE9 pDevice,
    HMODULE           hSrcModule,
    LPCSTR            pSrcResource,
    CONST D3DXMACRO*  pDefines,
    LPD3DXINCLUDE     pInclude,
    DWORD             Flags,
    LPD3DXEFFECTPOOL  pPool,
    LPD3DXEFFECT*     ppEffect,
    LPD3DXBUFFER*     ppCompilationErrors
);

 引数が沢山ありますがpDevicepDefines以下はD3DXCreateEffectFromFile関数と一緒です。これらについての説明はこちらをご覧下さい。ここでは、この関数固有の引数であるhSrcModulepSrcResourceについて重点的に説明します。

 hSrcModuleはりソースが埋め込まれているモジュール(実行ファイルなど)のハンドルを指定します。GetModuleHandle Win32API関数で取得できます。ここにNULLを指定すると現在起動中の実行ファイル内にリソースが埋め込まれていると判断されます。当然今回はNULL指定です。
 pSrcResourceがこの関数の鍵を握ります。ここにはリソースへのポインタを指定します。「埋め込んだリソースのポインタなんて知らないよ?」と思われるかもしれませんが、ここで@で仕込んだ定義が生かされてくるんです。

 例として定義したFXID_TESTEFFECTリソースIDの場合、pSrcResourceに次のような記述をします:

リソースへのポインタを取得
D3DXCreateEffectFromResource(
  pDev,
   NULL,
   MAKEINTRESOURCE( FXID_TESTEFFECT ),
   NULL,
   NULL,
   0,
   NULL,
   &pEffect,
   NULL
);


MAKEINTRESOURCEマクロは引数のID(整数)をLPCSTR(もしくはLPWSTR)に変換するマクロです。どうゆう事か良くわからないため、実際に定義を見てみると、こんな凄い事をしていました:

MAKEINTRESOURCEマクロ
#define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))

#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#else
#define MAKEINTRESOURCE MAKEINTRESOURCEA
#endif // !UNICODE


括弧だらけで目がチカチカします(笑)。何をしているかと思えば、最初にIDをWORD型にして、次に符号なしLONGのポインタにして、最後にLPSTR型に変換しています。この強引さ、凄いです(^-^;。ちなみに2つあるのはUnicodeへの対応のためで、D3DXCreateEffectFromResource関数もちゃんとUnicodeに対応しています。

 MAKEINTRESOURCEマクロはおまけで表示しましたが、要はこのマクロを用いると埋め込んだリソースへのポインタが取得できると言う事です。そういうもんだと使ったほうが吉です。

 リソースに埋め込んだエフェクトプログラムが正しければ、これでエフェクトオブジェクトが正しく生成されます。後の使い方は前章とまったく同じです。



B エフェクトのクラス化に向けたリソースファイルの取り扱い

 影を付けたり、セピア調にしたり、一部分の空間をゆがませたり水面に波紋を作成するなどのシェーダプログラムは、部品として扱えると極めてすばらしいと思います。部品とは要はクラスとして扱えるという意味です。クラス化の良い点は、機能をひとまとめにしメソッドを通してそのエフェクトが何を必要として何を生成してくれるかを定義できるところにあります。例えば板ポリゴンを用意して、クラスにセットしてレンダリングするだけで波紋になる。こんな表現の便利さが欲しいわけです。そのためには、エフェクトをクラスとしてパッケージ化する仕組みを考える必要があります。

 今、エフェクトをリソースにできる事は示しました。クラスはエフェクトと運命共同体になるので、定義されたエフェクトと大いに連携しても構わないでしょう。となると、後問題になるのはリソースIDの管理とプロジェクトへの組み込み方です。


 リソースIDは重複が許されません。これはまったく関係のない他のリソースに対しても同じです。ですから、リソースIDは例えばResource.hのような1つのファイルの中で定義するのが望ましいと思います。しかし、新しいプロジェクトにまったく関係のないリソースIDを沢山引き連れるのもどうかと思います。そこで、エフェクトリソースに限定したヘッダーファイルとリソースファイルを新規に作成して、そこにリソースIDを追加していくスタイルにします。これはID重複の根本解決にはなりませんが、リソースIDのマネージメントが少し楽になります。

 ちょっとイメージが沸かないかもしれませんので、何を考えているのか模式的な図を示します:


 ID番号に関与しないリソースファイル、エフェクトクラス(これらはマクロ名でリソースを判断)、エフェクトファイルを一まとめにし、これらエフェクトのマクロIDを1つのエフェクトリソースヘッダーで管理します。こうすることでIDがぶつかる危険を避け、万が一他とぶつかった時にはヘッダーファイルを入れ替える事で対処できます。

 文章だけで伝えるのは中々難しいのですが、例えばTestEffectクラスというのを作った場合、次の5つのファイルで1つのエフェクトクラスを規定することになります:

・ EffectResource.h  (マクロIDが登録されている共通リソースヘッダー)
・ TestEffect.h (クラス宣言部)
・ TestEffect.cpp (クラス実装部)
・ TestEffect.rc (クラス専用リソースファイル)
・ TestEffect.fx (エフェクトファイル)

これらは一心同体、運命共同体で、いつもくっつけておくファイル群です。そして、リリース時には、これらのファイルは統合されて内部プログラムとして働くため、一切見えなくなります。



 この章ではHLSLプログラムのリソース化について掘り下げて、さらにエフェクトのクラス化に向けたファイル操作について説明してきました。巷に沢山説明されている大変高度でかっこいいエフェクトに目を奪われて、私たちはエフェクトを作る事自体に兎角集中してしまいがちになりますが、こういう部分をちゃんと整理しておかないと、再利用可能な部品(オブジェクト)としてエフェクトを扱うことがかなり難しくなってしまうと思います。この章で試行錯誤した内容が、その解決の一端になるとうれしいものです。

 GPUを有効に使ったエフェクトがクラス化されていて、公開されているメソッドだけでその効果を自分のプログラムにすぐに取り込める。そんな便利なクラス化を夢見て、今後本編を発展させていこうと思います。