知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】オブジェクトに軌跡をつける #111

はじめに

UnityのTrailRendererに似た、オブジェクトに追従する軌跡を制作していきます。

https://styly.cc/wp-content/uploads/2020/04/4_Texture.gif

【Unity】Trail Rendererを使って軌跡を光らせる方法 | STYLY:より引用


実装

Unity上

Scene上に適当なオブジェクトを配置します。

オブジェクト

x座標を少しズラしてください。
また、今回制作するマテリアルを刺してください。

f:id:soramamenatan:20210731141930p:plain

マテリアル

マテリアルには適当なノイズ用テクスチャを刺します。

f:id:soramamenatan:20210731142012p:plain

スクリプト

using UnityEngine;

public class Afterimage : MonoBehaviour
{
    [SerializeField]
    private Material _material;
    [SerializeField]
    private float _trailSpeed = 10f;
    
    private Vector3 _trailPos;
    private int _dirId;

    private void Awake()
    {
        _trailPos = transform.position;
        _dirId = Shader.PropertyToID("_TrailDir");
    }

    private void Update()
    {
        Trail();
        Rotate();
    }

    /// <summary>
    ///  原点を中心に回転させる
    /// </summary>
    private void Rotate()
    {
        var tr = transform;
        var angleAxis = Quaternion.AngleAxis(180 * Time.deltaTime, Vector3.forward);
        var pos = tr.position;
        tr.position = angleAxis * pos;
    }

    /// <summary>
    /// 軌跡
    /// </summary>
    private void Trail()
    {
        var time = Mathf.Clamp01(Time.deltaTime * _trailSpeed);
        var tr = transform.position;
        _trailPos = Vector3.Lerp(_trailPos, tr, time);
        _material.SetVector(_dirId, transform.InverseTransformDirection(_trailPos - tr));
    }
}

シェーダー

Shader "Unlit/Afterimage"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _NoiseTex ("Noise", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _NoiseTex;
            float4 _NoiseTex_ST;
            fixed4 _TrailDir;

            v2f vert (appdata v)
            {
                v2f o;
                fixed weight = saturate((dot(v.normal, _TrailDir)));
                fixed noise = tex2Dlod(_NoiseTex, float4(v.uv.xy, 0, 0)).r;
                fixed4 trail = _TrailDir * weight * noise;
                v.vertex.xyz = float3(v.vertex.x + trail.x, v.vertex.y + trail.y, v.vertex.z + trail.z);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}


解説

Trail()

軌跡用に頂点をどのくらい伸ばすかをシェーダー側に渡しているメソッドになります。

private void Trail()
{
    var time = Mathf.Clamp01(Time.deltaTime * _trailSpeed);
    var tr = transform.position;
    _trailPos = Vector3.Lerp(_trailPos, tr, time);
    _material.SetVector(_dirId, transform.InverseTransformDirection(_trailPos - tr));
}

InverseTransformDirection

引数に入れた方向ベクトルを、ワールド空間からローカル空間へと変更してくれます。
方向ではなく、位置の場合にはTransform.InverseTransformPointを使用します。

バーテックスシェーダー

頂点を伸ばしている箇所になります。

fixed weight = saturate((dot(v.normal, _TrailDir)));
fixed noise = tex2Dlod(_NoiseTex, float4(v.uv.xy, 0, 0)).r;
fixed4 trail = _TrailDir * weight * noise;
v.vertex.xyz = float3(v.vertex.x + trail.x, v.vertex.y + trail.y, v.vertex.z + trail.z);

weight

自身の法線方向と、スクリプトから渡された方向ベクトルの内積を取得することで、重みを出しています。
今回は、角度差が小さいほど重みが大きくなります。

tex2Dlod

バーテックスシェーダー版のtex2D()になります。
定義は以下となります。

tex2Dlod(テクスチャ, float4(u値, v値, 0, lodの値の指定(0 ~ 7)))
tex2Dlodの詳しい解説

テクスチャのlodの値がDepth値(カメラからの距離)によって決定されます。
Depth値が計算されるのはバーテックスシェーダーの処理が終わり、フラグメントシェーダーに値を渡すときになります。
ですので、バーテックスシェーダー内ではlodの値が自動で割り当てられないので、tex2Dlod()を使用して明示的にlod値を指定して上げる必要があります。
MipMap等でも同様の処理が必要になります。

https://docs.unity3d.com/2018.3/Documentation/uploads/SL/PipelineCullDepth.png

Unity - Manual: ShaderLab: Culling & Depth Testing:より引用


結果

オブジェクトに軌跡が追従しています。

f:id:soramamenatan:20210731141425g:plain


参考サイト様

qiita.com

akinow.livedoor.blog