本日はHoloLens表現枠です。
昨日に引き続きHoloLensで床や壁に穴をあけていきます。
〇HoloLens 2022年アドベントカレンダー
HoloLens 2022年アドベントカレンダーはQiita上で私の師であるがち本さんが開催している企画です。
クリスマスまで毎日記事を埋めていくことが目的で本日は11日目の記事になります。
〇SpatialClippingShader
今回はSpatialMeshのマテリアルにデフォルトで使用されているシェーダーを改造して『ある座標からの距離に応じてクリッピングしてSpatialMeshを一部非表示にする』というシェーダーを作ってきます。
満たすべき要件は次のようになります
・Unity上の座標に対してクリッピング表現を行う
・マテリアルのパラメータとしてUnityの座標を渡すことができる。
・HandTrackingの座標をマテリアルパラメータに渡す。
要素自体はこれまでの記事でやっていることではあるので今回は概要だけ紹介します。
float4 col = float4(0.0, 0.0, 0.0, 0.0);
HoloLensでは黒を描画することはできません。 これは光学シースルー特有の現象で、黒=実機では透明になります。 このためSpatialMeshに黒を使用することでメッシュ自体が実機では描画されないように見えるようにしています。
本来はColorMask 0を使用することで描画を行わないことを実現しキャプチャ時に描画されないようになっていますが、今回は外しています。
これによってキャプチャ時に黒く描画されるようになってしまっています。こちらは今後の改善点です。
クリッピング表現の計算はdistance関数を使用することで引数同士の距離を求めることができます。
float val = distance(_HandPos, input.worldPos)*; col.a = col.a +val; col.a = (_AlphaThreshold<col.a)? 1:0;
ここではC#側から代入する変数_HandPosと描画されるピクセルの距離を求めています。
この値をおおもとのピクセルのα値に足しています。
この値と_AlphaThresholdを比較し距離によるクリッピング表現を実装しています。
C#側のスクリプトでSpatialMeshのシェーダーの_HandPosに自身のオブジェクトの座標を渡すようにしています。
void Update() { _targetMaterial.SetVector("_HandPos",this.transform.position); }
このスクリプトをSolverHandlerおよびHandConstraintコンポーネントをアタッチしてHandTrackingをトラッキングできるようにしたオブジェクトにアタッチします。
以上で機能の完成です。
〇実機で確認
ステンシルを使用した場合と異なりちゃんとSpatialMeshの裏にオブジェクトが存在するので、こちらの方がより良いアプローチかもしれません。
〇コード一覧
Shader "Unlit/Spatialhole" { Properties { _MainTex ("Texture", 2D) = "white" {} _HandPos ("HandPosition",vector) =(0, 0, 0) _AlphaThreshold("AlphaThreshold",float)=0.5 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry-1" } AlphaToMask On Blend SrcAlpha OneMinusSrcAlpha BlendOp Add ZWrite On ZTest LEqual Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct appdata { float4 vertex : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 position : SV_POSITION; float3 worldPos : TEXCOORD1; UNITY_VERTEX_OUTPUT_STEREO }; float3 _HandPos; float _AlphaThreshold; v2f vert(appdata input) { v2f output; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); output.position = TransformObjectToHClip(input.vertex); output.worldPos = mul(unity_ObjectToWorld, input.vertex).xyz; return output; } float4 frag(v2f input) : SV_Target { float4 col = float4(0.0, 0.0, 0.0, 0.0); float val = distance(_HandPos, input.worldPos)*-1; col.a = col.a -val; col.a = (_AlphaThreshold<col.a)? 1:0; return col; } ENDHLSL } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SpatialHole : MonoBehaviour { [SerializeField] private Material _targetMaterial; // Update is called once per frame void Update() { _targetMaterial.SetVector("_HandPos",this.transform.position); } }