はじめに
GOROman さん(@GOROman)が、以下の様なつぶやきをされているのを見かけたので、その日のうちにやってみたお話です。
これVRストリーム中継楽勝で出来るんじゃないのかなー。ステッチどうするかだけど。。。とりあえず球体をざっくりUVテクスチャで繋ぐだけでもいいかも。
— GOROman@今日はサンノゼへ (@GOROman) 2015年9月21日
THETA S は 10/23 に発売予定の全天球カメラで、現行機の m15 にはなかった Dual-Fisheye の USB カメラストリーミング(1280x720 15fps)と HDMI カメラストリーミング(1920x1080 30fps)が機能として加わります。これを Unity 上で見れるように、適当な UV 展開した球と、境界をアルファブレンドする適当なシェーダを作ってみました。最終的には、Nora さん(@Stereoarts)が直接 Equirectangular(正距円筒図法)でフラグメントシェーダだけで平面のメッシュに書き出すものを作ってくださったので、THETA S で全天球をしたい、という目的の場合はそちらを使用されたほうが圧倒的にクオリティ高い&便利だと思います(Skybox にも出来る!)。
Theta Shader Pack をリリースしました。Unity 向けの THETA / THETA S の全天周画像をリアルタイムに Equirectanguler に変換するシェーダーと、それらの補助スクリプト一式です。http://t.co/mmrKXr6MH0
— Nora (@Stereoarts) 2015年9月26日
本記事では、私の方法も一応備忘録という形で残しておこうと思って書いてみました。
サンプル
THETA で取れる画の例
THETA m15 の動画は円がオーバーラップするような形でしたが、THETA S からは綺麗に別れた形になります。片方の円でカバーする半球の画角は 180 度よりも若干広いです。
UnityからWebCamTexture
— GOROman@今日はサンノゼへ (@GOROman) 2015年9月21日
THETA S pic.twitter.com/8vjFQuTYxd
作業の際は GOROman さんに WebCamTexture
で撮ったサンプルのテクスチャを送ってもらって調整しました。
良い感じに UV を設定した球を作る
Maya LT で作業してみます。普通に球を作成すると UV はこんな感じに展開されています。
これを適当な平面で UV 展開を行うと以下のようになります。
あとは 半分に切って分割し、適当に動かせば OK です。
これで以下のようになります。
@GOROman こんな感じですかね(調整してないので多分ズレると思いますが...) https://t.co/QT7zIuPzLV pic.twitter.com/ApIaIFklH6
— 凹 (@hecomi) 2015年9月21日
動画も上げました:
実際は境界でアルファブレンドを行いたかったので、球ではなくオーバーラップした半球を 2 個作りました。境界部分の UV は手動で適当に伸ばしてあります。
なお、面はすべて法線を反転させて内側を向くように設定しています。UV の位置や大きさは後でシェーダの方で微調整できるようにするので適当です。
Unity で設定
Maya LT で作成したモデルを Unity でインポートして中心にカメラを置きます。モデルは UV の位置を微調整したりアルファブレンドできるようにシェーダを書きます。向きによって境界が前後することのないよう描画順を制御するために、半球ごとに別のシェーダにしています。
Shader "Theta/Sphere1" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _AlphaBlendTex ("Alpha Blend (RGBA)", 2D) = "white" {} _OffsetU ("Offset U", Range(-0.5, 0.5)) = 0 _OffsetV ("Offset V", Range(-0.5, 0.5)) = 0 _ScaleU ("Scale U", Range(0.8, 1.2)) = 1 _ScaleV ("Scale V", Range(0.8, 1.2)) = 1 _ScaleCenterU ("Scale Center U", Range(0.0, 1.0)) = 0 _ScaleCenterV ("Scale Center V", Range(0.0, 1.0)) = 0 } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Background" } Pass { Name "BASE" Blend SrcAlpha OneMinusSrcAlpha Lighting Off ZWrite Off CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex; uniform sampler2D _AlphaBlendTex; uniform float _OffsetU; uniform float _OffsetV; uniform float _ScaleU; uniform float _ScaleV; uniform float _ScaleCenterU; uniform float _ScaleCenterV; float4 frag(v2f_img i) : COLOR { // 中心位置や大きさを微調整 float2 uvCenter = float2(_ScaleCenterU, _ScaleCenterV); float2 uvOffset = float2(_OffsetU, _OffsetV); float2 uvScale = float2(_ScaleU, _ScaleV); float2 uv = (i.uv - uvCenter) * uvScale + uvCenter + uvOffset; // アルファブレンド用のテクスチャを参照してアルファを調整 float4 tex = tex2D(_MainTex, uv); tex.a *= pow(1.0 - tex2D(_AlphaBlendTex, i.uv).a, 2); return tex; } ENDCG } } }
Shader "Theta/Sphere2" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _AlphaBlendTex ("Alpha Blend (RGBA)", 2D) = "white" {} _OffsetU ("Offset U", Range(-0.5, 0.5)) = 0 _OffsetV ("Offset V", Range(-0.5, 0.5)) = 0 _ScaleU ("Scale U", Range(0.8, 1.2)) = 1 _ScaleV ("Scale V", Range(0.8, 1.2)) = 1 _ScaleCenterU ("Scale Center U", Range(0.0, 1.0)) = 0 _ScaleCenterV ("Scale Center V", Range(0.0, 1.0)) = 0 } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Background+1" } UsePass "Theta/Sphere1/BASE" } }
アルファブレンド用に以下の様な UV に合わせてアルファを調整したテクスチャを作っておきます。私は UV を postscript で吐き出してイラレで読み込んでピッタリ合うように調整しながら作りました(内側の白い円がアルファ = 1、円の周辺部で内側から外側にかけて 1 から 0 に変化、外側は使わないので適当)。
後はパラメタを微調整すれば全天球の出来上がりです。
凹さん(@hecomi )に作ってもらったリアルタイムスティッチ!
— GOROman@今日はサンノゼへ (@GOROman) 2015年9月21日
なかなかいい感じ! pic.twitter.com/wHNofw5gwl
Equirectangular 化
頂点シェーダの変形でやってみました。
Shader "Theta/Equirectangular1" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _AlphaBlendTex ("Alpha Blend (RGBA)", 2D) = "white" {} _OffsetU ("Offset U", Range(-0.5, 0.5)) = 0 _OffsetV ("Offset V", Range(-0.5, 0.5)) = 0 _ScaleU ("Scale U", Range(0.8, 1.2)) = 1 _ScaleV ("Scale V", Range(0.8, 1.2)) = 1 _ScaleCenterU ("Scale Center U", Range(0.0, 1.0)) = 0 _ScaleCenterV ("Scale Center V", Range(0.0, 1.0)) = 0 _Aspect ("Aspect", Float) = 1.777777777 } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Background" } Pass { Name "BASE" Blend SrcAlpha OneMinusSrcAlpha Lighting Off ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #define PI 3.1415925358979 #include "UnityCG.cginc" uniform sampler2D _MainTex; uniform sampler2D _AlphaBlendTex; uniform float _OffsetU; uniform float _OffsetV; uniform float _ScaleU; uniform float _ScaleV; uniform float _ScaleCenterU; uniform float _ScaleCenterV; uniform float _Aspect; struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata_base v) { float4 modelBase = mul(_Object2World, float4(0, 0, 0, 1)); float4 modelVert = mul(_Object2World, v.vertex); float x = modelVert.x; float y = modelVert.y; float z = modelVert.z; float r = sqrt(x*x + y*y + z*z); x /= 2 * r; y /= 2 * r; z /= 2 * r; float latitude = atan2(0.5, -y); float longitude = atan2(x, z); float ex = longitude / (2 * PI); float ey = (latitude - PI / 2) / PI * 2; float ez = 0; ex *= _Aspect; modelVert = float4(float3(ex, ey, ez) * 2 * r, 1); v2f o; o.position = mul(UNITY_MATRIX_VP, modelVert); o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord); return o; } float4 frag(v2f i) : COLOR { float2 uvCenter = float2(_ScaleCenterU, _ScaleCenterV); float2 uvOffset = float2(_OffsetU, _OffsetV); float2 uvScale = float2(_ScaleU, _ScaleV); float2 uv = (i.uv - uvCenter) * uvScale + uvCenter + uvOffset; float4 tex = tex2D(_MainTex, uv); tex.a *= pow(1.0 - tex2D(_AlphaBlendTex, i.uv).a, 2); return tex; } ENDCG } } }
Shader "Theta/Equirectangular2" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _AlphaBlendTex ("Alpha Blend (RGBA)", 2D) = "white" {} _OffsetU ("Offset U", Range(-0.5, 0.5)) = 0 _OffsetV ("Offset V", Range(-0.5, 0.5)) = 0 _ScaleU ("Scale U", Range(0.8, 1.2)) = 1 _ScaleV ("Scale V", Range(0.8, 1.2)) = 1 _ScaleCenterU ("Scale Center U", Range(0.0, 1.0)) = 0 _ScaleCenterV ("Scale Center V", Range(0.0, 1.0)) = 0 _Aspect ("Aspect", Float) = 1.777777777 } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Background+1" } UsePass "Theta/Equirectangular1/BASE" } }
メッシュを見てみるとこんな感じに移動されています。
端のほうでポリゴンが当たらず空白ができてしまっていますね…。Nora さんのように直接フラグメントシェーダから起こせば回避できると思います。
おわりに
全天球の AR やスタビライズなど色々とテーマがありそうで面白そうです。発売したら色々遊んでみようと思います。