[PR]
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
プログラミング、3DCGとその他いろいろについて
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
//グローバル変数(C#側からセット) float4x4 World; float4x4 View; float4x4 Projection; texture Texture; //頂点シェーダ struct VertexPositionTexture { float4 Position : POSITION; float4 TextureCoordinate : TEXCOORD; }; VertexPositionTexture VertexShader(VertexPositionTexture input) { VertexPositionTexture output; output.Position = mul(input.Position, mul(World, mul(View, Projection))); output.TextureCoordinate = input.TextureCoordinate; return output; } //ピクセルシェーダ sampler DiffuseSampler = sampler_state{ Texture = (Texture); }; float4 PixelShader(float2 textureCoordinate : TEXCOORD) : COLOR { return tex2D(DiffuseSampler, textureCoordinate); } technique SimpleEffect { pass { VertexShader = compile vs_1_1 VertexShader(); PixelShader = compile ps_2_0 PixelShader(); } }
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; public class MyGame : Game { GraphicsDeviceManager graphics; ContentManager content; Model model; public MyGame() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); } protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { model = content.Load<Model>("Ship"); ChangeEffectUsedByModel( content.Load<Effect>("SimpleEffect") ); } } void ChangeEffectUsedByModel(Effect replacementEffect) { Texture2D texture = content.Load<Texture2D>("ShipDiffuse"); foreach (ModelMesh mesh in model.Meshes) { foreach (ModelMeshPart part in mesh.MeshParts) { Effect newEffect = replacementEffect.Clone( graphics.GraphicsDevice ); newEffect.Parameters["Texture"].SetValue( texture ); part.Effect = newEffect; } } } protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent) content.Unload(); } protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); Matrix world = Matrix.CreateRotationY( MathHelper.ToRadians(-40) ); Matrix view = Matrix.CreateLookAt( new Vector3(3000, 1500, 0), Vector3.Zero, Vector3.Up ); Matrix projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians(45), (float)graphics.GraphicsDevice.Viewport.Width / graphics.GraphicsDevice.Viewport.Height, 1000, 10000 ); DrawModel(world, view, projection); } void DrawModel(Matrix world, Matrix view, Matrix projection) { Matrix[] transforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(transforms); foreach (ModelMesh mesh in model.Meshes) { foreach (Effect effect in mesh.Effects) { Matrix localWorld = transforms[mesh.ParentBone.Index] * world; effect.Parameters["World"].SetValue(localWorld); effect.Parameters["View"].SetValue(view); effect.Parameters["Projection"].SetValue(projection); } mesh.Draw(); } } }
void ChangeEffectUsedByModel(Effect replacementEffect) { foreach (ModelMesh mesh in model.Meshes) { foreach (ModelMeshPart part in mesh.MeshParts) { Effect newEffect = replacementEffect.Clone( graphics.GraphicsDevice ); newEffect.Parameters["Texture"].SetValue( ((BasicEffect)part.Effect).Texture ); part.Effect = newEffect; } } }
上手く表示されるテクスチャ | 上手く表示されないテクスチャ | |
Width | 256 | 256 |
Height | 256 | 256 |
ResourceManagementMode | Automatic | Automatic |
ResourceUsage | None | None |
Format | Dxt5 | Color |
LevelCount | 9 | 1 |
LevelOfDetail | 0 | 0 |
IsDisposed | False | False |
Tag | ||
Name | ||
ResourceType | Texture2D | Texture2D |
Priority | 0 | 0 |
前回はハードウェア・インスタンシングをやりましたが、これはGPUシェーダー・モデル3.0以上でなければ動きません。
どうやらまだ2.0までのものがよく使われているそうなので、これでは困る場合もあるでしょう。
実は、シェーダーモデルが2.0でも上手くいく方法があります。
グラフィックスカードのメモリを少々食うのですが、モデルそのものはたくさん複製して1つのVertexBufferに入れておいて、インスタンスの情報は配列にしてHLSLのグローバル変数としてセットしてしまうというものです。
これをシェーダー・インスタンシングといいます。
ハードウェア・インスタンシングではVertexBufferにインスタンスの情報を格納しましたが、こちらはHLSLのグローバル変数に格納します。
そして、本来のVertexBufferの中にあるそれぞれのモデルに、インデックスを振ります。
そのインデックスから対応するインスタンスの情報を特定して、頂点に適用するのです。(どうもわかりにくいですね・・・)
こうすることによって、1つのVertexBufferから、自由に動かせる複数のモデルを一度に描画することが出来ます。
モデルのデータを複製して1つのVertexBufferに入れるため、その分無駄なグラフィックス・カードのメモリを食うのですが、それでも一つ一つモデルを描画するよりもパフォーマンスが良くなる(CPUの負荷が減るので)そうです。
HLSLのコードは次のようになります:
ShaderInstancing.fx
float4x4 InstanceTransforms[10]; struct VertexPositionColor { float4 Position : POSITION; float4 Color : COLOR; }; VertexPositionColor VertexShader( VertexPositionColor input, float instanceIndex : TEXCOORD1 ) { VertexPositionColor output; output.Position = mul(input.Position, InstanceTransforms[instanceIndex]); output.Color = input.Color; return output; } float4 PixelShader(float4 color : COLOR):COLOR { return color; } technique ShaderInstancing { pass ShaderInstancingPass { VertexShader = compile vs_2_0 VertexShader(); PixelShader = compile ps_2_0 PixelShader(); } }
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; struct VertexPositionColorIndex { public Vector3 Position; public Color Color; public float Index; public static readonly int SizeInBytes = System.Runtime.InteropServices.Marshal.SizeOf( typeof(VertexPositionColorIndex) ); public static readonly VertexElement[] VertexElements = new VertexElement[]{ new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ), new VertexElement( 0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ), new VertexElement( 0, sizeof(float)*3 + 4, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ) }; } class MyGame : Game { GraphicsDeviceManager graphics; ContentManager content; const int instanceCount = 10; VertexPositionColorIndex[] vertices = new VertexPositionColorIndex[3 * instanceCount]; Matrix[] instanceTransforms = new Matrix[instanceCount]; Effect effect; VertexBuffer vertexBuffer; public MyGame() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); for (int i = 0; i < instanceCount; i++) { vertices[3 * i].Color = Color.Blue; vertices[3 * i].Position = new Vector3(-0.1f, 0.1f, 0); vertices[3 * i].Index = i; vertices[3 * i + 1].Color = Color.White; vertices[3 * i + 1].Position = new Vector3(0.1f, 0.1f, 0); vertices[3 * i + 1].Index = i; vertices[3 * i + 2].Color = Color.Red; vertices[3 * i + 2].Position = new Vector3(0.1f, -0.1f, 0); vertices[3 * i + 2].Index = i; } for (int i = 0; i < instanceTransforms.Length; i++) { instanceTransforms[i] = Matrix.CreateTranslation(0.1f * i, 0, 0); } } protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { effect = content.Load<Effect>("ShaderInstancing"); vertexBuffer = new VertexBuffer( graphics.GraphicsDevice, vertices.Length * VertexPositionColorIndex.SizeInBytes, ResourceUsage.None ); vertexBuffer.SetData<VertexPositionColorIndex>(vertices); graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration( graphics.GraphicsDevice, VertexPositionColorIndex.VertexElements ); } } protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); graphics.GraphicsDevice.Vertices[0].SetSource( vertexBuffer, 0, VertexPositionColorIndex.SizeInBytes ); effect.Parameters["InstanceTransforms"].SetValue(instanceTransforms); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); graphics.GraphicsDevice.DrawPrimitives( PrimitiveType.TriangleList, 0, vertices.Length / 3 ); pass.End(); } effect.End(); } }
Hardware Instancing |
Windows (shader 3.0) |
Shader Instancing |
Windows (shader 2.0) |
VFetch | Xbox360 |
解説 | |
GraphicsDevice.Vertices[0] | 普通のVertexBufferと同じ。モデルを構成する頂点を持っています。ここでは、C#で言うなら「クラス」の役割を果たします。 |
GraphicsDevice.Vertices[1] | 頂点の代わりに、モデルの各インスタンス固有の情報をもっています。C#で言うなら「メンバ変数」がいっぱい詰まっている感じ。構造体の配列みたい。 |
struct VertexPositionColor { float4 Position : POSITION0; float4 Color : COLOR; }; VertexPositionColor VertexShader( VertexPositionColor input, float3 position:POSITION1 ) { input.Position.xyz += position; return input; } float4 PixelShader(float4 color : COLOR) : COLOR0 { return color; } technique HardwareInstancing { pass Pass1 { VertexShader = compile vs_3_0 VertexShader(); PixelShader = compile ps_3_0 PixelShader(); } }
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; class MyGame : Game { GraphicsDeviceManager graphics; ContentManager content; VertexPositionColor[] vertices = new VertexPositionColor[]{ new VertexPositionColor(new Vector3(-0.1f, 0.1f, 0), Color.Blue), new VertexPositionColor(new Vector3(0.1f, 0.1f, 0), Color.White), new VertexPositionColor(new Vector3(0.1f, -0.1f, 0), Color.Red) }; Vector3[] trianglePositions = new Vector3[] { new Vector3(), new Vector3(0.1f, 0.1f, 0) }; //Graphics Device Objects Effect effect; VertexBuffer triangleVertexBuffer; IndexBuffer indexBuffer; VertexBuffer positionVertexBuffer; public MyGame() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); } protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { effect = content.Load<Effect>("HardwareInstancing"); graphics.GraphicsDevice.VertexDeclaration = createVertexDeclaration(); triangleVertexBuffer = new VertexBuffer( graphics.GraphicsDevice, VertexPositionColor.SizeInBytes * vertices.Length, ResourceUsage.None ); triangleVertexBuffer.SetData<VertexPositionColor>(vertices); indexBuffer = new IndexBuffer( graphics.GraphicsDevice, sizeof(int) * 3, ResourceUsage.None, IndexElementSize.ThirtyTwoBits ); indexBuffer.SetData<int>(new int[] { 0, 1, 2 }); positionVertexBuffer = new VertexBuffer( graphics.GraphicsDevice, sizeof(float) * 3 * trianglePositions.Length, ResourceUsage.None ); positionVertexBuffer.SetData<Vector3>(trianglePositions); } } VertexDeclaration createVertexDeclaration() { System.Collections.Generic.List<VertexElement> elements = new System.Collections.Generic.List<VertexElement>(); elements.AddRange(VertexPositionColor.VertexElements); elements.Add( new VertexElement( 1, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 1) ); return new VertexDeclaration( graphics.GraphicsDevice, elements.ToArray() ); } protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); graphics.GraphicsDevice.Vertices[0].SetSource( triangleVertexBuffer, 0, VertexPositionColor.SizeInBytes ); graphics.GraphicsDevice.Vertices[0].SetFrequencyOfIndexData( trianglePositions.Length ); graphics.GraphicsDevice.Vertices[1].SetSource( positionVertexBuffer, 0, sizeof(float) * 3 ); graphics.GraphicsDevice.Vertices[1].SetFrequencyOfInstanceData(1); graphics.GraphicsDevice.Indices = indexBuffer; effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); graphics.GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, //baseVertex 0, //minVertexIndex vertices.Length, //numVertices 0, //startIndex vertices.Length/3 //primitiveCount ); pass.End(); } effect.End(); } }
ゲームを作ってみて、いくつか思い知らされたことがあるのでメモしておきます。
1.シェーダはなるべく低いバージョンでコンパイルしよう!
これは当たり前のことだと思えるかもしれません。
あんまり高いバージョンにしてしまうと、そのバージョンに未対応の
GPUでは動かないからです。
でもぼくは実は今まで全部(1.1ではなく)2.0でコンパイルしていたのです。
VertexShader = compile vs_2_0 MyVertexShader();
PixelShader = compile ps_2_0 MyPixelShader();
理由は「いちいち調べるのめんどくさいからもう全部2.0でいいや」というもので、
昨日まではこれでも差し支えなかったのですが、
ついに昨日、「時には1.1にすることも考えなきゃいけないかな」という気分になりました。
古いDellのパソコン(にGPU、ATI Radeon9250を付けたもの)でゲーム
を試しに動かしていたのですが、
なんと弾と爆発が表示されません。
これではゲームになりません。
嫌な汗が出ました。
これはGPUがシェーダーモデル2.0に対応していないからだろうと思い、
大急ぎで弾と爆発のシェーダーのバージョンを二つとも1.1に変えました。
(バージョンを下げたことで、使えない機能が出てきて、新しいパソコンでも上手く動かなくなる危険性もありましたが、大丈夫だったようです。)
すると幸い古いパソコンでも上手く弾や爆発が表示されました。
めでたしめでたし
この話の教訓は、「ときどきでいいですから、バージョン関係にも気を使いましょうね」ということです。
ちょっぴり反省です。
2.描画をするときにはパフォーマンスも考慮しよう!
もちろんパフォーマンスを考えすぎてコードが可読性の低い、ぐちゃぐちゃなものになってしまってもいけません。
ものの本によるとパフォーマンスを考えるべきなのはプログラムの2割で、残りの8割はパフォーマンスにさして影響がありません。
そこでぼくは描画するときはパフォーマンスをあんまり考えていませんでした。
しかし昨日、古いDellのパソコンでゲームを動かしたところ、弾を撃ったときに信じられないくらいノロくなってしまうということを発見したのです。
原因はどうやらピクセルシェーダに負荷がかかりすぎているようで、たとえば弾を撃ったばっかりで弾が画面のピクセルの多くを占めるような場合にはものすごく遅くなります。(しかし弾が遠くに行ってしまい小さくなればスムーズに動きます。)
本当に原因がピクセルシェーダなのかはわかりませんが、えらくリッチな描画をやって問題が起きたというのも事実です。
以前は弾はビルボードで描画していたのですが、そのときには快適に動いていました。
つまり描画はパフォーマンスに影響のある2割だったということです。
しかしながら新しいパソコンでは遅くなったりはしていないようなので、弾の描画を以前のようなしょぼいものに戻すのもしのびないですね。
ですから教訓としては、「処理の遅いパソコン(グラフィックスカード)のために複数の描画方法を用意しよう!」といったところでしょうか・・・
でもめんどくさいですね。うーん・・・