Yaminabe

らくがきぶろぐ

てすと:Yaminabeちゃんは生まれたばかりのブログです みんなで仲よく使ってね

カテゴリ: シリーズ講座

 

 

 

今回は 最近になってGitなどバージョン管理を使い始めたとか、意外に使っていない人が多かったのと 自分が少し必要な知識だったので簡単にまとめてみました。

自分はWEB関連の作業だとほぼ必須なので 以前から使用しているのですが、 GitHubから別のサービスへの移行を考えている人がいたりします

おそらくは6月のマイクロソフトのGutHub買収の噂からだと思うのですが。 すでに技術的な部分は多くのサイトで詳細に解説されているので

そこら辺はリンク先で確認していただいて、GItの周辺の情報いろいろあってよくわからない という人向けにざっくり書きます。

 

(18 9.18)ブログを更新した気になっていたが 更新失敗していた

おもにGitSubversion の違いと選択について その他です。


 

Gitとは? ? Gitの仕組みを速攻で理解する

  理解のための 用語解説が必要だと思ったのですけど、こちらのサイトがとても完結で詳しいので抜粋しようと思ったのですが、ブログの文字数制限に引っかかりそうなのでキャプチャさせていただきました。 説明は省く予定でしたけど用語ぐらいは知っておかないとね。

 Gityougo

 


GitSubversion の違い

subversionを使っていた人がgit便利だなと感じたこと

subversion

・単一リポジトリ(リポジトリは一つだけ)
・commit したら即反映
・add するのは新規にファイルを追加するときだけ
・リビジョン番号は数字
・考えるのは『ローカル( checkout した場所)』と『リポジトリ』の2つだけ

 

git

・分散リポジトリ(マスターリポジトリは一つ。でも個人リポジトリを自由に作れる)
・commit しても個人リポジトリにしか反映されない( push するとマスターリポジトリに反映)
・commit 前には毎回 add する必要あり
・リビジョン番号はハッシュ値
・考えるのは『ローカル』と『インデックス』と『個人リポジトリ』と『マスターリポジトリ』の4つ



 

 

■ ドキュメントのバージョン管理はGitかSVNか???

実は、サイズの大きなバイナリファイルをGitで扱うのはあまりお勧めできません。
Gitで管理されるリソースは、圧縮されますが、既に圧縮済みのバイナリファイルはそれ以上圧縮できませんから、サイズが大きくなればなるほどPUSHやFETCHにかかる時間に影響を与えることになります。
Excelのファイル形式にしろ、Javaのライブラリ単位であるJARファイルにしろ、既に圧縮されているバイナリファイルです。

僕個人の意見としては『Gitでの管理対象リソースとして、バイナリファイルは必要最小限とし、可能であれば画像だけにする。』としたいですね。

 

Gitが分散型で 差分データを圧縮管理してそれを結合することで、効率化するシステムなのでバイナリファイルはもともと圧縮状態なうえに

結合ができないためさほど効率の良い管理ができない。 大きなバイナリの塊のままプッシュすると 転送にかかる時間も長くなるためGitの長所はあまり生かせないという仕組みだと考えられます。

 

Subversion Git の構造で混乱しないように画像でわかりやすくしてみます


Subversion(TortoiseSVN)とGit(SourceTree)を比べてみる

GitSubversionの仕組みをなるべく単純に表した画像はないのかと調べた結果こちらのサイトの画像にたどり着きました。

最初の画像では省略されていますが、GitSVNと比較して差分バージョンを派生させるブランチや ブランチを結合するマージといった機能があります>下の2つの画像

解説はそれぞれのリンク先で確認するか 調べていただくとしてここでは深く触れません。 不親切ですが今回の趣旨とは異なるのですいません。

Subversionは単純で理解しやすいですね

 

  • SubversionとGitのデータ管理の相違

20160415193410

  • Gitのブランチによる管理

 

blunch

git-branch

 

 


 

 

ゲームエンジンのバージョン管理

Unityまわりは普通にGitで運用している方が多い印象 UE4では Perforce, SVN, Git(ベータ版) がソース管理に指定できます。

  • Perforce           料金が高めなので個人開発だといまひとつ。 情報があまり出回っていないというハードルがある。
  • SVN                  UE4のプロジェクトが全体に容量大きめなのでGitよりSVNがおすすめらしい。Gitより利便性は落ちる。
  • Git(ベータ版)   細かいバージョン管理ができるため多人数PJならGitが有利?。クラウド越しの管理でなければ使えそう。

前述した理由でUnityはファイルサイズがそれほど大きくならないためGitの運用でも さほど問題が出にくいのか。またはテキストコードを中心にバージョン管理を行っているのかもしれないですが、問題はUE4で巨大なバイナリファイルで運用するのでSVNがおすすめされるのも理解できます。 巨大なファイルをネットワーク越しでGitでにプッシュとかは考えにくいですね。ローカルに環境構築して運用するのが妥当かもしれません。

ローカル環境かクラウド環境か

代表的なリポジトリ管理サービスについても書いておきます

  • クラウド環境

Gitリポジトリ管理サービスのメジャーは GitHub、Bitbucket あたりになりますがこの様に違いがあるようです。

  • GitHub          完全オープンソース OSSで 基本的にリポジトリは公開 有料プランでリポジトリを非公開にできる。
  • Bitbucket      GitHubとほぼ同様ですが無料でも非公開設定が選べる、無料でSSHキーは5人まで有効。

GitHubBitbucket を同じ名前で登録するとエラーが出ることがあるようなので移行時には注意

  • ローカル環境

GitLabBitbucket、AWS CodeCommit といった無料で使用できるサービスでは 自前でサーバーを用意できるため社内開発での導入を検討する向きが多い様で クラウド上に開発ソースを置くことに抵抗があるクライアントサイドの意向だったり理由は様々。 買収の噂でGithubからの移行を検討するケースも多い様子。 導入はNginxApacheが走るXAMPPMAMPでサーバーを立てるだけで良さそうだですが、主にメンテナンスコストがデメリットに挙げられている様です。 担当者の引き継ぎがうまくいかない場合などですね サーバー管理に限らずでしょうが。

  • GitLab、              無料で使用でき GitクローンでGitHubと似たUIで機能面でも遜色ない ローカルに環境が構築できる。
  • Bitbucket               GitLabとほぼ同じ内容だが 個人が運営しているためサポート面で劣るかも。
  • AWS CodeCommit  まだ新しいサービスなので、使い勝手の面で改良の余地ありとか 不満な点も

      ※GitHubは自分のサーバーで利用できるGitHub Enterpriseがあります

 

この中ではGitLabが少し人気が高いようで以下にGitlabの考察について記述があるので 簡単に抜粋してみます。

プロジェクト管理ツールでRedmineTracを使用している場合はプラグインでバージョン管理ができるGit対応ができますが、専用のバージョン管理サービスに比べて機能面ではやや劣るため。 それ以外のローカル環境に構築できる選択肢としてGitLabなどが挙げられているみたいです。

Gitlabに触ってみて、Githubと比較した

会社でのローカル環境で利用していたGitlab, てっきりGithubのローカライズ版かと思っていましたが、githubのマイクロソフト買収話をきっかけに公開webサイトもあると知りました。
知ったのは結構前ですが、なぜか今日急にwebサイト版のgitlabを試してみたくなったので、触ってみた感想を記載します。

  • Githubで普段使う機能は大抵揃っている
  • 各種SNSアカウントでのログインが可能。
  • 無料版でもプライベートリポジトリを作ることが出来る。
  • Githubのリポジトリをインポートできる。
  • UIに癖がある。
  • 動作は重ため?
  • 完全無料。
 

Subversion(SVN) ⇔ Git

GitSVNどっちで環境構築すればいいのかという 悩みが発生しますが、相互にコンバートできるのであまり考える必要はなさそうです。

なーんだ 話終わっちゃった

以下のサイトでの情報のように Git-svnでGit、SVN相互変換が可能で、さらに SubGitのほうが作業のハードルが低めだということです。

 

SubGitでGitリポジトリをSubversionリポジトリへ移行

   

■ Git-svnでSubversionからGitに移行しよう。

  1. 「git svn」コマンドを使用して、GitからSubversionリポジトリへアクセスする(図1の【1】の操作)
  2. 「GitHub Importer」を使用して、SubversionからGitHubへの移行を実行する(図1の【2】の操作)
  3. SubversionクライアントからGitHubリポジトリへアクセスする(図1の【3】の操作)

 

at-it-git-14-001

 

 


■ まとめ

 

  • テキスト、ソースコードやアセット類などで圧縮できるもの サイズのちいさなバイナリファイルならGitを使用するのが有利。
  • プロジェクト全体が大きくなる、大きなバイナリファイルを扱うならばSVN(サブバージョン)を使用する。
  • ファイルサイズが大きい場合、データの流出セキュリティに不安がある場合はローカルにバージョン管理を構築
  • もともとGitが大きなバイナリを扱うゲーム用途の想定でなくWEB系などのテキストコードの扱いを考えられているようなので ゲームではSVNが有利なのかもしれませんが、Gitの利便性も捨てがたいので作業内容によって使い分けるのが良いかもしれません。
  • GitでもSVNでも相互にコンバートできるので、さほど構築時の選択があとで問題になるというわけでもなさそう 。
  • あとは 好みの問題かも 。。 まとめてないね

 

ちなみに 自分の環境では UnityとWEB関連は Bitbucket+SourceTree 、UE4はTurtoiseSVN でやってます。

 

 

もう少し理解を深めたい方にリンクを貼っておきます。



ローカルサーバーでGitHubのようなサービスを構築したい人向けリンク

GitHubのようなサイトを独自に運用できる「GitLab」や「GitBucket」を使ってみよう

GitLab自社運用のための注意点とノウハウ(2018/06版)

 


Git自体がよくわからない人向けリンク

 

■ SVNを捨ててGitを使うべき5つの理由

■ Learn Git Branching
http://pcottle.github.io/learnGitBranching/

■ こわくないgit
http://www.slideshare.net/kotas/git-15276118

 

■ サル以下の存在がまとめた「Git用語」図解

こんなGitの教え方をするエンジニアはデザイナーからモテるぞ!

■ Gitとは?入門向け基礎知識のまとめ


UE4でのバージョン管理が知りたい人向けリンク <Unityは軽いからどんなでもいいかも 雑ね>

 

今日から始めるUnreal Engine 4のSource Control SVN編

UnrealEngine4でGitを使って共同開発したい

 

GIT のGUIクライアント

ついでに書いておきます Git単体でコンソールコマンドで操作というのはつらいです。GUIクライアントを導入するとブランチツリーがグラフィカル表示されて 操作がマイルドになるので導入したいところ、 有名どころは SourceTreeですが、新しめのGitKrakenなどもいい感じです。SourceTreeははじめから日本語対応しています。GitKrakenは非公式ですが日本語パッチを当てることで日本語化が可能なようです。 他にもTortoiseGitはGUIが搭載されているのでGUIクライアントを別に導入する必要はありません こちらも日本語化パッチで日本語化が可能です。

「GitKraken」は使用料金が発生するようですが、個人開発、インディーズならばそれほど負担にならないようです。

洗練されたUIのGitクライアント「GitKraken」の使用料金が発表 - Proライセンスは60ドル/年

GitKrakenを使用して商用開発を行うユーザーは、基本的に60ドル/年のサブスクリプション登録が必要となる事になります。 ただし例外も認められていて、創立後一年に満たず、従業員が20人より少ないスタートアップ企業や、個人開発、オープンソース開発、非営利、教育用途の場合は無料のGitKrakenが継続して使用可能とされています。

 

ちなみnGitのGUIクライアントアプリ どのような種類があるか調べてみたところ Windows対応版だけでこんなにあるらしいです。

多すぎ

 

screencapture-git-scm-download-gui-windows-2018-09-17-13_27_56

 


 

最近のGitサービス移行に関する動きは 、GitHubをMSが買収するかもという噂から。始まっているようですがMSに買われたサービスが室の劣化を遂げているのを観ると悲観的な意見になるのもわかります。 最近だと意見に上がってますけどSkypeかな。以前記事に書いた中でも MSがゲームエンジンの買収に乗り出す可能性が頭によぎってました 近年買収されたところ以外ですが。

まあ 特にすぐ動く必要はないような気もしますが、いざというときのために退避場所は準備しておくのがいいかも

 

どうなるGithub!?MSが買収と噂しているが世間の意見はどうなのかまとめてみた。

 

Git買収

 


 

- おまけ -

 

■ 背景アーティストのためのSubstanceDesignerマテリアルデザイン入門

 

Tweetで流れてきたので購入してみました。以前から使用しているので、内容に目新しい部分というのはないのですが 教え方の切り口に興味があったりで時々学習がひととおり終わってから入門書を購入します 普通は逆なんでしょうけど。知識が入ってる状態だと楽に読めますが  意外に気づかされることも多いんですよね。

SubstanceDesignerは知らない人はもう少ないでしょうが Photoshopフィルタ群のレイヤー処理をノードベースにして、 ノーマルマップのベイク処理(ShaderMap、xNormal, CrazyBump ) エフェクト関連だとFilter Forgeプラグイン そういう各種ツールを一本にまとめた 広範囲に制作パイプラインをカバーすることができる多機能アプリですね。 最近流行りらしいです。

Youtubeのチュートリアル動画などで比較するとわかるかと思いますが使用するフィルタは制作スタイルごとに結構ばらつきがあるようです。SubstanceDesignerは大量のフィルタ機能が搭載されていますが、デザイナーごとにだいたい得意ルートがあるようで どの制作ケースでも似たようなフィルタを使用しています 機能をしぼっている感じ、 従来のPhotoshopでのテクスチャ制作でも機能全部覚えて使ってる人はいないですしね 機能に振り回されないことが必要かな。。

ゲームエンジンでシェーダノードの操作に慣れた人であれば理解はそれほど難しくないと思います。 モジュール化したシェーダのようなもの あってる?。

ゲームエンジン側でスクリプト制御できるのでプロシージャルテクスチャ的な使い方ができますが Unityはいまのとこ実行開始の初期化時にパラメータ変更できる様ですが、最新バージョンはわからないです。 エフェクトなど実行時の制御にはシェーダ書かないとだめかな。

内容には触れませんが、今回の書籍は入門にとても良いと思います。ほんとにはじめての人は動画がセットのほうが理解しやすいので 、さらっと公式のチュートリアル動画を見てからが読むのが良いかと思います。

覚えたての時こういう書籍がもっと出ていたら良かったんだけどね。

SDSD

 


【後記】

ひさしぶりに長い記事を書いたのですが 調子はいまいち。 記事が全体にうす味です

休みを取る宣言をしましたが、休み始めてすぐに魔物が潜んでいたといういつものパターン。 以前にくらべると時間の余裕は少しできたので 散文的にいくつか思うところを書きすすめて 記事になりそうなものを優先してみたというところです。

期待に沿える記事なのかはわかりませんが、様々な目的でブログに訪れる方がいるので たまにはアタリ記事もあることでしょう。

あと時間のあるときにチュートリアルを大量に見て読んでいたので、そこら辺の情報をそのうち形にできたらいいかなと思っています。

 

次回は未定 それでは また

 

 

え アニメ感想ですか? 書いていたんですけどもうすぐ今期アニメ最終回だしどうしようかな という脳内から物言いがついたので、時間が許せば記事にする予定なんですけどね、 スマヌ

 

 


前回に引き続きですが 進捗の2です。

 

今回の動画はAI部分だけを分離して実際にコースを走らせてみるチェックです。 フィジックスの設定が少しおかしくなっているので コースの路面コリジョンの状態によって時々AIカーがポップしていますが、 これは問題ないですね。 ちょっと突っ込みどころを残したほうが、あえてテストっぽさが出るしいいかな?という判断です。 誰が見てもおかしいところは開発者はだいたい把握してますって(笑

シーンレベルのモデルは流用ですが、一応スプラインベンドに対応して背景オブジェとコリジョンが追従するプロシージャルなコンストラクタを作成して これで道具が一通り揃ったので これから背景作成を始められるかなという段階ですね。

Unityでもできなくはないんですけども 全体に描画が重たい...ので 学習も兼ねて今回はUE4で作ってます。 別にUnityをやらないとかUnrealべったりというわけではなく並行していろいろ進めていますけど なんというかこれは頼まれもので、しかもこちらの都合で数年経っているという。 時間のあるときに詰めてしまわないとまた仕事が忙しくなるとテンションが下がってしまうため 今は少しこちらに比重をおいている感じです。

今どきの自動車のメッシュモデルはノーマルマップで見せかけを良くすると言ったごまかしが効かないので (あまりきれいなハイライトが入らない)、ポリゴン数が結構食うんですよね。 最適化しなくても数万ポリゴンのモデルが処理落ちしない これは開発効率いいです。 シーンの容量はでかいですけど許容範囲じゃないかな。

いろいろと触ってみると 作業に対して見え方が変わって効率が上がったりするので、急がば回れです

 

簡単にAI実装のおはなしをしますね

AIは基本スプライン上をclosedポイント(最も近い場所ね)を探索しながら 車体のローテーションを決定してアクセルのボリュームを上げます。

障害物がある場合は左右どちらかにステアリングを切るavoid処理(障害物を避ける)を実行します。進めない場合は一旦バックギアに入れて戻す。

おおまかにはそのようなシーケンスですが、 それだけでは人間の操作ぽさが出にくいので走行スプラインは複数を配列に登録して、ゲームの進行に合わせてそれらスプラインを選択しながら走行します。 ここらへんは人間の思考と同様に状況に応じて複数パターンのラインを使い分けるということです。

そして走行データをすべて記録しておくレコードシステムを実装します。 最速ラップが出るたびにこの記録をもとにスプライン上のウェイポイントを書き換えてデータとして保持します。 ゲーム開発はあまりスケジュールに余裕がないケースが多いため、開発の序盤にレコードシステムを実装してレベルデザインができ次第テストを行いAIを強化していくことになります。 このシステムを流用したものがゴーストカーと呼ばれるもので、開発中のテストモードをそのままオプションに流用しているというか 基本開発はスケジュールがきついのでオプション系は開発に使用したコードの流用など まかないみたいなものが多かった気がします。

ゲームセンターの筐体の場合は電源を落としても基盤内部にランキングなど簡単なデータを残せるバックアップメモリが搭載されていて、この部分だけ電池が入っています。 家庭用のゲームソフトも電池のバックアップが搭載されていたので理解しやすいかと思います。 サーバーに接続していなかった時代はリリース後も筐体内部のメモリーにデータを蓄え続けて、AIが強化され続けるという仕組みになっていました ※ものによります。 ゲームはリアル世界とはどんなに近づけても法則が異なるため プレイヤーが裏技を発見して突然想定外な記録が出る場合がありますが、 そうした場合でもロム交換のようなコストの掛かる方法は回避することができるため 効率が良い実装だったようです。 もちろんメーカーによって差異があるのでどのゲームも同じではないですよ。 ただし筐体の中にデータを持つのでタイムアタックが加熱しているロケーションは敵AIも強めになりますが、過疎っているロケーションであまりプレイヤーがいないと己との戦いになってしまうという 弱点もあります。

ココらへんの昔話は またおいおいしていくとして

 

 

あとUnity感想的なもの

Unity2018からECSとJobSystemという機能がベータ版?として投入されました。 が まだ積極的に移行していないので見当違いの考えかもしれませんが 構造を理解している開発者には メモリ管理が手動でできるようになる スレッドプログラミングが比較的容易になる というのはメリットになるとは思うのですが、当初のゲームエンジンで開発のハードルを下げるという目的からは遠ざかっているような気もします。

開発の利便性を向上させる目的で、構造体に多くのアトリビュート情報を付加してあるのはエンジン側の設計の都合で それで処理の足を引っ張るようならば、そこら辺り考えずに今まで通りのコードの書き方をすれば、ECSやJobSystem使用時と同等の性能が発揮できるようにコンパイル時に最適化されるだけでよいだけでは という気がしなくもないです。 さらなる調整オプションとして使用できる分には良いのですが、 今ひとつ方向性が見えにくくなってきました。 うまく伝わるかわかりませんが、動作が不安定なアプリケーションの改善点が、「新機能のオートセーブがつきました !!」だったような なんとなくですが

今さらですがゲームエンジンもだいぶ市民権を得てきたようで 当初は「ゲームエンジンだけでプログラムの基礎を学ばなければ、ゲームエンジンがなくなったらどうするんだ 」という意見も散見されましたが (長く開発を続けている職人さんね) もうさすがに急になくなるとかは無いでしょう。 スクラッチでゲームエンジンと同等の質と開発スピードに対抗できるならば分かりませんが。 寿司職人さんが、「回転寿司が急になくなったらどうするんですか」といっているようなもので、市場の需要を考えれば、まずないでしょう。

将来 現行ゲームエンジンにとって変わるものが出てきたとして使用するのは人間ですからインターフェースは大きなシェアを持っているツールに寄せてくるでしょうし 導入しやすい似たような環境になると思うので ビッグウェーブに乗っていけば大丈夫ではないでしょうか。

 

問題としてはゲームエンジンは進化が早いので必死に機能をマスターすることを重点にしてしまうとゲームを作れず終わる可能性はあります。しかも割と多いタイプな気がします。 ネットは大量の情報が参照できますけど ゲームはシステムや種類ごとに実装方法が異なるため情報化しにくく ネット上では制作物にぴったりと合った情報が見つけにくいか 見つからない そのため実装して制作ノウハウを蓄積していく必要があってその部分はネットの情報だけでは補強できないからです。

「クックパッドを毎日閲覧して料理の知識を身につけたら いつかはシェフになれるはず!」といった会話を小耳に挟んだとして ナンセンスなのでまずは実際に料理を作ってくださいというアドバイスをすると思いますがどうでしょう。 とりあえず実際に制作してゲーム制作ノウハウを身につけていけば 現行のゲームエンジンが廃れようがどんと来いなので、モノをどんどん作らないとですね。

 

 

ということで、 もうちょっと頑張っていきましょう

ではまた

 

 

現在Editorクラスでプリセット周りの実装をしているのですが、古めのスクリプトが仕様の変更で動作しないため しらべている最中です。

Editorクラスの情報は大量に存在しているので需要があるかわかりませんが。せっかくなので今回調べた経過を記事にしておきます。

 

まずUnityのEditorクラスから格納される内部データがどのように格納されているのかとデータへのアクセスのための実装ポイント、を簡単に解説している記事がありました。

 

■Accessing Unity's saved palettes

Q: 1つのパレットを保存する場合、それはスクリプト可能なオブジェクトのように見えるに格納されることに気づいた のですが、そこに保存されている色にアクセスする方法はありますか? できれば本当に便利です!

Screen Shot 2018-01-24 at 5.04.44 pm-CiS9zWDpoW

Screen Shot 2018-01-24 at 5.04.50 pm-VCX4uZJpsD

 

1.クリックしたものの種類を把握するように設定することができます:

  1. public static class EditorCommands {

  2. [MenuItem("Commands/Get Type Of Selected")]

  3. public static void GetTypeOfSelected() {

  4. Debug.Log(Selection.activeObject?.GetType().Name);

  5. }

  6. }

2.これを使用すると、パレットのタイプがColorPresetLibraryであることがわかります。さて、それは私たちが協力できるタイプですか?あなたがそれを行うにはいくつかの方法がありますが、有能なコードエディタがあれば、その名前を検索して、コンパイルされていないバージョンの型を見つけることができます:

  1. namespace UnityEditor

  2. {

  3. internal class ColorPresetLibrary : PresetLibrary

  4.    ...

  5. }

3.まあ内部的なので、スクリプトからアクセスすることはできません。アセットをテキストエディタで開いてその外観を確認してみます。

  1. %YAML 1.1

  2. %TAG !u! tag:unity3d.com,2011:

  3. --- !u!114 &1

  4. MonoBehaviour:

  5.   m_ObjectHideFlags: 52

  6.   m_PrefabParentObject: {fileID: 0}

  7.   m_PrefabInternal: {fileID: 0}

  8.   m_GameObject: {fileID: 0}

  9.   m_Enabled: 1

  10.   m_EditorHideFlags: 1

  11.   m_Script: {fileID: 12323, guid: 0000000000000000e000000000000000, type: 0}

  12.   m_Name:

  13.   m_EditorClassIdentifier:

  14.   m_Presets:

  15. - m_Name:

  16.     m_Color: {r: 1, g: 1, b: 1, a: 1}

  17. - m_Name:

  18.     m_Color: {r: 0.9705882, g: 0.007136685, b: 0.007136685, a: 1}

  19. - m_Name:

  20.     m_Color: {r: 0.13559689, g: 0.4712593, b: 0.5588235, a: 1}

さて、これは簡単に操作できます! そのテキストファイルをつかんで、 "m_Color"で始まるすべての行を探し、色を解析することをお勧めします。それをヘルパーメソッドとして作成するのはかなり簡単です。

 

次にヘルパークラスの実装例を探してみました

ヘルパーメソッドの実装例

 

Unityでプリセット内部変数にアクセスで検索すると上位にくるサイトですが掲載から時間が経過しているためスクリプトは手直しを入れる必要があります。掲載されているHelper関数のスクリプトですが以下のようなメッセージが帰ると思います。

error CS0619: `UnityEngine.Types.GetType(string, string)' is obsolete: `This was an internal method which is no longer used'
unity5以降の仕様変更でこのやり方では内部データにアクセスできないためエラーが返ります。そこで以下のように。
  1. Unityちゃん2Dのインポートのエラー解決(error CS0619)[ver.2017.2.1f1] - Qiita
  2. UnityEngine.Typesが使えなくなりました - FreelyApps
1.のサイトリンクではこのような変更で、エラーの回避ができたということです。
Types.GetType("UnityEditor.AnimationClipEditor", "UnityEditor.dll");
  ? var baseType = Types.GetType("UnityEditor.AnimationClipEditor", "UnityEditor.dll");
  ○ System.Type baseType = System.Reflection.Assembly.Load("UnityEditor.dll").GetType(typeName);
 
 

2.のサイトでは

  •   UnityEngine.Types.GetType(className,"Assembly-CSharp");
  •  System.Reflection.Assembly.Load("Assembly-CSharp").GetType(className);

IDEのサジェストにしたがうとSystem.Reflectionは省略して 良いそうなので

  • Assembly.Load("Assembly-CSharp").GetType(typeName);

このように記述することでエラーは回避できるようです。  さらに以下のような記述があります。

  • System.Type.GetTypeというメソッドでもTypeを取得できるようでした。型の名前を引数にとり、型を返すメソッドです。実行中のアセンブリ(dllと考えていい)かMscorlib.dllに含まれる型であれば名前空間で修飾した型名で型が取れるようです。
  •   System.Type.GetType(className+ ",Assembly-CSharp");

 

 

 

さらに 検索してみたところ以下のようなスクリプトが発見できました、新しめの2018.2月の情報ですが、コメント部分が詳細なので参考にしてみてください。

ColorPresetLibraryCreator.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Reflection;
public static class ColorPresetLibraryCreator
{

/*

■1:

Unity creates new preset librarys internally through typed singleton instances of the PresetLibraryManager class.

The two key methods we need to access through reflection are:

public T CreateLibrary(ScriptableObjectSaveLoadHelper helper, string presetLibraryPathWithoutExtension) where T : ScriptableObject

public void SaveLibrary(ScriptableObjectSaveLoadHelper helper, T library, string presetLibraryPathWithoutExtension) where T : ScriptableObject

CreateLibrary does some file path checking before creating the library through the helper object

and registering it with the library cache. SaveLibrary does what it says through the helper object.

In between the two calls is when we can actually add presets to the library.

We could use the helper object directly to save the library but it's probably safer to let the Manager class do it.

*/

    private const string assemblyDef = "UnityEditor.{0},UnityEditor";

    public static void CreateNewLibraryThroughPresetLibraryManager(string name, List colors)
    {

   

//■2:

// The ScriptableSingleton class is public, but because PresetLibraryManager isn't

// we still need to make a generic type and then use reflection to get the static instance property.

// This is assuming that we need the singleton instance for library registration purposes -

// it might not be necessary.


        Type managerType = Type.GetType(string.Format(assemblyDef, "PresetLibraryManager"));
        Type singletonType = typeof(ScriptableSingleton<>).MakeGenericType(managerType);
        PropertyInfo instancePropertyInfo = singletonType.GetProperty("instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
        var managerInstance = instancePropertyInfo.GetValue(null, null);

// ■3:

// We create an instance of the save/load helper and then pass it and the path to the CreateLibrary method.

// "colors" is the file extension we want for the library asset without the '.'

 
        Type libraryType = Type.GetType(string.Format(assemblyDef, "ColorPresetLibrary"));
        Type helperType = Type.GetType(string.Format(assemblyDef, "ScriptableObjectSaveLoadHelper`1"))
                              .MakeGenericType(libraryType);
        MethodInfo createMethod = managerType.GetMethod("CreateLibrary", BindingFlags.Instance | BindingFlags.Public)
                                             .MakeGenericMethod(libraryType);
        var helper = Activator.CreateInstance(helperType, new object[] { "colors", SaveType.Text });
        var library = createMethod.Invoke(managerInstance, new object[] { helper, Path.Combine("Assets/Editor", name) });

//■4:

// We can't cast library to the desired type so we get the Add method through reflection

// and add the desired colours as presets through that.

// After that the library can be saved!


        if ((UnityEngine.Object)library != (UnityEngine.Object)null)
        {
            MethodInfo addPresetMethod = libraryType.GetMethod("Add");
            foreach (var color in colors)
            {
                addPresetMethod.Invoke(library, new object[] { color, color.ToString() });
            }

            MethodInfo saveMethod = managerType.GetMethod("SaveLibrary", BindingFlags.Instance | BindingFlags.Public)
                                             .MakeGenericMethod(libraryType);
            saveMethod.Invoke(managerInstance, new object[] { helper, library, Path.Combine("Assets/Editor", name) });
        }

■5:

// The library could be returned as an Object or ScriptableObject reference if that was useful

// I don't know of a way to cast it to the actual ColorPresetLibrary type.

   
    }

// Extension function because why not
    public static void CreateNewPresetLibrary(this List colors, string name)
    {
        CreateNewLibraryThroughPresetLibraryManager(name, colors);
    }
}

※誤訳があるかもしれませんのでスクリプト中のコメントで確認してください。

 1.  Unityは、PresetLibraryManagerクラスの型指定されたシングルトンインスタンスを通じて、内部的に新しいプリセットライブラリを作成します。リフレクションを通じてアクセスする必要がある2つの重要な方法は次のとおりです。

  • public T CreateLibrary(ScriptableObjectSaveLoadHelper helper, string presetLibraryPathWithoutExtension) where T : ScriptableObject
  • public void SaveLibrary(ScriptableObjectSaveLoadHelper helper, T library, string presetLibraryPathWithoutExtension) where T : ScriptableObject


  CreateLibraryは、ヘルパーオブジェクトを通してライブラリを作成する前に、いくつかのファイルパスのチェックを行いライブラリキャッシュに登録します。 SaveLibraryはヘルパーオブジェクトを通して何を言うのかを行います。
2つの呼び出しの間に、ライブラリーに実際にプリセットを追加することができます。
ヘルパーオブジェクトを直接使用してライブラリを保存することもできますが、Managerクラスで行うほうが安全でしょう。

2. ScriptableSingletonクラスはpublicですが、PresetLibraryManagerはpublicではないため ジェネリック型を作成し、リフレクションを使用して静的インスタンスプロパティを取得する必要があります。これは、ライブラリ登録の目的でシングルトンインスタンスが必要であると仮定していますが、必要ないのかもしれません。

3.  セーブ/ロードヘルパーのインスタンスを作成し、それをパスとCreateLibraryメソッドに渡します。"colors"は、ライブラリアセットに必要なファイル拡張子で '.'を必要としません。

4. ライブラリを目的の型にキャストできないため、リフレクションでAddメソッドを取得します プリセットとして必要な色を追加します。その後、ライブラリを保存することができます!
 
5.  有用であれば、ライブラリはObjectまたはScriptableObject参照として返すことができます。 ColorPresetLibraryタイプにキャストする方法はわかりません。
なぜ拡張機能なのでしょう?

 

この解説によればUnityではManagerクラスが用意されているので内部データへのアクセスはそちらを使用することで安全なアクセスができるようです。今回の場合PresetLibraryManagerのコードを参照することになります、

PresetLibraryManageにどのようなクラス定義がされているのか簡単に抜粋してみました

unity-decompiled/UnityEditor/UnityEditor/PresetLibraryManager.cs

 

  • public void GetAvailableLibraries(ScriptableObjectSaveLoadHelper helper, out List preferencesLibs, out List projectLibs) where T : ScriptableObject
  • private string GetLibaryNameFromPath(string filePath)
  • public T CreateLibrary(ScriptableObjectSaveLoadHelper helper, string presetLibraryPathWithoutExtension) where T : ScriptableObject
  • public T GetLibrary(ScriptableObjectSaveLoadHelper helper, string presetLibraryPathWithoutExtension) where T : ScriptableObject
  • public void UnloadAllLibrariesFor(ScriptableObjectSaveLoadHelper helper) where T : ScriptableObject
  • public void SaveLibrary(ScriptableObjectSaveLoadHelper helper, T library, string presetLibraryPathWithoutExtension) where T : ScriptableObject
  • private PresetLibraryManager.LibraryCache GetPresetLibraryCache(string identifier)
  • public List loadedLibraries
  • public List loadedLibraryIDs
  • public LibraryCache(string identifier)
  • public void UnloadScriptableObjects()

 

まだ全体を調べ終わっていないので実装までは時間がかかりそうですが、今回はここまで調べたということでメモ代わりに記事にしておきました。 参考になれば幸いです。 それではまた

 

 

 

■  1回ドローコールで複数のモデルをインスタンシング表示

今回のマージドメッシュインスタンシングの例

mergedmesh01

 

今回は複数のメッシュモデルをインスタンシングで同時に表示する つまり1ドローコールで何種類ものモデルを描画する方法ですね。

マージドメッシュ(MergedMesh)という呼び方があります。 インスタンシングによるバッチ処理も似たような効果ですがそちらはモデル自体をそのまま結合する方法なのである程度のデータの塊に限定されます。 一つのモデルが65536ポリゴンまで表示可能なこの方法とは少し異なります。

簡単にインスタンシング処理を説明するとCPUとGPUのメモリは分かれているのでメッシュデータはバスを通してGPUに毎回転送することになります。 転送済のデータのシャドーコピーをインスタンスとして複製表示することで描画処理を向上させるのが、メッシュのインスタンシングです。

複数のメッシュモデルはスタティックバッチやダイナミックバッチのような実行時にある程度の塊に結合して転送する方法があります GPUに一度に転送できるサイズは上限があるため複数回に分けて転送が行われますが。その回数分データ転送の処理時間がロスするため実行速度が低下することになります。

今回の方法ではメッシュ自体に形状をもたないトライアングルの塊を転送してGPUの側で復元するためどのような形状でもインスタンシング可能でメモリが許す限り何種類のメッシュでも1ドローコールの表示が可能です。

 

ちなみに画像の右上のstatではドローコールは2となっていますがこれは間違っていると思います 。表示物がないシーンでも

標準のライトパス:1 + シャドーパス:1 + カスケード:3 =5パス

のドローコールが必要なのでインスタンス表示自体のどローコールは1ですが 実際にはStatのドローコールは6程度になるはずです。がそれでも大分少ない描画負荷ですね。 カスケードというのはカメラの距離に応じて手前から奥に向かって3回シャドーのLODなどのレンダリングパスです

■マージドメッシュインスタンシングのあらまし

■fig1

MergeInstance3

fig1は実装例の画像です。同じような解説を繰り返しますが 図のように背景のレベルに同様のモデルが複数存在するような場合に インスタンシングが有効ですが、 インスタンシング描画とは一度GPU側に送ったメッシュデータをコピーして使いまわすことで描画速度を向上させる技術です、 メッシュモデルはそのままだとモデルオブジェクト一つにつき1ドローコールが発生しますが、それを軽減する技術がご存知のようにバッチ処理と呼ばれる複数モデルを結合転送する方法です。GPUに一度に送れる上限が65536ポリゴンまでなので、それ以上のメッシュサイズの場合はある程度何度かに分けて転送されます 分割された結合メッシュの個数分はドローコールが発生してしまうということになります。

そこで何度も描画される形状メッシュモデルをインスタンシング描画することで描画処理の速度を向上することができます。 ただし通常であればインスタンシングはセットされたメッシュモデル単体をコピーするのみなので、メッシュモデルの群衆を描画した場合でもすべて同じモデルが表示されてしまい、複数のモデル種類を表示する場合はモデルデータごと何度かインスタンシングのドローコールが発生してしまうためある程度までのモデル種類までは表示できますが 表現的には若干単調になってしまうかもしれません。 もちろんデスクトップPCなどのパワーの有るハードでであれば多少のドローコールは問題ないですが。ゲームの場合必ずしも高性能ハードで実行されるわけではないので少しでも余力を残したいところです。

 

■実装例:SIGRAPH2012より

■fig2

MergeInstance2

■fig3

MergeInstance1

fig3図のように メッシュインデックスはパックしたモデルデータにパックした順番にIDを割り付けます。 それらIDが示すのは頂点データ配列のインデックス <頂点座標、ノーマル、UV>などが格納されたデータの先頭インデックスを呼び出します。

それにより元のメッシュの頂点データが配列内の頂点データに置き換わり 見かけ上別のメッシュモデルを表示することができるという仕組みになります。

 

■マージドメッシュのポイントスプライト実装例

■fig4

MergrInstance_Ps

 

マージドメッシュを使用した基本コード例としてパーティクルのポイントスプライトが挙げられます。 GL系ではPointSprite命令をPontSizeというポイントに大きさを与える命令で実装しているコードを見かけますが。 HLSLシェーダの場合はマージドメッシュを使用してビルボードポリゴンを描画するという スニペットと呼んでよいのかな このような手法で実装されることが多いようです。

fig4のコードを簡単に解説します

頂点バッファにはこの場合Quad(四角形メッシュ)単位でデータが入力されるのでなので連続した4頂点が四角形ポリゴンの1セットとなります。

入力の id:SV_VERTEXID は頂点シェーダに入力される連続した頂点列に0から順番に頂点数分の割り付けられたIDで

 

uint particleIndex = id / 4;   パーティクルのインデクスは四角形メッシュの先頭を4で割り算して求め

uint VertexInQuad =id % 4; 4で割ったあまりが各四角形メッシュのサブIDで0,1,2,3 頂点順の番号を取得できます。

position.x = (vertexInQuad % 2 )  ? 1.0 : ?1.0; 四角形メッシュの4スミの座標を決定します。

position.y = (vertexInQuad % 2 )  ? -1.0 :  1.0;

position.z = 0.0;

このように入力頂点データから 四角形メッシュを復元します。 入力頂点座標データはどんな値が入ってきてもよいのですが、とりあえず 0で初期化されて入ってくると仮定します。

Position =  mul(Position , (float3x3)g_mInvView   )  +  g_bufPosColor[ ParticleIndex   ].Pos.xyz;

g_bufPosColor[ ParticleIndex ].Pos.xyz はパーティクルIDごとに座標データが帰るので、 Positionに加算することでパーティクルがバッファーの値の座標に移動します。バッファーはパーティクルのPosition座標が計算されて入力された配列です。

position.xy *= PARTICLE_RADIUS; パーティクル大きさにサイズを成分を掛けて 最後に頂点にMVP(プロジェクション行列)を掛けてワールド上のカメラからの見かけ位置に配置するとパーティクルビルボードが完成します いつもの演算ですね。

 

 

■シェーダスクリプトの実装

meshIndex0_thumb[1]

 

頂点IDを取得する場合 HLSLでは標準では以下のようになります

v2f vert (uint id : SV_VertexID, uint inst : SV_InstanceID){}

uint id : SV_VertexID :  頂点IDは入力されたメッシュモデルの頂点番号順に割り付けられるID

uint inst : SV_InstanceID: インスタンシングされたメッシュモデルごとに割り付けられるID

idデータを別のシェーダ内で使いまわしたい場合はPOSITIONデータなど同じく構造体に含める必要があります。

Unityヘルパー関数があるので以下のように

UNITY_VERTEX_INPUT_INSTANCE_ID :頂点IDの取得
UNITY_SETUP_INSTANCE_ID(v);    :インスタンスID の取得
UNITY_TRANSFER_INSTANCE_ID(v, i);
unityマクロでTRANSFERの名称がついている場合は構造体に含める作業を省略できます。

書き方はどちらでもかまわないです。Unity以外の既存のシェーダを書き換える場合などに参考にはなります。

fig2で vertex頂点の処理をfor命令で表現していますが、シェーダを入力されたデータ数だけ処理を実行するFOREACHループ

と捉えると、理解しやすいかもしれません。

vertexシェーダは入力頂点数分のループで、fragmentシェーダはスクリーン上のピクセル数分のループと考えてください。

 

トライアングルメッシュデータを今回は Graphics.DrawProcedural( )  命令で転送しますこれはMeshTopology.Trianglesか MeshTopology.Quadsのようにストリップの方法を指定してインスタンス描画する命令です。

Quads(四角形ポリゴン)は内部的にはTriangles(三角形ポリゴン)が2回記述するのと同義なので、Triangles(三角形ポリゴン)だけを対応します。

シークエンスは以下のようになります

  1. 複数のメッシュモデルからVertexデータを取得してストラクチャバッファないしテクスチャバッファにセットする。データはすべてを連続して配列にまとめて、それぞれのメッシュモデルのトライアングル数をシェーダに配列で渡す。インデックスIDはトライアングル数と対応させておく。
  2. インスタンシングには初期化された大きさのないトライアングルメッシュをセットする トライアングルの全体数は描画するメッシュモデルのポリゴン数をカバーできる大きさに設定する。つまりGPUに一度に転送できる最大ポリゴン数65536であればロスはあるが再設定の必要なくすべてのモデルデータのポリゴンサイズがカバーできることになる。
  3. 頂点シェーダで描画する際にSV_VertexIDをもとにしてバッファ(ストラクチャバッファ)から対応する頂点データを呼び出し頂点データを置き換える。 これによりポリゴントライアングルが大きさやノーマルなど属性を与えられ描画される。今回は頂点データをトライアングルに1体1対応させているので、データサイズは多少大きくなるがポイントスプライト例のようなVertexIDを割り算するような対応は必要ない。
  4. インスタンシング命令の描画時に設定したモデルのポリゴン数を上回った余分なポリゴンメッシュを描画スキップさせる。 頂点シェーダにはフラグメントシェーダのようにスキップ命令がないので、ポリゴンにスケール0を代入するか頂点座標にfloat3(0、0、0)を代入するまたは頂点アルファで非表示にするなどの方法が考えられる。

欠点というか注意点は

  1. ドローコールを抑えるにはマテリアルは基本的に1種類なので同じようなマテリアルを使用するモデル同士で結合する必要がある。
  2. メッシュ結合時に無駄なメッシュ部分をなるべく減らすために大体同じようなサイズのメッシュでまとめる必要がある。

 

■配列の転送

meshIndex

気をつけるポイントは 繰り返しますが

  • なるべく同じポリゴンサイズのメッシュでまとめて表示することでデータのロスを減らすこと
  • 偶数倍のサイズを指定すること :固定サイズの倍数を指定するだけでデータの先頭インデックスを計算できます。 条件分岐を発生させないため演算処理は向上します

下のように先頭インデックスの計算は必要となりますが、データの終了判定が必要なくなる分演算が軽くなります。ハードのスペックにもよりますけど・フレームレートで比較してもらうと条件分岐の処理がありなしで結果がだいぶ変わることがわかるかと思います。

id_offset =  _SegmentsID[inst]* _MaxLength

UNITY_BRANCH

if (id < id_offset + _SubDataSize[_SegmentsID[inst]])

?

id_offset =  _SegmentsID[inst]* _MaxLength

パディング(詰め物)でサイズを調整することでIF分岐の記述が必要なくなり処理速度の最適化につながります。

ちなみに if分岐で 比較時に(uint)型id と int型のindexで型エラーが生じると思いますので、速度は uint<int< float ですのでこの場合はuintをint型にキャストしてください

 

あと掲載するまでもないと思うのですが。 List<> 操作ですね STRUCTを使用する場合とCLASSを使用する場合がありますが

配列の大きさが不定の場合使用すると操作が楽になりますね 一応CLASS多用はメモリ分断が起きやすいのガベージコレクション上の問題があるという最適化の点から構造体でLIST操作をするのが良いかもしれません。 LIST型と配列は相互に変換できますのでケースに応じてします。

とりあえず初心者向けな解説

using System.Collections.Generic;

...
   List ppoints = new List();
    ppoints.Add(new Point {  });

           または

  List DataItems = new List();
   var cd = new ColumnData();
   cd.ColumnName = "Taco";
   DataItems.Add(cd);

string[] stringArray = stringList.ToArray();

 

 

■分岐命令のマクロ

Unityはif分岐で UNITY_BRANCH UNITY_FLATTEN のマクロ命令が定義されていますが これは HLSLのBRANCHとFLATTENを置き換えるだけのマクロ命令なので

  • GLには作用しません
  • #if はUnityのマルチコンパイルのマクロですのでこれにも作用しません

BRANCH オプションは分岐の片方だけを実行して評価するオプションでマルチコンパイルみたいなものでHLSLの場合にのみ有効。

FLATTENは分岐の両側を両サイドを実行して一方の値を選択します。 旧式のGPUで採用されている分岐がFLATTEN型なので処理が重かった

ということですが、 FLATTENの用途としてはif分岐でどちらか一方のみで定義された値がもう一方で参照されてもエラーが起こらない ということですが あまり使い所が思いつかないですね。

BRANCHは本家のフォーラムだとあまり期待するなというアドバイスもありましたが。害がないのでとりあえず書いておけばいいんじゃないかなと

 

■シェーダーの配列サイズ

モデルの種類にIDを指定する配列はコンスタントバッファで指定しています。IDをは入れるインデックスを指定すためにint型で指定しますがUnityでSetIntArrayはサポートされていない様です。しかし SetFloatArrayで代用できるようです コンスタントバッファにInt型で配列を確保して スクリプトから渡すデータはフロート型でシェーダ内はInt型でデータが渡るようなのでこれでいきます。

以前も書きましたが配列はシェーダ内で特に指定をしない場合はコンスタントバッファ扱いのようです コンスタントバッファは1024バイトのストライド長なので 。モデルの種類サイズが大きくなるようであればストラクチャバッファなど大きなサイズの配列を確保してください。もっともそんなに何種類も同時に表示する場面はなさそうなのですが。

シェーダでは配列の動的サイズは確保できませんので、マルチコンパイルオプションでキーワードをセットして

スクリプト側:

Shader.EnableKeyword("MYDEFINE")
Shader.DisableKeyword


シェーダー側:


#if defined(MYDEFINE)
...

#endif

今回の場合例えば次のように#defineで配列を初期化するようにしておけばスクリプト側から配列サイズを制御する。

#define ARRAY_SIZE128

#define ARRAY_SIZE256

#define ARRAY_SIZE512

#define ARRAY_SIZE1024

※シェーダ内部からならKeywordを使用して#defineで書きますか

//////////////////////////////////////////////////////////////////////////

#if defined(ARRAY_SIZE128 )

float _Array[128];

#endif

#if defined(ARRAY_SIZE256 )

float _Array[256];

#endif

#if defined(ARRAY_SIZE512 )

float _Array[512];

#endif

#if defined(ARRAY_SIZE1024)

float _Array[1024];

#endif

この書き方で多分大丈夫だと思いますが SSAOシェーダを参考にすると良いかも。

 

■構造体を定義する場合のTips的なもの

一つの構造体にパックする変数のサイズにも制限があって DirectXは128ビットを一単位として扱うそうで、例えばfloat4型などが転送の最小単位となるようです。 struct構造体単位が128ビットの倍数でパックされるようにしないと 足りないビット分は構造体の次の要素の一部からをセットで転送しようとするため転送ロスが発生するということだそうです。 場合によっては30%程度の速度低下が生じることがあるそうで、公開されてるコードで確認するとだいたいそうならないように最適化されていると思います。

struct Point {
	float3  vertex;
	float3  normal;
	float4  tangent;
	float2  uv;
	float4  color;
};

構造体を設計する場合に128ビットの倍数に足りない分は必要がなくてもfloat4型に定義するか、またはダミーのパディングデータを含めておくことで転送の速度ロスが軽減できるということです。 もちろん実行速度が十分なら気を使う必要はないですよ 既存コードを参考にしたときにデータを軽くしようとしてうっかり最適化部分を削ってしまうことはありそうなので 知識として持っておく程度でかまいません。

struct Point {
	float3  vertex;
	float3  normal;
	float4  tangent;
	float2  uv;
                                float2 uv2;
	float2 dummy;   このようにダミーデータを含めることで全体で128ビットの倍数になるように
     または float4 ....;にしてしまう
};

■テクスチャのアトラス化

モデル単位で複数のテクスチャ指定があると条件分岐が必要になるため 処理負荷軽減のためにもアトラス化が必要になります。 他の理由としてテクスチャ選択に条件分岐があると描画が崩れる場合があったのですが原因はまだわかりません。

スクリプトでメッシュを配列にパックする段階でアトラス化されたUVが指定されるようにすればどのようにUVがアトラス化されていても構いません。あらかじめUVがスクリプトのUV座標の計算部分はスルーして元のモデルからコピーするだけです。

InstancedMesh_Color

■LOD対応

メッシュLODはインスタンシングでオプションパラメータがサポートされていますが 今回の方法の場合はLODモデルを一緒にメッシュ配列にパックしカメラからの距離を参照してLODメッシュに割り付けたIDで選択するようにすれば良いと思います。

通常のインスタンシングの場合pragmaを記述するとlodに自動で対応できます。
 #pragma instancing_options lodfade 

■カリング対応

テセレーションシェーダからの抜粋ですが unity_CameraWorldClipPlanes[planeIndex] とトライアングル頂点の内積(Dot)をとることで トライアングルメッシュがスクリーンの内側にあるかの判定を行い 3頂点とも外部にあれば描画をスキップするようにすることでポリゴン単位でのカリングができます。 トライアングルがスクリーンを覆うように大きく表示される場合もあるので、重心で判定などの手抜きは突然欠けたりで危険ですね。

bool TriangleIsBelowClipPlane ( float3 p0, float3 p1, float3 p2, int planeIndex ) {
float4 plane = unity_CameraWorldClipPlanes[planeIndex];
return
dot(float4(p0, 1), plane) < 0 &&
dot(float4(p1, 1), plane) < 0 &&
dot(float4(p2, 1), plane) < 0;
}

モデル単位であれば、バウンディングボックスにモデルごとにトランスフォーム行列を掛けて 同様にスクリーンの内部にあるか判定をすれば良いと思います。 できる限りシェーダーでできることはスクリプトで書くのを避けたほうが良いと思います。

 

■コリジョンチェック

基本的にはシェーダ側の処理でできることはシェーダ側で済ませないと行けません CPU側からデータを触るとデータの移動が発生するためせっかく描画処理が向上してもそこで処理速度の低下を招きますので、セットするデータはなるべく小さく、できれば数フレームに一回で済むようにしたほうが良いです。シェーダ側のほうがCPUに比べて数十倍実行速度が速いので頻繁に値が書き換わるとデータ取得がコケる。調整すれば使えるかな。という範囲です。

プレイヤーの座標データをシェーダ側にセットすればインスタンスとlength()関数を使用して距離が計測できるので、最短距離または距離を近い順に並べた配列のインスタンスIDをシェーダ側の変数にセットする。プレイヤー座標とインスタンスの座標の方向が取得したい場合はベクトルもセットしておくことで、スクリプト側でGetを使用して変数の値を呼び出せばプレイヤーのヒット情報と状態が取得できます。 描画とはシェーダを分けて書いても良いし 描画数が多ければコンピュートシェーダが有効です。 AIのからみになると背景とのヒットチェックはDepthテクスチャを使用して計算するか または 上方からコリジョンを2Dレンダリングした背景画像を使用するなどで対応はできます。 スワップバッファを用いたスワップチェーン(シェーダで計算結果をレンダテクスチャで連続で入れ替える方法 パーティクル描画などで使いますね)かコンピュートシェーダを使用する方法になると思いますが NVIDIAには無いサンプルでもAMDかINTELのサンプルデモにはあったかと思います。

 

 

■コード・インスペクタなど サンプル

  • インスペクタの表示
mergedmesh02 インスペクタはプレファブエレメンツのサイズを指定して
マテリアルにシェーダをセットするだけです。

PREFABS:
メッシュモデルにMeshFilterをセットしてください IDは0から順番に割り付けます

INSTANCE COUNT:
全体のインスタンシングの数

SEGMENT LENGTH:
モデルの最大ポリゴン(トライアングル)数を セットしますポリゴンサイズが値より小さい場合データの残りがパディングで埋められます。

TEXDIM X、Y:
テクスチャアトラスのx,yの分割数です

 

■SAMPLE CODE A:   メッシュ配列にパディングを追加しているバージョン

"ProceduralMeshIstancing.cs”

 
using UnityEngine;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace   ProcedualScene {

public struct Point
{
public Vector3 vertex;
public Vector3 normal;
public Vector4 tangent;
public Vector2 uv;
public Color color;
}

  [ExecuteInEditMode]
    public class ProceduralMeshIstancing : MonoBehaviour
    {
        public Material _Material;

        [SerializeField]
        private GameObject[] _Prefabs;

        public int _InstanceCount = 10;
        private ComputeBuffer _ComputeBuffer;
      
        private int _MeshBufferSize = 0;

        [SerializeField]
        private int _SegmentLength = 65000;

        [SerializeField]
        private int _TexDimX = 2;
        [SerializeField]
        private int _TexDimY = 2;


        void Start()
        {

            Mesh mesh;

            for (int i = 0; i < _Prefabs.Length; ++i)
            {
                mesh = _Prefabs[i].GetComponent().sharedMesh;
                _MeshBufferSize += mesh.triangles.Length;

            }

            _MeshBufferSize = _SegmentLength * _Prefabs.Length;

            Point[] points = new Point[_MeshBufferSize];

            for (int i= 0; i < _MeshBufferSize; ++i)
            {
                points[i].vertex = Vector3.zero;
                points[i].normal = Vector3.zero;
                points[i].tangent = Vector4.zero;
                points[i].uv = Vector2.zero;
                points[i].color = Color.white;
            }


            for (int j = 0; j < _Prefabs.Length; ++j)
            {
                mesh = _Prefabs[j].GetComponent().sharedMesh;

                for (int i = 0; i < mesh.triangles.Length; ++i)
                {
                    points[_SegmentLength*j + i].vertex = mesh.vertices[mesh.triangles[i]];
                    points[_SegmentLength*j + i].normal = mesh.normals[mesh.triangles[i]];
                    points[_SegmentLength*j + i].tangent = mesh.tangents[mesh.triangles[i]];
                    points[_SegmentLength*j + i].uv = mesh.uv[mesh.triangles[i]];

                    points[_SegmentLength*j + i].uv.x /= _TexDimX;
                    points[_SegmentLength*j + i].uv.y /= _TexDimY;
                    points[_SegmentLength*j + i].uv.x += (j % _TexDimX) * 1.0f / _TexDimX;
                    points[_SegmentLength*j + i].uv.y += (1.0f - j / _TexDimX) * 1.0f / _TexDimY;

                    points[_SegmentLength*j + i].color.r = j / 255.0f;

                }
 
            }
          
            _Material.SetInt("_MaxLength", _SegmentLength);

            _ComputeBuffer = new ComputeBuffer(_MeshBufferSize, Marshal.SizeOf(typeof(Point)), ComputeBufferType.Default);
            _ComputeBuffer.SetData(points);
            _Material.SetBuffer("points", _ComputeBuffer);

            var Prefab_id = new float[_InstanceCount];

            for (int i = 0; i < _InstanceCount; ++i)
            {
                Prefab_id[i] = Random.Range(0, _Prefabs.Length);
            }

            _Material.SetFloatArray("_SegmentsID", Prefab_id);

        }

        void OnRenderObject()
        {
            _Material.SetPass(0);
            Graphics.DrawProcedural(MeshTopology.Triangles, _SegmentLength, _InstanceCount);       
        }

        void OnDestroy()
        {
            if (_ComputeBuffer != null)
                _ComputeBuffer.Release();
            _ComputeBuffer = null;

        }
    }
}


 

"DX11 MergedInstancing.shader”

  Shader "DX11 MergedInstancing" {

   Properties
    {
    _MainTex("Texture", 2D) = "white" {}
    }
     SubShader {
         Tags {"LightMode" = "ForwardBase" }
                
         Pass {
         CGPROGRAM
         #include "UnityCG.cginc"
         #pragma target 5.0 

         #pragma vertex vert
         #pragma fragment frag
      
         sampler2D _MainTex;
         float4 _MainTex_ST;

         uniform fixed4 _LightColor0;

CBUFFER_START(UpdatedSegmentVariables)
int _SegmentsID[1024];
int _MaxLength;
CBUFFER_END
       

struct Point {
float3  vertex;
float3  normal;
float4  tangent;
float2  uv;
float4  color;
};

StructuredBuffer points;

         struct v2f {
             float4 pos : SV_POSITION;
             float4 col : COLOR;
             float2 uv : TEXCOORD0;

         };

         v2f vert (uint id : SV_VertexID, uint inst : SV_InstanceID)
         {
             v2f o;

int  id_offset = (int)(_SegmentsID[inst]* _MaxLength);
             id += id_offset;

// 頂点IDごとバッファーから頂点データを取得します
                float4 vertex_position =  float4(points[id].vertex,1.0f);
             float4 vertex_normal = float4(points[id].normal, 1.0f);
// 座標変換はここに記述してください
vertex_position.x += 1.0f*(inst%10);
vertex_position.z -= 1.0f*(inst / 10);

             o.pos = mul (UNITY_MATRIX_VP, vertex_position);
             o.uv =  TRANSFORM_TEX(points[id].uv, _MainTex);

             float3 normalDir = normalize(vertex_normal.xyz);
float4 LightDir = normalize(_WorldSpaceLightPos0);
float4 DiffuseLight = saturate(dot(normalDir, LightDir))*_LightColor0;
             float4 AmbientLight = UNITY_LIGHTMODEL_AMBIENT*3.0;
           
             o.col=float4(AmbientLight + DiffuseLight);

             return o;
         }

         fixed4 frag(v2f i) : SV_Target
         {
    fixed4 finalColor;
   
    finalColor = tex2D(_MainTex, i.uv);
    finalColor *= i.col*1.8;
            return finalColor;
         }
      
         ENDCG
      }
     }
}


 

■SAMPLE CODE B: メッシュデータを配列にパディング無しで詰めたバージョン

"ProceduralMeshIstancing2.cs”

 
using UnityEngine;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace   ProcedualScene2 {

public struct Point
{
public Vector3 vertex;
public Vector3 normal;
public Vector4 tangent;
public Vector2 uv;
    public Color color;
}

//  [ExecuteInEditMode]
    public class ProceduralMeshIstancing2 : MonoBehaviour
    {
        public Material _Material;

        [SerializeField]
        private GameObject[] _Prefabs;

        public int _InstanceCount = 10;
        private ComputeBuffer _ComputeBuffer;
      
        private int _MeshBufferSize = 0;

        [SerializeField]
        private int _SegmentLength = 65000;

        [SerializeField]
        private int _TexDimX = 2;
        [SerializeField]
        private int _TexDimY = 2;


      

        void Start()
        {

            Mesh mesh;
           
            var _SubIndex= new float[_Prefabs.Length];
            var _SubDataSize=new float[_Prefabs.Length];



         
            for (int i = 0; i < _Prefabs.Length; ++i)
            {  
                mesh = _Prefabs[i].GetComponent().sharedMesh;

                _MeshBufferSize += mesh.triangles.Length;
                _SubDataSize[i] = mesh.triangles.Length;
               
            }
            _SubIndex[0] = 0;
            for (int i = 0; i < _Prefabs.Length-1; ++i)
            {
                _SubIndex[i+1] += _SubIndex[i]+_SubDataSize[i];
            }

          
              


                List points = new List();

            for (int j = 0; j < _Prefabs.Length; ++j)
            {
                mesh = _Prefabs[j].GetComponent().sharedMesh;

                for (int i = 0; i < mesh.triangles.Length; ++i)
                {

                    var ppoint = new Point();
                    ppoint.vertex = mesh.vertices[mesh.triangles[i]];
                    ppoint.normal = mesh.normals[mesh.triangles[i]];
                    ppoint.tangent = mesh.tangents[mesh.triangles[i]];
                    ppoint.uv = mesh.uv[mesh.triangles[i]];

                    ppoint.uv.x /= _TexDimX;
                    ppoint.uv.y /= _TexDimY;
                    ppoint.uv.x += (j % _TexDimX) * 1.0f / _TexDimX;
                    ppoint.uv.y += (1.0f - j / _TexDimX) * 1.0f / _TexDimY;

                    ppoint.color.r = j / 255.0f;

                    points.Add(ppoint);
                   
                }
 
            }
          
            _Material.SetInt("_MaxLength", _SegmentLength);

            _ComputeBuffer = new ComputeBuffer(_MeshBufferSize, Marshal.SizeOf(typeof(Point)), ComputeBufferType.Default);
            _ComputeBuffer.SetData(points.ToArray());
            _Material.SetBuffer("points", _ComputeBuffer);

            var Prefab_id = new float[_InstanceCount];

            for (int i = 0; i < _InstanceCount; ++i)
            {

            Prefab_id[i] = Random.Range(0, _Prefabs.Length);
               //  Prefab_id[i] = 1;
                Debug.Log("prefab=" + Prefab_id[i]);
            }

            _Material.SetFloatArray("_SegmentsID", Prefab_id);
            _Material.SetFloatArray("_SubIndex", _SubIndex);
            _Material.SetFloatArray("_SubDataSize", _SubDataSize);

        }

        void OnRenderObject()
        {
            _Material.SetPass(0);
            Graphics.DrawProcedural(MeshTopology.Triangles, _SegmentLength, _InstanceCount);       
        }

        void OnDestroy()
        {
            if (_ComputeBuffer != null)
                _ComputeBuffer.Release();
            _ComputeBuffer = null;

        }
    }
}

"DX11 MergedInstancing.shader2”


Shader"custom/DX11 MergedInstancing2" {

   Properties
    {
		_MainTex("Texture", 2D) = "white" {}	
    }
     SubShader {
         Tags {"LightMode" = "ForwardBase" }
                 
         Pass {
         CGPROGRAM
#include "UnityCG.cginc"
         #pragma target 5.0  

         #pragma vertex vert
         #pragma fragment frag
       
sampler2D _MainTex;
float4 _MainTex_ST;
	
uniform fixed4 _LightColor0;

CBUFFER_START(UpdatedSegmentVariables)
int _SegmentsID[256];

//int _MaxLength;
CBUFFER_END

CBUFFER_START(UpdatedSegmentVariables1)

int _SubIndex[256];
int _SubDataSize[256];
CBUFFER_END


struct Point
{
    float3 vertex;
    float3 normal;
    float4 tangent;
    float2 uv;
    float4 color;
};

StructuredBuffer points;

struct v2f
{
    float4 pos : SV_POSITION;
    float4 col : COLOR;
    float2 uv : TEXCOORD0;
			 
};
 
v2f vert(uint id : SV_VertexID, uint inst : SV_InstanceID)
{
	v2f o;

	
	int id_offset = (int)(_SubIndex[_SegmentsID[inst]]);
	id += id_offset;



	float4 vertex_position;
	float4 vertex_normal;
//	頂点IDごとバッファーから頂点データを取得します
	UNITY_BRANCH

	if ((int)id < (int)(id_offset + _SubDataSize[_SegmentsID[inst]]))
	{
	
	vertex_position = float4(points[id].vertex, 1.0f);
	vertex_normal = float4(points[id].normal, 1.0f);

             }
	else
	{
	vertex_position = float4(0,0,0,0);
        vertex_normal = float4(1,1,1,1);
	}

///////////座標変換はここに記述してください
     vertex_position.x += 1.0f * (inst % 10);
    vertex_position.z -= 1.0f * (inst / 10);
///////////////////////////////////////////
    o.pos = mul(UNITY_MATRIX_VP, vertex_position);
    o.uv = TRANSFORM_TEX(points[id].uv, _MainTex);
			
    float3 normalDir = normalize(vertex_normal.xyz);
    float4 LightDir = normalize(_WorldSpaceLightPos0);
    float4 DiffuseLight = saturate(dot(normalDir, LightDir)) * _LightColor0;
    float4 AmbientLight = UNITY_LIGHTMODEL_AMBIENT * 3.0;
            
    o.col = float4(AmbientLight + DiffuseLight);

    return o;
}
 
fixed4 frag(v2f i) : SV_Target
{
    fixed4 finalColor;
    
    finalColor = tex2D(_MainTex, i.uv);
    finalColor *= i.col * 1.8;
    return finalColor;
}
       
         ENDCG
      }
     }
}

 

■その他 今後の展開など

メッシュモデルを何種類表示しても 基本ドローコールは1となりますが 描画処理の負荷はかかるのでFPSは安定して高いというわけにはいきません。 樹木や、建物、プロップ類、ガレキ、キャラクタ あるいはエフェクト類などでまとめられるものを一度に表示することで、描画の負担は軽減できますのでアイデア次第では使えるテクニックです。

今回のサンプルはMeshFilterのみで作成しました。スクリプト部分は難しいところはないので 自分が使いやすいメッシュ結合の スクリプトを書き換えれば良いです。メッシュ結合はあまり処理が早いとはいえないので ゲームに実装する場合はプリプロセッサでメッシュにかためてプレファブ化するかasset化するかして 各メッシュのトライアングル数だけテーブルでシェーダに送れるようにします。

コードを読めばわかりますがトライアングルのデータに合わせて直接メッシュの頂点データを埋め込んでいます。 頂点データは重複しますが余分な計算が入らないので、スタティックメッシュなら多少データ量が増えた場合でも速度面で恩恵があると思います。頂点アニメーションを考慮するならば、頂点データを頂点へのインデックスIDで間接的に指定するほうが、一手間かかりますがアニメーション部分で演算量は減ります。

今回のサンプルコードで構造体の頂点カラーは未使用なのですが、頂点カラー部分に頂点へのインデックスIDを入れられますので インスタンスメッシュに更にサブメッシュを制御したい場合。 例えばキャラクターのメッシュに装備がついている場合や背景にオプション物がある場合などに表示非表示の制御 あるいは部分的に動きを与えるという際にはフラグとして活用することができますので予約として記述してあります。 頂点カラーでなくてもUV2など余っているところに自由にセットしてももちろんかまいません。

全体が65536ポリゴン以下で収まってしまう場合はバッチ結合とどうように、原点で複数メッシュモデルをマージして なんらかの方法(たとえば頂点カラー)でIDを指定して表示をコントロールすれば、同様に見かけ上の複数モデルの同時表示は可能です。 パーティクルと組み合わせる場合はこうした方法でも良いかもしれません。

最近はテクスチャ配列を使用した頂点アニメーションがクローズアップ されていますがわりと昔からある手法で確か2009年のGPUGEMsのインスタンシング記事には登場していました。 以前テクスチャ配列の記事を書いたときは、そこら辺につなげようとしていたのですが、まだUnityのインスタンシング周りが未完成だったため断念した経緯がありました ちょっと手を出すのが早すぎましたね 大分時間が経ってから記事のアクセスが伸び始めましたし。 テクスチャ配列のアニメーションはシンプルなため処理は軽いですが、頂点数が増加するとテクスチャサイズが肥大化するので、いまのところあまり大きなサイズのメッシュには適用できないという弱点もあります。65536頂点なら256*256テクスチャがアニメーションフレーム数分 ノーマルなどの要素もベイクするとさらにその数倍のテクスチャメモリが必要となります。キーフレームを削減するうまい方法ができれば解決できそうですが。 ボーンマトリクスだけベイクしてスキンウェイトシェーダを自前で書く skinweight対応したシェーダは公開されていますかね。あるいはローポリゴンのプロキシからハイメッシュデータに移し替えるような方法であればメモリの少ない低スペックハードにも恩恵がありそうです。 もっともロースペックマシンでそこまで無理する必要があるのかは、わかりません。

時間が許せばその辺も解説するかもしれませんが

 

■リファレンスリンク

 

 

■後記

更新間隔がだいぶ空いてしまっているので、更新のついでに感想を

Unityの記事は何故かゲームメーカに限らずメーカーさんのアクセスが多いのですが、Unityの記事が比較的初心者ユーザが読むことを前提に書かれているため コードを読めばデータの流れから意味が理解できるが、数式が苦手。というゲーム開発者さんが理解しやすく Unityエンジン以外でも参考になると そういう理由だと聞きました。 なんだか責任重大ですね 

数学苦手なプログラマさんとか意外と多いんですけどね(笑

最近は特許関係でいろいろ話もありました アレとかアレなど。 関連した話をしますと 海外では新し目の技術にはなるべく特許を取らないようにして 業界の発展を促そうという思想があるようで、ネット検索すると例えばシェーダーやAIや物理演算やらはソースがほとんどオープンになっていたりするのがわかるかと思います。 そこで特許の代わりになるものが、サイト記事やコードの中に貼られているリファレンスリンクというもので、製作時に参考にしたもの PDFやスライド。サイト、ブログ記事。書籍など できる限り記載することで学習効果をお互いに高めていきましょうという狙いがあるそうです。 国内の現状を見ればお互い権利主張した結果 開発は全体にブレーキがかかって衰退ムードになっていったというのがわかるのでは。 もちろん海外でもすべての新しめのコードやアイデアに特許がないわけではないので侵害してしまう場合もあります 例えば個人ライセンスを記載ミスでオープンにしたりといったような事故も起きることがあるそうなので、問題が起きないため 使用する側も権利侵害に抵触しないか調べる手間が省けるメリットもあるので、なるべくリファレンスの記載をするように心がけるのはマナーとして正しいような気がします。 罰則があるわけではないのであくまでマナーの範疇ですが。 ほとんどリファレンスリンクがないサイトもあるので判断に迷うこともあるかもしれませんが、初心者率の高めなUnityユーザーは後から続く開発者のためにも迷ったらとりあえずリファレンスリンクは貼ってくれるとうれしいかな と思います。

と言うことで今回は、ここまでです。 毎度長文で申し訳ないですが読んでいただいて有難うございました。

※git.gistのHighlighterを前回使用してみたのですが、表示のたびにサイトを呼び出すため重たいそうで、検索したところ評判があまり良くないようです。 今回は見送りましたが次回からシンタックスハイライトをgoogle code prettifyなど JSコードをサイトに埋め込むほうが表示が軽くなるようなので、そちらで対応しようかと考えています。ブログがフリー版の時代はアップロード機能が使用できなかったためうまい方法が見つからずハイライトなしでしたので、見づらいという指摘を受けてましたけど ほんと コード部分見づらくて申し訳ないです。

 

 

先月新しいOS(Win10)に変わったあと記事の更新がうまくいかなくて弱っていましたが、なんとか解決できたようで新規の記事投稿ができました。直接ブログのほうに書くことはできたんですけど量がある場合エディタを通して転送しないとちょっと無理なのでした。

 

今回の記事のネタ元はこちらの動画。関連動画で扱ったプロジェクトファイルを配布されているのですが、この動画のシーンファイルは入っていないんです そこでUnityで実装テストをしてみましたというのが今回の趣旨です。 後ほど作成したシーンファイルをアップロードします時間がなくてまだコードをつめてありませんが  しばらくお待ちください。

一応コード内のコメントが動画中のUE4シェーダノードのコメントに対応させてあります 変数名も合わせてありますので参考にしていただければ理解がし易いかと思います。

 

 

 

ProcTest

続きを読む

↑このページのトップヘ