Gaming Life

一日24時間、ゲームは10時間

UnityのHDRPでSNNフィルタを実装する

最近UnityのHDRPを使って色々と実験をしている。今回はその実験中に作ったSNNフィルタポストプロセスの実装メモ。今回実装にはGtihubで公開されている「HDRP-Custom-Passes」に付属するシーンを使用した。

github.com

f:id:ai_gaminglife:20210429151643p:plain

環境

Unity 2020.3.1f1 Personal

High Definition RP ver.10.3.2

SNNフィルタとは

SNNフィルタ(Symmetric Nearest Neighbor Filter)は、画像を油彩画のような見た目に変化させるフィルタである。中のアルゴリズムについては、既にわかりやすく説明されているサイトがあるので、ここでは省略する。

sites.google.com

実装

PostProcessを管理するC#クラスを作成する

Create > Rendering > C# Post Process Volume からPostProcessを管理するC#クラスを作成する。ここでは「SNNFilter.cs」と名付けた。

SNNFilterクラスの中身は以下の通り。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using System;

[Serializable, VolumeComponentMenu("Post-processing/Custom/SNNFilter")]
public sealed class SNNFilter : CustomPostProcessVolumeComponent, IPostProcessComponent
{
    [Tooltip("Controls the intensity of the effect.")]
    public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f);
    public FloatParameter sampleCountFactor = new FloatParameter(2f);
    Material m_Material;

    public bool IsActive() => m_Material != null && intensity.value > 0f;

    // Do not forget to add this post process in the Custom Post Process Orders list (Project Settings > HDRP Default Settings).
    public override CustomPostProcessInjectionPoint injectionPoint => CustomPostProcessInjectionPoint.AfterPostProcess;

    const string kShaderName = "Hidden/Shader/SNNFilter";

    public override void Setup()
    {
        if (Shader.Find(kShaderName) != null)
            m_Material = new Material(Shader.Find(kShaderName));
        else
            Debug.LogError($"Unable to find shader '{kShaderName}'. Post Process Volume SNNFilter is unable to load.");
    }

    public override void Render(CommandBuffer cmd, HDCamera camera, RTHandle source, RTHandle destination)
    {
        if (m_Material == null)
            return;

        m_Material.SetFloat("_Intensity", intensity.value);
        m_Material.SetFloat("_SampleCountFactor", sampleCountFactor.value);

        m_Material.SetTexture("_InputTexture", source);
        HDUtils.DrawFullScreen(cmd, m_Material, destination);
    }

    public override void Cleanup()
    {
        CoreUtils.Destroy(m_Material);
    }
}

SNNFilterシェーダを作成する

Create > Shader > HDRP > PostProcess でポストプロセス用のシェーダを作成できる。ファイル名はSNNFilterとする。この時シェーダ名は先程作成したC#クラス中の kShaderName と完全に一致させる必要がある。

シェーダの中身は以下の通り。

Shader "Hidden/Shader/SNNFilter"
{
    HLSLINCLUDE

    #pragma target 4.5
    #pragma only_renderers d3d11 playstation xboxone vulkan metal switch

    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/FXAA.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/RTUpscale.hlsl"

    struct Attributes
    {
        uint vertexID : SV_VertexID;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };

    struct Varyings
    {
        float4 positionCS : SV_POSITION;
        float2 texcoord   : TEXCOORD0;
        UNITY_VERTEX_OUTPUT_STEREO
    };

    Varyings Vert(Attributes input)
    {
        Varyings output;
        UNITY_SETUP_INSTANCE_ID(input);
        UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
        output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID);
        output.texcoord = GetFullScreenTriangleTexCoord(input.vertexID);
        return output;
    }

    // List of properties to control your post process effect
    float _Intensity;
    float _SampleCountFactor;
    TEXTURE2D_X(_InputTexture);

    float4 GetOffsetPixel(float2 offset, float2 coord) {
        float2 invScreenSize = 1.0 / _ScreenSize;
        float4 result = LOAD_TEXTURE2D_X(_InputTexture, (coord + invScreenSize * offset) * _ScreenSize.xy);
        return result;
    }

    float4 GetSNN(float4 centerColor, Varyings input, float2 offset) {
        float4 col0 = GetOffsetPixel(offset, input.texcoord);
        float4 col1 = GetOffsetPixel(-offset, input.texcoord);
        float3 d0 = col0.rgb - centerColor.rgb;
        float3 d1 = col1.rgb - centerColor.rgb;

        return dot(d0, d0) < dot(d1, d1) ? col0 : col1;
    }

    float4 CustomPostProcess(Varyings input) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

        float4 centerColor = LOAD_TEXTURE2D_X(_InputTexture, input.texcoord * _ScreenSize.xy);

        int count = 0;
        float4 resultColor = (0, 0, 0, 1);
        for (int x = -_SampleCountFactor; x <= _SampleCountFactor; ++x) {
            for (int y = -_SampleCountFactor; y <= _SampleCountFactor; ++y) {
                if (x == 0 && y <= 0) {
                    continue;
                }
                resultColor += GetSNN(centerColor, input, float2(x, y));
                count++;
            }
        }
        resultColor /= count;

        return resultColor;
    }

    ENDHLSL

    SubShader
    {
        Pass
        {
            Name "SNNFIlter"

            ZWrite Off
            ZTest Always
            Blend Off
            Cull Off

            HLSLPROGRAM
                #pragma fragment CustomPostProcess
                #pragma vertex Vert
            ENDHLSL
        }
    }
    Fallback Off
}

作成したポストプロセスをHDRPに登録する

Edit > ProjectSettings > HDRP Default Settings からHDRPの詳細な設定を決められる。今回は、 After Post Process に先程作成したSNNFilterを登録する。

f:id:ai_gaminglife:20210429151720p:plain

ポストプロセスをシーンに追加する

Hierarchyから、 Volume > Global Volume と選択して、シーンにGlobal Volumeを追加する。Global Volumeはポストプロセスを設定すると、シーン全体にそのポストプロセスを適用することができる。

Global Volume中のVolumeコンポーネントのProfileのNewを押して、Profileを新規作成する。

続けて Add Override を選択する。ここまで適切に設定していれば、ここで「SNNFilter」を追加できる。Intensityを0より大きい値に設定するとポストプロセスが適用される。

f:id:ai_gaminglife:20210429151730p:plain

SNNFilterの例

Sample Count Factor = 2の時

f:id:ai_gaminglife:20210429151738p:plain

Sample Count Factor = 10の時

f:id:ai_gaminglife:20210429151748p:plain

Sample Count Factor = 20の時

f:id:ai_gaminglife:20210429151759p:plain

参考資料

Custom Post Process | High Definition RP | 10.2.2

DirectXの話 第111回 - もんしょの巣穴

alelievr/HDRP-Custom-Passes: A bunch of custom passes made for HDRP