UVで移動・回転・拡縮をする
環境
Unity2021.2.18f1
概要
UVで移動・回転・拡縮をしてみました。
AnimationCurveからKeyframe配列のtimeとvalueを抜き出し、シェーダでバイナリサーチをかけ、リニア補間した値を使用しています。
using Unity.Collections; using UnityEditor; using UnityEngine; public unsafe class UvTransform : MonoBehaviour { [SerializeField] Material _material = null; //リニア補間のみ [SerializeField] AnimationCurve _uvOffsetXs = null; [SerializeField] AnimationCurve _uvOffsetYs = null; [SerializeField] AnimationCurve _uvRotateZs = null; [SerializeField] AnimationCurve _uvScaleXs = null; [SerializeField] AnimationCurve _uvScaleYs = null; private static int _spUvOffsetXs = Shader.PropertyToID("_UvOffsetXs"); private static int _spUvOffsetYs = Shader.PropertyToID("_UvOffsetYs"); private static int _spUvRotateZs = Shader.PropertyToID("_UvRotateZs"); private static int _spUvScaleXs = Shader.PropertyToID("_UvScaleXs"); private static int _spUvScaleYs = Shader.PropertyToID("_UvScaleYs"); private static int _spMaxTime = Shader.PropertyToID("_MaxTime"); private GraphicsBuffer[] _gbUvTransforms = null; private float _maxTime = 0.0f; #if UNITY_EDITOR private void Reset() { _uvOffsetXs.AddKey(new Keyframe(0.0f, 0.0f)); _uvOffsetXs.AddKey(new Keyframe(1.0f, 0.0f)); _uvOffsetYs.AddKey(new Keyframe(0.0f, 0.0f)); _uvOffsetYs.AddKey(new Keyframe(1.0f, 0.0f)); _uvRotateZs.AddKey(new Keyframe(0.0f, 0.0f)); _uvRotateZs.AddKey(new Keyframe(1.0f, 0.0f)); _uvScaleXs.AddKey(new Keyframe(0.0f, 1.0f)); _uvScaleXs.AddKey(new Keyframe(1.0f, 1.0f)); _uvScaleYs.AddKey(new Keyframe(0.0f, 1.0f)); _uvScaleYs.AddKey(new Keyframe(1.0f, 1.0f)); var animCurves = new AnimationCurve[] { _uvOffsetXs, _uvOffsetYs, _uvRotateZs, _uvScaleXs, _uvScaleYs }; foreach(var animCurve in animCurves) { for (int i = 0; i < animCurve.length; i++) { AnimationUtility.SetKeyLeftTangentMode(animCurve, i, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyRightTangentMode(animCurve, i, AnimationUtility.TangentMode.Linear); } } } private void OnValidate() { Setup(); } #endif private void Setup() { if(Application.isPlaying == false) return; var animCurves = new AnimationCurve[] { _uvOffsetXs, _uvOffsetYs, _uvRotateZs, _uvScaleXs, _uvScaleYs }; var shaderIds = new int[] { _spUvOffsetXs, _spUvOffsetYs, _spUvRotateZs, _spUvScaleXs, _spUvScaleYs }; if(_gbUvTransforms == null) { _gbUvTransforms = new GraphicsBuffer[animCurves.Length]; for(int i = 0; i < _gbUvTransforms.Length; i++) { var animCurve = animCurves[i]; _gbUvTransforms[i] = new GraphicsBuffer(GraphicsBuffer.Target.Structured, animCurve.length, sizeof(Vector2)); } } _maxTime = 0.0f; for(int i = 0; i < _gbUvTransforms.Length; i++) { var animCurve = animCurves[i]; var keyValues = new NativeArray<Vector2>(animCurve.length, Allocator.Temp); for(int j = 0; j < animCurve.length; j++) { var keyframe = animCurve.keys[j]; keyValues[j] = new Vector2(keyframe.time, keyframe.value); } if(_maxTime < keyValues[animCurve.length - 1].x) _maxTime = keyValues[animCurve.length - 1].x; _gbUvTransforms[i].SetData(keyValues); _material.SetBuffer(shaderIds[i], _gbUvTransforms[i]); } _material.SetFloat(_spMaxTime, _maxTime); } private void Start() { Setup(); } private void OnDestroy() { if(_gbUvTransforms != null) { foreach(var gb in _gbUvTransforms) { gb.Dispose(); } _gbUvTransforms = null; } } }
Shader "Custom/UvTransform" { Properties { _MainTex ("Texture", 2D) = "white" {} } CGINCLUDE #pragma target 4.5 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; StructuredBuffer<float2> _UvOffsetXs; StructuredBuffer<float2> _UvOffsetYs; StructuredBuffer<float2> _UvRotateZs; StructuredBuffer<float2> _UvScaleXs; StructuredBuffer<float2> _UvScaleYs; float _MaxTime; float SearchValue(float time, StructuredBuffer<float2> keyValues) { uint len; uint stride; keyValues.GetDimensions(len, stride); uint min = 0; uint max = len - 1; if (time <= keyValues[min].x) return keyValues[min].y; if (time >= keyValues[max].x) return keyValues[max].y; while ((max - min) > 1) { uint index = (min + max) / 2; if (keyValues[index].x < time) { min = index; } else //if (time < anims[index].x) { max = index; } } float r = (time - keyValues[min].x) / (keyValues[max].x - keyValues[min].x); return lerp(keyValues[min].y, keyValues[max].y, r); } float2 UvTransform(float2 tiling, float2 offset, float2 texcoord, float2 pos, float2 scale, float rotateZ) { float2 pivot = float2(0.5, 0.5); float cosAngle, sinAngle; sincos(rotateZ, sinAngle, cosAngle); float2 s = ((tiling - 1.0) + scale); float2x2 rotMatrix = float2x2(float2(cosAngle, -sinAngle), float2(sinAngle, cosAngle)); float2 uv = texcoord - pivot; uv = mul(rotMatrix, uv) * s + pivot + -pos; uv += pivot * tiling + offset - pivot; return uv; } v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); float time = fmod(_Time.y, _MaxTime); float ox = SearchValue(time, _UvOffsetXs); float oy = SearchValue(time, _UvOffsetYs); float rz = SearchValue(time, _UvRotateZs); float sx = SearchValue(time, _UvScaleXs); float sy = SearchValue(time, _UvScaleYs); o.uv = UvTransform(_MainTex_ST.xy, _MainTex_ST.zw, v.uv, float2(ox, oy), float2(sx, sy), rz); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG } } }