Making Tera

たまに書く不定期ブログ

Unityでインスタンシング

先日、参加中のサークルで「らせつ封魔伝」が完成しました!(製作期間7年っ)
自分はこのプロジェクトで3Dモデルやプログラムで協力させていただきました
www.dlsite.com

実はスケジュールを詰めたせいで村の草の設置が間に合っていませんでした
(次のアップデートで対応する予定です)


さて、この記事では草の配置に利用したインスタンスシングの実装についてさっとまとめましたので興味がある方はご覧ください

まずインスタンスシングって?って思う方のために
インスタンシング=「同じオブジェクトを大量に表示する機能」
です!


尚、導入前に以下についても検討の余地があると思われます

  • TerainのDetail機能による配置(今回の村はTerrainを使っていないっ)
  • Polybrushを利用する(我々が使用中のUnityバージョンでは古くて使えないっ)
  • 手動で配置(数が多いと重いし面倒)

(らせつでは化石のような開発環境で身動きが取れなくなっておりました…)

手順1 頂点群(配置データ)の作成

Polybrush等を利用してUnity上で頂点データを扱えればいいのですが、上記のようにUnityのバージョン縛りがあるので今回は3Dソフトで配置場所に点を打ち、その場所に草を生やすスクリプトを生成します

少しずつBlenderに移行したいと考えていたので練習を兼ねてBlenderのHair機能を利用してみました

手順は以下
・適用したい面を選択して頂点グループにしておく
・パーティクルも出ファイアを追加
・ヘアーに変更
・長さとセグメントを調整(最後に頂点にするので適当に)
・ヘア設定の頂点グループ>密度のところに先ほどの頂点グループをセットする

f:id:makingT:20201007204223p:plain

このモデファイアをメッシュに変換するといくつかの頂点+エッジになるのですが、変換直後に頂点モードに入ると根本の頂点のみが選択されている状態になるので選択を反転して削除してしまいます

f:id:makingT:20201007204558p:plain

頂点データを整頓したらデータ化に移ります
Exporterを作成するのがいいんでしょうが、一回しか使わないし…ということで秘儀テキストエディタの出番です
.objで書きだされたファイルは「v 100 10 20」のようにデータがずらーっと並んでいたりします
この部分を切り取る→いらない部分をテキストエディタで一斉置き換えという荒業

v 1 1 1
v 2 2 2
v 3 3 3

float[] hoge = new float[]{1,1,1,2,2,2,3,3,3};

に成形してそのままUnityのスクリプトに注入しました
(Ctrl+Hだけで数秒で終わると思います)

手順2 スクリプトの作成

まずは先ほどのデータをVectr3の配列に変換します
(利用している3Dソフトの座標系の違いにより、X座標にマイナスをかける必要がある点に注意してください)

スクリプト自体はUnityのマニュアルに載っているものがほぼそのまま流用できます
https://docs.unity3d.com/560/Documentation/ScriptReference/Graphics.DrawMeshInstancedIndirect.html

Vector4[] positions = new Vector4[instanceCount];
        for (int i=0; i < instanceCount; i++) {
            float angle = Random.Range(0.0f, Mathf.PI * 2.0f);
            float distance = Random.Range(20.0f, 100.0f);
            float height = Random.Range(-2.0f, 2.0f);
            float size = Random.Range(0.05f, 0.25f);
            positions[i] = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size);
        }

の部分を自身の頂点データに置き換えるわけですが、サンプルではVector4なのでfloat一個分余っています
何かシェーダーに値を渡してもOKですしVector3に変更しても問題ありません

上記に加え らせつ封魔伝では「マップを縦横10等分して自身のまわりの9マスのみ表示」という変更を加えました

手順3 シェーダーの作成

シェーダーも先ほどのURLのものをほぼそのまま使えばいいので書くことがないのですが…。
テクニックと注意点を記載しておきます

大量のオブジェクトを配置したい場合「固定されたランダム値」が欲しくなることが多いです

スクリプトで求めた乱数をバッファを通して渡してもいいのですが、すでにXYZ座標があるので座標の小数部分を利用します
具体的には

 frac(data.x)

とすることで0~1の簡易ランダム値がお手軽に手に入ります

また、スクリプトから乱数を渡す際、ロードによって値が変わることを望まない場合は乱数生成前にシードを与えます

Random.InitState(123456);//シード
for (int i = 0; i < test.Length; i++)
    hoge = Random.Range(1f, 1.5f)));

次に注意点ですが、サンプルの

unity_WorldToObject = unity_ObjectToWorld;
unity_WorldToObject._14_24_34 *= -1;
unity_WorldToObject._11_22_33 = 1.0f / unity_WorldToObject._11_22_33;

の部分は簡易的な逆行列の求め方です
回転行列を適用した行列だと正常に求まらないので回転操作を行う場合はきっちりとしたInverse関数を作ってください
参考URL:
Incorrect normals on after rotating instances [Graphics.DrawMeshInstancedIndirect] - Unity Forum


サンプル内では4x4の行列をそのまま取り扱っているので、そこにランダム値を適用することで好きなように「拡大、縮小、回転、変形」が可能です
例えば大量に配置した草をY軸回転したい場合は回転行列を調査します

y axis rot
[c 0 -s 0]
[0 1 0 0]
[s 0 c 0]
[0 0 0 1]

対応した場所にランダム値θから求めたコサインとサインの値を入れるだけでバラバラにY軸回転するインスタンシングが完成します※

草を揺らす表現にはシェーダーの_Time関数とせん断行列を利用しています

まとめ

.objファイルの中身はテキストで扱いやすいのでちょっとした作業なら直接編集
公式マニュアルにインスタンシングの処理がほとんど記載してある
スクリプトからデータを好きに渡せるので 位置 色 回転 大きさ 揺れ方 等、大量に置くデータにも自由に個性を持たせられる

インスタンシング設置前
f:id:makingT:20201007225529p:plain
設置後
f:id:makingT:20201007225525p:plain


何かの参考になれば幸いです





※本気で最適化するならSinCosを静的処理で済ませて圧縮してバッファに入れることも検討するべきかもっ

UnityのPrefabについて

Unityで同じオブジェクトを扱う場合は「Prefab」を利用する
なにかと混乱しやすい部分だったのでPrefabを扱い方を図解してみた



【作成方法】
もとになるデータの作成方法はヒエラルキ窓からプロジェクト窓にドラッグするだけ
f:id:makingT:20200622235207p:plain



【複製方法】
さっきの逆にドラッグするだけ
f:id:makingT:20200622235531p:plain

すっごい簡単
Prefabかどうかは文字の色でわかる(青色がPrefabを配置したもの)
f:id:makingT:20200623000101p:plain



配置後に、もとの保存したデータ(作成したPrefab)の中身を変更するとどうなるか?
↓↓↓↓↓↓↓↓
配置したすべてのデータが書き換えられる(開いていないシーンのデータであっても)

超便利!!めでたしめでたし



Prefab紹介記事は大体ここで終わっていて深く書いていないことが多いので
もう少し掘り下げてみる



まずデータの構造を見てみる
先ほどのCubeの場合4つのデータ(コンポーネント)で成り立っている
f:id:makingT:20200623000301p:plain

このデータのABCDがコピーされて配置されている状態を図解すると以下のようになる
f:id:makingT:20200623005258p:plain



ここで、コピー先のデータを変更してみたもののやっぱり戻したいって時
インスペクタ窓のOverridesをクリックしてRevertをクリックする
f:id:makingT:20200623010435p:plain

f:id:makingT:20200623010659p:plain



逆に、データの変更を元のPrefabに適用し、コピー先にも反映させたい場合はApplyをクリックする(コピー先のことをインスタンスと呼びます)
f:id:makingT:20200623010837p:plain

f:id:makingT:20200623011209p:plain




最後に一番混乱するところなのですが、
通常「Prefabを直接変更」もしくは「ApplyをクリックしてPrefabを変更」した場合コピー先はすべて変更されます
し か し
「配置後にデータを変更していた部分だけは Prefabの変更は無視」されます!
f:id:makingT:20200623012152p:plain
・・・・・・・・・・・・・・・・・・・・・・・・
f:id:makingT:20200623020814p:plain



Prefabを変更した際に何が共有されて何が変更されないのか?がわからなくなったら文字の太さを確認しましょう
f:id:makingT:20200623013242p:plain
この例だと
PositionのXYZ座標
RotationのXYZ回転情報
BoxコライダのSizeのX情報である5555
太字です
上記のデータは元のPrefabが変更されたとしても値が維持されます
(それ以外のデータはすべてPrefabと同値になります)



複数人でプロジェクトを触っていて、
「いつの間にかデータが壊れている」
なんてことが減れば幸いです!




 ※1 Prefabの一番親の位置と回転情報は強制的に太字になります
 ※2 マテリアルのようなプロジェクトファイルに作成されるデータは独立したデータであり マテリアルを参照するコンポーネントはあくまで参照先を記載した情報です よってPrefabの参照先の変更とプロジェクトデータの変更は混同してはいけません

NavMeshをカスタムする方法

多分すごい基本なんだけど、NavMeshでベイクしようとして詰まったので忘れないようにメモ。

NavMeshの障害物は以下で説明されているNavMeshObstacleコンポーネントしか無いと思っていたのが詰まった原因。
https://docs.unity3d.com/ja/current/Manual/class-NavMeshObstacle.html

【問題】
なだらかな水辺でキャラクターが水の中を歩けないようにしたい
段差がないのでベイクの設定だけでは無理?

【解決法】
1)水面PlaneをStaticにする(Navigation StaticがONになればOK)
2)水面Planeを選択
3)Navigation>Object>下の方にあるNavigationAreaのコンボボックスを「Not Walkable」にする
4)再ベイク

f:id:makingT:20181003090937j:plain

Unityで水際シェーダー

湿地帯マップの沼を表現するためシェーダーを作る必要があったのでメモを残しておきます。

具体的には水際のエッジが直線っぽく見えるのを防ぐためのシェーダーです。


水際シェーダー

実装は案外簡単で以下のシェーダーを水面に適用すればOKです。

・Input構造体に以下の二つを追記
float4 screenPos;
float eyeDepth;

サンプラーを用意
sampler2D_float _CameraDepthTexture;

・頂点シェーダー内に以下を追記して頂点の視点空間の深度マップをoに出力
COMPUTE_EYEDEPTH(o.eyeDepth);

・後はサーフェイスシェーダーで深度を扱うことができます。
float depth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos));//深度

・水際かどうかは以下のように求められます(深度の差*係数を0~1に収める)
float waterSite = saturate((LinearEyeDepth(depth) - IN.eyeDepth) * _Amount);

実際には上記のwaterSiteにノイズを加えた値を2つのテクスチャの合成値として利用することで動画のようになります。

Unityのテクスチャマッピング

以前、Unityでテクスチャの貼り付けについて聞かれたので簡単な記事にしてみます。
先に書いておきます。分かりにくくてすみません!!


まずテクスチャマッピングについてです。
頂点(面や線)に色をぬる時、テクスチャ画像のどの部分から色を取ったらいいか?という話になります。
これを決めるのがマッピングであり、XY座標(UV座標ともいう)を指定することになります。
実際にシェーダー内では頂点ごとに以下のtex2D関数を呼び出してXY座標から色を取得しています。

tex2D(texture, xy)
texture: 色を取得したいテクスチャを選択します
xy:ここで指定したX座標とY座標にある色を取り出します

【まずは標準的なUVマッピング!】

俗にいうUVマッピングとは上記のxyにUV座標を渡す方法です。
UV座標とはデザイナーが頂点ごとに座標を設定するもので、大抵のモデルデータには付属しています。魚の開きみたいなあれです。
UVマッピングはモデルの大きさによってテクスチャも拡大されます。

【モデルのサイズに関わらずテクスチャを表示したい!】

モデルサイズに依存したくない場合はtex2D関数のxyの場所にワールド座標を渡します。*1
ワールド座標の取得方法はUnityのシェーダー機能を利用しているかどうかで変わってきます。

サーフェイスシェーダーの場合

  • worldPosを使って取得できます。
  • 例:IN.worldPos.x

頂点/フラグメントシェーダーの場合

  • 頂点にワールドマトリックスを適用することで取得できます。
  • 例:mul(_Object2World, IN.vertex)

ワールドマトリックスのような行列はあらかじめ用意されておりシェーダー内から参照できるようです。*2

_Object2Worldモデル行列
UNITY_MATRIX_MVPモデルビュー行列×射影行列 (world*view*projection)
UNITY_MATRIX_MV モデルビュー行列
UNITY_MATRIX_Vビュー行列
UNITY_MATRIX_Pプロジェクション行列
UNITY_MATRIX_VPビュー行列×射影行列
_World2Objectモデル行列の逆行列

【UIのようにテクスチャを画面に連動させたい!】

スクリーン座標にテクスチャを固定したい場合は少し複雑です。

結果を先に書くと頂点シェーダーから
ComputeScreenPos(mul(UNITY_MATRIX_MVP, v.vertex))
の値をピクセルシェーダー(フラグメントシェーダー)に渡します。
渡されたxとyをwで割った値をテクスチャ座標として使います。

除算の理由はの描画に関する行列の知識が無いと少し混乱するかもしれません。
ComputeScreenPos関数を検索すると「ウィンドウ座標を求める関数」と書かれていることが多かったのですが
実際には座標の範囲を[-w、w]から[0、w]に変換しているだけの関数となります。
画面上の座標を求めているのはmul(UNITY_MATRIX_MVP, v.vertex)の部分です。
wで割るのは通常はGPUが行っている処理を手動で行っていることになります。*3


他にもいろいろなマッピングがありますが、基本的な方法は上記の三種類だと思います。
参考に慣れば幸いであります!

*1:XYZ(float3)をどうやってfloat2にするかでマッピングの方法が変化します。

*2:DirectXで言うところのワールドマトリックスはUnityではモデル行列と呼ぶようです。

*3:GPUの透視変換はX[-w~w]Y[-w~w]Z[0~far]をwで除算しX[-1~1]Y[-1~1]Z[0~1]に変換します。

壊れた地蔵モデリング

瘴気漂うマップのモデルの依頼がありましたのでメイキングをまとめてみました。
色んな風に壊れたお地蔵さんが4つ!各々が△200ポリということでディテールには限界が見えます。

暗い場所に置くらしいのでディフューズマップを無くしてノーマルマップのみで細部を表現することで描写コストを下げる作戦に。
もちろん4つのテクスチャは一つのテクスチャ、マテリアルにしておきます。
では早速スタート!

お地蔵さんのような彫刻系?はスカルプトで作成したほうが早いので3Dcoatで作成。
(一時期ZBrushも試したけどカメラをはじめ、あまりに独特の操作が多くて断念)

地蔵壊す前

原型を壊す!ここが一番楽しいけどあっという間!

地蔵ベイク前

作成したハイポリをリトポ。これが一番辛い。ただただ辛い。
詳細に作成したモデルのディティールをポリ数制限によって崩していくのが辛い。
(今回は表示される大きさもあってハイポリが適当だからいいけどっ)
f:id:makingT:20170301013049j:plain

ハイポリからローポリにベイクすると差分が法線マップとして作成される。
このとき表面の材質などはあとから合成するのでモデルの差だけを焼き込む!
f:id:makingT:20170301013604j:plain

SubstanceDesignerを使って材質の法線画像を合成する。
高ポリに材質を彫り込んじゃうと、大抵あとから「材質が綺麗に出てないな~」ってなります。
なぜか本番環境でだけ綺麗にでないっす。泣きます。
ってことでトライアンドエラーが気楽にできる方法を選択しておくのがお勧めっす。
f:id:makingT:20170301013726j:plain

あとはUnity上でどう表示されるか確認しながら微調整!

黄泉 瘴気エリア地蔵

枝垂桜モデリング

ある日、サークルの方から「枝垂れ桜」のモデルが欲しいと言われまして、分かってはいたのですがポリ数との戦いでした。
Unity用のモデルをModoで作成しております、誰かの役にたてば…とせっかくなので記事にしました。

まず最初に、普通に綺麗に見えるモデルを作成したのですが、四角ポリで7万ポリ!植物特有のやばい予感。
これをリトポ→ベイクしようと思ったはいいものの案の定ビジョンが見えない!!
さっくりモデルを捨てて作戦を立て直しました。


まずはバランスを見ながら幹をローポリで作成
f:id:makingT:20170226003903j:plain

細かい枝はすぐに頂点数が増えるので板ポリで作成
f:id:makingT:20170226004119j:plain

拡大すると分かりますが板ポリを「Ctrl+C → Ctrl+V → F(反転)→ 移動」で裏側にも同様の板ポリが配置されています。
シェーダーで両面描写してもよさそうですが、マテリアル分離などを考えるとこちらのほうがコストが低いと思われます。
f:id:makingT:20170226004441j:plain


物理演算で枝を垂らすことにしました。
まずはサーフェイスジェネレータでポイントを作成
次にリプリケータでカーブを配置
f:id:makingT:20170226005139j:plain

カーブを一つのメッシュにまとめたらダイナミックカーブを適用
プロパティウィンドウのダイナミクスの中にあるカーブ項目でカーブ始点をピン留めしてジョイント角度やピンの角度を調整
カーブパーティクルジェネレータで枝のメッシュを配置してから物理演算を行う!

物理演算


これである程度、完成が見えたと思ったのですが実際にゲーム内に置いてみると「見下ろし視点」の影響でシルエットが思ったように定まりません。
結局すべて手動で配置しなおすことにしました。
カーブにメッシュを沿わせるあたりは一緒ですが、カーブに対してペイントタブにある「ヘアツール」を多用します。
今回のような場合は大きいブラシで「分離」が使いやすいかも
f:id:makingT:20170226193131j:plain

ローポリのモデルが完成したらテクスチャの作成に移ります。
今回、「桜のモデルは暗いマップでライトアップしたい」とのことで通常のワークフローとは異なります。

まずは少ないポリ数でも綺麗に見えるようにノーマルマップを高ポリからベイクして作成。
エミッション用テクスチャも欲しくなるのでAO画像を代用
f:id:makingT:20170226012907j:plain

幹はライトのせいで色が飛ぶと思われるので暗いベース色+法線による表現で。
植物の高ポリモデルはスカルプトで作成するのが楽で早いです。
今回はModoではなくCoatを利用。
気合いを入れてもローポリによってはベイクに限界があるのでここは元モデルにさっと肉付けして終わり。
f:id:makingT:20170226013708j:plain

ここまで来たらゲーム内に持って行って画像を見ながら調整します。
Modoのゲームツールタブを利用するとワンクリックでUnity内で描画の様子を確認できるので捗ります!
(「表示の切替→選択したものだけ書き出し」の部分をマクロ化してショートカットに割り当てておくと便利です)

桜テスト