FFTによるグレアフィルター

環境

Unity2021.2.18f1

概要

FFTを用いたグレアフィルターです。

今回も参考リンクのページのコードを実装してみた感じです。

画像も同じのを使わせていただいています。

GenerateStarBurst2Window.cs

using System.IO;
using UnityEditor;
using UnityEngine;

public class GenerateStarBurst2Window : EditorWindow
{
    [MenuItem("Tools/Generate StarBurst2")]
    private static void Create()
    {
        var window = GetWindow<GenerateStarBurst2Window>( "GenerateStarBurst2" );
        window.Show();
    }
    private GenerateStarBurst2 _obj = null;
    private void OnEnable()
    {
        var ms = MonoScript.FromScriptableObject( this );
        var path = AssetDatabase.GetAssetPath( ms );
        path = path.Replace(Path.GetFileName( path ), "" );
        path = path + "GenerateStarBurst2.asset";
        _obj = AssetDatabase.LoadAssetAtPath<GenerateStarBurst2>( path );
        if(_obj == null)
        {
            var assets = AssetDatabase.LoadAllAssetsAtPath(path);
            if(assets.Length > 0)
                _obj = assets[0] as GenerateStarBurst2;
        }
        if( _obj == null )
        {
            _obj = ScriptableObject.CreateInstance<GenerateStarBurst2>();
            AssetDatabase.CreateAsset( _obj, path );
            AssetDatabase.ImportAsset( path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive );
        }
    }

    private void OnDisable()
    {
        if( _obj == null )
            return;
        EditorUtility.SetDirty( _obj );
        AssetDatabase.ImportAsset( AssetDatabase.GetAssetPath( _obj ), ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive );
    }

    private void OnGUI()
    {
        _obj.OnGUI();
        Repaint();
    }
}

GenerateStarBurst2.cs

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Experimental.Rendering;

public class GenerateStarBurst2 : ScriptableObject
{
    [SerializeField] private ComputeShader _computeShader = null;
    [SerializeField] private Texture2D _targetTexture = null;
    [SerializeField] private Texture2D _apertureTexture = null;

    private const int m_texheight = 512;        //computeShaderも変更する

    private float m_glareintensity = 20.0f;
    private float m_threshold = 0.96f;

    private RenderTexture[] m_RWfullsizeTex = null;
    private RenderTexture[] m_RWmaxminTex = null;
    private RenderTexture[] m_RWlineInnerTex = null;
    private RenderTexture m_RClearTex = null;

    private struct ComputeParameters
    {
        public float lambdaR;
        public float lambdaG;
        public float lambdaB;
        public float glareintensity;
        public float threshold;
    };
    private GraphicsBuffer _gsConstantParam = null;

    private void OnDestroy()
    {
        if(_gsConstantParam != null)
            _gsConstantParam.Dispose();
        if(m_RWfullsizeTex != null)
        {
            for(int i = 0; i < m_RWfullsizeTex.Length; i++)
                m_RWfullsizeTex[i]?.Release();
        }
        m_RWfullsizeTex = null;
        if(m_RWmaxminTex != null)
        {
            for(int i = 0; i < m_RWmaxminTex.Length; i++)
                m_RWmaxminTex[i]?.Release();
        }
        m_RWmaxminTex = null;
        if(m_RWlineInnerTex != null)
        {
            for(int i = 0; i < m_RWlineInnerTex.Length; i++)
                m_RWlineInnerTex[i]?.Release();
        }
        m_RWlineInnerTex = null;

        m_RClearTex?.Release();
        m_RClearTex = null;
    }

    private static string LabeledTextField(string label, string text, float labelFieldWidth = 100, float fieldWidth = 30)
    {
        using (var horizontalScope = new GUILayout.HorizontalScope())
        {
            EditorGUILayout.LabelField(label, GUILayout.Width(labelFieldWidth));
            var str = EditorGUILayout.TextField(text, GUILayout.Width(fieldWidth));
            return str;
        }
    }

    public void OnGUI()
    {
        var str = LabeledTextField("Glare Intensity", m_glareintensity.ToString(), 100, 60);
        m_glareintensity = (float)Convert.ToDouble(str);

        str = LabeledTextField("Threshold", m_threshold.ToString(), 100, 60);
        m_threshold = (float)Convert.ToDouble(str);

        if(GUILayout.Button( "Generate Glare" ) == true)
        {
            GenerateGlare();
        }
        if(GUILayout.Button( "Generate" ) == true)
        {
            Generate();
        }

        var baseRect = GUILayoutUtility.GetLastRect();
        var rect = baseRect;
        rect.position = new Vector2(rect.position.x, rect.position.y + rect.size.y);
        rect.size = new Vector2(128, 128);

        if(m_RWfullsizeTex != null)
        {
            for(int i = 0; i < m_RWfullsizeTex.Length; i++)
            {
                if(m_RWfullsizeTex[i] != null)
                    EditorGUI.DrawPreviewTexture(rect, m_RWfullsizeTex[i]);
                rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
            }
        }
        rect.position = new Vector2(baseRect.position.x, rect.position.y + rect.size.y);
        if(m_RWmaxminTex != null)
        {
            for(int i = 0; i < m_RWmaxminTex.Length; i++)
            {
                if(m_RWmaxminTex[i] != null)
                    EditorGUI.DrawPreviewTexture(rect, m_RWmaxminTex[i]);
                rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
            }
        }
        rect.position = new Vector2(baseRect.position.x, rect.position.y + rect.size.y);
        if(m_RWlineInnerTex != null)
        {
            for(int i = 0; i < m_RWlineInnerTex.Length; i++)
            {
                if(m_RWlineInnerTex[i] != null)
                    EditorGUI.DrawPreviewTexture(rect, m_RWlineInnerTex[i]);
                rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
            }
        }
        rect.position = new Vector2(baseRect.position.x, rect.position.y + rect.size.y);
        if(m_RClearTex != null)
        {
            EditorGUI.DrawPreviewTexture(rect, m_RClearTex);
            rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
        }
    }

    private void CreateClearTex()
    {
        if(m_RClearTex != null)
            return;
        m_RClearTex = new RenderTexture(m_texheight, m_texheight, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Default);
        m_RClearTex.useMipMap = false;
        m_RClearTex.enableRandomWrite = true;
        ExecuteClearCommand(m_RClearTex);
    }
    private unsafe void GenerateGlare()
    {
        var cp = new ComputeParameters();
        cp.lambdaR = 633e-9f;
        cp.lambdaG = 532e-9f;
        cp.lambdaB = 466e-9f;
        cp.glareintensity = m_glareintensity;
        cp.threshold = m_threshold;
        if(_gsConstantParam == null)
            _gsConstantParam = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(ComputeParameters));
        _gsConstantParam.SetData(new ComputeParameters[]{cp});

        if(m_RWfullsizeTex == null)
        {
            m_RWfullsizeTex = new RenderTexture[9];
            for (int i = 0; i < m_RWfullsizeTex.Length; i++)
            {
                m_RWfullsizeTex[i] = new RenderTexture(m_texheight, m_texheight, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Default);
                m_RWfullsizeTex[i].useMipMap = false;
                m_RWfullsizeTex[i].enableRandomWrite = true;
            }
        }
        if(m_RWmaxminTex == null)
        {
            m_RWmaxminTex = new RenderTexture[2];
            for (int i = 0; i < m_RWmaxminTex.Length; i++)
            {
                m_RWmaxminTex[i] = new RenderTexture(m_texheight, m_texheight, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Default);
                m_RWmaxminTex[i].useMipMap = false;
                m_RWmaxminTex[i].enableRandomWrite = true;
            }
        }
        if(m_RWlineInnerTex == null)
        {
            m_RWlineInnerTex = new RenderTexture[2];
            for (int i = 0; i < m_RWlineInnerTex.Length; i++)
            {
                m_RWlineInnerTex[i] = new RenderTexture(m_texheight, m_texheight, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Default);
                m_RWlineInnerTex[i].useMipMap = false;
                m_RWlineInnerTex[i].enableRandomWrite = true;
            }
        }
        CreateClearTex();
        ExecuteFFTCommand(_apertureTexture, m_RClearTex, m_RWfullsizeTex[3], m_RWfullsizeTex[4]);//グレア生成
        ExecuteCalcurateAmplitudeCommand(m_RWfullsizeTex[3], m_RWfullsizeTex[4], m_RWfullsizeTex[5], m_RWfullsizeTex[6]);
        ExecuteCalcMaxMinCommand(m_RWfullsizeTex[5], m_RWmaxminTex[0], m_RWmaxminTex[1]);
        ExecuteDivideMaxAmpCommand(m_RWmaxminTex[0], m_RWmaxminTex[1], m_RWfullsizeTex[5], m_RWfullsizeTex[6], m_RWfullsizeTex[3], m_RWfullsizeTex[4]);
        ExecuteRaiseRICommand(m_RWfullsizeTex[3], m_RWfullsizeTex[4], m_RWfullsizeTex[5], m_RWfullsizeTex[6]);//グレアの輝度を底上げ
        ExecuteSpectrumScalingCommand(m_RWfullsizeTex[5], m_RWfullsizeTex[6], m_RWfullsizeTex[3], m_RWfullsizeTex[4]);//波長スケーリング
        ExecuteCopyCommand(m_RWfullsizeTex[3], m_RWfullsizeTex[7]);
        ExecuteCopyCommand(m_RWfullsizeTex[4], m_RWfullsizeTex[8]);

        SaveRenderTexture(m_RWfullsizeTex[7], "GlareReal.png");
        SaveRenderTexture(m_RWfullsizeTex[8], "GlareImage.png");
    }
    private unsafe void Generate()
    {
        if(m_RWfullsizeTex == null)
            GenerateGlare();
        CreateClearTex();

        ExecuteBinaryThresholdCommand(_targetTexture, m_RWfullsizeTex[1]);
        ExecuteConvolutionCommand(m_RWfullsizeTex[1], m_RClearTex, m_RWfullsizeTex[1], m_RWfullsizeTex[2], m_RWfullsizeTex[7], m_RWfullsizeTex[8], m_RWfullsizeTex[3], m_RWfullsizeTex[4], m_RWfullsizeTex[5], m_RWfullsizeTex[6]);
        ExecuteCalcurateAmplitudeCommand(m_RWfullsizeTex[5], m_RWfullsizeTex[6], m_RWfullsizeTex[0], m_RWfullsizeTex[1]);
        ExecuteCalcMaxMinCommand(m_RWfullsizeTex[0], m_RWmaxminTex[0], m_RWmaxminTex[1]);
        ExecuteDivideMaxAmpCommand(m_RWmaxminTex[0], m_RWmaxminTex[1], m_RWfullsizeTex[0], m_RWfullsizeTex[1], m_RWfullsizeTex[3], m_RWfullsizeTex[4]);
        ExecuteAddCommand(_targetTexture, m_RWfullsizeTex[3], m_RWfullsizeTex[1]);

        SaveRenderTexture(m_RWfullsizeTex[1], "Final.png");
    }

    private void SaveRenderTexture(RenderTexture renderTex, string path)
    {
        var prevRt = RenderTexture.active;
        Texture2D tex = new Texture2D(renderTex.width, renderTex.height, renderTex.graphicsFormat, TextureCreationFlags.None);
        RenderTexture.active = renderTex;
        tex.ReadPixels(new Rect(0, 0, renderTex.width, renderTex.height), 0, 0);
        tex.Apply();
        RenderTexture.active = prevRt;

        var bytes = tex.EncodeToPNG();
        File.WriteAllBytes(Path.Combine($"{Application.dataPath}", path), bytes);
        AssetDatabase.Refresh();
    }

    private Dictionary<string, string> _nameDict = null;
    private int FindKernel(string name)
    {
        if(_nameDict == null)
        {
            _nameDict = new Dictionary<string, string>();
            _nameDict.Add("mulCS", "mainMULTIPLY");
            _nameDict.Add("fftCS_ROW", "mainFFT_ROW");
            _nameDict.Add("fftCS_COL", "mainFFT_COL");
            _nameDict.Add("ifftCS_ROW", "mainFFT_ROW_INV");
            _nameDict.Add("ifftCS_COL", "mainFFT_COL_INV");
            _nameDict.Add("ampCS", "mainAMPLITUDE");
            _nameDict.Add("divByMaxAMPCS", "mainDivByMaxAMP");
            _nameDict.Add("AddCS", "mainAdd");
            _nameDict.Add("BTCS", "mainBinaryThreshold");
            _nameDict.Add("copyCS", "mainCopy");
            _nameDict.Add("clearCS", "mainClear");
            _nameDict.Add("spectrumScalingCS", "mainSpectrumScaling");
            _nameDict.Add("raiseRICS", "mainRaiseBottomRealImage");
            _nameDict.Add("maxminfirstCS", "mainMAXMINfirst");
            _nameDict.Add("maxminsecondCS", "mainMAXMINsecond");
        }
        return  _computeShader.FindKernel(_nameDict[name]);
    }

    private static string[] _csNames = new string[]
    {
        "computeConstants",         //0
        "sourceImageR",             //1
        "sourceImageI",             //2
        "destinationImageR",        //3
        "destinationImageI",        //4
        "destinationImageR1",       //5
        "destinationImageI1",       //6
    };

    private string GetCsName(int i)
    {
        return _csNames[i];
    }

    private void ExecuteCopyCommand(Texture In, Texture Out)
    {
        var kernelIndex = FindKernel("copyCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);

        _computeShader.SetTexture(kernelIndex, GetCsName(1), In);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), Out);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteBinaryThresholdCommand(Texture In, Texture Out)
    {
        var kernelIndex = FindKernel("BTCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);

        _computeShader.SetTexture(kernelIndex, GetCsName(1), In);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), Out);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteClearCommand(Texture Tex)
    {
        var kernelIndex = FindKernel("clearCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);

        _computeShader.SetTexture(kernelIndex, GetCsName(3), Tex);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteFFTCommand(Texture Real, Texture Image)
    {
        ExecuteFFTCommand(Real, Image, Real, Image);
    }
    private void ExecuteFFTCommand(Texture Real, Texture Image, Texture OutReal, Texture OutImage)
    {
        //縦方向
        var kernelIndex = FindKernel("fftCS_ROW");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), Real);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), Image);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), m_RWlineInnerTex[0]);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), m_RWlineInnerTex[1]);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
        //横方向
        kernelIndex = FindKernel("fftCS_COL");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), m_RWlineInnerTex[0]);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), m_RWlineInnerTex[1]);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), OutReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), OutImage);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }

    private void ExecuteIFFTCommand(Texture Real, Texture Image)
    {
        //縦方向
        var kernelIndex = FindKernel("ifftCS_ROW");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), Real);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), Image);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), m_RWlineInnerTex[0]);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), m_RWlineInnerTex[1]);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
        //横方向
        kernelIndex = FindKernel("ifftCS_COL");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), m_RWlineInnerTex[0]);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), m_RWlineInnerTex[1]);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), Real);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), Image);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }

    private void ExecuteCalcurateAmplitudeCommand(Texture InReal, Texture InImage, Texture OutReal, Texture OutImage)
    {
        var kernelIndex = FindKernel("ampCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);

        _computeShader.SetTexture(kernelIndex, GetCsName(1), InReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), InImage);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), OutReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), OutImage);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteCalcMaxMinCommand(Texture Tex, Texture OutOnePixReal_MAX, Texture OutOnePixImage_MIN)
    {
        //最大値最小値の計算
        var kernelIndex = FindKernel("maxminfirstCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), Tex);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), m_RWlineInnerTex[0]);
        _computeShader.Dispatch(kernelIndex, m_texheight, 1, 1);

        kernelIndex = FindKernel("maxminsecondCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), m_RWlineInnerTex[0]);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), OutOnePixReal_MAX);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), OutOnePixImage_MIN);
        _computeShader.Dispatch(kernelIndex, 1, 1, 1);
    }
    private void ExecuteDivideMaxAmpCommand(Texture OutOnePixReal_MAX, Texture OutOnePixImage_MIN, Texture InReal, Texture InImage, Texture OutReal, Texture OutImage)
    {
        //最大振幅による除算
        var kernelIndex = FindKernel("divByMaxAMPCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), OutOnePixReal_MAX);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), OutOnePixImage_MIN);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), InReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), InImage);
        _computeShader.SetTexture(kernelIndex, GetCsName(5), OutReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(6), OutImage);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteRaiseRICommand(Texture InReal, Texture InImage, Texture OutReal, Texture OutImage)
    {
        var kernelIndex = FindKernel("raiseRICS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), InReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), InImage);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), OutReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), OutImage);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteSpectrumScalingCommand(Texture InReal, Texture InImage, Texture OutReal, Texture OutImage)
    {
        var kernelIndex = FindKernel("spectrumScalingCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), InReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), InImage);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), OutReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), OutImage);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }

    private void ExecuteConvolutionCommand(Texture InReal0, Texture InImage0, Texture InReal1, Texture InImage1, Texture OutReal, Texture OutImage)
    {
        ExecuteConvolutionCommand(InReal0, InImage0, InReal0, InImage0, InReal1, InImage1, InReal1, InImage1, OutReal, OutImage);
    }
    private void ExecuteConvolutionCommand(Texture InReal0, Texture InImage0, Texture TmpFftReal0, Texture TmpFftImage0, Texture InReal1, Texture InImage1, Texture TmpFftReal1, Texture TmpFftImage1, Texture OutReal, Texture OutImage)
    {
        //一方のFFT
        ExecuteFFTCommand(InReal0, InImage0, TmpFftReal0, TmpFftImage0);
        //もう一方のFFT
        ExecuteFFTCommand(InReal1, InImage1, TmpFftReal1, TmpFftImage1);
        //乗算
        ExecuteMultiplyCommand(TmpFftReal0, TmpFftImage0, TmpFftReal1, TmpFftImage1, OutReal, OutImage);
        //逆FFT
        ExecuteIFFTCommand(OutReal, OutImage);
    }

    private void ExecuteMultiplyCommand(Texture InReal0, Texture InImage0, Texture InReal1, Texture InImage1, Texture OutReal, Texture OutImage)
    {
        var kernelIndex = FindKernel("mulCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), InReal0);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), InImage0);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), InReal1);
        _computeShader.SetTexture(kernelIndex, GetCsName(4), InImage1);
        _computeShader.SetTexture(kernelIndex, GetCsName(5), OutReal);
        _computeShader.SetTexture(kernelIndex, GetCsName(6), OutImage);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
    private void ExecuteAddCommand(Texture Tex1, Texture Tex2, Texture Out)
    {
        var kernelIndex = FindKernel("AddCS");
        _computeShader.SetBuffer(kernelIndex, GetCsName(0), _gsConstantParam);
        _computeShader.SetTexture(kernelIndex, GetCsName(1), Tex1);
        _computeShader.SetTexture(kernelIndex, GetCsName(2), Tex2);
        _computeShader.SetTexture(kernelIndex, GetCsName(3), Out);
        _computeShader.Dispatch(kernelIndex, 1, m_texheight, 1);
    }
}

GenerateStarBurst2.compute

static const int WIDTH = 512;
static const int HEIGHT = 512;
static const float PI = 3.14159;
static const float RAD = PI / 180.0;
static const int LENGTH = HEIGHT;
static const int BUTTERFLY_COUNT = 9;

//--------------------------------
float2 complex_conjugate(float2 cmp)
{
    return float2(cmp.x, -cmp.y);
}
float complex_sqr(float2 cmp)
{
    return cmp.x * cmp.x + cmp.y * cmp.y;
}
float complex_norm(float2 cmp)
{
    return sqrt(complex_sqr(cmp));
}
float2 complex_add(float2 cmp1, float2 cmp2)
{
    return float2(cmp1.x + cmp2.x, cmp1.y + cmp2.y);
}
float2 complex_sub(float2 cmp1, float2 cmp2)
{
    return float2(cmp1.x - cmp2.x, cmp1.y - cmp2.y);
}
float2 complex_mul(float2 cmp1, float2 cmp2)
{
    return float2(cmp1.x * cmp2.x - cmp1.y * cmp2.y, cmp1.y * cmp2.x + cmp1.x * cmp2.y);
}
float2 complex_div(float2 cmp1, float2 cmp2)
{
    float2 cmp = complex_mul(cmp1, complex_conjugate(cmp2));
    float sqr = complex_sqr(cmp2);
    return float2(cmp.x / sqr, cmp.y / sqr);
}
float2 complex_polar(float amp, float phase)
{
    return float2(amp * cos(phase), amp * sin(phase));
}
//------------------------
struct ComputeParameters
{
    float lambdaR;
    float lambdaG;
    float lambdaB;
    float glareintensity;
    float threshold;
};

StructuredBuffer<ComputeParameters> computeConstants;

Texture2D<float4> sourceImageR : register(t0);
Texture2D<float4> sourceImageI : register(t1);
RWTexture2D<float4> destinationImageR : register(u0);
RWTexture2D<float4> destinationImageI : register(u1);
RWTexture2D<float4> destinationImageR1 : register(u2);
RWTexture2D<float4> destinationImageI1 : register(u3);
//------------------------
//copyCSに対応
#pragma kernel mainCopy
[numthreads(WIDTH, 1, 1)]
void mainCopy(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;

    destinationImageR[index] = sourceImageR[index];
}

//BTCSに対応
#pragma kernel mainBinaryThreshold
[numthreads(WIDTH, 1, 1)]
void mainBinaryThreshold(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;
    float3 input = sourceImageR[index].rgb;

    float3 col = float3(0.0, 0.0, 0.0);

    float r = 0;
    float g = 0;
    float b = 0;

    if ((input.r + input.g + input.b) / 3.0 > computeConstants[0].threshold)
    {
        col = float3(1.0, 1.0, 1.0);
    }

    destinationImageR[index] = float4(col, 1.0f);
}

//clearCSに対応
#pragma kernel mainClear
[numthreads(WIDTH, 1, 1)]
void mainClear(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;

    destinationImageR[index] = float4(0.0, 0.0, 0.0, 1.0);
}

//fftCS_シリーズで使用される
void ComputeSrcID(uint passIndex, uint x, out uint2 indices)
{
    uint regionWidth = 2 << passIndex;
    indices.x = (x & ~(regionWidth - 1)) + (x & (regionWidth / 2 - 1));
    indices.y = indices.x + regionWidth / 2;

    if (passIndex == 0)
    {
        indices = reversebits(indices) >> (32 - BUTTERFLY_COUNT) & (LENGTH - 1);
    }
}

void ComputeTwiddleFactor(uint passIndex, uint x, out float2 weights)
{
    uint regionWidth = 2 << passIndex;
    sincos(2.0 * PI * float(x & (regionWidth - 1)) / float(regionWidth), weights.y, weights.x);
    weights.y *= -1;
}

static const int REAL = 0;
static const int IMAGE = 1;

#ifdef DOUBLE//メモリに余裕があるとき
groupshared float3 ButterflyArray[2][2][LENGTH];
#define SharedArray(tmpID, x, realImage) (ButterflyArray[(tmpID)][(realImage)][(x)])
#else
groupshared float3 ButterflyArray[2][LENGTH];
#define SharedArray(tmpID, x, realImage) (ButterflyArray[(realImage)][(x)])
#endif

//fftCS_シリーズで使用される
void ButterflyWeightPass(uint passIndex, uint x, uint tmp, out float3 resultR, out float3 resultI)
{
    uint2 Indices;
    float2 Weights;

    ComputeSrcID(passIndex, x, Indices);

    float3 inputR1 = SharedArray(tmp, Indices.x, REAL);
    float3 inputI1 = SharedArray(tmp, Indices.x, IMAGE);

    float3 inputR2 = SharedArray(tmp, Indices.y, REAL);
    float3 inputI2 = SharedArray(tmp, Indices.y, IMAGE);

    ComputeTwiddleFactor(passIndex, x, Weights);

#ifndef DOUBLE
    GroupMemoryBarrierWithGroupSync();//ダブルバッファでない場合は格納の完了を保証する必要がる
#endif

#if INVERSE
    resultR = (inputR1 + Weights.x * inputR2 + Weights.y * inputI2) * 0.5;
    resultI = (inputI1 - Weights.y * inputR2 + Weights.x * inputI2) * 0.5;
#else
    resultR = inputR1 + Weights.x * inputR2 - Weights.y * inputI2;
    resultI = inputI1 + Weights.y * inputR2 + Weights.x * inputI2;
#endif
}

void initializeFFT_SharedArray(uint bufferID, inout uint2 texPos)
{
    texPos = (texPos + LENGTH / 2) % LENGTH;
    SharedArray(0, bufferID, REAL) = sourceImageR[texPos].xyz;

#if ROW && !INVERSE
    SharedArray(0, bufferID, IMAGE) = (0.0).xxx;
#else
    SharedArray(0, bufferID, IMAGE) = sourceImageI[texPos].xyz;
#endif
}

void ButterflyPass(in uint bufferID, out float3 real, out float3 image)
{
    for (uint butterFlyID = 0; butterFlyID < (uint)(BUTTERFLY_COUNT - 1); butterFlyID++)
    {
        GroupMemoryBarrierWithGroupSync();
        ButterflyWeightPass(butterFlyID, bufferID, butterFlyID % 2, SharedArray((butterFlyID + 1) % 2, bufferID, REAL), SharedArray((butterFlyID + 1) % 2, bufferID, IMAGE));
    }

    GroupMemoryBarrierWithGroupSync();

    ButterflyWeightPass(BUTTERFLY_COUNT - 1, bufferID, (BUTTERFLY_COUNT - 1) % 2,
        real, image);
}

//fftCS_シリーズに対応
void mainFFT(uint3 dispatchID)
{
    const uint bufferID = dispatchID.x;

#if ROW
    uint2 texPos = dispatchID.xy;
#else
    uint2 texPos = dispatchID.yx;
#endif

    initializeFFT_SharedArray(bufferID, texPos);

    float3 r_result = 0, i_result = 0;
    ButterflyPass(bufferID, r_result, i_result);

    destinationImageR[texPos] = float4(r_result, 1);
    destinationImageI[texPos] = float4(i_result, 1);
}

#pragma kernel mainFFT_ROW ROW
[numthreads(LENGTH, 1, 1)]
void mainFFT_ROW(uint3 dispatchID : SV_DispatchThreadID)
{
    mainFFT(dispatchID);
}
#pragma kernel mainFFT_COL
[numthreads(LENGTH, 1, 1)]
void mainFFT_COL(uint3 dispatchID : SV_DispatchThreadID)
{
    mainFFT(dispatchID);
}
#pragma kernel mainFFT_ROW_INV ROW INVERSE
[numthreads(LENGTH, 1, 1)]
void mainFFT_ROW_INV(uint3 dispatchID : SV_DispatchThreadID)
{
    mainFFT(dispatchID);
}
#pragma kernel mainFFT_COL_INV INVERSE
[numthreads(LENGTH, 1, 1)]
void mainFFT_COL_INV(uint3 dispatchID : SV_DispatchThreadID)
{
    mainFFT(dispatchID);
}

//ampCSに対応
#pragma kernel mainAMPLITUDE
[numthreads(WIDTH, 1, 1)]
void mainAMPLITUDE(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;
    float3 inputR = sourceImageR[index].rgb;
    float3 inputI = sourceImageI[index].rgb;

    float r = inputR.r * inputR.r + inputI.r * inputI.r;
    float g = inputR.g * inputR.g + inputI.g * inputI.g;
    float b = inputR.b * inputR.b + inputI.b * inputI.b;

    float3 col = float3(sqrt(r), sqrt(g), sqrt(b));

    destinationImageR[index] = float4(col, 1.0f);
    destinationImageI[index] = float4(col, 1.0f);
}

//maxminfirstCSに対応
#pragma kernel mainMAXMINfirst
[numthreads(1, 1, 1)]
void mainMAXMINfirst(uint3 dispatchID : SV_DispatchThreadID)
{
    uint2 index = dispatchID.xy;
    float3 color_max = sourceImageR[float2(0, 0)].xyz;
    float3 color_min = color_max;

    int i, j;
    for (i = 0; i < HEIGHT; i++)
    {
        uint2 indexx = uint2(index.x, i);
        float3 color = sourceImageR[indexx].xyz;

        color_max = max(color_max, color);
        color_min = min(color_min, color);
    }

    destinationImageR[float2(index.x, 0)] = float4(color_max, 1.0f);
    destinationImageR[float2(index.x, 1)] = float4(color_min, 1.0f);
}

//maxminsecondCSに対応
#pragma kernel mainMAXMINsecond
[numthreads(1, 1, 1)]
void mainMAXMINsecond(uint3 dispatchID : SV_DispatchThreadID)
{
    uint2 index = dispatchID.xy;
    float3 color_max = sourceImageR[float2(0, 0)].xyz;
    float3 color_min = color_max;

    int i, j;
    for (i = 0; i < HEIGHT; i++)
    {
        uint2 index1 = uint2(i, 0);
        uint2 index2 = uint2(i, 1);
        float3 colormax = sourceImageR[index1].xyz;
        float3 colormin = sourceImageR[index2].xyz;

        color_max = max(color_max, colormax);
        color_min = min(color_min, colormin);
    }

    destinationImageR[float2(0, 0)] = float4(color_max, 1.0f);
    destinationImageI[float2(0, 0)] = float4(color_min, 1.0f);
}

//divByMaxAMPCSに対応
#pragma kernel mainDivByMaxAMP
[numthreads(WIDTH, 1, 1)]
void mainDivByMaxAMP(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;

    float2 zero = float2(0.0, 0.0);
    //sourceImageRに最大値格納したテクスチャをセットしておく
    float3 color_max = sourceImageR[zero].rgb;

    //destinatinImageR/Iに正規化したいテクスチャをセットしておく
    float3 colorR = destinationImageR[index].rgb;
    float3 colorI = destinationImageI[index].rgb;

    colorR = colorR / color_max;
    colorI = colorI / color_max;

    float col_max_per = color_max.r;
    col_max_per = max(col_max_per, color_max.g);
    col_max_per = max(col_max_per, color_max.b);

    float3 ratio = color_max / col_max_per;

    colorR = colorR * ratio;
    colorI = colorI * ratio;

    destinationImageR1[index] = float4(colorR, 1.0f);
    destinationImageI1[index] = float4(colorI, 1.0f);
}

//raiseRICSに対応
#pragma kernel mainRaiseBottomRealImage
[numthreads(WIDTH, 1, 1)]
void mainRaiseBottomRealImage(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;
    float3 inputR = sourceImageR[index].rgb;
    float3 inputI = sourceImageI[index].rgb;

    float3 amplitude = float3(sqrt(inputR.r * inputR.r + inputI.r * inputI.r)
        , sqrt(inputR.g * inputR.g + inputI.g * inputI.g)
        , sqrt(inputR.b * inputR.b + inputI.b * inputI.b));

    if ((amplitude.r + amplitude.g + amplitude.b) / 3.0f < 0.9)
    {
        /*inputR = inputR * 10.0f;
        inputI = inputI * 10.0f;*/
        inputR = inputR * computeConstants[0].glareintensity;
        inputI = inputI * computeConstants[0].glareintensity;
    }

    if ((inputR.r + inputR.g + inputR.b) / 3.0f >= 1.0)
    {
        inputR = float3(1.0, 1.0, 1.0);
    }

    if ((inputI.r + inputI.g + inputI.b) / 3.0f >= 1.0)
    {
        inputI = float3(1.0, 1.0, 1.0);
    }

    destinationImageR[index] = float4(inputR, 1.0);
    destinationImageI[index] = float4(inputI, 1.0);
}

//spectrumScalingCSに対応
#pragma kernel mainSpectrumScaling
[numthreads(WIDTH, 1, 1)]
void mainSpectrumScaling(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 indexR = dispatchID.xy;

    float ratioRG = computeConstants[0].lambdaG / computeConstants[0].lambdaR;
    float ratioRB = computeConstants[0].lambdaB / computeConstants[0].lambdaR;

    float2 uvR = indexR - float2(0.5 * WIDTH, 0.5 * HEIGHT);
    float2 uvG = uvR * ratioRG;
    float2 uvB = uvR * ratioRB;

    float2 indexG = uvG + float2(0.5 * WIDTH, 0.5 * HEIGHT);
    float2 indexB = uvB + float2(0.5 * WIDTH, 0.5 * HEIGHT);

    float r = sourceImageR[indexR].r;
    float g = sourceImageR[indexG].g;
    float b = sourceImageR[indexB].b;

    destinationImageR[indexR] = float4(r, g, b, 1.0);

    r = sourceImageI[indexR].r;
    g = sourceImageI[indexG].g;
    b = sourceImageI[indexB].b;

    destinationImageI[indexR] = float4(r, g, b, 1.0);
}

//mulCSに対応
#pragma kernel mainMULTIPLY
[numthreads(WIDTH, 1, 1)]
void mainMULTIPLY(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;

    float3 color_real1 = sourceImageR[index].rgb;//元1 実部
    float3 color_image1 = sourceImageI[index].rgb;//元1 虚部

    float3 color_real2 = destinationImageR[index].rgb;//元2 実部
    float3 color_image2 = destinationImageI[index].rgb;//元2 虚部

    //各色からの複素数生成
    float2 compR1 = float2(color_real1.r, color_image1.r);
    float2 compG1 = float2(color_real1.g, color_image1.g);
    float2 compB1 = float2(color_real1.b, color_image1.b);

    float2 compR2 = float2(color_real2.r, color_image2.r);
    float2 compG2 = float2(color_real2.g, color_image2.g);
    float2 compB2 = float2(color_real2.b, color_image2.b);

    //各色毎に乗算
    float2 mulcompR = complex_mul(compR1, compR2);
    float2 mulcompG = complex_mul(compG1, compG2);
    float2 mulcompB = complex_mul(compB1, compB2);

    //代入用の複素数作成
    float3 colorREAL = float3(mulcompR.x, mulcompG.x, mulcompB.x);
    float3 colorIMAGE = float3(mulcompR.y, mulcompG.y, mulcompB.y);

    destinationImageR1[index] = float4(colorREAL, 1.0f);
    destinationImageI1[index] = float4(colorIMAGE, 1.0f);
}

//AddCSに対応
#pragma kernel mainAdd
[numthreads(WIDTH, 1, 1)]
void mainAdd(uint3 dispatchID : SV_DispatchThreadID)
{
    float2 index = dispatchID.xy;
    float3 input1 = sourceImageR[index].rgb;
    float3 input2 = sourceImageI[index].rgb;

    float3 col = input1 + input2;

    destinationImageR[index] = float4(col, 1.0f);
}

感想

真ん中だけ妙にグレアってるのは何ででしょうか。

こっちの差分コード実装し忘れました

ポストエフェクトクエスト - 波長方向の積分マジ大事という話 - - Qiita

参考

ポストエフェクトクエスト - 波動と回折とレンズと使われしシェーダ - - Qiita

DirectX* 11 のイメージ処理における高速フーリエ変換 | iSUS