Unityを利用してドメインワーピングを実装する

概要

ドメインワーピングと呼ばれる手法を用いた表現の作り方を解説いたします。
 
ドメインワーピング表現

ドメインワーピング表現

サンプル

GitHubサンプルプロジェクト

https://github.com/styly-dev/STYLY-Unity-Examples/tree/master/Assets/STYLY_Examples/DomainWarpingShader_Sample  

STYLYサンプル空間 ドメインワーピング表現を適用した板を置いた空間をサンプルとして公開しています。 

 

ドメインワーピングとは

ドメインワーピングとは、雲や水のような表現を fbm(フラクタルブラウン運動)と呼ばれるノイズを利用した計算によって表現しようというものです。

ドメインワーピング

ドメインワーピング

 

ドメインワーピング

ドメインワーピング

 

ドメインワーピングの解説

ここでは、ドメインワーピングの模様がどのようにして作られているのかを解説いたします。  

ピクセル座標pを使用してfbm(p)を生成

ピクセルの座標pを利用して、fbmを生成すると雲のような見た目になります。

fbm(p)

fbm(p)

  シェーダーコードで書いた場合は以下になります。

float2 p = i.uv * 8;
return fbm(p + 1.7);

 

補足

pのx座標(p.x)とpのy座標(p.y)を可視化すると以下のようになります。

p.xとp.yの表示

p.xとp.yの表示

 

fbm(p)にさらにfbmを適用し、fbm(fbm(p))を生成

fbm(p)をさらにfbmで歪ませると、以下のような模様ができます。 座標pに対してfbmを繰り返し適用し、複雑な模様を生み出すことをドメインワーピングと呼びます。

fbm(fbm(p))

fbm(fbm(p))

  シェーダーコードで書いた場合は以下になります。

float2 p = i.uv * 8;
p = float2(fbm(p + 1.7), fbm( p + 9.2));
p = p * 8.0;
return fbm(p + 8.3);

 

ドメインワーピングに動きを与える

ドメインワーピングの引数に時間を足すと動きが生まれます。

float2 p = i.uv * 4;
p = float2(fbm(p + _Time.y), fbm(p));
p = p * 8.0;
return fbm(p);
時間を足す

時間を足す

    今回のドメインワーピング表現はこのようにして動きを与えています。    

今回の表現の解説

ドメインワーピングで模様を作る

ドメインワーピングを利用して、以下のような模様を作ります。

ドメインワーピング

モザイクエフェクトをかける

ドメインワーピングで作った模様にモザイクエフェクトをかけます。

モザイクエフェクト

モザイクエフェクト

水玉でくりぬく

水玉模様でくりぬきます。

水玉でくりぬく

水玉でくりぬく


色が明るいところほど円が大きく、色が暗いところは円を小さくさせることによってコントラストをつけ、 イイ感じにしています。  

時間で動かして完成

fbmのパラメータに時間を足して動きを与え、今回の表現の完成です。

動きを与える

動きを与える

 

Unityでの作り方

ここでは、Unityのシェーダーを利用して今回のドメインワーピング表現を作る方法を解説します。 本記事でのマウス操作はWindowsを想定しています。  

シェーダーを作成

まずはProjectウィンドウで Create/Shader/Unlit Shaderを選択し、シェーダーファイルを作成します。

シェーダーファイル作成

シェーダーファイル作成

  シェーダーファイル名はDomainWarpingShaderとします。  

シェーダーの編集

作成したシェーダーファイルを開き、以下のコードをペーストします。

Shader "Unlit/DomainWarpingShader"
{
    Properties
	{
		_GradientTex("Gradient Texture", 2D) = "white" {}
		_GridNumber("Grid Number", Float) = 64.0
		_EllipseSize("Ellipse Size", Float) = 1.0
		_Speed("Speed", Float) = 1.0
		_Fbm_ScaleFactor("Fbm Scale Factor", Vector) = (1.0, 1.0, 4.0, 4.0)
	}
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            
            //////////////////////////////////////////////////////////////////////////////
            float random(float2 st) {
                return frac(sin(dot(st.xy,
                                    float2(12.9898,78.233)))*
                            43758.5453123);
            }

            //////////////////////////////////////////////////////////////////////////////
            // Based on Morgan McGuire @morgan3d
            // https://www.shadertoy.com/view/4dS3Wd
            float noise (float2 st) {
                float2 i = floor(st);
                float2 f = frac(st);

                // Four corners in 2D of a tile
                float a = random(i);
                float b = random(i + float2(1.0, 0.0));
                float c = random(i + float2(0.0, 1.0));
                float d = random(i + float2(1.0, 1.0));

                float2 u = f * f * (3.0 - 2.0 * f);

                return lerp(a, b, u.x) +
                        (c - a)* u.y * (1.0 - u.x) +
                        (d - b) * u.x * u.y;
            }
            
            //////////////////////////////////////////////////////////////////////////////
            #define OCTAVES 6
            // based on : https://thebookofshaders.com/13/?lan=jp
            float fbm (float2 st) {
                // Initial values
                float value = 0.0;
                float amplitude = .5;
                float frequency = 0.;
                // Loop of octaves
                for (int i = 0; i < OCTAVES; i++) {
                    value += amplitude * noise(st);
                    st *= 2.;
                    amplitude *= .5;
                }
                return value;
            }

            //////////////////////////////////////////////////////////////////////////////
            // domain warping pattern
            // based on : http://www.iquilezles.org/www/articles/warp/warp.htm
            float pattern (float2 p, float4 scale_1, float scale_2, float4 add_1, float4 add_2) {
                // first domain warping
                float2 q = float2( 
                                fbm( p + scale_1.x * add_1.xy ),
                                fbm( p + scale_1.y * add_1.zw ) 
                                );
                            
                // second domain warping
                float2 r = float2( 
                                fbm( p + scale_1.z * q + add_2.xy ),
                                fbm( p + scale_1.w * q + add_2.zw ) 
                                );

                return fbm( p + scale_2 * r );
            }
            
            //////////////////////////////////////////////////////////////////////////////
            sampler2D _GradientTex;
            float4 _GradientTex_ST;
            float _EllipseSize;
            float _GridNumber;

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

            float2 remap(float In, float2 InMinMax, float2 OutMinMax)
            {
                return OutMinMax.x + (In - InMinMax.x) * (OutMinMax.y - OutMinMax.x) / (InMinMax.y - InMinMax.x);
            }

            float ellipse(float2 UV, float Size)
            {
                float d = length(2 * UV - 1);
                return step(d, Size);
            }

            fixed2 posterize(fixed2 In, fixed Steps)
            {
                return floor(In * Steps) / Steps;     
            }

			//////////////////////////////////////////////////////////////////////////////
			float _Speed;
			float4 _Fbm_ScaleFactor;
			float _EllipseContrast;

            fixed4 frag (v2f i) : SV_Target
            {
                #define TIME_1 (_Time.y * (-0.1) * _Speed)
                #define TIME_2 (_Time.y * (-0.3) * _Speed)
                #define TIME_3 (_Time.y * (0.15) * _Speed)
                #define SIN_TIME_3 (4.0 * sin(TIME_3))

				//#define ScaleFactor_1 float4(1.0, 1.0, 4.0, 4.0)
				#define ScaleFactor_1 _Fbm_ScaleFactor
                #define ScaleFactor_2 4.0
                #define AddFactor_1 float4(TIME_1, TIME_2, 5.2, 1.3)
                #define AddFactor_2 float4(SIN_TIME_3, 9.2, 9.3, 2.8)

                #define GRID_N _GridNumber
                #define UV_Repeat frac(i.uv * GRID_N)
                #define UV_Posterized posterize(i.uv, GRID_N)

                // get domain warping value
                float domainWarping = pattern(UV_Posterized, ScaleFactor_1, ScaleFactor_2, AddFactor_1, AddFactor_2);

                // remap value
				domainWarping = remap(domainWarping, float2(0.39, 0.83), float2(0, 1));

				return ellipse(UV_Repeat, domainWarping * _EllipseSize) * tex2D(_GradientTex, domainWarping);
            }
            ENDCG
        }
    }
}

 

マテリアルを作成

シェーダーファイルを右クリックし、Create/Materialを選択します。

マテリアル作成

マテリアル作成


作成したマテリアル

作成したマテリアル

板オブジェクトの作成

作成したマテリアルを貼り付けるための板オブジェクトを作成する手順を解説します。

Planeオブジェクトの作成

Planeオブジェクトの作成

 

作成したPlaneオブジェクト

作成したPlaneオブジェクト

 

板にマテリアルを登録する

作成したPlaneオブジェクトにマテリアルをドラッグ&ドロップして、マテリアルを登録します。

マテリアルの登録

マテリアルの登録

  板にドメインワーピング表現が適用され、以下のような見た目になります。

マテリアルを登録した結果

マテリアルを登録した結果

 

色を付ける

次にこのドメインワーピング表現に色を付けます。 今回のシェーダーでは、グラデーションテクスチャを設定することで、色がつくようになっています。

グラデーションテクスチャ

グラデーションテクスチャ

 

グラデーション用テクスチャをマテリアルへ登録

グラデーション用テクスチャをマテリアルへ登録

  テクスチャを適用すると、色が付いて以下のような見た目になります。

テクスチャ設定結果

テクスチャ設定結果

 

色がつく仕組みについて

ドメインワーピングシェーダーで円が大きい部分ほど左の色を、円が小さい部分ほど右の色が適用されます。

グラデーションテクスチャを利用して色を付ける

グラデーションテクスチャを利用して色を付ける

 

UnityからSTYLYにアセットをアップロードする方法

UnityからSTYLYへアセットをアップロードする方法は下記URLをご覧ください https://styly.cc/ja/manual/unity-asset-uploader/