Unity シェーダーを学ぶ

【Unity Shader入門⑤】Timeを使ってシェーダーによる演出の幅を広げよう!

前回の記事では、Shaderを使って色の滑らかな変化を生み出す「グラデーション表現」について学びました。

今回はさらに一歩進めて、Shaderにおける「時間の概念」を取り扱います。

時間を使うことで、グラデーションを動かしたり、光らせたり、リズムに合わせた演出が可能になります。

この記事では、Shader内での時間変数の扱い方や、周期的な動きを作る基本技術について、実例を交えながら解説していきます。

Shader表現を「静」から「動」へと進化させる重要なステップを、ぜひ一緒に押さえていきましょう!

 

Shaderにおける「時間」の意味

Shaderにおける「時間」とは、視覚的な変化をリアルタイムで生み出すための根幹となる要素です。

動かないテクスチャや色に「時間」を加えることで、点滅や波打ち、流れといったアニメーションが可能になり、映像表現やゲーム演出に動的な命を吹き込みます。

例えば下記のような継続的に動く演出などで、ユーザーの目を引きたい場面や雰囲気を作りたい場面において、時間を使ったアニメーションは欠かせない、シーンの印象を左右する重要な要素です。

  • 魔法のエフェクト
  • ホログラムのちらつき
  • 注意喚起の点滅

これらの演出を「時間」を用いて、UV座標やテクスチャを動かしたり、三角関数(sin関数やcos関数)と組み合わせることで色のグラデーションを作ったり、透明度に応用してフェード効果を与えることで実現しています。

時間変数(_Time / Time ノード)の構造と単位

Unityにおける時間は、_Time(HLSL)またはTimeノード(Shader Graph)を通じて扱います。_Timeには .x(経過秒数)、.y(2倍)、.z(3倍)、.w(sin(時間))の4要素があり、すべて秒単位です。周期的な動きや速さの調整を、簡単に行える構造になっています。

 

Shader Graphで時間を使った表現を作る

さて実際にShader Graphにて時間を使った表現を作ってみましょう。

今回のゴールは下記のような帯がオブジェクトを伝わるような動きのあるシェーダーを作ります。

 

今回のShader Graphの全体像

Unity Shader Graph Timeノード

 

画像にすると見えないぐらいの大きさになりましたね!

今回解説する部分は赤い枠の部分で、左側の赤枠内にTimeノードがあります。

それぞれ拡大すると下記のようなノードになっています。

Unity Shader Graph Timeノード

Unity Shader Graph Timeノード

全体の流れとしては、下記のように作っています。

  1. Timeノードから時間とcos波を取り出す。
  2. cos波の周期の前半と後半に分け、前半はそのまま、後半は符号を反転させた値を取り出す。
  3. オブジェクトの頂点位置と2で取り出した値を足し合わせることで、オブジェクトの各頂点に対して0〜2の値を出力する。
  4. SmoothStepで境界値を滑らかにしつつ、正方向と負方向の動きを取り出し、掛け合わせる。
  5. Remapし0〜1の値を取り出したら、Lerpノードで色を決めて出力する。

それでは各ステップの詳細を説明します。

 

Timeノードの解説

Timeノードを用いることで時間経過による値を取得することができます。

今回使用したのはTime出力と、Cosin出力です。

 

Time出力

Time出力では(おそらく)オブジェクトがスポーンしてからの経過時間を取得することができます。

ずっとインクリメントされているカウンターと思いましょう。

 

Cosin出力

sin出力やcosin出力は-1〜1の間を動きます(下記のグラフはCosin出力の動き。横軸はTime出力)

Unity Shader Graph Timeノード cosin

Time出力が2π(≒6.2831)分経過すると1周期となります。

今回は周期の最初で1を取得したいので、Cosin出力を使います。

 

Time→Modulo→Step→Remap→Multiplyの流れ

左側の赤枠で囲った部分は下記の処理をしています。

  1. Timeノードで経過時間を取り出し、Moduloノードで剰余を取り出す。
  2. Stepノードで0〜πの時に0、π〜2πの時に1となる矩形波を取り出す。
  3. Remapノードで-1〜1の矩形波にして、Cosin波とMultiplyノードで掛け合わせることで、Cosin波の前半周期部を-1から1の値にする。

1〜3の処理をすることで、下図のような値を取り出すことができるようになります。

unit-shader-05-02

 

Time→SmoothStep→Multiply→Remapの流れ

右側の赤枠の部分は下記の流れとなっています。

  1. オブジェクトの頂点座標とTopベクトルの内積に、先ほどの出力を足し合わせることで、頂点座標ごとの変動する値を取得する(Topベクトルと交差する頂点が0〜2、その反対が-2〜0の値となる)。
  2. SmoothStepノードで-0.2〜0.2の値を0〜1に変換します。-0.2より小さい値は0、0.2より大きい値は1になる。
  3. 2を-0.2〜0.2及び0.2〜-0.2の二つを出し、Multiplyノードで掛け算をすることとにより、帯が動いているような表現になる。
  4. Remapノードで出力を調整して、Lerpノードで色を決める。

SmoothStepノードで急勾配をつけている

SmoothStepノードを使用した理由は、色の変化にエッジを加えるためです。

SmoothStepを使わないと、-1から1へ一定の速度で変わっていくため、2色の境がはっきりとしません。

そこで-1から1の変化を-0.2から0.2の中へ押し込むことで、色の変化量を変え、境目をはっきりとさせました(下グラフ参照)。

Unity Shader Graph Smooth Step

 

反転させて掛け合わせる意味

そして、エッジを作ったことにより、下グラフのようにエッジ部の抽出ができます。

赤い線が帯の部分、黄色が通常のSmoothStepの出力、青色が反転したSmoothStepの出力です。

Unity Shader Graph Smooth Step

赤い部分は0〜0.25の値を取るので、これをRemapすることで0〜1になるようにすれば、よりはっきりとした帯にすることができます。

この帯の部分がTimeによって、球の端から端まで動くことで帯が動くような表現をすることができるわけですね。

 

ShaderLabで時間を使った表現を作る

次に上記で作ったシェーダーをShaderLab + HLSLで実装しましょう。

 

ShaderLabの全体像

先に作成したShader GraphをShaderLabで実装すると下記のようになります。

Shader "Unlit/SphereMoveTieMaterial"
{
    Properties
    {
        _ColorA ("ColorA", Color) = (1,1,1,1)
        _ColorB ("ColorB", Color) = (0,0,0,1)
        _Top    ("Top", Vector) =  (1.0, 0.0 ,0.0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 vertex : TEXCOORD0;
            };

            float4 _ColorA;
            float4 _ColorB;
            float3 _Top;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.vertex = v.vertex;
                return o;
            }

            float remap ( float input, float2 InMinMax, float2 OutMinMax){
                return OutMinMax.x + (input - InMinMax.x) * (OutMinMax.y - OutMinMax.x) / (InMinMax.y - InMinMax.x);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float period = _Time.y % 6.28;
                float cosStep = step(period, 3.14);

                float2 InMinMax = float2(0, 1);
                float2 OutMinMax = float2(-1, 1);
                float remapStep = remap(cosStep, InMinMax, OutMinMax);
                float cosValue = _CosTime.w * remapStep;

                float4 dotProduct = dot(_Top, i.vertex);

                float addValue = cosValue + dotProduct;

                float smoothstep1 = smoothstep(-0.2, 0.2, addValue);
                float smoothstep2 = smoothstep(0.2, -0.2, addValue);

                float multiValue = smoothstep1 * smoothstep2;

                InMinMax = float2(0, 0.25);
                OutMinMax = float2(0, 1);
                float lerpInput = remap(multiValue, InMinMax, OutMinMax);

                return lerp(_ColorA, _ColorB, lerpInput);
            }

            ENDCG
        }
    }
}

 

このShaderを実行すると下記のような動きになりますね。

 

なぜ_Time変数のyを使っているのか?

_Time変数や_CosTime変数はUnityのビルトイン変数と呼ばれるものです。

_Time.yは時間経過の等倍を、_CosTime.wは等倍の時間経過の時のCos関数の値を示します。

下記、引用です。

ビルトイン変数

引用元:ビルトインのシェーダー変数

というわけで、今回は_Time.yと_CosTime.wを使っていたわけです。

 

なぜShader Graphと動く方向が逆なのか?

気づいているかと思いますが、動画を見ると帯の動く方向が逆になってますよね。

これは座標系が違う・・・のだと思ってます。

Unity内ではいわゆる左手座標系とか右手座標系とか言われることがあるわけですが、今回はオブジェクトのローカル座標を使っているので、座標系の影響を受けていると思います。

(なぜ「思います」なのかというと確認してないからです・・・。すみません。)

参考外部リンク:ややこしいUnityの座標系の確認方法

 

まとめ

Shaderにおいて時間を扱うことで、静止した表現に動的な変化を加えることができるようになりましたね。

時間を利用したShader表現は、単なる色変化にとどまらず、リズムに合わせた演出や、点滅、波打ち、フェードといった視覚効果の幅を大きく広げます。

今回学んだ内容は、今後さらに高度なShaderエフェクトを作成するための基礎技術となります。

時間の概念を自在に操れるようになれば、Shaderによる演出の自由度は飛躍的に高まり、ゲームや映像作品に一層深みと魅力を与えることができるでしょう!

次回はLitシェーダー、つまりは光の効果を取り入れたシェーダーについて解説します!

-Unity, シェーダーを学ぶ