[PR]
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
プログラミング、3DCGとその他いろいろについて
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; class MyGame : Game { GraphicsDeviceManager graphics; BasicEffect effect; VertexPositionNormalTexture[] vertices = new[] { new VertexPositionNormalTexture ( new Vector3(0, 1, 0), new Vector3(0, 0, 1), new Vector2(0.5f, 0) ), new VertexPositionNormalTexture ( new Vector3(1, 0, 0), new Vector3(0, 0, 1), new Vector2(1, 1) ), new VertexPositionNormalTexture ( new Vector3(-1, 0, 0), new Vector3(0, 0, 1), new Vector2(0, 1) ), }; Texture2D texture; public MyGame() { graphics = new GraphicsDeviceManager(this); } protected override void LoadContent() { effect = new BasicEffect(GraphicsDevice) { TextureEnabled = true, View = Matrix.CreateLookAt ( new Vector3(0, 0, 3), //カメラの位置 new Vector3(0, 0, 0), //カメラの見る点 new Vector3(0, 1, 0) //カメラの上向きベクトル ), Projection = Matrix.CreatePerspectiveFieldOfView ( MathHelper.ToRadians(45), //視野の角度。ここでは45° GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦) 1, //カメラからこれより近い物体は画面に映らない 100 //カメラからこれより遠い物体は画面に映らない ) }; effect.EnableDefaultLighting(); texture = Content.Load<Texture2D>("Content/Penguins"); } protected override void UnloadContent() { effect.Dispose(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); effect.Texture = texture; foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture> ( PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3 ); } } }
これまで描いてきた図形は全て頂点に色が付いていただけでしたが、
もちろんXNAではテクスチャが描かれた図形を表示することも出来ます。
テクスチャ付きの図形を表示するには、頂点にVertexPositionTexture構造体を使います。
これは色のデータの代わりに、テクスチャのデータ(Vector2)を持っています。
そのデータは1.0 × 1.0のテクスチャ座標上の点を表していて、
表示するテクスチャのどの部分を頂点が表示するかを意味します。
ここら辺はサンプルとその結果を見た方が速いかもしれません。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; class MyGame : Game { GraphicsDeviceManager graphics; BasicEffect effect; VertexPositionTexture[] vertices = new[] { 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)), }; Texture2D texture; public MyGame() { graphics = new GraphicsDeviceManager(this); } protected override void LoadContent() { effect = new BasicEffect(GraphicsDevice) { TextureEnabled = true, View = Matrix.CreateLookAt ( new Vector3(0, 0, 3), //カメラの位置 new Vector3(0, 0, 0), //カメラの見る点 new Vector3(0, 1, 0) //カメラの上向きベクトル ), Projection = Matrix.CreatePerspectiveFieldOfView ( MathHelper.ToRadians(45), //視野の角度。ここでは45° GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦) 1, //カメラからこれより近い物体は画面に映らない 100 //カメラからこれより遠い物体は画面に映らない ) }; texture = Content.Load<Texture2D>("Content/Penguins"); } protected override void UnloadContent() { effect.Dispose(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); effect.Texture = texture; foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>( PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3 ); } } }
このサンプルでは三角形の中に"Penguins"という画像を表示しています。
3つのそれぞれの頂点は、座標と、画像のどの部分を担当するかのデータを持っています。
そのデータは、画像のサイズを1.0 × 1.0であると考えたときのテクスチャの点の座標で、
その方向は普通のスクリーンの座標と同じように、左上が(0, 0)となっています。
この方法を使えば、複雑なマッピングも行うことが出来ます。
例えば世界地図を球にマップして地球を作ることも出来るのです。
もちろんこの三角形は3Dですからカメラの位置を調節すると遠近感が出ます。
(斜めから見た図)
さて、テクスチャを使った三角形を描画する上で忘れがちなのは、
BasicEffect.TextureEnabledプロパティをtrueにセットすることです。
これによってBasicEffectのテクスチャの描画が有効になります。
もしそうしなければ、BasicEffectが描画の時にテクスチャを考慮に入れなくなり、
真っ白の三角形が描かれるのみとなります。
これをする理由は色付きの三角形を表示するときにBasicEffect.VertexColorEnabledプロパティをtrueにした理由と同じです。
こういったプロパティによって、BasicEffectは内部の描画モードを変更しているのです。
頂点にパフォーマンスを上げるVertexBufferがあるのと同じように
インデックスにもパフォーマンスを上げる(しかしめんどくさい)IndexBufferがあります。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; class MyGame : Game { GraphicsDeviceManager graphics; BasicEffect effect; VertexPositionColor[] vertices = new[] { new VertexPositionColor(new Vector3(1, 0, 0), Color.White), new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red), new VertexPositionColor(new Vector3(0, 1, 0), Color.Blue), new VertexPositionColor(new Vector3(0, -1, 0), Color.Blue), }; VertexBuffer vertexBuffer; int[] indices = new[] { 0, 1, 2, 0, 3, 1 }; IndexBuffer indexBuffer; public MyGame() { graphics = new GraphicsDeviceManager(this); } protected override void LoadContent() { effect = new BasicEffect(GraphicsDevice) { VertexColorEnabled = true, View = Matrix.CreateLookAt ( new Vector3(0, 0, 3), //カメラの位置 new Vector3(0, 0, 0), //カメラの見る点 new Vector3(0, 1, 0) //カメラの上向きベクトル ), Projection = Matrix.CreatePerspectiveFieldOfView ( MathHelper.ToRadians(45), //視野の角度。ここでは45° GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦) 1, //カメラからこれより近い物体は画面に映らない 100 //カメラからこれより遠い物体は画面に映らない ) }; vertexBuffer = new VertexBuffer( GraphicsDevice, typeof(VertexPositionColor), vertices.Length, BufferUsage.None ); vertexBuffer.SetData<VertexPositionColor>(vertices); indexBuffer = new IndexBuffer( GraphicsDevice, IndexElementSize.ThirtyTwoBits, indices.Length, BufferUsage.None ); indexBuffer.SetData<int>(indices); } protected override void UnloadContent() { effect.Dispose(); vertexBuffer.Dispose(); indexBuffer.Dispose(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(vertexBuffer); GraphicsDevice.Indices = indexBuffer; GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, //baseIndex 0, //minVertexIndex vertices.Length, //numVertices 0, //startIndex indices.Length / 3 //primitiveCount ); } } }
見た目は普通に描画したときと全く変わりませんが、内部ではIndexBufferを使っており、
ほんの少しパフォーマンスがよくなっているはずです。
(しかしやはり、こういう単純な例では全くありがたみがありません。)
小さなサンプルではその有効性はさっぱりわかりませんが、
大きなプログラムではGame.Initializeメソッドが
(グラフィックスで無い)データの初期化を分離するのに役に立ちます。
(これはちょうどjavaアプレットのinitメソッドのようなものです)
例えば座標データをこの中で初期化することが出来ます。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; class MyGame : Game { GraphicsDeviceManager graphics; BasicEffect effect; VertexPositionColor[] vertices; VertexBuffer vertexBuffer; public MyGame() { graphics = new GraphicsDeviceManager(this); } protected override void Initialize() { vertices = new[] { new VertexPositionColor(new Vector3(1, 0, 0), Color.White), new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red), new VertexPositionColor(new Vector3(0, 1, 0), Color.Blue), }; base.Initialize(); } protected override void LoadContent() { effect = new BasicEffect(GraphicsDevice) { VertexColorEnabled = true, View = Matrix.CreateLookAt ( new Vector3(0, 0, 3), //カメラの位置 new Vector3(0, 0, 0), //カメラの見る点 new Vector3(0, 1, 0) //カメラの上向きベクトル ), Projection = Matrix.CreatePerspectiveFieldOfView ( MathHelper.ToRadians(45), //視野の角度。ここでは45° GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦) 1, //カメラからこれより近い物体は画面に映らない 100 //カメラからこれより遠い物体は画面に映らない ) }; vertexBuffer = new VertexBuffer( GraphicsDevice, typeof(VertexPositionColor), vertices.Length, BufferUsage.None ); vertexBuffer.SetData<VertexPositionColor>(vertices); } protected override void UnloadContent() { effect.Dispose(); vertexBuffer.Dispose(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(vertexBuffer); GraphicsDevice.DrawPrimitives( PrimitiveType.TriangleList, 0, //startIndex vertices.Length / 3 //primitiveCount ); } } }
ここでは、今までコンストラクタ内で初期化していた三角形の頂点データを、
MyGame.Initializeメソッド内で初期化しています。
そのあとbase.InitializeでGame.Initializeメソッドを呼んでいます。
不可解です。
なぜスーパークラスのInitializeメソッドを呼ばなければならないのでしょう?
その理由はもちろんWindowsFormsのControl.OnPaintメソッドの
オーバーライドでスーパークラスのOnPaintメソッドを
呼んでやらなければならない理由と同じです。
つまり、Game.Initializeメソッドが何か大切なことをしているからです。
このケースでは、Game.InitializeメソッドはLoadContentメソッドを呼んでおり、
base.Initialize
を忘れるとグラフィックス関係の変数が全て初期化されずにnullのままで、
描画すると同時に例外(NullReferenceException)が飛びます。
(同様に、DrawメソッドとUpdateメソッドも本当はスーパークラスの
メソッドを呼んでやらなければなりません。
しかしGameComponentを使わない限りは大して問題はないですし
サンプルなので割愛しました。
実際に問題が出る時に対処しても遅くは無いでしょう。
YAGNIの原則です。)
これはまぁいいでしょう。
しかしなぜ一番最後にbase.Initializeを呼んでいるのでしょうか?
コンストラクタは普通基底クラスのものから先に呼ばれます。
派生して出来たクラスのコンストラクタで基底クラスの何かを
利用するときに困らないようになっているのです。
とすると同じ初期化を表すInitializeメソッドも一番最初に
base.Initializeを呼ぶべきであるようにも思えます。
一番最後に呼ぶのではまるでデストラクタです。
一番最後にbase.Initializeとするのは
気持ち悪いように思えます。
なぜ一番最後に呼ぶのでしょう?
まず一番最初に呼ぶとどうなるかを実際に見るのがいいかもしれません。
…例外がスローされます。
verticesがまだnullだというのです。
実はこれは一つ目の疑問と関係しています。
Game.InitializeメソッドはLoadContentを呼んでいるので、
一番最初にGame.Initializeメソッドを呼ぶと
まだ三角形の頂点データが初期化されていないまま
VertexBufferが初期化されてしまうのです。
したがってVertexBufferのnew時に
例外がスローされるわけです。
ここまで三角形や四角形を描くのに
GraphicsDevice.DrawUserPrimitives<T>メソッドを使ってきました。
これはシンプルではあるのですがパフォーマンスがあまりよくありません。
良いパフォーマンスを持っているのは
GraphicsDevice.DrawPrimitivesメソッドです。
ただし、これは頂点データの配列そのものは直接使えません。
配列からVertexBufferを作り、それを使わねばならないのです。
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; class MyGame : Game { GraphicsDeviceManager graphics; BasicEffect effect; VertexPositionColor[] vertices = new[] { new VertexPositionColor(new Vector3(1, 0, 0), Color.White), new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red), new VertexPositionColor(new Vector3(0, 1, 0), Color.Blue), }; VertexBuffer vertexBuffer; public MyGame() { graphics = new GraphicsDeviceManager(this); } protected override void LoadContent() { effect = new BasicEffect(GraphicsDevice) { VertexColorEnabled = true, View = Matrix.CreateLookAt ( new Vector3(0, 0, 3), //カメラの位置 new Vector3(0, 0, 0), //カメラの見る点 new Vector3(0, 1, 0) //カメラの上向きベクトル ), Projection = Matrix.CreatePerspectiveFieldOfView ( MathHelper.ToRadians(45), //視野の角度。ここでは45° GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦) 1, //カメラからこれより近い物体は画面に映らない 100 //カメラからこれより遠い物体は画面に映らない ) }; vertexBuffer = new VertexBuffer( GraphicsDevice, typeof(VertexPositionColor), vertices.Length, BufferUsage.None ); vertexBuffer.SetData<VertexPositionColor>(vertices); } protected override void UnloadContent() { effect.Dispose(); vertexBuffer.Dispose(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(vertexBuffer); GraphicsDevice.DrawPrimitives( PrimitiveType.TriangleList, 0, //startIndex vertices.Length / 3 //primitiveCount ); } } }
foreach (var pass in effect1.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(vertexBuffer1); GraphicsDevice.DrawPrimitives( PrimitiveType.TriangleList, 0, //startIndex vertices1.Length / 3 //primitiveCount ); } foreach (var pass in effect2.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(vertexBuffer2); GraphicsDevice.DrawPrimitives( PrimitiveType.TriangleList, 0, //startIndex vertices2.Length / 3 //primitiveCount ); }