忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

カスタムエフェクトでモデルを表示しようとするとテクスチャがうまく貼られない問題とその解決法

ここ数日

「カスタムエフェクトでモデルを表示しようとすると明らかにテクスチャが上手く貼られていない」

問題に悩んだので、同じ問題に苦しむ人がこれ以上出ないようにメモしておきます。


テクスチャが上手く貼られない!

まず、事の発端はXna Creators ClubのChaseCameraSampleのモデル(Ship.fbxと、テクスチャShipDiffuse.tga)を、HLSLで新しく書いたカスタムエフェクトで描画してみようとしてみたことでした。
すると、明らかにテクスチャの貼られ方がおかしいのです。

modelRenderedWithBasicEffect.JPGBasicEffectで普通に描画した場合、モデルはこのように表示されます。(ライトなし)

modelRenderedWithCustomEffect.JPGこちらがカスタムエフェクトを適用して描画したモデルです。明らかにコクピットの窓がおかしいですね。窓のテクスチャの代わりにボディのテクスチャが貼られているようです。(でもまあ上のエンジン(?)っぽいところはこっちの方が自然かもしれません)

参考までに、エフェクトファイルとゲームのコードを書いておきます:

SimpleEffect.fx
//グローバル変数(C#側からセット)
float4x4 World;
float4x4 View;
float4x4 Projection;

texture Texture;


//頂点シェーダ
struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture VertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;

	output.Position = mul(input.Position, mul(World, mul(View, Projection)));
	output.TextureCoordinate = input.TextureCoordinate;
	
	return output;
}



//ピクセルシェーダ
sampler DiffuseSampler = sampler_state{
	Texture = (Texture);
};

float4 PixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(DiffuseSampler, textureCoordinate);
}


technique SimpleEffect
{
	pass
	{
		VertexShader = compile vs_1_1 VertexShader();
		PixelShader = compile ps_2_0 PixelShader();
	}
}


C#側はこうです:

MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

public class MyGame : Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Model model;


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
    }


    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            model = content.Load<Model>("Ship");
            ChangeEffectUsedByModel(
                content.Load<Effect>("SimpleEffect")
                );
        }
    }

    void ChangeEffectUsedByModel(Effect replacementEffect)
    {
        Texture2D texture = content.Load<Texture2D>("ShipDiffuse");

        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (ModelMeshPart part in mesh.MeshParts)
            {
                Effect newEffect = replacementEffect.Clone(
                    graphics.GraphicsDevice
                    );

                newEffect.Parameters["Texture"].SetValue(
                    texture
                    );
                part.Effect = newEffect;
            }
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent)
            content.Unload();
    }




    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        Matrix world = Matrix.CreateRotationY(
            MathHelper.ToRadians(-40)
            );

        Matrix view = Matrix.CreateLookAt(
            new Vector3(3000, 1500, 0),
            Vector3.Zero,
            Vector3.Up
            );

        Matrix projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45),
            (float)graphics.GraphicsDevice.Viewport.Width / graphics.GraphicsDevice.Viewport.Height,
            1000, 10000
            );

        DrawModel(world, view, projection);

    }

    void DrawModel(Matrix world, Matrix view, Matrix projection)
    {
        Matrix[] transforms = new Matrix[model.Bones.Count];

        model.CopyAbsoluteBoneTransformsTo(transforms);

        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (Effect effect in mesh.Effects)
            {
                Matrix localWorld = transforms[mesh.ParentBone.Index] * world;

                effect.Parameters["World"].SetValue(localWorld);
                effect.Parameters["View"].SetValue(view);
                effect.Parameters["Projection"].SetValue(projection);
            }

            mesh.Draw();
        }
    }
}


解決法
さて、どこがまずいのでしょうか?
結論から言うと、テクスチャをコンテントパイプラインから新たにロードしたのがまずかったようです。
「Modelに最初から付いているBasicEffectに付いているテクスチャ」をカスタムエフェクトにセットすると、問題は解決したように見えます(「見えます」というのは、BasicEffectとやや違う(窓が黒)ところがあるからです。でも画像としてみて明らかにおかしいということはありません。じっさい、この方法はCreators ClubのサンプルNonPhotoRealisticで使われているものです)

ChangeEffectUsedByModelメソッド
    void ChangeEffectUsedByModel(Effect replacementEffect)
    {
        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (ModelMeshPart part in mesh.MeshParts)
            {
                Effect newEffect = replacementEffect.Clone(
                    graphics.GraphicsDevice
                    );

                newEffect.Parameters["Texture"].SetValue(
                    ((BasicEffect)part.Effect).Texture
                    );
                part.Effect = newEffect;
            }
        }
    }



modelRenderedWithCustomEffectAndTextureFromBasicEffect.JPG

これで窓のテクスチャはまともに表示されます。(でもエンジンが真っ黒って言うのは審美的観点から見てどうなんでしょうね……)

さて、このことから、どうやらこのブログの記事の題名、「カスタムエフェクトで・・・」というのは実はおかしいことがわかりますね。
この問題が発生した原因はカスタムエフェクトを使ったことにあるのではなく、実はテクスチャにあるのです。
したがって「カスタムエフェクト」うんぬんは題名にあってはいけないのかもしれませんね。
でもこの問題が発生するのはたいていカスタムエフェクトを使った時でしょうから、「まぁいいんじゃないのかなぁ」と思ったり思わなかったりです。


余談(?)

この問題を解決しようとして行った実験を参考までに書いておきます。
解決の過程というのはおもしろいですからね!


まず、この問題にぶち当たってかなり早い段階でしたことは、シェーダーをBasicEffectにして描画してみることです。
つまり、コンテントパイプラインからロードするのではなく、新たにBasicEffectを生成して、モデルに適用してみたのです。
この結果によって、この問題の原因がカスタムエフェクトのHLSLのコードにあるのかC#のコードにあるのかがはっきりします。

すると、テクスチャが上手く貼られません!
同じように、コクピットの
このことからこの問題の原因がHLSLではなく、C#のコードにあることは明白です(たぶん)。

おそらくカスタムエフェクトをモデルに適用する際になにかまずいことをしてしまったのでしょうね。

NonPhotoRealisticサンプルのC#のコードを持ってきて、モデル描画に最低限必要なところ以外を消していけば、問題点が浮き彫りになるはずです!



そんな感じでテクスチャのセットに問題があることがわかったのですが、その後HLSLでカスタムエフェクトを書き直しているときに、別の問題がでてきたのです。

それは、窓の色です。

modelRenderedWithPS_1_1.JPG

窓の色が黒ではなく白になっています。
さて、サンプルと照らし合わせてみると、こんどは問題はHLSL側にあることがわかりました。
どうやらピクセルシェーダのバージョンが(2.0ではなく)1.1だと、このように窓が白になってしまうらしいのです。

教訓として、テクスチャの色がおかしい時にはピクセルシェーダのバージョンをチェックすべきだということなのでしょうね




さらに、テクスチャについても調べてみました。
窓にうまくテクスチャが貼られるかどうかはテクスチャのインスタンスが関係しているようです。(もしかしたら違うかもしれませんが、話が進みませんからね)
具体的にどのようなテクスチャなら上手くいくのでしょうか?

これを調べるために、上手くいくテクスチャ(BasicEffectから取り出したテクスチャ)と上手くいかないテクスチャ(コンテントパイプラインから直接ロードしたテクスチャ)のプロパティを比較してみました:
  上手く表示されるテクスチャ 上手く表示されないテクスチャ
Width 256 256
Height 256 256
ResourceManagementMode Automatic Automatic
ResourceUsage None None
Format Dxt5 Color
LevelCount 9 1
LevelOfDetail 0 0
IsDisposed False False
Tag
Name
ResourceType Texture2D Texture2D
Priority 0 0

あやしいのはFormatプロパティとLevelCountプロパティですね……。
上手く表示されるテクスチャと上手くいかないテクスチャでこの2つのプロパティの値が違うということは、この2つのプロパティのうちどちらか、または両方が窓の表示がおかしくなる原因であると考えたくなります。(残念ながらこの2つのプロパティには両方ともGetしかないので、検証しようがありませんが…)

でももしかしたらプロパティとは全く関係ないところに原因があるかもしれないので、油断は禁物です。
(FormatとLevelCountが何でテクスチャ座標に関係あるのかはわかりませんしね)

正直に言うと、この問題の原因は厳密にはまだよくわからないので、今のところはとりあえずBasicEffectからテクスチャを持ってこようといったところです。
(でもSpaceWarの宇宙船はテクスチャを自由に変えることができましたよね……あれはテクスチャをコンテントパイプラインからロードしているはず……あれ?)

拍手[0回]

PR