夜風のMixedReality

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

MRTK StanderdShaderを読み解く その17 RoundCorners

本日はMRTKシェーダー枠です。

今回はRoundCornerを読み解きます。

〇RoundCorner

[RoundCorner]は一言で表すとメッシュのエッジを落とすシェーダーの機能です。

[RoundCorner]の機能を用いることで正方形の板のオブジェクトを円の見た目に変えることなどできます。

f:id:Holomoto-Sumire:20201026090235g:plain

〇フラグメントシェーダー

RoundCornerはピクセル色の描画に当たるフラグメントシェーダーで処理されます。

    #if defined(_ROUND_CORNERS)
                    float2 halfScale = i.scale.xy * 0.5;
                    float2 roundCornerPosition = distanceToEdge * halfScale;
                    fixed currentCornerRadius; 
                    currentCornerRadius = _RoundCornerRadius;
                    float cornerCircleRadius = saturate(max(currentCornerRadius - _RoundCornerMargin, 0.01)) * i.scale.z;
                    float2 cornerCircleDistance = halfScale - (_RoundCornerMargin * i.scale.z) - cornerCircleRadius;
                    float roundCornerClip = RoundCorners(roundCornerPosition, cornerCircleDistance, cornerCircleRadius);
    #endif

RoundCornerを使用するためにRoundCorner用の各変数を作成します。

halfScaleはv2f構造体のscale=TEXCOORD3(テクスチャ画像)の半分となった値が入ります。

roundCornerPositionには直前で定義されたdistanceToEdgeにhalfScaleが積算されています。

 #if defined(_DISTANCE_TO_EDGE)
                    fixed2 distanceToEdge;
                    distanceToEdge.x = abs(i.uv.x - 0.5) * 2.0;
                    distanceToEdge.y = abs(i.uv.y - 0.5) * 2.0;
    #endif

distanceToEdgeはV2fのuv(テクスチャ座標)の各成分から0.5を引いた絶対値を倍にしています。 

currentCornerRadiusはマテリアル側で設定される_RoundCornerRadius(0~0.5)がそのまま代入されます。

この_RoundCornerRadiusはMixedRealityStandardShaderGUIによって[Unit Radius]として表示されます。

f:id:Holomoto-Sumire:20201026092455j:plain

cornerCircleRadiusはcurrentCornerRadiusからMaterialで設定できる_RoundCornerMarginの値を引いた値が0.01より大きければその値、小さければ0.01が1までの値の間に収めた値にv2f構造体のscaleのz成分が積算されます。

currentCornerRadiusはRoundCornerRadiusなのでRoundCornerRadiusと_RoundCornerMarginの差になります。

cornerCircleDistanceにはhalfScaleの値から_RoundCornerMarginにv2f構造体のscale.zを積算した値にcornerCircleRadiusを引いた値が代入されます。

roundCornerClipには RoundCorners(roundCornerPosition, cornerCircleDistance, cornerCircleRadius)が代入されます。

このRoundCorners()は次のようになっています。

  inline fixed RoundCorners(float2 position, float2 cornerCircleDistance, float cornerCircleRadius)
                {
                    return (PointVsRoundedBox(position, cornerCircleDistance, cornerCircleRadius) < 0.0);
                }
               inline float PointVsRoundedBox(float2 position, float2 cornerCircleDistance, float cornerCircleRadius)
                {
                    return length(max(abs(position) - cornerCircleDistance, 0.0)) - cornerCircleRadius;
                }

PointVsRoundedBoxの返り値はroundCornerPositionの絶対値からcornerCircleDistanceを引いた値の正の値からcornerCircleRadiusを引いた値が返されます。これがroundCornerClipつまりクリッピングされる値になります。

 #if defined(_ROUND_CORNERS)
                    albedo *= roundCornerClip;
                    pointToLight *= roundCornerClip;
    #endif

    #if defined(_ALPHA_CLIP)
   
                    clip(albedo.a - _Cutoff);
                    albedo.a = 1.0;
    #endif

albedoの値にこのroundCornerClipが積算されこれがRoundCornerの値を最終的な連でリング結果に反映させる処理になります。

以上でRoundCornerでした。 今回はざっくりと呼んでいたのでまた改めてじっくり読み解きます。

〇shader

今回作成したシェーダーは次の通りです。

Shader "Custom/MRTKRoundCorner"
{
    Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("Albedo", 2D) = "white" {}

        _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5
   

            // Rendering options.
            [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
            
        

            // Fluent options.
         
            [Toggle(_ROUND_CORNERS)] _RoundCorners("Round Corners", Float) = 0.0
            _RoundCornerRadius("Round Corner Radius", Range(0.0, 0.5)) = 0.25
            _RoundCornerMargin("Round Corner Margin", Range(0.0, 0.5)) = 0.01
          
                // Advanced options.
                [Enum(RenderingMode)] _Mode("Rendering Mode", Float) = 0                                     // "Opaque"
                [Enum(CustomRenderingMode)] _CustomMode("Mode", Float) = 0                                   // "Opaque"
                [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend("Source Blend", Float) = 1                 // "One"
                [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend("Destination Blend", Float) = 0            // "Zero"
                [Enum(UnityEngine.Rendering.BlendOp)] _BlendOp("Blend Operation", Float) = 0                 // "Add"
                [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("Depth Test", Float) = 4                // "LessEqual"
                [Enum(DepthWrite)] _ZWrite("Depth Write", Float) = 1                                         // "On"
                _ZOffsetFactor("Depth Offset Factor", Float) = 0                                             // "Zero"
                _ZOffsetUnits("Depth Offset Units", Float) = 0                                               // "Zero"
                [Enum(UnityEngine.Rendering.ColorWriteMask)] _ColorWriteMask("Color Write Mask", Float) = 15 // "All"
                [Enum(UnityEngine.Rendering.CullMode)] _CullMode("Cull Mode", Float) = 2                     // "Back"
                _RenderQueueOverride("Render Queue Override", Range(-1.0, 5000)) = -1
                [Toggle(_INSTANCED_COLOR)] _InstancedColor("Instanced Color", Float) = 0.0
                [Toggle(_IGNORE_Z_SCALE)] _IgnoreZScale("Ignore Z Scale", Float) = 0.0
                [Toggle(_STENCIL)] _Stencil("Enable Stencil Testing", Float) = 0.0
                _StencilReference("Stencil Reference", Range(0, 255)) = 0
                [Enum(UnityEngine.Rendering.CompareFunction)]_StencilComparison("Stencil Comparison", Int) = 0
                [Enum(UnityEngine.Rendering.StencilOp)]_StencilOperation("Stencil Operation", Int) = 0
    }

        SubShader
            {
                Pass
                {
                    Name "Main"
                    Tags{ "RenderType" = "Opaque" "LightMode" = "ForwardBase" }

                    Blend[_SrcBlend][_DstBlend]
                    BlendOp[_BlendOp]

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag

                    #pragma shader_feature _DIRECTIONAL_LIGHT
                    #pragma shader_feature _ROUND_CORNERS       
                    #include "UnityCG.cginc"
                    #include "UnityStandardConfig.cginc"
                    #include "UnityStandardUtils.cginc"
                    #include "MixedRealityShaderUtils.cginc"

                // This define will get commented in by the UpgradeShaderForLightweightRenderPipeline method.
                //#define _LIGHTWEIGHT_RENDER_PIPELINE

    #if  defined(_DIRECTIONAL_LIGHT)
                #define _NORMAL
    #else
                #undef _NORMAL
    #endif

    #if defined(_NORMAL) || defined(_PROXIMITY_LIGHT)
                #define _WORLD_POSITION
    #else
                #undef _WORLD_POSITION
    #endif

    #if  defined(_ROUND_CORNERS)
                #define _ALPHA_CLIP
    #else
                #undef _ALPHA_CLIP
    #endif

    #if  defined(_ROUND_CORNERS) 
                #define _SCALE
    #else
                #undef _SCALE
    #endif

    #if defined(_DIRECTIONAL_LIGHT) 
                #define _FRESNEL
    #else
                #undef _FRESNEL
    #endif

    #if defined(_ROUND_CORNERS) 
                #define _DISTANCE_TO_EDGE
    #else
                #undef _DISTANCE_TO_EDGE
    #endif

    #if !defined(_DISABLE_ALBEDO_MAP)  
                #define _UV
    #else
                #undef _UV
    #endif

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    // The default UV channel used for texturing.
                    float2 uv : TEXCOORD0;
  
                    // Used for smooth normal data (or UGUI scaling data).
                    float4 uv2 : TEXCOORD2;
                    // Used for UGUI scaling data.
                    float2 uv3 : TEXCOORD3;
 
                    fixed3 normal : NORMAL;
  
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };

                struct v2f
                {
                    float4 position : SV_POSITION;
   
    #if defined(_UV)
                    float2 uv : TEXCOORD0;
    #endif
   

    #if defined(_WORLD_POSITION)
    
                    float3 worldPosition : TEXCOORD2;
    #endif
    #if defined(_SCALE)
                    float3 scale : TEXCOORD3;
    #endif
    #if defined(_NORMAL)
                    fixed3 worldNormal : COLOR3;
    #endif
                    UNITY_VERTEX_OUTPUT_STEREO
                };

  
                fixed4 _Color;

                sampler2D _MainTex;
                fixed4 _MainTex_ST;

    #if defined(_ALPHA_CLIP)
                fixed _Cutoff;
    #endif
                fixed _Metallic;
                fixed _Smoothness;
    #if defined(_DIRECTIONAL_LIGHT)
  
                fixed4 _LightColor0;
    #endif

    #if defined(_REFRACTION)
                fixed _RefractiveIndex;
    #endif
 
      #if defined(_ROUND_CORNERS)
                fixed _RoundCornerRadius;
                fixed _RoundCornerMargin;
    #endif

    #if defined(_ROUND_CORNERS) 
                fixed _EdgeSmoothingValue;
    #endif

    #if defined(_DIRECTIONAL_LIGHT)
                static const fixed _MinMetallicLightContribution = 0.7;
                static const fixed _IblContribution = 0.1;
    #endif


    #if defined(_FRESNEL)
                static const float _FresnelPower = 8.0;
    #endif


    #if defined(_ROUND_CORNERS)
                inline float PointVsRoundedBox(float2 position, float2 cornerCircleDistance, float cornerCircleRadius)
                {
                    return length(max(abs(position) - cornerCircleDistance, 0.0)) - cornerCircleRadius;
                }

                inline fixed RoundCornersSmooth(float2 position, float2 cornerCircleDistance, float cornerCircleRadius)
                {
                    return smoothstep(1.0, 0.0, PointVsRoundedBox(position, cornerCircleDistance, cornerCircleRadius) / _EdgeSmoothingValue);
                }

                inline fixed RoundCorners(float2 position, float2 cornerCircleDistance, float cornerCircleRadius)
                {
  
                    return (PointVsRoundedBox(position, cornerCircleDistance, cornerCircleRadius) < 0.0);

                }
    #endif


                v2f vert(appdata_t v)
                {
                    v2f o;
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                    float4 vertexPosition = v.vertex;

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

    #if defined(_SCALE)
                    o.scale.x = length(mul(unity_ObjectToWorld, float4(1.0, 0.0, 0.0, 0.0)));
                    o.scale.y = length(mul(unity_ObjectToWorld, float4(0.0, 1.0, 0.0, 0.0)));
         o.scale.z = length(mul(unity_ObjectToWorld, float4(0.0, 0.0, 1.0, 0.0)));

   
    #endif

                    fixed3 localNormal = v.normal;

    #if defined(_NORMAL) 
                    fixed3 worldNormal = UnityObjectToWorldNormal(localNormal);
    #endif


                    o.position = UnityObjectToClipPos(vertexPosition);

    #if defined(_WORLD_POSITION)
                    o.worldPosition.xyz = worldVertexPosition;
    #endif


    #if  defined(_ROUND_CORNERS)
                    o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

                    float minScale = min(min(o.scale.x, o.scale.y), o.scale.z);

                    if (abs(localNormal.x) == 1.0) // Y,Z plane.
                    {
                        o.scale.x = o.scale.z;
                        o.scale.y = o.scale.y;

                    }
                    else if (abs(localNormal.y) == 1.0) // X,Z plane.
                    {
                        o.scale.x = o.scale.x;
                        o.scale.y = o.scale.z;

  
                    }
                    else  // X,Y plane.
                    {
                        o.scale.x = o.scale.x;
                        o.scale.y = o.scale.y;

  
                    }

                    o.scale.z = minScale;

   
    #elif defined(_UV)
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    #endif

    #if defined(_NORMAL)
  
    #if defined(_NORMAL_MAP)
                    fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                    fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                    fixed3 worldBitangent = cross(worldNormal, worldTangent) * tangentSign;
                    o.tangentX = fixed3(worldTangent.x, worldBitangent.x, worldNormal.x);
                    o.tangentY = fixed3(worldTangent.y, worldBitangent.y, worldNormal.y);
                    o.tangentZ = fixed3(worldTangent.z, worldBitangent.z, worldNormal.z);
    #else
                    o.worldNormal = worldNormal;
    #endif
    #endif

                    return o;
                }

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {
 



  
                    fixed4 albedo = tex2D(_MainTex, i.uv);
    #if defined(_DISTANCE_TO_EDGE)
                    fixed2 distanceToEdge;
                    distanceToEdge.x = abs(i.uv.x - 0.5) * 2.0;
                    distanceToEdge.y = abs(i.uv.y - 0.5) * 2.0;
    #endif

                    // Rounded corner clipping.
    #if defined(_ROUND_CORNERS)
                    float2 halfScale = i.scale.xy * 0.5;
                    float2 roundCornerPosition = distanceToEdge * halfScale;

                    fixed currentCornerRadius;

 
                    currentCornerRadius = _RoundCornerRadius;


                    float cornerCircleRadius = saturate(max(currentCornerRadius - _RoundCornerMargin, 0.01)) * i.scale.z;

                    float2 cornerCircleDistance = halfScale - (_RoundCornerMargin * i.scale.z) - cornerCircleRadius;

                    float roundCornerClip = RoundCorners(roundCornerPosition, cornerCircleDistance, cornerCircleRadius);
    #endif

   
                    albedo *= _Color;
   
                    // Normal calculation.
    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));
  
                    fixed3 worldNormal;

    #if defined(_NORMAL_MAP)
 
                    fixed3 tangentNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv), _NormalMapScale);
                    worldNormal.x = dot(i.tangentX, tangentNormal);
                    worldNormal.y = dot(i.tangentY, tangentNormal);
                    worldNormal.z = dot(i.tangentZ, tangentNormal);
                    worldNormal = normalize(worldNormal) * facing;

    #else
                    worldNormal = normalize(i.worldNormal) * facing;
    #endif
    #endif

                    fixed pointToLight = 1.0;
                    fixed3 fluentLightColor = fixed3(0.0, 0.0, 0.0);


    #if defined(_ROUND_CORNERS)
                    albedo *= roundCornerClip;
                    pointToLight *= roundCornerClip;
    #endif

    #if defined(_ALPHA_CLIP)
   
                    clip(albedo.a - _Cutoff);
                    albedo.a = 1.0;
    #endif

                    // Blinn phong lighting.
    #if defined(_DIRECTIONAL_LIGHT)
   
                    float4 directionalLightDirection = _WorldSpaceLightPos0;
                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection));
                    fixed specular = 0.0;
    #endif

                    // Image based lighting (attempt to mimic the Standard shader).
   
                    fixed3 ibl = unity_IndirectSpecColor.rgb;
   
                    // Fresnel lighting.
    #if defined(_FRESNEL)
                    fixed fresnel = 1.0 - saturate(abs(dot(worldViewDir, worldNormal)));
  
                    fixed3 fresnelColor = unity_IndirectSpecColor.rgb * (pow(fresnel, _FresnelPower) * max(_Smoothness, 0.5));
    #endif
                    // Final lighting mix.
                    fixed4 output = albedo;
  
                    fixed3 ambient = glstate_lightmodel_ambient + fixed3(0.25, 0.25, 0.25);

                    fixed minProperty = min(_Smoothness, _Metallic);
    #if defined(_DIRECTIONAL_LIGHT)
                    fixed oneMinusMetallic = (1.0 - _Metallic);
                    output.rgb = lerp(output.rgb, ibl, minProperty);
 
                    fixed3 directionalLightColor = _LightColor0.rgb;

                    output.rgb *= lerp((ambient + directionalLightColor * diffuse + directionalLightColor * specular) * max(oneMinusMetallic, _MinMetallicLightContribution), albedo, minProperty);
                    output.rgb += (directionalLightColor * albedo * specular) + (directionalLightColor * specular * _Smoothness);
                    output.rgb += ibl * oneMinusMetallic * _IblContribution;

    #endif

    #if defined(_FRESNEL)
                    output.rgb += fresnelColor * (1.0 - minProperty);   
    #endif
                    return output;
                }
                ENDCG
            }
            }
               Fallback "Hidden/InternalErrorShader"
                      
}