ジャイロ(Gyro)で視差

環境

Unity2022.3.1f1

概要

ジャイロを使用した以下のツイートのような事をしたかったので試してみました。

スマホをひっくり返すと後部が見えます。

プログラムには傾斜錐台を使用しています。

コード

using UnityEngine;
using TMPro;
public class ObliqueProjection : MonoBehaviour
{
    [SerializeField] private Camera _camera = null;
    [SerializeField] private TextMeshProUGUI _text = null;
    [SerializeField] [Range(-1f,1f)] private float _horizontal = 0.0f;
    [SerializeField] [Range(-1f,1f)] private float _vertical = 0.0f;
    private Vector3 _org_position;
    private float _distance;

    void OnEnable()
    {
        _org_position = _camera.transform.localPosition;
        _distance = _org_position.magnitude;
        Input.gyro.enabled = true;
#if UNITY_EDITOR
        Input.gyro.enabled = false;
#endif
    }

    void OnDisable()
    {
    }

    void LateUpdate()
    {
        if(Input.gyro.enabled == true)
        {
            const float scale = 1.0f;
            const float offset = 1.0f;
            _horizontal = Mathf.Clamp(Input.gyro.gravity.x * scale, -1.0f, 1.0f);
            _vertical = Mathf.Clamp(Input.gyro.gravity.y * scale + offset, -1.0f, 1.0f);
        }
        var cam = _camera;
        var rect = cam.pixelRect;
        var ratio = (float)rect.height / (float)rect.width;
        _text.text = $"horizontal:{_horizontal} vertical:{_vertical}";

        cam.ResetProjectionMatrix();
        var proj = cam.projectionMatrix;
        proj.m02 = _horizontal * ratio;
        proj.m12 = _vertical;
        cam.projectionMatrix = proj;        
        var tan = Mathf.Tan(cam.fieldOfView*0.5f*Mathf.Deg2Rad);
        _camera.transform.localPosition = _org_position + new Vector3(-_horizontal * _distance * tan, 0.0f, -_vertical * _distance * tan);
    }
}

参考

傾斜錐台の使用 - Unity マニュアル

[Unity]カメラの消失点を中央からずらすスクリプト - Qiita

https://twitter.com/apphands/status/1556128036549865472

炎のシミュレーション2

環境

Unity2022.3.1f1

概要

以前作った炎のシミュレーションの外力部分をパーティクルにしたもののテストです。

パーティクルと同じ位置、見え方で炎を表示するために、シザープロジェクション行列を用いています。

炎のシェーダはフローマップとディティール用のボロノイ的なテクスチャを加えてそれっぽい感じにしたものです。

コード

using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using static UnityEngine.GraphicsBuffer;

public unsafe class TestFluid5 : MonoBehaviour
{
    [SerializeField] private Transform _target = null;
    [SerializeField] Material _material = null;
    [SerializeField] private ComputeShader _compute = null;
    [SerializeField] private RawImage _densityPreview = null;
    [SerializeField] private RawImage _velocityPreview = null;
    [SerializeField] private RawImage _forcePreview = null;
    [SerializeField] private Camera _camera = null;
    [SerializeField] private Camera _forceCamera = null;
    [SerializeField] private float _planeSize = 1.0f;
    [SerializeField] private Vector3 _pivot = Vector3.zero;
    [SerializeField] private float _attenuation = 0.99f;
    [SerializeField] private float _deisityAttenuation = 0.985f;
    [SerializeField] private int _simTexSize = 64;
    [SerializeField] private int _densityTexSize = 256;
    private RenderTexture _divergenceTexture = null;
    private DoubleBufferdRenderTexture _velocityBuffer = null;
    private DoubleBufferdRenderTexture _pressureBuffer = null;
    private DoubleBufferdRenderTexture _densityBuffer = null;
    private RenderTexture _forceTexture = null;

    private int _knUpdateAdvection;
    private int _knInteractionForce;
    private int _knInteractionDensityForce;
    private int _knUpdateDivergence;
    private int _knUpdatePressurePsfH;
    private int _knUpdatePressurePsfV;
    private int _knUpdateVelocity;
    private int _knUpdateDensity;
    private static int _spTime = Shader.PropertyToID("_Time");
    private static int _spDeltaTime = Shader.PropertyToID("_DeltaTime");
    private static int _spSourceVelocity = Shader.PropertyToID("_SourceVelocity");
    private static int _spResultVelocity = Shader.PropertyToID("_ResultVelocity");
    private static int _spSourcePressure = Shader.PropertyToID("_SourcePressure");
    private static int _spSourceForce = Shader.PropertyToID("_SourceForce");
    private static int _spResultPressure = Shader.PropertyToID("_ResultPressure");
    private static int _spResultDivergence = Shader.PropertyToID("_ResultDivergence");
    private static int _spSourceDensity = Shader.PropertyToID("_SourceDensity");
    private static int _spResultDensity = Shader.PropertyToID("_ResultDensity");
    private static int _spSimWidth = Shader.PropertyToID("_SimWidth");
    private static int _spSimHeight = Shader.PropertyToID("_SimHeight");
    private static int _spSimForceStart = Shader.PropertyToID("_SimForceStart");
    private static int _spDensityForceStart = Shader.PropertyToID("_DensityForceStart");
    private static int _spAttenuation = Shader.PropertyToID("_Attenuation");
    private static int _spDeisityAttenuation = Shader.PropertyToID("_DeisityAttenuation");
    private static int _spDensityWidth = Shader.PropertyToID("_DensityWidth");
    private static int _spDensityHeight = Shader.PropertyToID("_DensityHeight");
    private static int _spVectorFieldTex = Shader.PropertyToID("_VectorFieldTex");
    private static int _spDensityTex = Shader.PropertyToID("_DensityTex");

    private GraphicsBuffer _gbVertices = null;
    private GraphicsBuffer _gbWorldPositions = null;
    private Bounds _bounds;
    private Vector2Int _simFirePos = new Vector2Int(0, 2);
    private static int _spVertices = Shader.PropertyToID("_Vertices");
    private static int _spWorldPositions = Shader.PropertyToID("_WorldPositions");
    private static int _spDepthPosWS = Shader.PropertyToID("_DepthPosWS");

    private void Start()
    {
        Setup();
    }

    private void OnValidate()
    {
        _simTexSize = Mathf.Max(_simTexSize, 8);
        _densityTexSize = Mathf.Max(_densityTexSize, 8);

        DestroyBuffers();
        Setup();
    }

    private void OnDestroy()
    {
        DestroyBuffers();
        if(_gbVertices != null)
            _gbVertices.Dispose();
        if(_gbWorldPositions != null)
            _gbWorldPositions.Dispose();
    }

    private void Setup()
    {
        _camera.depthTextureMode |= DepthTextureMode.Depth;

        CreateBuffers();

        _knUpdateAdvection = _compute.FindKernel("UpdateAdvection");
        _knInteractionForce = _compute.FindKernel("InteractionForce");
        _knInteractionDensityForce = _compute.FindKernel("InteractionDensityForce");
        _knUpdateDivergence = _compute.FindKernel("UpdateDivergence");
        _knUpdatePressurePsfH = _compute.FindKernel("UpdatePressurePsfH");
        _knUpdatePressurePsfV = _compute.FindKernel("UpdatePressurePsfV");
        _knUpdateVelocity = _compute.FindKernel("UpdateVelocity");
        _knUpdateDensity = _compute.FindKernel("UpdateDensity");

        _gbVertices = new GraphicsBuffer(Target.Structured, 4, sizeof(Vector2));
        var vertices = new Vector2[]
        {
            new Vector2(-0.5f, -0.5f),
            new Vector2(-0.5f,  0.5f),
            new Vector2( 0.5f,  0.5f),
            new Vector2( 0.5f, -0.5f)
        };
        _gbVertices.SetData(vertices);
        _material.SetBuffer(_spVertices, _gbVertices);

        _bounds = new Bounds(Vector3.zero, new Vector3(10000.0f, 10000.0f, 10000.0f));

        _gbWorldPositions = new GraphicsBuffer(Target.Structured, UsageFlags.LockBufferForWrite, 4, sizeof(Vector3));
        _material.SetBuffer(_spWorldPositions, _gbWorldPositions);
    }

    private void CreateBuffers()
    {
        _velocityBuffer = DoubleBufferdRenderTexture.Create(new RenderTextureDescriptor(_simTexSize, _simTexSize, RenderTextureFormat.ARGBHalf, 0,0){enableRandomWrite = true});
        _pressureBuffer = DoubleBufferdRenderTexture.Create(new RenderTextureDescriptor(_simTexSize, _simTexSize, RenderTextureFormat.ARGBHalf, 0,0){enableRandomWrite = true});
        _densityBuffer = DoubleBufferdRenderTexture.Create(new RenderTextureDescriptor(_densityTexSize, _densityTexSize, RenderTextureFormat.ARGBHalf, 0,0){enableRandomWrite = true});
        _divergenceTexture = new RenderTexture(_simTexSize, _simTexSize, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
        _divergenceTexture.enableRandomWrite = true;
        _divergenceTexture.Create();

        _forceTexture = new RenderTexture(_simTexSize, _simTexSize, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
        _forceTexture.filterMode = FilterMode.Point;
        _forceCamera.targetTexture = _forceTexture;
        _forceTexture.Create();
    }

    private void DestroyBuffers()
    {
        if(_divergenceTexture != null)
            _divergenceTexture.Release();
        if(_velocityBuffer != null)
            _velocityBuffer.Dispose();
        if(_pressureBuffer != null)
            _pressureBuffer.Dispose();
        if(_densityBuffer != null)
            _densityBuffer.Dispose();
        if(_forceTexture != null)
            _forceTexture.Release();
    }

    private static void SetScissorRect(Camera cam, bool isAdjust, ref Rect r)
    {
        if(isAdjust == true)
        {
            if ( r.x < 0 )
            {
                r.width += r.x;
                r.x = 0;
            }
        
            if ( r.y < 0 )
            {
                r.height += r.y;
                r.y = 0;
            }
            r.width = Mathf.Min( 1 - r.x, r.width );
            r.height = Mathf.Min( 1 - r.y, r.height );          
        }
    
        Matrix4x4 m = cam.projectionMatrix;
//      Matrix4x4 m1 = Matrix4x4.TRS( new Vector3( r.x, r.y, 0 ), Quaternion.identity, new Vector3( r.width, r.height, 1 ) );
        Matrix4x4 m2 = Matrix4x4.TRS (new Vector3 ( ( 1/r.width - 1), ( 1/r.height - 1 ), 0), Quaternion.identity, new Vector3 (1/r.width, 1/r.height, 1));
        Matrix4x4 m3 = Matrix4x4.TRS( new Vector3( -r.x  * 2 / r.width, -r.y * 2 / r.height, 0 ), Quaternion.identity, Vector3.one );
        cam.projectionMatrix = m3 * m2 * m; 
    }   

    private void FixedUpdate()
    {
#if UNITY_EDITOR
         var sceneView = SceneView.lastActiveSceneView;
        if(sceneView != null)
        {
            var sceneViewCamera = sceneView.camera;
            _camera.transform.localPosition = sceneViewCamera.transform.localPosition;
            _camera.transform.localRotation = sceneViewCamera.transform.localRotation;
        }
#endif
        var sizeHalf = _planeSize * 0.5f;
        var sizeHalfH = sizeHalf;// * 1.0f / _mainCamera.aspect;
        var centerPosWS = _target.position + _pivot * -sizeHalf;
        var centerPosVS = _camera.worldToCameraMatrix.MultiplyPoint(centerPosWS);
        var posVPSs = (Span<Vector3>)stackalloc Vector3[4];

        posVPSs[0] = _camera.projectionMatrix.MultiplyPoint(centerPosVS + new Vector3(-sizeHalfH, -sizeHalf, 0.0f));
        posVPSs[1] = _camera.projectionMatrix.MultiplyPoint(centerPosVS + new Vector3( sizeHalfH,  sizeHalf, 0.0f));
        if(float.IsNaN(posVPSs[0].x) == true || float.IsInfinity(posVPSs[0].x) == true)
            return;
        posVPSs[0] = posVPSs[0] * 0.5f + new Vector3(0.5f, 0.5f, 0.0f);
        posVPSs[1] = posVPSs[1] * 0.5f + new Vector3(0.5f, 0.5f, 0.0f);
        var scissorRect = new Rect();
        scissorRect.min = posVPSs[0];
        scissorRect.max = posVPSs[1];

        _forceCamera.rect = new Rect(0,0,1,1);
        _forceCamera.ResetProjectionMatrix();
        _forceCamera.transform.position = _camera.transform.position;
        _forceCamera.transform.rotation = _camera.transform.rotation;
        _forceCamera.fieldOfView = _camera.fieldOfView;
        var forceTargetTex = _forceCamera.targetTexture;
        _forceCamera.targetTexture = null;
        var isAdjust = false;
        SetScissorRect(_forceCamera, isAdjust, ref scissorRect);
        posVPSs[0].Set(scissorRect.min.x, scissorRect.min.y, posVPSs[0].z);
        posVPSs[1].Set(scissorRect.min.x, scissorRect.max.y, posVPSs[1].z);
        posVPSs[2].Set(scissorRect.max.x, scissorRect.max.y, posVPSs[2].z);
        posVPSs[3].Set(scissorRect.max.x, scissorRect.min.y, posVPSs[3].z);

        _forceCamera.targetTexture = forceTargetTex;

        var targetPosVS = _camera.worldToCameraMatrix.MultiplyPoint(_target.position);
        var positions = _gbWorldPositions.LockBufferForWrite<Vector3>(0, 4);
        for(int i = 0; i < 4; i++)
        {
            posVPSs[i].z = -centerPosVS.z;//-targetPosVS.z - sizeHalf;
            positions[i] = _camera.ViewportToWorldPoint(posVPSs[i]);
        }
        Debug.DrawLine(positions[0], positions[1], Color.cyan);
        Debug.DrawLine(positions[1], positions[2], Color.cyan);
        Debug.DrawLine(positions[2], positions[3], Color.cyan);
        Debug.DrawLine(positions[3], positions[0], Color.cyan);
        _gbWorldPositions.UnlockBufferAfterWrite<Vector3>(4);

        var depthPos = _target.position + _pivot * -_planeSize;
        _material.SetVector(_spDepthPosWS, depthPos);

        _compute.SetFloat(_spSimWidth, _simTexSize);
        _compute.SetFloat(_spSimHeight, _simTexSize);
        _compute.SetFloat(_spDeltaTime, Time.deltaTime);
        _compute.SetFloat(_spAttenuation, _attenuation);
        _compute.SetFloat(_spDeisityAttenuation, _deisityAttenuation);
        _compute.SetFloat(_spTime, Time.time);
        _compute.SetFloat(_spDensityWidth, _densityTexSize);
        _compute.SetFloat(_spDensityHeight, _densityTexSize);

        UpdateAdvection();
        InteractionForce();
        UpdateDivergence();
        UpdatePressure();
        UpdateVelocity();
        UpdateDensity();

        _forcePreview.texture = _forceTexture;
        _velocityPreview.texture = _velocityBuffer.current;
        _densityPreview.texture = _densityBuffer.current;
        _material.SetTexture(_spVectorFieldTex, _velocityBuffer.current);
        _material.SetTexture(_spDensityTex, _densityBuffer.current);
    }

    private void Update()
    {
        Graphics.DrawProcedural(_material, _bounds, MeshTopology.Quads, 4, 1, _camera);
    }

    // 移流
    private void UpdateAdvection()
    {
        _compute.SetTexture(_knUpdateAdvection, _spSourceVelocity, _velocityBuffer.current);
        _compute.SetTexture(_knUpdateAdvection, _spResultVelocity, _velocityBuffer.back);
        _compute.Dispatch(_knUpdateAdvection, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
        _velocityBuffer.Flip();
    }

    // 外力
    private void InteractionForce()
    {
        _compute.SetTexture(_knInteractionForce, _spSourceForce, _forceTexture);
        _compute.SetTexture(_knInteractionForce, _spResultVelocity, _velocityBuffer.current);
        _compute.SetTexture(_knInteractionForce, _spResultDensity, _densityBuffer.current);
        _compute.Dispatch(_knInteractionForce, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
    }

    // 発散
    private void UpdateDivergence()
    {
        _compute.SetTexture(_knUpdateDivergence, _spSourceVelocity, _velocityBuffer.current);
        _compute.SetTexture(_knUpdateDivergence, _spResultDivergence, _divergenceTexture);
        _compute.Dispatch(_knUpdateDivergence, _divergenceTexture.width / 8, _divergenceTexture.height / 8, 1);
    }

    // 圧力
    private void UpdatePressure()
    {
        // Horizontal
        _compute.SetTexture(_knUpdatePressurePsfH, _spSourcePressure, _pressureBuffer.current);
        _compute.SetTexture(_knUpdatePressurePsfH, _spResultPressure, _pressureBuffer.back);
        _compute.SetTexture(_knUpdatePressurePsfH, _spResultDivergence, _divergenceTexture);
        _compute.Dispatch(_knUpdatePressurePsfH, _pressureBuffer.width / 8, _pressureBuffer.height / 8, 1);
        // Vertical
        _compute.SetTexture(_knUpdatePressurePsfV, _spSourcePressure, _pressureBuffer.current);
        _compute.SetTexture(_knUpdatePressurePsfV, _spResultPressure, _pressureBuffer.back);
        _compute.SetTexture(_knUpdatePressurePsfV, _spResultDivergence, _divergenceTexture);
        _compute.Dispatch(_knUpdatePressurePsfV, _pressureBuffer.width / 8, _pressureBuffer.height / 8, 1);

        _pressureBuffer.Flip();
    }

    // 速度
    private void UpdateVelocity()
    {
        _compute.SetTexture(_knUpdateVelocity, _spSourceVelocity, _velocityBuffer.current);
        _compute.SetTexture(_knUpdateVelocity, _spSourcePressure, _pressureBuffer.current);
        _compute.SetTexture(_knUpdateVelocity, _spResultVelocity, _velocityBuffer.back);
        _compute.Dispatch(_knUpdateVelocity, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
        _velocityBuffer.Flip();
    }

    // 密度
    private void UpdateDensity()
    {
        _compute.SetTexture(_knUpdateDensity, _spSourceDensity, _densityBuffer.current);
        _compute.SetTexture(_knUpdateDensity, _spSourceVelocity, _velocityBuffer.current);
        _compute.SetTexture(_knUpdateDensity, _spResultDensity, _densityBuffer.back);
        _compute.Dispatch(_knUpdateDensity, _densityBuffer.width / 8, _densityBuffer.height / 8, 1);
        _densityBuffer.Flip();
    }
}
Shader "Custom/Fire4"
{
    Properties
    {
        _GradientTex ("Gradient Texture", 2D) = "white" {}
        _DetailTex ("Detail Texture", 2D) = "white" {}
        _FlowNoiseTex ("FlowNoise Texture", 2D) = "white" {}
        _FlowNoiseScale ("FlowNoise Scale", Range(0.0, 1.0)) = 0.5
        _FlowAnimLength ("Flow Animation Length", Range(0.0, 20.0)) = 4
        _FlowStrength ("Flow Strength", Range(0.0, 1.0)) = 0.5
        _DetailBlendScale ("Detail BlendScale", Range(0.0, 5.0)) = 2.0
        _DetailAlphaScale ("Detail AlphaScale", Range(0.0, 5.0)) = 2.0
        _SoftParticlesNearFadeDistance("Soft Particles Near Fade", Float) = 0.01
        _SoftParticlesFarFadeDistance("Soft Particles Far Fade", Float) = 1.0
    }

    CGINCLUDE
    #include "UnityCG.cginc"

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

    struct v2f
    {
        float4 posCS : SV_POSITION;
        float2 uv : TEXCOORD1;
        float4 posSS : TEXCOORD2;
    };
    
    sampler2D _VectorFieldTex;
    float4 _VectorFieldTex_TexelSize;
    sampler2D _DensityTex;
    float4 _DensityTex_TexelSize;
    sampler2D _GradientTex;
    sampler2D _DetailTex;
    float4 _DetailTex_ST;
    StructuredBuffer<float2> _Vertices;
    StructuredBuffer<float3> _WorldPositions;

    sampler2D _FlowNoiseTex;
    float _FlowNoiseScale;
    float _FlowAnimLength;
    float _FlowStrength;
    float _DetailBlendScale;
    float _DetailAlphaScale;

    sampler2D _CameraDepthTexture;
    float _SoftParticlesNearFadeDistance;
    float _SoftParticlesFarFadeDistance;

    float GetFlowDetail(float2 uv, float2 flowDir)
    {
        half phaseOffset = _FlowNoiseScale * tex2D(_FlowNoiseTex, uv).r;
        float flowPhase0 = frac(phaseOffset + (_Time.y) / _FlowAnimLength);
        float flowPhase1 = frac(phaseOffset + (_Time.y) / _FlowAnimLength + 0.5);
        float flowLerp = abs(0.5 - flowPhase0) * 2;

        float detail0 = tex2D(_DetailTex, uv * _DetailTex_ST.xy + flowDir * flowPhase0);
        float detail1 = tex2D(_DetailTex, uv * _DetailTex_ST.xy + flowDir * flowPhase1);
        return lerp(detail0, detail1, flowLerp);
    } 

    ENDCG

    SubShader
    {
        Tags
        { 
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }

        ZWrite Off
        Lighting Off
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off
        Pass
        {
            ZTest Always

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            v2f vert(appdata v)
            {
                v2f o;
                const uint vnum = 4;
                uint vertexId = v.vertexId % vnum;

                o.posCS = mul(UNITY_MATRIX_VP, float4(_WorldPositions[vertexId], 1.0));

                const float2 uv[] =
                {
                    float2(0, 0),
                    float2(0, 1),
                    float2(1, 1),
                    float2(1, 0)
                };
                o.uv = uv[vertexId];
                o.posSS = ComputeScreenPos(o.posCS);

                o.posSS.z = -mul(UNITY_MATRIX_V, float4(_WorldPositions[vertexId], 1.0)).z;
                return o;
            }
 
            float4 frag(v2f i) : SV_Target
            {
                float4 col;
                float2 vec = tex2D(_VectorFieldTex, i.uv);
                float density = saturate(tex2D(_DensityTex, i.uv).a);
                float2 flowDir = -vec * lerp(_FlowStrength * 0.3, _FlowStrength, 1.0 - density);
                float detail = GetFlowDetail(i.uv, flowDir);

                float gradiant = density - (1.0 - detail) * _DetailBlendScale;
                col = fixed4(tex2D(_GradientTex, float2(gradiant, 0.5)).rgb, saturate((gradiant * _DetailAlphaScale) * density));
                float invFadeDistance = 1.0 / (_SoftParticlesFarFadeDistance - _SoftParticlesNearFadeDistance);
                float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.posSS)));
                float softParticlesFade = saturate(invFadeDistance * ((sceneZ - _SoftParticlesNearFadeDistance) - i.posSS.z));
                col.a *= softParticlesFade;

                return col;
            }
            ENDCG
        }
    }
}

参考

Scissor rectangle - Unity Forum

炎のシミュレーション - テキトープログラム( ..)φメモ

NaiveSurfaceNetsで穴堀り

環境

Unity2022.2.14f1

概要

以前作ったNaiveSurfaceNetsでボックスを作り、球状に穴を開けるテストです。

基本的に以前のコードのままですが、ComputeShaderに以下の穴を開ける関数と法線を算出する関数を追加しています。

モグラゲームとか作れそうです。

コード

#pragma kernel ChangeSdfVoxels
[numthreads(1, 1, 1)]
void ChangeSdfVoxels(uint3 id : SV_DispatchThreadID)
{
    uint3 start = SpherePos - SphereRadius;
    uint3 vpos = start + id;
    if(vpos.x > SdfVoxelSize - 1 || vpos.y > SdfVoxelSize - 1 || vpos.z > SdfVoxelSize - 1)
        return;
    
    uint index = (vpos.z * SdfVoxelSize * SdfVoxelSize) + (vpos.y * SdfVoxelSize) + (vpos.x);
    float value = 1.0 - (length(vpos - SpherePos) / (float)SphereRadius);
    SdfVoxels[index] = max(value, SdfVoxels[index]);
}

#pragma kernel GenerateNormals
[numthreads(1024, 1, 1)]
void GenerateNormals(uint3 id : SV_DispatchThreadID)
{
    if(id.x >= IndirectArgs[0] / 3)
        return;

    uint vindex = id.x * 3;
    float3 vec0 = Vertices[Indices[vindex + 1]] - Vertices[Indices[vindex]];
    float3 vec1 = Vertices[Indices[vindex + 2]] - Vertices[Indices[vindex]];
    float3 normal = normalize(cross(vec0, vec1));
    float QUANTIIZE_FACTOR = 32768.0;
    int3 inormal = (int3) (normal * QUANTIIZE_FACTOR);

    Normals.InterlockedAdd(normalStride * Indices[vindex + 0] + 0, inormal.x);
    Normals.InterlockedAdd(normalStride * Indices[vindex + 0] + 4, inormal.y);
    Normals.InterlockedAdd(normalStride * Indices[vindex + 0] + 8, inormal.z);

    Normals.InterlockedAdd(normalStride * Indices[vindex + 1] + 0, inormal.x);
    Normals.InterlockedAdd(normalStride * Indices[vindex + 1] + 4, inormal.y);
    Normals.InterlockedAdd(normalStride * Indices[vindex + 1] + 8, inormal.z);

    Normals.InterlockedAdd(normalStride * Indices[vindex + 2] + 0, inormal.x);
    Normals.InterlockedAdd(normalStride * Indices[vindex + 2] + 4, inormal.y);
    Normals.InterlockedAdd(normalStride * Indices[vindex + 2] + 8, inormal.z);
}
Shader "Unlit/GpuNaiveSurfaceNets2"
{
    Properties
    {
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    #include "Lighting.cginc"

    struct appdata
    {
        uint vertexId : SV_VertexID;
    };

    struct v2f
    {
        float4 posCS : SV_POSITION;
        float3 normalOS : NORMAL;
        float3 lightDirOS : TEXCOORD0;
    };
    StructuredBuffer<float3> Vertices;
    StructuredBuffer<int3> Normals;
    StructuredBuffer<uint> Indices;
    sampler2D _MainTex;
    float4 _MainTex_ST;

    ENDCG

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

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

            v2f vert (appdata v)
            {
                v2f o;

                float3 vertex = Vertices[Indices[v.vertexId]];
                float4 posOS = float4(vertex, 1.0);
                o.posCS = UnityObjectToClipPos(posOS);
#if 0
                // flat shading
                uint vindex = v.vertexId - (v.vertexId % 3);
                float3 vec0 = Vertices[Indices[vindex + 1]] - Vertices[Indices[vindex]];
                float3 vec1 = Vertices[Indices[vindex + 2]] - Vertices[Indices[vindex]];
                o.normalOS = normalize(cross(vec0, vec1));
#else
                float QUANTIIZE_FACTOR = 32768.0;
                int3 n = Normals[Indices[v.vertexId]];
                o.normalOS = normalize(float3(n.x / QUANTIIZE_FACTOR, n.y / QUANTIIZE_FACTOR, n.z / QUANTIIZE_FACTOR));
#endif
                o.lightDirOS = ObjSpaceLightDir(posOS);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 normalOS = normalize(i.normalOS);
                float3 lightDirOS = normalize(i.lightDirOS);
                float NdotL = dot(normalOS, lightDirOS) * 0.5 + 0.5;
                fixed4 col = fixed4(1,0,0,1);
                col.rgb *= NdotL;
                return col;
            }
            ENDCG
        }
    }
}

参考

SDFの視覚化(NaiveSurfaceNets) - テキトープログラム( ..)φメモ

Question - Calculating Normals of a Mesh in Compute Shader - Unity Forum

ユニティちゃんがいっぱい

環境

Unity2022.2.14f1

概要

以前作ったAnimationInstancing的なコードとComputeShaderを用いたSpringBoneで作ったものです。

手法はだいたい同じですが、SpringBoneはSphere(髪やスカートについているSpringColliderの値を使用したもの)とのコリジョン判定をして体にめり込まないようにしています。

顔の表情切り替えにも対応していて、VATを用いて切り替えています。

うちの非力なPC(GTX1660)でもSDユニティちゃんが1000体以上出ています。

表情や揺れものがない、シンプルなAnimationInstancingなら2000弱位出ていた気がします。

参考

DrawProcedualで1マテでスキニングモデルの一括描画テスト - テキトープログラム( ..)φメモ

ComputeShaderでSpringBone - テキトープログラム( ..)φメモ

Gizmos.DrawFrustumの使い方

環境

Unity2022.2.14f1

概要

ビューフラスタム(視錐台)を表示するデバッグ機能の使い方

コード

private void OnDrawGizmos()
{
    Gizmos.color = Color.cyan;
    Gizmos.matrix = Matrix4x4.TRS(_camera.transform.position, _camera.transform.rotation, new Vector3(_camera.aspect, 1.0f, 1.0f));

    Gizmos.DrawFrustum(Vector3.zero, _camera.fieldOfView, _camera.farClipPlane, _camera.nearClipPlane, 1.0f);

    // 指定距離のクリップ面のサイズ
    float height = _cameraDistance * Mathf.Tan(_camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * 2.0f;
    Gizmos.DrawCube(Vector3.forward * _cameraDistance, new Vector3(height * _camera.aspect, height, 0.1f));

    // クリップ面の高さから距離を求める
    float distance = height * 0.5f / Mathf.Tan(_camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
    Gizmos.DrawLine(Vector3.zero, Vector3.forward * distance);
}

ニット調画面

環境

Unity2022.2.14f1

概要

画面をニット調にするテストです。

コード

using UnityEngine;
using UnityEngine.Rendering;

public class Knit : MonoBehaviour
{
    [SerializeField] private Material _material = null;
    [SerializeField] private int _tileSize = 16;

    private void Start()
    {
        var camera = GetComponent<Camera>();
        var tileNum = new Vector2Int(camera.pixelWidth / _tileSize, camera.pixelHeight / _tileSize);

        var renderTex = new RenderTexture(tileNum.x, tileNum.y, 24, RenderTextureFormat.Default);
#if true
        renderTex.filterMode = FilterMode.Point;
#endif
        var rts = new RenderBuffer[]
        {
            renderTex.colorBuffer,
        };
        camera.SetTargetBuffers(rts, renderTex.depthBuffer);
        _material.SetVector("_TileNum", new Vector2(tileNum.x / 2, tileNum.y));

        var commandBuffer = new CommandBuffer();
        commandBuffer.name = "BlitRenderTexture";
        commandBuffer.SetRenderTarget(-1);
        commandBuffer.Blit(renderTex, BuiltinRenderTextureType.CurrentActive, _material);
        camera.AddCommandBuffer(CameraEvent.AfterEverything, commandBuffer);
    }
}
Shader "Hidden/Knit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ColorBias("Color Bias", Range(0.0, 1)) = 0.0
        _ColorScale("Color Scale", Range(0.5, 3)) = 1.5
        _SeamBias("Seam Bias", Range(0.0, 0.5)) = 0.2
    }
    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;
            }

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            float2 _TileNum;
            float _ColorBias;
            float _ColorScale;
            float _SeamBias;

            float Seam(float2 uv)
            {
                uv.y = (1.0 - uv.y) * 0.5 + 0.5;
                float sb = length(uv - float2(0.5, 1.0));
                float sl = length(uv - float2(0.0, 0.5));
                float sr = length(uv - float2(1.0, 0.5));
                sl = max(sb, sl);
                sr = max(sb, sr);
                float s = min(sl, sr);
                return smoothstep(s - _SeamBias, s + _SeamBias, 0.5);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 tile = i.uv * _TileNum;
                float2 uvInTile = frac(tile);
                fixed4 col = tex2D(_MainTex, i.uv);

                float mask = Seam(uvInTile);
                col.rgb = (col.rgb + _ColorBias.xxx) * _ColorScale * mask;
                return col;
            }
            ENDCG
        }
    }
}

画像を歪ませる版

Shader "Hidden/Knit2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ColorBias("Color Bias", Range(0.0, 1)) = 0.0
        _ColorScale("Color Scale", Range(0.5, 3)) = 1.5
        _SeamBias("Seam Bias", Range(0.0, 0.5)) = 0.2
    }
    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;
            }

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            float2 _TileNum;
            float _ColorBias;
            float _ColorScale;
            float _SeamBias;

            float Seam(float2 uv)
            {
                //y = 0.0 ~ 1.0 -> 1.0 ~ 0.5
                uv.y = (1.0 - uv.y) * 0.5 + 0.5;
                const float scale = 0.85;
                float sb = length(uv - float2(0.5, 1.0)) * scale;
                float sl = length(uv - float2(0.0, 0.5)) * scale;
                float sr = length(uv - float2(1.0, 0.5)) * scale;
                sl = max(sb, sl);
                sr = max(sb, sr);
                float s = min(sl, sr);
                return smoothstep(s - _SeamBias, s + _SeamBias, 0.5);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 tile = i.uv * _TileNum;
                float2 uvInTile = frac(tile);

                tile.y += abs(uvInTile.x - 0.5);
                float2 uv = tile / _TileNum;
                fixed4 col = tex2D(_MainTex, uv);

                tile = uv * _TileNum;
                uvInTile = frac(tile);

                float mask = Seam(uvInTile);
                col.rgb = (col.rgb + _ColorBias.xxx) * _ColorScale * mask;
                return col;
            }
            ENDCG
        }
    }
}

RenderTextureを使わず、ImageEffectでやる場合

Shader "Hidden/ImageEffectKnit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _TileSize("Tile Size", int) = 16
        _ColorBias("Color Bias", Range(0.0, 1)) = 0.0
        _ColorScale("Color Scale", Range(0.5, 3)) = 1.5
        _SeamBias("Seam Bias", Range(0.0, 0.5)) = 0.2
    }
    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;
            }

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            uint _TileSize;
            float _ColorBias;
            float _ColorScale;
            float _SeamBias;

            float Seam(float2 uv)
            {
                uv.y = (1.0 - uv.y) * 0.5 + 0.5;
                float sb = length(uv - float2(0.5, 1.0));
                float sl = length(uv - float2(0.0, 0.5));
                float sr = length(uv - float2(1.0, 0.5));
                sl = max(sb, sl);
                sr = max(sb, sr);
                float s = min(sl, sr);
                return smoothstep(s - _SeamBias, s + _SeamBias, 0.5);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                uint2 tileNum = uint2(_MainTex_TexelSize.z / (_TileSize * 2), _MainTex_TexelSize.w / _TileSize);
                float2 tile = i.uv * tileNum;
                float2 uvInTile = frac(tile);

                float2 uv = (floor(tile) + 0.5) / tileNum;
                fixed4 col = tex2D(_MainTex, uv);

                float mask = Seam(uvInTile);
                col.rgb = (col.rgb + _ColorBias.xxx) * _ColorScale * mask;
                return col;
            }
            ENDCG
        }
    }
}

感想

renderTex.filterMode = FilterMode.Point;

コメントアウトしてバイリニア補間にしたほうが色が漏れてそれっぽく見えました。

参考

Shader - Shadertoy BETA

炎のシミュレーション

環境

Unity2022.2.14f1

概要

炎のシミュレーションのテストです。

参考の動画を見つつ、ナビエストークスを「poisson separable filter」を使用して行ってみました。 正直正しいのかわからないです。

ヤコビ反復32回分の代替になると解説している気がする。

コード

using UnityEngine;
using UnityEngine.UI;

public class TestFluid : MonoBehaviour
{
    public class DoubleRenderTexture
    {
        private RenderTexture[] _textures = new RenderTexture[2];
        public RenderTexture current => _textures[0];
        public RenderTexture back => _textures[1];
        private int _width = 0;
        private int _height = 0;
        public int width => _width;
        public int height => _height;
        public DoubleRenderTexture(int width, int height)
        {
            _width = width;
            _height = height;
            for (int i = 0; i < _textures.Length; i++)
            {
                _textures[i] = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
                _textures[i].enableRandomWrite = true;
                _textures[i].Create();
            }
        }

        public void Flip()
        {
            RenderTexture temp = _textures[0];
            _textures[0] = _textures[1];
            _textures[1] = temp;
        }

        public void Release()
        {
            foreach (var texture in _textures)
                texture.Release();
        }
    }

    [SerializeField] Material _material = null;
    [SerializeField] private ComputeShader _shader = null;
    [SerializeField] private RawImage _densityPreview = null;
    [SerializeField] private RawImage _velocityPreview = null;
    [SerializeField] private float _attenuation = 0.99f;
    [SerializeField] private float _deisityAttenuation = 0.985f;
    [SerializeField] private Vector2Int _vectorNum = new Vector2Int(16, 16);
    [SerializeField] private Vector2Int _densityNum = new Vector2Int(128, 128);

    private RenderTexture _divergenceTexture = null;
    private DoubleRenderTexture _velocityBuffer = null;
    private DoubleRenderTexture _pressureBuffer = null;
    private DoubleRenderTexture _densityBuffer = null;

    private int _knUpdateAdvection;
    private int _knInteractionForce;
    private int _knInteractionDensityForce;
    private int _knUpdateDivergence;
    private int _knUpdatePressurePsfH;
    private int _knUpdatePressurePsfV;
    private int _knUpdateVelocity;
    private int _knUpdateDensity;
    private static int _spTime = Shader.PropertyToID("_Time");
    private static int _spDeltaTime = Shader.PropertyToID("_DeltaTime");
    private static int _spSourceVelocity = Shader.PropertyToID("_SourceVelocity");
    private static int _spResultVelocity = Shader.PropertyToID("_ResultVelocity");
    private static int _spSourcePressure = Shader.PropertyToID("_SourcePressure");
    private static int _spResultPressure = Shader.PropertyToID("_ResultPressure");
    private static int _spResultDivergence = Shader.PropertyToID("_ResultDivergence");
    private static int _spSourceDensity = Shader.PropertyToID("_SourceDensity");
    private static int _spResultDensity = Shader.PropertyToID("_ResultDensity");
    private static int _spSimWidth = Shader.PropertyToID("_SimWidth");
    private static int _spSimHeight = Shader.PropertyToID("_SimHeight");
    private static int _spSimForceStart = Shader.PropertyToID("_SimForceStart");
    private static int _spDensityForceStart = Shader.PropertyToID("_DensityForceStart");
    private static int _spAttenuation = Shader.PropertyToID("_Attenuation");
    private static int _spDeisityAttenuation = Shader.PropertyToID("_DeisityAttenuation");
    private static int _spDensityWidth = Shader.PropertyToID("_DensityWidth");
    private static int _spDensityHeight = Shader.PropertyToID("_DensityHeight");
    private static int _spVectorFieldTex = Shader.PropertyToID("_VectorFieldTex");
    private static int _spDensityTex = Shader.PropertyToID("_DensityTex");

    private void Start()
    {
        Setup();
    }

    private void OnValidate()
    {
        _vectorNum.x = Mathf.Max(_vectorNum.x, 8);
        _vectorNum.y = Mathf.Max(_vectorNum.y, 8);
        _densityNum.x = Mathf.Max(_densityNum.x, 8);
        _densityNum.y = Mathf.Max(_densityNum.y, 8);

        DestroyBuffers();
        Setup();
    }

    private void OnDestroy()
    {
        DestroyBuffers();
    }

    private void Setup()
    {
        CreateBuffers();

        _knUpdateAdvection = _shader.FindKernel("UpdateAdvection");
        _knInteractionForce = _shader.FindKernel("InteractionForce");
        _knInteractionDensityForce = _shader.FindKernel("InteractionDensityForce");
        _knUpdateDivergence = _shader.FindKernel("UpdateDivergence");
        _knUpdatePressurePsfH = _shader.FindKernel("UpdatePressurePsfH");
        _knUpdatePressurePsfV = _shader.FindKernel("UpdatePressurePsfV");
        _knUpdateVelocity = _shader.FindKernel("UpdateVelocity");
        _knUpdateDensity = _shader.FindKernel("UpdateDensity");
    }

    private void CreateBuffers()
    {
        _velocityBuffer = new DoubleRenderTexture(_vectorNum.x, _vectorNum.y);
        _pressureBuffer = new DoubleRenderTexture(_vectorNum.x, _vectorNum.y);
        _densityBuffer = new DoubleRenderTexture(_densityNum.x, _densityNum.y);
        _divergenceTexture = new RenderTexture(_vectorNum.x, _vectorNum.y, 0, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
        _divergenceTexture.enableRandomWrite = true;
        _divergenceTexture.Create();
    }

    private void DestroyBuffers()
    {
        if(_divergenceTexture != null)
            _divergenceTexture.Release();
        if(_velocityBuffer != null)
            _velocityBuffer.Release();
        if(_pressureBuffer != null)
            _pressureBuffer.Release();
        if(_densityBuffer != null)
            _densityBuffer.Release();
    }

    private void Update()
    {
        _shader.SetFloat(_spSimWidth, _vectorNum.x);
        _shader.SetFloat(_spSimHeight, _vectorNum.y);
        _shader.SetFloat(_spDeltaTime, Time.deltaTime);
        _shader.SetFloat(_spAttenuation, _attenuation);
        _shader.SetFloat(_spDeisityAttenuation, _deisityAttenuation);
        _shader.SetFloat(_spTime, Time.time);
        _shader.SetFloat(_spDensityWidth, _densityNum.x);
        _shader.SetFloat(_spDensityHeight, _densityNum.y);

        UpdateAdvection();
        InteractionForce();
        UpdateDivergence();
        UpdatePressure();
        UpdateVelocity();
        UpdateDensity();

        _velocityPreview.texture = _velocityBuffer.current;
        _densityPreview.texture = _densityBuffer.current;
        _material.SetTexture(_spVectorFieldTex, _velocityBuffer.current);
        _material.SetTexture(_spDensityTex, _densityBuffer.current);
    }

    // 移流
    private void UpdateAdvection()
    {
        _shader.SetTexture(_knUpdateAdvection, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateAdvection, _spResultVelocity, _velocityBuffer.back);
        _shader.Dispatch(_knUpdateAdvection, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
        _velocityBuffer.Flip();
    }

    // 外力
    private void InteractionForce()
    {
        var width = _vectorNum.x / 8;
        var halfWidth = width / 2;
        var halfX = _vectorNum.x / 2;
        var start = halfX - halfWidth;
        _shader.SetInt(_spSimForceStart, start);
        _shader.SetTexture(_knInteractionForce, _spResultVelocity, _velocityBuffer.current);
        _shader.Dispatch(_knInteractionForce, width, 1, 1);

        // Density
        width = _densityNum.x / 8;
        halfWidth = width / 2;
        halfX = _densityNum.x / 2;
        start = halfX - halfWidth;
        _shader.SetInt(_spDensityForceStart, start);
        _shader.SetTexture(_knInteractionDensityForce, _spResultDensity, _densityBuffer.current);
        _shader.Dispatch(_knInteractionDensityForce, width, 1, 1);
    }

    // 発散
    private void UpdateDivergence()
    {
        _shader.SetTexture(_knUpdateDivergence, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateDivergence, _spResultDivergence, _divergenceTexture);
        _shader.Dispatch(_knUpdateDivergence, _divergenceTexture.width / 8, _divergenceTexture.height / 8, 1);
    }

    // 圧力
    private void UpdatePressure()
    {
        // Horizontal
        _shader.SetTexture(_knUpdatePressurePsfH, _spSourcePressure, _pressureBuffer.current);
        _shader.SetTexture(_knUpdatePressurePsfH, _spResultPressure, _pressureBuffer.back);
        _shader.SetTexture(_knUpdatePressurePsfH, _spResultDivergence, _divergenceTexture);
        _shader.Dispatch(_knUpdatePressurePsfH, _pressureBuffer.width / 8, _pressureBuffer.height / 8, 1);
        // Vertical
        _shader.SetTexture(_knUpdatePressurePsfV, _spSourcePressure, _pressureBuffer.current);
        _shader.SetTexture(_knUpdatePressurePsfV, _spResultPressure, _pressureBuffer.back);
        _shader.SetTexture(_knUpdatePressurePsfV, _spResultDivergence, _divergenceTexture);
        _shader.Dispatch(_knUpdatePressurePsfV, _pressureBuffer.width / 8, _pressureBuffer.height / 8, 1);

        _pressureBuffer.Flip();
    }

    // 速度
    private void UpdateVelocity()
    {
        _shader.SetTexture(_knUpdateVelocity, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateVelocity, _spSourcePressure, _pressureBuffer.current);
        _shader.SetTexture(_knUpdateVelocity, _spResultVelocity, _velocityBuffer.back);
        _shader.Dispatch(_knUpdateVelocity, _velocityBuffer.width / 8, _velocityBuffer.height / 8, 1);
        _velocityBuffer.Flip();
    }

    // 密度
    private void UpdateDensity()
    {
        _shader.SetTexture(_knUpdateDensity, _spSourceDensity, _densityBuffer.current);
        _shader.SetTexture(_knUpdateDensity, _spSourceVelocity, _velocityBuffer.current);
        _shader.SetTexture(_knUpdateDensity, _spResultDensity, _densityBuffer.back);
        _shader.Dispatch(_knUpdateDensity, _densityBuffer.width / 8, _densityBuffer.height / 8, 1);
        _densityBuffer.Flip();
    }
}
#pragma kernel UpdateAdvection
#pragma kernel InteractionForce
#pragma kernel InteractionDensityForce
#pragma kernel UpdateDivergence
#pragma kernel UpdatePressurePsfH
#pragma kernel UpdatePressurePsfV
#pragma kernel UpdateVelocity
#pragma kernel UpdateDensity

Texture2D<float4> _SourceVelocity;
Texture2D<float4> _SourcePressure;
Texture2D<float4> _SourceDensity;

RWTexture2D<float4> _ResultVelocity;
RWTexture2D<float4> _ResultPressure;
RWTexture2D<float4> _ResultDivergence;
RWTexture2D<float4> _ResultDensity;

#define SAMPLE _LinearClamp
SamplerState _LinearClamp;
SamplerState _PointClamp;

float _Time;
float _DeltaTime;
float _SimWidth;
float _SimHeight;
uint _SimForceStart;
uint _DensityForceStart;
float _DensityWidth;
float _DensityHeight;
float _Attenuation;
float _DeisityAttenuation;

#define PI 3.1415926538
#define PI2 (PI * 2)

float RandomRange(float2 Seed, float Min, float Max)
{
    float randomno =  frac(sin(dot(Seed, float2(12.9898, 78.233)))*43758.5453);
    return lerp(Min, Max, randomno);
}

// 移流
[numthreads(8,8,1)]
void UpdateAdvection(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0/w, 1.0/h, 0.0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;

    float2 velocity = _SourceVelocity.SampleLevel(SAMPLE, uv, 0).xy;
    float2 result = _SourceVelocity.SampleLevel(SAMPLE, uv - velocity * _DeltaTime, 0).xy;

    _ResultVelocity[id] = float4(result, 0.0, 1.0);
}

// 外力
[numthreads(1,1,1)]
void InteractionForce(uint2 id : SV_DispatchThreadID)
{
    id.x += _SimForceStart;
    const float power = 1.5;
    float3 vec = float3(0.0, power, 0.0);
    _ResultVelocity[id] = float4(vec, 1.0);

    // 適当な風力
    const float speed = 5.0;
    float rad = _Time * speed;
    id.x -= _SimForceStart;
    id.y = _SimHeight / 2;
    vec.xy = float2(cos(rad), sin(rad)) * power * 1.0;
    _ResultVelocity[id] = float4(vec, 1.0);
}

[numthreads(1,1,1)]
void InteractionDensityForce(uint2 id : SV_DispatchThreadID)
{
    id.x += _DensityForceStart;
    _ResultDensity[id] = RandomRange(_Time + id.x, 0.3, 1.0);
}

// 発散
[numthreads(8,8,1)]
void UpdateDivergence(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;

    float x0 = _SourceVelocity.SampleLevel(SAMPLE, uv - px.xz, 0).x;
    float x1 = _SourceVelocity.SampleLevel(SAMPLE, uv + px.xz, 0).x;
    float y0 = _SourceVelocity.SampleLevel(SAMPLE, uv - px.zy, 0).y;
    float y1 = _SourceVelocity.SampleLevel(SAMPLE, uv + px.zy, 0).y;

    float divergence = (x1 - x0 + y1 - y0);

    _ResultDivergence[id] = float4(divergence.xx, 0.0, 1.0);
}

static const float poissonFilter[7] = {
    .57843719174,
    .36519596949,
    .23187988879,
    .14529589353,
    .08816487385,
    .05184872885,
    .02906462467
};

// 圧力 H
[numthreads(8,8,1)]
void UpdatePressurePsfH(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;
    int i;

    float p0 = 0.0;
    [unroll]
    for (i = -6; i <= 6; i++) {
        float x = _SourcePressure.SampleLevel(_LinearClamp, uv + float2(px.x * i, 0), 0).r;
        p0 += poissonFilter[abs(i)] * x;
    }
    _ResultPressure[id] = float4(p0, 0.0, 0.0, 0.0);
}

// 圧力 V
[numthreads(8,8,1)]
void UpdatePressurePsfV(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;
    int i;

    float p1 = 0.0;
    [unroll]
    for (i = -6; i <= 6; i++) {
        float y = _SourcePressure.SampleLevel(_LinearClamp, uv + float2(0, px.y * i), 0).r;
        p1 += poissonFilter[abs(i)] * y;
    }

    float p0 = _ResultPressure[id].x;
    float d = _ResultDivergence[id].r;
    const float scale = 0.5;
    float relaxed = (p1 - d) * 0.25 * scale;

    _ResultPressure[id] = float4(relaxed.xx, 0.0, 1.0);
}

// 速度
[numthreads(8,8,1)]
void UpdateVelocity(uint2 id : SV_DispatchThreadID)
{
    float w = _SimWidth;
    float h = _SimHeight;

    float3 px = float3(1.0 / w, 1.0 / h, 0);
    float2 uv = float2(id.x / w, id.y / h) + px.xy * 0.5;

    float x0 = _SourcePressure.SampleLevel(SAMPLE, uv - px.xz, 0).r;
    float x1 = _SourcePressure.SampleLevel(SAMPLE, uv + px.xz, 0).r;
    float y0 = _SourcePressure.SampleLevel(SAMPLE, uv - px.zy, 0).r;
    float y1 = _SourcePressure.SampleLevel(SAMPLE, uv + px.zy, 0).r;

    float2 v = _SourceVelocity.SampleLevel(SAMPLE, uv, 0).xy;
    float4 v2 = float4((v - (float2(x1, y1) - float2(x0, y0)) * 0.5), 0.0, 1.0);
    v2 *= _Attenuation;

    _ResultVelocity[id] = v2;
}

// 密度
[numthreads(8,8,1)]
void UpdateDensity(uint2 id : SV_DispatchThreadID)
{
    float dw = _DensityWidth;
    float dh = _DensityHeight;
    float2 baseUv = float2(id.x / dw, id.y / dh);
    float3 dpx = float3(1.0 / dw, 1.0 / dh, 0);
    float2 duv = baseUv + dpx.xy * 0.5;

    float vw = _SimWidth;
    float vh = _SimHeight;
    float3 vpx = float3(1.0 / vw, 1.0 / vh, 0);
    const float scale = 0.075;
    float2 vuv = baseUv + vpx.xy * scale;

    float2 vel = _SourceVelocity.SampleLevel(SAMPLE, vuv, 0).xy;

    float4 col = _SourceDensity.SampleLevel(SAMPLE, duv - vel * _DeltaTime, 0);
    col *= _DeisityAttenuation;
    _ResultDensity[id] = col;
}
Shader "Custom/Fire"
{
    Properties
    {
        _GradientTex ("Gradient Texture", 2D) = "white" {}
    }

    CGINCLUDE
    #include "UnityCG.cginc"

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

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

    sampler2D _VectorFieldTex;
    float4 _VectorFieldTex_TexelSize;
    sampler2D _DensityTex;
    float4 _DensityTex_TexelSize;
    sampler2D _GradientTex;
 
    static const float PI = 3.1415927;
    static const int   ARROW_V_STYLE = 1;
    static const int   ARROW_LINE_STYLE = 2;
    static const int   ARROW_STYLE = ARROW_LINE_STYLE;
    static const float ARROW_HEAD_ANGLE = 45.0 * PI / 180.0;
    static const float ARROW_SHAFT_THICKNESS = 3.0;

    static const float ARROW_BASE_SIZE = 1024.0;
    #define ARROW_TILE_SIZE (ARROW_BASE_SIZE / (float)_VectorFieldTex_TexelSize.z)
    #define ARROW_HEAD_LENGTH (ARROW_TILE_SIZE / 6.0)

 
    float2 ArrowTileCenterCoord(float2 pos)
    {
        return (floor(pos / ARROW_TILE_SIZE) + 0.5) * ARROW_TILE_SIZE;
    }
 
    float Arrow(float2 p, float2 v)
    {
        p -= ArrowTileCenterCoord(p);
        float mag_v = length(v), mag_p = length(p);  
        if (mag_v > 0.0)
        {
            float2 dir_p = p / mag_p, dir_v = v / mag_v;
            mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
            v = dir_v * mag_v;
            float dist;
            if (ARROW_STYLE == ARROW_LINE_STYLE)
            {
                dist = max(ARROW_SHAFT_THICKNESS / 4.0 - max(abs(dot(p, float2(dir_v.y, -dir_v.x))),
                    abs(dot(p, dir_v)) - mag_v + ARROW_HEAD_LENGTH / 2.0),
                    min(0.0, dot(v - p, dir_v) - cos(ARROW_HEAD_ANGLE / 2.0) * length(v - p)) * 2.0 +
                    min(0.0, dot(p, dir_v) + ARROW_HEAD_LENGTH - mag_v));
            }
            else
            {
                dist = min(0.0, mag_v - mag_p) * 2.0 +
                        min(0.0, dot(normalize(v - p), dir_v) - cos(ARROW_HEAD_ANGLE / 2.0)) * 2.0 * length(v - p) +
                        min(0.0, dot(p, dir_v) + 1.0) +
                        min(0.0, cos(ARROW_HEAD_ANGLE / 2.0) - dot(normalize(v * 0.33 - p), dir_v)) * mag_v * 0.8;
            }
            return clamp(1.0 + dist, 0.0, 1.0);
        }
        else
        {
            return max(0.0, 1.2 - mag_p);
        }
    }
 
    float2 field(float2 pos)
    {
        return tex2D(_VectorFieldTex, pos / ARROW_BASE_SIZE);
    }

    ENDCG

    SubShader
    {
        Tags
        { 
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
        }

        ZWrite Off
        Lighting Off
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
 
            float4 frag(v2f i) : SV_Target
            {
                float2 fragCoord = i.uv * float2(ARROW_BASE_SIZE,ARROW_BASE_SIZE);

                float4 col;

                float arrow = (1.0 - Arrow(fragCoord.xy, field(ArrowTileCenterCoord(fragCoord.xy)) * ARROW_TILE_SIZE * 0.4));
                float4 densityCol = tex2D(_GradientTex, float2(saturate(tex2D(_DensityTex, i.uv).r), 0.5));
                col.rgb = densityCol.rgb * arrow;
                col.a = max(densityCol.a, 1.0 - arrow);
                return col;
            }
            ENDCG
        }
    }
}

参考

Fast Eulerian Fluid Simulation In Games Using Poisson Filters (SCA 2020 Showcase) - YouTube

Damien Rioux-Lavoie

https://www.shadertoy.com/view/NdjyRh

Unityで流体シミュレーションを実装する 〜 実装編 〜 - e.blog

ShaderToy (GLSL) porting to HLSL - Unity Forum