読者です 読者をやめる 読者になる 読者になる

おもちゃラボ

Unityで遊びを作っていきます

【Unityシェーダ入門】Unityのシェーダでノイズを作る その1

Unity シェーダ

ノイズといえば、砂嵐のようなランダムノイズから連続性を保ったパーリンノイズまで様々なノイズが考えられています。残念なことにUnityのシェーダにはノイズを生成するメソッドが用意されていないため、プログラムを作る必要があります。Unityでランダムノイズ、ブロックノイズ、バリューノイズ、フラクタルノイズ、パーリンノイズを作る方法を見ていきましょう。

f:id:nn_hokuson:20170127195433p:plain

今回の記事では、次の3つのノイズを紹介します。Unityでフラクタルノイズとパーリンノイズについては次回説明しますね!

ランダムノイズ

ランダムノイズとは場所や時間に依存せずに全くのランダムに値を決めるノイズのことです。テレビの砂嵐(古い!)と同じものですね。

www.youtube.com

まずはノイズ生成の基本になるランダムノイズを表示するシェーダを作ってみましょう!シーン中にノイズを表示するための平面モデルをUnityのシーンビューに配置して、そこにシェーダプログラムをアタッチします。

Unityのヒエラルキービューから「Create」→「3D Object」→「Plane」を選択します。続いて、プロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surface Shader」を選択します。作成したシェーダのファイル名は「RandomNoise」にしておきましょう。

f:id:nn_hokuson:20170127192620p:plain

先に、マテリアルを作っておきましょう。プロジェクトビューから「Create」→「Material」を選択しファイル名を「NoiseTest」に変更します。作成したマテリアルをシーンビューのPlaneにドラッグ&ドロップしてアタッチし、インスペクタからシェーダファイルを「Custom/RandomNoise」に変更します。まだRandomNoiseのシェーダプログラムを書いていないので、マテリアルは真っ白のままです。

f:id:nn_hokuson:20170127192909p:plain

ではランダムノイズのシェーダプログラムを作っていきましょう。RandomNoise.shaderを開いて次のプログラムを入力してください。

Shader "Custom/RandomNoise" {
	Properties {
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Standard fullforwardshadows
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		float random (fixed2 p) { 
		    return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453);
		}

		void surf (Input IN, inout SurfaceOutputStandard o) {
			float c = random(IN.uv_MainTex);
			o.Albedo = fixed4(c,c,c,1);
		}
		ENDCG
	}
	FallBack "Diffuse"
}


ここでは、ランダムノイズを表示するためにrandomメソッドを使っています。randomメソッドは引数にuv座標をとり、その座標からランダムな値を生成するメソッドです。このメソッドの値がどのように決められたかは下の記事に書かれているようです。

qiita.com

randomメソッドからの戻り値を明度として出力しています。Unityで実行すると、実行結果は次のようになります。まさにランダムな砂嵐ですね。

f:id:nn_hokuson:20170127194012p:plain:w450

ブロックノイズ

次にブロックノイズを見ていきます。ブロックノイズはランダムノイズに比べると粒度が荒いノイズになります。ディジタルテレビの調子が悪いときや、動画を圧縮しすぎたときに出てくるやつです。


www.youtube.com


先程と同様に、Unityでブロックノイズを表示するためのシェーダプログラムを作り、シェーダのファイル名は「BlockNoise」にします。また、シーンビューで平面モデルを選択し、インスペクタからシェーダを「Custom/BlockNoise」 に変更します。

f:id:nn_hokuson:20170127194101p:plain

では、ブロックノイズを生成するシェーダプログラムを作りましょう。次のプログラムを入力してください。

Shader "Custom/BlockNoise" {
	Properties {
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Standard fullforwardshadows
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		float random (fixed2 p) 
		{ 
		    return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453);
		}

		float noise(fixed2 st)
		{
			fixed2 p = floor(st);
			return random(p);
		}

		void surf (Input IN, inout SurfaceOutputStandard o) 
		{
			float c = noise( IN.uv_MainTex*8 );			
			o.Albedo = fixed4(c,c,c,1);
		}
		ENDCG
	}
	FallBack "Diffuse"
}


実行すると次のように、正方形のブロックがランダム色で配置されます。

f:id:nn_hokuson:20170127194116p:plain:w450

ブロックノイズのシェーダプログラムは、先ほど作ったランダムノイズを拡張して作っています。やっていることは非常に簡単で、ランダムノイズからブロックごとに代表的な1点を取り出してベタ塗りしているだけです。

f:id:nn_hokuson:20170127194125p:plain:w450

代表的な一点はfloorメソッドを使って決めています。floorメソッドは小数値の整数部分を返すメソッドです。例えば、4.3なら4、5.1なら5を返すので、これをuv座標に使うと(0 <= x < 1 かつ 0 <= y < 1) のエリアの代表点は(0, 0)に、(3 <= x < 4 かつ 1 <= y < 2)のエリアの代表点は(3, 1)になります。

f:id:nn_hokuson:20170127194624p:plain:w250

ここではテクスチャ座標を8倍してからnoiseメソッドに渡しているので、8x8のブロックノイズを生成できます。

バリューノイズ

ブロックノイズではブロック単位できっちりと色が分かれてしまったため、ノイズっぽくありませんでした。そこでブロックノイズを平滑化したバリューノイズというものが考案されます。

Unityでバリューノイズ用のシェーダファイルを作りましょう。作成したシェーダのファイル名は「ValueNoise」にします。また、シーンビューで平面モデルを選択し、インスペクタからシェーダを「Custom/ValueNoise」 に変更します。

f:id:nn_hokuson:20170127194329p:plain

次のシェーダプログラムを入力して下さい。

Shader "Custom/ValueNoise" {
	Properties {
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Standard fullforwardshadows
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		float random (fixed2 p) { 
		    return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453);
		}

		float noise(fixed2 st)
		{
			fixed2 p = floor(st);
			return random(p);
		}

		float valueNoise(fixed2 st)
		{
			fixed2 p = floor(st);
			fixed2 f = frac(st);

			float v00 = random(p+fixed2(0,0));
		    float v10 = random(p+fixed2(1,0));
		    float v01 = random(p+fixed2(0,1));
		    float v11 = random(p+fixed2(1,1));
		    
		    fixed2 u = f * f * (3.0 - 2.0 * f);		    

		    float v0010 = lerp(v00, v10, u.x);
		    float v0111 = lerp(v01, v11, u.x);
		    return lerp(v0010, v0111, u.y);
		}

		void surf (Input IN, inout SurfaceOutputStandard o) {
			float c = valueNoise( IN.uv_MainTex*8);			
			o.Albedo = fixed4(c,c,c,1);
		}
		ENDCG
	}
	FallBack "Diffuse"
}


バリューノイズでは、ブロックノイズで使った四隅の色からブロック内の色を補間して決めます。四隅の色はv00、v10、v01、v11で求めています。

f:id:nn_hokuson:20170127194347p:plain:w400

値を4隅の値から補間するためには、補間したい座標がブロック内の左下からどれくらい離れているかを知る必要があります。ここではそのオフセット値をfracメソッドで求めています。fracメソッドは少数値の少数部分を返すメソッドです。たとえば、(x, y)=(2.7, 1.2)であれば、fracの値は(0.7, 0.2)になります。

f:id:nn_hokuson:20170127194804p:plain:w250

このオフセット値を -2f^3 + 3f^2という数式を使って補間します。この数式をグラフ化すると次のようになり、0〜1の間の値を緩やかに補間できることがわかります(この行をu=f;にすると線形補間になります)

f:id:nn_hokuson:20170127194405p:plain:w450

ここではまずブロックの下側の色(v0010)を決めて、次にブロックの上側の色(v0111)を決めています。最後にその間の2点を補間して目的の座標の色を決めています。

f:id:nn_hokuson:20170127194411p:plain

実行結果は次のようになります。先ほどのブロックノイズと比べると滑らかなノイズになっているのが分かりますね。

f:id:nn_hokuson:20170127194418p:plain:w500

長くなってきました・・・・ノイズは奥が深いですね。Unityのシェーダでノイズを発生させるメソッドが用意されていないのが残念ですね〜

次回はバリューノイズを応用したfbm(Fractional Brownian Noise)とパーリンノイズの2種類をUnityを使って説明していきます〜

参考書

DirectX 9 シェーダプログラミングブック

DirectX 9 シェーダプログラミングブック

OpenGL 4.0 シェーディング言語 -実例で覚えるGLSLプログラミング-

OpenGL 4.0 シェーディング言語 -実例で覚えるGLSLプログラミング-