ささみ雑記帳

少し長くなりそうな色々を書き留めておくためのノート。

ノイズシェーダーを勉強して拡張した話

最近は初級シェーダー書きとしてシェーダーを勉強してます。
知識おいしい。もぐもぐ。

ノイズシェーダーの基礎勉強

については、主にこちらの記事から学びました。
nn-hokuson.hatenablog.com postd.cc

他色々なサイトも巡っていて、情報が錯綜していて少し混乱していましたが、
パーリンノイズはよくバリューノイズ(Bilinear補完したブロックノイズ)を多オクターブ化したものと
勘違いされている(ので誤情報が多い)らしいですね。これを理解するまで時間がかかりました。

下の画像は左からバリューノイズ(1枚)、バリューノイズ(粒度=オクターブを変えて5枚加算)、
パーリンノイズ(1枚)、パーリンノイズ(5オクターブ)の計算結果です。
f:id:sasanon:20181021235816p:plain
バリューノイズは補完元のブロックノイズ感が見えるのに対し、
パーリンノイズは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値を整数で増やすと、ノイズテクスチャがループするような(タイルを敷き詰めたような)見た目になっていきます。
f:id:sasanon:20181022003920g:plain:h200
逆に1未満の値にすると、引き伸ばされます。拡大縮小どちらもUV片軸ずつ行うことができるのも重要な点ですね。
f:id:sasanon:20181022004046g:plain:h200

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で上から下にスクロールしてみるとこんな感じ。
f:id:sasanon:20181022005405g:plain:h200

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アニメーションに対応する拡張と合わせると、流れる水が表現できそうな法線が出来てきます。
f:id:sasanon:20181022020743p:plain:h200f:id:sasanon:20181022020748g:plain:h200

ソースコード

ノイズ算出部分はそれなりに行数が長い処理になるので、
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