無線ステレオカメラ映像をOculus Riftで扱うソースコード

外部無線カメラ映像をUSB経由で取り込みOculusで扱いました。ソースコードは、下記URLから取得できます。
https://github.com/takeoworks/Oculus-FPV-with-VR

Oculusでは、DirectXやOpenGLのような技術を使用してプログラミングしますが、画像を扱うのは、若干面倒です。コントロールを貼り付けて、あとはデータを流し込むだけ。。。というようなお手軽な方法はありません。ワールド空間座標にキャンパスを貼り付け、ローカルな画像を先ほどのキャンパスにワールド空間座標を考慮して行列を使って展開するといった考え方が必要となります。また、VC++だけでなく、グラフィック高速化のための、HLSLのようなシェーダー言語を使う必要があります。すこし前のプログラムなので、詳細なソースコード説明はできませんが、Oculusで外部のカメラを使って画像を取り扱いたいという方は参考にしてみてください。


システム構成
----
全体的な動きとしては、ステレオカメラ（単体のカメラを２つ並べただけ）の映像を無線で飛ばします。カメラ側の制御にはArduinoを使用していますが、これは、HMDの角度を伝え、サーボで制御するためだけに使用しています。カメラがとらえた映像は、無線を通じて、受信器で受け取ります。Oculus側のプログラムでタイミングを計り２つの視点が異なる映像を映像を取得しOculusに送ってやります。

完成イメージ
----

![image1.jpg](https://qiita-image-store.s3.amazonaws.com/0/67901/71f370d8-7fa6-364a-7688-fc298c1410c8.jpeg)



使用したパーツ
----
Arduino UNO
Arduino Wireless SD Shield
LCD Character Display (SD1602HULB(-XA-G-G))
XBeeZB Module (To R/S Headtraking Info)
USB Explore for XBee
Camera (2SW-410AM00C000) 
Tranmitter (TS 351  Wireless Transmitter)
Reciever (RC305 Wireless Reciever)
USB Mpeg Capture (Buffalo PC-SDVD/U2G)


カメラ部分の取り込みの処理
----
カメラ画像を扱うのがかなり面倒だったので、そこだけさらっとコードだけ紹介しておきます。

なお、コアな部分は、
https://github.com/takeoworks/Oculus-FPV-with-VR/tree/master/Samples
のソースコードです。Oculus Tiny Roomを土台にして、カメラ映像を表示しているので、カメラ画像にバーチャルな映像を重ねることも可能です。調整が必要ですが。。。
ソースコードを試すには、USB Mpeg Capture (Buffalo PC-SDVD/U2G)とカメラは必須ですが、そのほかは作りたいシステム構成によるでしょう。


画像を取り扱うためのシェーダーですが、D3D11_INPUT_ELEMENT_DESC でシェーダ扱うデータ構造を定義は以下のようにしています。

~~~~
    // Shader vertex format
    D3D11_INPUT_ELEMENT_DESC VideoCaptureVertexDesc[] = {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,       0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 }};

~~~~

次に頂点シェーダーとピクセルシェーダを定義しています。平面として画像を扱う場合は以下のように記述します。

~~~~
    // Video Capture vertex shader
    const char* BackGroundVertexShaderSrc =
        "struct VS_INPUT {\n"
        "   float4 m_vPosition : POSITION;\n"
        "   float2 m_vTexcoord : TEXCOORD;\n"
        "   float4 m_vColor    : COLOR;\n"
        "};\n"
        "struct VS_OUTPUT {\n"
        "   float4 m_vPosition : SV_POSITION;\n"
        "   float2 m_vTexcoord : TEXCOORD;\n"
        "   float4 m_vColor    : COLOR;\n"
        "};\n"
        "float4x4 Projection;\n"
        "VS_OUTPUT main(VS_INPUT Input) {\n"
        "   VS_OUTPUT Output;\n"
        "   Output.m_vPosition = mul(Projection, Input.m_vPosition);\n"
        "   Output.m_vTexcoord = Input.m_vTexcoord;\n"
        "   Output.m_vColor = Input.m_vColor;\n"
        "   return Output;\n"
        "}\n";

    // Video Capture pixel shader
    static const char* BackGroundPixelShaderSrc =
        "struct VS_OUTPUT {\n"
        "   float4 m_vPosition : SV_POSITION;\n"
        "   float2 m_vTexcoord : TEXCOORD;\n"
        "   float4 m_vColor    : COLOR;\n"
        "};\n"
        "Texture2D          g_txDiffuse : register(t0);\n"
        "SamplerState       g_samLinear : register(s0);\n"
        "float4 main(VS_OUTPUT Input) : SV_TARGET{\n"
        "   return g_txDiffuse.Sample(g_samLinear, Input.m_vTexcoord);\n"
        "}\n";
~~~~


DirectXでは扱うオブジェクトの頂点を設定しなければいけませんが、今回扱うオブジェクトはカメラの画像データです。
画像の頂点は、下記のように定義しています。時計回りの頂点設定となっています。

~~~~
  const VideoCapVertex CapVertex[] = {
            { { -1.0f, 1.0f, 0.0f }, { 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 0.0f } },
            { {  1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 0.0f } },
            { { -1.0f,-1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 0.0f } },
            { { -1.0f,-1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 0.0f } },
            { {  1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 0.0f } },
            { {  1.0f,-1.0f, 0.0f }, { 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 0.0f } } }; 

~~~~
説明がなく申し訳ないのですが、もしかしたら参考になれば幸いです。