忍者ブログ

Memeplexes

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

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


XNA爆発

Xna Creators Clubのサンプル、Particle 3D Sampleをいじっていたら
障害(?)っぽいものにぶち当たったので問題とその解決、
事の顛末を書いておきます。

Particle 3D Sampleというのは爆発や煙、炎などを
ポイントスプライトで描画するサンプルなのですが、
どうもカメラの距離によっておかしくなっているように感じられたのです。

Particle 3D Sampleのクラスを取り出して
別のプロジェクトで遊んでいたのですが、
カメラを爆発に近づけすぎるとどうやら変になるようなのです。

まず、カメラを爆発から150離れた位置におくと
次のようにきちんと爆発が起こっているように見えます:

explosion150.jpgカメラからの距離:150

これは爆発です。
これは静止画なのでよくわかりませんが、実際にサンプルを実行すると
爆炎がまわりにゆっくりと広がっていくのがわかります。
ここまでは問題ありません。

しかし、カメラをこの爆発に近づけるとどうもおかしなことになるようです。
次の画像はカメラを先ほどの1/10の距離、つまり15離れた位置に
動かしたときの画像です:

explosion15.jpgカメラからの距離:15

ウィンドウにはいくつもの炎のようなものが映っています。
これは別に複数の爆発を起こしたと言うわけではありません。
ここで映っている爆発は1つだけです。

どうやら爆炎がバラバラになって表示されているようです。
爆発を構成している一つ一つの爆炎が本来よりも小さく表示されているのでしょう。
実際、この一つ一つの爆炎はサンプルで使われている
"explosion.png"と同じものです。

ebd0a9be.png
(explosion.png : Particle 3DのContentフォルダ内より)

つまりここでの問題は
爆発に近づいたときに一つ一つの爆炎のポイントスプライトが拡大されない
ことにあるようです。

そのため、遠くから表示するとうまくいっているように見える一方で、
近づいたら、各々の炎が拡大されないために、本来そうであるべきよりも炎が小さく見え、
バラバラになっているように見えるのでしょう。

ここで考えられるのは
もとのサンプルからクラスを取り出すときに何か間違ったことをしてしまったのではないか
ということです。
しかしどうやら元のサンプルでも同じような現象が起きているようなのです。

3DParticlesExplosion.jpg
Particle 3Dで爆発に近づいた場合。やはり爆発がバラバラになっています。
(※見やすくするために爆発の周りの煙は表示していません。)

ちなみに、サンプルでも爆発を遠くから見ると問題なく表示されます。

3DParticlesExplosion2.jpg


ということはこの問題はサンプルの問題だったのでしょうか?
サンプルがポイントのサイズを固定していたのでしょうか?
HLSLのソースコードを読む限りそうではありませんでした。
きちんと、カメラから離れればサイズが小さく、近づけばサイズが大きくなるように
書かれていました。

これはプログラムの実行によっても検証できます。
カメラを爆発からうんと離してやるのです。
もし各ポイントのサイズが固定されているのなら
どんなに離してもそれ以上爆発が小さくならないような
カメラの距離が存在するはずです。
爆発がexplosion.pngの爆炎より小さくなることはありえないはずです。

smallExplosionCamera1500.jpg

小さくなっていますね・・・。
ということはやはり、各ポイントのサイズはカメラの距離によって変わっているようです。

それではどうして近づきすぎるとおかしくなるのでしょう?
これではまるでポイントのサイズの上限が存在しているようではありませんか!
・・・・・・ポイントのサイズの上限?
そういえばPointSizeMaxというプロパティがあったような気がします。
もしかしたらそれが関係しているのかも!


調べてみるとParticleSystemクラスに次のような行が見つかりました:

renderState.PointSizeMax = 256;

とりあえずこれをコメントアウトして実行してみました。
すると、なんとうまく描画されたのです。

bigExplosion.jpgカメラからの距離:15

よし!

問題は解決です。
解決法は
"renderState.PointSizeMax = 256";をコメントアウトする
です。

・・・しかしちょっと待ってください。
このステートメントはいったい何をしていたのでしょうか?
まさかサンプルを実行する人を困らせるためにこんなことをしたわけではないでしょう。
なにか理由があるはずです。

とりあえずこのプロパティを調べてみることにしました。
msdnによると、これはポイントのサイズの上限を表しているのだそうです。
(「そんなものは最初からわかってる!」という声が聞こえてきそうです。)
さらに、デフォルトの値は64.0fなのだそうです。

なるほど、ポイントのサイズはデフォルトでは64が上限と言うことですね。
読めてきました。
ということは、"renderState.PointSizeMax = 256;"というのは
ポイントのサイズの上限を大きくして、より大きなポイントも表示できるようにした
ということなのでしょう。

しかし、そうだとすると新たな疑問が浮かびます。
なぜ"renderState.PointSizeMax = 256;" をコメントアウトして上手くいったのでしょうか?
ポイントサイズの上限を大きくするステートメントをコメントアウトしたのですから、症状は悪化するはずです。
これではアベコベではありませんか!

もともと、ここでの問題は「ポイントのサイズがもっと大きくならない」
というものでした。
そこで「より大きなポイントも表示できるようにする」ステートメントをコメントアウトしたのなら
問題はますますひどくなるはずです。
しかし実際にはこれで問題が解決したのです。

ひとつの可能性として、「実はデフォルトの値は64.0fではなくもっと大きい値だった
ということが考えられます。
たとえばデフォルトの値が実は512.0fくらいで、"renderState.PointSizeMax = 256;"が
ポイントサイズの上限を引き下げていたのかもしれません。

さっそくかんたんなステートメントを追加して調べてみました。
LoadGraphicsContentメソッド内に次の行を追加したのです。

Window.Title += "maxdefault:" + graphics.GraphicsDevice.RenderState.PointSize;

これでウィンドウのタイトルにPointSizeプロパティのデフォルトの値が表示されるはずです。
結果は"maxdefault:8192"でした。
デフォルトの値は8192です。
・・・あれ、何でこんなに大きいんだろ・・・・・・?
びっくりです。
予想では512、大きくてもせいぜい1024くらいだと思っていたのですがそれを大きく上回りました。
ぼくがこのとき使ったディスプレイの解像度は1920 x 1200ですから上限など無いも同然です。
ディスプレイの4倍以上の大きさのポイントを描画できるわけですからね。
これでは "renderState.PointSizeMax = 256;"なんてしたらおかしくなるのもうなずけます。

気にかかるのはこの値がmsdnの記述と大きくかけ離れている事実です。
8192と64では128倍の差があるではありませんか!
どう考えてもおかしいです。

この理由はわかりませんが、ぼくは次のように考えました:

msdnのPointSizeMaxプロパティのページをさらに見ると、
このプロパティはGraphicsDeviceCapabilities.PointSizeMaxの値を
超えられないとも書いてあります。
グラフィックスデバイスには表示できるポイントの大きさに限界があって、
それをこのプロパティが表しているのでしょう。

ということは、もしかするとRenderState.PointSizeMaxは
グラフィックスデバイスの能力を調べて、自動的に上限を
最大の値にまで引き上げてくれるのかもしれません。

おそらくこのパソコンのグラフィックスデバイスのポイントサイズの上限が8192だったのでしょう。(後で調べてみると、実際そうでした)
それをRenderState.PointSizeMaxは読み取って、
自動的に最大限の機能を発揮できるようにしてくれたのかもしれないというわけです。
別のパソコンならRenderState.PointSizeMaxからはまた違った値が返されるでしょう。

蛇足

msdnのサンプルを参考にして、グラフィックスデバイスのポイントサイズの上限を調べるのに使ったソースコードを書いておきます。
            foreach (GraphicsAdapter adapter in GraphicsAdapter.Adapters)
            {
                GraphicsDeviceCapabilities caps = adapter.GetCapabilities(DeviceType.Hardware);
                Window.Title += "max:" + caps.MaxPointSize;
            }

グラフィックスデバイスの能力を調べるには
Microsoft.Xna.Framework.Graphics.GraphicsDeviceCapabilitiesクラスを使います。

[Serializable]
public sealed class GraphicsDeviceCapabilities : IDisposable


このクラスはポイントサイズの上限のほかにも、使用できるテクスチャの大きさの上限や、
頂点のインデックスの上限も調べることが出来ます。

ポイントサイズの上限を表すプロパティは、MaxPointSizeです。

public float MaxPointSize { get; }

このクラスのインスタンスを得るには、GraphicsAdapter.GetCapabilitiesメソッドを使います。

public GraphicsDeviceCapabilities GetCapabilities ( 
        DeviceType deviceType
)


このメソッドは、引数のdeviceTypeに指定された種類の
デバイスの能力を表すGraphicsDeviceCapabilitiesのインスタンスを返します。

DeviceTypeは列挙型で、3つのメンバ、つまりHardware, NullReference, Referenceを持ちます。
ここではHardwareを使いました。

このメソッドを持っているのはグラフィックスアダプタを表す、
Microsoft.Xna.Framework.Graphics.GraphicsAdapterクラスです。(そのまんまですが)

このクラスのインスタンスを得るには、コンストラクタではなく
自分のクラスのAdapters静的プロパティを使います。

public static ReadOnlyCollection<GraphicsAdapter> Adapters { get; }

このプロパティの中にシステムで使うことの出来る全てのグラフィックス・アダプタが入っています。
foreachなんかでまわして使うと良いでしょう。

拍手[0回]

PR

XNA火の玉

XNA Creators Clubのサンプルを参考にして火の玉を作ってみました。
これはポイントスプライトを使っていて、
各頂点(?)にサンプルから拝借したfire.pngを貼り付けています。

fireball1.jpg

おもうに、公式のサンプルは長すぎて難しいですね。

Fireball.zip(ソースコード)

拍手[0回]


かんたん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回]


XNA Game Studio 2.0

XNA Game Studio 2.0の予告の解説みたいなのはひにけにXNAさんででているようなので、
ここではリンクを貼るだけにします。(全部英語ですが)

Microsoft XNA Developer Presentations(英語)

Xnaに関する動画があります。
英語ですが、時々デモがあって、雰囲気を味わうことが出来るかもしれません。
ここに、Xna Game Studio 2.0の解説みたいなのがあります。

Gamefest 2007: What's New in XNA Game Studio 2.0

Xna Game Studio 2.0で新しいこと

Networking with the XNA Framework

2.0で新しく出るネットワーク機能のデモみたいです。

拍手[0回]


かんたんXNA HLSL編 その6 マトリックス

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

ここまではHLSLで面白い効果を出すと言っておきながら、
見た目は3Dではなく2Dでした。
頂点シェーダに流し込む頂点データが変換されずにそのまま返されていたからです。
この点で、Projectionプロパティでモデルを立体的に見せることのできるBasicEffectより劣っています。
この差を埋めることにしましょう。

頂点の座標データを変換するにはマトリックスを使います。
ちょうどBasicEffectのViewプロパティやProjectionプロパティのようにです。
グローバル変数としてマトリックスを宣言し、それをC#側から操作すればいいのです。
HLSL側でマトリックスを宣言するには、次のようにします:

[型][変換前のベクトルの次元数]x[変換後のベクトルの次元数] [変数名];

普通は4次元ベクトル→4次元ベクトルの変換なのでfloat4x4です。
(3DCGの話をしているのになぜ4次元が出てくるのか不思議に思えるかもしれませんが、これはマトリックスによる変換の柔軟性を大きくするためです。3次元ベクトルではなく4次元ベクトルの変換ということにしたほうが、よりいろんな種類の変換が出来るのです。そこで、位置ベクトルなんかは3次元でなくて4次元ベクトルとして扱います。その4次元ベクトルの一番最後の成分には位置の情報ではなく1が入っていますが(ジョークではありませんよ)、これは数学的な計算のつじつまを合わせるためで、特に意味はありません。かといって一番最後の成分に全く意味がないと言うわけでもなく、この値が1でないとマトリックスで変換したときの結果が変になってしまいます。)

float4x4 transform;

ただし、『プログラミングDirectX9グラフィックスパイプライン』なんかをみると、
4次元ベクトル→3次元ベクトルの変換を表すfloat4x3や
3次元ベクトル→3次元ベクトルの変換を表すfloat3x3も使われるようです。
きっとこれはそれほど柔軟性が必要ないからでしょう。(多分)

HLSL側でグローバル変数として宣言したマトリックスはたいていC#側で値がセットされるので
初期化についてはあんまり気にする必要はないでしょう。

このグローバル変数を使って頂点シェーダに引数として入ってくる位置ベクトルを変換していくわけですが、
それにはmul関数を使います。

mul ( vector, matrix )

vectorは変換前のベクトル、matrixは変換を表すマトリックスです。
戻り値は変換後のベクトルです。

mulという名前から想像できるように、これは掛け算を意味する関数です。
(※マトリックスによるベクトルデータの変換は掛け算として表されるのです。)
そのため、mul関数の引数は実はスカラーでもベクトルでも何でもかまいません

mul(2, 3);    // == 6
mul(2, float3(1, 0, 0));    // == float3(2, 0, 0)

もちろん、マトリックス同士のかけ算をすることも出来ます。
その場合は、2つのマトリックスの変換を同時に行うマトリックスを返します。

mul(view, projection);    // viewとprojectionの変換をいっぺんにやってしまうマトリックス。



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;
    VertexPositionColor[] vertices = {
            new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
            new VertexPositionColor(new Vector3(1, 0, 0), Color.Blue),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red)
        };


    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(2, 0, 3), 
                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["Transform"].SetValue(
                view * projection
                );

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.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<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 Transform;

struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	VertexPositionColor output = input;
	output.Position = mul(input.Position, Transform);
	return output;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	return color;
}

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

hlslTransformed.jpg
斜めから見た図です。
きちんと三角形が遠近法の効果でゆがんでいるのがわかると思います。
これでようやくBasicEffectに一歩近づきました。

BasicEffectと違ってViewやProjectionを分けていません。
この2つのマトリックスを一緒にしてTransformという名前のグローバル変数にしています。
C#側では別々に作ってはいますが、結局掛け合せてエフェクトにセットしています。
こういうのもアリと言うことです。

もちろん、ViewとProjectionを分けることも出来ます。

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;
    VertexPositionColor[] vertices = {
            new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
            new VertexPositionColor(new Vector3(1, 0, 0), Color.Blue),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red)
        };


    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(2, 0, 3), 
                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);

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.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<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 View;
float4x4 Projection;

struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

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

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	return color;
}

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

実行結果はさっきと同じです。
さっきと違うのは、マトリックスを2つに分けていることです。
このようにマトリックスはいくらでも増やすことが出来ます。
BasicEffectでは後ひとつ、Worldマトリックスがありました。

拍手[0回]