
【Unity】meshlabで出力した点群データを軽量化して可視化する
2021.12.20
Unityで点群の表示に挑戦!
スマートフォンアプリや、FacebookGameroom、Oculus などの VR のゲームまで開発することのできるソフト「Unity(ユニティ)」。
近頃はゲームアプリだけではなく、土木関係のビューワーや、医療関係のシュミレーションでも使用されています。
Unity を使用すれば、3Dモデルを利用した作品を簡単に作成することができます。
ですが、一番大変なのはモデリングの作業です。
人物のモデルの場合、プロのモデラーに頼むと、1つでも数万円から数十万円の費用が掛かることも。
点群とは
実際に存在する建物や地形などを、簡単に表現する方法があります。
それが「点群」です。
ドローンなどで撮影した航空写真から、自動で作成した点の集まりです。
拡大すると点の集まりですが、全体的に見ると 3Dモデルと遜色ありません。
これを Unity で扱っていきましょう。
注意点
- Unity の ve rは 2018.3.14f1 です。
それ以前、以後のバージョンではエラーが出る恐れがあります。 - Point Cloud Free Viewer の情報は、2020年2月20日現在のものです
- 点群を扱うので、ある程度のマシンスペックを必要とします。
メモリは 16GB 以上を、グラフィックボードは GEFORCE GTX1060 以上を推奨します。
Unity Assets Store からアセットをインストール
今回は、「Point Cloud Free Viewer」というアセットを利用します。
使用するアセットはこちら!
【Point Cloud Free Viewer】
https://assetstore.unity.com/packages/tools/utilities/point-cloud-free-viewer-19811

「Open in Unity 」を選択し、その後 Unity で「Import」を押します。
(画像をクリックすると、別ウィンドウで大きなサイズの画像が開きます)
そして、Unity のヒエラルキーの横にある、プロジェクトビューのなかに「Point Cloud」というフォルダができていれば完了です。
【注意】点群の拡張子を確認する
点群には、加工に使用したソフトによって様々な拡張子があります。
今回使用しているアセットは、「off」という拡張子のみに対応しています。
「off」でない場合は、「meshlab」などで変更しましょう。
変換できる拡張子
ply、obj、3ds、ptx、v3d、pts、apts、xyz、txt など
変換できない拡張子
csv、lsproj、cl3 など
Unity に点群を読ませる
それでは、実際に Unity に点群を読ませていきます。
アセットをインポートした時に「Point Cloud」ができたと思います。
この中に「Example」というシーンが出来ているので、それを選択してください。
開くと、ヒエラルキーに「PointCloudManager」というゲームオブジェクトがあるので、そこに注目してください。
そこに、オブジェクトと同名のスクリプトがアタッチされています。
そこの「Data Path」を点群のデータのものにします。
パスを見ていただければわかると思いますが、「PointCloud」のフォルダ直下に入れるようにして下さい。
「playボタン」を押せば、下記画像のように点群が表示されます。
カメラ操作をつける
これだけでは、視点の変更ができないので、カメラに以下のスクリプトをアタッチして、視点変更ができるようにしましょう。
スクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SocialPlatforms; using UnityEngine.EventSystems; using UnityEngine.UI; //カメラを動かすためのスクリプト public class Cameramover : MonoBehaviour { public int movepower; public int rockon; GameObject mainCamObj; Camera cam; //カメラの移動量 [SerializeField, Range(0.1f, 10.0f)] private float _positionStep = 2.0f; //マウス感度 [SerializeField, Range(30.0f, 150.0f)] private float _mouseSensitive = 90.0f; //カメラ操作の有効無効 private bool _cameraMoveActive = true; //カメラのtransform private Transform _camTransform; //マウスの始点 private Vector3 _startMousePos; //カメラ回転の始点情報 private Vector3 _presentCamRotation; private Vector3 _presentCamPos; //初期状態 Rotation private Quaternion _initialCamRotation; //UIメッセージの表示 private bool _uiMessageActiv; void Start() { _camTransform = this.gameObject.transform; //初期回転の保存 _initialCamRotation = this.gameObject.transform.rotation; mainCamObj = GameObject.Find("Camera"); cam = mainCamObj.GetComponent<Camera>(); } void Update() { CamControlIsActive(); //カメラ操作の有効無効 if (_cameraMoveActive) { if (!(EventSystem.current.IsPointerOverGameObject())) { ResetCameraRotation(); //回転角度のみリセット CameraRotationMouseControl(); //カメラの回転 マウス CameraSlideMouseControl(); //カメラの縦横移動 マウス CameraPositionKeyControl(); //カメラのローカル移動 キー float scroll = Input.GetAxis("Mouse ScrollWheel") * 15.0f * _positionStep;//マウススクロール transform.position += transform.forward * scroll; } } } //回転を初期状態にする private void ResetCameraRotation() { if (Input.GetKeyDown(KeyCode.P)) { this.gameObject.transform.rotation = _initialCamRotation; Debug.Log("Cam Rotate : " + _initialCamRotation.ToString()); } } //カメラの回転 マウス private void CameraRotationMouseControl() { //左右クリックによる視点を変えながらの前進後退をするために両方に回転を割り当て //単純な回転は右クリックのみで可能 if (Input.GetMouseButtonDown(1)) { _startMousePos = Input.mousePosition; _presentCamRotation.x = _camTransform.transform.eulerAngles.x; _presentCamRotation.y = _camTransform.transform.eulerAngles.y; } if (Input.GetMouseButton(1)) { /* //(移動開始座標 - マウスの現在座標) / 解像度 で正規化 float x = (_startMousePos.x - Input.mousePosition.x) / Screen.width; float y = (_startMousePos.y - Input.mousePosition.y) / Screen.height; //回転開始角度 + マウスの変化量 * マウス感度 float eulerX = _presentCamRotation.x + y * _mouseSensitive; float eulerY = _presentCamRotation.y + x * _mouseSensitive; _camTransform.rotation = Quaternion.Euler(eulerX, eulerY, 0); */ //(移動開始座標 - マウスの現在座標) / 解像度 で正規化 float x = (_startMousePos.x - Input.mousePosition.x) / Screen.width; float y = (_startMousePos.y - Input.mousePosition.y) / Screen.height; //回転開始角度 + マウスの変化量 * マウス感度 float eulerX = _presentCamRotation.x + y * _mouseSensitive; float eulerY = _presentCamRotation.y + x * _mouseSensitive; _camTransform.rotation = Quaternion.Euler(eulerX, eulerY, 0); } if (Input.GetMouseButtonDown(0)) { _startMousePos = Input.mousePosition; _presentCamRotation.x = _camTransform.transform.eulerAngles.x; _presentCamRotation.y = _camTransform.transform.eulerAngles.y; } if (Input.GetMouseButton(0)) { //(移動開始座標 - マウスの現在座標) / 解像度 で正規化 float x = (_startMousePos.x - Input.mousePosition.x) / Screen.width; float y = (_startMousePos.y - Input.mousePosition.y) / Screen.height; //回転開始角度 + マウスの変化量 * マウス感度 float eulerX = _presentCamRotation.x + y * _mouseSensitive*1.5f; float eulerY = _presentCamRotation.y + x * _mouseSensitive*1.5f; //計算した値を角度に適用 _camTransform.rotation = Quaternion.Euler(eulerX, eulerY, 0); } } //カメラの移動 private void CameraSlideMouseControl() { //予備関数です。空っぽです。 } //カメラのローカル移動 private void CameraPositionKeyControl() { Vector3 campos = _camTransform.position; //w前進 s後退 d右 e左 q下降 e上昇 qとeについては重力優先 //前進後退はロック if (Input.GetKey(KeyCode.D)) { campos += _camTransform.right * Time.deltaTime * _positionStep*movepower; } if (Input.GetKey(KeyCode.A)) { campos -= _camTransform.right * Time.deltaTime * _positionStep * movepower; } if (Input.GetKey(KeyCode.E)) { campos += _camTransform.up * Time.deltaTime * _positionStep * movepower; } if (Input.GetKey(KeyCode.Q)) { campos -= _camTransform.up * Time.deltaTime * _positionStep * movepower; } if (Input.GetKey(KeyCode.W)) { campos += _camTransform.forward * Time.deltaTime * _positionStep * movepower; } if (Input.GetKey(KeyCode.S)) { campos -= _camTransform.forward * Time.deltaTime * _positionStep * movepower; } //右と左同時クリックで後退 if ((Input.GetMouseButton(1))&&(Input.GetMouseButton(0))) { campos -= _camTransform.forward * Time.deltaTime * _positionStep * movepower; } //右のクリックで if ((Input.GetMouseButton(0))&&!(Input.GetMouseButton(1))) { campos += _camTransform.forward * Time.deltaTime * _positionStep * movepower; } //中ボタンでパン if((Input.GetMouseButton(2))){ campos -= _camTransform.right * Time.deltaTime * _positionStep*movepower*Input.GetAxis("Mouse X")*6; campos -= _camTransform.up * Time.deltaTime * _positionStep * movepower*Input.GetAxis("Mouse Y")*6; } //回転した値を位置に適用 _camTransform.position = campos; } //---------------------------------------------------------------------------------以下予備システム無視 ----------------------------- //UIメッセージの表示 private IEnumerator DisplayUiMessage() { _uiMessageActiv = true; float time = 0; while (time < 2) { time = time + Time.deltaTime; yield return null; } _uiMessageActiv = false; } void OnGUI() { if (_uiMessageActiv == false) { return; } GUI.color = Color.black; if (_cameraMoveActive == true) { GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height - 30, 100, 20), "カメラ操作 有効"); } if (_cameraMoveActive == false) { GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height - 30, 100, 20), "カメラ操作 無効"); } } //カメラ操作の有効無効 public void CamControlIsActive() { if (Input.GetKeyDown(KeyCode.Space)) { _cameraMoveActive = !_cameraMoveActive; if (_uiMessageActiv == false) { StartCoroutine(DisplayUiMessage()); } Debug.Log("CamControl : " + _cameraMoveActive); } } } |
カメラの操作方法
操作方法は以下のようになっています。
W、A、S、D | 前後左右の移動 |
Q、E | 上昇、降下 |
右ドラッグ | カメラの回転 |
左ドラッグ | 移動 |
中ドラッグ | パン |
軽量化とエラー対策
この状態で、ビルドしようとするとエラーが出ることがあります。
「PointCloudManager」のフォルダにアクセスする機能が、悪影響を及ぼしています。
プレイ状態で、点群のオブジェクトをプレハブで保存します。
そのプレハブを配置して、「PointCloudManager」はプロジェクトビューから削除してしまいます。

そうすることで、エラーも出ず、起動が早くなります。
さいごに
いかがでしたか?
実際の建物や、地形などを使用しているので、モデリングの代用という考え以上に、いろいろと楽しい使い方が出来るかもしれませんね!
ご紹介した内容を参考に、点群データを使って、地形や建物を表現してみてください!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン