カスタムエフェクト

フォーラムの質問を見て、今まで書いていなかったことに気づいたので遅ればせながらカスタムエフェクトの使い方を説明します。

例によって例の如く、コピー元はShawn Hargreaves氏の投稿からです。

独自に作ったエフェクトを使うには2つの方法があります。ひとつはコンテント・パイプラインが処理したエフェクトをゲーム実行時に切り替える方法。そしてふたつ目はカスタムプロセッサを書くことで、コンテントパイプライン内でコンテントビルド時に独自のエフェクトに切り替える方法です。

後者の方法の方が自由度が高く、ゲーム実行時に余計な処理をしなくて済むようになるので、ここではこの方法を紹介します。

まず最初にModelProcessorから派生したプロセッサーを作り、ConvertMaterialメソッドをオーバーライドします。

 /// <summary>
/// ModelProcessorから派生したプロセッサー
/// </summary>
[ContentProcessor(DisplayName = "MyModelProcessor")]
public class MyModelProcessor : ModelProcessor
{
    // ConvertMaterialをオーバーライドして、マテリアル変換時に自分のマテリアルに差し替える
     protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
    {
       // エフェクトを含むことのできるEffectMaterialContentを使う
        EffectMaterialContent myMaterial = new EffectMaterialContent();

       // 使いたいエフェクトファイルを外部参照として指定する
        string effectPath = Path.GetFullPath("MyEffect.fx");
       myMaterial.Effect = new ExternalReference<EffectContent>(effectPath);

       // 引数で渡されたmaterialの代わりに、ここで作ったmyMaterialを渡す
        return base.ConvertMaterial(myMaterial, context);
    }
}

ここで指定しているMyEffect.fxファイルは直接読み込んでいるので、Visual Studioのプロジェクトに追加する必要はありません。また、複数のモデルやメッシュから参照されている場合でもこのマテリアルの実体は一度しか作られません。

このサンプルコードでは直接ファイル名を指定していますが、渡されたマテリアル名(material.Name)や、material.OpaqueDataからファイル名を取得したり、ProcessメソッドをオーバーライドしてNodeContentの情報を保持しておいて、以下のコードのようにしてモデルファイルからの相対パスでファイル名を指定することもできます。

 string directory = Path.GetDirectoryName(rootNode.Identity.SourceFilename);
string effectPath = Path.Combine(directory, "MyEffect.fx");

これでエフェクトが使えますが、エフェクトにはパラメーターを設定するのが普通です。また、基本的なマテリアル情報は渡されたマテリアルから取得できるので、以下のようなコードを書くことができます。

 if (material is BasicMaterialContent)
{
    BasicMaterialContent basicMaterial = (BasicMaterialContent)material;

    // エフェクトで使うテクスチャを設定する
     myMaterial.Textures.Add("DiffuseTexture", basicMaterial.Texture);

    // ここで任意のエフェクトパラメーターの設定もできる
     myMaterial.OpaqueData.Add("Shininess", basicMaterial.SpecularPower * 10);
    myMaterial.OpaqueData.Add("BumpSize", 42);
}
else if (material is EffectMaterialContent)
{
    EffectMaterialContent effectMaterial = (EffectMaterialContent)material;

    // TODO: 渡されたマテリアルがEffectMaterialContentだった
     // 時の処理をここでする(必要ならば)
}
else
    throw new Exception("なんじゃこりぁ?");

ここで設定したエフェクトやそのパラメーターは実行時に自動的に読み込まれ、設定されます。これで実行時の特別な処理いらずで通常のModelを描画するようにして独自のエフェクトが適用されたモデルを描画することができます。