夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

MixedRealityToolkit GraphicsTools StandardShaderのVertex Extrusionの機能を読み解く

本日はMixedRealityToolkit GraphicsToolsStandardShaderのVertex Extrusionの機能を読み解いていきます。

〇HoloLens 2022年アドベントカレンダー

 HoloLens 2022年アドベントカレンダーはQiita上で私の師であるがち本さんが開催している企画です。

 クリスマスまで毎日記事を埋めていくことが目的で本日は15日目の記事になります。

qiita.com

〇Vertex Extrusionとは?

Vertex Extrusion(頂点押し出し)は面の法線方向に頂点を展開する機能で、滑らかなメッシュでは太るようにエッジをつぶしながら拡大していきます。

デフォルトのメッシュ

Vertex Extrusionを使用したメッシュ

あくまで見た目上の形状が変わります

真球などトポロジーが整っているオブジェクトの場合は単にスケールの縮小拡大が起こります。

〇プロパティ

Graphics Tools/StandardシェーダーのRendering optionの枠内に[Toggle]でチェックボックスとして使用されShaderFeatureに使用する_VertexExtrusionと実際のパラメータであるVertex Extrusion Value、オプションとしての_VertexExtrusionSmoothNormalsの3つがVertex Extrusionのパラメータになります。

Shader "Graphics Tools/Standard"
{
    Properties
    {
        // Main maps.
   ・・・

        // Rendering options.
        ・・・
        [Toggle(_VERTEX_EXTRUSION)] _VertexExtrusion("Vertex Extrusion", Float) = 0.0
        _VertexExtrusionValue("Vertex Extrusion Value", Float) = 0.0
        [Toggle(_VERTEX_EXTRUSION_SMOOTH_NORMALS)] _VertexExtrusionSmoothNormals("Vertex Extrusion Smooth Normals", Float) = 0.0

   ・・・

MRGTStandardシェーダーでは使用する機能のみに処理が有効になりコストを支払うというUberShaderを採用しています。

マテリアルから [Toggle(VERTEX_EXTRUSION)] VertexExtrusionを有効にした場合

 #if defined(_VERTEX_EXTRUSION)~#elseが有効化され、条件コンパイルが有効になります。

/// <summary>
/// Features.
/// </summary>

・・・
#pragma shader_feature_local _VERTEX_EXTRUSION
#pragma shader_feature_local_vertex _VERTEX_EXTRUSION_SMOOTH_NORMALS
 ・・・

VertexExtrusionはその名の通り頂点での処理になりすべての処理が頂点シェーダー上で行われています。

〇頂点シェーダーの処理

Vertex Extrusionの機能に関してのみ抽出した頂点シェーダーが以下になります。

Varyings VertexStage(Attributes input)
{
    Varyings output = (Varyings)0;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    float4 vertexPosition = input.vertex;

#if defined(_WORLD_POSITION) || defined(_VERTEX_EXTRUSION)
    float3 worldVertexPosition = mul(UNITY_MATRIX_M, vertexPosition).xyz;
#endif

#if defined(_SCALE)
    output.scale = GTGetWorldScale();
    float canvasScale = 1.0;
#endif

    half3 localNormal = input.normal;

#if defined(_NORMAL) || defined(_VERTEX_EXTRUSION)
#if defined(_URP)
    half3 worldNormal = TransformObjectToWorldNormal(localNormal);
#else
    half3 worldNormal = UnityObjectToWorldNormal(localNormal);
#endif
#endif

#if defined(_VERTEX_EXTRUSION)

    worldVertexPosition += worldNormal * _VertexExtrusionValue;

#if defined(_URP)
    vertexPosition = mul(UNITY_MATRIX_I_M, float4(worldVertexPosition, 1.0));
#else
    vertexPosition = mul(unity_WorldToObject, float4(worldVertexPosition, 1.0));
#endif
#endif

#if defined(_URP)
    output.position = TransformObjectToHClip(vertexPosition.xyz);
#else
    output.position = UnityObjectToClipPos(vertexPosition);
#endif
    output.color = UNITY_ACCESS_INSTANCED_PROP(PerMaterialInstanced, _Color);

    return output;
}

ざっくりと読み解いていきます

  float3 worldVertexPosition = mul(UNITY_MATRIX_M, vertexPosition).xyz;

mul(UNITY_MATRIX_M, vertexPosition)ではモデル行列を使用して頂点の位置をUnityのワールド座標に代入しています。

Vertex Extrusionのコア処理は次のものになります。

#if defined(_VERTEX_EXTRUSION)
    worldVertexPosition += worldNormal * _VertexExtrusionValue;
#if defined(_URP)
    vertexPosition = mul(UNITY_MATRIX_I_M, float4(worldVertexPosition, 1.0));
#else
    vertexPosition = mul(unity_WorldToObject, float4(worldVertexPosition, 1.0));
#endif
#endif

worldVertexPositionは事前に取得していたワールド座標での法線情報とプロパティで設定する_VertexExtrusionValueの値を乗算して足し合わせています。

つまりworldVertexPositionは通常のメッシュの座標に法線方向へのベクトルを追加しています。これがVertex Extrusionの由来です。

URPとビルドインで処理が分かれていますが、使用しているビルドイン関数が異なるだけでどちらも行っていることはvertexPositionにTransformObjectToClip()を行っています。つまりUnity座標に頂点を再配置しています。

これだけの簡単な処理ですがこれでメッシュの法線方向への展開が可能になっています。