2020年2月23日日曜日

チマチマ


**********
 株式会社インフィニットループのマスコットキャラクターのモデルデータ「あいえるたん3Dモデル」を使用しています。
 配布元:https://www.infiniteloop.co.jp/special/iltan/
**********

 チマチマ最適化して、ちょっとだけ高速化。



 あ、輪郭入れるの忘れてた……
 輪郭入れて、昨日と比べて15msくらい早くなった。

 ラスタライズ以降のデータの持ち方を変えたりしたけど、変更点多くて大変だった割に、そこはあんまり効いてないかな。
 バッファのクリアを、あらかじめクリアしたバッファを持っておくことで、3倍くらい高速化。メモリ消費量? x64なめんな!!たかがXGA程度!!!

 テクスチャのIDはクリアが-1なのと、デプスバッファのクリアがfloat.MaxValueなので、これをループで処理すると結構時間がかかる。初期化済みをArray.Copyで移動すれば、メモリ間コピーをやるだけなので早い。都度値を入れるとCPU処理だけど、コピーならメモリコントローラで爆速で処理できる。
 あるいは、テクスチャ0に1x1pxの白画像とか書いておいて、背景相当はそれを読ませる、とかも有りかも。それならテクスチャ範囲の確認のifを消せるし。背景面積が大きい場合は本来不要なテクセル読み出しがオーバーヘッドになるけど、ifで分岐させるのとどっちが無駄かというと、大差ない気もする。
 深度に関しても、描画最遠位置をゼロ、カメラの位置を1、みたいにして正規化しておけば、ゼロクリアでも問題ない。深度値が1を超えるなら、それはカメラの裏側だから描画しない、とか。ただ、余計に演算が必要なので、トータルでは利点は少なそう。今はカメラ位置をゼロ、奥に向かってプラス、だけど、これはローカル座標からスクリーン座標に変換したときのZ値そのものなので、余分の演算が必要ない。


 ラスタライズで、ピクセルへの書き込みを、関数呼び出しから、直接バッファに書き込むように変更して、3割ほど高速化。割合で言えばそれなりだけど、時間で言えば10msくらい稼げる。
 ポストエフェクトは長くなった。
 フレームバッファ(最終的なレンダリング結果)の一時形式を変更したので、Bitmapへの書き出しが爆速になった。これもCPU逐次処理から、メモリコントローラで扱えるように変更した。

 全体的に早くなったけど、ポストエフェクトでかなり損してるので、トータルではさほど早くなってないのがつらい。


 かなり大部分を書き換えていて、「やっぱ戻そう」って時に困る。バージン管理使ってればそのあたり楽できそうだけど、コミットメッセージ考えるのが結構苦痛なんだよなぁ。
 あとは、ユニットテストを使えばリファクタリングでバグが入り込むことを防げるけど、大本のデータの扱い方を変更したりするときに、テストもガッツリ手を入れないとコンパイルすらできないので、結局テストにバグが入り込む危険性とか、テストの保守の手間とか考えると、デメリットもデカイんだよなぁ。
 アルゴリズム自体を大きく変えたい場合とかは、TDDには向かないはず。レンダリングエンジンだけじゃなく、例えばゲームシステムを色々変えて試したい、みたいな場合も、いちいちテストを書いていると大変そう。
 メインのコードをgitで管理して、エンジンやシステムの変更を試したいときは、テストを除外してクローンして、とりあえず動いたら、それを仕様としてテストを変更して、それに通るようにメインを書き換えていく、みたいな感じかなぁ。


 ポストエフェクトでは、各処理はFuncで流している。基本的には(x, y) => 0のようにダミーが実装されていて、プロセス開始前にフラグに応じてFuncの中身を実装し、Parallel.Forから呼んでいる。
 これは、全ピクセルの総当り時にifでフラグを監視するよりも、最初にdelegeteの中身を固定したほうが早いんじゃないか、と思ってそういう実装にしたんだが、ちょっと確認した限りだと、総当り中にifしてもあんまり関係なさそう。最適化で見えなくなってるだけかもしれないけど。


 ポリゴン類は、グローバル空間に変換したものは、ConcurrentBagに入れている。座標変換自体はシーケンシャルに再帰しているだけだが、ポリゴン管理クラスは複数枚(あいえるたんだと数万枚)のポリゴンをParallelで処理している。座標変換後のラスタライズもParallelで回している。
 コイツの欠点はCapacityを固定できない点。レンダリングが終わった時点でBagのCountは0になるので、ある程度の時間が立つとGCによってメモリが開放されてしまう。ある程度短い頻度で描画を繰り返すのであれば問題ないが、数秒程度の間隔が開くと、Bagの確保である程度の時間(数十ms)食われる。ま、高頻度にリフレッシュしてれば短時間でメモリ確保できるし、低頻度にリフレッシュするときは20ms前後伸びたところで実害はないので、大きな問題ではない。

***

 Fusionでチャチャッと家具を作ってSTLで読み込み。ちょっと凝った形作ろうとすると操作方法がわからなくて困るな。修行不足。。。

 エフェクト無し


 陰影が無いのでシルエットでしか見えない。

 ライティングあり


 椅子は一部にフィレットを作ってあって、それの反射で、なんとなく形が見える部分がある。殆どの部分はフィレットを作ってないので、シルエットのまま(フィレットをSTLで出すとポリ数ヤバいのだ)。

 深度で輪郭


 一部に輪郭線が出るので、形状の把握がしやすくなる。

 法線で輪郭


 山折り・谷折りに縁が出るので、だいぶピシッとした印象になる。法線ベクトルが同じ向きの箇所には影は出ない。机の柱部分に顕著。
 実装の問題上、法線が複雑なところではきれいな輪郭線が書けない。椅子の背もたれ部分に顕著。
 キャラの髪とか、アクセサリー類とか、細かい部分にも輪郭線が出てしまうので、汚い印象になる。
 このあたりは閾値の設定も大きく関わってきそうだが。

 深度と法線で輪郭


 大部分の輪郭線が書かれているので、かなり引き締まった印象。リソース要求マシマシ。

 法線ベクトル(グローバル)


 今までは、クリア時は異常値を設定していたけど、Fogとかの関係でとりあえず上向きのベクトルを初期値にしている。

 深度(スクリーン)


 テクスチャID


 UVマップ


 椅子と机にはテクスチャが貼られていないので、テクスチャIDやUVマップでは表示されない。ただし、当然ながら深度値によって上書きされるので、手前にオブジェクトがあれば「テクスチャなし」で抜けて見える(靴部分、椅子と重なった部分)。

***

 3Dモデルを配置したときに、なぜか角度が反映されなくて結構ハマった(机は出力の向きを間違えたのでレンダーで回転させている)。
 原因は、自作ライブラリのnew Quaternion()がX=Y=Z=0, W=1なのに対して、new System.Numerics.Quaternion()がX=Y=Z=W=0で、これを経由するとすべての回転が無効になるため。
 Quaternion.Identityを使えばX=Y=Z=0, W=1になるので、デフォルトの値はこれを使う必要がある。

 STLはミリメートル単位だけど、あいえるちゃんは身長20mmで出てたので、かなり大きさの違いが出てた。とりあえずObjの親に78.5のスケーリングを設定して対応。いちおう、拡大や回転のパラメータはちゃんと機能してるっぽい。ネストしたときまでは確認してないけど。
 机や椅子の高さからすれば、現実的なサイズ感で見えてる。今度から家具作るときはコレで大きさ確認するようにしようかな……

***

 テクスチャ貼るのどうするかなー。
 メタセコとか使ったら楽なんだろうけど。メタセコでSTLやOBJを出し入れするならStdライセンスが必要っぽい。Stdなら高いライセンスじゃないけど、そんなに使う予定ないからなぁ。
 メタセコは大昔にちょっと遊んでたことがあるけど、v4でUIかなり変わったのでなぁ。まぁ、昔のUIだって使い慣れてたわけじゃないし、あんまり関係ないか。

 自作ソフト? うーん。面倒くさそう…… UVマッピングやるなら、それを確認するための手段が必要なんだよな。それこそ自作エンジンの活躍する場ではあるんだが。それに、ちゃんとしたソフトで作ったマッピングじゃないと、自作エンジンの健全性確認には使えない、という言い訳。
 たぶん最終的にはBlenderなりメタセコなり使うだろうけど、とりあえず試しに何か作ってみるか。

 脱線? 何の話? 俺の前には道もレールもないッ!!
 まぁ、「俺の後ろに道ができる」ってわけでもないんだけどね。道作るの面倒くさいじゃん? 自分が通る分だけ草かき分けて歩くの楽じゃん。笹とか切り倒して道作るの、内燃機関搭載の刈払機でもないと無理だって。。。それに刈払機使うのだって楽じゃねーんだぞ!!

0 件のコメント:

コメントを投稿