ノイズシェーダーを勉強して拡張した話
最近は初級シェーダー書きとしてシェーダーを勉強してます。
知識おいしい。もぐもぐ。
ノイズシェーダーの基礎勉強
については、主にこちらの記事から学びました。
nn-hokuson.hatenablog.com
postd.cc
他色々なサイトも巡っていて、情報が錯綜していて少し混乱していましたが、
パーリンノイズはよくバリューノイズ(Bilinear補完したブロックノイズ)を多オクターブ化したものと
勘違いされている(ので誤情報が多い)らしいですね。これを理解するまで時間がかかりました。
下の画像は左からバリューノイズ(1枚)、バリューノイズ(粒度=オクターブを変えて5枚加算)、
パーリンノイズ(1枚)、パーリンノイズ(5オクターブ)の計算結果です。
バリューノイズは補完元のブロックノイズ感が見えるのに対し、
パーリンノイズは1枚の時点でランダムな方向に混ざっているのがわかるでしょうか?
それぞれ粒度=オクターブを変えて5枚加算すると、どちらも何となくPhotoShopの雲模様感が出てきますが、
ブロックノイズ感=縦横方向へのラインが見えるか、そうでないかの違いは残っています。
Tilingへの対応
おもちゃラボさんのコードを元に、Unityでテクスチャを扱う際によくあるTiling/Offsetと、UVスクロールへ対応させる拡張をしてみました。
(どれでも良いのですが、以降は5枚加算のパーリンノイズで解説します)
Tilingの拡張を行うためには、仮想的なテクスチャサイズが必要かなと考えました。
まず、vertexシェーダー側でサイズとTiling値とテクスチャサイズを乗算しておき、
// TRANSFORM_TEX相当の処理(vertexシェーダーでの変換)
fixed2 TRANSFORM_NOISE_TEX(fixed2 uv, fixed2 tiling, fixed2 size) {
// 仮想ノイズテクスチャサイズ, Tilingの適用
uv = uv * tiling * size;
return uv;
}
fragmentシェーダー側の乱数算出(rand)内でテクスチャサイズで除算、 frac関数でループする小数値を得てみました。
(計算結果が浮動小数計算の誤差? で1超になることがあったので、調整で0.999...を掛けています)
fixed rand (fixed2 uv, fixed2 size) {
// UV値をテクスチャサイズ(周期)で割り、Tiling値で繰り返すUV値にする
uv = frac(uv/size);
return frac(sin(dot(frac(uv/size), fixed2(12.9898,78.233))) * 43758.5453) * 0.99999;
}
効果がわかりやすいよう、仮想テクスチャサイズをものすごく絞って4*4サイズでお試し。
Tiling値を整数で増やすと、ノイズテクスチャがループするような(タイルを敷き詰めたような)見た目になっていきます。
逆に1未満の値にすると、引き伸ばされます。拡大縮小どちらもUV片軸ずつ行うことができるのも重要な点ですね。
Tiling/Offset、さらにUVスクロールへの対応
vertexシェーダー側に更に直して、OffsetずらしとUVスクロールができるようにもしておきます。
Tiling, Offset, 仮想テクスチャサイズ、スクロール量がそれぞれUV方向で、パラメータがちょっと多い…
// TRANSFORM_TEX相当の処理(vertexシェーダーでの変換)
fixed2 TRANSFORM_NOISE_TEX(fixed2 uv, fixed4 tilingOffset, fixed4 sizeScroll) {
// 仮想ノイズテクスチャサイズ, Tiling, Offsetの適用
uv = uv * tilingOffset.xy * sizeScroll.xy + tilingOffset.zw;
// Scrollの適用。Tilingに合わせた相対速度でUVスクロールを行う
uv += fixed2(sizeScroll.z * tilingOffset.x, -sizeScroll.w * tilingOffset.y) * _Time.y;
return uv;
}
UVスクロール効果をお試し。
sizeScrollはxyが仮想テクスチャサイズ、zwがスクロール値として使っているので、
試しにw=-10で上から下にスクロールしてみるとこんな感じ。
Tilingによる引き伸ばし+UVスクロールで、流れ落ちる水の感じが出てきました。
ノイズ画像的な法線マップを生成してみる
ノイズっぽい法線マップもつくってみました。
これらのノイズはUV値を元に疑似乱数で生成しているのですが、
法線マップ的にはXYで異なる乱数を得ないと、常に斜め右上か左下方向の
面の傾きベクトルになってしまいます。
そこで、適当に位相をずらしてあげます。
パーリンノイズやバリューノイズでは、格子点(vertexシェーダーで計算した時点でUV値が1.0や2.0となる点)が重要なので、
位相ずらしもとりあえずジャスト1.0ずつずらしてみました。
// 法線マップ用ノイズ(x:0.0 ~ 1.0, x:0.0 ~ 1.0, z:1.0)
fixed3 normalNoise( fixed2 uv, fixed2 size )
{
fixed3 result = fixed3( perlinNoise(uv.xy, size),
perlinNoise(uv.xy+fixed2(1,1), size),
1.0 );
return result;
}
これで、こんな感じに法線マップっぽいノイズ画像が得られます。
Tiling/OffsetとUVアニメーションに対応する拡張と合わせると、流れる水が表現できそうな法線が出来てきます。
ソースコード
ノイズ算出部分はそれなりに行数が長い処理になるので、
SasamiNoise.cgincファイルとしてシェーダーから分離し、使い回せるようにしておきました。
gist.github.com
そして、Unlitシェーダーから少しだけ改変したシェーダー側(.shader)では、
UnityでUnlitシェーダー生成時に記述されている#include "UnityCG.cginc"を代わりに#include "SasamiNoise.cginc" 、
vertexシェーダーでTRANSFORM_TEXの代わりにTRANSFORM_NOISE_TEX、
fragmentシェーダーでperlinNoiseやbumpNoise関数を呼び出して使います。
gist.github.com