May 12, 2011

[Silverlight]Silverlight5ベータで3D表示する方法(「関西ゲームプログラミング勉強会」LT内容)

先日、「関西ゲームプログラミング勉強会」というちょっと変わった勉強会に参加しました

*関西ゲームプログラミング勉強会 #oustudy を開催しました
http://developers.oneup-inc.com/2011/05/oustudy2/

*関西ゲームプログラミング勉強会 #oustudy
http://atnd.org/events/14697


*関西ゲームプログラミング勉強会2011/05/07 さおのLT資料

「XNA(Silverlight)で初めてのSilverlight」

<セッション資料>
http://handsout.jp/slide/3916

<Silverlight3D光ちゃんキューブ動作サイト>
http://jyurimaru.info/data/20110507LT/20110507LT.html

<上記のプロジェクトファイル>
http://jyurimaru.info/data/20110507LT/20110507LT.zip

こんな絵です!やったね★Silverlight5(*・ω・)ノ
SL3D_hikaru



これはIE9も日本語版が正式にリリースしたので、Silverlight5Betaでの3D描画を実践してみたやつです
// 開発環境作りは、先の投稿 [Silverlight]Silverlight5ベータで3D描画をする準備(まだ準備中★ を参考にしてください

Silverlight5では新しく3Dハードウェアアクセラレータがサポートされたのですが
3DのAPIという意味では非常に低いレイヤの機能しか提供されていません
XNAの一部が使えるようになった、という印象です
(MatrixやVectorがまだベータ版ではサポートされていないのです)

そこでサンプルを必死に探してみました

*Controllable Textured 3D Cube (Silverlight)
http://code.msdn.microsoft.com/Controllable-Textured-3D-f7b1beab

*上記の動作画面(3Dキューブになってます)
http://samples.msdn.microsoft.com/silverlight/samplebrowser/#/?sref=SilverlightApplication3DTest

以下は、コードギャラリーサンプルを動作させる手順です



* Controllable Textured 3D Cube (Silverlight) をダウンロード

上にもリンクを張っている、キューブが動くサンプルを落としてきます
http://code.msdn.microsoft.com/Controllable-Textured-3D-f7b1beab

これ、実はプロジェクトファイルは落とせますが、そのままではコンパイルできません(T0T)

”SilverlightApplication3DTest”プロジェクトファイルの下に
”test.cs” という配布されていないファイルが混ざっているためです

このファイルは思い切ってプロジェクトから削除します
csfiledel


この段階でF5実行すると、ちゃんと3DキューブがSilverlight(ブラウザ内)で動きます



このサンプルをちょろっと回収したのが、LT発表資料になります
以下、LTで紹介した光ちゃんのキューブサンプルの作り方です



(1) XNA Math Helper DLLをダウンロード

http://code.msdn.microsoft.com/XNA-Math-Helper-DLL-d4d1f7d4

まずは、先ほども言ったように、演算を補助するクラスの定義などがありません
MSDNのCode Sample Gallery から演算サポートしてくれるDLLをダウンロードします
以下を落としてきます
MathHelperDL


これにはソリューションファイルが入っています
プロジェクトをまるっと自分のプロジェクトにコピー追加しても良いのですが
すでに DLL : Microsoft.Xna.Framework.Math.dll が
解凍した直下に用意されているので
それを自分のプロジェクトファイルで参照するもできます
 // VSプロジェクトの「参照設定」で「Microsoft.Xna.Framework.Math.dll」を追加

これらには演算関係の機能が入っています
 // さっき落としてきた”SilverlightApplication3DTest”プロジェクトには
 // すでにこのDLLが参照の中に入っています

(2) 新規プロジェクトを作成

SL5ベータプロジェクトを作成し、(1)で落としてきた「Microsoft.Xna.Framework.Math.dll」を追加します


(3) <Main.xaml>

ここのポイントは「DrawingSurface」★

Silverlightは普通であれば、XAML内に
ボタンやテキストボックスなどのコントロールを配置してゆくのですが
Silverlight5ベータのXNA(もどき)の描画対象は
このDrawingSurfaceのようです
 <UserControl x:Class="SilverlightApplication3DTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="Gray">
<DrawingSurface Draw="OnDraw" SizeChanged="DrawingSurface_SizeChanged" />
</Grid>
</UserControl>


(4) <Mainpage.xaml.cs>

C++でゲームを作っていた経験が長いと、どうしてもwhile(1) を使いたくなります

なので、Silverlightでそれを実現するために、わたしは空のStoryboard を作成しています
今回の場合は、3Dキューブをくるくる回すために使用しています

ゲームの場合は、kye(); move(); trans(); など
イベントドリブンではなく、定期的にフレームレート単位で回ってきてほしい処理が
存在しますので、この自作Storyboard をうまく駆使すれば、らしき処理ができますね
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
//using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Silverlight;
using System.IO;
using System.Windows.Media.Imaging;

namespace _20110507LT
{
// 角度の状態
public static class State
{
public static float sliderYawVal = new float();
public static float sliderPitchVal = new float();
public static float sliderRollVal = new float();
}

public partial class MainPage : UserControl
{
// 3D演算するとこ
Scene scene = new Scene();

public MainPage()
{
InitializeComponent();

// ----------------------------
// while(1)の変わり
// ----------------------------
var storyboard = new Storyboard();
storyboard.Completed += (o, e) =>
{
State.sliderYawVal += 0.005f;
State.sliderPitchVal += 0.005f;
// State.sliderRollVal += 0.005f; // 2軸の回転だけで立体の傾きは表現できるため
storyboard.Begin();
};
storyboard.Begin();

}

void OnDraw(object sender, DrawEventArgs args)
{
// 描画
scene.Draw(args.GraphicsDevice, args.TotalTime);

// 次のフレームへ
args.InvalidateSurface();
}

// アスペクト比の設定
private void DrawingSurface_SizeChanged(object sender, SizeChangedEventArgs e)
{
DrawingSurface surface = sender as DrawingSurface;
scene.AspectRatio = (float)surface.ActualWidth / (float)surface.ActualHeight;
}


// ================================================================
// 3D演算関係
// ================================================================
public class Scene
{
static readonly Color transparent = new Color(0, 0, 0, 0); // avoid allocating in draw
Matrix view; // viewマトリクス
Matrix projection; // 透視変換マトリクス

// キューブ
Cube Cube = new Cube();

public Scene()
{
Vector3 cameraPosition = new Vector3(0, 0, 8.0f); // カメラ位置
Vector3 cameraTarget = Vector3.Zero; // 見る位置

// viewマトリクス
view = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
}

public float AspectRatio
{
set
{
// 透視変換マトリクス
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, value, 1.0f, 100.0f);
}
}

public void Draw(GraphicsDevice graphicsDevice, TimeSpan totalTime)
{
// いままでの描画をクリア
graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, transparent, 1.0f, 0);

// キューブ描画
Cube.Draw(graphicsDevice, totalTime, view * projection);
}
}
}

// ================================================================
// キューブ関係
// ================================================================
public struct VertexPositionTexture
{
public Vector3 Position;
public Vector2 UV;

public VertexPositionTexture(Vector3 position, Vector2 uv)
{
Position = position;
UV = uv;
}

public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
);
}

public class Cube
{
static readonly GraphicsDevice resourceDevice = GraphicsDeviceManager.Current.GraphicsDevice;

VertexBuffer vertexBuffer;
VertexShader vertexShader;
PixelShader pixelShader;
Texture2D texture;

public Cube()
{
// キューブのテクスチャ設定など
vertexBuffer = CreateCube();

// 頂点シェーダ読み込み(ファイルプロパティ:コンテンツ)
Stream shaderStream = Application.GetResourceStream(new Uri("Cube.vs", UriKind.Relative)).Stream;
vertexShader = VertexShader.FromStream(resourceDevice, shaderStream);

// ピクセルシェーダ読み込み(ファイルプロパティ:コンテンツ)
shaderStream = Application.GetResourceStream(new Uri("Cube.ps", UriKind.Relative)).Stream;
pixelShader = PixelShader.FromStream(resourceDevice, shaderStream);

// テクスチャ読み込み(ファイルプロパティ:コンテンツ)
Stream imageStream = Application.GetResourceStream(new Uri("20110507LT.png", UriKind.Relative)).Stream;
var image = new BitmapImage();
image.SetSource(imageStream);

// テクスチャ貼り付け
texture = new Texture2D(resourceDevice, image.PixelWidth, image.PixelHeight, false, SurfaceFormat.Color);
image.CopyTo(texture);
}

VertexBuffer CreateCube()
{
var cube = new VertexPositionTexture[36];

Vector3 topLeftFront = new Vector3(-1.0f, 1.0f, 1.0f);
Vector3 bottomLeftFront = new Vector3(-1.0f, -1.0f, 1.0f);
Vector3 topRightFront = new Vector3(1.0f, 1.0f, 1.0f);
Vector3 bottomRightFront = new Vector3(1.0f, -1.0f, 1.0f);
Vector3 topLeftBack = new Vector3(-1.0f, 1.0f, -1.0f);
Vector3 topRightBack = new Vector3(1.0f, 1.0f, -1.0f);
Vector3 bottomLeftBack = new Vector3(-1.0f, -1.0f, -1.0f);
Vector3 bottomRightBack = new Vector3(1.0f, -1.0f, -1.0f);

Vector2 topLeftUVXNA = new Vector2(0.0f, 0.0f);
Vector2 topRightUVXNA = new Vector2(0.5f, 0.0f);
Vector2 bottomLeftUVXNA = new Vector2(0.0f, 1.0f);
Vector2 bottomRightUVXNA = new Vector2(0.5f, 1.0f);

Vector2 topLeftUVSL = new Vector2(0.5f, 0.0f);
Vector2 topRightUVSL = new Vector2(1.0f, 0.0f);
Vector2 bottomLeftUVSL = new Vector2(0.5f, 1.0f);
Vector2 bottomRightUVSL = new Vector2(1.0f, 1.0f);

// Front face
cube[0] = new VertexPositionTexture(topRightFront, topRightUVXNA);
cube[1] = new VertexPositionTexture(bottomLeftFront, bottomLeftUVXNA);
cube[2] = new VertexPositionTexture(topLeftFront, topLeftUVXNA);
cube[3] = new VertexPositionTexture(topRightFront, topRightUVXNA);
cube[4] = new VertexPositionTexture(bottomRightFront, bottomRightUVXNA);
cube[5] = new VertexPositionTexture(bottomLeftFront, bottomLeftUVXNA);

// Back face
cube[6] = new VertexPositionTexture(bottomLeftBack, bottomRightUVXNA);
cube[7] = new VertexPositionTexture(topRightBack, topLeftUVXNA);
cube[8] = new VertexPositionTexture(topLeftBack, topRightUVXNA);
cube[9] = new VertexPositionTexture(bottomRightBack, bottomLeftUVXNA);
cube[10] = new VertexPositionTexture(topRightBack, topLeftUVXNA);
cube[11] = new VertexPositionTexture(bottomLeftBack, bottomRightUVXNA);

// Top face
cube[12] = new VertexPositionTexture(topLeftBack, topLeftUVSL);
cube[13] = new VertexPositionTexture(topRightBack, topRightUVSL);
cube[14] = new VertexPositionTexture(topLeftFront, bottomLeftUVSL);
cube[15] = new VertexPositionTexture(topRightBack, topRightUVSL);
cube[16] = new VertexPositionTexture(topRightFront, bottomRightUVSL);
cube[17] = new VertexPositionTexture(topLeftFront, bottomLeftUVSL);

// Bottom face
cube[18] = new VertexPositionTexture(bottomRightBack, topLeftUVSL);
cube[19] = new VertexPositionTexture(bottomLeftBack, topRightUVSL);
cube[20] = new VertexPositionTexture(bottomLeftFront, bottomRightUVSL);
cube[21] = new VertexPositionTexture(bottomRightFront, bottomLeftUVSL);
cube[22] = new VertexPositionTexture(bottomRightBack, topLeftUVSL);
cube[23] = new VertexPositionTexture(bottomLeftFront, bottomRightUVSL);

// Left face
cube[24] = new VertexPositionTexture(bottomLeftFront, bottomRightUVSL);
cube[25] = new VertexPositionTexture(bottomLeftBack, bottomLeftUVSL);
cube[26] = new VertexPositionTexture(topLeftFront, topRightUVSL);
cube[27] = new VertexPositionTexture(topLeftFront, topRightUVSL);
cube[28] = new VertexPositionTexture(bottomLeftBack, bottomLeftUVSL);
cube[29] = new VertexPositionTexture(topLeftBack, topLeftUVSL);

// Right face
cube[30] = new VertexPositionTexture(bottomRightBack, bottomRightUVXNA);
cube[31] = new VertexPositionTexture(bottomRightFront, bottomLeftUVXNA);
cube[32] = new VertexPositionTexture(topRightFront, topLeftUVXNA);
cube[33] = new VertexPositionTexture(bottomRightBack, bottomRightUVXNA);
cube[34] = new VertexPositionTexture(topRightFront, topLeftUVXNA);
cube[35] = new VertexPositionTexture(topRightBack, topRightUVXNA);

var vb = new VertexBuffer(resourceDevice, VertexPositionTexture.VertexDeclaration, cube.Length, BufferUsage.WriteOnly);
vb.SetData(0, cube, 0, cube.Length, 0);
return vb;
}

public void Draw(GraphicsDevice graphicsDevice, TimeSpan totalTime, Matrix viewProjection)
{
Matrix position = Matrix.Identity; // キューブの持つマトリクス
Matrix scale = Matrix.CreateScale(1.0f); // スケールの操作

// 回転の計算
Matrix rotation = Matrix.CreateFromYawPitchRoll(State.sliderYawVal, State.sliderPitchVal, State.sliderRollVal);

// ワールド座標に変換する
Matrix world = scale * rotation * position;

// 透視変換
Matrix worldViewProjection = world * viewProjection;

// 描画設定
graphicsDevice.SetVertexBuffer(vertexBuffer);
graphicsDevice.SetVertexShader(vertexShader);
graphicsDevice.SetVertexShaderConstantFloat4(0, ref worldViewProjection); // pass the transform to the shader

graphicsDevice.SetPixelShader(pixelShader);
graphicsDevice.Textures[0] = texture;

// 描画!
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
}
}
}

あとは、描いた描画をクリアしたり
XAMLで表示しているアスペクト比が変更される度に計算しなおす処理も必要です
// ブラウザの大きさを変更されたりした場合の対策

ここでは透視変換マトリクスを作っています
3D座標空間のものを、2Dに落としてくるための行列式です

Sceneクラス、これが一番泥臭い作業になります

(キューブのモデリングデータは、そのまま”SilverlightApplication3DTest”プロジェクトのものを再利用しています)

VSファイル、PSファイルはHLSL言語からコンパイルして作るのですが
非常にめんどうですね。。
今は、このシェーダを別読み込みする方法しか無いのかなぁ…?
// なんせ、情報が全然出ていないので、間違っていたらすみません

わたしのプロジェクトファイルでは
テクスチャデータを、台湾SLキャラの光ちゃんに変えておきました★

読み込むデータ [Cube.vs / Cube.ps / 20110507LT.png ] については
ファイルのプロパティで「ビルドアクション」項目を「コンテンツ」に設定しています


(5) <スタートページ.html>

SilverlightのHTMLタグが記載されている(xapファイルが入ってる) ところに
以下のパラメータを設置しておきます

これはGPUアクセラレーションをONにするパラメータです
ONにしておかないとSilverlight5の3Dは動かないみたいです
<body>
<form id="form1" runat="server" style="height:100%">
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/20110507LT.xap"/>
<param name="EnableGPUAcceleration" value="true" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="5.0.60401.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.60401.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Microsoft Silverlight の取得" style="border-style:none"/>
</a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
</form>
</body>


こんなところが基本になります
DirectXに比べたらかなり行数は少ないのですけど
それでもちょっと大変ですね


最後に、おもしろいサンプルがあるので、見てみてください
バイオハザードみたいに、建物の中を徘徊できます
(もちろんゾンビはいないけど)

http://david.blob.core.windows.net/babylon/Babylon.html

これでみなさんも、Let's Silverlight5 3D★

そして、「関西ゲームプログラミング勉強会」でこのような発表の機会を与えてもらえて本当に良かったです
秋猫さんありがとうございました★

haruka_sao at 21:00コメント(0)トラックバック(0)Silverlight | 3D 

トラックバックURL

コメントする

名前:
URL:
  情報を記憶: 評価:  顔   星
 
 
 
Sao's Tech Memo
mvp_logo_140

Microsoft MVP for Windows
Platform Development
[Jan,2015-Dec,2015]
Microsoft MVP for
Client Development
[Jan,2014-Dec,2014]
Microsoft MVP for Client App Dev [Jan,2010-Dec,2013]
Recent Comments
訪問者数
  • 今日:
  • 昨日:
  • 累計:

記事検索