はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】ComputeShaderで微分フィルタを実装してみる【Q14】

微分フィルタとは

微分フィルタは輝度の急激な変化が起こっている部分のエッジを取り出すフィルタで、隣り合う画素同士の差を取ることで求められます。

横方向と縦方向の2つをやるところが今までと少し違いますね。

使うフィルタはこんな感じ。

f:id:hanaaaaaachiru:20200220223419p:plain

コード

ComputeShaderはこんな感じ。

#pragma kernel DifferentialFilter

RWTexture2D<float4> LengthResult;
RWTexture2D<float4> WidthResult;
Texture2D<float4> Texture;

[numthreads(32,16,1)]
void DifferentialFilter (uint3 id : SV_DispatchThreadID)
{
    float3x3 lengthFilter = float3x3(0, 0, 0, 0, 1, 0, 0, -1, 0);
    float3x3 widthFilter = float3x3(0, 0, 0, -1, 1, 0, 0, 0, 0);
    float4 rgb2gray = float4(0.2126, 0.7152, 0.0722, 0);
    uint i;

    float lengthArray[9] = {
        dot(Texture[id.xy + int2(-1, -1)], rgb2gray) * lengthFilter[0][0],
        dot(Texture[id.xy + int2(0, -1)], rgb2gray) * lengthFilter[0][1],
        dot(Texture[id.xy + int2(1, -1)], rgb2gray) * lengthFilter[0][2],
        dot(Texture[id.xy + int2(-1, 0)], rgb2gray) * lengthFilter[1][0],
        dot(Texture[id.xy], rgb2gray) * lengthFilter[1][1],
        dot(Texture[id.xy + int2(1, 0)], rgb2gray) * lengthFilter[1][2],
        dot(Texture[id.xy + int2(-1, 1)], rgb2gray) * lengthFilter[2][0],
        dot(Texture[id.xy + int2(0, 1)], rgb2gray) * lengthFilter[2][1],
        dot(Texture[id.xy + int2(1, 1)], rgb2gray) * lengthFilter[2][2]
    };

    float widthArray[9] = {
        dot(Texture[id.xy + int2(-1, -1)], rgb2gray) * widthFilter[0][0],
        dot(Texture[id.xy + int2(0, -1)], rgb2gray) * widthFilter[0][1],
        dot(Texture[id.xy + int2(1, -1)], rgb2gray) * widthFilter[0][2],
        dot(Texture[id.xy + int2(-1, 0)], rgb2gray) * widthFilter[1][0],
        dot(Texture[id.xy], rgb2gray) * widthFilter[1][1],
        dot(Texture[id.xy + int2(1, 0)], rgb2gray) * widthFilter[1][2],
        dot(Texture[id.xy + int2(-1, 1)], rgb2gray) * widthFilter[2][0],
        dot(Texture[id.xy + int2(0, 1)], rgb2gray) * widthFilter[2][1],
        dot(Texture[id.xy + int2(1, 1)], rgb2gray) * widthFilter[2][2]
    };

    float lr = 0;
    float wr = 0;
    for(i = 0; i < 9; i++){
        lr += lengthArray[i];
        wr += widthArray[i];
    }
    LengthResult[id.xy] = float4(lr, lr, lr, 1);
    WidthResult[id.xy] = float4(wr, wr, wr, 1);
}


C#のコードはこちら。

using UnityEngine;
using UnityEngine.UI;

public class DifferentialFilter : MonoBehaviour
{
    [SerializeField] private ComputeShader _computeShader;
    [SerializeField] private Texture2D _tex;
    [SerializeField] private RawImage _lengthRenderer;
    [SerializeField] private RawImage _widthRenderer;

    struct ThreadSize
    {
        public uint x;
        public uint y;
        public uint z;

        public ThreadSize(uint x, uint y, uint z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    private void Start()
    {
        if (!SystemInfo.supportsComputeShaders)
        {
            Debug.LogError("Comppute Shader is not support.");
            return;
        }

        // RenderTextueの初期化
        var lengthResult = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32);
        lengthResult.enableRandomWrite = true;
        lengthResult.Create();
        var widthResult = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32);
        widthResult.enableRandomWrite = true;
        widthResult.Create();

        // カーネルインデックスを取得
        var kernelIndex = _computeShader.FindKernel("DifferentialFilter");

        // 一つのグループの中に何個のスレッドがあるか
        ThreadSize threadSize = new ThreadSize();
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out threadSize.x, out threadSize.y, out threadSize.z);

        // GPUにデータをコピーする
        _computeShader.SetTexture(kernelIndex, "Texture", _tex);
        _computeShader.SetTexture(kernelIndex, "LengthResult", lengthResult);
        _computeShader.SetTexture(kernelIndex, "WidthResult", widthResult);

        // GPUの処理を実行する
        _computeShader.Dispatch(kernelIndex, _tex.width / (int)threadSize.x, _tex.height / (int)threadSize.y, (int)threadSize.z);

        // テクスチャを適応
        _lengthRenderer.texture = lengthResult;
        _widthRenderer.texture = widthResult;
    }
}

さいごに

フィルタのfloat3x3のところの[i][j]のところってもしかして今まで間違ってた気がしたようなしなかったような。

とりあえず今回は確認したのでこの書き方で間違いないと思います。

float3x3(0, 0, 0,      // row1
         0, 1, 0,      // row2
         0, -1, 0);    // row3

これが縦フィルタになっているのですが、上下反転していて少し見辛いですよね。

-1の値が欲しいなら[2][1]になるはずです。

まあ使っていれば慣れることを祈りましょう。

ではまた。