ひらしょー

平山尚が技術のことを書く場所。Unityが多そう。

Unite Tokyo 2018

Uniteに参加してきたのでメモをしておく。

さては非同期だなオメー!async/await完全に理解しよう

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session53

 

非同期処理をどう書けばいいかがまだよくわかってないので聞いておいた。

 

関数にasyncとつけた上で、中でawaitの後ろにGetAwaiter()を持った

ものを書くと、そこで待ってくれる。

コンパイラはIEnumerableの時同様に関数をバラバラにして、

ローカル変数を保持するためのクラスをでっちあげてくれるわけだ。

 

C#の標準では勝手にスレッドにブン投げて並列化してくれるが、

unityでやる分にはシングルスレッドになる。

それでもTask.Runを使って明に他のスレッドにブン投げて、

そのハンドルをawaitすれば裏に飛ばせる。

受信したjsonを裏でデシリアライズする、というようなことはそれでできる。

 

さて、async/awaitを使うことがおいしいケースは何か?

 

使ったことがないのではっきりとはわからないが、

おそらくは戻り値だろう。

IEnumeratorやIEnumerableを返す関数は、

戻り値を持てない。やむをえず、

 

class RetVal{ T result; }

IEnumerator SomeFunc(RetVal ret){ ... }

 

みたいなことをして引数に戻り値や完了状態を返すか、

最後に返したIEnumeratorから結果を取れるような凝った細工をするか、

という話になる。私は前者の方がシンプルなので好みだ。

 

しかしasyncをつけた関数は普通にモノを返せるので、

そんな必要はない。そして、

IEnumeratorという本来非同期処理のためにあるわけでもないものを

無理矢理非同期処理に使う必要がなくなり、

IEnumeratorを返せばコレクション、asyncがあれば非同期処理、

と見た瞬間に種別がわかりやすくなる。

 

とはいえ、すでにして大量にyieldで書かれた非同期処理があり、

2018に移行するのはもう少し先だと考えると、

焦って使う必要はたぶんない。

 

なお、Task.Runで実行するとプロファイラでは見えない。

Profiler.BeginThreadProfilingを呼ぶTaskScheduler

自作すれば見えるようになるそうだ。

 

そのうち標準で入るようになるだろうし、

自分でやっても大した手間ではあるまい。

 

 スクリプトによるTimelineがっつり拡張入門

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session56

 

そもそもTimelineという機能を知らないのだが、

Animationの置き換えだろうと推測する。

カーブ定義して出力するパラメータを決めて再生制御する、

という点では同じだが、

Animationには使いにくい点が多々ある。

いちいちAnimatorを用意しないといけないあたり、

UIを動かすために使うような用途には甚だ向かない。

APIを見ていないので確たることは言えないが、

Timelineならそういう用途にもおそらく使えるだろう。

エディタ側のUIの設定に関しても、再生制御にしても、

いじれる余地が大きそうだ。

 

さて、現状私が日常目にするUIその他の動きをつける方法は5つある。

- Animationの利用

- DOTweenの利用

- Updateベタ書き

- AfterEffectsからエクスポートしてスクリプト

- Spine

 

昔はAnimationを使って作ることを模索した時期もあったが、

最近はめっきりやらなくなった。GameObjectとのバインドが

パス文字列なので、一つGameObjectをはさんだだけで全部切れてしまう。

あとassetが増えるのが邪魔くさい。結局なんだかんだ言って、

何か作ればクラスを定義することになるわけで、

Animationだけ作って差せばいいというわけには行かない。

調整もスクリプトだけで完結しない。

そもそも私はそもそも細かくキーをいじって

アニメを調整するようなことはなく、

Animationの利点をあまり得られないのである。

もし細かく調整するならAnimationでやる方が

早く反復できるので、その他の方法より良いだろうと言える。

しかしそれはアーティストがやる方がいいと私は思っている。

 

次はDOTWeenで、これならスクリプトで完結するし、

簡単なものならすぐ書ける。

しかし、簡単なものでないとコードが意味不明に複雑化するし、

多量に発生するものに使うと性能的にヤバい。

画面に同時に10個とか出て、連打するとその度に消えたり出たりする、

というようなものの制御にこれを使うのは悪夢だ。

アニメーションの定義と再生インスタンスが割れてないので、

再生する度にアニメーションの定義コードとメモリ確保が走ることになる。

また、関数が呼ばれる度にSequenceのインスタンスを作るような場合、

前に起動したものを殺し忘れるとひどいことになる。

 

次がベタ書きで、これはUpdateの度にdeltaTimeを使って

手動で動かす方法だ。「0.2秒かけて現れる、消える」

みたいな単純なものならこれで書ける。

ただし、時刻や状態を保持するメンバ変数を用意して

管理する手間がかかり、やるアニメの複雑度が上がると

一瞬にして手間とコード規模がふくれ上がる。

ただし、ガベコレは一切発生させないで書けるし、

Updateでなく自作の更新関数でやればオーバーヘッドもない。

私は結構これもやる。

 

そしてAfterEffectsから吐いたものを自動変換して作ったコードで、

コードなのでできたものは上の「ベタ書き」に近い。

ただしカーブを表現するデータを多量に初期化したり、

カーブとgameObjectをバインドしたりするオーバーヘッドがあるので、

ベタ書きほどは速くないし、初期化ではメモリを食う。

そして、生成されたコードはもはや人が読めるものではなく、

コードなのでAssetBundleにも入れられない。

しかし、AfterEffectsで作ったもので、しかも垂れ流しで良いものであれば、

どんなに複雑なものでも同じ手間で再現できる。

そしてアニメーション定義と再生インスタンスが別なので、

同じアニメを多量に再生する場合の負荷やメモリは軽く済む。

ただし、対応していない機能を使われているとそこだけ手書き対応になって

手間が跳ね上がる。現状マスクやトライトーンは対応しておらず、

とりわけシェイプを使ってマスクをやられるとえらい手間がかかる。

まあそれはAnimationを使っても同じだろうが。

 

最後がSpineだが、これはあんまりUIには使ってない。

再生ランタイムがMeshRendererとして描画するので、

UGUIの中に挟みこめないからだ。

しかし完全にデータ化されていて、アセットバンドルに入れられるので、

こっちを使う手もある。あるいは再生ランタイムを自作したり、

いじったりしてUGUI化することもできるだろう。

 

さて、Timelineの話だった。

これをどこで使いたいかと言えば、当然AfterEffects再現だろう。

現状コードで吐いているものを、Timelineのアセットとプレハブにすれば

AssetBundleにも入れられるし、C++の実装によって高速化することも

期待できるかもしれない。

ただし、

「このノードの下に別のプレハブ差す」

「AE上では画像だが、実際はTextにする」

といった改変は日常茶飯事で、Timelineまで持っていけるケースが

どれくらいあるかはやってみないとわからない。

 

なお、現状私がいる場所ではデザイナーがUnityで作業する、

ということがほぼない。もしそれがあるなら、

最初からTimelineで作られるはずで、

そうなれば話は全く変わってくる。

 

60fpsのその先へ!スマホの物量限界に挑んだSTG「アカとブルー」の開発設計

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session63

 

2000個弾が出る縦シューをどうやってUnityで作るか、という話。

C#の最適化の話はよくわかる。

Normalizeの中でmagnitude呼んでるとか許し難いだろう。

しかし幸いにして私が作っているものは

そういう所を気にしてもほとんど影響がないので、

Vector2やVector3をそのまま使っている。申し訳程度にrefをつける程度だ。

[,]を使うか[ ][ ]を使うか、structでforeachするコスト、

そういったものはほとんど問題にならない。

他にいくらでも重い処理がある。

 

ただし、デバグ描画ライブラリでは千文字以上画面に出たりする関係で、

描画負荷自体は弾が2000個出るのとさほど変わらない状態になるので、

デバグ描画ライブラリの中は結構気を使っている。

例えばVector2の関数は使わない。ベタ書きだ。

refがつけられる時はつけるし、不要にclassを使うこともない。

モノをたくさん出すゲームならまた違うのだろうが、

今はそういう状態だ。

 

とはいえ、スパイクについては考えさせられた。

GCに関してはやれる範囲でとしか言いようがないが、

シェーダコンパイルとテクスチャのVRAM転送に関しては

確かに手を打っておいても良い気がする。

OpenGLでは実際に画面に絵が出ない限りテクスチャがVRAMに

送られず、初めて絵が出るフレームにスパイクが出る、

というのは知っていたが、まさか2018年になってそんな話を聞くとは

思わなかった。今起こっていることが

Unityのせいなのか下のGLのせいなのかは知らないが、

確実な方法が「一回描画すること」しかないことに違いはない。

各アトラスについて、それを貼ったポリゴンを何かしら

描画したらいいんだろう。

しかし、動的にロードされるエフェクトの類では打てる手はなさそうだ。

全種類前もってメモリに乗せるわけにも行かない時もある。

 

そういえば、移動経路の曲線データを事前に分割した直線データに

変換していたのは面白かった。

おおまかに言って、時代が進むほど、計算と転送は計算の方が安くなる。

昔は事前計算して読み込んでいた方が速かったものが、

その場で計算する方が速くなる、というようなことだ。

なにせ2018年なので、スマホはストレージやメモリ

からの転送の方が計算よりも高くつく公算が大きいハードだろうと

勝手に思っていた。それだけに、ベジェの計算程度でも事前計算の方が速い、

というのはちょっとショックだ。そんなにCPU遅いのか。

 

ScriptableObjectの利用について。

会話シーンの類はUnity上に編集環境作って

ScriptableObjectにしてるらしい。

企画にjsonやらxmlやらエクセルやらを作らせて、

それを何かに変換してプログラムにロード、

という作りだと、どうにもイテレーションが遅くなる。

Unity上でやってもらってその場で保存してもらうのは自然だろう。

しかし、企画やアートにとってUnityが手に馴染む良いツールになるのか?

というのは未だに確信が持てない。

エクセル編集でも実行したまま再ロードができる仕組みがあれば、

それはそれでアリだ。もちろん実行中の再ロードができなかったり、

サーバの再起動が必要だったりするフローは論外だが。

 

P-MAPというものを使うと、加算と通常合成が交互に来ても

1パスで書けるらしい。どうもテクスチャを事前に加工してるらしい。

同じシェーダと同じブレンドモードで行けるということで、

一体何をしているのだろうか。

 

通常合成は、D' = S*Sa + D(1-Sa)

加算はD' = S*Sa + D

だ。

ブレンドモードがSa, 1-Saであれば前者、Sa, 1であれば後者になる。

これを同じにしたいと。ここでSにSSa=S*Saを入れておき、

通常合成はSaをそのままにし、加算ではSaを0にし、

ブレンドモードを1, 1-Saにすれば、

加算合成も加算もD' = SSa*1 + D(1-Sa)

となって同じブレンドモードで扱える。

シェーダ内で分岐して吐くアルファを0にするかSaにするかを決めてもいいが、

それではシェーダに分岐が入って重くなるしマテリアルが割れるから意味がない。

その情報を入れるなら頂点かテクスチャかだ。

アトラス化も兼ねてテクスチャでやるアプローチの方がおそらくは普通だろう。

しかしテクスチャに触りたくないケースでは頂点という可能性も残る。

おおよそ話はわかった。

Unityが透明部分に勝手な細工をしないようにAlphaIsTransparencyは

切った方がいいだろう。

 

 運営中コンテンツにおける大型アップデート成功のための考え方とUnity最適化手法 

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session66

 

テクスチャに影描きこんで照明計算なしで出したゲームを、

後から照明計算を足して豪華な絵にしよう、という話。

 

解像度を上げて、フルスクリーンアンチエイリアス(MSAA)をかけ、ポスプロを足し、

テクスチャに含まれていない成分である環境光と鏡面反射を足している。

影が描いてあるならディフューズ項はいじれない。

とはいえ、場所依存の環境ディフューズの成分は描かれていないだろうから

足しても問題は少ないだろうし、

描いてある影のいくぶんかはアンビエント遮蔽だろうから、

それはそのまま乗算してもそれっぽくはなる。

 

ただ法線が問題だ。元々アウトライン用のウソ法線しか入れていなかったそうで、

本物の法線を入れねばならないのだが、FBXには法線が2つ入らない。

UVのフリをして入れて、インポート時にタンジェントにつっこんだとのこと。

なかなか手間だ。

 

ポスプロはZバッファ生成パスを用意してそれを使ったそうだが、

よくわからない。通常描画で描いたZは使えないのだろうか?

もしそうなら、私だったらZを使うポスプロはあきらめたくなる。

ライトシャフトだろうか。

 

豪華版は影描画も追加。

Unityの標準だとモバイルはハードシャドウになって許容できないので

自作したとのこと。物を見ていないのでわからないが、

普通のデプス影でソフトにするには結構な計算量がかかる。

プロジェクションシャドウと言っていたが、

地形だけに落とす奴だろうか?

 

出してしまったゲームの画質を後から上げる、

ということはスマホの場合には結構あるだろうし、

そもそも出した当初から機械の性能には天地の開きがあり、

機械によって使う技法を切り換えるのはありうることだ。

良い機械を持っている人にはより良い体験を、

というのは作り手の精神として共感できる。

ただ、それを割に合う手間で、というのがなかなかに厄介なのだ。

 

まず、解像度とフレームレートに関してはほぼタダでできる。

良い機械では解像度が上がり、フレームレートが上がる。

これは自然にできる。

性能の割に解像度が低い端末であれば、

例えば2倍解像度で描画して縮めたっていい。

単純に2倍重いが、MSAAなどより遥かに綺麗だ。

MSAAは機械によって負荷がどうなるかが読めないところがあるし、

綺麗になるのはポリゴンエッジだけなので、

安く出来るチップでない限りは割に合わない印象がある。

そのチップがどんな特性のチップかがわからない状態でMSAAは

あまりやりたくない気がするが、

多数の機械で試しているのであれば問題ないだろうし、

なにせスイッチを入れるだけで有効化できるので手間では圧倒的に有利だ。

ただ、centroidサンプリングを選択できないと、

おかしなテクセルを拾って絵がおかしくなる。

実際そうなっていたらしく、テクスチャのアトラス境界を広げたそうだ。

モバイル向けのUnityではcentroidサンプリングはできないのだろう。

ますますMSAAを使いたくなくなる。

コンピュートシェーダが使えるマシンでだけそれを使ってAAをかける、

というのではダメだろうか。

 

元の状態でスペキュラがなかったのであれば、

人間は物の形や動きをスペキュラで把握する傾向があり、

スペキュラを足すのは効果が大きい。

しかし、スペキュラをまともに足すにはマテリアル設定が必要で、

何をどう考えても手間が増す。

元のモデルがマテリアル境界で分割されているとは思えないわけで、

粗さや反射率をテクスチャにつっこむことになるのだろう。

これも結構な手間だ。

そして、スペキュラは法線マップとセットでないと効果が薄い。

頂点が少ないとディティール感が出ない。

だが法線マップを貼るにはタンジェントが必要だ。

さらに手間が増す。これは頭が痛いな。やりたくない。

 

テクスチャはETC2にしたらしい。

どうせ低性能機種で読めないならASTCでもいいんじゃないか?

と思うが、単に「高級機種だけ綺麗にしたい」

というわけではなく、より多くのマシンで改善したかったのだろう。

ETC1に比してマッハバンドが改善しているそうだ。

どうも圧縮のQuality設定を上げればETC1圧縮でもマッハバンドは

消えるようなのだが、べらぼうに圧縮が遅くなって

パイプラインに悪影響があるので、ETC2だとそこそこのQualityでも

マッハバンドが消える、ということを重く見たとのこと。

更新頻度と物量次第だろうとは思うが、

ローカルではQuality0でまずインポートし、

土日に自動でQuality0のものを100に変えてインポートしなおせば

我慢できなくもない気はする。

キャッシュサーバも助けになる。 

それで済むならETC2のためにUnityのバージョンを上げる必要もなかっただろう。

おそらくはそれで済まない理由があったのだろうと思う。

 

では、そのクラスの大型更新をどうやって確実に遂行するか。

まずチームは割り、運営チームの邪魔をしないことを最優先する。

完全に作業が終わるまでは運営側のdevelopには一切入れない。

更新用のdevelopにちょくちょく運営のdevelopをマージしつつ開発を進める。

#if で古いUnityと新しいUnityを分岐させる時はシェーダは注意せよとのこと。

自動でシェーダを新しいUnity用に描き換える機能があるらしく、

それが#ifを無視するそうだ。切っておかねばならない。

 

さらに、AssetBundleにシェーダが入ると互換性がない。

古いUnity用のシェーダが入っていると新しい方で読めず、その逆もある。

ABにシェーダが入っているか調べるツールを書いたそうだ。

これは他人事ではない。

シェーダが足りなくて紫になる事故はたまに起こる。

 

ABビルドは並列ビルドしてるらしい。

ビルド済みのManifestがあればビルドしないので、

Manifestをいい具合にコピーしておいてビルドを抑制すると。

manifestがなければ、それに対応するABがビルドできると。

後から別の人に聞いた話では、

実際やってみるといろいろ罠があるらしく簡単ではないそうだが、

しかしやってできないほど難しいことではないだろう。

ABのビルド時間が問題になるなら試してみても良さそうだ。

 

 

『CARAVAN STORIES』のアセットバンドル事例

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session72

 

正直衝撃の連続だったのだが、それは私の経験が浅いからだろうか。

 

まず「アプリが2GB越えしてからAB化した」というのにド肝を抜かれた。

ビルドは3時間だそうだ。

AB化にはビルド時間の分散と短縮という意味もある。

ビルドの度に全アセットの変換を行うよりは、

こまめにAB化して、その後は触らずに済む方が合計時間は短くて済む。

 

一体日々のテストプレイはどうしていたのだろうか。

2GBのapkを皆に配っていたのだろうか。

実機でのテストは一体どれくらいしていたのだろうか。

実機でないと感じがわからないことは多々あるだろう。

その度に3時間待たされたのだろうか。

どのようなフローで開発しているのか正直想像がつかない。

 

また、同期ロードを非同期ロードに変える工数を嫌った、

というような話があったが、

正直私の感覚だと「同期でファイルIOをやって許されるのはデバグ時だけ」

なので、それも衝撃だった。

最初から全てが非同期であれば何ら手間などかからないし、

いずれアセットバンドル化することは明らかなのだから、

Resourcesから読むかABから読むかは最初から抽象化しておけば良い

ように思える。ベータに突入してから、

極力影響が及ばないようにその作業をする、

というのは一体何が起こっているのだろうか。

 

しかし、私はMMOなんて作ったことはないし、

スマホで物を売ったこともない。

おそらくは、私に見えていない何かがあって、

そこに妥当性があるのだろうと思う。

 

それにしても、先にResourcesを見に行って、

なかったらABを読む、というのはレイテンシが心配だ。

ファイルの存在非存在だけでもシステムコールを叩くわけで、

多少はかかりそうに思える。

 

データのunload2秒待ってからにしてるらしい。

次に同じものを使う時に再度ロードのレイテンシがかかるのを

防ぐためらしいが、それなら適当なキャッシュシステムを

用意する方が確実な気はする。

例えば50MB分キャッシュすると決めておいて、リストにつなぎ、

使った時にリストの先頭に動かす。容量があふれたら、

末尾から順に削る。

こんな簡単な処理でも、即座に再利用した場合にはロードが走らなくなる。

しかしそれが面倒だったということだろうか。

 

ABにしたらアニメーションのロードが速くなった、

という話は意味がわからなかった。

Resourcesから31個のAnimationClipを読むのに6秒かかっていたが、

それが一瞬になったという。6秒もかかること自体ありえないと思うのだが、

テキストとかバイナリとか言っていた気がする。

まさか、Animationの保存形式にテキストとバイナリを選択でき、

テキストにしていたが、ABにビルドする際にはテキストの選択肢がないので

バイナリになって高速化した、ということだろうか。

知識がなくて意味が取れなかったのでこのあたりの話はよくわからない。

 

最終的にアプリのビルド時間は3時間から5分になったという。

アプリサイズは45MBだそうだ。5分はうらやましい。

アプリ埋め込みのリソースを減らしてABに叩き出せば速くなるのだろう。

専用スクリプトが不要なシーンがあるならば、シーンごとABに

叩き出すこともできるのだろうし、それができれば拡張の点から有利なのだが、

現状そういう作り方をできる目処は立っていない。

いずれ考えたいところだ。

 

ABは4600ファイル。元のアセットは10万個。

4並列でダウンロードする。4並列もやれば、

それくらいのファイル数でもオーバーヘッドが気にならないのだろう。

であるならば更新時の差分を小さくする意味でも、

あまりまとめすぎない方が良いのかもしれない。

ただ、ABのバージョンチェックにはファイル数に比例した時間がかかる

印象があるのだが、そのあたりはどうだったのだろうか。

 

一つ気になる話があった。

音が鳴らないことがあり、UnloadUnusedAssetsで再生中のが破棄された

のではないか?とのこと。

UnloadUnusedAssets後再生が止まったら再度再生して回避しているらしい。

音が鳴らない、という不具合が出てきたらこのことを思い出そうと思う。

 

カスタムシェーダーでモバイルでも最先端グラフィックスな格闘ゲームを!

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session72

 

大手で社内ライブラリを作っていた人がUnityの台頭で

チームを渡り歩いて最適化をやる仕事をしている、

ということには何となく時代を感じる。

私は下層を作ることをやめてゲームの上層を作る側に転じたが、

この講演の人は過去の経験をそのままUnityで活かしていて、

いろいろな生き方があるなと思う。

 

グラフィクス系の講演であるにも関わらず

一度も絵が出てこなかったことに度肝を抜かれた。

論文への参照があるあたりもUniteの他の講演と違う臭いを感じる。

このような言い方が良いかはわからないが、

「あ、オレも以前あそこにいたんだったな」的な郷愁がこみ上げた。

 

内容としては、

Unityに任せると遅いので昔PS3あたりでやっていたことをUnityでまたやった、

という話と言って良いかと思う。

 

影を描くためにカメラを用意すると諸々遅いので、

メインカメラのスクリプトでコマンドバッファへの描画を発行して影を描く。

標準シェーダの影は遅いので昔ながらのユニフォームシャドウマップを自作。

行列は自前生成するからカメラは不要。

カメラのカリング処理などが全部不要で速い。

 

テクスチャの焼きはPlatformをモバイルに設定するとLDRで焼かれて

話にならないので、Standaloneにして焼いて、

出てきたテクスチャをReinhardでLDR化して吐き出し。使用時に逆変換。

ライトマップはLightmapSettingsから抜ける。RT用意してBlitしてReadPixels。

 

デコードは lightmap / (1h - min(0.9h, lightmap))

最大10で切って無限に飛ぶのを抑制する。

モバイルでもdepth shadowmapはハードウェア支援があるので、

影はdepth shadow。

 

影描画は、影を受けるオブジェクトをリスト化しておいて、

MaterialPropertyBlockで影テクスチャと影フェッチ行列をつっこむ。

こうすればマテリアルを生成せずに済む。

これらの処理を行うのはメインカメラのCamera.OnPreRender。

 

透視変換行列はGL.GetGPUProjectionMatrixで取れるので、

フェッチの際にはこれを使う。Vが上プラスか下プラスかは下層のAPI依存なので

これが必要。

 

照明計算はリニア空間HDR。forwardでMRTなし。

テクスチャはsRGBなので、フェッチ後リニアに変換。

RTへの描き込み前にトーンマッピング

ガンマ変換してsRGBにする。

HDRバッファがあればHDRかつリニアのまま書き込んで、

描画終了後にポスプロでトーンマッピングとガンマ変換を

やる所だが、HDRバッファがないので

それぞれのモデルのレンダリングでバラバラでやらねばならない。

 

トーンマッピング時のEV値はアーティストが設定。

Filmic Tonemapping近似を使用(Hejl 2010)。

 

BRDFの詳細は語られなかったが、

「物理ベース」と言っていたので、おそらくはUEの計算モデルだろう。

私がやるとしても似たようなことをやるのだろうな、と思う講演だったが、

もしそういうプロジェクトがあってもやるのは私ではあるまいな、とも思う。

シェーダをやりたい人は他にいくらでもいるだろう。

 

それにしても会場にいた人は皆BRDFと言われてそれがなんだかわかったのだろうか。

普段Unityを使っていればまず出てこない単語だと思うのだが。

 

Unityの医療と教育への応用 ~ちょっと人を助けてみませんか?~

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session82

 

脳神経外科の現役バリバリの外科医が、

Unityを使って可視化やシミュレーションを行って、

日々の手術に役立てている、という話。

 

CTのビューアは私も書こうとしたことがある。JSでdicom(医療画像フォーマット)

の解釈をして、webGLにつっこんで描画まではできた。

しかし今の時代それはUnityの方が良いのだろう。

ネイティブになるので速度も出るし、AppStoreその他で配布もできるし、

物理シミュレーションなどもついてくる。

腫瘍が血管を圧迫して血管が本来と違う位置にある、

ということをシミュレートするのにUnityの標準の物理を使っていたりして、

専用に用意しなくても実用になるものが作れるのかと驚いた。

 

医療用のソフトウェアの市場規模は1400億と小さいが、

年率15%で成長しているそうだ。

以前は診断に使われるソフトウェアには厳しい基準があり、

なかなか認可されなくなったが、そのあたりで規制緩和があって

認可されやすくなったとのこと。

確かにフリーで配布されていたCTのビューアも「診断には使えません」

とただし書きがついていた。もしかしたら今は不要になっているのかもしれない。

 

全体的に、患者の固有データをつっこんでシミュレーションしたり、

治療戦略を立てる助けになるようなソフトが足りていないそうだ。

患者個人の脳を立体化したり、変形を加えたりできないと、

治療戦略を立てる役には立たないという。

 

細かい話はいろいろあったが、医療分野を知らない会社との

コラボが良い成果を上げているという話は印象的だった。

それはつまり、プログラマとして優秀な人間が医療分野になかなか行かない、

ということでもあるのだろう。もしかしたらそこにチャンスがあるのかもしれない。

例えば、分析の待ち時間が長くて困る、というような話は、

アルゴリズムと望む結果がある程度わかっていれば、

高速化を得意とする技術者が当たった方が良い結果が出るかもしれない。

患者のCTを撮って3日後に手術、という時に24時間かかるようでは使い物に

ならないわけだ。

優秀なプログラマgoogleやらゲーム屋やらにたくさんいて金を稼いでいるわけで、

医療や学問の世界でプログラマをやろうとは思わないのかもしれない。

 

現状特に時間がかかるのはセグメンテーション処理、

つまり、CTやMRIから立体化する際に、これが脳で、これが骨、これが皮膚、

のように領域分けをする処理だ。そもそも正しく領域分けをすること自体が

困難なのだが、現状のアルゴリズムでも時間がかかりすぎるという。

機械学習を応用して精度を上げた話なども出てきた。

普段実際に頭を開けて手術している医者からこういう話が出てくるというのは

本当にすごい。一緒に何かできる機会があったら面白いのだが。

身内に二人も癌患者が出たこともあって、医療にはかなり関心もあるし。

 

そういえば、一つ現状の用途で役に立っていることとして、

どのクリップを使うと丁度いいかを推測するのに使っている、というのは面白かった。

血管を止めるクリップは使い捨てで数万円する。使ってみてダメだといきなり

万単位でドブに捨てることになるので、前もってシミュレーションで

種類を限定しておきたいそうだ。実際に効果があるのだという。

 

Unityにおける疎結合設計 ~UIへの適用事例から学ぶ、テクニックとメリット~

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session84

 

疎結合、なんて言葉を聞くと、コードの設計のことかと思うが、

全然違った。いや、全然違う、と言うと正確ではないのだが、

おそらくは「全然違う」と思った人は多かろうと思う。

しかし私にとっては、そんな「疎結合」の話をされるよりずっと面白かった。

というかこのドイツの人、話がおもろい。

 

まず結合という言葉を「片方が変更されるともう片方にも変更が必要になること」

と定義し、疎結合はそのような結合がないこと、としている。

「疎」は「まばら」であって「無」ではないはずなのだが、話としてはわかりやすい。

例えばデザイナーが素材をいじった時に、プログラマに作業が発生すれば

疎結合ではない。密結合だ。

 

どれくらい疎であるべきか、というのは主にプロジェクトの規模によって変わる。

人が多く、期間が長く、仕様が大きいほど、疎にしないと辛くなる。

なるほどそうだ。

 

そして、「何が疎か」ということに関してはレベルがあり、

ワークフローやデータ、そしてコードのレベルがある。

ワークフローにおいて関係が疎であれば、それはデータにおいても

疎であることを要求し、それはさらにコードにおいても疎であることを要求する。

 

デザイナーが直してもプログラマに仕事が発生しないためには、

デザイナーが直したデータをプログラマが受け取る必要がない状態でなくてはならず、

それはそのようなコードによって実現される。

コードにアニメ書いてあるとか、Unityの参照に画像をプログラマが差してるとか、

そういう状態では疎にはなりえない。

 

言われてみれば当たり前なのだが、そのようなことを「疎結合

なる言葉で説明することには意義がある。別に他の言葉でもいいのだが、

大きな概念に名前がつくと応用範囲が広い。

 

ではこの概念上の道具を使ってうちらはどうすべきか?

 

うちのフローは、デザイナーが素材を作ってプログラマが実装する、

この人が言う「ダメなフロー」だ。画像が変わればプログラマ

受け取って差し換えねばならないし、位置が変われば

プログラマがシーン中で置き直さねばならない。

また、デザイナーが仕様を書いて渡すコスト、

プログラマが仕様を受け取って理解するコスト、

といった無駄なコストも発生する。

こういった害は、デザイナーが直す回数が多いほど大きくなる。

直すコストが大きければ直さなくなるので、試行錯誤の回数は減り、

普通に考えて品質に悪い影響が出る。

 

つまり、理屈の上では、デザイナーがUnity上で組んでしまうのが最も良い。

配置も動きもUnity上でデザイナーがつける。

そして、ゲームロジック側とのやりとりは何らかのインターフェイスを設けて

行う。scriptableオブジェクト一個用意して、

そこにsetがあるプロパティを置けば、それはアニメからロジックへの送信になり、

getがあるプロパティを置けば、それはロジックからアニメへの通知になる。

setには「ボタンが押された」のようなイベントや「今のHPゲージの値」

などが入るだろうし、getには「時間が来たからボタンを無効化しろ」

のような制御が入るだろう。いずれもdelegateなりeventなりを置けば、

pullでなくpushにできる。

 

このあたりの作りをこの講演ではメッセージバス、と呼んでいた。

バス、つまり通信チャネル的なものに情報を流し、

相手が誰かもわからないまま送信、送信元が誰かもわからないまま受信する。

こういう作りであれば、UIとゲームロジックを分離でき、

UI単体テストなども可能になる。ゲームを起動しないとテストができないのは

密結合でよろしくない作りだ。

 

実にもっともで、考えさせられることは多い。

 

しかし、それはそれとして、単体テストが不要で、担当者が分かれていない、

というような状況であれば、ゲームエンジンとUIが密結合した

昔の形でも許容できることはあるだろう。

大した回数直さないとか、そもそもデザイナー資源が極端に少なくて

アニメまでつけないとか、アニメをつけるのがうまいプログラマがいるとか、

そういう条件があれば現状のフローも正当化される。

コード的にも切り離せば抽象化層を作る手間はあるし、その分だけ長くなる。

決め打ちであれば不要だった検査類も、分離するなら書いた方がいいかもしれない。

私はここのところ疎結合だったものを密結合に直すという

逆行したことをやっているが、

一旦くっつけ終わったらまた分離を再度考えてもいい気がしてきた。

 

とりあえずはAfterEffectsからTimelineのassetに変換するものは作りたい。

それがあればAEを多少いじってもプログラマが一切知らないまま

ABが更新されて勝手に出る、という状態にできる。

デザイナーが手持ちの実機で確認できるところまで

整備しておかねばならない。