テッセレーションとディスプレースメントマップ

環境

Unity2021.2.18f1

概要

テッセレーションシェーダによるディスプレースメントマップです。

テッセレーションで距離により分割数を変えています。

普通に処理するとカメラを移動した時に、分割が変わる部分が見えてしまい見苦しい画面になってしまいました。

なので距離に応じてディスプレースの変位値を変えています。

また上に盛り上げるのではなくパララックスオクルージョンマップのように下にえぐるようにしています。

Shader "Custom/Tessellation"
{

    Properties
    {
        _Color("Color", color) = (1, 1, 1, 0)
        _MainTex("Base (RGB)", 2D) = "white" {}
        _DispTex("Disp Texture", 2D) = "gray" {}
        _NormalTex("Normal Texture", 2D) = "white" {}
        _MinDist("Min Distance", Range(0.1, 1000)) = 10
        _MaxDist("Max Distance", Range(0.1, 1000)) = 25
        _TessFactor("Tessellation", Range(1, 200)) = 100 //分割レベル
        _Displacement("Displacement", Range(0, 10.0)) = 1.0 //変位
        [KeywordEnum(integer, fractional_even, fractional_odd, pow2)] _Partitioning("Partitioning", int) = 0
        [KeywordEnum(Off, On)] _LerpDisplacementEnabled("Lerp Displacement Enabled", Float) = 1
    }
    SubShader
    {

        Tags
        {
            "RenderType" = "Opaque"
            "LightMode" = "ForwardBase"
        }

        Pass
        {
            CGPROGRAM
            #pragma shader_feature _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD _PARTITIONING_POW2
            #pragma shader_feature _ _LERPDISPLACEMENTENABLED_ON

            #pragma vertex vert
            #pragma fragment frag
            #pragma hull hull
            #pragma domain domain

            #include "Tessellation.cginc"
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            #define INPUT_PATCH_SIZE 3
            #define OUTPUT_PATCH_SIZE 3

            float _TessFactor;
            float _Displacement;
            float _MinDist;
            float _MaxDist;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float2 _MainTex_TexelSize;
            sampler2D _DispTex;
            float2 _DispTex_TexelSize;
            sampler2D _NormalTex;
            float2 _Normal_TexelSize;
            fixed4 _Color;

            struct appdata
            {
                float3 vertex : POSITION;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
                float2 texcoord : TEXCOORD0;
            };

            struct HsInput
            {
                float4 position : POS;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
                float2 texCoord : TEXCOORD;
            };

            struct HsControlPointOutput
            {
                float3 position : POS;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
                float2 texCoord : TEXCOORD;
            };

            struct HsConstantOutput
            {
                float tessFactor[3] : SV_TessFactor;
                float insideTessFactor : SV_InsideTessFactor;
            };

            struct DsOutput
            {
                float4 position : SV_Position;
                float2 uv : TEXCOORD0;
                float3 lightDirTS : TEXCOORD1;
                float3 viewDirTS : TEXCOORD2;
            };

            HsInput vert(appdata i)
            {
                HsInput o;
                o.position = float4(i.vertex, 1.0);
                o.normal = i.normal;
                o.tangent = i.tangent;
                o.texCoord = TRANSFORM_TEX(i.texcoord, _MainTex);
                return o;
            }

            [domain("tri")]
#if _PARTITIONING_INTEGER
            [partitioning("integer")]
#elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")]
#elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]
#else//elif _PARTITIONING_POW2
            [partitioning("pow2")]
#endif
            [outputtopology("triangle_cw")]
            [patchconstantfunc("hullConst")]
            [outputcontrolpoints(OUTPUT_PATCH_SIZE)]
            HsControlPointOutput hull(InputPatch<HsInput, INPUT_PATCH_SIZE> i, uint id : SV_OutputControlPointID)
            {
                HsControlPointOutput o = (HsControlPointOutput)0;
                o.position = i[id].position.xyz;
                o.normal = i[id].normal;
                o.tangent = i[id].tangent;
                o.texCoord = i[id].texCoord;
                return o;
            }

            HsConstantOutput hullConst(InputPatch<HsInput, INPUT_PATCH_SIZE> i)
            {
                HsConstantOutput o = (HsConstantOutput)0;

                float4 p0 = i[0].position;
                float4 p1 = i[1].position;
                float4 p2 = i[2].position;
                float4 tessFactor = UnityDistanceBasedTess(p0, p1, p2, _MinDist, _MaxDist, _TessFactor);

                o.tessFactor[0] = tessFactor.x;
                o.tessFactor[1] = tessFactor.y;
                o.tessFactor[2] = tessFactor.z;
                o.insideTessFactor = tessFactor.w;

                return o;
            }

            [domain("tri")]
            DsOutput domain(HsConstantOutput hsConst, const OutputPatch<HsControlPointOutput, OUTPUT_PATCH_SIZE> i, float3 bary : SV_DomainLocation)
            {
                DsOutput o = (DsOutput)0;

                float3 position =
                    bary.x * i[0].position +
                    bary.y * i[1].position +
                    bary.z * i[2].position;

                float3 normal = normalize(
                    bary.x * i[0].normal +
                    bary.y * i[1].normal +
                    bary.z * i[2].normal);

                float3 tangent = normalize(
                    bary.x * i[0].tangent +
                    bary.y * i[1].tangent +
                    bary.z * i[2].tangent);

                o.uv =
                    bary.x * i[0].texCoord +
                    bary.y * i[1].texCoord +
                    bary.z * i[2].texCoord;

#if _LERPDISPLACEMENTENABLED_ON
                float3 positionVS = UnityObjectToViewPos(float4(position, 1));
                float dispPower = lerp(_Displacement, 0, saturate(-positionVS.z / _MaxDist));
                position = (dispPower > 0) ? position + -normal * (1.0 - tex2Dlod(_DispTex, float4(o.uv, 0, 0)).r) * dispPower : position;
#else
                float disp = tex2Dlod(_DispTex, float4(o.uv, 0, 0)).r * _Displacement;
                position.xyz += normal * disp;
#endif
                o.position = UnityObjectToClipPos(float4(position, 1.0));

                float3 lightDirOS = ObjSpaceLightDir(float4(position, 1));
                float3 viewDirOS = ObjSpaceViewDir(float4(position, 1));

                float3 binormal = cross(normal, tangent.xyz);
                float3x3 rotation = float3x3(tangent.xyz, binormal, normal);
                o.lightDirTS = mul(rotation, lightDirOS);
                o.viewDirTS = mul(rotation, viewDirOS);
                return o;
            }

            fixed4 frag(DsOutput i) : SV_Target
            {
                float3 normalTS = UnpackNormal(tex2D(_NormalTex, i.uv));
                float3 lightDirTS = normalize(i.lightDirTS);
                float3 viewDirTS = normalize(i.viewDirTS);
                float3 halfDirTS = normalize(lightDirTS + viewDirTS);
                float diffuse = dot(normalTS, lightDirTS) * 0.5 + 0.5;
                float speculer = pow(saturate(dot(normalTS, halfDirTS) * 0.5 + 0.5), 40);
                return diffuse * tex2D(_MainTex, i.uv) * _LightColor0 + speculer * _LightColor0;
            }
            ENDCG
        }
    }
}