夜風のMixedReality

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

ゼロから始めるUnityShader開発 第五章 ワールド座標の取得

本日はShader勉強枠です。

これまでで書いたシェーダーではClipPosと呼ばれるUnityのカメラに対してオブジェクトがどう映っているかという座標軸で描画計算を行っていました。

 今回は描画対象のオブジェクトがUnityのワールド座標軸に対してどのようにあるかの関係で描画処理を行っていきます。

〇Shaderにおける座標

Unityのシェーダーでは与えられた頂点を変換し描画を行っていました。

〇クリッピングスペース(Clipping)座標

 クリッピングスペースとは日本語に直すと表示範囲という意味になります。

 冒頭でも少し記述していますがこの表示範囲の表示とは最終的な見た目を画面に映し出すカメラを意味しています。

 UnityCG.cgincではUnityObjectToClipPos("頂点")を使用し、URP版のCore.hlslではTransformToClipPos("頂点")が使用されます。

 この計算によって与えられた頂点が表示範囲(カメラの範囲=視界)のどこに存在するのかという変換が行われています。

 この変換は通常のオブジェクトの描画に使用される最もベーシックなものになります。

〇Unityワールド座標の取得と座標で処理を変える。

ここからは本題であるUnityのワールド座標に対しての位置関係で描画を変えていきます。

Shaderはピクセル単位での処理を行っているためUnityでの軸を取得することで描画されるピクセルのUnity座標を取得して処理を変えることができます。

Core.hlsl では*TransformObjectToWorld()**を使用することでUnityのワールド座標を取得することができます。

                o.worldPos= TransformObjectToWorld( v.vertex.xyz);

今回の場合この情報を使用してフラグメントシェーダーで処理を変えるためv2f構造体にo.worldPosという値を定義しています。

 この際にworldPosのセマンティクスにはTEXCOORD3を使用しています。

    struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD3;
            };

これは頂点シェーダー内でTransformObjectToWorldによって計算された頂点はすでに位置を示すものではなくただの値のためPOSITIONではなくTEXCOORDを使用しています。

またTEXCOORD1やTEXCOORD2は筆者の場合よく2つ目のUVの処理などに使用しているためここではTEXCOORD3を使用しました。

こうしてて頂点シェーダーで処理されたデータを使用してフラグメントシェーダーで処理を行います。

今回はY座標が0以下のピクセルは黒くなる(Float4=0,0,0,0)ような処理を記述しています。

  float4 frag (v2f i) : SV_Target
            {
                // sample the texture
                float4 col = tex2D(_MainTex, i.uv);
                col *= ( i.worldPos.y <0 )? 0:1; 
                return col;
            }

余談ですが条件演算子を用いています。

 Shaderでは条件分岐を用いることもできますが、GPU上で実行されているという特性上if分を使用した場合にパフォーマンスが著しく落ちることがあります。

 Shaderにおけるif文の扱いに関しては筆者もよくブログの記事参考にさせていただいている次のLIGHT11さんの記事が詳しいです。

light11.hatenadiary.com

 上記の記事によると処理の内部が軽い処理の場合は条件演算子を用いた場合の方が処理が軽くなるようです。

 実際に筆者の経験上多くのシェーダーでif文よりも条件演算子が用いられている場合が多いです。


  話題がそれてしまいましたが、ここではi.worldPos.y =ピクセルごとのUnityにおける座標が0以下の場合=Unityのワールド座標でY軸0以下にある場合に出力であるColにゼロをかけ合わせています。

個のシェーダーを実行すると次のような結果を得られます。

このようにオブジェクトをY軸で移動してY座標が0以下にあるピクセルが黒くなるシェーダーが実装できました。

本日は以上です。

〇コード

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }//Shader属性
        LOD 100

        Pass//Shaderのパス
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
               o.vertex = TransformObjectToHClip(v.vertex);
                o.worldPos = TransformObjectToWorld( v.vertex.xyz);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                // sample the texture
                float4 col = tex2D(_MainTex, i.uv);
                col *= ( i.worldPos.y <0 )? 0:1; 
                return col;
            }
            ENDHLSL
        }
    }
}