【UnityShader】オブジェクトに軌跡をつける #111
はじめに
UnityのTrailRendererに似た、オブジェクトに追従する軌跡を制作していきます。
実装
Unity上
Scene上に適当なオブジェクトを配置します。
オブジェクト
x座標を少しズラしてください。
また、今回制作するマテリアルを刺してください。
マテリアル
マテリアルには適当なノイズ用テクスチャを刺します。
スクリプト
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等でも同様の処理が必要になります。
結果
オブジェクトに軌跡が追従しています。