クラスのリコンストラクタでシェーダをサクサク作れるよう改良だ前章でディフューズとスペキュラマップを貼り付けた自動生成シェーダを実際に作って描画に成功しました。実際に派生クラスを実装してみて様々な「気になる所」が出てきました。その多くは「派生クラスでやらなくてはならないことが多すぎる」という点に端を発します。派生クラスでシェーダをサクサク作りたいのですが、シェーダプログラム意外に色々な仮想メソッドをオーバーライドするのがとにかく面倒という印象でした。そこでこの章では先に作成したシェーダ作成クラスであるShaderCreatorクラスをリコンストラクタ(再構築)する事で、派生クラスで継承すべき仮想メソッドをぐっと減らしてみます。細かく色々とあるためすべては説明しきれませんが、最終的には継承メソッドが7つから3つに減り、非常に扱いやすくなります。...ま、まぁ、ある意味眠くなる章でもありますね。リコンストラクタの過程を見てみたい御暇な方はどうぞです。何が一体面倒を引き起こしているのか?先のサンプル実装を見ながら、少しずつ面倒を無くしていきます。テクスチャの名前はセマンティクスで良い気がします乱雑さの多くは初期化部分(宣言時初期化)にあるように感じました。その一つとしてテクスチャ変数(GlobalTextureValクラス)の名前定義を見てみます。"SPECULARTEXTURE"),...メンバ変数diffuseTexture、でその変数が保持するシェーダ内のテクスチャ名が"diffuseTexutre"、んでそのセマンティクスがDIFFUSETEXTURE...。明らかに冗長ですよね。これを少しシュリンクしましょう。メンバ変数としてdiffuseTextureという宣言は必要です。セマンティクス「DIFFUSETEXTURE」はプログラム上でテクスチャをアタッチする時に必要になります。セマンティクスは重複がゆるされないし、シェーダが必要とするテクスチャを判別するのにも使える便利なものです。そこで、テクスチャ名はセマンティクス名と同じ(一応全部小文字にして区別)にすることにしました。:diffuseTexture("DIFFUSETEXTURE"),specularTexture("SPECULARTEXTURE"),...とセマンティクス名だけで良くなってしまいます。サンプラの初期化時に対応テクスチャとかサンプリング関数を渡してしまおう続いてサンプラ(GlobalSamplerValクラス)です。このメンバ変数の宣言時初期化では現在サンプラ名だけを指定しています。サンプラは宣言される時に自分がサンプリングするテクスチャを内部に持ってしまうものです。valName){}....要はですね、「テクスチャから色を貰う」という作業を担う人をサンプラクラスに集約したわけです。今までは「テクスチャ」「サンプラ」「サンプリング関数(SamplerFunc)」と3つがバラバラだったのをサンプラだけにしてしまったのが上の改良です。実質サンプラ名(valName)、テクスチャオブジェクト(texture)、テクスチャが無かった時の代替初期化カラー(initVal)だけですべてが賄えます。コンバータへの登録とグローバル変数の書き込みの冗長性を排除しようShaderCreatorクラスは、シェーダプログラムを上のように可読性よく書きやすくするために、内部に「シェーダ文コンバータ」を持っています。このコンバータが変数やサンプラを元シェーダ文から識別できるようにするため、コンバータに派生ShaderCreatorクラス内で宣言した各種メンバを登録する作業がどうしても必要になります。一つ、シェーダのグローバル変数を宣言する部分では、メンバ変数が持っているdeclメソッドを逐一呼び出す必要がありました。projMatrix.decl();}いやっはっは、笑えるほど同じような羅列を書いていますね。これは何とかしたい所です。グローバル変数はすべてGlobalValクラスを継承していて、派生クラスはdeclメソッドを実装するように強要されています。GlobalValな配列に登録してしまえば上のように具体的に変数.decl()とする必要はありません。いう登録をしているのは、あらそうですよ、conv.regist()ではないですか(笑)。よって、グローバル変数の書き出しはコンバートクラスに担ってもらいましょう。{conv.globalValCode(out);}のような呼び出しでシェーダ文を書き出してもらうように改良します。する事により、元々仮想メソッドだったグローバル変数の書き出しはShaderCreator親クラスの固定作業となり、派生クラスでわざわざ上書きする手間がごっそりと省ける事になりました。globalValCodeメソッドは忘れて良い存在となりました(^-^)。registUserValsメソッドは、残念ながら必要です。テクスチャサンプリング関数の置き換えを行うsamplerFunctionsCodeメソッドもコンバータの仕事に次です。テクスチャサンプリング関数の置換えを行うsamplerFunctionsCodeメソッドも冗長です。specularSampler);}これ、ShaderCreator::sanplerFuncOutというヘルパーメソッドを呼び出して置き換え関数の書き出しを簡略化しているのですが、それでも上のコードでは2回呼び出ししています。2行のコードで異なっているのはサンプラ変数だけです。このサンプラはすでにregistUserValsメソッド内でコンバータに登録してあります。texInfo);}こうすれば、先ほどと同じように処理をShaderCreator親クラスで担えますので、派生クラスで上書き実装する必要が無くなります。頂点シェーダ入力セマンティクスの欠落時初期化はコンストラクタで設定しちゃおうまだまだ改良点は尽きません。シェーダを適用する予定のモデルが、シェーダが要求する頂点シェーダの入力セマンティクスを持っていなかった場合、この自動生成システムでは代わりに初期値を与えて対処しています。"\n";}やっぱりこれも似たようなコードを書きますね。いう重複はなるべく避けるのが得策です。上の初期値は各セマンティクス毎に与えるもので、その入力セマンティクスはVertexInputSemanticsStructクラスで扱っていました。このクラスは派生ShaderCreatorクラスのコンストラクタで初期化しています。その初期化時に上の初期化値も書き込むようメソッドを追加してしまいましょう。"\n";}という事でこのメソッドももう気にする必要のないメソッドになってしまいました。忘れて良いメソッドが沢山できました。これがリコンストラクタ(リファクタリング)の良さですね。必要なメソッドは3つだけ!以上でリコンストラクタ作業は一応終わりです。上を実現するために実際はあちこち色々変更するのですが、それは流石に冗長な説明になるので割愛します。);};}グローバル変数をコンバータに登録するregistUserVals、頂点シェーダのコードを記述するvertexShaderCode、そしてピクセルシェーダのコードを書くpixelShaderCode。この3つとコンストラクタでの各メンバを形式的に初期化するだけで柔軟なシェーダコードを作成してくれるようになりました。前章の実装に比べて非常にシンプルに生まれ変わりました〜今回のリコンストラクトしたShaderCreatorを用いたサンプルを公開します。内容は前章と同じですが、DSShaderCreatorクラスの宣言や実装が遥かに簡素になっています。し、使える奴に少しずつなってきました。