こんにちは.@rootxです.
Unityをアセット管理として用いたレイトレプログラムを書いてみましょう.
Unity5でレイトレーシング
はじめに
なぜUnityで行うか?
先日から少しだけUnityを操作しているのですが,最近のゲームエンジンらしく物理ベースマテリアルに対応している上に,いろんなローダやアセットがたくさんあって,自分で書くよりこれでいいじゃん,と思ったところが始まりです.
ベタのレンダラを書いたことのある人はお分かりかと思いますが,最初に絵を出力するまでに,レンダリング以外の処理を書くのがすごく大変です.例えば,ファイルの読み込みや,画像として保存する,テクスチャに対応する,などなどです.ちょっとプレビューしたいと思ったり,それにGUIを付けたいと思っても,なんだかよくわからないことを,たくさんたくさん調べて書く必要があります.
そこで,
本論の目的は,初学者(私も含め)向けにレンダラの本質(と思われるところ)に焦点を
また,高速化や多くのテクニックの必要性を体験してもらうことです.
なお他のアプローチとしては,任意のフレームをobj等に出力して,自作あるいは既存のレンダラでレンダリングする,という方法もあります.
開発環境
os x 10.10
Unity 5.2.0f3
UnityScript (C#)
開発指針
レイキャスト,古典的レイトレ,パストレの流れを追う.
マテリアルはstandard shaderの設定を使う.
テクスチャ対応.
高速化手段はあまり考えない.
MMDモデルのレンダリングくらいまで.
ここから
準備(適当に)
新規プロジェクトを作成.
適当なオブジェクトの配置.
カメラの設定.
スクリプトを作成.
カメラに,作成したスクリプトをアタッチ.
とりあえず,こんな感じにしてみました.
絵を出す(モノクロから)
レイトレーシングの基本的な考え方は,視点(カメラ)からたくさんの視線(レイ)を飛ばして,当たったオブジェクトの位置に基づいていろいろ計算して,色を決定します.
Unityでは便利なAPIがたくさん用意されているのでそれを使って,色の決定の部分を書いていく,という感じで進めます.
レンダリング結果を保持するキャンバスを用意して画面に表示する
レンダリング結果は,テクスチャに描画します.
最初にテクスチャを用意します.今回は,スクリーンサイズと同じサイズのテクスチャをStart()で確保し,レンダリングのためのRendering()を呼びだします,
全画素について処理を行うため,横方向(width)と縦方向(height)の2重ループとなります.
レイトレースを行うには,各ピクセルにおいて,カメラから発するその画素位置を向いたレイを生成し,その結果に応じて色を計算します.
この時点では,まず単純な背景色(黒)をテクスチャへ書き込み,そして画面への表示を行うだけの処理にします.
UnityのColorは不透明度を持っているので,黒の場合は (0.0f, 0.0f, 0.0f, 1.0f)とします.
また,数値で場合は必ずfloat精度として記述してください.うっかり new Color(0.0, 0.0, 0.0 1.0)のように記述すると,コンパイルに失敗してUnityがエラーとなってしまいます.
すべてのピクセルに色を設定したら,テクスチャのApply()で反映することを忘れないようにしてください.
描画した内容を画面に表示するために,OnGUI()コールバックを用意して,テクスチャを画面を覆うような領域に描画します.
https://gist.github.com/cslroot/c66ebadf2ad3651bcab3
この状態で実行すると,真っ黒になると思います.
背景色として指定したbackgroundColorの値をいろいろ変えて,指定した色になることを確認してみましょう.
ここまでできれば,各ピクセルの色をどのように決定するか,という部分に集中して取り組むことができるでしょう.
レイを飛ばして,当たったところを表示する
Unityでは,Physics.Raycast()という大変便利な関数が用意されています.このAPIは,任意の位置(座標)と指定の方向(ベクトル)を指定することで,その半直線の交差情報を取得することができます.
交差情報は,単純に当たった/当たっていないだけではなく,どの物体(シーン内のGameObject)にあたったか,その位置,法線,uv座標なども取得することが可能です.
注意:当たったかどうか,はCollider(コライダ)と呼ばれるコンポーネントで判定されます.Colliderがアタッチされていない要素には当たり判定がないため,Raycast()では無視されてしまいます.
これを使うには,各ピクセルに対しするレイの位置と方向を算出する必要がありますが,Unityにはその計算を自動的に行なってくれるAPIが用意されています:
mainCamera.ScreenPointToRay(Vector3(w, h, 0));
それではまず,当たった場合に白,当たらない場合に黒で表示するように改造してみます.
https://gist.github.com/cslroot/4eb475710a11f87f174e
オブジェクトの色を表示する
物体が表示されましたが,陰影もなく色もない状態です.
そこで,当たった点の情報を利用して物体色を反映させたいと思います,
現時点では2つの物体(CubeとSphere)に材質情報がありませんので,まずはマテリアルを用意します.
Unity上の操作
プロジェクトビューから,新規のマテリアルを作成します(Create > Material).
それぞれの物体に別々のマテリアルを持たせるので2つ作成しておきましょう(MatCube, MatSphere).
それらのマテリアルを選択して,Albedoの色を指定します.とりあえず赤と青にしておきます.
マテリアルを,Hierarchy内のCubeとSphereにそれぞれドラッグ&ドロップしてアタッチします.
ここまでの操作で,Unity上の物体に色が反映されたと思います.
ScriptからAlbedoカラーの取得
色を取得するには,レイと交差した物体のマテリアルを参照する必要があります.
これは,Raycast()のout引数であるRaycastHit構造体から,collider経由で取得します.
https://gist.github.com/cslroot/ce44b752bedce904c6ac
テクスチャマッピングを有効にする
マテリアルが取得できればテクスチャも簡単に習得できます.
テクスチャが設定されている場合は,Material.mainTextureプロパティでアクセスできます.
また,レイと交差した箇所のテクスチャ座標は,RaycastHit.textureCoordに格納されています.
https://gist.github.com/cslroot/ddcff7c39476e19da582
テクスチャを使うときに重要な点が1つあります.
スクリプトからテクスチャの値を取得する場合は,テクスチャの設定で「Read/Write Enble」を変更しなければなりません.これをしない場合,実行時にエラーが発生します.
テクスチャを選択「Texture Type > Advanced」にして「Read/Write Enable」をONにしてください.
今回はこのへんでー.
参考文献
でわでわ.