ささみ雑記帳

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

ノイズでアルファ加算画像を歪ませるシェーダーをつくる

ノイズシェーダーを歪みマップに応用する話です。
元記事

テクスチャと色指定でアルファ加算する

アルファ加算は、炎などを表現するときによく用いられる加算合成方法で、
元画像のアルファ値(不透明度)が高い部分ほど、色を強く加算します。
f:id:sasanon:20181023010052p:plain 左から、Default-Particleテクスチャをそのまま表示、ベースカラーで赤を乗算、
ベースカラーを付けた上に黄色のアルファ加算カラーを加算、の状態です。
アルファ加算画像を表示するシェーダーを書いてみます。

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _BaseColor         ("Base Color"     , Color ) = (1,1,1,1)
        _AddAlphaColor    ("Add Alpha Color", Color ) = (0,0,0,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        LOD 100
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            ・・・
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _BaseColor;
            fixed4 _AddAlphaColor;
            ・・・

メインテクスチャ(_MainTex)、ベースカラー(_BaseColor)、アルファ加算カラー(_AddAlphaColor) をパラメータとして追加します。TagsやBlendは透過色を使える設定にしておきます。

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 color = tex2D(_MainTex, i.uv.xy);
                color *= _BaseColor;
                color.rgb += _AddAlphaColor * color.a;
                return color;
            }

fragmentシェーダーでは、メインテクスチャ色にベースカラーを乗算して乗せた後、アルファ加算カラーを足し込みます。
アルファ加算カラーはメインテクスチャのアルファ値と乗算することで、
アルファ値が大きい部分ほど強く色が足されます。
PhotoShopなどの色範囲0-255は、シェーダー内では0.0~1.0範囲に対応するので、  乗算されると暗く、加算されると明るくなります)

歪みマップの考え方

こうしてできたアルファ加算なDefault-Particleテクスチャを、ノイズを利用して歪ませます。
よくある歪みマップは、以下のように値を扱います。
PhotoShopなどの色範囲0-255は、シェーダー内では0.0~1.0範囲になります)
・Rチャンネル(RGB=XYZの対応ならX)が0.5より大きいほど、メイン画像のUV値をU(X)+方向にずらす
・Rチャンネル(RGB=XYZの対応ならX)が0.5より小さいほど、メイン画像のUV値をU(X)-方向にずらす
・Gチャンネル(RGB=XYZの対応ならY)が0.5より大きいほど、メイン画像のUV値をV(Y)+方向にずらす
・Gチャンネル(RGB=XYZの対応ならY)が0.5より小さいほど、メイン画像のUV値をV(Y)-方向にずらす
f:id:sasanon:20181023012242p:plain
これは、法線(=面の傾きを表す方向)マップと非常によく似た扱い方で、
法線のRG値を歪みマップ情報として扱うと都合が良いです。

※法線マップの場合、RGチャンネルに加えてBチャンネルが
 貼り付けた面のモデル上の法線(面の傾き方向)=Z+方向を表す1.0となります。
 法線マップを画像として見ると青っぽいのはこのためで、全く傾きのない面は全ピクセルの色が(0.5, 0.5, 1.0)になります。
f:id:sasanon:20181023012629p:plain

ノイズで生成した法線を歪みマップとして扱う

ノイズシェーダーをつくった際、位相をずらしてXYそれぞれ異なる乱数を生成して、
法線をつくっていました。これを歪みマップとして扱うことを考えてみます。

ノイズテクスチャのTiling/Offset(_NoiseTilingOffset)とサイズ、UVスクロール速度(_NoiseSizeScroll)、さらに歪み強度(_DistortionPower)をパラメータとして追加します。

    {
        _MainTex ("Texture", 2D) = "white" {}
        _BaseColor         ("Base Color"     , Color ) = (1,1,1,1)
        _AddAlphaColor    ("Add Alpha Color", Color ) = (0,0,0,1)

        _NoiseTilingOffset ("NoiseTex Tiling(x,y)/Offset(z,w)", Vector) = (0.1,0.1,0,0)
        _NoiseSizeScroll   ("NoiseTex Size(x,y)/Scroll(z,w)"  , Vector) = (16,16,0,0)
        _DistortionPower   ("Distortion Power", Float ) = 0
    }
    SubShader
    {
            ・・・

        Pass
        {
            ・・・
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _BaseColor;
            fixed4 _AddAlphaColor;

            fixed4 _NoiseTilingOffset;
            fixed4 _NoiseSizeScroll;
            fixed _DistortionPower;
            ・・・

メインテクスチャとノイズテクスチャでUV値が2つになるので、vertexシェーダーから受け渡すUVを拡張します。

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 uv : TEXCOORD0;
            };
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv.zw = TRANSFORM_NOISE_TEX(v.uv, _NoiseTilingOffset, _NoiseSizeScroll);
                return o;
            }

fragmentシェーダーでは、まずノイズテクスチャのUV値から法線マップ(≒歪みマップ)を計算します。
値の範囲が0.0~1.0(中央値0.5)なので、これを-1.0~1.0(中央値0.0)に変換し、歪み強度を掛けたものが歪み量(dist)になります。
こうして算出した歪み量の分だけ、メインテクスチャを参照するためのUVをずらすことで、歪んだ画像を得ます。

            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 dist = normalNoise(i.uv.zw, _NoiseSizeScroll.xy);    // perlinノイズで算出した法線を得る
                dist = dist * 2 - 1;                                        // 範囲を0.0~1.0から-1.0~1.0へ変換
                dist *= _DistortionPower;                                   // 歪み強度を乗算(歪み強度をシェーダーパラメータとして調整可能にする)

                i.uv.xy += dist.xy;                                         // 歪み量だけ、メインテクスチャのUVをずらす

                fixed4 color = tex2D(_MainTex, i.uv.xy);
                color *= _BaseColor;
                color.rgb += _AddAlphaColor * color.a;
                return color;
            }

ノイズ歪みシェーダーで炎を描く

このノイズ歪みシェーダーで、先ほどのアルファ加算したDefault Particleテクスチャを歪ませると、炎ができます。
f:id:sasanon:20181023015928p:plain Tiling値を大きくすると急で細かい歪み方となり、0に近づける(絶対値を小さくする)と広く緩やかな歪みになります。
また、Distortion Powerの強さによって炎の歪み具合も変わります。
ノイズ=歪みマップは上方向にUVスクロールさせることもできるので、
f:id:sasanon:20181023020154g:plain
燃えるようなアニメーションも可能です。

ノイズ歪みシェーダーで煙を描く

このノイズ歪みシェーダーですが、Particle Systemに適用することもできます。
パーティクル設定カラーは、どうやら頂点カラーとして入力されるらしいので、これを乗算するように直してみます。

            struct appdata
            {
                ...
                float4 color : COLOR;
            };

            struct v2f
            {
                ...
                float4 color : TEXCOORD1;
            };

            v2f vert (appdata v)
            {
                ...

                o.color = _BaseColor * v.color;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                ...

                color *= i.color;
                color.rgb += _AddAlphaColor * color.a;
                return color;
            }

Particle SystemのRendererにMaterialをセットすれば準備完了。
Particle Systemの設定値は多すぎるので割愛しますが、Color over Lifetimeで透明、不透明、透明と遷移させることで煙のような表現になります。
f:id:sasanon:20181023022703g:plain
煙量マシマシ化。
f:id:sasanon:20181023022732g:plain

ノイズ歪みシェーダー(アルファ加算画像)のソースコード

同じ階層に、元記事のSasamiNoise.cgincと一緒に置くことで動くはずです。
gist.github.com