このチュートリアルは3DCGを想定しておきながら、前回までは2Dと大差ありませんでした。
話を簡単にするために三角形をそのままの大きさで描いていたのです。
これではいけません。
近くにあるものは大きく、遠くにあるものは小さく表示されるのが自然です。
今回はそのように遠近感を出してみましょう。
遠くにあるものは小さく、近くにあるものは大きく。
あとついでに回転させてみましょう。
マトリクス
今回やることは、頂点データを変更するという意味で前回と同じです。
前回はfloatの変数を用意して、その値の分だけX軸方向に三角形を平行移動しました。
今回が前回と違うのは、頂点データの変換が複雑だということです。
今回は回転と遠近法するデータの変換です。
「こうするとたくさん変数が必要そうだし、計算もなんだかめんどくさくなりそうだ」と思われるかもしれません。
しかし実際は簡単です。
変数は1つでいいですし、ベクトルの変換も1行で住みます。
マトリクスという、ベクトルデータの変換方法を示した変数を使うのです。
マトリクスはC#側では構造体で、16個のフィールドを持ち、回転だとか平行移動だとか遠近法だとかいった変換を全て司ります。
おもしろいのは、「回転してから平行移動して遠近法を適用したい」という複数の変換も1つのマトリクスだけですんでしまうということです。
16個のフィールドがその変換を全部表現してくれるのです。
ですから、今回HLSL側に必要な変数はmatrix型の変数1つだけです。
変数の数は前回から増えません。
マトリクス様様です。
マトリクスの生成
マトリクスはベクトルの変換方法を表すデータです。
C#ではSlimDX.Matrix構造体を使います。
public struct Matrix : IEquatable<Matrix>
一口にベクトルの変換と言っても様々です。
回転、平行移動、遠近法…
それぞれに、対応するマトリクスを生成するためのファクトリメソッドが付いています。
今回使うのは2つです。
物体をある方向から見たときに、物体の相対的な座標がどう見えるかは、Matrix.LookAtRHメソッドを使います。 このメソッドはカメラから見たものの見え方にするようなマトリックスを作り出します。
public static Matrix LookAtRH(Vector3 eye, Vector3 target, Vector3 up);
eyeはカメラの位置を表します。
targetはカメラの向いている対象の位置です。target - eyeで、カメラの向いている方向になります。
upはカメラの上方向です。これは普通y方向、new Vector3(0, 1, 0)です。これをnew Vector3(0, -1, 0)にすると画面は上下ひっくり返ります。
プレイヤーが迷路の中をすすんでいくとき、引数eyeを変えていくことになるでしょう。
プレイヤーがその場であたりを見まわし回転したら引数targetの値が変わります。
遠近法を司るマトリクスはMatrix.PerspectiveFovRHメソッドによって作り出します。
public static Matrix PerspectiveFovRH(float fov, float aspect, float znear, float zfar);
fovはfieldOfViewの略です。縦の視野角を表します。
aspectはアスペクト比です。つまり横幅 / 縦幅です。
znearは画面のz座標。
zfarは最も遠い地点のz座標です。
こうして作ったマトリクスは、*演算子で合成することができます。
A * Bは「ベクトルをAで変換してからBで変換せよ」というマトリクスを新たに作り出します。
こうして合成しても必要なメモリは全く変わらないというのだから驚きです。
public static Matrix operator *(Matrix left, Matrix right);
leftははじめにベクトルを変換するマトリクス。
rightは次にベクトルを変換するマトリクスです。
マトリクスのセット
C#側からHLSLの変数にマトリクスをセットするには、EffectMatrixVariable.SetMatrix()メソッドを使います。
public Result SetMatrix(Matrix matrix);
matrixはセットするマトリクスです。
マトリクスでベクトルを変換する(HLSL)
今回、HLSL側のコードにはマトリクス(matrix)型の変数がひとつあります。
この変数を使って、頂点位置情報を変換します。
変換にはmul関数を使います。
float4 mul(float4 position, matrix transform)
positionは変換前の位置ベクトル。
transformはベクトルの変換方法を表すマトリクスです。
コード
Program.cs
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;
class Program
{
static void Main()
{
using (Game game = new MyGame())
{
game.Run();
}
}
}
class MyGame : Game
{
Effect effect;
InputLayout vertexLayout;
Buffer vertexBuffer;
protected override void Draw()
{
GraphicsDevice.ImmediateContext.ClearRenderTargetView(
RenderTarget,
new SlimDX.Color4(1, 0.39f, 0.58f, 0.93f)
);
updateCamera();
initTriangleInputAssembler();
drawTriangle();
SwapChain.Present(0, PresentFlags.None);
}
private void updateCamera()
{
double time = System.Environment.TickCount / 500d;
Matrix view = Matrix.LookAtRH(
new Vector3((float)System.Math.Cos(time), 0, (float)System.Math.Sin(time)),
new Vector3(),
new Vector3(0, 1, 0)
);
Matrix projection = Matrix.PerspectiveFovRH(
(float)System.Math.PI / 2,
ClientSize.Width / ClientSize.Height,
0.1f, 1000
);
effect.GetVariableByName("ViewProjection")
.AsMatrix().SetMatrix(view * projection);
}
private void drawTriangle()
{
effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
GraphicsDevice.ImmediateContext.Draw(3, 0);
}
private void initTriangleInputAssembler()
{
GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
0,
new VertexBufferBinding(vertexBuffer, VertexPositionColor.SizeInBytes, 0)
);
GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
= PrimitiveTopology.TriangleList;
}
protected override void LoadContent()
{
initEffect();
initVertexLayout();
initVertexBuffer();
}
private void initEffect()
{
using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
"myEffect.fx", "fx_5_0",
ShaderFlags.None,
EffectFlags.None
))
{
effect = new Effect(GraphicsDevice, shaderBytecode);
}
}
private void initVertexLayout()
{
vertexLayout = new InputLayout(
GraphicsDevice,
effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
VertexPositionColor.VertexElements
);
}
private void initVertexBuffer()
{
vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
GraphicsDevice,
new[] {
new VertexPositionColor
{
Position = new Vector3(0, 0.5f, 0),
Color = new Vector3(1, 1, 1)
},
new VertexPositionColor
{
Position = new Vector3(0.5f, 0, 0),
Color = new Vector3(0, 0, 1)
},
new VertexPositionColor
{
Position = new Vector3(-0.5f, 0, 0),
Color = new Vector3(1, 0, 0)
},
});
}
protected override void UnloadContent()
{
effect.Dispose();
vertexLayout.Dispose();
vertexBuffer.Dispose();
}
}
struct VertexPositionColor
{
public Vector3 Position;
public Vector3 Color;
public static readonly InputElement[] VertexElements = new[]
{
new InputElement
{
SemanticName = "SV_Position",
Format = Format.R32G32B32_Float
},
new InputElement
{
SemanticName = "COLOR",
Format = Format.R32G32B32_Float,
AlignedByteOffset = InputElement.AppendAligned//自動的にオフセット決定
}
};
public static int SizeInBytes
{
get
{
return System.Runtime.InteropServices.
Marshal.SizeOf(typeof(VertexPositionColor));
}
}
}
class Game : System.Windows.Forms.Form
{
public SlimDX.Direct3D11.Device GraphicsDevice;
public SwapChain SwapChain;
public RenderTargetView RenderTarget;
public void Run()
{
initDevice();
SlimDX.Windows.MessagePump.Run(this, Draw);
disposeDevice();
}
private void initDevice()
{
MyDirectXHelper.CreateDeviceAndSwapChain(
this, out GraphicsDevice, out SwapChain
);
initRenderTarget();
initViewport();
LoadContent();
}
private void initRenderTarget()
{
using (Texture2D backBuffer
= SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
)
{
RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
}
}
private void initViewport()
{
GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
new Viewport
{
Width = ClientSize.Width,
Height = ClientSize.Height,
}
);
}
private void disposeDevice()
{
UnloadContent();
RenderTarget.Dispose();
GraphicsDevice.Dispose();
SwapChain.Dispose();
}
protected virtual void Draw() { }
protected virtual void LoadContent() { }
protected virtual void UnloadContent() { }
}
class MyDirectXHelper
{
public static void CreateDeviceAndSwapChain(
System.Windows.Forms.Form form,
out SlimDX.Direct3D11.Device device,
out SlimDX.DXGI.SwapChain swapChain
)
{
SlimDX.Direct3D11.Device.CreateWithSwapChain(
DriverType.Hardware,
DeviceCreationFlags.None,
new SwapChainDescription
{
BufferCount = 1,
OutputHandle = form.Handle,
IsWindowed = true,
SampleDescription = new SampleDescription
{
Count = 1,
Quality = 0
},
ModeDescription = new ModeDescription
{
Width = form.ClientSize.Width,
Height = form.ClientSize.Height,
RefreshRate = new SlimDX.Rational(60, 1),
Format = Format.R8G8B8A8_UNorm
},
Usage = Usage.RenderTargetOutput
},
out device,
out swapChain
);
}
public static Buffer CreateVertexBuffer(
SlimDX.Direct3D11.Device graphicsDevice,
System.Array vertices
)
{
using (SlimDX.DataStream vertexStream
= new SlimDX.DataStream(vertices, true, true))
{
return new Buffer(
graphicsDevice,
vertexStream,
new BufferDescription
{
SizeInBytes= (int)vertexStream.Length,
BindFlags = BindFlags.VertexBuffer,
}
);
}
}
}
myEffect.fx
matrix ViewProjection;
struct VertexPositionColor
{
float4 Position : SV_Position;
float4 Color : COLOR;
};
VertexPositionColor MyVertexShader(VertexPositionColor input)
{
VertexPositionColor output = input;
output.Position = mul(output.Position, ViewProjection);
return output;
}
float4 MyPixelShader(VertexPositionColor input) : SV_Target
{
return input.Color;
}
technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}
このプログラムは回転する三角形を描きます。 ただ正確に言うと、回転しているのは三角形の方ではなくてカメラなのですが。