視差遮蔽マッピング

環境

Unity2021.3.4f1

概要

視差遮蔽マッピング(ParallaxOcclusionMapping)です。

Depthを書き換えないシンプルなものです。

Shader "Custom/ParallaxOcclusionMapping"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
        _NormalTex("NormalTex",2D) = "white"{}
        _HeightTex("DepthTex", 2D) = "white" {}
        _Height ("Height", Range(0.01, 0.3)) = 0.3
        _MinSamples ("MinSamples", int) = 16
        _MaxSamples ("MaxSamples", int) = 128
        _SpecularColor("Specular Color", Color) = (1,1,1,1)
        _SpecularExp ("SpecularExp", Range(32, 256)) = 64
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    #include "Lighting.cginc"

    struct appdata
    {
        float4 position : POSITION;
        float2 texCoord : TEXCOORD0;
        float3 normal : NORMAL;
        float4 tangent : TANGENT;
    };
    struct v2f
    {
        float4 position : POSITION;
        float2 texCoord : TEXCOORD0;
        float3 lightTS : TEXCOORD1;
        float3 viewTS : TEXCOORD2;
        float2 parallaxOffsetTS : TEXCOORD3;
        float3 normalWS : TEXCOORD4;
        float3 viewWS : TEXCOORD5;
    };
    sampler2D _MainTex;
    float4 _MainTex_ST;
    sampler2D _NormalTex;
    sampler2D _HeightTex;
    float4 _HeightTex_TexelSize;
    float _Height;
    int _MinSamples;
    int _MaxSamples;
    float4 _SpecularColor;
    float _SpecularExp;

    v2f vert(appdata v)
    {
        v2f o = (v2f)0;

        o.position = UnityObjectToClipPos(v.position);
        o.texCoord = TRANSFORM_TEX(v.texCoord, _MainTex);
        o.normalWS = UnityObjectToWorldNormal(v.normal);
        o.viewWS = WorldSpaceViewDir(v.position);

        float3 lightWS = WorldSpaceLightDir(v.position);
        float3 tangentWS = UnityObjectToWorldDir(v.tangent.xyz);
        float3 binormalWS = cross(o.normalWS, tangentWS) * v.tangent.w;
        float3x3 worldToTangent = float3x3(tangentWS, binormalWS, o.normalWS);
        o.lightTS = mul(worldToTangent, lightWS);
        o.viewTS = mul(worldToTangent, o.viewWS);

        float2 parallaxDirection = normalize(o.viewTS.xy);
        float len = length(o.viewTS);
        float parallaxLength = sqrt(len * len - o.viewTS.z * o.viewTS.z) / o.viewTS.z;
        o.parallaxOffsetTS = parallaxDirection * parallaxLength;
        o.parallaxOffsetTS *= _Height;
        return o;
    }

    fixed4 frag(v2f i) : COLOR0
    {
        float3 viewTS = normalize(i.viewTS);
        float3 viewWS = normalize(i.viewWS);
        float3 lightTS = normalize(i.lightTS);
        float3 normalWS = normalize(i.normalWS);

        float2 dx, dy;
        dx = ddx(i.texCoord);
        dy = ddy(i.texCoord);

        float2 texSample = i.texCoord;
        int numSteps = (int)lerp(_MaxSamples, _MinSamples, dot(viewWS, normalWS));
        float currHeight = 0.0;
        float stepSize = 1.0 / (float)numSteps;
        float prevHeight = 1.0;
        float2 texOffsetPerStep = stepSize * i.parallaxOffsetTS;
        float2 texCurrentOffset = i.texCoord;
        float currentBound = 1.0;

        for(int step = 0; step < numSteps && currHeight < currentBound; step++)
        {
            texCurrentOffset -= texOffsetPerStep;
            prevHeight = currHeight;
            currHeight = tex2Dgrad(_HeightTex, texCurrentOffset, dx, dy).r;
            currentBound -= stepSize;
        }
        float2 pt1 = float2(currentBound, currHeight);
        float2 pt2 = float2(currentBound + stepSize, prevHeight);

        float delta2 = pt2.x - pt2.y;
        float delta1 = pt1.x - pt1.y;
        float parallaxAmount = (pt1.x * delta2 - pt2.x * delta1) / (delta2 - delta1);
        float2 parallaxOffset = i.parallaxOffsetTS * (1 - parallaxAmount);
        float2 texSampleBase = i.texCoord - parallaxOffset;
        texSample = texSampleBase;

        fixed4 baseColor = tex2Dgrad(_MainTex, texSample, dx, dy);
        float3 normalTS = normalize(UnpackNormal(tex2Dgrad(_NormalTex, texSample, dx, dy)));
        fixed3 diffuse = _LightColor0.rgb * baseColor.rgb * saturate(dot(normalTS, lightTS));
        fixed3 halfDirTS = normalize(lightTS + viewTS);
        fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(halfDirTS, normalTS)), _SpecularExp);
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * baseColor;
        fixed4 finalColor = fixed4(ambient + diffuse + specular, 1.0);
        return finalColor;
    }

    ENDCG

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    }
}

感想

処理負荷が高いのでディスプレースメントマッピングのほうが良いでしょう。

参考

https://advances.realtimerendering.com/s2006/Chapter5-Parallax_Occlusion_Mapping_for_detailed_surface_rendering.pdf

http://maverickproj.web.fc2.com/pg97.html

DepthOffset付き

こちらはDepthを書き換えるタイプのものです。

Shader "Custom/ParallaxOcclusionMappingDepth"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
        _NormalTex("NormalTex",2D) = "white"{}
        _HeightTex("DepthTex", 2D) = "white" {}
        _Height ("Height", Range(0.01, 0.3)) = 0.3
        _MinSamples ("MinSamples", int) = 16
        _MaxSamples ("MaxSamples", int) = 128
        _SpecularColor("Specular Color", Color) = (1,1,1,1)
        _SpecularExp ("SpecularExp", Range(32, 256)) = 64
        _DepthBufferScale("DepthBufferScale", Range(1.0, 5.0)) = 1.2
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    #include "Lighting.cginc"

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

    struct v2f
    {
        float4 position : POSITION;
        float2 texCoord : TEXCOORD0;
        float3 lightTS : TEXCOORD1;
        float3 viewTS : TEXCOORD2;
        float2 parallaxOffsetTS : TEXCOORD3;
        float3 normalWS : TEXCOORD4;
        float3 viewWS : TEXCOORD5;
        float3 posWS : TEXCOORD6;
    };

    struct fout
    {
        float4 col : SV_Target;
        float depth : SV_Depth;
    };

    sampler2D _MainTex;
    float4 _MainTex_ST;
    sampler2D _NormalTex;
    sampler2D _HeightTex;
    float4 _HeightTex_TexelSize;
    float _Height;
    int _MinSamples;
    int _MaxSamples;
    float4 _SpecularColor;
    float _SpecularExp;
    float _DepthBufferScale;

    v2f vert(appdata v)
    {
        v2f o = (v2f)0;

        o.position = UnityObjectToClipPos(v.vertex);
        o.texCoord = TRANSFORM_TEX(v.texCoord, _MainTex);
        o.normalWS = UnityObjectToWorldNormal(v.normal);
        o.viewWS = WorldSpaceViewDir(v.vertex);

        float3 lightWS = WorldSpaceLightDir(v.vertex);
        float3 tangentWS = UnityObjectToWorldDir(v.tangent.xyz);
        float3 binormalWS = cross(o.normalWS, tangentWS) * v.tangent.w;
        float3x3 worldToTangent = float3x3(tangentWS, binormalWS, o.normalWS);
        o.lightTS = mul(worldToTangent, lightWS);
        o.viewTS = mul(worldToTangent, o.viewWS);

        float2 parallaxDirection = normalize(o.viewTS.xy);
        float len = length(o.viewTS);
        float parallaxLength = sqrt(len * len - o.viewTS.z * o.viewTS.z) / o.viewTS.z;
        o.parallaxOffsetTS = parallaxDirection * parallaxLength;
        o.parallaxOffsetTS *= _Height;
        o.posWS = mul(UNITY_MATRIX_M, v.vertex);
        return o;
    }

    inline float ComputeDepth(float4 posPS)
    {
        float z = posPS.z / posPS.w;
#if defined(SHADER_API_GLCORE) || defined(SHADER_API_OPENGL) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
        return z * 0.5 + 0.5;
#else 
        return z;
#endif 
    }

    fout frag(v2f i)
    {
        fout o;
        float3 viewTS = normalize(i.viewTS);
        float3 viewWS = normalize(i.viewWS);
        float3 lightTS = normalize(i.lightTS);
        float3 normalWS = normalize(i.normalWS);

        float2 dx, dy;
        dx = ddx(i.texCoord);
        dy = ddy(i.texCoord);

        float2 texSample = i.texCoord;
        int numSteps = (int)lerp(_MaxSamples, _MinSamples, dot(viewWS, normalWS));

        float stepSize = 1.0 / (float)numSteps;
        float currHeight = 0.0;
        float prevHeight = 1.0;
        float2 texOffsetPerStep = stepSize * i.parallaxOffsetTS;
        float2 texCurrentOffset = i.texCoord;
        float currentBound = 1.0;

        for(int step = 0; step < numSteps && currHeight < currentBound; step++)
        {
            texCurrentOffset -= texOffsetPerStep;
            prevHeight = currHeight;
            currHeight = tex2Dgrad(_HeightTex, texCurrentOffset, dx, dy).r;
            currentBound -= stepSize;
        }
        float2 pt1 = float2(currentBound, currHeight);
        float2 pt2 = float2(currentBound + stepSize, prevHeight);

        float delta2 = pt2.x - pt2.y;
        float delta1 = pt1.x - pt1.y;
        float parallaxAmount = (pt1.x * delta2 - pt2.x * delta1) / (delta2 - delta1);
        float2 parallaxOffset = i.parallaxOffsetTS * (1 - parallaxAmount);
        float2 texSampleBase = i.texCoord - parallaxOffset;

        float3 posWS = i.posWS;
        float depthScale = _DepthBufferScale;
        float parallaxLength = length(i.parallaxOffsetTS / _Height);
        depthScale += depthScale * parallaxLength;
        float3 offsetWS = -viewWS * (1 - parallaxAmount) * depthScale;
        posWS += offsetWS;
        float4 posPS = mul(UNITY_MATRIX_VP, float4(posWS, 1.0));
        o.depth = ComputeDepth(posPS);

        texSample = texSampleBase;
        fixed4 baseColor = tex2Dgrad(_MainTex, texSample, dx, dy);
        float3 normalTS = normalize(UnpackNormal(tex2Dgrad(_NormalTex, texSample, dx, dy)));
        fixed3 diffuse = _LightColor0.rgb * baseColor.rgb * saturate(dot(normalTS, lightTS));
        fixed3 halfDirTS = normalize(lightTS + viewTS);
        fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(halfDirTS, normalTS)), _SpecularExp);
        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * baseColor;
        o.col = fixed4(ambient + diffuse + specular, 1.0);
        return o;
    }

    ENDCG

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    }
}

参考

https://kentie.net/article/terrain/ass2_doc_1.pdf