- 投稿日:2020-06-19T19:14:17+09:00
UnityのWebGLビルドでgif撮影/保存する
PartyParrotぽいVRMサイト作ってみるならgif保存がしたい
ホントにそれだけでgif保存に手を出してみました。意外とやっている話が見つからずjsのライブラリを使ってやることにしました。
jslibとの連携はUnityのjslib経由で別ファイルjsを呼び出すで書いてある手法を使用しています。
実装したのがこちら、VRMを移動させてPartyParrot風のgifをダウンロードします
↓ ダウンロードしたgif
かなり雑ですがキャプチャできています。今のままではファイルサイズが大きいため、容量削減のために解像度を低くしたりなどの一工夫が必要にはなる気がしています。
使ったjsのライブラリはccapture.jsというものです。
そこのissueにgif capture result contains complete black framesというUnityのWebGLを表示しているcanvasをキャプチャしても真っ暗な画面になるというものがありました。実際自分も同じ現象にあって調べるうちに辿り着きました。そしてissueの最後に
WaitForEndOfFrame()で最後のフレームまで待ってからjsでcaptureを実行することで成功しました。感謝?実装
PartyParrotVRMはuGUIボタンからイベントが始まるのでその処理を基準に書きます。
jslibとNativeExecuter.csはUnityのjslib経由で別ファイルjsを呼び出すを使っているものとして省きます。
using System; using System.Collections; using UnityEngine; using UnityEngine.UI; namespace Sample { public class DownloadGif : MonoBehaviour { [SerializeField] public class CaptureParameter { public int index; public int max; } private const int CAPTURE_COUNT = 10; [SerializeField] private Button downloadGifButton; private NativeExecuter executer = new NativeExecuter(); private int captureIndex = 0; public string CallbackMethodName { get { Action callback = StartNotify; return callback.Method.Name; } } void Start() { downloadGifButton.onClick.AddListener(() => { var callbackParameter = new CallbackParameter { callbackGameObjectName = gameObject.name, callbackFunctionName = CallbackMethodName }; var parameterJson = JsonUtility.ToJson(callbackParameter); executer.Execute("downloadStartGif", parameterJson); }); } public void StartNotify() { StartCoroutine(CaptureScreen()); } IEnumerator CaptureScreen() { while (captureIndex < CAPTURE_COUNT) { yield return new WaitForEndOfFrame(); captureIndex += 1; var captureParameter = new CaptureParameter { index = captureIndex, max = CAPTURE_COUNT }; var parameterJson = JsonUtility.ToJson(captureParameter); executer.Execute("captureFrame", parameterJson); } captureIndex = 0; } } }WebGLTempleteにあるindex.htmlに以下の二行を追加しておきます。
index.html<script src="https://cdn.jsdelivr.net/npm/ccapture.js/build/CCapture.all.min.js"></script> <script id="execute" src="execute.js"></script>ccapture.jsに必要な処理のようなのですがgif.worker.jsというものをダウンロードしてindex.htmlの階層配下に置いておく必要があるそうです。ないとエラーが出ました。
executer.jslet capturer = {} let captureIndex = 0 function downloadStartGif(parameter) { unityCanvas = document.getElementById('#canvas') // workersPathはgif.worker.jsの置き場です。この場合は`js/gif.worker.js`という置き方をしています。 capturer = new CCapture( { format: 'gif', workersPath: 'js/' } ) capturer.start() unityInstance.SendMessage(parameter.callbackGameObjectName, parameter.callbackFunctionName) } function captureFrame(parameter) { capturer.capture(unityCanvas) if (parameter.index === parameter.max) { capturer.stop() capturer.save() } } function recieveMessage(event) { var data = JSON.parse(event.detail) var methodName = data.methodName var parameter = data.parameter try { parameter = JSON.parse(parameter) } catch (e) { parameter = null } eval(`${methodName}(parameter)`) } window.addEventListener('unityMessage', recieveMessage, false)まとめ
Unityでgif作成処理と保存処理を作成するよりもネイティブのライブラリを使った方がいいかなと思い今回はネイティブ側で実装しました。issueにたどり着くまでに時間が少しかかりましたがわかってしまえば結構簡単でした。
実はキャプチャのライブラリをいくつか試しているうちにUnityのWebGL canvasのContextはwebgl2で取得できるという知見を得ました。WebGL2RenderingContextで詳しい描画のデータが取れるようです。ネイティブ書くときに必要になるかもな…というメモ…
- 投稿日:2020-06-19T16:51:31+09:00
Android Build時の unityeditor.buildplayerwindow+buildmethodexception
- 投稿日:2020-06-19T15:21:18+09:00
静岡県の点群データをUnityで表示してOculus Questで見る
はじめに
静岡県が蓄積・公開している3次元点群データをUnityで表示してVRで中に入ってみよう、という試みの忘備録として。
このやり方だと遠景はきれいだけど近付くと見えなくなってしまいます。点なので。
また、無料の範囲で、あまり難しいことを考えず、とりあえず表示するだけ表示してみたい…程度の気持ちによるものなので途中でズルをしています。今のところ動いていますが問題があったら教えていただけるとありがたいです。環境と使用ソフト
OS:Windows10
Unity:2018.4.10f
点群データ処理:MeshLab、CloudCompare手順
Unityでの設定
最初にOculus Questで実行可能なapkファイルを作成するための設定を行います。
若干時間がかかるため、実行中に他の作業を進めるのがいいかと思います。
1. File>Build SettingsをAndroidに設定(switch platform)
2. Edit>Project Settings>XR SettingsのVirtual Reality Supportedにチェックを入れ、リストにOculusを追加
3. Other Settings>Identification>Minimum API Levelを「marshmallow」に設定(KitKatでもいけた)し、Package Nameを適当に設定
4. AssetStoreで「Oculus Integration」をImport(特に時間かかるため他の作業を進める)
5. Assets内のOculus>VR>Prefabsの「OVRPlayerContoroller」をHierarchyにドラッグ&ドロップし、もとからあったMain Cameraを削除静岡県点群DBのデータ(lasデータ)を取得
静岡県点群DBにアクセスし、「閲覧・DL」をクリックします。
すると地図が表示されるので、行ってみたい場所のマークアップをクリックします。
クリックすると地図上に赤い四角形が表示されます。これが点群データを取得している範囲です。
赤い四角をクリックするとダウンロードページが表示されるため、「3Dデータ」項目内の「.las」のデータを全てダウンロードします。
今回は「白糸の滝」付近のデータを取得しました。lasデータをUnityで読める形に変換
lasデータのままだと読み込めないのでoffデータに変換していきます。
lasデータをtxtデータに変換
CloudCompareを使用してtxtデータに変換します。
変換のために以下のバッチファイルを用意します。
書きやすさのためCloudCompareのディレクトリを環境変数PATHに設定しています。
また、各ファイルのパスを絶対パスで書いたため一部伏せています。適宜書き換えてください。
(バッチファイル作成のため参考にした記事)CloudCompare -SILENT ^ -AUTO_SAVE OFF ^ -O -GLOBAL_SHIFT AUTO "***\Downloads\28XXX00030006-1.las" ^ -O -GLOBAL_SHIFT AUTO "***\Downloads\28XXX00030006-2.las" ^ -O -GLOBAL_SHIFT AUTO "***\Downloads\28XXX00030006-3.las" ^ -O -GLOBAL_SHIFT AUTO "***\Downloads\28XXX00030006-4.las" ^ -DROP_GLOBAL_SHIFT ^ -MERGE_CLOUDS ^ -SS SPATIAL 0.2 ^ -REMOVE_ALL_SFS ^ -C_EXPORT_FMT ASC ^ -PREC 3 ^ -SAVE_CLOUDS FILE "***\shiraito.txt"これを実行すれば
X Y Z R G B
の形でshiraito.txtファイルが作られます。txtデータをoffデータに変換
txtファイルをMeshLabで開きます。(Import Mesh)
開く際のオプションでは以下の二つをこのように変更します。
Point format:X Y Z R G B
Separator:SPACE
読み込めたら「Export Mesh As」で「.off」形式を選び、出力します。
ここでは「shiraito.off」とします。Unityで読み込む(ズルするところ)
ここがズルです!!
Point Cloud Free Viewerを使用して点群のPrefabを作ります。
1. 点群Prefab作成用のUnityのプロジェクトを新しく作成します!ここではVR用の特別な設定は要りません。
2. AssetStoreで「Point Cloud Free Viewer」を検索してImport。まずは全部Importします。
3. インポートされたアセット内のPointCloudフォルダに作成したoffファイルを入れます。
4. AssetsのPointCloudの中にある「Example」というSceneを選択します。
5. Hierarchy内のPointCloudManagerを選択し、InspectorのDataPathを書き換えます。PointCloudフォルダ内にshiraito.offがあるはずなので、「/PointCloud/shiraito」とします。
6. Scaleの値を書き換えます。ここでは「1」とします。(デフォルトの「100」だとカスカスになったため)
7. 「Play」を押すとAssets>Resource>PointCloudMeshes内にshiraitoのPrefabが作られます。※一度Playを押してしまうと、そのあとScaleの値を変更してもPrefabは変わりません。PointCloudMeshes内のPrefabを削除してからPlayを押すと反映されたものが作られます。
Oculus Questで表示する
ここからはもとのUnityに戻ります。
1. Point Cloud Free ViewerをImportします。ただし、ここではMaterialsとShadersにだけチェックを入れます。
2. 先ほどのUnityプロジェクトの、ResourceフォルダにあるPointCloudMeshesを、こちらのプロジェクトのResourceフォルダにコピペしてきます。
3. Unity画面に戻り、PointCloudMeshes内のshiraitoのPrefabをHierarchyにドラッグ&ドロップします。このとき角度がおかしければInspectorで角度を編集します。(私の場合X軸が90度回転していたためRotateのXを「-90」としました)
4. apkファイルを作成し、SideQuestでインストールすれば「提供元不明のアプリ」から点群データの景色を見ることができます。
最後に
PointCloudをMaterialsとShadersだけ残して消してしまうのは良くないやり方だとは思います…。
- 投稿日:2020-06-19T13:35:29+09:00
UnityのWebGLで作ったサイトにドラッグ&ドロップでVRMを出現させる
VRMの切り替えをドラッグ&ドロップでやりたい
VRMをPartyParrot風に表示できるPartyParrotVRMを作ってみたという記事にまとめている「PartyParrotVRM」を作っている際に、VRMの切り替えをドラッグ&ドロップでやりたいと思いました。
途中で固まってしまうのがネックなのですが別スレッドで実行するなどの処理がうまくいかず現時点ではこのようになっています…一応切り替えれているのでいいかなと…
drag&dropを検知してUnityにデータを渡す
VRMのSDKを入れたプロジェクトを作成しておきます。
そして、以下のようなC#を定義してGameObjectに追加します。using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; using VRM; namespace Provider { public class VrmProvider : MonoBehaviour { private int length; private int index; private List<byte> byteContent = new List<byte>(); public void ByteLength(int length) { this.length = length; index = 0; byteContent.Clear(); } public void AddRange(string byteString) { var decode = Convert.FromBase64String(byteString); byteContent.AddRange(decode); index += 1; if (length != index) return; Spawn(); } public void Spawn() { var vrmBytes = byteContent.ToArray(); var context = new VRMImporterContext(); context.ParseGlb(vrmBytes); context.Load(); context.ShowMeshes(); context.EnableUpdateWhenOffscreen(); } } }上記のcsのメソッドに対してjsからデータを送り込んでいきます。
UnityのWebGLTempleteを編集して以下のjsを追加して、index.htmlにもscriptタグで追加しました。
dragAndDrop.jsdocument.addEventListener('DOMContentLoaded', function () { var dropArea = document.getElementById('screen') function confirmVrmFile(files) { let length = files.length, file for (let i = 0; i < length; i++) { file = files[i] let isVrmFile = file.name.endsWith('.vrm') if (isVrmFile) { // .vrmのbyteをstringに変換する let reader = new FileReader() reader.onload = function () { let source = this.result let bytes = new Uint8Array(source) let len = source.byteLength let byteString = "" for (var i = 0; i < len; i++) { byteString += String.fromCharCode(bytes[i]) } var base64String = window.btoa(byteString) sendUnity(base64String) }; reader.readAsArrayBuffer(file) break } } } function sendUnity(base64String) { // stringの容量が大きいままSendMessageに渡そうとするとエラーが起きるので文字列を分割する let splitLength = 1000 let len = parseInt(base64String.length / splitLength) unityInstance.SendMessage("VrmProvider", "ByteLength", len + 1) for (let i = 0; i < len; i++) { let next = base64String.substr(i * splitLength, splitLength) unityInstance.SendMessage("VrmProvider", "AddRange", next) } let last = base64String.substr( splitLength * len, base64String.length % splitLength ) unityInstance.SendMessage("VrmProvider", "AddRange", last) } dropArea.addEventListener('dragover', function (event) { event.preventDefault() event.dataTransfer.dropEffect = 'copy' dropArea.classList.add('dragover') }) dropArea.addEventListener('dragleave', function () { dropArea.classList.remove('dragover') }) dropArea.addEventListener('drop', function (event) { event.preventDefault() dropArea.classList.remove('dragover') confirmVrmFile(event.dataTransfer.files) }) })実際にPartyParrotVRMというので使っているものから少し変えていますがこれでドラッグ&ドロップで動くと思います。
まとめ
画面が固まらないようにするのができれば切り替えたいとは思っています。最低限の切り替え処理にはなりますがそんなにコードを書かなくてもできたので便利だなと思っています。VRMも簡単に扱えてとても便利な印象を持ちました。
- 投稿日:2020-06-19T10:20:01+09:00
Virtual Chara開発 - その2
店舗接客などに使える仮想のキャラクターが扱えるアプリ開発をしました。そちらの説明はこちらになります。
https://qiita.com/NestVisual/items/fe4dd168178e27156682環境:
Windows 10
Unity 2018.4.1
RealSense D435iNuitrack:
Nuitrackは3Dセンサーを扱うソフトで、主に全身骨格のトラッキング、顔のトラッキング、に焦点を当てていますが、ジェスチャー認識もあります。RealSense D435は、フロントページに掲載されているいくつかのサポートされている3Dセンサーの中に含まれています。
https://nuitrack.com/
また、Unityだけでなく、Unreal Engineのインストール方法やチュートリアルが記載された文書化ページもあります。
https://download.3divi.com/Nuitrack/doc/Installation_page.htmlNuitrackをインストールして、現在のプロジェクトに統合しようとしたところ、OpenCVと干渉してエラーが発生しました。シーン内でNuitrackを無効にするとエラーが消えていました。Nuitrackには顔トラッキングが付いているので、それを使って何ができるかを見てみようと思って、とりあえずNuitrackだけで新しいシーンを作ってみました。
すべてのチュートリアルの中で、「Animating the avatar using skeleton」というのが一番参考になりました。無料のUnityアセットストアモデルの "Unity chan "をNuitrackのトラッキングされたスケルトンでアニメーションします。 結果はかなり良いものになりました。チュートリアルでは、間接マッピングと直接マッピングの使用例が紹介されています。 間接マッピングは、モデルを歪めることなく、追跡された骨格の四肢の位置に基づいてキャラクターの四肢を移動させるので、このプロジェクトには最適だと思いました。直接マッピングは、モデルを歪めて、手足の真の位置をより正確に表現します。 このタイプは、VR ゲームなど、没入感が大切なものに適しているかもしれません。キャラクターの体との解離を感じないようにするためです。また、モデルが利用者に近い等身であれば、間接マッピングよりも見栄えが良いかもしれません。
Vroid:
Unityちゃんモデルとは別のモデルが使えるかどうかを確認するために、VRoidというアプリケーションを使ってみました。
https://vroid.com/en/
キャラクターをカスタマイズしてオリジナルにする機能があります。一時的に使うだけなので、プリセットキャラの一つをエクスポートしました。「Unityちゃん」のモデルとかなり似たような設定します。全ての手足にコリダーが付属しています。ぶつかるたびにギクシャクした動きをしていたので、これで手足が体に入れるようになったとはいえ、個別に外しました。
次回はNuitrackのデバッグに関して投稿します。それではまた
- 投稿日:2020-06-19T08:18:03+09:00
Unityのjslib経由で別ファイルjsを呼び出す
Unityからhoge.jsのhogeMethodを呼び出したい!
PartyParrotVRMというサイトを作っている際にjsの処理をライブラリ使ったりして行いたいと思いました。
その際に使った方法をメモ書きしておきます。
C#とjslibの連携
C#からの呼び出し処理
CallbackParameter.cs[Serializable] public class CallbackParameter { public string callbackGameObjectName; public string callbackFunctionName; }void Start() { var executer = new NativeExecuter(); var callbackParameter = new CallbackParameter { callbackGameObjectName = gameObject.name, callbackFunctionName = "Callback" }; var parameterJson = JsonUtility.ToJson(callbackParameter); executer.Execute("hogeMethod", parameterJson); } public void Callback() { Debug.Log("callback from js"); }C#からは呼び出すjsのメソッド名とjsonを渡すようにしています。jsonであればjsで簡単にparseができるためです。今回はCallbackを受けるGameObjectのメソッド名を渡しています。
次にjslibを呼びだすcsです
NativeExecuter.csusing System.Runtime.InteropServices; using UnityEngine; namespace PartyParrotVRM { public class NativeExecuter { #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern void execute(string methodName, string parameter); #endif public void Execute(string methodName, string parameter = "{}") { #if UNITY_WEBGL && !UNITY_EDITOR execute(methodName, parameter); #else Debug.Log($"call native method: {methodName}, parameter : {parameter}"); #endif } } }上記のように書けば共通処理としてネイティブプラグインを呼び出すことができます。またEditor実行時に確認できるようにDebug.Logを仕込んでおくと便利です。
var jsBridge = { execute: function(methodName, parameter) { // jsの文字列に変換する methodName = Pointer_stringify(methodName) parameter = Pointer_stringify(parameter) // 実行するメソッド名とパラメータをまとめる var jsonObj = {} jsonObj.methodName = methodName jsonObj.parameter = parameter var argsmentString = JSON.stringify(jsonObj) // カスタムイベントを作成して発行する var event = new CustomEvent('unityMessage', { detail: argsmentString }) window.dispatchEvent(event) } } mergeInto(LibraryManager.library, jsBridge);UnityのPlugins配下に置いておく.jslibになります。C#から受け取ったものはjsonにまとめてカスタムイベントに入れて発行します。jslibを調べるとよくpostMessageが使われているのですが何故CustomEventなのかと言うと、jsのライブラリによってはpostMessageを使っているものもあり予期せぬ挙動が起こるためにCustomEventにして回避しています。
今回、呼び出す.jsを
hoge.js、呼び出すメソッド名をhogeMethodとしておきます。(hoge.jsはindex.htmlに追加しているものとします)hoge.jsfunction hogeMethod(parameter) { console.log(parameter) // C#から指定されていたGameObject名とメソッド名を使ってコールバック処理を行う unityInstance.SendMessage(parameter.callbackGameObjectName, parameter.callbackFunctionName) } function recieveMessage(event) { var data = JSON.parse(event.detail) var methodName = data.methodName var parameter = data.parameter try { parameter = JSON.parse(parameter) } catch (e) { parameter = null } // C#から指定されているメソッドを呼び出しparameterを渡す eval(`${methodName}(parameter)`) } // unityMessageというCustomEventを受け取る window.addEventListener('unityMessage', recieveMessage, false)以上で全体の処理です。これでC#から
hoge.jsからhogeMethodを呼び出すことができます。まとめ
現時点ではこれが汎用的でC#から操作しやすいネイティブプラグインとなっていると思います。jsonを使えば言語ごとの型変換や引数の数に対応しなくてよくなるので書きやすいです。これはjsだとparseが簡単だというところもありますが、他のiosやwindowsなどのネイティブプラグインでもjsonでパースさせて解釈させる部分を作ればかなり簡単に受け渡しができるんじゃないかと思います。
hoge.jsなどの自前scriptなどはWebGLTempleteを使うことで簡単にビルドに組み込めるので必要があれば使うことをお勧めします。
- 投稿日:2020-06-19T01:00:08+09:00
Unity シングル・ダブルクリック 回数に応じたイベント実行
Unityでクリック数に応じたイベント処理を実装したい場合、
どう記述すべきか簡単に考えたので自分用のメモとして残す。ClickCounter.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class ClickCounter : MonoBehaviour { int counter; bool isClickStart; void Start() { counter = 0; isClickStart = false; } void Update() { // メモ クリックを検知したら、加算 if (Input.GetMouseButtonDown(0)) { counter++; StartCoroutine("MouseCounter"); } } IEnumerator MouseCounter() { //メモ 1回目のクリックを検知した後、if内の処理が実行されないよう制御する if (isClickStart == false) { isClickStart = true; //メモ 下記の時間待機 この設定した時間の間、クリックされるとupdateにあるcounterが加算される yield return new WaitForSeconds(0.2f); //メモ クリックされた回数に応じて処理が分岐する if (counter > 1) { Debug.Log("複数クリック処理"); } else { Debug.Log("ワンクリック処理"); } //メモ 初期化(未検知に設定) isClickStart = false; counter = 0; } } }処理概要
一定時間内にクリックされた回数を加算し、回数に応じて処理を実行する補足
・Invokeでも実装できると思うものの引数が渡せないので、今後の拡張を見据えて
今回はコルーチンを使用している
・int counter → Event e = Event.current に、counter を e.clickCount としても
問題ないが、一定時間の幅を調整したかったのでこのような形にした。







