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

テッセレーションとディスプレースメントマップ

環境

Unity2021.2.18f1

概要

テッセレーションシェーダによるディスプレースメントマップです。

テッセレーションで距離により分割数を変えています。

普通に処理するとカメラを移動した時に、分割が変わる部分が見えてしまい見苦しい画面になってしまいました。

なので距離に応じてディスプレースの変位値を変えています。

また上に盛り上げるのではなくパララックスオクルージョンマップのように下にえぐるようにしています。

Shader "Custom/Tessellation"
{

    Properties
    {
        _Color("Color", color) = (1, 1, 1, 0)
        _MainTex("Base (RGB)", 2D) = "white" {}
        _DispTex("Disp Texture", 2D) = "gray" {}
        _NormalTex("Normal Texture", 2D) = "white" {}
        _MinDist("Min Distance", Range(0.1, 1000)) = 10
        _MaxDist("Max Distance", Range(0.1, 1000)) = 25
        _TessFactor("Tessellation", Range(1, 200)) = 100 //分割レベル
        _Displacement("Displacement", Range(0, 10.0)) = 1.0 //変位
        [KeywordEnum(integer, fractional_even, fractional_odd, pow2)] _Partitioning("Partitioning", int) = 0
        [KeywordEnum(Off, On)] _LerpDisplacementEnabled("Lerp Displacement Enabled", Float) = 1
    }
    SubShader
    {

        Tags
        {
            "RenderType" = "Opaque"
            "LightMode" = "ForwardBase"
        }

        Pass
        {
            CGPROGRAM
            #pragma shader_feature _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD _PARTITIONING_POW2
            #pragma shader_feature _ _LERPDISPLACEMENTENABLED_ON

            #pragma vertex vert
            #pragma fragment frag
            #pragma hull hull
            #pragma domain domain

            #include "Tessellation.cginc"
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            #define INPUT_PATCH_SIZE 3
            #define OUTPUT_PATCH_SIZE 3

            float _TessFactor;
            float _Displacement;
            float _MinDist;
            float _MaxDist;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float2 _MainTex_TexelSize;
            sampler2D _DispTex;
            float2 _DispTex_TexelSize;
            sampler2D _NormalTex;
            float2 _Normal_TexelSize;
            fixed4 _Color;

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

            struct HsInput
            {
                float4 position : POS;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
                float2 texCoord : TEXCOORD;
            };

            struct HsControlPointOutput
            {
                float3 position : POS;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
                float2 texCoord : TEXCOORD;
            };

            struct HsConstantOutput
            {
                float tessFactor[3] : SV_TessFactor;
                float insideTessFactor : SV_InsideTessFactor;
            };

            struct DsOutput
            {
                float4 position : SV_Position;
                float2 uv : TEXCOORD0;
                float3 lightDirTS : TEXCOORD1;
                float3 viewDirTS : TEXCOORD2;
            };

            HsInput vert(appdata i)
            {
                HsInput o;
                o.position = float4(i.vertex, 1.0);
                o.normal = i.normal;
                o.tangent = i.tangent;
                o.texCoord = TRANSFORM_TEX(i.texcoord, _MainTex);
                return o;
            }

            [domain("tri")]
#if _PARTITIONING_INTEGER
            [partitioning("integer")]
#elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")]
#elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]
#else//elif _PARTITIONING_POW2
            [partitioning("pow2")]
#endif
            [outputtopology("triangle_cw")]
            [patchconstantfunc("hullConst")]
            [outputcontrolpoints(OUTPUT_PATCH_SIZE)]
            HsControlPointOutput hull(InputPatch<HsInput, INPUT_PATCH_SIZE> i, uint id : SV_OutputControlPointID)
            {
                HsControlPointOutput o = (HsControlPointOutput)0;
                o.position = i[id].position.xyz;
                o.normal = i[id].normal;
                o.tangent = i[id].tangent;
                o.texCoord = i[id].texCoord;
                return o;
            }

            HsConstantOutput hullConst(InputPatch<HsInput, INPUT_PATCH_SIZE> i)
            {
                HsConstantOutput o = (HsConstantOutput)0;

                float4 p0 = i[0].position;
                float4 p1 = i[1].position;
                float4 p2 = i[2].position;
                float4 tessFactor = UnityDistanceBasedTess(p0, p1, p2, _MinDist, _MaxDist, _TessFactor);

                o.tessFactor[0] = tessFactor.x;
                o.tessFactor[1] = tessFactor.y;
                o.tessFactor[2] = tessFactor.z;
                o.insideTessFactor = tessFactor.w;

                return o;
            }

            [domain("tri")]
            DsOutput domain(HsConstantOutput hsConst, const OutputPatch<HsControlPointOutput, OUTPUT_PATCH_SIZE> i, float3 bary : SV_DomainLocation)
            {
                DsOutput o = (DsOutput)0;

                float3 position =
                    bary.x * i[0].position +
                    bary.y * i[1].position +
                    bary.z * i[2].position;

                float3 normal = normalize(
                    bary.x * i[0].normal +
                    bary.y * i[1].normal +
                    bary.z * i[2].normal);

                float3 tangent = normalize(
                    bary.x * i[0].tangent +
                    bary.y * i[1].tangent +
                    bary.z * i[2].tangent);

                o.uv =
                    bary.x * i[0].texCoord +
                    bary.y * i[1].texCoord +
                    bary.z * i[2].texCoord;

#if _LERPDISPLACEMENTENABLED_ON
                float3 positionVS = UnityObjectToViewPos(float4(position, 1));
                float dispPower = lerp(_Displacement, 0, saturate(-positionVS.z / _MaxDist));
                position = (dispPower > 0) ? position + -normal * (1.0 - tex2Dlod(_DispTex, float4(o.uv, 0, 0)).r) * dispPower : position;
#else
                float disp = tex2Dlod(_DispTex, float4(o.uv, 0, 0)).r * _Displacement;
                position.xyz += normal * disp;
#endif
                o.position = UnityObjectToClipPos(float4(position, 1.0));

                float3 lightDirOS = ObjSpaceLightDir(float4(position, 1));
                float3 viewDirOS = ObjSpaceViewDir(float4(position, 1));

                float3 binormal = cross(normal, tangent.xyz);
                float3x3 rotation = float3x3(tangent.xyz, binormal, normal);
                o.lightDirTS = mul(rotation, lightDirOS);
                o.viewDirTS = mul(rotation, viewDirOS);
                return o;
            }

            fixed4 frag(DsOutput i) : SV_Target
            {
                float3 normalTS = UnpackNormal(tex2D(_NormalTex, i.uv));
                float3 lightDirTS = normalize(i.lightDirTS);
                float3 viewDirTS = normalize(i.viewDirTS);
                float3 halfDirTS = normalize(lightDirTS + viewDirTS);
                float diffuse = dot(normalTS, lightDirTS) * 0.5 + 0.5;
                float speculer = pow(saturate(dot(normalTS, halfDirTS) * 0.5 + 0.5), 40);
                return diffuse * tex2D(_MainTex, i.uv) * _LightColor0 + speculer * _LightColor0;
            }
            ENDCG
        }
    }
}

DrawProcedualで1マテでスキニングモデルの一括描画テスト

環境

Unity2021.2.18f1

burst1.6.5

jobs0.50.0-preview.8

概要

DrawProcedualの実験です。 多数のスキニングモデルを1マテリアルで一括描画してみるテストです。

AnimationInstancingのように1つのモデルを大量に出すのではなく、複数種類のモデルをたくさん出す為のテストです。

面倒だったので使用するスキニングモデルは1スキンモデルのみにしています。

アニメーション毎にボーン行列をGraphicsBufferに保存して、シェーダでスキニング処理をしています。

テクスチャはTexture2DArrayに纏めています。(モデルのテクスチャはベーステクスチャのみで全部同じサイズ)

また、無駄にComputeShaderやJobSystemを使用しています。

※モデルはオプティマイズオプションを無効にしないと実機でちゃんと表示されませんでした。

ModelManager.cs

using System;
using System.Linq;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using Unity.Jobs;
using Unity.Burst;

public class ModelManager: MonoBehaviour
{
    [SerializeField] private ComputeShader _computeShader = null;
    [SerializeField] private Material _material = null;
    [SerializeField] private GameObject[] _prefabs = null;
    private class ModelCreateParam
    {
        //Vertices
        public int vertexOffset = 0;
        public GraphicsBuffer[] gbVertexStreams;
        public int vertexCount = 0;
        //Indices
        public int indexOffset = 0;
        public GraphicsBuffer gbIndices;
        //Textures
        public int textureOffset = 0;
        public Texture[] textures = null;
        //Animations
        public int animationClipOffset = 0;
        public int boneCount = 0;
        public AnimationClipCreateParam[] animationClipCreateParams = null;
    }
    private List<ModelCreateParam> _modelCreateParams = new List<ModelCreateParam>();
    private struct ModelParam
    {
        public int vertexOffset;
        public int indexOffset;
        public int textureOffset;
        public int animationClipOffset;
        public int boneCount;
    }
    private NativeArray<ModelParam> _modelParams;
    private int _vertexStride = 0;
    private GraphicsBuffer _gbVertices = null;

    private class AnimationClipCreateParam
    {
        public int nameHash = 0;
        public int frameCount = 0;
        public int boneMatrixOffset = 0;
        public Matrix4x4[] boneMatrices = null;
    }
    private struct AnimationClipParam
    {
        public int nameHash;
        public int frameCount;
        public int boneMatrixOffset;
    }
    NativeArray<AnimationClipParam> _animationClipParams;

    private GraphicsBuffer _gbBoneMatrices = null;

    private class TextureParam
    {
        public int spTextureId;
        public Texture2DArray textureArray = null;
    }
    private static TextureParam[] _textureParams = 
    {
        new TextureParam(){spTextureId = Shader.PropertyToID("_MainTex") },
    };
    private static int _spSrcVertices = Shader.PropertyToID("SrcVertices");
    private static int _spSrcVertexCount = Shader.PropertyToID("SrcVertexCount");
    private static int _spSrcStride = Shader.PropertyToID("SrcStride");
    private static int _spDestVertices = Shader.PropertyToID("DestVertices");
    private static int _spDestStride = Shader.PropertyToID("DestStride");
    private static int _spDestOffsetVertex = Shader.PropertyToID("DestOffsetVertex");
    private static int _spDestOffsetInStride = Shader.PropertyToID("DestOffsetInStride");

    private static int _spDestIndices = Shader.PropertyToID("DestIndices");
    private static int _spDestOffsetIndex = Shader.PropertyToID("DestOffsetIndex");
    private static int _spSrcIndexCount = Shader.PropertyToID("SrcIndexCount");
    private static int _spSrcUnitIndex = Shader.PropertyToID("SrcUnitIndex");
    private static int _spSrcVertexOffset = Shader.PropertyToID("SrcVertexOffset");
    
    private static int _spSrcIndices = Shader.PropertyToID("SrcIndices");

    private static int _spVerticesId = Shader.PropertyToID("_Vertices");
    private static int _spBoneMatricesId = Shader.PropertyToID("_BoneMatrices");
    private static int _spUnitDescsId = Shader.PropertyToID("_UnitDescs");
    private static int _spUnitUpdateDescsId = Shader.PropertyToID("_UnitUpdateDescs");
    private bool _modelCreateParamDirty = false;

    [BurstCompile(CompileSynchronously = true)]
    private struct UnitParam
    {
        public int modelId;
        public int animationId;
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 scale;
        public float animationTime;

        private enum State
        {
            Wait,
            Walk,
        }
        private State state;
        private Vector3 basePosition;
        private float waitTime;
        private Vector3 destPosition;
        private Unity.Mathematics.Random random;

        public void Setup()
        {
            random = new Unity.Mathematics.Random((uint)UnityEngine.Random.Range(1, 65535));
            basePosition = position;
            state = State.Wait;
            waitTime = UnityEngine.Random.Range(0.0f, 3.0f);
        }

        [BurstCompile(CompileSynchronously = true)]
        public unsafe void Update(float deltaTime)
        {
            if(state == State.Wait)
            {
                if(waitTime > 0)
                {
                    waitTime -= deltaTime;
                    return;
                }
                state = State.Walk;
                var q = Quaternion.Euler(0.0f, random.NextFloat(0, 360), 0.0f);
                var v = q * new Vector3(0, 0, 10);
                destPosition = basePosition + v;
            }
            else if(state == State.Walk)
            {
                var v = destPosition - position;
                const float minDist = 0.5f;
                if(v.sqrMagnitude < minDist * minDist)
                {
                    state = State.Wait;
                    waitTime = 1.0f;
                    return;
                }
                var destRot = Quaternion.LookRotation(v);
                const float rotSpeed = 3.0f;
                rotation = Quaternion.SlerpUnclamped(rotation, destRot, rotSpeed * deltaTime);
                var forward = rotation * Vector3.forward;

                const float moveSpeed = 1.0f;
                position += forward * moveSpeed * deltaTime;
            }
        }
    }
    private NativeList<UnitParam> _units;
    [BurstCompile(CompileSynchronously = true)]
    struct JobUnits : IJobParallelFor
    {
        public NativeArray<UnitParam> units;
        public NativeArray<UnitUpdateDesc> unitUpdateDescs;
        [ReadOnly] public NativeArray<ModelParam> modelParams;
        [ReadOnly] public NativeArray<AnimationClipParam> animationClipParams;
        [ReadOnly] public float deltaTime;

        public unsafe void Execute(int index)
        {
            var pUnits = (UnitParam*)NativeArrayUnsafeUtility.GetUnsafePtr(units);
            var pUnit = &pUnits[index];
            pUnit->Update(deltaTime);
            UpdateUnitDesc(pUnit, index);
        }

        private unsafe void UpdateUnitDesc(UnitParam* pUnit, int index)
        {
            var pUnitUpdateDesc = (UnitUpdateDesc*)NativeArrayUnsafeUtility.GetUnsafePtr(unitUpdateDescs);
            var modelParam = modelParams[pUnit->modelId];
            var animParam =  animationClipParams[modelParam.animationClipOffset + pUnit->animationId];
            var animFrame = (int)(pUnit->animationTime * _animationFrameRate);
            pUnit->animationTime = Mathf.Repeat(pUnit->animationTime + deltaTime, (float)animParam.frameCount / (float)_animationFrameRate);
            pUnitUpdateDesc[index].boneMatrixId = animParam.boneMatrixOffset + animFrame * modelParam.boneCount;
            pUnitUpdateDesc[index].worldMatrix = Matrix4x4.TRS(pUnit->position, pUnit->rotation, pUnit->scale);
        }
    }
    private JobHandle _jhUnits;

    private bool _unitDirty = false;
    private GraphicsBuffer _gbIndices = null;
    private GraphicsBuffer _gbArgs = null;

    private struct UnitDesc
    {
        public int modelId;//textureId
    }
    private GraphicsBuffer _gbUnitDescs = null;
    private struct UnitUpdateDesc
    {
        public Matrix4x4 worldMatrix;
        public int boneMatrixId;
    }
    private NativeArray<UnitUpdateDesc> _unitUpdateDescs;
    private GraphicsBuffer _gbUnitUpdateDescs = null;
    private Bounds _bounds;
    private const int _animationFrameRate = 30;

    private void Start()
    {
        for(int i = 0; i < _prefabs.Length; i++)
        {
            CreateModelCreateParam(_prefabs[i]);
        }
        CreateTextureArrays();
        _units = new NativeList<UnitParam>(Allocator.Persistent);
        for(int i = 0; i < 1000; i++)
        {
            var x = (i % 25 - 12) * 2.0f;
            var z = (i / 25 - 12) * 2.0f;
            var modelId = i %  _modelCreateParams.Count;
            var animationId = Animator.StringToHash("idle");
            CreateUnit(modelId, animationId, new Vector3(x, 0.0f, z), Quaternion.identity, Vector3.one);
        }
        _bounds = new Bounds(Vector3.zero, new Vector3(1000.0f, 1000.0f, 1000.0f));// mesh.bounds;
    }

    private void OnDestroy()
    {
        _jhUnits.Complete();

        for(int i = 0; i < _modelCreateParams.Count; i++)
        {
            DestroyModelCreateParam(_modelCreateParams[i]);
        }
        if(_gbVertices != null)
            _gbVertices.Dispose();
        if(_gbIndices != null)
            _gbIndices.Dispose();
        if(_gbArgs != null)
            _gbArgs.Dispose();
        if(_gbBoneMatrices != null)
            _gbBoneMatrices.Dispose();
        if(_gbUnitDescs != null)
            _gbUnitDescs.Dispose();
        if(_gbUnitUpdateDescs != null)
            _gbUnitUpdateDescs.Dispose();
        if(_unitUpdateDescs.IsCreated == true)
            _unitUpdateDescs.Dispose();
        if(_units.IsCreated == true)
            _units.Dispose();
        if(_modelParams.IsCreated == true)
            _modelParams.Dispose();
        if(_animationClipParams.IsCreated == true)
            _animationClipParams.Dispose();
    }

    private unsafe void CreateModelCreateParam(GameObject prefab)
    {
        _modelCreateParamDirty = true;
        var vertexOffset = 0;
        var indexOffset = 0;
        var animationClipOffset = 0;
        if(_modelCreateParams.Count > 0)
        {
            var lastModelCreateParam = _modelCreateParams[_modelCreateParams.Count - 1];
            vertexOffset = lastModelCreateParam.vertexOffset + lastModelCreateParam.vertexCount;
            indexOffset = lastModelCreateParam.indexOffset + lastModelCreateParam.gbIndices.count;
            animationClipOffset = lastModelCreateParam.animationClipOffset + lastModelCreateParam.animationClipCreateParams.Length;
        }

        Mesh mesh;
        Renderer renderer;
        var skinnedMeshRenrerer = prefab.GetComponentInChildren<SkinnedMeshRenderer>();
        if(skinnedMeshRenrerer != null)
        {
            mesh = skinnedMeshRenrerer.sharedMesh;
            renderer = skinnedMeshRenrerer;
        }
        else
        {
            var meshFilter = prefab.GetComponentInChildren<MeshFilter>();
            mesh = meshFilter.sharedMesh;
            renderer = meshFilter.GetComponent<Renderer>();
        }
        var vertexStreamNum = 0;
        var attrs = mesh.GetVertexAttributes();
        vertexStreamNum = attrs[attrs.Length - 1].stream + 1;
        var gbVertexStreams = new GraphicsBuffer[vertexStreamNum];
        for(int i = 0; i < vertexStreamNum; i++)
        {
            gbVertexStreams[i] = mesh.GetVertexBuffer(i);
            if((gbVertexStreams[i].target & GraphicsBuffer.Target.Raw) == 0)
            {
                var count = gbVertexStreams[i].count;
                var stride = gbVertexStreams[i].stride;
                var temp = new byte[count, stride];
                gbVertexStreams[i].GetData(temp);
                gbVertexStreams[i].Dispose();
                gbVertexStreams[i] = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, count, stride);
                gbVertexStreams[i].SetData(temp);
            }
        }

        var vertexCount = gbVertexStreams[0].count;
        var vertexStride = 0;
        for(int i = 0; i < vertexStreamNum; i++)
            vertexStride += gbVertexStreams[i].stride;
        if(_vertexStride == 0)
            _vertexStride = vertexStride;
        if(_vertexStride != vertexStride)
            Debug.LogError($"_vertexStride != vertexStride prefab:{prefab.name}");
        var gbIndices = mesh.GetIndexBuffer();
        var sourceIndices = new UInt16[gbIndices.count];
        gbIndices.GetData(sourceIndices);
        fixed(UInt16* pSrcIndices = sourceIndices)
        using(var destIndices = new NativeArray<UInt32>(gbIndices.count, Allocator.Temp))
        {
            var pDestIndices = (UInt32*)NativeArrayUnsafeUtility.GetUnsafePtr(destIndices);
#if true
            UnsafeUtility.MemCpyStride(pDestIndices, sizeof(UInt32), pSrcIndices, sizeof(UInt16), sizeof(UInt16), gbIndices.count);
#else
            for(int i = 0; i< sourceIndices.Length; i++)
                pDestIndices[i] = /*(UInt32)vertexOffset + */(UInt32)pSrcIndices[i];
#endif
            gbIndices.Dispose();
            gbIndices = new GraphicsBuffer(GraphicsBuffer.Target.Index | GraphicsBuffer.Target.Raw, destIndices.Length, sizeof(UInt32));
            gbIndices.SetData(destIndices);
        }

        var material = renderer.sharedMaterial;
        var textures = new Texture[_textureParams.Length];
        for(int  i = 0; i < _textureParams.Length; i++)
        {
            var textureParam = _textureParams[i];
            textures[i] = material.GetTexture(textureParam.spTextureId);
        }

        var modelCreateParam = new ModelCreateParam();
        modelCreateParam.vertexOffset = vertexOffset;
        modelCreateParam.gbVertexStreams = gbVertexStreams;
        modelCreateParam.vertexCount = vertexCount;
        modelCreateParam.indexOffset = indexOffset;
        modelCreateParam.gbIndices = gbIndices;
        modelCreateParam.textureOffset = _modelCreateParams.Count - 1;
        modelCreateParam.textures = textures;
        modelCreateParam.animationClipOffset = animationClipOffset;
        modelCreateParam.boneCount = skinnedMeshRenrerer.bones.Length;
        if(skinnedMeshRenrerer != null)
            CreateAnimations(modelCreateParam, prefab, skinnedMeshRenrerer);

        _modelCreateParams.Add(modelCreateParam);
    }

    private void CreateAnimations(ModelCreateParam ModelCreateParam, GameObject prefab, SkinnedMeshRenderer skinnedMeshRenderer)
    {
        var boneMatrixOffset = 0;
        if(_modelCreateParams.Count > 0)
        {
            var lastModelCreateParam = _modelCreateParams[_modelCreateParams.Count - 1];
            var lastAnimationParam = lastModelCreateParam.animationClipCreateParams[lastModelCreateParam.animationClipCreateParams.Length - 1];
            boneMatrixOffset = lastAnimationParam.boneMatrixOffset + lastAnimationParam.boneMatrices.Length;
        }

        var animParams = new List<AnimationClipCreateParam>();
        var animator = prefab.GetComponentInChildren<Animator>();
        var animClips = animator.runtimeAnimatorController.animationClips;
        ModelCreateParam.animationClipCreateParams = new AnimationClipCreateParam[animClips.Length];

        var prevPrefabPos = prefab.transform.localPosition;
        var prevPrefabRot = prefab.transform.localRotation;
        var prevPrefabScale = prefab.transform.localScale; 
        prefab.transform.position = Vector3.zero;
        prefab.transform.rotation = Quaternion.identity;
        for(int i = 0; i < animClips.Length; i++)
        {
            var animParam = new AnimationClipCreateParam();
            var animClip = animClips[i];
            animParam.nameHash = Animator.StringToHash(animClip.name.ToLower());
            animParam.boneMatrixOffset = boneMatrixOffset;
            animParam.frameCount = (int)(animClip.length * _animationFrameRate);
            animParam.boneMatrices = new Matrix4x4[animParam.frameCount * skinnedMeshRenderer.bones.Length];
            var matrixIndex = 0;
            for(int frame = 0; frame < animParam.frameCount; frame++)
            {
                animClip.SampleAnimation(prefab, (float)frame / _animationFrameRate);
                for(int boneIndex = 0; boneIndex < skinnedMeshRenderer.bones.Length; boneIndex++)
                {
                    var bone = skinnedMeshRenderer.bones[boneIndex];
                    animParam.boneMatrices[matrixIndex] = bone.localToWorldMatrix * skinnedMeshRenderer.sharedMesh.bindposes[boneIndex];
                    matrixIndex++;
                }
            }
            boneMatrixOffset += animParam.boneMatrices.Length;
            ModelCreateParam.animationClipCreateParams[i] = animParam;
        }
        prefab.transform.localPosition = prevPrefabPos;
        prefab.transform.localRotation = prevPrefabRot;
        prefab.transform.localScale = prevPrefabScale; 
        animClips[0].SampleAnimation(prefab, (float)0.0f);
    }

    private void DestroyModelCreateParam(ModelCreateParam ModelCreateParam)
    {
        for(int i = 0; i < ModelCreateParam.gbVertexStreams.Length; i++)
        {
            if(ModelCreateParam.gbVertexStreams[i] != null)
                ModelCreateParam.gbVertexStreams[i].Dispose();
        }
        if(ModelCreateParam.gbIndices != null)
            ModelCreateParam.gbIndices.Dispose();
    }

    private void CreateTextureArrays()
    {
        if(_modelCreateParamDirty == false)
            return;
        var baseModelCreateParam = _modelCreateParams[0];
        for(int i = 0; i < _textureParams.Length; i++)
        {
            var textureParam = _textureParams[i];
            var baseTexture = baseModelCreateParam.textures[i] as Texture2D;
            textureParam.textureArray = new Texture2DArray(baseTexture.width, baseTexture.height,  _modelCreateParams.Count, baseTexture.format, baseTexture.mipmapCount, false);
            for(int j = 0; j < _modelCreateParams.Count; j++)
            {
                var modelCreateParam = _modelCreateParams[j];
                var srcTex = modelCreateParam.textures[i] as Texture2D;
                for(int m = 0; m < srcTex.mipmapCount; m++)
                {
                    var pixelData = srcTex.GetPixelData<Color32>(m);
                    textureParam.textureArray.SetPixelData<Color32>(pixelData, m, j);
                }
            }
            textureParam.textureArray.Apply(updateMipmaps: false);
        }
    }

    public int CreateUnit(int modelId, int animationNameToHash, Vector3 position, Quaternion rotation, Vector3 scale)
    {
        var unit = new UnitParam();
        unit.modelId = modelId;
        unit.animationId = _modelCreateParams[unit.modelId].animationClipCreateParams.Select((x,i) => (x, i )).Where(y => y.x.nameHash == animationNameToHash).Select(z => z.i).FirstOrDefault();
        unit.position = position;
        unit.rotation = rotation;
        unit.scale = scale;
        unit.animationTime = 0.0f;
        unit.Setup();
        _units.Add(unit);
        _unitDirty = true;
        return _units.Length - 1;
    }

    private unsafe void LateUpdate()
    {
        if(_unitUpdateDescs.IsCreated == true)
        {
            _jhUnits.Complete();
            _gbUnitUpdateDescs.SetData(_unitUpdateDescs);
            if(_gbArgs != null)
                Graphics.DrawProceduralIndirect(_material, _bounds, MeshTopology.Triangles, _gbIndices, _gbArgs);
        }

        var unitCount = _units.Length;
        if(_modelCreateParamDirty == true)
        {
            _modelCreateParamDirty = false;
            //Vertices
            if(_gbVertices != null)
                _gbVertices.Dispose();
            var lastModelCreateParam = _modelCreateParams[_modelCreateParams.Count - 1];
            var vertexCount = lastModelCreateParam.vertexOffset + lastModelCreateParam.vertexCount;
            _gbVertices = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, (int)vertexCount,  _vertexStride);
            var kernelIndex = _computeShader.FindKernel("GenerateVertices");
            uint numThreadX, numThreadY, numThreadZ;
            _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
            _computeShader.SetBuffer(kernelIndex, _spDestVertices, _gbVertices);
            _computeShader.SetInt(_spDestStride, _vertexStride);
            for(int i = 0; i < _modelCreateParams.Count; i++)
            {
                var modelCreateParam = _modelCreateParams[i];
                _computeShader.SetInt(_spDestOffsetVertex, modelCreateParam.vertexOffset * _vertexStride);
                _computeShader.SetInt(_spSrcVertexCount, modelCreateParam.vertexCount);
                var groupLoopCount = (modelCreateParam.vertexCount + (int)(numThreadY - 1)) / (int)numThreadY;
                var destOffsetInStride = 0;
                for(int j = 0; j < modelCreateParam.gbVertexStreams.Length; j++)
                {
                    var srcStride = modelCreateParam.gbVertexStreams[j].stride;
                    _computeShader.SetBuffer(kernelIndex, _spSrcVertices, modelCreateParam.gbVertexStreams[j]);
                    _computeShader.SetInt(_spSrcStride, srcStride);
                    _computeShader.SetInt(_spDestOffsetInStride, destOffsetInStride);
                    _computeShader.Dispatch(kernelIndex, srcStride / 4, groupLoopCount, 1);
                    destOffsetInStride += srcStride;
                }
            }
            _material.SetBuffer(_spVerticesId, _gbVertices);
            //ModelParams
            if(_modelParams.IsCreated == true)
                _modelParams.Dispose();
            _modelParams = new NativeArray<ModelParam>(_modelCreateParams.Count, Allocator.Persistent);
            var pModelParams = (ModelParam*)NativeArrayUnsafeUtility.GetUnsafePtr(_modelParams);
            for(int i = 0; i < _modelCreateParams.Count; i++)
            {
                var modelCreateParam = _modelCreateParams[i];
                pModelParams[i].vertexOffset = modelCreateParam.vertexOffset;
                pModelParams[i].indexOffset = modelCreateParam.indexOffset;
                pModelParams[i].textureOffset = modelCreateParam.textureOffset;
                pModelParams[i].animationClipOffset = modelCreateParam.animationClipOffset;
                pModelParams[i].boneCount = modelCreateParam.boneCount;
            }
            //TextureArray
            for(int i = 0; i < _textureParams.Length; i++)
            {
                var textureParam = _textureParams[i];
                _material.SetTexture(textureParam.spTextureId, textureParam.textureArray);
            }
            // Animations
            if(_gbBoneMatrices != null)
                _gbBoneMatrices.Dispose();
            var lastAnimationClipCreateParam = lastModelCreateParam.animationClipCreateParams[lastModelCreateParam.animationClipCreateParams.Length - 1];
            var boneMatrixCount = lastAnimationClipCreateParam.boneMatrixOffset + lastAnimationClipCreateParam.boneMatrices.Length;
            _gbBoneMatrices = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, (int)boneMatrixCount, sizeof(Matrix4x4));
            for(int i = 0; i < _modelCreateParams.Count; i++)
            {
                var modelCreateParam = _modelCreateParams[i];
                for(int j = 0; j < modelCreateParam.animationClipCreateParams.Length; j++)
                {
                    var animParam = modelCreateParam.animationClipCreateParams[j];
                    _gbBoneMatrices.SetData(animParam.boneMatrices, 0, animParam.boneMatrixOffset, animParam.boneMatrices.Length);
                }
            }
            _material.SetBuffer(_spBoneMatricesId, _gbBoneMatrices);

            var animCliipParamCount = lastModelCreateParam.animationClipOffset + lastModelCreateParam.animationClipCreateParams.Length;
            if(_animationClipParams.IsCreated == true)
                _animationClipParams.Dispose();
            _animationClipParams = new NativeArray<AnimationClipParam>(animCliipParamCount, Allocator.Persistent);
            var pAnimationClipParams = (AnimationClipParam*)NativeArrayUnsafeUtility.GetUnsafePtr(_animationClipParams);
            var index = 0;
            for(int i = 0; i < _modelCreateParams.Count; i++)
            {
                var modelCreateParam = _modelCreateParams[i];
                for(int j = 0; j < modelCreateParam.animationClipCreateParams.Length; j++)
                {
                    var animParam = modelCreateParam.animationClipCreateParams[j];
                    pAnimationClipParams[index].boneMatrixOffset = animParam.boneMatrixOffset;
                    pAnimationClipParams[index].frameCount = animParam.frameCount;
                    pAnimationClipParams[index].nameHash = animParam.nameHash;
                    index++;
                }
            }
        }
        if(_unitDirty == true)
        {
            _unitDirty = false;
            //Indices
            if(_gbIndices != null)
                _gbIndices.Dispose();
            var indexCount = 0;
            for(int i = 0; i < unitCount; i++)
            {
                var modelCreateParam = _modelCreateParams[_units[i].modelId];
                indexCount += modelCreateParam.gbIndices.count;
            }
            _gbIndices = new GraphicsBuffer(GraphicsBuffer.Target.Index | GraphicsBuffer.Target.Raw, indexCount, sizeof(UInt32));
            var indexOffset = 0;
            var kernelIndex = _computeShader.FindKernel("GenerateIndices");
            uint numThreadX, numThreadY, numThreadZ;
            _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
            _computeShader.SetBuffer(kernelIndex, _spDestIndices, _gbIndices);
            for(int i = 0; i < unitCount; i++)
            {
                var modelCreateParam = _modelCreateParams[_units[i].modelId];
                _computeShader.SetInt(_spDestOffsetIndex, indexOffset * sizeof(UInt32));
                _computeShader.SetInt(_spSrcIndexCount, modelCreateParam.gbIndices.count);
                _computeShader.SetInt(_spSrcUnitIndex, i);
                _computeShader.SetInt(_spSrcVertexOffset, modelCreateParam.vertexOffset);
                _computeShader.SetBuffer(kernelIndex, _spSrcIndices, modelCreateParam.gbIndices);
                var groupLoopCount = (modelCreateParam.gbIndices.count + (int)(numThreadX - 1)) / (int)numThreadX;
                _computeShader.Dispatch(kernelIndex, groupLoopCount, 1, 1);
                indexOffset += modelCreateParam.gbIndices.count;
            }
            //IndirecctArgument
            var argInstanceNum = 1;
            var argIndexOffset = 0;
            var argVertexOffset = 0;
            var argInstanceOffset = 0;  //zero only?
            var args = new int[]{_gbIndices.count, argInstanceNum, argIndexOffset, argVertexOffset, argInstanceOffset};
            var stride = Buffer.ByteLength(args);
            if(_gbArgs != null)
                _gbArgs.Dispose();
            _gbArgs = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, stride);
            _gbArgs.SetData(args);
            //UnitDesc
            if(_gbUnitDescs != null)
                _gbUnitDescs.Dispose();
            _gbUnitDescs = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, unitCount, sizeof(UnitDesc));
            using(var unitDescs = new NativeArray<UnitDesc>(unitCount, Allocator.Temp))
            {
                var pUnitDesc = (UnitDesc*)NativeArrayUnsafeUtility.GetUnsafePtr(unitDescs);
                for(int i = 0; i < unitCount; i++)
                {
                    var unit = _units[i];
                    pUnitDesc[i].modelId = unit.modelId;
                }
                _gbUnitDescs.SetData(unitDescs);
            }
            _material.SetBuffer(_spUnitDescsId, _gbUnitDescs);
            //UnitUpdateDesc
            if(_gbUnitUpdateDescs != null)
                _gbUnitUpdateDescs.Dispose();
            _gbUnitUpdateDescs = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, unitCount, sizeof(UnitUpdateDesc));
            if(_unitUpdateDescs.IsCreated == true)
                _unitUpdateDescs.Dispose();
            _unitUpdateDescs = new NativeArray<UnitUpdateDesc>(unitCount, Allocator.Persistent);
            _material.SetBuffer(_spUnitUpdateDescsId, _gbUnitUpdateDescs);
        }

        var job = new JobUnits()
        {
            units = _units.AsDeferredJobArray(),
            unitUpdateDescs = _unitUpdateDescs,
            modelParams = _modelParams,
            animationClipParams = _animationClipParams,
            deltaTime = Time.deltaTime,
        };
        _jhUnits = job.Schedule(_units.Length, 1);
        JobHandle.ScheduleBatchedJobs();
    }
}

TestDrawProcedual.compute

#pragma kernel GenerateVertices

ByteAddressBuffer SrcVertices;
uint SrcVertexCount;
int SrcStride;
RWByteAddressBuffer DestVertices;
int DestStride;
int DestOffsetVertex;
int DestOffsetInStride;

[numthreads(1,32,1)]
void GenerateVertices(uint2 id : SV_DispatchThreadID)
{
    if (id.y >= SrcVertexCount)
        return;
    int srcOffsetVertex = id.y * SrcStride;
    int destOffsetVertex = DestOffsetVertex + id.y * DestStride;

    int offsetInStride = id.x * 4;
    int destOffsetInStride = DestOffsetInStride + offsetInStride;
    int value = SrcVertices.Load(srcOffsetVertex + offsetInStride);
    DestVertices.Store(destOffsetVertex + destOffsetInStride, value);
}

#pragma kernel GenerateIndices

ByteAddressBuffer SrcIndices;
uint SrcIndexCount;
uint SrcUnitIndex;
uint SrcVertexOffset;
RWByteAddressBuffer DestIndices;
int DestOffsetIndex;

[numthreads(128, 1, 1)]
void GenerateIndices(uint id : SV_DispatchThreadID)
{
    if (id.x >= SrcIndexCount)
        return;
    int destOffsetIndex = DestOffsetIndex + id.x * 4;
    int srcOffsetIndex = id.x * 4;
    uint value = SrcIndices.Load(srcOffsetIndex);
    value = (SrcUnitIndex << 22) | ((SrcVertexOffset + value) & 0x3fffff);
    DestIndices.Store(destOffsetIndex, value);
}

TestDrawProcedual.shader

Shader "Test/TestDrawProcedual"
{
    Properties
    {
        _MainTex("Tex", 2DArray) = "" {}
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    #include "AutoLight.cginc"
    #pragma require 2darray

    struct appdata
    {
        uint vertexId : SV_VertexID;
        uint instanceId : SV_InstanceID;
    };

    struct VertexAndNormal
    {
        float4 vertex;
        float3 normal;
    };

    struct VertexStream
    {
        float3 position;
        float3 normal;
        float4 tangent;
        float2 uv;
        float4 blendWeight;
        int4 blendIndices;
    };
    StructuredBuffer<VertexStream> _Vertices;
    StructuredBuffer<float4x4> _BoneMatrices;
    struct UnitDesc
    {
        int modelId;
    };
    StructuredBuffer<UnitDesc> _UnitDescs;
    struct UnitUpdateDesc
    {
        float4x4 worldMatrix;
        int boneMatrixId;
    };
    StructuredBuffer<UnitUpdateDesc> _UnitUpdateDescs;
    UNITY_DECLARE_TEX2DARRAY(_MainTex);

    void GetBoneMatrices(VertexStream vertex, UnitUpdateDesc unitUdapteDesc, out float4x4 boneMatrix0, out float4x4 boneMatrix1, out float4x4 boneMatrix2, out float4x4 boneMatrix3)
    {
        int animMatrixId = unitUdapteDesc.boneMatrixId;
        boneMatrix0 = _BoneMatrices[animMatrixId + vertex.blendIndices.x];
        boneMatrix1 = _BoneMatrices[animMatrixId + vertex.blendIndices.y];
        boneMatrix2 = _BoneMatrices[animMatrixId + vertex.blendIndices.z];
        boneMatrix3 = _BoneMatrices[animMatrixId + vertex.blendIndices.w];
    }

    void ComputeBone(VertexStream vertex, UnitUpdateDesc unitUdapteDesc, out float4 pos, out float3 normal)
    {
        float4x4 boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3;
        GetBoneMatrices(vertex, unitUdapteDesc, boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3);

        pos = float4(vertex.position, 1.0);
        pos =   mul(boneMatrix0, pos) * vertex.blendWeight.x +
                    mul(boneMatrix1, pos) * vertex.blendWeight.y +
                    mul(boneMatrix2, pos) * vertex.blendWeight.z +
                    mul(boneMatrix3, pos) * vertex.blendWeight.w;

        normal =    mul(boneMatrix0, vertex.normal) * vertex.blendWeight.x +
                        mul(boneMatrix1, vertex.normal) * vertex.blendWeight.y +
                        mul(boneMatrix2, vertex.normal) * vertex.blendWeight.z +
                        mul(boneMatrix3, vertex.normal) * vertex.blendWeight.w;
    }

    void ComputeBone(VertexStream vertex, UnitUpdateDesc unitUdapteDesc, out float4 pos)
    {
        float4x4 boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3;
        GetBoneMatrices(vertex, unitUdapteDesc, boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3);

        pos = float4(vertex.position, 1.0);
        pos = mul(boneMatrix0, pos) * vertex.blendWeight.x +
            mul(boneMatrix1, pos) * vertex.blendWeight.y +
            mul(boneMatrix2, pos) * vertex.blendWeight.z +
            mul(boneMatrix3, pos) * vertex.blendWeight.w;
    }

    ENDCG

    SubShader
    {
        Tags{ "LightMode" = "ForwardBase" "RenderType" = "Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma target 5.0
            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 uv : TEXCOORD0;
                SHADOW_COORDS(1)
            };

            v2f vert(appdata i)
            {
                v2f o;
                uint descId = (i.vertexId >> (22)) & 0x3ff;
                uint vertexId = i.vertexId & 0x3fffff;

                UnitDesc unitDesc = _UnitDescs[descId];
                VertexStream vertex = _Vertices[vertexId];
                UnitUpdateDesc unitUdapteDesc = _UnitUpdateDescs[descId];

                VertexAndNormal v;
                ComputeBone(vertex, unitUdapteDesc, v.vertex, v.normal);

                unity_ObjectToWorld = unitUdapteDesc.worldMatrix;
                o.pos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, v.vertex));
                o.uv = float3(vertex.uv, unitDesc.modelId);
                TRANSFER_SHADOW(o)
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.uv);
                fixed shadow = max(SHADOW_ATTENUATION(i), 0.5);
                return col * shadow;
            }
            ENDCG
        }

        Pass
        {
            Name "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}
            Zwrite On
            ZTest LEqual

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #pragma target 5.0

            struct v2f
            {
                float3 uv : TEXCOORD0;
                V2F_SHADOW_CASTER;
            };

            inline float3 CustomObjectToWorldDir(in float3 dir, in float3x3 worldMatrix)
            {
                return normalize(mul(worldMatrix, dir));
            }
            inline float4 CustomClipSpaceShadowCasterPos(in float4 vertex, in float3 normal, in float4x4 worldMatrix)
            {
                float4 wPos = mul(worldMatrix, vertex);
                if (unity_LightShadowBias.z != 0.0)
                {
                    float3 wNormal = CustomObjectToWorldDir(normal, (float3x3)worldMatrix);
                    float3 wLight = normalize(CustomObjectToWorldDir(wPos.xyz, (float3x3)worldMatrix));
                    float shadowCos = dot(wNormal, wLight);
                    float shadowSine = sqrt(1 - shadowCos * shadowCos);
                    float normalBias = unity_LightShadowBias.z * shadowSine;
                    wPos.xyz -= wNormal * normalBias;
                }
                return mul(UNITY_MATRIX_VP, wPos);
            }

            v2f vert(appdata i)
            {
                v2f o;
                uint descId = (i.vertexId >> (22)) & 0x3ff;
                uint vertexId = i.vertexId & 0x3fffff;

                UnitDesc unitDesc = _UnitDescs[descId];
                VertexStream vertex = _Vertices[vertexId];
                UnitUpdateDesc unitUdapteDesc = _UnitUpdateDescs[descId];

                VertexAndNormal v;
                ComputeBone(vertex, unitUdapteDesc, v.vertex, v.normal);

                unity_ObjectToWorld = unitUdapteDesc.worldMatrix;
                o.uv = float3(vertex.uv, unitDesc.modelId);

                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.uv);
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}

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

カメラの開口形状からグレア形状を得る

環境

Unity2021.2.18f1

概要

FFTを用いたグレア形状(StarBurst)の生成を行ってみました。

参考リンクのレンズフレアプロジェクトのコードから該当部分のみUnityで動作するようにしています。

GenerateStarBurstWindow.cs

using System.IO;
using UnityEditor;
using UnityEngine;

public class GenerateStarBurstWindow : EditorWindow
{
    [MenuItem("Tools/Generate StarBurst")]
    private static void Create()
    {
        var window = GetWindow<GenerateStarBurstWindow>( "GenerateStarBurst" );
        window.Show();
    }
    private GenerateStarBurst _obj = null;
    private void OnEnable()
    {
        var ms = MonoScript.FromScriptableObject( this );
        var path = AssetDatabase.GetAssetPath( ms );
        path = path.Replace(Path.GetFileName( path ), "" );
        path = path + "GenerateStarBurst.asset";
        _obj = AssetDatabase.LoadAssetAtPath<GenerateStarBurst>( path );
        if(_obj == null)
        {
            var assets = AssetDatabase.LoadAllAssetsAtPath(path);
            if(assets.Length > 0)
                _obj = assets[0] as GenerateStarBurst;
        }
        if( _obj == null )
        {
            _obj = ScriptableObject.CreateInstance<GenerateStarBurst>();
            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();
    }
}

GenerateStarBurst.cs

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

public class GenerateStarBurst : ScriptableObject
{
    [SerializeField] private Texture2D _apertureTexture = null;
    [SerializeField] private Texture2D _dustTexture = null;
    [SerializeField] private ComputeShader _csFft = null;
    [SerializeField] private Material _matCombineApurture = null;
    [SerializeField] private Material _matStarBurst = null;
    [SerializeField] private Material _matGenAperture = null;

    private RenderTexture[] _fftTextures = null;
    private RenderTexture _starBurstTexture = null;
    private RenderTexture _starBurstFilterTexture = null;
    private const int _fftTextureSize = 512;        //computeShaderも変更する
    private const int _starBurstTextureSize = 2048;
    private RenderTexture _genApertureTexture = null;
    private int _genApertureAngle = 0;
    private int _genApertureNumOfBlade = 5;

    private void OnDestroy()
    {
        if(_fftTextures != null)
        {
            for(int i = 0; i < _fftTextures.Length; i++)
                _fftTextures[i]?.Release();
            _fftTextures = null;
        }
        _starBurstTexture?.Release();
        _starBurstTexture = null;
        _starBurstFilterTexture?.Release();
        _starBurstFilterTexture = null;
        _genApertureTexture?.Release();
        _genApertureTexture = null;
    }

    private static Texture TextureField(string name, Texture texture, float size = 70)
    {
        using (var verticalScope = new GUILayout.VerticalScope())
        {
            var style = new GUIStyle(GUI.skin.label);
            style.alignment = TextAnchor.UpperCenter;
            style.fixedWidth = size;
            GUILayout.Label(name, style);
            var result = (Texture)EditorGUILayout.ObjectField(texture, typeof(Texture), false, GUILayout.Width(size), GUILayout.Height(size));
            return result;
        }
    }
    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("Angle", _genApertureAngle.ToString(), 100, 30);
        _genApertureAngle = Convert.ToInt32(str);

        str = LabeledTextField("Number of Blades", _genApertureNumOfBlade.ToString(), 100, 30);
        _genApertureNumOfBlade = Convert.ToInt32(str);

        if(GUILayout.Button( "Generate Apurture" ) == true)
        {
            GenerateAperture();
        }
        //using (var horizontalScope = new GUILayout.HorizontalScope())
        using (var verticalScope = new GUILayout.VerticalScope())
        {
            _apertureTexture = TextureField("Apurture", _apertureTexture) as Texture2D;
            _dustTexture = TextureField("Dust", _dustTexture) as Texture2D;
        }
        if(GUILayout.Button( "Generate" ) == true)
        {
            Generate();
        }
        var rect = GUILayoutUtility.GetLastRect();
        rect.position = new Vector2(rect.position.x, rect.position.y + rect.size.y);
        rect.size = new Vector2(128, 128);

        var srcApertureTexture = (_apertureTexture != null) ? _apertureTexture as Texture : _genApertureTexture as Texture;
        if(srcApertureTexture != null)
            EditorGUI.DrawPreviewTexture(rect, srcApertureTexture);
        rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);

        if(_fftTextures != null)
        {
            for(int i = 0; i < _fftTextures.Length; i++)
            {
                if(_fftTextures[i] != null)
                    EditorGUI.DrawPreviewTexture(rect, _fftTextures[i]);
                rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
            }
        }
        if(_starBurstTexture != null)
            EditorGUI.DrawPreviewTexture(rect, _starBurstTexture);
        rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
        if(_starBurstFilterTexture != null)
            EditorGUI.DrawPreviewTexture(rect, _starBurstFilterTexture);
        rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y);
    }

    private void GenerateAperture()
    {
        if(_genApertureTexture == null)
        {
            var format = RenderTextureFormat.ARGB32;//.ARGBFloat;
            _genApertureTexture = new RenderTexture(_fftTextureSize, _fftTextureSize, 0, format, RenderTextureReadWrite.Default);
            _genApertureTexture.useMipMap = false;
            _genApertureTexture.enableRandomWrite = true;
        }
        var prevRt = RenderTexture.active;

        _matGenAperture.SetFloat("aperture_opening", _genApertureAngle * Mathf.Deg2Rad);
        _matGenAperture.SetFloat("number_of_blades", _genApertureNumOfBlade);
        RenderTexture.active = _genApertureTexture;
        Graphics.Blit(_dustTexture, _matGenAperture);

        RenderTexture.active = prevRt;

        SaveRenderTexture(_genApertureTexture, "GenAperture.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();
        //AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive);
    }

    private void Generate()
    {
        if(_fftTextures == null)
        {
            _fftTextures = new RenderTexture[4];
            for (int i = 0; i < _fftTextures.Length; i++)
            {
                _fftTextures[i] = new RenderTexture(_fftTextureSize, _fftTextureSize, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default);
                _fftTextures[i].useMipMap = false;
                _fftTextures[i].enableRandomWrite = true;
            }
            _starBurstTexture = new RenderTexture(_starBurstTextureSize, _starBurstTextureSize, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default);
            _starBurstTexture.useMipMap = false;
            _starBurstFilterTexture = new RenderTexture(_starBurstTextureSize, _starBurstTextureSize, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default);
            _starBurstFilterTexture.useMipMap = false;
        }
        if(_matCombineApurture == null)
            return;
        var prevRt = RenderTexture.active;

        if(_apertureTexture != null)
        {
            RenderTexture.active = _fftTextures[0];
            _matCombineApurture.SetTexture("_ApurtureTex", _apertureTexture);
            _matCombineApurture.SetTexture("_DustTex", _dustTexture);
            Graphics.Blit(null, _matCombineApurture);
        }
        else
        {
            Graphics.Blit(_genApertureTexture, _fftTextures[0]);
        }

        var kernelFFTRow = _csFft.FindKernel("ButterflySLM_ROW");
        _csFft.SetTexture(kernelFFTRow, "TextureSourceR", _fftTextures[0]);
        _csFft.SetTexture(kernelFFTRow, "TextureSourceI", _fftTextures[1]);
        _csFft.SetTexture(kernelFFTRow, "TextureTargetR", _fftTextures[2]);
        _csFft.SetTexture(kernelFFTRow, "TextureTargetI", _fftTextures[3]);
        _csFft.Dispatch(kernelFFTRow, 1, _fftTextureSize, 1);

        var kernelFFTCol = _csFft.FindKernel("ButterflySLM_COL");
        _csFft.SetTexture(kernelFFTCol, "TextureSourceR", _fftTextures[2]);
        _csFft.SetTexture(kernelFFTCol, "TextureSourceI", _fftTextures[3]);
        _csFft.SetTexture(kernelFFTCol, "TextureTargetR", _fftTextures[0]);
        _csFft.SetTexture(kernelFFTCol, "TextureTargetI", _fftTextures[1]);
        _csFft.Dispatch(kernelFFTCol, 1, _fftTextureSize, 1);

        RenderTexture.active = _starBurstTexture;
        _matStarBurst.SetTexture("_FFTTex0", _fftTextures[0]);
        _matStarBurst.SetTexture("_FFTTex1", _fftTextures[1]);
        Graphics.Blit(null, _matStarBurst, 0);

        RenderTexture.active = _starBurstFilterTexture;
        _matStarBurst.SetTexture("_MainTex", _starBurstTexture);
        Graphics.Blit(null, _matStarBurst, 1);

        RenderTexture.active = prevRt;

        SaveRenderTexture(_starBurstTexture, "StarBurst.png");
        SaveRenderTexture(_starBurstFilterTexture, "StarBurstFilter.png");
    }
}

FFT.compute

//--------------------------------------------------------------------------------------
// Copyright 2014 Intel Corporation
// All Rights Reserved
//
// Permission is granted to use, copy, distribute and prepare derivative works of this
// software for any purpose and without fee, provided, that the above copyright notice
// and this statement appear in all copies.  Intel makes no representations about the
// suitability of this software for any purpose.  THIS SOFTWARE IS PROVIDED "AS IS."
// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY,
// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE,
// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  Intel does not
// assume any responsibility for any errors which may appear in this software nor any
// responsibility to update it.
//--------------------------------------------------------------------------------------
#pragma kernel ButterflySLM_ROW ROWPASS
#pragma kernel ButterflySLM_COL

Texture2D<float3> TextureSourceR;
Texture2D<float3> TextureSourceI;
RWTexture2D<float3> TextureTargetR;
RWTexture2D<float3> TextureTargetI;
static const int Length = 512;
static const int ButterflyCount = (int)(log(Length) / log(2.0));

static const float PI = 3.14159265f;

void GetButterflyValues(uint passIndex, uint x, out uint2 indices, out float2 weights)
{
    int sectionWidth = 2 << passIndex;
    int halfSectionWidth = sectionWidth >> 1;

    int sectionStartOffset = x & ~(sectionWidth - 1);
    int halfSectionOffset = x & (halfSectionWidth - 1);
    int sectionOffset = x & (sectionWidth - 1);

    sincos(2.0 * PI * sectionOffset / (float)sectionWidth, weights.y, weights.x);
    weights.y = -weights.y;

    indices.x = sectionStartOffset + halfSectionOffset;
    indices.y = sectionStartOffset + halfSectionOffset + halfSectionWidth;

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

groupshared float3 pingPongArray[4][Length];
void ButterflyPass(int passIndex, uint x, uint t0, uint t1, out float3 resultR, out float3 resultI)
{
    uint2 Indices;
    float2 Weights;
    GetButterflyValues(passIndex, x, Indices, Weights);

    float3 inputR1 = pingPongArray[t0][Indices.x];
    float3 inputI1 = pingPongArray[t1][Indices.x];

    float3 inputR2 = pingPongArray[t0][Indices.y];
    float3 inputI2 = pingPongArray[t1][Indices.y];

    resultR = inputR1 + Weights.x * inputR2 - Weights.y * inputI2;
    resultI = inputI1 + Weights.y * inputR2 + Weights.x * inputI2;
}

void ButterflySLM(uint3 position)
{
#ifdef ROWPASS
    uint2 texturePos = uint2(position.xy);
#else
    uint2 texturePos = uint2(position.yx);
#endif

    pingPongArray[0][position.x].xyz = TextureSourceR[texturePos];
#if defined(ROWPASS)
    pingPongArray[1][position.x].xyz = 0;
#else
    pingPongArray[1][position.x].xyz = TextureSourceI[texturePos];
#endif

    uint4 textureIndices = uint4(0, 1, 2, 3);
    for (int i = 0; i < ButterflyCount - 1; i++) {
        GroupMemoryBarrierWithGroupSync();
        ButterflyPass(i, position.x, textureIndices.x, textureIndices.y, pingPongArray[textureIndices.z][position.x].xyz, pingPongArray[textureIndices.w][position.x].xyz);
        textureIndices.xyzw = textureIndices.zwxy;
    }

    GroupMemoryBarrierWithGroupSync();
    ButterflyPass(ButterflyCount - 1, position.x, textureIndices.x, textureIndices.y, TextureTargetR[texturePos], TextureTargetI[texturePos]);

}

[numthreads(Length, 1, 1)]
void ButterflySLM_ROW(uint3 position : SV_DispatchThreadID)
{
    ButterflySLM(position);
}

[numthreads(Length, 1, 1)]
void ButterflySLM_COL(uint3 position : SV_DispatchThreadID)
{
    ButterflySLM(position);
}

Aperture.shader

Shader "Custom/Aperture"
{
    Properties
    {
        _MainTex ("Dust Texture", 2D) = "white" {}
        aperture_resolution ("RenderTarget Resolution", Vector) = (512, 512, 0, 0)
        aperture_opening ("Opening", float) = 7.0
        number_of_blades ("Number Of Blades", float) = 5.0
   }
    SubShader
    {
        // No culling or depth
        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
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

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

            Texture2D _MainTex;
            SamplerState sampler_linear_clamp;
            float2 aperture_resolution;
            float aperture_opening;
            float number_of_blades;

            float fade_aperture_edge(float radius, float fade, float signed_distance)
            {
                float l = radius;
                float u = radius + fade;
                float s = u - l;
                float c = 1.f - saturate(saturate(signed_distance - l)/s);
                return smoothstep(0, 1, c);
            }
 
            float smax(float a, float b, float k)
            {
                float diff = a - b;
                float h = saturate(0.5 + 0.5 * diff / k);
                return b + h * (diff + k * (1.0f - h));
            }

            float4 frag(v2f In) : SV_Target
            {
                float2 uv = In.vertex.xy / aperture_resolution;
                float2 ndc = ((uv - 0.5f) * 2.f);

                int num_of_blades = int(number_of_blades);

                float a = (atan2(ndc.x, ndc.y) + aperture_opening)/UNITY_TWO_PI + 3.f/4.f;
                float o = frac(a * num_of_blades + 0.5);
                float w1 = lerp(0.010, 0.001f, saturate((num_of_blades - 4)/10.f));
                float w2 = lerp(0.025, 0.001f, saturate((num_of_blades - 4)/10.f));
                float s0 = sin(o * 2 * UNITY_PI);
                float s1 = s0 * w1;
                float s2 = s0 * w2;

                // fft aperture shape
                float signed_distance = 0.f;
                int i;
                for(i = 0; i < num_of_blades; ++i)
                {
                    float angle = aperture_opening  + (i/float(num_of_blades)) * UNITY_TWO_PI;
                    float2 axis = float2(cos(angle), sin(angle));
                    signed_distance = max(signed_distance, dot(axis, ndc));
                }

                //signed_distance += s1;
                float aperture_fft = fade_aperture_edge(0.7, 0.00001, signed_distance);
                // camera aperture shape
                signed_distance = 0.f;
                for(i = 0; i < num_of_blades; i++)
                {
                    float angle = aperture_opening + (i/float(num_of_blades)) * UNITY_TWO_PI;
                    float2 axis = float2(cos(angle), sin(angle));
                    signed_distance = smax(signed_distance, dot(axis, ndc), 0.1);
                }

                signed_distance += s2;
                float aperture_mask = fade_aperture_edge(0.7, 0.1, signed_distance);

                { // Diffraction rings
                    float w = 0.2;
                    float s = signed_distance + 0.05;
                    float n = saturate(saturate(s + w) - (1.f - w));
        
                    float x = n/w;
                    float a = x;
                    float b = -x + 1.f;
                    float c = min(a,b) * 2.f;
                    float t = (sin(x * 6.f * UNITY_PI - 1.5f) + 1.f) * 0.5f;
                    float rings = pow(t*c, 1.f);
                    aperture_mask = aperture_mask + rings * 0.125;
                }

                float dust_fft = 0.f;
                { // Dust
                    dust_fft = _MainTex.Sample(sampler_linear_clamp, uv).r;
                    aperture_mask *= saturate(dust_fft + 0.9);
                }

                float3 rgb = float3(aperture_fft, dust_fft, aperture_mask);
#if 1
                rgb.b = 0.0;
#endif
                return float4(rgb, 1);
            }
            ENDCG
        }
    }
}

StarBurst.shader

Shader "Custom/StartBurst"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        starburst_resolution ("RenderTarget Resolution", Vector) = (2048, 2048, 0, 0)
    }

    CGINCLUDE
    #include "UnityCG.cginc"

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

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

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

    bool Clamped(float2 ndc)
    {
        return ndc.x < -0.5 || ndc.x > 0.5 || ndc.y < -0.5 || ndc.y > 0.5;
    }

    float3 wl2rgbTannenbaum(float w)
    {
        float3 r;

        if(w < 350.0)
            r = float3(0.5, 0.0, 1.0);
        else if((w >= 350.0) && (w < 440.0))
            r = float3((440.0 - w) / 90.0, 0.0, 1.0);
        else if((w >= 440.0) && (w <= 490.0))
            r = float3(0.0, (w - 440.0) / 50.0, 1.0);
        else if((w >= 490.0) && (w < 510.0))
            r = float3(0.0, 1.0, (-(w - 510.0)) / 20.0);
        else if ((w >= 510.0) && (w < 580.0))
            r = float3((w - 510.0) / 70.0, 1.0, 0.0);
        else if((w >= 580.0) && (w < 645.0))
            r = float3(1.0, (-(w - 645.0)) / 65.0, 0.0);
        else
            r = float3(1.0, 0.0, 0.0);
    
        if(w < 350.0)
            r *= 0.3;
        else if((w >= 350.0) && (w < 420.0))
            r *= 0.3 + (0.7 * ((w - 350.0) / 70.0));
        else if((w >= 420.0) && (w <= 700.0))
            r *= 1.0;
        else if((w > 700.0) && (w <= 780.0))
            r *= 0.3 + (0.7 * ((780.0 - w) / 80.0));
        else
            r *= 0.3;

        return r;
    }

    float2 Rotate(float2 p, float a)
    {
        float x = p.x;
        float y = p.y;

        float cosa = cos(a);
        float sina = sin(a);

        float x1 = x * cosa - y * sina;
        float y1 = y * cosa + x * sina;

        return float2(x1, y1);
    }

    SamplerState sampler_linear_repeat;
    float2 starburst_resolution;
    ENDCG

    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            Texture2D _FFTTex0;
            Texture2D _FFTTex1;

            float4 frag(v2f In) : SV_Target
            {

                float2 uv = In.vertex.xy / starburst_resolution - 0.5;
                float d = length(uv) * 2;
    
                // -ve violet, +v reds
                float scale1 =  0.50f;
                float scale2 = -0.75f;
                float fft_scale = 0.00001f;

                float3 result = 0.f;
                int num_steps = 256;
                for(int i = 0; i <= num_steps; ++i)
                {
                    float n = (float)i/(float)num_steps;
        
                    float2 scaled_uv1 = uv * lerp(1.f + scale1, 1.f, n);
                    float2 scaled_uv2 = uv * lerp(1.f + scale2, 1.f, n);

                    bool clamped1 = Clamped(scaled_uv1);
                    bool clamped2 = Clamped(scaled_uv2);

                    float r1 = _FFTTex0.Sample(sampler_linear_repeat, scaled_uv1).r * !clamped1;
                    float i1 = _FFTTex1.Sample(sampler_linear_repeat, scaled_uv1).r * !clamped1;

                    float r2 = _FFTTex0.Sample(sampler_linear_repeat, scaled_uv2).g * !clamped2;
                    float i2 = _FFTTex1.Sample(sampler_linear_repeat, scaled_uv2).g * !clamped2;

                    float2 p1 = float2(r1, i1);
                    float2 p2 = float2(r2, i2);

                    float starburst = pow(length(p1), 2.f) * fft_scale * lerp(0.0f, 25.f, d);
                    float dust      = pow(length(p2), 2.f) * fft_scale * lerp(0.5f,  0.f, d);

                    float lambda = lerp(380.f, 700.f, n);
                    float3 rgb = wl2rgbTannenbaum(lambda);
                    rgb = lerp(1.f, rgb, 0.75f);

                    result += (starburst + dust * rgb * 0.25f);
                }

                result /= (float)num_steps;
                return float4(result, 1);
            }
            ENDCG
        }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            Texture2D _MainTex;

            float4 frag(v2f In) : SV_Target
            {
                float2 uv = In.vertex.xy / starburst_resolution - 0.5f;

                float3 result = 0.f;
                uint num_steps = 256;
                for(uint i = 0; i <= num_steps; ++i)
                {
                    float n = (float)i/(float)num_steps;
                    float a = n * UNITY_TWO_PI * 2.f;

                    float2 spiral = float2(cos(a), sin(a)) * n * 0.002f;
                    float2 rotated_uv = Rotate(uv + spiral, n * 0.05f);

                    float3 starburst = _MainTex.Sample(sampler_linear_repeat, rotated_uv + 0.5f).rgb * !Clamped(rotated_uv);

                    float lambda = lerp(380.f, 700.f, (i % 80)/80.f);
                    float3 rgb = wl2rgbTannenbaum(lambda);
                    rgb = lerp(rgb, 1.f, 0.5f);

                    result += starburst * rgb;
                }

                result /= (float)num_steps;

                return float4(result, 1);
            }
            ENDCG
        }
    }
}

CombineAperture.shader

Shader "Custom/CombineAperture"
{
    Properties
    {
    }
    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
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

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

            Texture2D _ApurtureTex;
            Texture2D _DustTex;
            SamplerState sampler_linear_clamp;

            float4 frag(v2f In) : SV_Target
            {
                float aperture = _ApurtureTex.Sample(sampler_linear_clamp, In.uv).r;
                float dust = _DustTex.Sample(sampler_linear_clamp, In.uv).g;
                return float4(aperture, dust, 0, 1);
            }
            ENDCG
        }
    }
}

感想

カメラの開口形状の代わりに目の形等の形状を使っても生成されます。

参考

GitHub - greje656/PhysicallyBasedLensFlare: Lens flare

水玉コラージュ

環境

Unity2021.2.18f1

概要

DeepNudeの学習済みデータが手に入ったので、onnxファイルに変換してBarracudaで動作確認してみました。

せっかくなので、その情報(水着部分を緑色で塗る)を使い、ComputeShaderで水玉コラージュを作成してみました。

顔情報の取得にはUltraFaceBarracudaを使用させていただいています。

こちらのサイトの無料素材を使用させていただいています。 www.photo-ac.com

入力画像としては512x512ではないとダメなのでそうなるように画像ソフトであらかじめ修正をしています。

float4 FaceRect;
uint Width;
uint Height;
uint GridSize;
uint GridWidth;
uint GridHeight;
float4 MaskColor;
RWTexture2D<float4> Result;
Texture2D<float4> SrcTex;
RWStructuredBuffer<uint> Grids;
RWStructuredBuffer<uint2> Masks;
RWStructuredBuffer<uint> MaskCount;
RWStructuredBuffer<float> Distances;
float4 Pos;

#pragma kernel CSWriteFaceRect
[numthreads(8,8,1)]
void CSWriteFaceRect(uint3 id : SV_DispatchThreadID)
{
    if((uint)FaceRect.x <= id.x && id.x <= (uint)FaceRect.z && (uint)FaceRect.y <= id.y && id.y <= (uint)FaceRect.w)
        Result[id.xy] = float4(1, 1, 1, 1);
}

#pragma kernel CSGrid
[numthreads(8, 8, 1)]
void CSGrid(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= GridWidth)
        return;
    if (id.y >= GridHeight)
        return;
    uint2 basePos = id.xy * GridSize;
    uint value = 0;
    for (uint y = 0; y < GridSize; y++)
    {
        uint py = basePos.y + y;
        if (py >= Height)
            break;
        for (uint x = 0; x < GridSize; x++)
        {
            uint2 pos = uint2(basePos.x + x, py);
            if (pos.x >= Width)
                break;
            float4 col = SrcTex[pos.xy];
            if (col.r == MaskColor.r && col.g == MaskColor.g && col.b == MaskColor.b)
            {
                value = 1;
                break;
            }
        }
    }
    Grids[id.y * GridWidth + id.x] = (value > 0) ? 1 : 0;
}

#pragma kernel CSWriteGrid
[numthreads(8, 8, 1)]
void CSWriteGrid(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= Width)
        return;
    if (id.y >= Height)
        return;
    uint2 gridPos = id.xy / GridSize;
    uint mask = Grids[gridPos.y * GridWidth + gridPos.x];
    Result[id.xy] = (mask == 1) ? float4(0, 0, 1, 1) : Result[id.xy];
}

#pragma kernel CSClear
[numthreads(512, 1, 1)]
void CSClear(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= Width)
        return;
    if (id.y >= Height)
        return;
    Result[id.xy] = float4(0, 0, 0, 1);
}

#pragma kernel CSCopy
[numthreads(512, 1, 1)]
void CSCopy(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= Width)
        return;
    if (id.y >= Height)
        return;
    Result[id.xy] = SrcTex[id.xy];
}

#pragma kernel CSCombine
[numthreads(512, 1, 1)]
void CSCombine(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= Width)
        return;
    if (id.y >= Height)
        return;
    Result[id.xy] = lerp(float4(1, 0, 1, 1), SrcTex[id.xy], Result[id.xy].r);
}

#pragma kernel CSGridMask
[numthreads(1, 1, 1)]
void CSGridMask(uint3 id : SV_DispatchThreadID)
{
    MaskCount[0] = 0;
    uint count = GridWidth * GridHeight;
    for (uint i = 0; i < count; i++)
    {
        if (Grids[i] != 1)
            continue;
        uint x = i % GridWidth;
        uint y = i / GridWidth;
        Masks[MaskCount[0]] = uint2(x, y);
        MaskCount[0] += 1;
    }
}

#pragma kernel CSInitGridDistance
[numthreads(512, 1, 1)]
void CSInitGridDistance(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= GridWidth)
        return;
    if (id.y >= GridHeight)
        return;
    uint index = id.y * GridWidth + id.x;
    uint count = GridWidth * GridHeight;
    Distances[index] = float(count);
}

#pragma kernel CSGridDistance
[numthreads(8, 8, 1)]
void CSGridDistance(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= GridWidth)
        return;
    if (id.y >= GridHeight)
        return;
    uint index = id.y * GridWidth + id.x;
    for (uint i = 0; i < MaskCount[0]; i++)
    {
        int2 v = Masks[i] - id.xy;
        float distance = max(length(v) - 1, 0);
        if (Distances[index] > distance)
            Distances[index] = distance;
    }
}

#pragma kernel CSWriteGridDistance
[numthreads(8, 8, 1)]
void CSWriteGridDistance(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= Width)
        return;
    if (id.y >= Height)
        return;
    uint2 gridPos = id.xy / GridSize;
    uint index = gridPos.y * GridWidth + gridPos.x;
    float c = (Distances[index] == 0) ? 0 : 1;
    Result[id.xy] = float4(c, c, c, 1);
}

#pragma kernel CSWriteCircle
[numthreads(8, 8, 1)]
void CSWriteCircle(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= Width)
        return;
    if (id.y >= Height)
        return;
    uint2 gridPos = int2(Pos.xy);
    int2 pos = gridPos.xy * GridSize;
    float distance = length(int2(id.xy) - pos);
    uint index = gridPos.y * GridWidth + gridPos.x;
    if (distance >= (Distances[index] * GridSize))
        return;
    Result[id.xy] = float4(1, 1, 1, 1);

    gridPos = id.xy / GridSize;
    index = gridPos.y * GridWidth + gridPos.x;
    Grids[index] = 1;
}
using UnityEngine;
using Unity.Barracuda;
using UltraFace;
using System.Linq;
using System;
using UnityEngine.UI;
using System.Collections.Generic;

public unsafe class GenerateMizutamaCollage : MonoBehaviour
{
    [SerializeField] private Texture _inputTexture = null;
    [Header("DeepNude")]
    [SerializeField] private NNModel _modelAsset = null;
    [Header("FaceDitect")]
    [SerializeField] ResourceSet _resources = null;
    [SerializeField, Range(0, 1)] float _threshold = 0.9f;
    [SerializeField] ComputeShader _computeShader = null;
    [SerializeField] RawImage _rawImageInput = null;
    [SerializeField] RawImage _rawImageOutput = null;

    private Model _runtimeModel = null;
    private IWorker _worker = null;
    private FaceDetector _detector;
    private RenderTexture _outputTexture = null;
    private RenderTexture[] _tempTextures = null;

    private static int _spResult = Shader.PropertyToID("Result");
    private static int _spFaceRect = Shader.PropertyToID("FaceRect");

    private static int _spMaskColor = Shader.PropertyToID("MaskColor");
    private static int _spWidth = Shader.PropertyToID("Width");
    private static int _spHeight = Shader.PropertyToID("Height");
    private static int _spGridSize = Shader.PropertyToID("GridSize");
    private static int _spGridWidth = Shader.PropertyToID("GridWidth");
    private static int _spGridHeight = Shader.PropertyToID("GridHeight");
    private static int _spSrcTex = Shader.PropertyToID("SrcTex");
    private static int _spGrids = Shader.PropertyToID("Grids");
    private static int _spMaskCount = Shader.PropertyToID("MaskCount");
    private static int _spMasks = Shader.PropertyToID("Masks");
    private static int _spDistances = Shader.PropertyToID("Distances");
    private static int _spPos = Shader.PropertyToID("Pos");

    private RectInt _faceRect;
    private Vector2Int _gridFaceCenter;
    private const int _gridSize = 2;
    private int _gridWidth, _gridHeight;
    private GraphicsBuffer _gbGrids = null;
    private GraphicsBuffer _gbMasks = null;
    private GraphicsBuffer _gbMaskCount = null;
    private GraphicsBuffer _gbDistances = null;

    private void Start()
    {
        _rawImageInput.texture = _inputTexture;
        _rawImageInput.rectTransform.sizeDelta = new Vector2(_inputTexture.width * 0.25f, _inputTexture.height * 0.25f);

        _outputTexture = new RenderTexture(_inputTexture.width, _inputTexture.height, 0, RenderTextureFormat.ARGB32, 0);
        _outputTexture.enableRandomWrite = true;
        _rawImageOutput.texture = _outputTexture;
        _rawImageOutput.rectTransform.sizeDelta = new Vector2(_outputTexture.width, _outputTexture.height);

        _tempTextures = new RenderTexture[2];
        for(int i = 0; i < _tempTextures.Length; i++)
        {
            _tempTextures[i] = new RenderTexture(_inputTexture.width, _inputTexture.height, 0, RenderTextureFormat.ARGB32, 0);
            _tempTextures[i].enableRandomWrite = true;
        }

        _runtimeModel = ModelLoader.Load(_modelAsset);
        _worker = WorkerFactory.CreateWorker(WorkerFactory.Type.Compute, _runtimeModel);
        Execute();

#if True
        //FaceDetect
        _detector = new FaceDetector(_resources);
        _detector.ProcessImage(_inputTexture, _threshold);
        foreach (var d in _detector.Detections)
            Debug.Log(d);
        var ditection = _detector.Detections.FirstOrDefault();
        _gridFaceCenter = Vector2Int.zero;
        var isDetect = false;
        if(ditection.score > _threshold)
        {
            isDetect = true;
            _faceRect = new RectInt(
                (int)(ditection.x1 * _inputTexture.width),
                (int)((1.0f - ditection.y2) * _inputTexture.height),
                (int)((ditection.x2 - ditection.x1) * _inputTexture.width),
                (int)((ditection.y2 - ditection.y1) * _inputTexture.height)
            );
            Debug.Log($"faceRect:{_faceRect}");
            _gridFaceCenter = new Vector2Int((int)(_faceRect.center.x / (float)_gridSize), (int)(_faceRect.center.y / (float)_gridSize));
        }
        CopyTexture(_outputTexture, _tempTextures[0]);
        //DrawFace();
        CreateGrids();
        //DrawGrids();
        ClearTexture();
        _gbMasks = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _gridWidth * _gridHeight, sizeof(Vector2Int));
        _gbMaskCount = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(UInt32));
        _gbDistances = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _gridWidth * _gridHeight, sizeof(float));
        var corners = new Vector2Int[]
        {
            new Vector2Int(_gridFaceCenter.x, _gridFaceCenter.y),
            new Vector2Int(0, 0),
            new Vector2Int(_gridWidth - 1, 0),
            new Vector2Int(_gridWidth - 1, _gridHeight - 1),
            new Vector2Int(0, _gridHeight - 1),
        };
        for(int i = (isDetect == true) ? 0 : 1; i < corners.Length; i++)
        {
            CreateMaskPositions();
            CreateDistances();
            //DrawDistances(_tempTextures[1]);
            DrawCircle(corners[i]);
        }
        CreateMaskPositions();
        CreateDistances();

        var grids = new UInt32[_gridWidth * _gridHeight];
        _gbGrids.GetData(grids);
        var positions = new List<Vector2Int>();
        for(int i = 0; i < _gridWidth * _gridHeight; i++)
        {
            var x = i % _gridWidth;
            var y = i / _gridWidth;
            if(grids[i] == 1)
                continue;
            positions.Add(new Vector2Int(x, y));
        }
        //シャッフル
        positions = positions.OrderBy(a => Guid.NewGuid()).ToList();
        var count = Mathf.Min(200, positions.Count);
        for(int i = 0; i < count; i++)
        {
            var pos = positions[i];
            DrawCircle(pos);
            CreateMaskPositions();
            CreateDistances();
        }
        CombineTexture(_inputTexture);

        _gbMasks.Dispose();
        _gbMaskCount.Dispose();
        _gbDistances.Dispose();
        _gbGrids.Dispose();
#endif
    }

    private void Execute()
    {
        Tensor input = new Tensor(_inputTexture as Texture, channels:3);
        Inference(input);
        input.Dispose();
    }

    private void Inference(Tensor input)
    {
        _worker.Execute(input);
        Tensor output = _worker.PeekOutput();
        output.ToRenderTexture(_outputTexture, 0, 0, 1, 0, null);
        output.Dispose();
    }

    private void DrawFace()
    {
        //顔Rect描画
        var kernelIndex = _computeShader.FindKernel("CSWriteFaceRect");
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.SetTexture(kernelIndex, _spResult, _outputTexture);
        _computeShader.SetVector(_spFaceRect, new Vector4(_faceRect.xMin, _faceRect.yMin, _faceRect.xMax, _faceRect.yMax));
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }
    private void CreateGrids()
    {
        //グリッドで分割
        _gridWidth = Mathf.CeilToInt((float)_outputTexture.width / (float)_gridSize);
        _gridHeight = Mathf.CeilToInt((float)_outputTexture.height / (float)_gridSize);
        _gbGrids = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _gridWidth * _gridHeight, sizeof(UInt32));
        var kernelIndex = _computeShader.FindKernel("CSGrid");
        _computeShader.SetVector(_spMaskColor, Color.green);
        _computeShader.SetInt(_spWidth, _outputTexture.width);
        _computeShader.SetInt(_spHeight, _outputTexture.height);
        _computeShader.SetInt(_spGridSize, _gridSize);
        _computeShader.SetInt(_spGridWidth, _gridWidth);
        _computeShader.SetInt(_spGridHeight, _gridHeight);
        _computeShader.SetTexture(kernelIndex, _spSrcTex, _outputTexture);
        _computeShader.SetBuffer(kernelIndex, _spGrids, _gbGrids);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_gridWidth / (float)numThreadX), Mathf.CeilToInt((float)_gridHeight / (float)numThreadY), 1);
    }
    private void DrawGrids()
    {
        //グリッド描画
        var kernelIndex = _computeShader.FindKernel("CSWriteGrid");
        _computeShader.SetBuffer(kernelIndex, _spGrids, _gbGrids);
        _computeShader.SetTexture(kernelIndex, _spResult, _outputTexture);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }
    private void ClearTexture()
    {
        var kernelIndex = _computeShader.FindKernel("CSClear");
        _computeShader.SetInt(_spWidth, _outputTexture.width);
        _computeShader.SetInt(_spHeight, _outputTexture.height);
        _computeShader.SetTexture(kernelIndex, _spResult, _outputTexture);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }
    private void CopyTexture(Texture src, Texture dst)
    {
        var kernelIndex = _computeShader.FindKernel("CSCopy");
        _computeShader.SetInt(_spWidth, _outputTexture.width);
        _computeShader.SetInt(_spHeight, _outputTexture.height);
        _computeShader.SetTexture(kernelIndex, _spSrcTex, src);
        _computeShader.SetTexture(kernelIndex, _spResult, dst);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }
    private void CombineTexture(Texture src)
    {
        var kernelIndex = _computeShader.FindKernel("CSCombine");
        _computeShader.SetInt(_spWidth, _outputTexture.width);
        _computeShader.SetInt(_spHeight, _outputTexture.height);
        _computeShader.SetTexture(kernelIndex, _spSrcTex, src);
        _computeShader.SetTexture(kernelIndex, _spResult, _outputTexture);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }
    private void CreateMaskPositions()
    {
        var kernelIndex = _computeShader.FindKernel("CSGridMask");
        _computeShader.SetInt(_spGridWidth, _gridWidth);
        _computeShader.SetInt(_spGridHeight, _gridHeight);
        _computeShader.SetBuffer(kernelIndex, _spGrids, _gbGrids);
        _computeShader.SetBuffer(kernelIndex, _spMasks, _gbMasks);
        _computeShader.SetBuffer(kernelIndex, _spMaskCount, _gbMaskCount);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, 1,1,1);
    }
    private void CreateDistances()
    {
        //距離マップの作成
        var kernelIndex = _computeShader.FindKernel("CSInitGridDistance");
        _computeShader.SetInt(_spGridWidth, _gridWidth);
        _computeShader.SetInt(_spGridHeight, _gridHeight);
        _computeShader.SetBuffer(kernelIndex, _spDistances, _gbDistances);
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_gridWidth / (float)numThreadX), Mathf.CeilToInt((float)_gridHeight / (float)numThreadY), 1);

        kernelIndex = _computeShader.FindKernel("CSGridDistance");
        _computeShader.SetInt(_spGridWidth, _gridWidth);
        _computeShader.SetInt(_spGridHeight, _gridHeight);
        _computeShader.SetInt(_spGridSize, _gridSize);
        _computeShader.SetBuffer(kernelIndex, _spMasks, _gbMasks);
        _computeShader.SetBuffer(kernelIndex, _spMaskCount, _gbMaskCount);
        _computeShader.SetBuffer(kernelIndex, _spDistances, _gbDistances);
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_gridWidth / (float)numThreadX), Mathf.CeilToInt((float)_gridHeight / (float)numThreadY), 1);
    }
    private void DrawDistances(Texture dst)
    {
        //距離マップ描画
        var kernelIndex = _computeShader.FindKernel("CSWriteGridDistance");
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.SetInt(_spWidth, _outputTexture.width);
        _computeShader.SetInt(_spHeight, _outputTexture.height);
        _computeShader.SetInt(_spGridSize, _gridSize);
        _computeShader.SetInt(_spGridWidth, _gridWidth);
        _computeShader.SetTexture(kernelIndex, _spResult, dst);
        _computeShader.SetBuffer(kernelIndex, _spDistances, _gbDistances);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }
    private void DrawCircle(Vector2Int pos)
    {
        //円の描画
        var kernelIndex = _computeShader.FindKernel("CSWriteCircle");
        uint numThreadX, numThreadY, numThreadZ;
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ);
        _computeShader.SetInt(_spWidth, _outputTexture.width);
        _computeShader.SetInt(_spHeight, _outputTexture.height);
        _computeShader.SetInt(_spGridSize, _gridSize);
        _computeShader.SetInt(_spGridWidth, _gridWidth);
        _computeShader.SetVector(_spPos, new Vector4(pos.x, pos.y, 0,0));
        _computeShader.SetTexture(kernelIndex, _spResult, _outputTexture);
        _computeShader.SetBuffer(kernelIndex, _spDistances, _gbDistances);
        _computeShader.SetBuffer(kernelIndex, _spGrids, _gbGrids);
        _computeShader.Dispatch(kernelIndex, Mathf.CeilToInt((float)_outputTexture.width / (float)numThreadX), Mathf.CeilToInt((float)_outputTexture.height / (float)numThreadY), 1);
    }

    private void OnDestroy()
    {
        _detector?.Dispose();
        _worker?.Dispose();
        _outputTexture.Release();
        if(_tempTextures != null)
        {
            for(int i = 0; i < _tempTextures.Length; i++)
                _tempTextures[i].Release();
        }
    }
}

感想

マスク画像の生成部分が適当だし遅いですが、そこそこ見えるような気もします。

円を描く時の中心座標をちゃんと求めてないです。

参考

https://github.com/keijiro/UltraFaceBarracuda

[DeepNude]水着を丸裸にするAI技術DeepNudeを解説してみる - AIなんて気合いダッ!

テッセレーションで草

環境

Unity2021.2.18f1

概要

metalがジオメトリシェーダに対応していないらしいので使わずに草を生やしてみます。

テッセレーションでは点から三角(もしくは四角)形を生成できるのでやってみました。

1チャンネルのテクスチャからMeshTopology.Pointsのメッシュを作成して、テッセレーションシェーダで草にしています。

距離で分割数を変えているので手前は割と分割されていますが、少し後ろに行くとただの四角形になります。

Shader "Custom/TessellationGrass" 
{
    Properties
    {
        _Color("Color", color) = (12, 173, 0, 1)
        _UnderColor("UnderColor", color) = (0, 0, 0, 1)
        _FlowTex("Flow Texture", 2D) = "white" {}
        _MinDist("Min Distance", Range(0.1, 100)) = 0.1
        _MaxDist("Max Distance", Range(0.1, 100)) = 2
        _TessFactor("Tessellation", Range(1, 5000)) = 10 //分割レベル
        _PositionScale("Position Scale", Range(0.1, 100)) = 10
        _Width("Width", Range(0.01, 0.5)) = 0.05
        _Height("Height", Range(0.1, 1)) = 0.5
        _FlowPower("FlowPower", Range(0.1, 1)) = 0.1
        _FlowSpeed("_FlowSpeed", Range(0.1, 1)) = 0.1
        [KeywordEnum(integer, fractional_even, fractional_odd, pow2)] _Partitioning("Partitioning", int) = 2
    }
    SubShader 
    {
        Tags
        {
            "RenderType" = "Opaque"
            "LightMode" = "ForwardBase"
        }
        Cull Off
        Pass 
        { 
            CGPROGRAM
            #include "Tessellation.cginc"
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            #pragma shader_feature _PARTITIONING_INTEGER _PARTITIONING_FRACTIONAL_EVEN _PARTITIONING_FRACTIONAL_ODD _PARTITIONING_POW2

            #pragma vertex vertex_shader
            #pragma hull hull_shader
            #pragma domain domain_shader
            #pragma fragment pixel_shader
 
            fixed4 _Color;
            fixed4 _UnderColor;
            sampler2D _FlowTex;
            float4 _FlowTex_ST;
            float _TessFactor;
            float _MinDist;
            float _MaxDist;
            float _PositionScale;
            float _Width;
            float _Height;
            float _FlowPower;
            float _FlowSpeed;

            struct appdata
            {
                float4 position : POSITION;
            };

            struct VS_OUTPUT
            {
                float4 position : POSITION;
                float2 flow : TEXCOORD0;
            };
 
            struct HS_CONSTANT_DATA_OUTPUT
            {
                float Edges[4] : SV_TessFactor;
                float Inside[2] : SV_InsideTessFactor;
            };
 
            struct HS_OUTPUT
            {
                float4 position : POS;
                float2 flow : TEXCOORD0;
                float2 basePosXZ : TEXCOORD1; 
            };

            struct DS_OUTPUT
            {
                float4 position : SV_Position;
                float4 color : Color;
            };

            VS_OUTPUT vertex_shader(appdata i)
            {
                VS_OUTPUT o;
                o.position = i.position;
                o.position.xyz *= _PositionScale;
                o.position.w = i.position.w / 255.0;
                float2 uv = frac(i.position.xz + 0.5/*/ _TexelSize.xy*/ + _Time.y * _FlowSpeed);
                o.flow = (tex2Dlod(_FlowTex, float4(uv, 0, 0)).xy * 2 - 1) * _FlowPower;
                return o;
            }
#define INPUT_PATCH_SIZE 1
#define OUTPUT_PATCH_SIZE 4
            HS_CONSTANT_DATA_OUTPUT constantsHS(InputPatch<VS_OUTPUT, INPUT_PATCH_SIZE> i)
            {
                float tessFactor = UnityCalcDistanceTessFactor(i[0].position, _MinDist, _MaxDist, _TessFactor);

                HS_CONSTANT_DATA_OUTPUT o;
                o.Edges[0] = 1;
                o.Edges[1] = tessFactor;
                o.Edges[2] = 1;
                o.Edges[3] = tessFactor;
                o.Inside[0] = tessFactor;
                o.Inside[1] = 1;
                return o;
            }
 
            [domain("quad")]
#if _PARTITIONING_INTEGER
            [partitioning("integer")]
#elif _PARTITIONING_FRACTIONAL_EVEN
            [partitioning("fractional_even")]
#elif _PARTITIONING_FRACTIONAL_ODD
            [partitioning("fractional_odd")]
#else//elif _PARTITIONING_POW2
            [partitioning("pow2")]
#endif
            [outputtopology("triangle_cw")]
            [outputcontrolpoints(OUTPUT_PATCH_SIZE)]
            [patchconstantfunc("constantsHS")]
            
            HS_OUTPUT hull_shader(InputPatch<VS_OUTPUT, INPUT_PATCH_SIZE>i, uint id: SV_OutputControlPointID, uint patchID: SV_PrimitiveID)
            {
                HS_OUTPUT o;
                uint loopIndex = (id / INPUT_PATCH_SIZE);
                uint inIndex = id % INPUT_PATCH_SIZE;
                uint index = loopIndex * INPUT_PATCH_SIZE + inIndex;
                float widthH = _Width * 0.5;
                float3 offsets[] = {float3(-widthH,0,0), float3(-widthH,_Height,0), float3(widthH,0,0), float3(widthH, _Height, 0)};
                o.position = float4(i[inIndex].position.xyz + offsets[index], i[inIndex].position.w);
                o.position.y *= i[inIndex].position.w;
                o.basePosXZ = i[inIndex].position.xz;
                o.flow = i[inIndex].flow;
                return o;
            }
 
            [domain("quad")]
            DS_OUTPUT domain_shader(HS_CONSTANT_DATA_OUTPUT iConst, const OutputPatch<HS_OUTPUT, OUTPUT_PATCH_SIZE> i, float2 bary: SV_DomainLocation)
            {
                DS_OUTPUT o;
                float3 pa = lerp(i[0].position.xyz, i[1].position.xyz, bary.x);
                float3 pb = lerp(i[2].position.xyz, i[3].position.xyz, bary.x);
                float3 pos = lerp(pa, pb, bary.y);

                float height = _Height * i[0].position.w;
                float minH = height * 0.3;
                float maxH = height;
                float r = saturate((pos.y - minH) / (maxH - minH));
                float fr = sin(saturate(pos.y / height) * UNITY_HALF_PI);
                pos.xz = i[0].basePosXZ + (pos.xz - i[0].basePosXZ) * (1.0 - r) + i[0].flow * fr;

                o.position = UnityObjectToClipPos(float4(pos, 1.0));

                float cminH = height * 0;
                float cmaxH = height * 1;
                float cr = saturate((pos.y - cminH) / (cmaxH - cminH));
                o.color = lerp(_UnderColor, _Color, cr);
                return o;
            }

            fixed4 pixel_shader(DS_OUTPUT i) : SV_TARGET
            {
                return i.color;
            }
 
            ENDCG
        }
    }
}
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.Rendering;

public class Texture2DToPointCloudedMesh : MonoBehaviour
{
    [SerializeField] private Texture2D _texture = null;

    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    struct Vertex
    {
        public Vector4 Position;
    }
    private unsafe void OnEnable()
    {
        NativeArray<byte> bytes = _texture.GetPixelData<byte>(0);
        var pBytes = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(bytes);

        var mesh = new Mesh();
        var layout = new[]
        {
            new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 4),
        };
        var vertexCount = _texture.width * _texture.height;
        mesh.SetVertexBufferParams(vertexCount, layout);

        var verts = new NativeArray<Vertex>(vertexCount, Allocator.Temp);
        var pVerts = (Vertex*)NativeArrayUnsafeUtility.GetUnsafePtr(verts);
        var indices = new NativeArray<UInt16>(vertexCount, Allocator.Temp);
        var pIndices = (UInt16*)NativeArrayUnsafeUtility.GetUnsafePtr(indices);
        for(int i = 0; i < vertexCount; i++)
        {
            var x = -0.5f + (float)(i % _texture.width) / (float)_texture.width;
            var z = -0.5f + (float)(i / _texture.width) / (float)_texture.height;
            pVerts[i].Position = new Vector4(x, 0, z, pBytes[i]);
            pIndices[i] = (UInt16)i;
        }
        mesh.SetVertexBufferData(verts, 0, 0, vertexCount);
        mesh.SetIndexBufferParams(vertexCount, IndexFormat.UInt16);
        mesh.SetIndices<UInt16>(indices, MeshTopology.Points, 0);

        mesh.bounds = new Bounds(Vector3.zero, new Vector3(1000, 1000, 1000));

        var meshFilter = gameObject.GetComponent<MeshFilter>();
        meshFilter.sharedMesh = mesh;
    }
}

感想

・QuadじゃなくてTriangleで良かったです。

・今回はMeshにしましたが、頂点データが無駄です。

参考

テッセレーション