![見出し画像](https://assets.st-note.com/production/uploads/images/68790384/rectangle_large_type_2_fab0b243b305829e6ef0923ea879e34d.png?width=1200)
Oculus Quest 2+UnityでVR:グリッドルームを作ろう
前章まででVRなカメラを置いて手を使えるようになって、プレイヤー側の最低限なインターフェイスは整いました。で、ここまでテストして思った事。「今自分がどこにいるかわからん!」。何も置いていないSkyboxがあるだけの世界では、スケールを感じるものが何も無いわけです。まぁCubeなりSphereなりを試しに置けば目印にはなるのですが、それじゃちと味気無いと。
そこで今回は世界にグリッドを敷いてみます。グリッドがあれば世界を実寸で測定する事ができ、VRの世界のスケール等を感じ取れますし、デバッグ作業にもきっと役立ちます(^-^)
ワールド絶対座標グリッド
今回目指すのは、ワールドの絶対座標に沿ったグリッドです。ただしXYZではなくて、XZやYZなど平面を指定するとします。理由はXYZ全部を使うとボリュームテクスチャが必要で面倒だから(^-^;。
グリッドはスケール(単位)を指定できるようにします。1mグリッドなのか10mグリッドなのかという事です。また、補助グリッド線も4分割程度入れる事にします。
グリッドテクスチャを作成
まず用意するのはグリッドが描かれたテクスチャです。色々サイズを試したのですが、OQ2の解像度では512×512が程良い感じでした。これ以上小さいサイズだとジャギによるモアレが目立つのとグリッド線がどうしても太くなってしまい見栄えが悪くなります。作成したテクスチャはこんな感じです:
![](https://assets.st-note.com/img/1640827317571-A2p3OT7OKV.png)
512×512で周囲が3ピクセル、補助グリッド線が2ピクセルの幅です。3ピクセルなラインがグリッド単位になります。このテクスチャが床等に敷き詰められるという事です。
このテクスチャをUnityのプロジェクトに追加します。デフォルトで大丈夫ですが、ミップマップが有効、そしてBilinear補間が入っている事が絶対です。これが無いとモアレ&ジャギってかなり気持ち悪くなります(-_-;。またWarpModeはRepeatにしていくらでも敷き詰められるようにします:
![](https://assets.st-note.com/img/1640827714582-SAcUNDp9U4.png)
ワールドグリッドシェーダを作成
続いてグリッド描画用の専用シェーダを書きます。「デフォルトのStandardとUnlitシェーダじゃダメなの?」と思うかもしれませんが、ダメです!今回のグリッドはワールド座標に沿ったグリッドなので、デフォルトシェーダだとグリッドテクスチャを貼ったモデルが動くとグリッドも移動してしまいます。モデルが動いてもグリッドは不変。そういう特殊な描画をするため、専用シェーダが必要になります。
シェーダコードを掲載します:
Shader "MyShader/WorldGrid"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Unit ("Unit", float) = 1.0
_Color("Color", Color) = (0.5, 0.5, 0.5, 1.0)
// グリッド平面指定
[KeywordEnum( XZ, XY, YZ )] _Plane ( "Grid Plane", int ) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 平面ごとにシェーダバリアントを作成
#pragma multi_compile_local _PLANE_XZ _PLANE_XY _PLANE_YZ
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Unit;
float4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// UVはワールド座標ベースで
float4 posW = mul(unity_ObjectToWorld, v.vertex);
#ifdef _PLANE_XZ
o.uv = posW.xz / _Unit;
#elif _PLANE_XY
o.uv = posW.xy / _Unit;
#else
o.uv = posW.yz / _Unit;
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col * _Color;
}
ENDCG
}
}
}
まず、グリッド平面を列挙型で指定できるようにするためKeywordEnumを利用します。コード内のような書き方をする事で、マテリアルのインスペクタに列挙型を指定するプロパティが追加されます:
![](https://assets.st-note.com/img/1640828264050-1CFQfcFfHh.png)
この列挙型は数値として直接使う事も出来ますが、シェーダバリアント(シェーダのバリエーション)の区別となる#pragma multi_compile_localの値としても利用する事が出来ます。プロパティ名が_Planeで、指定の列挙型が例えばXZの場合、バリアント名は「_PLANE_XZ」となります。これはUnityの命名規則で定められていて「<プロパティ名>_<列挙型>」と定義されています。
頂点シェーダでは頂点座標をワールド空間に持っていきます。そして、そのワールド空間の座標値をそのままUVとして利用します。これによりワールド座標に沿ったグリッドになれるわけです。_Unitには1グリッドの単位が入っているので。ワールド座標を_Unitで割ってUVのスケールを単位に合わせます。
フラグメントシェーダは至って普通。グリッドテクスチャを計算したUVでフェッチして、_Colorで色味を調整して終わりです。グリッドの濃度なども工夫すれば変更できますが、今回はシンプルな実装に留めます。
グリッドの部屋を作ってみる
以上を使って壁一面がグリッドになっている部屋を作ってみましょう。Unityのプリミティブを使い床や壁や天井となる平面(Quad)を6枚用意します。これPlaneではなくてQuadで。Planeは辺の長さが1ではないので使いにくいからです:
![](https://assets.st-note.com/img/1640829226088-IPw9MXvi08.png)
Roomノードにぶら下げているのは、Roomのスケールを変更する事で部屋のサイズを自由に決められるからです。なのでRoom以下の部屋は辺々が1mの単位部屋にします:
![](https://assets.st-note.com/img/1640829436603-wx9OSPMt2x.png?width=1200)
上のWorldGridシェーダを適用したマテリアルを、XZ平面、XY平面、YZ平面の3つ分用意します。グリッド単位は1にします。カラーはちょっとグレイにするとジャギ感が軽減し目にも優しくて良いです:
![](https://assets.st-note.com/img/1640829193593-2bsHZvh5NW.png)
各マテリアルを平面の方向が合っているQuadにそれぞれ適用します。これで単位部屋はOK。
最後に1辺が10mの部屋に拡大します。これはRoomのスケールを変えるだけです:
![](https://assets.st-note.com/img/1640829587973-ySQI9iZpZw.png)
シェーダ内で1m単位に敷き詰められるようUVが自動計算されているので、マテリアル側でタイリングスケールを合わせる必要はありません。便利(^-^)
さ、これで準備は整いました。OQ2を被って部屋を見回してみましょうか:
うわ~、頭がバグるww。でもきっちり壁一面にグリッドが描かれていて、太い線を数えるとちゃんと10m四方になっているのがわかりますね。動画は左目カメラの映像なので立体視はできませんが、OQ2を通すとそのくらいの部屋の広さを感じます。
これで空間を測定できるようになりましたので、次は部屋の中をうろついてみましょうか。アナログスティックを前に押すと、自分が今見ている方向(XZ平面上)を移動するサンプルを作ってみます。「VR酔い」を体験できるかもしれません(^-^;
この記事が気に入ったらサポートをしてみませんか?