ページ「MvvmCross」と「MonoGameの3Dモデルを管理描画するクラスを作る」の間の差分

提供: MonoBook
(ページ間の差分)
ナビゲーションに移動 検索に移動
 
imported>Administrator
 
1行目: 1行目:
'''MvvmCross'''とは、[[.NET Framewrok]]および[[Mono]](各種[[Xamarin]])で利用できる[[MVVM]]の[[フレームワーク]]である。
+
[[MonoGame]]での3Dモデルの「表示」について解説しているサイトは多数あるが、「回転」や「位置」について記述しているサイトはほとんどないようだ。そもそもモデルと位置情報などを切り離して管理すること自体が馬鹿げているので、それらを管理するクラスを作っておくと捗る。
  
==概要==
+
また、Drawコールの中でBasicEffect周りを毎回設定している記述も多い。[[PC]]のように[[CPU]]が高速な環境では問題にならないのだろうが、[[Android]]などではそれだけで結構な負担になる。Android端末の多くが「[[OpenGL ES 2.0]]」であるため[[Geometry Instancing]]のようなジオメトリシェーダーを使った技術も使えないのでDrawコールを極力軽くする努力が求められる。 
[[MVVM]]といえば古くから[[WPF]][[Silverlight]]が存在したが、これら実質的に[[Windows]]という単一プラットフォームでしか利用できなく、[[デバッグ]]が楽だね、でも[[ソースコード]]の記述量が増えた分で相殺されて無意味ではないか、などと言われてきた。このように初登場のしかたが悪かったために不遇な扱いを受けてきた[[MVVM]]であるが、[[Xamarin.iOS]][[Xamarin.Android]]などの登場でやっとマルチプラットフォームという利点を活かせる土壌が整い、様々なMVVMフレームワークが登場している。その中のひとつがMvvmCrossである。
 
  
==MVVMおさらい==
+
追記:小手先の最適化より自前Modelクラスを実装してメッシュ結合できるようにした方が速そう。なお純正のModelクラスはsealed指定されているので継承できない。
===モデル===
 
いわゆる[[ビジネスロジック]]と呼ばれるものを記述する層である。[[設計]]が破綻しているプロジェクトでは単なる[[データベース]]の定義となっている事が多いが、本来であれば机上の空論的に本質的な部分はすべてここに記載され、本質的な[[デバッグ]]はここだけで完結する。
 
  
今では[[スタンドアローン]]などという考え方は死滅した御時世であり、[[クラウド]]や[[オンプレミス]]を問わずモデル層の99%は[[サーバーサイド]]で実装され、[[.NET Framework]]であれば[[WCF]]や[[ASP.NET Web API]]などの領域であると言える。[[スマホ]]の[[アプリ]]などの極小規模なものであれば[[SQLite]]などを用いてローカルにモデル層を保持することもある。
+
<source>
 +
    using System;
 +
    using System.Collections.Generic;
  
===ビューモデル===
+
    using Microsoft.Xna.Framework;
ビューモデルとは仮想化されたビューである。架空のビューを作り各種操作が行われたときの動作を記述する。[[GUI]]などの要素がない架空のものであるため、[[マウスカーソル]]やボタンを座標などを考える必要がなく「ボタンを押した際の動作」などの[[デバッグ]]が簡単に行える。
+
    using Microsoft.Xna.Framework.Graphics;
 +
    using Microsoft.Xna.Framework.Input;
  
===ビュー===
+
    public class Model3D
ビューとはビューモデルに装飾して実際の[[UI]]を貼付ける部分をいう。
+
    {
 +
        public static Dictionary<string, Model> ModelsCache = new Dictionary<string, Model>();
  
===コントローラー===
+
        public Game Game;
あまり考える必要はない。
 
  
==使い方==
+
        public Model Model;
===Mac===
 
  
==関連項目==
+
        public Matrix[] Bones;
  
==参考文献==
+
        /// <summary>
{{reflist}}
+
        /// 回転(単位:degree)
 +
        /// </summary>
 +
        public Vector3 Rotation;
  
{{stub}}
+
        /// <summary>
 +
        /// 位置
 +
        /// </summary>
 +
        public Vector3 Position;
 +
 
 +
        public Model3D(Game game, string modelName)
 +
        {
 +
            // game
 +
            this.Game = game;
 +
 
 +
            // model
 +
            if (!ModelsCache.ContainsKey(modelName))
 +
            {
 +
                var model = game.Content.Load<Model>(modelName);
 +
                ModelsCache.Add(modelName, model);
 +
            }
 +
            this.Model = ModelsCache[modelName];
 +
 
 +
            // boneを抽出しておく。
 +
            this.Bones = new Matrix[this.Model.Bones.Count];
 +
            this.Model.CopyAbsoluteBoneTransformsTo(this.Bones);
 +
 
 +
            // BasicEffectの基本設定をDrawメソッドの中で毎回やるのは非常に無駄なので事前に設定しておく。
 +
            // Androidでは実質的にGeometry Instancingの様な技法が使えないのでDrawコールの軽量化は重要になる。
 +
            // (ジオメトリシェーダーに対応したOpenGL ES 3.2対応端末が広く普及するのは10年後だろう)
 +
            // これだけでもショボいAndroid(F-04G)では300モデルでフレームレートが20%くらい違ってくる。
 +
            foreach (var mesh in this.Model.Meshes)
 +
            {
 +
                foreach (BasicEffect effect in mesh.Effects)
 +
                {
 +
                    effect.EnableDefaultLighting();
 +
                    effect.LightingEnabled = true;
 +
                    effect.PreferPerPixelLighting = true;
 +
                }
 +
            }
 +
        }
 +
        // Camera引数は「MonoGameのカメラを作る」を参照
 +
        public void Draw(Camera camera)
 +
        {
 +
            var meshes = this.Model.Meshes;
 +
            var bones  = this.Bones;
 +
 
 +
            // 回転をMatrixに変換
 +
            var yaw  = MathHelper.ToRadians(this.Rotation.Y);
 +
            var pitch = MathHelper.ToRadians(this.Rotation.X);
 +
            var roll  = MathHelper.ToRadians(this.Rotation.Z);
 +
            var rotation = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll);
 +
 
 +
            // 位置をMatrixに変換
 +
            var postion = Matrix.CreateTranslation(this.Position);
 +
 
 +
            // この手の定番計算はCameraクラスを作っておくと捗る。
 +
            var view = camera.View;
 +
            var projection = camera.Projection;
 +
         
 +
            foreach (var mesh in meshes)
 +
            {
 +
                // 一般的な四則演算と異なり、行列の乗算除算は順番が重要。
 +
                // そのため「rotation」と「position」を入れ替えると残念な結果になる。
 +
                var world = bones[mesh.ParentBone.Index] * rotation * postion;
 +
 
 +
                foreach (BasicEffect effect in mesh.Effects)
 +
                {
 +
                    effect.World = world;
 +
                    effect.View = view;
 +
                    effect.Projection = projection;
 +
                }
 +
                mesh.Draw();
 +
            }
 +
        }
 +
    }
 +
</source>
 +
 
 +
== 関連項目 ==
 +
* [[MonoGameのカメラを作る]]
 +
 
 +
* [[Geometry Instancing]]
 +
* [[MonoGameで使う3DモデルをFusion360で作成する]]
 +
* [[MacOS版のMonoGameのPipeline.appで3Dモデルをビルドできない]]
 +
* [[MonoGameで外部のXNBファイルを読み込む]]
 +
 
 +
[[category: MonoGame]]

2018年1月30日 (火) 03:26時点における最新版

MonoGameでの3Dモデルの「表示」について解説しているサイトは多数あるが、「回転」や「位置」について記述しているサイトはほとんどないようだ。そもそもモデルと位置情報などを切り離して管理すること自体が馬鹿げているので、それらを管理するクラスを作っておくと捗る。

また、Drawコールの中でBasicEffect周りを毎回設定している記述も多い。PCのようにCPUが高速な環境では問題にならないのだろうが、Androidなどではそれだけで結構な負担になる。Android端末の多くが「OpenGL ES 2.0」であるためGeometry Instancingのようなジオメトリシェーダーを使った技術も使えないのでDrawコールを極力軽くする努力が求められる。 

追記:小手先の最適化より自前Modelクラスを実装してメッシュ結合できるようにした方が速そう。なお純正のModelクラスはsealed指定されているので継承できない。

    using System;
    using System.Collections.Generic;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;

    public class Model3D
    {
        public static Dictionary<string, Model> ModelsCache = new Dictionary<string, Model>();

        public Game Game;

        public Model Model;

        public Matrix[] Bones;

        /// <summary>
        /// 回転(単位:degree)
        /// </summary>
        public Vector3 Rotation;

        /// <summary>
        /// 位置
        /// </summary>
        public Vector3 Position;

        public Model3D(Game game, string modelName)
        {
            // game
            this.Game = game;

            // model
            if (!ModelsCache.ContainsKey(modelName))
            {
                var model = game.Content.Load<Model>(modelName);
                ModelsCache.Add(modelName, model);
            }
            this.Model = ModelsCache[modelName];

            // boneを抽出しておく。
            this.Bones = new Matrix[this.Model.Bones.Count];
            this.Model.CopyAbsoluteBoneTransformsTo(this.Bones);

            // BasicEffectの基本設定をDrawメソッドの中で毎回やるのは非常に無駄なので事前に設定しておく。
            // Androidでは実質的にGeometry Instancingの様な技法が使えないのでDrawコールの軽量化は重要になる。
            // (ジオメトリシェーダーに対応したOpenGL ES 3.2対応端末が広く普及するのは10年後だろう)
            // これだけでもショボいAndroid(F-04G)では300モデルでフレームレートが20%くらい違ってくる。
            foreach (var mesh in this.Model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.LightingEnabled = true;
                    effect.PreferPerPixelLighting = true;
                }
            }
        }
        // Camera引数は「MonoGameのカメラを作る」を参照
        public void Draw(Camera camera)
        {
            var meshes = this.Model.Meshes;
            var bones  = this.Bones;

            // 回転をMatrixに変換
            var yaw   = MathHelper.ToRadians(this.Rotation.Y);
            var pitch = MathHelper.ToRadians(this.Rotation.X);
            var roll  = MathHelper.ToRadians(this.Rotation.Z); 
            var rotation = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll);

            // 位置をMatrixに変換
            var postion = Matrix.CreateTranslation(this.Position);

            // この手の定番計算はCameraクラスを作っておくと捗る。
            var view = camera.View;
            var projection = camera.Projection;
          
            foreach (var mesh in meshes)
            {
                // 一般的な四則演算と異なり、行列の乗算除算は順番が重要。
                // そのため「rotation」と「position」を入れ替えると残念な結果になる。
                var world = bones[mesh.ParentBone.Index] * rotation * postion;

                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                }
                mesh.Draw();
            }
        }
    }

関連項目[編集 | ソースを編集]