Silverlight 5(Beta)で Teapot + Blinn-Phong ②
DrawingSurface に描画する Teapot クラスを実装します。Silverlight 5 Beta の XNA には Model とか Mesh のような便利なクラスがないので、自前で頂点バッファとインデックス バッファを作り、DrawIndexedPrimitive で描画しなければなりません。非常にプリミティブな下位レベルの実装になります。
Visual Studio の[プロジェクト]→[クラスの追加]で Teapot.cs クラスを作成します。
XNA 用の名前空間を追加
XNA で使うクラスやメソッド用に Microsoft.Xna.Framework などの名前空間を追加します。
…
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Silverlight;
using System.Windows.Media.Imaging;
頂点バッファとインデックス バッファ用のクラス変数
頂点バッファとインデックス バッファを宣言するとともに、ティーポットのデータは頂点と法線なので、カスタム頂点構造体 VertexPositionNormal を作成します
private VertexBuffer vertexBuffer;
private IndexBuffer indexBuffer;
private int numVertices;
private int numElements;
private struct VertexPositionNormal
{
public Vector3 Position;
public Vector3 Normal;
public VertexPositionNormal
(float positionX, float positionY, float positionZ,
float normalX, float normalY, float normalZ)
{
Position = new Vector3(positionX, positionY, positionZ);
Normal = new Vector3(normalX, normalY, normalZ);
}
public static readonly VertexDeclaration VertexDeclaration =
new VertexDeclaration(
new VertexElement(0, VertexElementFormat.Vector3,
VertexElementUsage.Position, 0),
new VertexElement(12, VertexElementFormat.Vector3,
VertexElementUsage.Normal, 0)
);
シェーダーと行列などのためのクラス変数
Teapot.cs では描画時のシェーダーや行列なども実装するのでそのための変数を定義します。World, View, Project 行列をパブリックにしていますが、今回の実装ではトラックボールが World 行列を変化させるので、World 行列だけをパブリックにしてもよいです。逆にトラックボールで視点や光源位置を変更したければ、これらの変数をパブリックにした方がよいでしょう。
private VertexShader vertexShader;
private PixelShader pixelShader;
private Vector4 eye = new Vector4(0, 0, 5, 1);
private Vector4 light = new Vector4(10, 10, 10, 1);
private Vector4 diffuse = new Vector4(1, 0, 0, 1);
private Vector4 specular = new Vector4(1, 1, 1, 32);
public Matrix World { get; set; }
public Matrix View { get; set; }
public Matrix Projection { get; set; }
コンストラクターの実装
コンストラクターでは、位置と法線とインデックスを配列として定義します。以前 WPF で使ったTeapot.xamlからコピペして、区切りのスペースをカンマに変更し float 型を示すfを追加しました。Direct3D, XNA な人は慣れていると思いますが、ハードウェア 3D グラフィックスでは浮動小数点の型は float を使います。これはもともと GPU が float(32ビット浮動小数点)しかサポートしなかったためと、GPUに送るデータサイズの最適化のためです。
次に位置と法線を前述のVertexPositionNormal配列に格納して頂点バッファを作成します。GraphicsDevice は呼び出し元から引数で渡されることにします。
そしてインデックス配列からインデック スバッファを作成します Silverlight 5 Beta では 16ビットのインデックスしかサポートしないので(だから ushort型)、IndexElementSize.SixteenBits を指定します。
最後は World, View, Projection 行列の初期化と、引数で渡されたシェーダーをプライベート変数に代入します。
public Teapot(GraphicsDevice resourceDevice, VertexShader vs, PixelShader ps)
{
float[] positions = new float[] {…};
float[] normals = new float[] {…};
ushort[] indices = new ushort[] {…};
numVertices = positions.Length / 3;
// Create Vertex Buffer
VertexPositionNormal[] vpn = new VertexPositionNormal[numVertices];
for (int i = 0; i < numVertices; i++)
{
int s = i * 3;
vpn[i] = new VertexPositionNormal(
positions[s + 0], positions[s + 1], positions[s + 2],
normals[s + 0], normals[s + 1], normals[s + 2]);
}
vertexBuffer = new VertexBuffer(resourceDevice,
VertexPositionNormal.VertexDeclaration,
numVertices, BufferUsage.None);
vertexBuffer.SetData(0, vpn, 0, numVertices, 0);
// Create Index Buffer
indexBuffer = new IndexBuffer(resourceDevice,
IndexElementSize.SixteenBits,
indices.Length, BufferUsage.None);
indexBuffer.SetData(0, indices, 0, indices.Length);
numElements = indices.Length / 3;
// Setup Default Matices
this.World = Matrix.Identity;
this.View = Matrix.CreateLookAt(new Vector3(eye.X, eye.Y, eye.Z),
Vector3.Zero, Vector3.Up);
this.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, 1.0f, 1.0f, 10.0f);
// Shaders
pixelShader = ps;
vertexShader = vs;
}
Draw メソッドの実装
Draw メソッドでは、Clearメソッドで前の結果を消して、DrawIndexedPrimitivesメソッドを呼び出して描画します。
しかしその前にデバイスにレンダリングに必要な多くの設定を行う必要があります。まずは、ティーポットのインデックスの回り順(カリング)がデフォルトと逆だったので、レンダーステートでCullClockwiseにしておもて面を時計回りに指定。次に頂点バッファとインデックスバッファ。そして頂点シェーダーとピクセル シェーダー。そのあとは、シェーダーのレジスターに行列や光源位置、視点位置、色などのデータを渡します。SetVertexShaderConstantFloat4 メソッドの第一引数はレジスターの番号で、この番号でシェーダー内の変数と対応させます。
public void Draw(GraphicsDevice device)
{
device.RasterizerState = RasterizerState.CullClockwise;
// Set VertexBuffer and IndexBuffer
device.SetVertexBuffer(vertexBuffer);
device.Indices = indexBuffer;
// Set VertexShader and PixelShader
device.SetVertexShader(vertexShader);
device.SetPixelShader(pixelShader);
// Set Shader Registers
Matrix wvp = this.World * this.View * this.Projection;
device.SetVertexShaderConstantFloat4(0, ref wvp);
device.SetVertexShaderConstantFloat4(4, ref light);
device.SetVertexShaderConstantFloat4(5, ref eye);
device.SetVertexShaderConstantFloat4(6, ref diffuse);
Matrix inverse = Matrix.Invert(this.World);
device.SetVertexShaderConstantFloat4(7, ref inverse);
device.SetPixelShaderConstantFloat4(0, ref specular);
// Finally Draw Indexed Primitives
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,
Color.Transparent, 1.0f, 0);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
numVertices, 0, numElements);
}
次回は Blinn-Phong シェーダーの実装を解説します。ビルド時にシェーダーも同時にコンパイルするよう、シェーダーコンパイラーの Visual Studio への登録も行います。
実装した teapot.cs を添付します。