したかみ ぶろぐ

Unity成分多め

CustomRenderTextureで実装するGray-Scottモデル

f:id:vxd-naoshi-19961205-maro:20200814004309j:plain

始めに

つい最近、部活のブログでComputeShaderを使用したGray-Scottモデルについて紹介しました。

aizu-vr.hatenablog.com



このブログを書きながら、「これってCustomRenderTextureでも実装できるんじゃね?」と思ったのでやってみようと思います。


今回のプロジェクトはこちらになります。

github.com

CustomRenderTextureについて

カスタムレンダーテクスチャはレンダーテクスチャの拡張機能で、これを使うと簡単にシェーダー付きのテクスチャを作成できます。これは、コースティクス、雨の効果に使われるリップルシミュレーション、壁面へぶちまけられた液体など、あらゆる種類の複雑なシミュレーションを実装するのに便利です。また、カスタムレンダーテクスチャはスクリプトやシェーダーのフレームワークを提供し、部分的更新、または、マルチパスの更新、更新頻度の変更などのさらに複雑な設定をサポートします。

Unity公式から引用しました。( カスタムレンダーテクスチャ - Unity マニュアル )


私なりに要約すると、「機能を盛り込んだシェーダーをテクスチャに付加して動きを付けることが出来る」ということだと思います。



実装

では早速スクリプトを紹介したいと思います。

UpdateGrayScott.shader

Shader "Unlit/UpdateGrayScott"
{
    Properties
    {
        _F("F", Float) = 0.04
        _K("K", Float) = 0.06

        _SimulateSpeed("SimulateSpeed", Float) = 1
        
        _GridSize("Delta UV", Float) = 1

        _Du("Du", Float) = 0.2
        _Dv("Dv", Float) = 0.1

    }
    SubShader
    {
        Cull Off
        ZWrite Off
        ZTest Always

        Pass
        {
            Name "Update"
            CGPROGRAM
            #pragma vertex CustomRenderTextureVertexShader
            #pragma fragment frag

            #include "UnityCustomRenderTexture.cginc"

            float _F;
            float _K;
            float _SimulateSpeed;

            float _GridSize;
            
            float _Du;
            float _Dv;

            fixed4 frag (v2f_customrendertexture i) : SV_Target
            {
                float2 uv = i.globalTexcoord;

                // 1pxあたりの単位を計算
                float du = 1.0 / _CustomRenderTextureWidth;
                float dv = 1.0 / _CustomRenderTextureHeight;
                float3 duv = float3(du, dv, 0) * _GridSize;

                // 現在のテクスチャの取得
                float2 c = tex2D(_SelfTexture2D, uv);
                float u = c.x;
                float v = c.y;

                // ラプラスの演算を行う
                float2 laplacian = 
                    tex2D(_SelfTexture2D, uv - duv.zy).xy +
                    tex2D(_SelfTexture2D, uv + duv.zy).xy +
                    tex2D(_SelfTexture2D, uv - duv.xz).xy +
                    tex2D(_SelfTexture2D, uv + duv.xz) - 4.0*float2(u, v);

                float uvv = u*v*v;

                // Gray-Scottモデルの反応拡散方程式
                float dudt = _Du * laplacian.x - uvv + _F * (1.0 - u);
                float dvdt = _Dv * laplacian.y + uvv - (_F + _K) * v;

                return float4(saturate(u + dudt * _SimulateSpeed), saturate(v + dvdt * _SimulateSpeed), 0, 0);
            }
            ENDCG
        }
    }
}


InitializeGrayScott

Shader "Unlit/InitializeGrayScott"
{
    Properties
    {
        _MainTex ("NoiseTexture", 2D) = "white" {}
        _QuadWidth("QuadWidth", Range(0, 0.5)) = 0.2
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Pass
        {
            Name "Initialize"
            CGPROGRAM

            #pragma vertex InitCustomRenderTextureVertexShader
            #pragma fragment frag
            
            #include "UnityCustomRenderTexture.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _QuadWidth;

            fixed4 frag (v2f_init_customrendertexture i) : SV_Target
            {
                float2 uv = i.texcoord;

                // ノイズテクスチャを取得
                fixed4 col = tex2D(_MainTex, uv) * 0.1;

                // テクスチャの中央の閾値を求める
                fixed minThreadhold = 0.5 - _QuadWidth;
                fixed maxThreadhold = 0.5 + _QuadWidth;

                if(minThreadhold < uv.x && uv.x < maxThreadhold &&
                    minThreadhold < uv.y && uv.y < maxThreadhold)
                    col.xy += float2(0.5, 0.25);
                else
                    col.xy += float2(1, 0);

                return col;
            }
            ENDCG
        }
    }
}


RenderTextureUpdater

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RenderTextureUpdater : MonoBehaviour
{
    [SerializeField]
    private CustomRenderTexture m_texture;

    [SerializeField]
    private int m_updateStep = 1;

    void Start()
    {
        m_texture.Initialize();
    }

    void Update()
    {
        m_texture.Update(m_updateStep);
    }
}



解説

主にCustomRenderTextureについて解説します。

また、ここではGray-Scottについては解説しません。もし興味がある方は冒頭でも紹介した記事を参照してください。

aizu-vr.hatenablog.com



CustomRenderTextureの作成

まずはCustomRenderTextureを作成します。

f:id:vxd-naoshi-19961205-maro:20200813235308p:plain


作成したCustomRenderTextureの設定は次のようになります。 Color Formatを"R16G16_UNORM"、DoubleBufferedにチェックを入れてください

f:id:vxd-naoshi-19961205-maro:20200814000609j:plain



Gray-Scottのシミュレーションでは2つの成分しか使用しないので2チャンネルのカラーフォーマットを使用します。

DoubleBufferedをオンにすることで、値を更新する際にテクスチャにアクセスすることが可能になります。



初期化処理

こちらも冒頭記事の内容と同様に以下の処理を行っております。

  • テクスチャの中央に指定された幅で(u, v) = (0.5, 0.25)、それ以外は(u, v) = (1, 0)で初期化
  • ランダム性を持たせるために、ノイズテクスチャを加算
            // InitializeGrayScott.shaderの25行目から
            fixed4 frag (v2f_init_customrendertexture i) : SV_Target
            {
                float2 uv = i.texcoord;

                // ノイズテクスチャを取得
                fixed4 col = tex2D(_MainTex, uv) * 0.1;

                // テクスチャの中央の閾値を求める
                fixed minThreadhold = 0.5 - _QuadWidth;
                fixed maxThreadhold = 0.5 + _QuadWidth;

                if(minThreadhold < uv.x && uv.x < maxThreadhold &&
                    minThreadhold < uv.y && uv.y < maxThreadhold)
                    col.xy += float2(0.5, 0.25);
                else
                    col.xy += float2(1, 0);

                return col;
            }


結果は以下のようになります。

f:id:vxd-naoshi-19961205-maro:20200814001604p:plain



更新処理

こちらはそのまま数式を実装した形になります。ほぼほぼComputeShaderの実装と同じです。

            // UpdateGrayScott.shaderの40行目から
            fixed4 frag (v2f_customrendertexture i) : SV_Target
            {
                float2 uv = i.globalTexcoord;

                // 1pxあたりの単位を計算
                float du = 1.0 / _CustomRenderTextureWidth;
                float dv = 1.0 / _CustomRenderTextureHeight;
                float3 duv = float3(du, dv, 0) * _GridSize;

                // 現在のテクスチャの取得
                float2 c = tex2D(_SelfTexture2D, uv);
                float u = c.x;
                float v = c.y;

                // ラプラスの演算を行う
                float2 laplacian = 
                    tex2D(_SelfTexture2D, uv - duv.zy).xy +
                    tex2D(_SelfTexture2D, uv + duv.zy).xy +
                    tex2D(_SelfTexture2D, uv - duv.xz).xy +
                    tex2D(_SelfTexture2D, uv + duv.xz) - 4.0*float2(u, v);

                float uvv = u*v*v;

                // Gray-Scottモデルの反応拡散方程式
                float dudt = _Du * laplacian.x - uvv + _F * (1.0 - u);
                float dvdt = _Dv * laplacian.y + uvv - (_F + _K) * v;

                // return float4(saturate(u + dudt * _SimulateSpeed), saturate(v + dvdt * _SimulateSpeed), 0, 0);
                return float4(c, 0, 0);
            }


比較用としてComputeShaderでの実装も載せておきます。

GrayScottCalculator.compute

// GrayScottCalculator.computeの38行目から
void UpdateGrayScotte(uint3 id : SV_DispatchThreadID) {

    float u = Texture[id.xy].x;
    float v = Texture[id.xy].y;

    float2 laplacian =
        Texture[id.xy + uint2(-1, 0)] +
        Texture[id.xy + uint2(1, 0)] +
        Texture[id.xy + uint2(0, -1)] +
        Texture[id.xy + uint2(0, 1)] - 4 * Texture[id.xy];

    laplacian /= (dx*dx);
    
    float dudt = Du * laplacian.x - u * v * v + f * (1.0 - u);
    float dvdt = Dv * laplacian.y + u * v * v - (f + k) * v;
    
    Texture[id.xy] = float2(u + dt * dudt, v + dt * dvdt);
}



C#プログラムから初期化、更新処理を呼び出す

CustomRenderTextureはC#からパス処理を呼び出すことが出来ます。 また、更新処理は指定された回数分行われます。

    // RenderTextureUpdater.csの13行目から
    void Start()
    {
        m_texture.Initialize();
    }

    void Update()
    {
        m_texture.Update(m_updateStep);
    }



マテリアルをCustomRenderTextureに設定する

2つシェーダーからマテリアルを作成して、CustomRenderTextureに設定してください。また、この時Initialize ModeとUpdate ModeはOnLoadに設定しましょう。

f:id:vxd-naoshi-19961205-maro:20200814002833j:plain

結果

ComputeShaderの時と似た形を生成することが出来ました。

f:id:vxd-naoshi-19961205-maro:20200814003519g:plain


あとは同じようにパラメータを変更したり、マテリアルを変更することで以下のような模様も作成ることが出来ます。

f:id:vxd-naoshi-19961205-maro:20200814003808p:plain



感想

前に波動方程式の実装を調べた際にComputeShaderとCustomRenderTextureの2種類の実装を見つけました。今回はそれと同じようにGrayScottを2種類の実装を提示できました。

正直どっちがいいのかと言われればケースバイケースだと思うので好きな方で実装するのがいいと思います。


次は、Gray-Scottで簡単に遊んだ記事でも書こうかと思います。


追記

遊んでみた記事書きました。

shitakami.hatenablog.com



参考

light11.hatenadiary.com

tips.hecomi.com

edom18.hateblo.jp