DDX/DDY

環境

Unity2021.2.18f1

概要

ddxとddyは勾配を取得する命令です。

引数で渡した変数のとなりのピクセル(厳密には違う)との差分が得られます。

以下のシェーダはデプスとノーマルのそれぞれの差分を取り、エッジ検出をしてそこにAA的な処理(ただぼかしているだけ)を施しています。

Shader "Hidden/PostDdxy"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeNormal("Judge Edge Normal", float) = 0.99
        _EdgeDepth("Judge Edge Depth", float) = 0.001
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            sampler2D _CameraDepthTexture;
            sampler2D _CameraDepthNormalsTexture;
            float _EdgeNormal;
            float _EdgeDepth;

            fixed3 antiAlias(v2f i, float3 rgb)
            {
                float2 offset = float2(1.0 - _ScreenParams.z, 1.0 - _ScreenParams.w);
#if 0
                float3 u = tex2D(_MainTex, i.uv + float2(0, offset.y)).rgb;
                float3 b = tex2D(_MainTex, i.uv + float2(0, -offset.y)).rgb;
                float3 l = tex2D(_MainTex, i.uv + float2(-offset.x, 0)).rgb;
                float3 r = tex2D(_MainTex, i.uv + float2(offset.x, 0)).rgb;
                rgb = (rgb + u + b + l + r) / 5.0;
#else
                float3 rgb0 = tex2D(_MainTex, i.uv + float2( 0.0, offset.y)).rgb;
                float3 rgb1 = tex2D(_MainTex, i.uv + float2( 0.87 * offset.x, -0.50 * offset.y)).rgb;
                float3 rgb2 = tex2D(_MainTex, i.uv + float2(-0.87 * offset.x, -0.50 * offset.y)).rgb;
                rgb = (rgb + rgb0 + rgb1 + rgb2) / 4.0;
#endif
                return rgb;
            }

            bool IsEdgeNormal(float2 uv)
            {
                float4 depthNormal = tex2D(_CameraDepthNormalsTexture, uv);
                float tempDepth;
                float3 normal;
                DecodeDepthNormal(depthNormal, tempDepth, normal);
                float3 diffNormalX = ddx(normal);
                float3 diffNormalY = ddy(normal);
                float3 normal2 = normal + (0 + diffNormalX + diffNormalY) / 3.0;
                float edgeNormal = dot(normal, normal2);
                return (edgeNormal < _EdgeNormal);
            }

            bool IsEdgeDepth(float2 uv)
            {
                float depth = LinearEyeDepth(tex2D(_CameraDepthTexture, uv).r) * _ProjectionParams.w;
                float edgeDepth = abs(ddx(depth)) + abs(ddy(depth));
                return (edgeDepth > _EdgeDepth);
            }

            bool IsEdge(float2 uv)
            {
                return IsEdgeDepth(uv) || IsEdgeNormal(uv);
            }

            fixed4 frag(v2f i) : SV_Target
            {
                float2 offset = float2(1.0 - _ScreenParams.z, 1.0 - _ScreenParams.w);
                float4 col = tex2D(_MainTex, i.uv);
                if (IsEdge(i.uv) || IsEdge(i.uv + offset))
                    col.rgb = antiAlias(i, col.rgb);
                //else
                //  col.rgb = 0.0;
                return col;
            }
            ENDCG
        }
    }
}
using UnityEngine;

public class PostDdxy : MonoBehaviour
{
    [SerializeField] private Material _material = null;

    private void Start()
    {
        Camera.main.depthTextureMode |= DepthTextureMode.DepthNormals;
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, _material);
    }
}

参考

ddx, ddyの挙動を調べてみた - もんしょの巣穴ブログ Ver2.0