Unityシェーダー超入門⑧ ブロックノイズ
今回はブロックノイズを作ります
こんな感じでアニメーションもさせます。
コードは以下の通りです。
Shader "Unlit/08_BlockNoise"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float random(float2 seeds)
{
return frac(sin(dot(seeds, float2(12.9898, 78.233))) * 43758.5453);
}
float blockNoise(float2 seeds)
{
return random(floor(seeds));
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float value = random(blockNoise(i.uv * 20) + _Time.x * 0.00001);
return value;
}
ENDCG
}
}
}
今回はfragシェーダー外に関数を置いてあるので注意してください。
fragを含む3つの関数で完結しています。
float random(float2 seeds)
{
return frac(sin(dot(seeds, float2(12.9898, 78.233))) * 43758.5453);
}
float blockNoise(float2 seeds)
{
return random(floor(seeds));
}
fixed4 frag (v2f i) : SV_Target
{
float value = random(blockNoise(i.uv * 20) + _Time.x * 0.00001);
return value;
疑似乱数
乱数とはある範囲に決められた値の一つの数です。サイコロをイメージすると分かりやすいですね。
ただ、プログラミングには完全なる乱数は再現が出来ないようです。
プログラミングにおいての乱数は擬似乱数と呼ばれています。
検索すれば同じ内容のものが沢山ヒットしますが、シェーダーにおける疑似乱数は以下のように算出していることが多いようです。
いきなりめっちゃ難しそうな数式が…って身構えるかもですが、重要な部分だけ理解をしましょう。
- 引数seedsに適当な値を入れると、0~1を返します。
※frac関数にかんして補足
マイナス値入ったら0~1無理じゃない?って思うかもですが
frac関数は符号を考慮しないです。
例えばfrac(-2.2)の場合、小数部分は0.2でその絶対値の小数部分を1から引くと、1 - 0.2 = 0.8になります。 - 引数seedsが同じ値であれば、必ず同じ結果を返し続ける。
sin関数も使っているので使い方によっては周期性が垣間見えますが、シェーダープログラミングにおいては軽量でそこそこの精度があるということで、この関数が愛用されているとのことです。
float random(float2 seeds)
{
return frac(sin(dot(seeds, float2(12.9898, 78.233))) * 43758.5453);
}
float2 random2(float2 seeds)
{
seeds = float2(dot(seeds, float2(127.1, 311.7)),
dot(seeds, float2(269.5, 183.3)));
return frac(sin(seeds) * 43758.5453123);
}
float3 random3(float3 seeds)
{
seeds = float3(dot(seeds, float3(127.1, 311.7, 542.3)),
dot(seeds, float3(269.5, 183.3, 461.7)),
dot(seeds, float3(732.1, 845.3, 231.7)));
return frac(sin(seeds) * 43758.5453123);
}
ブロックノイズ
実装自体は凄く簡単です、floorを使うだけです。
ただし、UV座標がそのままの場合、0~1の値しかないので引数にしても0が返ってきます。
float blockNoise(float2 seeds)
{
return random(floor(seeds));
}
なのでUV座標に対してスケーリングする値を乗算しましょう。
今回は20倍していますね。すると
float value = random(blockNoise(i.uv * 20));
見事ブロック上に分かれましたね。
20倍したことで、0~20のUV座標に分けられそれぞれの塗り分けをrandom関数で行われている。
というわけですね。
では、このノイズをアニメーションをさせましょう。
_Timeでアニメーションさせる
以下のように追加してみましょう。
0.00001を掛けているのは丁度いい具合だったからで意味はないです。
float value = random(blockNoise(i.uv * 20) + _Time.x * 0.00001);
余談ですが、_Timeにはxyzwの成分があります。
成分は以下のようです。
x=1/20
y=1
z=2
w=3
今回はアニメーション速度を調整したかったのでxを使っています。
なお、_Timeは実行時から常に加算されていく値になるので経過と共に肥大していきます。
その結果、予期せぬ結果を生む恐れがあるので
fmod関数などで値に制限を加えたりする場合もあるようですね。
ちゃんとアニメーションがされましたね!
random関数の引数であるblockNoiseに対し、_Time.xの結果を加算し続けることで変化を与え続けているという単純な仕組みです。
参考文献
ノイズ周りの参考文献は非常に多いです。
学習する上で意識していることですが、「差分を多く持て」です。
複数のリファレンスは情報の解像度を上げるので是非色々見てみましょう。
個人的に大変参考にさせて頂いた記事等を紹介させてください。
・ザ・ブックオブシェーダーさん
ノイズ以外の部分でも非常に参考になる内容が多いです。
・おもちゃラボさん
初学習の時に何度もお世話になりました。
こちらの記事の内容を何度もトレースし、内容を深掘りしていくと楽しいですよ。
・Unity Shader Programming Vol.05
XJINEさんが書かれているシェーダープログラミングの解説本第5段です。
自分は全部購入していますが、どれもおすすめの内容となっています。
vol.05がノイズプログラミングに特化した内容となっています。
特に初学習者の方には是非おすすめしたい本です。
僕は暇さえあれば読んでます。基本的にこの本を軸に差分を取って学習することが多いかも。
終わりに
ノイズは楽しい。適当に値を与えればそれなりに良い画になるので癖になってくる。
ただそれは、ノイズ関数が凄いというだけであることは忘れてはいけない。
良い傾向として、ある程度の関数の内容は調べなくても頭に入ってきた。
何回も使ったり調べることの重要性を知った気がする。
ノイズは結構自分でもやりたい事が多いので、もっと知識と、色々な物を残しておきたい。
Discussion