凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

OMMF2014 で レゴ x プロジェクションマッピングなゲーム Mont Blanc Pj. を出展してきた

f:id:hecomi:20140910003725p:plain

はじめに

仕事が立て込んでいて書くのが遅れてしまいましたが、8/23、24 でソフトピアジャパンで行われた Ogaki Mini Maker Faire 2014 (OMMF2014) に、レゴで出来た自由に組み替えられるステージにプロジェクションしてインタラクティブに遊べるゲーム Mont Blanc Pj.(もんぶらんぷろじぇくと)を出展してきました。

Mont Blanc Pj. は以下の3つのコンセプトを主軸において、友人の id:jonki と趣味で開発しているゲームです。

  1. レゴで作成した立体ステージにプロジェクションしたゲーム
  2. レゴを自由に組み替えることでゲームの世界も連動して変化
  3. ゲーム中のオブジェクトハードウェアが連動

現実の世界とバーチャルな世界を良い感じに融合させることで、小さいお子さんでも直感的に楽しく遊べるゲームを目指して、昨年の Maker Faire Tokyo 2013 で出展し、その後昨年末の第5回ニコニコ学会で発表させて頂きました。大きく仕組みは変化していないので、詳細につきましては、以前の情報をご覧ください。

私はソフトウェア(認識周り・ゲーム)を担当しているので、本エントリでは、ソフトウェア側の改良点や発表しての感想、今後の展望などについてご紹介したいと思います。ハードウェア、本体の改良についてなどは後ほど id:jonki がエントリを書いてくれると思います(きっと)。

コード

利用しているコード一式は以下にあげています。

今回の改良点

ゲームの見た目

ニコニコ学会で発表した時のものがベースになっています。ロボット君(仮)が工場を駆け回ってるイメージです(世界観やストーリーは未設定です...)。

f:id:hecomi:20140909202814j:plain

f:id:hecomi:20140910003252p:plain

1日目の展示ではジャンプブロックが毒々しい赤色だった点など諸々実際に展示してみると見栄え的に辛いところが多かったので、懇親会後にドット打ちなおしたりして色々デザイン修正しました。

認識の高速化

以前は認識に2〜3秒かかっていたのですが、これは Qt で作成している認識プログラムにおいて QML 側(JavaScript 側) に多くのロジックを追いやっていたこと等が大きな原因でした。そこで今回は認識用のプログラムを全面的に書きなおして、ロジックはなるべく C++ 側に持たせ、QML 側では極力 UI 構築とデータの受け渡しのみにするよう注意しました。手が入っていないかを判断するために一定の判断時間を設けているのですが、感覚的には手をどけて直ぐに反応するようになりました。

キャリブ方法の改善

まだ自動化にまでは至っていないですが、レゴの位置合わせ(キャリブレーション)を大幅に改良しました。グリッドを微調整したりどうしても認識が暴れる部位はダブルクリックで除外(緑色のところ)するようにしたりなどの対応もしています。前回同様、突然の死(強制終了)に備えて、すべてのパラメタはリアルタイムに保存するようにしています(が、今回は1度も落ちませんでした)。

機材の削減(Xtion の利用)

以前は赤外線投光機 + 赤外線カメラを用いて赤外領域による認識を行っていましたが、赤外投光機がかなり大きく重いのと、散乱光を用いる(直接光だとムラができてしまう)関係上、展示員の立ち位置による影響が大きかったりと様々な問題が有りました。

そこで今回は Xtion を用いて投光機と赤外線カメラ両方の役割を果たしてもらうことにしました。Xtion は基本的にはデプスセンサなのですが、赤外画のみを利用しています。レーザーによる赤外パターン光は色々と試した結果、ティッシュ2枚が一番散乱したので、これを全面につけ投光機代わりに利用しています。

f:id:hecomi:20140823103936j:plain

正面から当てると中心部の明るい光が見えてしまいますが、側面からだとそれほどムラなく全体を照らすことが出来ました。結果として、USB ケーブル 1 本のみで電源供給&投光が可能になりました。

また、タブレットで行っていたブロック変更操作を物理ボタンにしたことから無線ルータも不要になりました。

ハードウェアの追加 / 改良

ゲーム上のキャラがブロックを叩くと実際の世界のブロックも壊れる(ように見える)ギミックを追加しました。場所は決め打ちで特殊なブロックが追加され、叩くとゲームの世界で壊れるエフェクトを出す & ソレノイドによって裏からついて実際のレゴブロックを吹っ飛ばす、というものです。

なるべくインタラクションはブロックの付け外しのみに拘りたかったのですが、それによって単にブロックが出たり消えたりでは単調になってしまうので、3つ目のコンセプトを活用して、こういった特殊なルールを設けるとちょっとした味付けになるかなと思って作成しました。何だ何だと見に来てくれたり、面白がって何度もつけてくれるお子さんもいて、作ってみてよかったなと思いました。大人は突然吹っ飛ぶとビクッとするのですが、お子さんはほとんどビクッとしないのが面白かったです(慣れてるのかな?)。

ギミック作成を頑張ってくれた相方に感謝です。その他の改良もあるので id:jonki の執筆に期待。

今回の反省・今後の改良について

認識の高速化

小さいお子さんだとブロックを付けた後になかなか手を引っ込めてくれなかったりしました。そこで次は領域分割して手が画面に入っていてもある程度認識をさせようかなと考えています。現在は全体が安定したら差分でブロック追加/削除を判断、としているのですが、ここを全体でなくて複数領域に分割するイメージです。

これに加えて、手を引っ込めたら面白いことが起こりそうだという表現で自然に促せたら良いなと思うので、なにか考えたいです。

1ポチ認識

今は 2x2 のブロックを認識して計 24 x 24 ステージを構成しているのですが、奇数位置に 2x2 をつけてしまうと誤認識(2x4 や 1 ポチズレた 2x2 等)してしまい、これに惑わされる方々が多かったです。そこで、次回は 1x1 を認識するようにして奇数位置も認識できるようにしたいです。

おそらく Xtion 化で解像度が高くなったので問題なく認識はできると思うのですが、問題はゲームの方で色々と整合取れなくなるところが多いので、そこをうまく何とかしたいです。

レゴのみによるブロックの出し分け

今回一番失敗したな、というのがスイッチによるブロック種別選択です。追加できるブロックには普通のブロックと接地状帯で触れるとジャンプするブロックがあるのですが、以前はタブレットでブロックを切り替えできるようにしていたところを、物理的なスイッチで切り替えるようにしてみました。

f:id:hecomi:20140909214224p:plain

が、そもそもブロックの切り替えは説明しないと絶対に分からない点、ボタンを押すと新たに取り付けたレゴブロックがゲームに反映されると勘違いされてしまった点など、色々な反省点が有りました。

ただこれはレゴブロックの識別が難しいために直前でえいやで作った苦肉の策でもあったので、淡色レゴやレゴの凸部にパターンを描くなど色々と試して、次回こそはなんとかこの点を修正したいと思います。

ハードウェアのモジュール化

ハードウェア連動についてはもっと色々と出来るのではと考えていて、その中で最もやりたいのがハードウェアを搭載したレゴブロックです。現在1ブロックしかない光るブロックを任意の位置につけれたり、スイッチを押すとアイテムが出てくるブロックがあったり、スライダでゲーム内のギミックを動かせるブロックがあったりなどです。

レゴの付け外しのインタラクションからルールは増えてしまいますが、何が出来るか(スイッチを押す、スライダを動かすなど)直感的に分かる範囲内では色々挑戦してみたいです。

1段以上の段差の取得

複数段の取得については以前 Kinect で試して失敗した(うまく取れなかった)経緯あったのですが、近距離ならいけるかなと思い、今回 Intel Createive Camera でも試してみました。が、精度は出ていると思うのですが綺麗に認識できなかったので保留にしています。

f:id:hecomi:20140909205020p:plain

取り敢えず Kinect v2 も手に入れたので試してみます。

画面のズレ

Unity のゲーム画面とレゴブロックの位置調整のために、カメラの位置移動、回転を用意していたのですが、いざやってみると微妙に位置が合わなかったりして残念だったので、次回は Image Effect で簡単に射影変換出来るようにしておこうと思います。

教えていただいた先行事例

ぶろっくぴーぽー

Eric さんに SEGA が 2009 年の CEDEC にて発表した資料を教えて頂きました。プロジェクションと液晶の違いはあれど、実際のブロックの赤外線による認識、そのゲームへの反映などかなり共通点があるなと感じました。アーケードゲームにまで仕上げるのはさすがプロですね...。

よく頂いた質問

売らないの?

今のところ販売やビジネス化は考えていません。2点理由があり、1点目は機材がまだ高過ぎること(特にプロジェクタ)で、2点目はビジネス化しようとすると楽しくならなくなりそうだな...、と思ったからです。特に2点目について、まだまだ掘り甲斐のある分野だと思うので、体験を縮小させずに純粋に楽しさの部分を色々改良していきたいと思っています。あと単純に我々が作ってるのが好きなので、あまり別のことに時間を割きたくない...、というのも大きいです。

ただ、ご要望も頂いたので、色々な方に体験してもらうためにソフト / ハード両面から設置の簡単化や耐久性の向上をして、我々以外でもいろんな場所で展示してもらえるようにする、といった改良につきましては、可能な限り進めていきたいと思います。

次回出展予定

Maker Faire Tokyo 2014 に出展したいと思っています。

おわりに

今回は展示のお手伝いに id:AMANE に参加してもらったお陰で、去年の MFT と比べてかなり楽させてもらうことが出来ました。ありがとうございます。また、色々な方と交流できる素敵な場を提供して頂いた OMMF2014 の運営の皆様、ありがとうございました!

ノードベースのエディタで Shader をリアルタイムに編集して色々な質感を簡単に作れる Shdaer Forge を使ってみた

はじめに

本記事はUnity アセット真夏のアドベントカレンダー 2014 Summer!の 9 日目の記事になります。前回は id:baba_s さんによる 【Unity】Editor Console Pro でゲーム開発効率化 - コガネブログ でした。

Unity では、シェーダによって様々な質感や効果を加えた表現がされています。

しかしながらシェーダを書くには色々と知識が必要で、思い通りのものを作れるようになるまでかなり時間がかかるイメージです。また、知識がついた後でも色々な物体に応じて数多くのシェーダを書くのは大変です。

そこで、本エントリでは、ノードベースビジュアルプログラミングでシェーダをグラフィカルに作成できる Shader Forge という Asset の紹介をしたいと思います。

今年 4 月に行われた Unite Japan 2014 でも @tsubaki_t1 さんが紹介されていました。

$80 / PC の有料のアセットですが、公式といわれても疑いようのないクオリティで、ノードを組みわせて Diffuse や Specular、Normal を始めとして、Refraction の調整や、Vertex の変形、Tessellation に至るまで様々なパラメータを簡単に調整し、それらをリアルタイムでプレビューしてみることが出来ます。

f:id:hecomi:20140809123705p:plain

一通りの雰囲気を概観できるようにまとめてみました。

ドキュメント

  • Shader Forge
    • 具体的にどういったノードが利用できるのかについてアニメーション GIF と共にまとめられています。
  • Shader Forge Wiki - Shader Forge Wiki
    • どういうノードの接続の仕方をすれば何が出来るのかカテゴリ別にアニメーション付きでまとまっています。

チュートリアル

公式で動画によるチュートリアルが3つあがっています。

Shader Forge - Making a basic shader

Shader Forge - Vertex color blending & UV tiling

Shader Forge - Custom Blinn-Phong

最初の動画あたりを見ると、Shader Forge のイメージが掴めると思います。

取り敢えず使ってみる

f:id:hecomi:20140805204500p:plain

Window > Shader Forge からウィンドウを開き、「New Shader」を選択します。

f:id:hecomi:20140805204605p:plain

すると初期画面が表示されます。最初から表示されているこの「Main」ノードに色々な情報を入力する経路を作ることで、最終的な見栄えを完成させるわけです。まずは色をつけるために右側の検索バーに「Color」と打ち込むと出てくる「Color」ノードをドラッグ&ドロップして適当な色を指定、RGB の出力を「Main」の入力につなぎます。

f:id:hecomi:20140805204724p:plain

シェーダがリアルタイムにコンパイルされて赤く表示されました!この要領で「Texture2D」ノードを生成して同じく線を伸ばせば画像が表示されます。ノードの上部で名前を変更できるので、ここでは「MainTex」と名前をつけてみました。

f:id:hecomi:20140805205259p:plain

この要領でポチポチノードを配置していけば、チュートリアルのムービーのように法線マップと色を指定できるシェーダの出来上がりです。

f:id:hecomi:20140805210729p:plain

マテリアルの作成

ここまでではシェーダを作っただけで、Unity ではシェーダはマテリアルに指定して使います。Shader Forge のエディタ上では画像を指定してプレビューを見ていましたが、実際にはシェーダはプレースホルダしか有することが出来ません(色は持てます)。そこで適当なマテリアルを作成、そこに先ほど作成したシェーダを適用し、プレースホルダを自分で埋める必要があります。シェーダを適用した直後はこんな感じになります。

f:id:hecomi:20140805211151p:plain

そしてテクスチャを同じものを指定すれば、プレビューで見ていたものと同じ表現になるわけです。

f:id:hecomi:20140805211658p:plain

考えを変えればテクスチャを差し替えた別のマテリアルにも同じシェーダが適用できるわけです、便利ですね。でもプレースホルダを埋めてくれたマテリアルも一緒に作ってくれたらいいのになぁ...、とも思います。

キーボードショートカット

ちょっと操作についても触れておきます。

右クリックで各ノードを選択できるコンテキストメニューが表示されます。

f:id:hecomi:20140805205803p:plain

また、アルファベット押下(しっぱなし) -> スクロール -> クリックでもノードを挿入できます。

f:id:hecomi:20140805205838p:plain

また Ctrl + C / Ctrl + V でのコピーや Ctrl + D での複製も出来ます。しばらくハマってたのですが、不要になったノードを消去するには「⌘ + X」(Ctrl + X)のみで、delete キーは効かないようです。

サンプルを見てみる

何が出来るか雰囲気が掴めたところでサンプルを見てみましょう。サンプルは Shader Forge ディレクトリ直下に Example Scene という名前でシーンが入っています。

f:id:hecomi:20140805223425p:plain

見たいオブジェクトを選択して Inspector から「Open Shader in Shader Forge」を選択するとどういったノードの接続をしているかを見ることが出来ます。以下は物理ベースシェーダを作るためのノードの接続例です。

f:id:hecomi:20140805223924p:plain

知識不足でちょっと説明はできないですが、ノードで接続されていると何をしているかとても見やすいです。またパラメタをリアルタイムにいじりながら結果を見れるので、シェーダのイメージを掴みながら勉強する上でも役に立ちそうです。

出力されるコード

Shader Forge で作成したシェーダは簡単に読めるコードとして出力されます。ちょっと長いですが書き出してみます。

// Shader created with Shader Forge Beta 0.36 
// Shader Forge (c) Joachim Holmer - http://www.acegikmo.com/shaderforge/
// Note: Manually altering this data may prevent you from opening it in Shader Forge
/*SF_DATA;ver:0.36;sub:START;pass:START;ps:flbk:,lico:1,lgpr:1,nrmq:1,limd:1,uamb:True,mssp:True,lmpd:False,lprd:False,enco:False,frtr:True,vitr:True,dbil:False,rmgx:True,rpth:0,hqsc:True,hqlp:False,tesm:0,blpr:0,bsrc:0,bdst:0,culm:0,dpts:2,wrdp:True,ufog:True,aust:True,igpj:False,qofs:0,qpre:1,rntp:1,fgom:False,fgoc:False,fgod:False,fgor:False,fgmd:0,fgcr:0.5,fgcg:0.5,fgcb:0.5,fgca:1,fgde:0.01,fgrn:0,fgrf:300,ofsf:0,ofsu:0,f2p0:False;n:type:ShaderForge.SFN_Final,id:1,x:32512,y:32689|diff-58-OUT,normal-52-RGB;n:type:ShaderForge.SFN_Tex2d,id:3,x:32996,y:32639,ptlb:MainTex,ptin:_MainTex,tex:b66bceaf0cc0ace4e9bdc92f14bba709,ntxv:0,isnm:False;n:type:ShaderForge.SFN_Tex2d,id:52,x:32822,y:32835,ptlb:Normal,ptin:_Normal,tex:bbab0a6f7bae9cf42bf057d8ee2755f6,ntxv:3,isnm:True;n:type:ShaderForge.SFN_Multiply,id:58,x:32791,y:32656|A-3-RGB,B-59-RGB;n:type:ShaderForge.SFN_Color,id:59,x:32996,y:32835,ptlb:MainColor,ptin:_MainColor,glob:False,c1:0.7573529,c2:0.7051217,c3:0,c4:1;proporder:3-52-59;pass:END;sub:END;*/

Shader "Shader Forge/MyFirstShader" {
    Properties {
        _MainTex ("MainTex", 2D) = "white" {}
        _Normal ("Normal", 2D) = "bump" {}
        _MainColor ("MainColor", Color) = (0.7573529,0.7051217,0,1)
    }
    SubShader {
        Tags {
            "RenderType"="Opaque"
        }
        Pass {
            Name "ForwardBase"
            Tags {
                "LightMode"="ForwardBase"
            }
            
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma exclude_renderers xbox360 ps3 flash d3d11_9x 
            #pragma target 3.0
            uniform float4 _LightColor0;
            uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform sampler2D _Normal; uniform float4 _Normal_ST;
            uniform float4 _MainColor;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float2 texcoord0 : TEXCOORD0;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 tangentDir : TEXCOORD3;
                float3 binormalDir : TEXCOORD4;
                LIGHTING_COORDS(5,6)
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o;
                o.uv0 = v.texcoord0;
                o.normalDir = mul(float4(v.normal,0), _World2Object).xyz;
                o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.binormalDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
                o.posWorld = mul(_Object2World, v.vertex);
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }
            fixed4 frag(VertexOutput i) : COLOR {
                i.normalDir = normalize(i.normalDir);
                float3x3 tangentTransform = float3x3( i.tangentDir, i.binormalDir, i.normalDir);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
/////// Normals:
                float2 node_63 = i.uv0;
                float3 normalLocal = UnpackNormal(tex2D(_Normal,TRANSFORM_TEX(node_63.rg, _Normal))).rgb;
                float3 normalDirection =  normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
                float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
////// Lighting:
                float attenuation = LIGHT_ATTENUATION(i);
                float3 attenColor = attenuation * _LightColor0.xyz;
/////// Diffuse:
                float NdotL = dot( normalDirection, lightDirection );
                float3 diffuse = max( 0.0, NdotL) * attenColor + UNITY_LIGHTMODEL_AMBIENT.rgb;
                float3 finalColor = 0;
                float3 diffuseLight = diffuse;
                finalColor += diffuseLight * (tex2D(_MainTex,TRANSFORM_TEX(node_63.rg, _MainTex)).rgb*_MainColor.rgb);
/// Final Color:
                return fixed4(finalColor,1);
            }
            ENDCG
        }
        Pass {
            Name "ForwardAdd"
            Tags {
                "LightMode"="ForwardAdd"
            }
            Blend One One
            
            
            Fog { Color (0,0,0,0) }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDADD
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #pragma multi_compile_fwdadd_fullshadows
            #pragma exclude_renderers xbox360 ps3 flash d3d11_9x 
            #pragma target 3.0
            uniform float4 _LightColor0;
            uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform sampler2D _Normal; uniform float4 _Normal_ST;
            uniform float4 _MainColor;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float2 texcoord0 : TEXCOORD0;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 tangentDir : TEXCOORD3;
                float3 binormalDir : TEXCOORD4;
                LIGHTING_COORDS(5,6)
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o;
                o.uv0 = v.texcoord0;
                o.normalDir = mul(float4(v.normal,0), _World2Object).xyz;
                o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.binormalDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
                o.posWorld = mul(_Object2World, v.vertex);
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }
            fixed4 frag(VertexOutput i) : COLOR {
                i.normalDir = normalize(i.normalDir);
                float3x3 tangentTransform = float3x3( i.tangentDir, i.binormalDir, i.normalDir);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
/////// Normals:
                float2 node_64 = i.uv0;
                float3 normalLocal = UnpackNormal(tex2D(_Normal,TRANSFORM_TEX(node_64.rg, _Normal))).rgb;
                float3 normalDirection =  normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
                float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));
////// Lighting:
                float attenuation = LIGHT_ATTENUATION(i);
                float3 attenColor = attenuation * _LightColor0.xyz;
/////// Diffuse:
                float NdotL = dot( normalDirection, lightDirection );
                float3 diffuse = max( 0.0, NdotL) * attenColor;
                float3 finalColor = 0;
                float3 diffuseLight = diffuse;
                finalColor += diffuseLight * (tex2D(_MainTex,TRANSFORM_TEX(node_64.rg, _MainTex)).rgb*_MainColor.rgb);
/// Final Color:
                return fixed4(finalColor * 1,0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
    CustomEditor "ShaderForgeMaterialInspector"
}

4 行目のコメントにノードの位置や接続などの情報が格納されているようです。

外から変数を指定できるようにプレースホルダとなるノードは Properties に書きだされます。先ほど指定した名前が変数名として与えられています。

Properties {
    _MainTex ("MainTex", 2D) = "white" {}
    _Normal ("Normal", 2D) = "bump" {}
    _MainColor ("MainColor", Color) = (0.7573529,0.7051217,0,1)
}

全体を見てみると、メインライトや環境光用に ForwardBase、追加のライト用に ForwardAdd の 2 つの Pass が作成されています。それぞれ内部でノードの流れに沿う用に VertexInputVertexOutput が定義され、頂点シェーダの vert とフラグメントシェーダの frag が定義されています。シェーダ自体はそれほど長いコードでも読みにくいコードでもないので吐き出されたシェーダを調整する、みたいなこともそれほど難しくないように思われます。

既存のシェーダを読み込む

Shader Forge は通常の Unity のシェーダを書き出す仕組みなので、逆にシェーダを読み込むことも出来るようです。が、試しに、Unity に付属のトゥーンシェーダを読み込んでみようとしたところ、うまく行きませんでした...。

f:id:hecomi:20140805212240p:plain

「Replace with Shader Forge shader」というボタンがあるのでクリックして、指定したシェーダのノードが出てきたら格好良かったのですが。

コードの利用

ビジュアルプログラミングの欠点は簡単なことをやりたいだけなのに複雑にノードを結ばなければならず見た目が煩雑になってしまう点だと思います。例えば時間に応じて明滅する以下の様なシェーダを作ったとします。

f:id:hecomi:20140805221150p:plain

結構ぐちゃぐちゃしますね...。そこで Shader Forge には「Code」ノードが用意されています。ここではノードの値を入力とした関数が Cg 言語をそのまま書けます。

f:id:hecomi:20140805221458p:plain

単純なノードが大分すっきりしました(本当は _SinTime.x を使おうと思ったのですが、プレビューでアニメーションされなかったので「Time」ノードを入力にしてます)。適宜こういったノードを利用することでシンプルな構成を保てる気がします。

Skyshop との連携

Skyshop をインポートして Examples.zip および ShaderForgeExtension.zip を解凍します。

f:id:hecomi:20140807125634p:plain

すると、Shader Forge 内で Skyshop のノードが使えるようになります。

f:id:hecomi:20140807125745p:plain

f:id:hecomi:20140807125804p:plain

超綺麗です。

その他

Shader Forge - Real-time spherical area lights

カスタムライトのサンプル動画です。

類似の Asset

同じようにノードベースでシェーダを記述できる Unity のアセットに、無料の Strumpy Shader Editor があります。優良なこともあり、Shader Forge の方がよりグラフィカルに且つ色々なことができる高機能なものになっている印象です。

Unity オフィシャルのノードベースエディタ?

Shader Forge について調べている最中、Unity の中の人の Tim Cooper さんが開発しているノードベースのエディタの動画を見かけました。

Shader Editor Beta 4 from Tim Cooper on Vimeo.

2010、2011 頃の動画投稿だったので今は開発停止しているのでしょうか...。

おわりに

Unity 5 より汎用的に使える物理ベースシェーダの導入がされますが、より細かい調整が必要な際はやはり自前のシェーダが必要になってくると思います。そういった際に Shader Forge を使うと、直にシェーダを書くよりも、より少ない知識で短時間に所望の質感が作れると思います。また、グラフィカルにシェーダが表現されることにより、中で何が起きているのか把握しやすくなり、学習コストも下がります。ちょっとお高めですが、それだけの価値はあると思いますので、導入してみてはいかがでしょうか。

次回は、Poto7 さんによる「1日で作るカジュアルゲーム!FlappyXXXXを作ろう」になります。

Oculus Rift DK2 が届いたので遊んでみた

はじめに

ついに Oculus Rift DK2 (Developer Kit 2) を手に入れました!

f:id:hecomi:20140803002344j:plain

f:id:hecomi:20140803002350j:plain

f:id:hecomi:20140803002031j:plain

DK2 は 2014/7 時点で全世界で 45,000 個もの注文(DK1 は 2014/3 時点で 60,000 個)の注文があったとのことです。私は開発用と布教用で 2 個買いました。

公式の動画を含め、いくつか動画を見るといろいろとイメージが掴めると思います。

外観など筐体の特徴については @kawauso3 さんによる週刊アスPlus の以下の記事がとてもわかり易く、中身については iFixit による分解レポートもあがっているため、合わせてみると特徴がよくわかると思います。

自分の知識を整理するためと、ハンズオン時の感想を記録・共有するためにエントリをまとめてみました。

特徴 - DK1 との違い

今回のアップデートのポイントはいくつかあります。

すでにご存じの方も多いと思いますが下記にまとめておきます。

ポジショナルトラッキングによる自然な動き

DK1 では回転しか取れなかったため下から覗き込むといった動作が実現できていませんでした。DK2 からは 60 Hz の近赤外線 CMOS センサ(赤外線カメラ)をプレイヤーの前方に配置することで、HMD 側に仕込まれた赤外線 LED を検知、そこから位置を割り出すといった手法で、位置のトラッキングを実現しています。

f:id:hecomi:20140803130301j:plain

f:id:hecomi:20140803130311j:plain

以下の動画が大変わかり易いです。

OLED 液晶による残像感のない高コントラストな映像

DK1 では 1280 x 800(640 x 800 / eye) だったのに対し、DK2 では 1920 x 1080 (960 x 1080 / eye) になりました。またパネルも LCD から高速で自発光型な OLED になり、よりきれい(黒が黒く見えるなど)で残像感のない映像が見れるようになりました。ディスプレイは冒頭の iFixit 記事によると、 Samsung の Galaxy Note 3 のパネル(60 Hz)をオーバークロックして 75 Hz で利用しているようです。

実際に装着してみると、DK1 の時に気になっていた網目(ドットとドットの隙間)が見えてしまう効果もかなり低減されていて、より自然な映像になっていました。また、色収差を補正するためにスクリーン上の映像は RGB がずれたような絵になっています。

f:id:hecomi:20140803142528p:plain

ちょっとずれているとこの RGB のズレが目立ってしまいますが、横のダイヤルを回して液晶までの距離を調節したり、バンドで装着位置をしっかり調整すれば気にならなくなり、より鮮明な画になります。こういったいくつかの対応が SDK 側でもなされていて、例えばディストーション用に大きく(2364x1461)描画するなどの対応がされています。

f:id:hecomi:20140803154607p:plain

これらハード・ソフト両面からのきめ細やかな施策により、よりきれいな VR 映像が実現されています。

複数の遅延対策による自然なヘッドトラッキング

ジャイロ、加速度、磁気センサによる 1000 Hz なヘッドトラッキングは前回と同じですが、今回はレイテンシーテスターが新しく入りました。レイテンシーテスターは以前 DK1 用にスタンドアロンで売られていたものが、今回ビルトインされた形になっています。

Oculus Rift では予測補間をすることにより遅延を吸収していますが、この補間は実際に目に届くまでの時間を知らないと正確にはなりません。そこで光学式の本テスターにより実際に目に液晶の映像が入るまでの時間をリアルタイムに計測することでより自然な動きを実現しています。

遅延に関しては公式でスライドも上がっています。

コントロールボックスの排除

なくなりました。ケーブルは HDMI とヘッドトラッキング用のケーブル 2 本がまとめられて 1 本になって本体から出ています。代わりに、カメラとのシンクケーブルが 1 本増えました。あと本体に USB 口が 1 つついているのが面白いです。色々と連携デバイスが出てくると面白いですね。

ちょっと重くなった

DK1 は 395g だったのに対し、DK2 は 453g と重くなっています。確かに装着した感じ少し重い印象も受けますが、今回からコントロールボックスが排除されたことを考慮すればトータルでは相当な軽量化

ちょっと高くなった

DK1 は $300 + 送料 $50 でしたが、今回は $350 + 送料 $75 になりました。それでも安い...。

セットアップについて

エヴァンジェリストの @GOROman さんをはじめとする有志の方々による素晴らしい情報がまとめられています。今回は単純な映像出力ではないため、色々とインストールに手こずることが多い(私も動かすまでに時間かかりました...)ので、導入前に一度目を通しておくことを強くお勧めします。

必要とされるスペックについて

解像度が上がったことも影響し、DK1 の時よりもより高スペックが求められています。しかしながら Untiy での開発に関しては、アンチエイリアスをオフにすることで、ある程度パワーのない PC でも動くようです。

設定について

Oculus Configration Utility

f:id:hecomi:20140803165818p:plain

複数のユーザ設定が保存できます。中でも重要なのが Advanced にある IPD(瞳孔間距離)の設定です。設定すると自然に立体視ができるようになります。

f:id:hecomi:20140803170229p:plain

これは @yuujii さんがスクショ付きでまとめてくださっているので以下のページをご参照ください。

Rift Display Mode

f:id:hecomi:20140803170353p:plain

「Direct HMD Access from Apps」モードと、「Extend Desktop to the HMD」モードという 2 つのモードがあります。

Direct HMD Access from Apps モード

f:id:hecomi:20140803164926p:plain

Windows にディスプレイとして認識されずに、直接 HMD へ描画を行うモードです。上記のようにどういった画面が表示されているかを別ウィンドウで見ることができます。後述の Extend モードと比較してレイテンシが低減されるモードになり、前述のレイテンシテスターも動作する模様です。可能であればこちらを使いましょう。

Extend Desktop to the HMD モード

f:id:hecomi:20140803165611p:plain

従来通り、Windows にディスプレイ(縦として認識)として認識されます。

アプリケーション

以下のリンクに DK2 で遊べるソフトウェアがまとまっています。

いろいろ試した中からいくつかピックアップしてみました。

Demo Scene

f:id:hecomi:20140803171852p:plain

Configuration Utility から「Show Demo Scene」ボタンを押すと起動します。机の上にいろんなオブジェクトがあって DK2 の綺麗さとポジトラの威力を最初に体験するのに相応しいデモになっています。思わず机の上のものを取ろうとして、あれ、手がない、となる感覚はとても不思議です。

Oculus World Demo

Oculus SDK for Windows に含まれています。

最初はこんな感想をいだきました。

が、SDK 付属のデモは軽量化のために色々とエフェクトが切ってあるためチープに見えてしまうようです(@yando さんに教えていただきました)。

そこで、同サイトの Unity 4 Pro Integration でビルドをしたところ、色々なオブジェクトが動いたりともろもろのクオリティが上がり、「あー、異国来たんじゃー」感が出ました。

BLAST BUSTER 2

@yasei_no_otoko さんによる Leap Motion にも対応したシューティングゲーム。Perilous Dimension という名で DK1 時代有名でした。きれいな宝石を Leap Motion がない場合は頭の向きでロックオン、ある場合は 10 本の指を使ってロックオンして壊していくゲームです。頭の向きだけで操作できるのがすごい楽です。ホーミングレーザーがとても綺麗でぶっ壊すのが爽快です。

すわこちゃん Cubic for Oculus Rift

※ 動画は DK1 時のもの

f:id:hecomi:20140805012428p:plain

@rodonjohn さんによるジャンピング立体弾幕ゲームのすわこちゃん Cubic の待望の DK2 版です!ゲームとして一番完成している Oculus Rift ゲームだと思います。DK2 のきれいな画面&ポジトラ付きでいろんな角度から楽に見れるようになってプレイしやすくなりました。こんなにすごいものが無料でできるのは驚きです...。

最初、エラー(「動作を停止しました」で終了)で起動しなかったのですが、@rodonjohn さんに色々と教えていただき、結論としては私のパソコンに DirectX のランタイムが入っていなかったというのが原因でした。

Totoro Bus Stop Scene

著作権的にはいいのか気になりますが、あー、アニメの世界が現実だったらこんな感じになるんだろうなぁ...、と感動しました。動画を見ずにプレイするのをおすすめします。

Mikulus DK2

f:id:hecomi:20140805013642p:plain

そして @GOROman さんによる FHD でポジトラする MikulusDK2 版が昨日発表されました。DK2 を持った人々は新たなる世界へと旅立って行きました。

その他情報

ドキュメント
Alienware 17 での動作

私がテストしたのは Alienware 17(GeForce GTX880M)なのですが、以下の 3 点ひっかかりました。

認識しない

どういう理屈かはわかりませんが本体ケーブル接続部のカバーを外して別の HDMI を挿して PC と接続したところ認識し、以降認識するようになりました。

Extend Desktop to HMD モードの映像が変

DK2 はモニタの向きを「縦」にするのが普通なのですが、以下のように「横(反転)」でないと正常の表示されませんでした(縦画で表示される)。

f:id:hecomi:20140803162001p:plain

NVIDIA の設定

ドライバを最新にした後、以下の様な設定にしました。

f:id:hecomi:20140803162737p:plain

f:id:hecomi:20140803162725p:plain

Optimus を切るために優先するグラフィックスプロセッサを「高パフォーマンス NVIDIA プロセッサ」、垂直同期を「オン」、レンダリング前最大フレーム数を「1」にしています。

おわりに

以下の記事を書いてからおよそ 1 年しか経っていません。

進化に驚くばかりですし、この先を進んでいけば面白い未来がある気がしてなりません。私は平日は本職が立て込んでいて DK2 が受け取れなかったのですが、日本では Oculus Rift コミュニティの活気がすごいので、ようやく受け取れたあとは既にベストプラクティス的なレールが敷かれていて「楽しく使う Oculus Rift DK2」に従ってインストールすることで、それほど躓かずスムーズに体験までこぎつけられました。また、Twitter で DK2 で検索すると色々な知見をみんなつぶやいていますし、困っていれば色々と助けてくれます。私もこの一端を担えるよういろいろ頑張っていきたいと思います。

以下総括: