炎のシミュレーション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

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