Quest3 でなんか作ってみた

Quest3 ではカラーパススルーが使えるので試してみました。


ここまでに作ったデモの種明かしや、ちょっと困った点とかのメモなど。

カメラの CM でありそうなやつ

最初に作ったのがこれ。カメラの CM でありそうな沢山のボールが跳ねるやつ。

残念ながら Quest3 の空間認識ではここまで正確にコリジョンを取れません。
実はこの家に引っ越してきた時、階段の傾斜が急だったので手前の壁をぶち抜いてリフォームできないかなぁと蹴上と踏面を何cmまで行けるかシミュレーションするためにUnityで階段のモデル作ってたんです。

そのモデルを透明(Depth Only)で表示してコリジョンと遮蔽に使っています。箱に当たっているのは箱サイズの Cube に合わせて箱を置いたから!

 

間違えて自宅をフォーマットしてしまった

AR のバイブルといえば電脳コイル。その電脳コイルから印象的な「データがありません」と「フォーマットしています」のシーンを再現してみました。こちらも家の図面に合わせて Cube で壁を配置して表示しています。

フォーマットされていくのはサーフェースシェーダーで if (IN.worldPos.z < _CutZ) discard; してるだけ。実際には切断面が気になったのでパーリンノイズでごまかしていますが。

よく見ると窓の位置がちょっとズレていたりするのでもう少し正確に配置するべきだったかなぁというのが反省点。
あとは一軒単位のフォーマット(?)なので「フォーマットしています」が小さくてあんまり怖くないところかな?

 

パススルーでトイレ入ったら変なゲーム始まったんだけど…

Quest3 のコントローラモデル、ストラップ部分に縦長の穴が開いてるのを見ているとピコーンと何か閃いてしまったようで…

便器に正確に合成したかったので TOTO の系列サイトから BIM 用モデルをダウンロードして使っています。ただ蓋を閉じた状態のモデルしかなくて蓋をとっても内側のボウル部分が作られていなかったのでタンクや便座はうちの便器と同じモデルですが便器本体は別型番の蓋無しモデルを使っています。側面形状などがちょっと違うんだけどそこはあまり見えないのでOK。便座は土台と便座に分かれてはいるんだけど可動するようになってなかったので Blender でメッシュを分割して便座を上げました。

スプラトゥーン風ペイントは AssetStore にあった無料のやつを使っています。
このときは DepthAPI をちゃんと見ていなかったので床に垂れたペイントが手の上に来てしまっていますね。DepthAPI を使えば簡単になおるのでちゃんとドキュメント読んでおけば良かったな。

ネタ的にはダブルとミサイルはやりたかったんだけど、もっとびちゃびちゃにしたかったので上向き噴射が必要なボスも作ってレーザーでなくサイクロンに。シールドは付く位置を間違えちゃったかな?私がそんな下品な事を考える訳ないじゃないですか!

ここまでの3作の位置合わせは原始的だけど最初にコントローラで移動と回転できるようにしてあって、目視で合わせています。ちょっと面倒になってきたので SceneAPI で自動的に合わせるようにしよう。

 

DepthAPI で 3Dスキャナーできたよ~

DepthAPI を使って何かやってみようと 3D スキャナー的なデモ。

仕組みとしてはカスタムシェーダーでスキャンビーム的なやつと EnvironmentDepth の距離が1cm未満なら そのピクセルのワールド座標を1cm単位に丸めて GraphicsBuffer にストア。

    struct Varyings
    {
        ...
        float3 worldPos : TEXCOORD2;
    };

    RWStructuredBuffer<float3> VertBuffer;

    Varyings vert(Attributes input)
    {
        ...
        output.worldPos = TransformObjectToWorld(input.vertex.xyz);
        return output;
    }

    half4 frag(Varyings input) : SV_Target
    {
        ...
        float2 uv = input.positionNDC.xy / input.positionNDC.w;
        float dist = abs(SampleEnvironmentDepthReprojected(uv) - LinearEyeDepth(input.positionCS.z, _ZBufferParams));

        if (dist < 0.005) { // 1cm
            float3 pos = float3(round(input.worldPos.x*100)*0.01, round(input.worldPos.y*100)*0.01, round(input.worldPos.z*100)*0.01);
            uint num;
            uint stride;
            VertBuffer.GetDimensions(num, stride);
            uint idx = VertBuffer.IncrementCounter()-1;
            if (idx < num){
                VertBuffer[idx] = pos;
            }
        }
        ...
    }

c# 側で HashSet に入れて重複を排除。あとは表示するだけ。

public class Scanner : MonoBehaviour
{
    GraphicsBuffer vBuffer;
    static int size = 1024*1024;
    Vector3[] arr = new Vector3[size];
    HashSet<Vector3> vertDic = new HashSet<Vector3>();

    void Start() {
        ...
        vBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Vertex|GraphicsBuffer.Target.Counter, size , sizeof(float)*3);
        vBuffer.SetCounterValue(0);
        Graphics.SetRandomWriteTarget(1, vBuffer, false); // 無いとRWStructuredBufferに渡らない
        material.SetBuffer("VertBuffer", vBuffer);
    }

    void Update() {
        ...
        GraphicsBuffer.CopyCount(vBuffer, argBuffer, 0);
        argBuffer.GetData(arg);
        vBuffer.GetData(arr);
        int incomingNum = arg[0];
        if (incomingNum > size) incomingNum = size;

        for (int i=0; i<incomingNum; i++) {
            if (vertDic.Add(arr[i])){
                GameObject cube = GameObject.Instantiate(voxelPrefab, arr[i], Quaternion.identity);
                tmpCubeList.Add(cube);
            }
        }
        vBuffer.SetCounterValue(0);
    }
}

Voxel 表示のシェーダーを作ろうと思っていたんだけど色が取れない事に気づいてテンション下がったので普通に 1cm の Cube を沢山表示。ちょっと重かったのでトリガーを離したタイミング(ダイソンとイイねポーズ)で CombineMeshes しています。

ちょっと嵌った点など

  • スキャン面がコントローラに近いと手の Depth を拾ってしまうので斜めのステーでちょっと離した感じにしています。
  • 背景画像などカラー情報はプライバシー的な制約で取れない。(ARCoreとかスマホアプリでは取れるんだからオプトインのダイアログ出せばOKにして欲しいね…)
  • Depth 画像は VkImage なのかな? 可視化してみたかったんだけどフォーマットがよく分からなかったので断念。(ダンプするとサイズらしき 0x7d0 やフォーマットらしき 0x7c (VK_FORMAT_D16_UNORM) や何かへのポインタが見えるのでヘッダ構造が分かれば解析出来そう)
  • Depth との比較はお手軽にやるなら soft occlusion の occlusionValue が0.1付近とかでもほぼ同等の結果が得られる。(ちゃんと計算したほうがパリッとする印象はある)
  • GraphicsBuffer は SetRandomWriteTarget() しないとシェーダー側の RWStructuredBuffer に渡らない。
  • GraphicsBuffer は Target.Counter にして index 位置に書き込むと良い。
  • pixel shader で書いているので毎フレーム大量のバッファが必要。(私は 1024 x 1024 x Vector3 用意した)
  • IncrementCounter() はあるのにカウンタ値を読み出せない(?)のでインクリメントして戻り値がバッファ上限を超えていたらスキップした。

 

ケルヒャーなら頑固な現実も綺麗に落とせます

こちらは SceneAPI のテスト。(Twitter の動画は140秒までだそうでかなり雑な掃除になりましたが上の動画はもう少し丁寧に掃除しているロングバージョンです)
位置合わせがちょっと面倒になってきたので SceneAPI で部屋の形状に合わせて部屋のモデルを自動配置するようにしてみました。

SceneAPI で取得した OVRScenePlane を Depth Only のマテリアルで表示して遮蔽に使っています。ペイントされた部分は discard して Depth が書かれないので部屋のモデルが見えるという仕組み。

OVRScenePlane のメッシュは非同期で生成されるんだけど完了をコールバックやポーリングできそうな機能が見当たらない。今回は適当に待ったけどどうするのが正解なのだろう?

ところでこの壁の uv が座標値になっていてへんてこりんに思うのですが上手い使い方あるんですかね?思いつかなかったので私は uv を張り替えました。

ペイントやグリッド表示付きの壁などは自作のシェーダーを作りましたが URP でシェーダー書くの大変ですね。慣れてないのもあるけど builtin-RP ならコピペして修正とかも簡単なのに URP だとインクルードが多くてコピペしにくい。一部シェーダーグラフで書きましたがその方向なのかなぁ?




前の記事 次の記事
No Comment
コメントを追加
comment url