そろそろShaderをやるパート33 空間スキャン表現

7 min read読了の目安(約6400字

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

デモ

LiDER搭載端末で作成したMeshに今回作成したスキャンShaderを適用してみました。

記事の内容としてはエディター上でボタン押下によりスキャンを走らせる方法を解説します。

Shaderサンプル

内積の部分は下記で解説しています。
【参考リンク】:そろそろShaderをやるパート31 内積を使う

Shader "Custom/Scan"
{
    Properties
    {
        [HDR]_LineColor("Scan Line Color", Color) = (1,1,1,1)
        [HDR]_TrajectoryColor("Scan Trajectory Color", Color) = (0.3, 0.3, 0.3, 1)
        _LineSpeed("Scan Line Speed", Float) = 1.0
        _LineSize("Scan Line Size", Float) = 0.02
        _TrajectorySize("Scan Trajectory Size", Float) = 1.0
        _IntervalSec("Scan Interval", Float) = 2.0
        _MaxAlpha("Max Alpha", Range(0,1)) = 0.5
        _TrajectoryAlpha("Trajectory Alpha", Range(0.1,1)) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Transparent" "Queue"="Transparent"
        }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : WORLD_POS;
            };

            float4 _LineColor;
            float _LineSpeed;
            float _LineSize;
            float4 _TrajectoryColor;
            float _TrajectorySize;
            float _IntervalSec;
            float _MaxAlpha;
            float _TrajectoryAlpha;
            
            //C#から受け取る
            float _TimeFactor;
            float _AlphaFactor;

            v2f vert(appdata v)
            {
                v2f o;
                //unity_ObjectToWorld × 頂点座標(v.vertex) = 描画しようとしてるピクセルのワールド座標 らしい
                //mulは行列の掛け算をやってくれる関数
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                float timeDelta = (_TimeFactor *_LineSpeed);
                //カメラの正面方向にエフェクトを進める
                //-UNITY_MATRIX_V[2].xyzでWorldSpaceのカメラの向きが取得できる
                float dotResult = dot(i.worldPos,normalize(-UNITY_MATRIX_V[2].xyz));
                //時間変化に伴い値を減算する
                float linePosition = abs(dotResult - timeDelta);
                //スキャンラインの大きさを計算 step(a,b) はbがaより大きい場合1を返す
                //すなわち、_LineSizeが大きくなればstepが1を返す値の範囲も大きくなる
                float scanline = step(linePosition,_LineSize);
                //軌跡の大きさを計算 smoothstep(a,b,c) はcがa以下の時は0、b以上の時は1、0~1は補間
                //1 - smoothstep(a,b,c)とすることで補間値を逆転できる 
                //つまり 1 - smoothstep(a,b,c) はcがa以上の時は1、b以下の時は0、0~1は補間
                float trajectory = 1 - smoothstep(_LineSize,_LineSize + _TrajectorySize, linePosition);
                //同様にして徐々に透過させる
                float alpha = 1 - smoothstep(_LineSize, (_LineSize + _TrajectorySize) *_TrajectoryAlpha, linePosition);
                //ここまでの計算結果を元に色を反映
                float4 color = _LineColor * scanline + _TrajectoryColor * trajectory;
                //透明度調整 clamp(a,b,c) aの値をb~cの間に収める
                color.a = clamp(alpha *_AlphaFactor,0, _MaxAlpha);
                return color;
            }
            ENDCG
        }
    }
}

smoothstep

Shader内にもコメントを残していますが、smoothstep(a,b,c) はcがa以下の時は0、b以上の時は1、a~bは補間してくれます。

下記は引数a,bに0.1,0.5を代入した場合のグラフです。


1 - smoothstep(a,b,c)とすることで返す値を下記のように反転させることができます。

カメラの方向

カメラの方向は-UNITY_MATRIX_V[2].xyzで利用できます。
下記に詳しいまとめがあり、助かりました。
【参考リンク】:[Unity] CGに使用される行列についての考察

Script

本題とずれますが、ボタン押下でShaderを動かす仕組みに利用したコードもメモします。

結論から言うとAnimatorのTriggerをオンオフする処理をボタンに登録し、Shaderの一部のパラメーターをアニメーションさせています。

Shader側にC#から値を渡す方法などは下記にまとめています。
【参考リンク】:そろそろShaderをやるパート13 マウスのRayの座標をC#からShaderで受け取る
【参考リンク】:そろそろShaderをやるパート21 Animator使ってジオメトリーシェーダーを制御

まずはAnimatorのTriggerをオンオフする処理をボタンに登録しているコードです。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// スキャンボタン押下時のイベント登録
/// </summary>
public class ScanButtonEvent : MonoBehaviour
{
    [SerializeField] private Button button;
    [SerializeField] private Animator animator;

    private void Start()
    {
        button.onClick.AddListener(ScanEvent);
    }

    private void OnDestroy()
    {
        button.onClick.RemoveListener(ScanEvent);
    }

    /// <summary>
    /// スキャン時のイベント
    /// アニメーターのTriggerを切り替えるだけ
    /// </summary>
    private void ScanEvent()
    {
        animator.SetTrigger("Scan");
    }
}

次にShaderへAnimatorで変化するパラメーターを渡しているコードです。

using UnityEngine;

/// <summary>
/// アニメーションで変化させた値をShaderで使う
/// </summary>
[ExecuteAlways] //こいつ付けとけばEditorでプレビュー可能
public class ParameterToShader : MonoBehaviour
{
    // シェーダーで利用する値たち C#側ではAnimatorで変化させる
    public float TimeValue;
    public float AlphaValue;

    /// <summary>
    /// Shaderを適用したマテリアル
    /// </summary>
    [SerializeField] private Material mat;
    
    
    // Shader側に用意した定義済みの値を受け取る変数たち
    private string _timeFactor = "_TimeFactor";
    private string _alphaFactor = "_AlphaFactor";

    void Update()
    {
        //Shaderに値を渡す
        mat?.SetFloat(_timeFactor, TimeValue);
        mat?.SetFloat(_alphaFactor, AlphaValue);
    }
}

参考リンク

Shader(HLSL), 手続き的にテクスチャ生成など行うとき使用頻度の高い関数
Unityを利用した空間スキャンライン表現
3Dスキャンライン表現