ノイズといえば、砂嵐のようなランダムノイズから連続性を保ったパーリンノイズまで様々なノイズが考えられています。残念なことにUnityのシェーダにはノイズを生成するメソッドが用意されていないため、プログラムを作る必要があります。
この記事では、Unityでランダムノイズ、ブロックノイズ、バリューノイズ、パーリンノイズ、fBmを作る方法を見ていきましょう。
ランダムノイズ
ランダムノイズとは場所や時間に依存せずに全くのランダムに値を決めるノイズのことです。テレビの砂嵐(古い!)と同じものですね。
まずはノイズ生成の基本になるランダムノイズを表示するシェーダを作ってみましょう!シーン中にノイズを表示するための平面モデルをUnityのシーンビューに配置して、そこにシェーダプログラムをアタッチします。
Unityのヒエラルキービューから「Create」→「3D Object」→「Plane」を選択します。続いて、プロジェクトビューで右クリックし、「Create」→「Shader」→「Standard Surface Shader」を選択します。作成したシェーダのファイル名は「RandomNoise」にしておきましょう。
先に、マテリアルを作っておきましょう。プロジェクトビューから「Create」→「Material」を選択しファイル名を「NoiseTest」に変更します。作成したマテリアルをシーンビューのPlaneにドラッグ&ドロップしてアタッチし、インスペクタからシェーダファイルを「Custom/RandomNoise」に変更します。まだRandomNoiseのシェーダプログラムを書いていないので、マテリアルは真っ白のままです。
ではランダムノイズのシェーダプログラムを作っていきましょう。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座標をとり、その座標からランダムな値を生成するメソッドです。このメソッドの値がどのように決められたかは下の記事に書かれているようです。
randomメソッドからの戻り値を明度として出力しています。Unityで実行すると、実行結果は次のようになります。まさにランダムな砂嵐ですね。
ブロックノイズ
次にブロックノイズを見ていきます。ブロックノイズはランダムノイズに比べると粒度が荒いノイズになります。ディジタルテレビの調子が悪いときや、動画を圧縮しすぎたときに出てくるやつです。
先程と同様に、Unityでブロックノイズを表示するためのシェーダプログラムを作り、シェーダのファイル名は「BlockNoise」にします。また、シーンビューで平面モデルを選択し、インスペクタからシェーダを「Custom/BlockNoise」 に変更します。では、ブロックノイズを生成するシェーダプログラムを作りましょう。次のプログラムを入力してください。
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" }
実行すると次のように、正方形のブロックがランダム色で配置されます。
ブロックノイズのシェーダプログラムは、先ほど作ったランダムノイズを拡張して作っています。やっていることは非常に簡単で、ランダムノイズからブロックごとに代表的な1点を取り出してベタ塗りしているだけです。
代表的な一点は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)になります。
ここではテクスチャ座標を8倍してからnoiseメソッドに渡しているので、8x8のブロックノイズを生成できます。
バリューノイズ
ブロックノイズではブロック単位できっちりと色が分かれてしまったため、ノイズっぽくありませんでした。そこでブロックノイズを平滑化したバリューノイズというものが考案されます。
Unityでバリューノイズ用のシェーダファイルを作りましょう。作成したシェーダのファイル名は「ValueNoise」にします。また、シーンビューで平面モデルを選択し、インスペクタからシェーダを「Custom/ValueNoise」 に変更します。次のシェーダプログラムを入力して下さい。
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で求めています。
値を4隅の値から補間するためには、補間したい座標がブロック内の左下からどれくらい離れているかを知る必要があります。ここではそのオフセット値をfracメソッドで求めています。fracメソッドは少数値の少数部分を返すメソッドです。たとえば、(x, y)=(2.7, 1.2)であれば、fracの値は(0.7, 0.2)になります。
このオフセット値を -2f^3 + 3f^2という数式を使って補間します。この数式をグラフ化すると次のようになり、0〜1の間の値を緩やかに補間できることがわかります(この行をu=f;にすると線形補間になります)
ここではまずブロックの下側の色(v0010)を決めて、次にブロックの上側の色(v0111)を決めています。最後にその間の2点を補間して目的の座標の色を決めています。
実行結果は次のようになります。先ほどのブロックノイズと比べると滑らかなノイズになっているのが分かりますね。
パーリンノイズ
バリューノイズは境界線をぼかしたことでかなり滑らかになりましたが、まだブロック感が残っていますね。パーリンノイズはバリューノイズをさらに滑らかにしたものになります。このパーリンノイズは、炎や雲などにみられる自然な変化のノイズを表現するのによく使われます。
立体的に見えるけれども、スプライトにフラグメントシェーダで絵を描いてるだけだから、横から見るとこんなこんな感じ。 pic.twitter.com/1WPrlQyK4A
— 北村愛実 (@tasonco_company) 2016年9月24日
こちらもUnityでパーリンノイズ用のシェーダファイルを作りましょう。作成したシェーダのファイル名は「PerlinNoise」にします。また、シーンビューで平面モデルのシェーダを「Custom/PerlinNoise」 に変更します。PerlinNoise.shaderには次のシェーダプログラムを入力して下さい。
Shader "Custom/PerlinNoise" { 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; }; fixed2 random2(fixed2 st){ st = fixed2( dot(st,fixed2(127.1,311.7)), dot(st,fixed2(269.5,183.3)) ); return -1.0 + 2.0*frac(sin(st)*43758.5453123); } float perlinNoise(fixed2 st) { fixed2 p = floor(st); fixed2 f = frac(st); fixed2 u = f*f*(3.0-2.0*f); float v00 = random2(p+fixed2(0,0)); float v10 = random2(p+fixed2(1,0)); float v01 = random2(p+fixed2(0,1)); float v11 = random2(p+fixed2(1,1)); return lerp( lerp( dot( v00, f - fixed2(0,0) ), dot( v10, f - fixed2(1,0) ), u.x ), lerp( dot( v01, f - fixed2(0,1) ), dot( v11, f - fixed2(1,1) ), u.x ), u.y)+0.5f; } void surf (Input IN, inout SurfaceOutputStandard o) { float c = perlinNoise(IN.uv_MainTex*8);//(perlinNoise( IN.uv_MainTex*6))*0.5+0.5; o.Albedo = fixed4(c,c,c,1); o.Metallic = 0; o.Smoothness = 0; o.Alpha = 1; } ENDCG } FallBack "Diffuse" }
パーリンノイズの実行結果は次のようになります。バリューノイズに比べると滑らかに変化しているのがわかります。
バリューノイズが格子状に区切った四隅の色を補完してブロック内部の色を決めていたのにに対して、パーリンノイズではブロック内部の点の座標により、四隅の色もなだらかに変化します。
この「四隅の色をなだらかに変化」させるため、格子点に設定されたランダムなベクトルと、ブロック内部の点から格子点に向かうベクトルの内積を取って、それを色情報として扱います。これにより、ブロック内部の座標を変化させることで、四隅の色もなだらかにグラデーション状に変化します。
パーリンノイズでは「内部の点から格子点へのベクトル」の内積を取っているため、格子点上では必ず黒色になっていることに注意して下さい。(ここでは分かりやすいように、-1.0〜1.0の範囲でスケーリングして、マイナスの値を赤、プラスの値を青色で表示しています)
fBmノイズ
パーリンノイズをさらに綺麗に見せるためには、さまざまな解像度のノイズをずらしながら重畳させます。これをfBm (Fractional Brownian Motion)とよび、地形や雲など幅広い表現に使われています。
UnityでfBm用のシェーダファイルを作りましょう。作成したシェーダのファイル名は「fBm.shader」にします。また、シーンビューで平面モデルのシェーダを「Custom/fBm」 に変更します。fBm.shaderには次のシェーダプログラムを入力して下さい。
Shader "Custom/fBm" { 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; }; fixed2 random2(fixed2 st){ st = fixed2( dot(st,fixed2(127.1,311.7)), dot(st,fixed2(269.5,183.3)) ); return -1.0 + 2.0*frac(sin(st)*43758.5453123); } float perlinNoise(fixed2 st) { fixed2 p = floor(st); fixed2 f = frac(st); fixed2 u = f*f*(3.0-2.0*f); float v00 = random2(p+fixed2(0,0)); float v10 = random2(p+fixed2(1,0)); float v01 = random2(p+fixed2(0,1)); float v11 = random2(p+fixed2(1,1)); return lerp( lerp( dot( v00, f - fixed2(0,0) ), dot( v10, f - fixed2(1,0) ), u.x ), lerp( dot( v01, f - fixed2(0,1) ), dot( v11, f - fixed2(1,1) ), u.x ), u.y)+0.5f; } float fBm (fixed2 st) { float f = 0; fixed2 q = st; f += 0.5000*perlinNoise( q ); q = q*2.01; f += 0.2500*perlinNoise( q ); q = q*2.02; f += 0.1250*perlinNoise( q ); q = q*2.03; f += 0.0625*perlinNoise( q ); q = q*2.01; return f; } void surf (Input IN, inout SurfaceOutputStandard o) { float c = fBm(IN.uv_MainTex*6); o.Albedo = fixed4(c,c,c,1); o.Metallic = 0; o.Smoothness = 0; o.Alpha = 1; } ENDCG } FallBack "Diffuse" }
fBmには新しい概念は登場しません。パーリンノイズの解像度を変えたテクスチャを、一定の割合で足し合わせていきます。このとき、いくつの階層のノイズを合成するかを表す値を「オクターブ」、それぞれのノイズをどのような割合で合成するかを「パーシステンス」と呼びます。
上のプログラムでは、4つの解像度のノイズのテクスチャを次のような割合で合成しています。
ノイズの解像度が高まるごとに、パーシステンスをべき乗した値を合成する割合として使用しています。最も解像度の低いテクスチャを0.5、次に解像度が低いテクスチャを0.25、次のテクスチャを0.125、最も解像度の高いテクスチャを0.0625の割合で合成しています。
fBmを使用したノイズ画像がこちらになります。これまでの中で最も綺麗なノイズになっていますね。
まとめ&参考書
ここでは、Unityのシェーダで作れるノイズを5種類(ランダムノイズ・ブロックノイズ・バリューノイズ・パーリンノイズ・fBm)紹介しました。