忍者ブログ

Memeplexes

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

かんたんXNA HLSL編 その7 テクスチャ

このページは古いです
最新版はこちら

今回はテクスチャです。
ポリゴンの上に貼り付けるテクスチャを扱います。
つまりBasicEffectで言うとTextureプロパティ関連でやっていたことです。
ただしはるかに柔軟なことが出来ます。


大まかな流れを言うと、

「まずHLSL側でtexture型のグローバル変数を宣言し、
C#側からそれにインスタンスをセットし、
ピクセルシェーダでそのテクスチャから
相当するテクスチャ座標の色をサンプルして出力する」、

といったぐあいです。


これをやると「ピクセルの色は自分が完全に決めているんだなぁ」という実感が持てると思います。
ちょこっとプログラムを書き換えると水面の波のような効果を出すことも出来ます。


HLSLでテクスチャを宣言するにはこうします:

texture [変数名];

このテクスチャのテクセル(テクスチャのピクセル)の色を、
ピクセルシェーダで使います。
つまり、ピクセルシェーダの引数として与えられるテクスチャ座標に
対応する部分のテクスチャの色をtex2D関数を使って得て、戻り値として返すのです。

float4 tex2D ( sampler samplerState, float2 textureCoordinate )

textureCoordinateはテクスチャ上の座標を意味します。ここに指定した座標の色が返されます。
この関数の戻り値は色を表すfloat4です。
こいつをピクセルシェーダに使うことになるでしょう。

ただし、最初の引数samplerというのを見てわかるように、
初学者にとっては困ったことですが、このままではtex2D関数を使うことが出来ません。
テクスチャからどのように色を取り出す(サンプルする)かを
表すサンプラを宣言しなければならないのです。
このサンプラしだいで表示されるテクスチャがギザギザになったり滑らかになったりします。

しかしまあ、最初は一番シンプルな例を見るべきでしょう。
ギザギザにはなってしまいますが。

sampler [変数名] = sampler_state
{
        Texture = <テクスチャの変数名>;
};

「テクスチャの変数名」というのは、このサンプラが使うテクスチャです。
(HLSLの将来のバージョンではこれを設定する必要はなく、
サンプラとテクスチャは(正当にも)独立して扱えるようですが、
現時点では必ずテクスチャを指定してやらねばなりません。)

テクスチャの変数名は < > で囲ってやらなければなりません。(でも試したところ ( ) でもいけました。)
単に変数名を書いただけではコンパイルエラーになります。
(まあ、初期化が行われるときには変数にインスタンスは入っていないでしょうから正当です。
C#側からセットして初めてテクスチャの変数がつかえるようになるわけですからね。)

もしかしたら < > にはC/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;

    Effect effect;

    VertexPositionTexture[] vertices = {
        new VertexPositionTexture(new Vector3(0, 1, 0), new Vector2(0.5f, 0)),
        new VertexPositionTexture(new Vector3(1, 0, 0), new Vector2(1, 1)),
        new VertexPositionTexture(new Vector3(-1, 0, 0), new Vector2(0, 1))
    };


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

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");
            Matrix view = Matrix.CreateLookAt(
                new Vector3(1, 0, 1), 
                new Vector3(),
                new Vector3(0, 1, 0)
                );
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(90),
                (float)Window.ClientBounds.Width/Window.ClientBounds.Height,
                0.1f, 100
                );
            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["MyTexture"].SetValue(content.Load<Texture2D>("Xnalogo"));

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionTexture.VertexElements
                );
        }
    }

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

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

        effect.Begin();

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Begin();

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

hlslTesture.jpg(公式のXnalogo.pngを拝借してきました。クリックで拡大)
テクスチャがちゃんと張り付いていますね。

しかし、よくみると、テクスチャがギザギザしています。
一番近い"a"のところなんか荒すぎてすりガラスのようです。
手前はテクスチャが拡大されるので、そのように見えるのです。
これを治し滑らかにするには、サンプラでフィルターを指定してやります。

使うフィルターはMagFilter (Magnification filter)、
テクスチャが拡大されるときに使われえるフィルターです。
これをLinearにセットすると、拡大したとき滑らかに表示されます。
フィルターにセットできる値は、C#でのTextureFilter列挙型に対応しているようです。

定数名 解説
None  「MipMapは無効になり、ラスタライザはかわりにMagFilterを使います。」だそうです。
Point  滑らかになりません。一番近いテクセルの色が使われます。ギザギザです。
Linear  バイリニアフィルターです。周りの2x2の領域の中での平均値が使われます。いろんなサンプルを見たところ、これが一番使われているようですね。
Anisotropic  異方性フィルターです。
PyramidalQuad  4サンプル・テントフィルターです。
GaussianQuad  4サンプル・ガウシアンフィルターです。画像をぼかすことで滑らかにするそうです。

とりあえずLinearを使えば良いようです。
ほとんどのサンプルがLinearをフィルターとして使っています。

float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
	MagFilter = Linear;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

magLinear.jpg
近くの文字、"a"が滑らかになりました。

しかしやっぱり、よく見ると変です。
確かに近くの文字は滑らかになりましたが、遠くの文字はまだ荒れています。
"x"の下の部分がなんだかギザギザしていますね。

遠くの文字は遠近法によって縮小されるからです。
MagFilterをセットすることによってテクスチャを拡大したときには滑らかになったものの、
テクスチャを逆に縮小したときにはまだギザギザしてしまうのです。

テクスチャが縮小されるときに使われるフィルターはMinFilter (Minification Filter)です。
これにLinearをセットすると、テクスチャが縮小されたときも滑らかになります。
float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
	MagFilter = Linear;
	MinFilter = Linear;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

minMagFilter.jpg
xの字もなめらかになりました。

拍手[0回]

PR