kazumalab tech log

流行りとリラックマと嵐が大好きです。技術的ログ。

【Unity アセット真夏のアドベントカレンダー 2017】世界をローポリにしてやろうか!

かずまです。 今日は「Unity アセット真夏のアドベントカレンダー 2017」の12日目の記事になります。

昨日の記事

オノッチさんCharacter Particle!でした。

肝試しとかに使えそうで、夏にピッタリ!?(ホラー…!)

今回書くもの

さて今日は「世界をローポリにしてやろうか!」というタイトルで書いてきます。

今回メインで紹介するアセットは

このアセットになります。 お手頃価格で購入できますね!

ローポリのアセットって結構あるのですが、雰囲気に合わせたりするのが難しかったりします。 あとは通常のゲームを作ってて途中でローポリにしたい!そんな場面もあると思います。

そこでこのアセットです! モデルは準備する必要がありますが、それをローポリに変換してくれるEditor拡張のアセットです。

導入

インストールすると次のようになっています。

f:id:kazumalab:20170811231200p:plain

Editor拡張でwindow/VaccumShardersにビルドするためのEditorが入っているのでそこを開いてみます。

f:id:kazumalab:20170811231403p:plain

試して見る

とりあえずサンプルでSphereをSceneでローポリに変換してみます。

f:id:kazumalab:20170811231938p:plain

Sphereを指定して、今回は何も設定せずにGenerateしてみます。

f:id:kazumalab:20170811231955p:plain

すごく簡単にできました。 MeshもSharderはデフォルトのを使わずに独自で新たに作っています。 今回はrestoreしないにはチェックを入れていないので、Project内にMesh、Material、Prefabを作ります。

f:id:kazumalab:20170811232504p:plain

名前も自由に決められます。

できるMeshの頂点数

デフォルトのSphereは以下のようになっています。

f:id:kazumalab:20170811232608p:plain

頂点数 : 515, ポリゴン数 768 ですが、これを変換すると以下のようになります。

f:id:kazumalab:20170811232735p:plain

頂点数 : 2304, ポリゴン数 768 頂点数はかなり多くなっちゃいましたが、ポリゴン数は変わらず生成できました。

ちょっとAsset Storeにあるものをコンバートしてみる!

f:id:kazumalab:20170811234009p:plain

よく観るこのアセットです。

これをほいっと!やってみます。

f:id:kazumalab:20170811234626p:plain

若干テクスチャが変わっちゃう感じしますが、雰囲気が全く変わりますね。 これはいい感じっぽいです。」

Blenderで作ったモデルに適用してみる

f:id:kazumalab:20170811235425p:plain

適当に10分ほどで作ったモデルなので適当です。 これをコンバートしてみます。

f:id:kazumalab:20170811235554p:plain こんなに子のオブジェクトになっているのですが、大丈夫なのでしょうか? …親を指定すればすべて変換されます!

Materialのカラーだと白色になる

f:id:kazumalab:20170812001052p:plain Textureで色付けされていればコンバートがうまくいくのですが、マテリアルの色は引き継がれないようです。 手動で入れてあげればこんな感じになりました。

f:id:kazumalab:20170812001423p:plain

木のマテリアルは1つのオブジェクトで2つのマテリアルを使っていたため、コンバートした時に消えちゃいました。 残念。まぁそんな設計しなければいけるのですが。

ちょっと寄り道

いい感じになってきたので海もつけたいですね。 そこで、

これを使います! アニメーションもついていて、オブジェクトに接触する部分は水しぶきが発生します。 結構すごい。

f:id:kazumalab:20170812002230g:plain

ローポリすごーい!

まとめ

今回はローポリにしてしまうアセットを使ってみました。 これが使えればモデルを作るときにローポリで作らなくても良さそうな感じです。

明日!

明日の「Unity アセット真夏のアドベントカレンダー 2017」担当は Limes@XRDeveloperさんで「Set Pass Callを劇的に減らす「Mesh Baker」の使い方と、いくつかのモデルをHoloLensで表示した動作結果」です! Hololens!Mesh Backer!気になるものばかり!お楽しみに!

おさらい

今日使ったアセットをまとめました。

初めてのRubyGemsを作りました。

かずまです。
今回は色々困っていることがあってGemを作りました。

作ったもの

Haml-lintをAutoCorrectするものです。
今回はHash Attributeのあとにスペースを自動で入れてくれるものです。

既存のプロジェクトにHaml-Lintを入れたのですが、かなりHashのあとにスペースがなかったりしたので、これは手動でやっていたら果てしない時間がかかると思って作りました。

GlobalなRuby Gemsにはしないつもりです。
自分にとって必要だったので作成してローカルビルドして、ローカルインストールして使っています。

github.com

検証2日、実装1日ほどで作成したのでリファクタリング等はできていませんが、きちんと置き換えてくれると思います。

テストのカバレッジを上げていきたいですがmockを使ってテストを書きたかったので若干そこはまだです。

使ってみる

# bundle exec haml_lint_auto_corrector load file_name.html.haml

今回のlintの規約として

%div{ class: "sample" }
  %button{ class: "button" }= "Sample #{@user.name}"

ハッシュの間の値の前後にスペースを一つ入れるようにしないとLintで引っかかる場合に有効です。
上記コードであれば何もせずにそのまま終わります。

%div{class: "sample"}
  %button{class: "button"}= "Sample #{@user.name}"

このコードだとLintに引っかかるので自動で一つ前のコードのようにスペースを入れた形に置換してくれます。

Haml-lintにはAutoCorrectがない

Rubocopのように自動で置換してくれる機能がありません。
issuesにはたまにあがってくるそうなのですが、作者が好まないのか、Closeされるようです。

もちろん、AutoCorrectを使うのはあまりいい手だとは思えませんのでもしそれで作らないというのであればわかります。

ただ、これから作るプロジェクトであれば、作らなければOKなのですが、既存のプロジェクトだとかなりのコード量にもなっているので治すのは一苦労です。
ですので、使う用途としてはhamlファイルをいじるときに面倒なのでこのコマンドを実行して、自動修正して、あとのLintErrorは自分で修正する形がいいと思います。

実装

今回の実装

  • Pathからファイルを取得
  • バックアップを作成
  • nodeによる分割
  • 正規表現でlintに引っかかったらnodeの中身を置換
  • contextでマージ
  • バックアップが不要になったら削除

node一つがファイルの1行になっています。
contextが、置換したあとのデータファイルを持つイメージです。

難しい部分

面倒だったのが、Stringの中にインスタンスを入れて動的に変更する部分です。
上の例のコードであれば"#{@user.name}"です。

マッチしたら変更しない、にしようと思ったのですが、
1行レベルの正規表現マッチだったので、結構面倒な実装になるなーと思ったので、
計算量的には増えますが、一旦Hash全てにスペース入れたあとにマッチした部分だけもとに戻すということをしています。

if is_string_interprolation
  code_line.gsub!(/\#{\s.*\s\}/) do |word|
    word.gsub!(FRONT_REG_SPACE) { |w| "{#{w[2]}" }
    word.gsub!(BUTTOM_REG_SPACE) { |w| "#{w[0]}}" }
  end
end

色々ツッコミどころはありそうなコードですが、
そういった場合...気になったらpullrequestいただければ嬉しいです。
後にリファクタリングする予定ですが。。。

ということで今後もサービスを効率よく進めるために色々作りつつコードの綺麗さを上げて行きたいと思います。頑張ります。

Unity SteamVR Pluginのソースを読む Part2

かずまです。

今日も少しSteamVRのAPIを読んでいこうと思います。
前回は後半寝ぼけながら書いて...いました。

f:id:kazumalab:20170704070157j:plain

SteamControllerが有効になったところの処理を追っかけて終わりました。
今日はその続きから。

読むのもちろんこちら!


前提

まず、Controllerを動かすための分類分けをしておきます。

APIを提供している部分

  • SteamVR_Controller.cs

Controllerの実装を行っている部分

  • SteamVR_TrackedObject.cs
  • SteamVR_TrackedController.cs
  • open_api.cs
  • SteamVR_Utils.cs

基本開発で使うものはSteamVR_Controllerだけをスクリプトで呼び出して、ボタンが押された!などと言ったCallbackを受け取ることになります。
前回の読み会はわかりにくかったので、今回はサンプルを出して、そこから辿っていこうと思います。

// 常にControllerの位置を取るサンプルコード
void Start () {
	if (trackedController == null) {
		trackedController = this.gameObject.GetComponent<SteamVR_TrackedController>();
	}
}

void Update () {
	device = SteamVR_Controller.Input ((int)trackedController.controllerIndex);

	Debug.Log(device.transform.pos);
}

上記コードのDebugの部分、Controllerの位置を常に表示しているスクリプトについて辿って行きます。

device.transform

まずはtransformを構成している部分のお話です。
通常のMonobehaviorのtransformとは型が違うようです。

f:id:kazumalab:20170704054557p:plain

SteamVR_Controller.csの49行目に来ました。
ここではまずSteamVR_Utils.RigidTransformがどんなものなのかを見て、その後ゲッターのインスタンス生成時の引数poseが何かについて見ていきます。

SteamVR_Utils.RigidTransform

f:id:kazumalab:20170704055607p:plain

RigidTransformメソッドが何をしているのか?

ここでは4 × 4のMatrixを受け取ってVector3に変換しています。
Matrix4x4.identityは恒等行列を返しています。

恒等行列はこれです。

 {
Matrix4x4.identity =
\left(
 \begin{array}{ccc}
   1 & 0 & 0 & 0 \\\
   0 & 1 & 0 & 0 \\\
   0 & 0 & 1 & 0 \\\
   0 & 0 & 0 & 1
 \end{array}
\right)

}

UnityC#のリファレンスにも載っています。
Unity - スクリプトリファレンス: Matrix4x4.identity

// 
public RigidTransform(HmdMatrix34_t pose) {
	var m = Matrix4x4.identity;

	m[0, 0] =  pose.m0;
	m[0, 1] =  pose.m1;
	m[0, 2] = -pose.m2;
	m[0, 3] =  pose.m3;

	m[1, 0] =  pose.m4;
	m[1, 1] =  pose.m5;
	m[1, 2] = -pose.m6;
	m[1, 3] =  pose.m7;

	m[2, 0] = -pose.m8;
	m[2, 1] = -pose.m9;
	m[2, 2] =  pose.m10;
	m[2, 3] = -pose.m11;

	this.pos = m.GetPosition();
	this.rot = m.GetRotation();
}

ControllerとかHMDの座標はMatrixで返って来てるのかな?という所感です。
次にここを常に読んでいる場所があるはずです。そこを見ていきます。
座標ゴニョゴニョはわかりやすいものを参考文献に乗せています。

値がマイナスになっている箇所は位置の場合はZ軸に対して反転させている、という解釈ですが間違っていればご指摘ください。
参考にした資料にも載っていますが、GetPositionメソッドに書かれている通りです。

// SteamVR_Utils.cs 117行目
public static Vector3 GetPosition(this Matrix4x4 matrix)
	{
		var x = matrix.m03;
		var y = matrix.m13;
		var z = matrix.m23;

		return new Vector3(x, y, z);
	}

三列目を使っていることがわかります。
matrixにはGetColumn(int)というメソッドがあるのでそれで取ってきても同じことができそうですね。

ん?あれ?おかしい。
Rubyのメタプロっぽくなってるのはなんで?

  var m = Matrix4x4.identity;

この行ではMatrixの型が入るはず、、、なのになんでSteamVR_Utils.GetPositionが呼び出されてるんだろうか。
詳細を見てみるとExtension method from SteamVR_Utilsと書いてあります。

とりあえず、"Extension method from" でググってみたところ、C#には拡張メソッドがあるみたいです。
それを使えばRubyのメタプロっぽい書き方ができますね。ただし、新たにメソッド名を定義しては使えないみたいですね。

ufcpp.net

なので、ここでGetPositionの引数にthisが入っているのはそういうことだったんですね。
このクラス内だけで呼び出せる拡張メソッドということですね。
気になるのでこの話題は今度プログラム書いてみようと思います。

pose(TrackedDevicePose_t)

次にSteamVR_Utils.RigidTransformのインスタンスを生成する時に使用しているposeについて見ていきます。

f:id:kazumalab:20170704055500p:plain

poseはTrackedDevicePose_tという型を持っていて、それはstruct型になっています。
Trackingするもの(HMDとかControllerとかTracker)の情報を構造的に持たせるために作っています。
次のopen_api.csにかかれているTrackedDevicePose_tです。

// open_api.cs 3944行目
[StructLayout(LayoutKind.Sequential)] public struct TrackedDevicePose_t
{
	public HmdMatrix34_t mDeviceToAbsoluteTracking;
	public HmdVector3_t vVelocity;
	public HmdVector3_t vAngularVelocity;
	public ETrackingResult eTrackingResult;
	[MarshalAs(UnmanagedType.I1)]
	public bool bPoseIsValid;
	[MarshalAs(UnmanagedType.I1)]
	public bool bDeviceIsConnected;
}

StructLayout

最適なレイアウト(メモリレイアウトの話)をするために利用します。
そのためにまずはメモリのアラインメントを知る必要があるらしい。

前提としてメモリの読み書きは4、8の倍数になっている方が読み書きが早いので
プログラミング言語ではアドレスがきれいな倍数になるようにフィールドを並び替えます。

C#ではStructLayoutを使うことでそのフィールドをカスタムできるらしいです。
そもそも個人でゲーム作ってる時にアラインメントを気にしたことはなかった、と言うより初めて知りました。(恥ずかしい)

LayoutKind.Sequential

これは宣言した順番に並べていく手法。
未使用であってもきちんとそのメモリを確保します。

他にも

  • Auto: コンパイラー裁量で並び替えを認める
  • Explicit: 複合型の作者が明示的に位置を指定する

もしかすると今後使うところがあるかもしれないのでメモします。

MarshalAs(UnmanagedType.I1)

マネージ型とアンマネージ型

マネージ型はGC(ガーベージコレクション)が自動的にいらなくなったオブジェクトを破壊してくれたりする。
ココらへんは自動でメモリ管理を行ってくれないようになっているの管理しないと行けないのだろうと予測している。
MarshalAsを使うことで、マネージ型からアンマネージ型への変換が可能になるそうです。
ココらへんは個人的に若干難しく感じます。

I1

1 バイト符号付き整数。 このメンバーを使用すると、Boolean 値を 1 バイトの C スタイル bool (true = 1、false = 0) に変換することができるみたいです。ってかC#ではそもそも1と0のboolはできなかったのか....普段は使わないけど。

d.hatena.ne.jp

あとbool型をそのまま使うと8bitあるそうです。
bbs.wankuma.com
若干信憑性に欠けるソースかもしれませんが...。

今日はここまで!
これでtransformに何が入っていて、どのように値が生成されて...までが読めたはずです、はず。

最後に

今日はControllerが動いているところのソースを分解してみました。
このような感じでControllerが動いているんだなーという所感です。
若干これでいいのか?という感じで終わりますが、読みにくくはないかな〜という感じです。
正直openvr_apiについては5000行近くあるのに少ししか読めてないので結構つらそうだなーという...。

気になる点があればご指摘くださいませ〜。

Unity SteamVR Pluginのソースを読む Part1 導入

かずまです。

SteamVRPluginのソースを読みます。
そのメモです。
主にC#を理解できればいいかなってところです。

準備アセット

これです。
HTC Viveを持っていればこれをUnityに入れて動かすって感じです。

ここから気になったところをどんどん羅列して書いていく

SteamVR_Events.csでのイベント管理

// 189行目
static System.Collections.Generic.Dictionary<EVREventType, Event<VREvent_t>> systemEvents = new System.Collections.Generic.Dictionary<EVREventType, Event<VREvent_t>>();
	public static Event<VREvent_t> System(EVREventType eventType)
	{
		Event<VREvent_t> e;
		if (!systemEvents.TryGetValue(eventType, out e))
		{
			e = new Event<VREvent_t>();
			systemEvents.Add(eventType, e);
		}
		return e;
	}

Calibrationが有効になっているときとか、なった瞬間とかいろんなシステム側のイベントクラスになっているみたい。
Dictionaryを取り出す時にTryGetValueを使ったほうがKeyNotFoundExceptionよりも計算量的にも効率いいらしい。なるほど。

Dictionary.TryGetValue メソッド (System.Collections.Generic)

EventクラスがUnityEventを継承している

永続的にシーンにコールバックを追加できるらしい。
わざわざ毎回Callbackをセットしなくてもいいのか。便利。

確かにSteamControllerが有効になった時にいつでもCallbackが呼び出せるようにしておかないとだめか。
呼び出しはInvoke()で呼べる。

Unity - マニュアル: UnityEvent
Unity - スクリプトリファレンス: UnityEvent

SteamVR_TrackedObject.csでのトラッキングするオブジェクト

このスクリプトではMonoBehaviourを継承している。
そのためOnEnableやOnDisable()が使える。
この2つのメソッドはオブジェクトが有効になった時に呼び出されるCallcackになっている。

Unity - スクリプトリファレンス: MonoBehaviour.OnEnable()

// 74行目
newPosesAction.enabled = true;

newPosesActionは先程見たSteamEventsになる。

void Awake()
	{
		newPosesAction = SteamVR_Events.NewPosesAction(OnNewPoses);
	}

OnNewPosesというメソッドをActionに追加してオブジェクトを渡している。
ここの56行目あたりで、位置が取れそう。

  var pose = new SteamVR_Utils.RigidTransform(poses[i].mDeviceToAbsoluteTracking);
  if (origin != null)
    {
	transform.position = origin.transform.TransformPoint(pose.pos);
	transform.rotation = origin.rotation * pose.rot;
  } else {
	transform.localPosition = pose.pos;
	transform.localRotation = pose.rot;
}


ここのposeで値が取れそうな気がします。
次はSteam_Utils_SteamVR_Utils.RigidTransformを見ていこうと思いますが、
今日はここまで。
とりあえず、SteamControllerやTrackingするオブジェクトが
Steamに認識されたときに行われる部分までいきました。

以上です。

VM上のNginxからProxypassでMac上で起動しているRailsを動かすお話

かずまです。

お久しぶりです。
社会人になってはや3ヶ月、元気にやっております。
Rubyのエンジニアとしてどうにか勉強中です。


さて、今日はネットワークネタになります。
Ansibleを使ってVagrant上にNginxを起動し、ローカル環境のMacで動いているRailsを動かしてみようというお話です。
その時に詰まったのでメモしておきます。

Vagrant

www.vagrantup.com

簡単にMac上にVMを建てられます。
AnsibleやChef、Itamaeを使うと環境構築が一発にできます。

今回はCentos7を使って行きます。
Vagrant Fileにprivate ipをお忘れなく。

www.vagrantup.com
ちなみに、外からも見えるようにしたい場合はpublicを使うと良さそうです。

Nginx

静的なページを表示したりするWebサーバー。
RailsとかNodeJSはアプリケーションサーバーです。

今回の構造

f:id:kazumalab:20170628222948p:plain

あるPCからNginxのIPアドレスでアクセスして、それをNginxが受けて、Rails側にリクエストを送るという構造。
このボックス一つは一つのサーバーになっています。

ただし、今回は面倒なので、リクエストの送る先と受ける先を同じにするということです。

Nginxの設定

今回はインストールなどの導入は省きます。
Ansibleを使う場合は一度手動でサーバー構築してからやるほうが良さそうです。

とりあえず今回はdefault.confをいじることにします。
アクセスしたものをプロキシーパスでローカル環境のMacを指し示しているわけですね。

# on VM Terminal
# vim /etc/nginx/conf.d/default.conf
server {
    listen       80;
    server_name  localhost;

    access_log  /var/log/nginx/log/host.access.log  main;
    error_log   /var/log/nginx/log/host.error.log;

    location / {
        # rails ip address in other server
        proxy_pass http://192.168.0.2:3000;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

一応、ミニマムな感じで書きました。
実際に運用する場合はヘッダーに色々記述する必要があると思いますので。

後はログは出力する設定にしたので、もしディレクトリがなければ生成してください。
権限はrootでも大丈夫なはずです。書き込み権限だけは与えてください。

proxy_passに自分のローカル環境のip + Railsを立ち上げたポートを記述しておきます。

Railsを立ち上げる

# on Mac Terminal
$ rails s -b 0.0.0.0

これで同じネットワーク内であればIPでアクセスが可能です。
一応、Macのip+ポート番号でアクセスできるか確認して、VM側でもCurlとかして確認すると良さそうです。
Headerのみ返す場合は

# on VM Terminal
$ curl --head http://192.168.0.2:3000

多分こんな感じだったはず。

アクセス時にPermissionがないといわれる

これでNginxを再起動してIPでアクセスしたところ、一応反応はあるけど502が返ってくるようになりました。
先程設定したログを見てみるとパーミッションでコケているらしいです。

# on VM Terminal
failed (13: Permission denied) while 
connecting to upstream, client: 192.168.0.2, server: localhost, request: "GET / HTTP/1.1", upstream:

ぐぐってみたところ似たような問題が!

stackoverflow.com

SELinuxがだめっていっているそうですね。
まぁそもそも違うサーバーにプロキシーで飛ばすのおかしいよというお話だと思いますが、とりあえず、動かすことが目的なので、
SELinuxをDisableにしてみます。
もちろんSELinuxにProxypassを有効にしてあげればいいのですが、その前にまず原因がSELinuxなのかを判別したかったのでとりあえずオフにしました。

# on VM Terminal
$ setenforce 0

これでNginxを介して、ローカルのRailsを動かすことに成功しました。

参考URLに書いてある感じで、止めるのではなくSELinuxの設定をすると良さそう。
以上です。