Created
November 6, 2025 13:46
-
-
Save tobyapi/ab23ae0e6424ba778661032d1127d4b8 to your computer and use it in GitHub Desktop.
1€ Filters for Unity (Vector3 & Quaternion)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine; | |
| // https://gery.casiez.net/1euro/ | |
| public sealed class OneEuroFilterVector3 | |
| { | |
| readonly float dCutoff; | |
| readonly LowPassFilterVector3 xFilt = new(); | |
| readonly LowPassFilterVector3 dxFilt = new(); | |
| // micCutoff を減らすとゆっくり動いているときの jitter が減る | |
| float minCutoff; | |
| // beta を増やすと素早く動いているときの lag が減る | |
| float beta; | |
| float tPrev; | |
| bool isFirstTime = true; | |
| public OneEuroFilterVector3(float minCutoff = 1.0f, float beta = 0.0f, float dCutoff = 1.0f) | |
| { | |
| this.minCutoff = minCutoff; | |
| this.beta = beta; | |
| this.dCutoff = dCutoff; | |
| } | |
| public void SetMinCutoff(float minCutoff) | |
| { | |
| this.minCutoff = minCutoff; | |
| } | |
| public void SetBeta(float beta) | |
| { | |
| this.beta = beta; | |
| } | |
| public Vector3 Filter(Vector3 x) | |
| { | |
| float t = Time.time; | |
| float rate = t - tPrev; | |
| if (rate <= 1e-9f) | |
| { | |
| return xFilt.LastValue(); | |
| } | |
| tPrev = t; | |
| var dx = isFirstTime switch | |
| { | |
| true => Vector3.zero, | |
| false => (x - xFilt.LastValue()) / rate | |
| }; | |
| isFirstTime = false; | |
| var edx = dxFilt.Filter(dx, Alpha(rate, dCutoff)); | |
| var cutoff = minCutoff + beta * edx.magnitude; | |
| return xFilt.Filter(x, Alpha(rate, cutoff)); | |
| } | |
| float Alpha(float rate, float cutoff) | |
| { | |
| var r = 2.0f * Mathf.PI * cutoff * rate; | |
| return r / (r + 1.0f); | |
| } | |
| } | |
| public sealed class OneEuroFilterQuaternion | |
| { | |
| readonly float dCutoff; | |
| readonly LowPassFilterQuaternion xFilt = new(); | |
| readonly LowPassFilterVector3 dxFilt = new(); | |
| float minCutoff; | |
| float beta; | |
| float tPrev; | |
| bool isFirstTime = true; | |
| public OneEuroFilterQuaternion(float minCutoff = 1.0f, float beta = 0.0f, float dCutoff = 1.0f) | |
| { | |
| this.minCutoff = minCutoff; | |
| this.beta = beta; | |
| this.dCutoff = dCutoff; | |
| } | |
| public void SetMinCutoff(float minCutoff) | |
| { | |
| this.minCutoff = minCutoff; | |
| } | |
| public void SetBeta(float beta) | |
| { | |
| this.beta = beta; | |
| } | |
| public Quaternion Filter(Quaternion x) | |
| { | |
| float t = Time.time; | |
| float rate = t - tPrev; | |
| if (rate <= 1e-9f) | |
| { | |
| return xFilt.LastValue(); | |
| } | |
| tPrev = t; | |
| Vector3 dx; | |
| if (isFirstTime) | |
| { | |
| dx = Vector3.zero; | |
| } | |
| else | |
| { | |
| var deltaQ = x * Quaternion.Inverse(xFilt.LastValue()); | |
| deltaQ.ToAngleAxis(out var angle, out var axis); | |
| if (angle > 180f) | |
| { | |
| angle -= 360f; | |
| } | |
| dx = axis * (angle * Mathf.Deg2Rad / rate); | |
| } | |
| isFirstTime = false; | |
| var edx = dxFilt.Filter(dx, Alpha(rate, dCutoff)); | |
| var cutoff = minCutoff + beta * edx.magnitude; | |
| return xFilt.Filter(x, Alpha(rate, cutoff)); | |
| } | |
| float Alpha(float rate, float cutoff) | |
| { | |
| var r = 2.0f * Mathf.PI * cutoff * rate; | |
| return r / (r + 1.0f); | |
| } | |
| } | |
| sealed class LowPassFilterVector3 | |
| { | |
| Vector3 hatx; | |
| bool isFirstTime = true; | |
| public Vector3 LastValue() | |
| { | |
| return hatx; | |
| } | |
| public Vector3 Filter(Vector3 value, float alpha) | |
| { | |
| hatx = isFirstTime switch | |
| { | |
| true => value, | |
| false => alpha * value + (1.0f - alpha) * hatx | |
| }; | |
| isFirstTime = false; | |
| return hatx; | |
| } | |
| } | |
| sealed class LowPassFilterQuaternion | |
| { | |
| Quaternion hatx; | |
| bool isFirstTime = true; | |
| public Quaternion LastValue() | |
| { | |
| return hatx; | |
| } | |
| public Quaternion Filter(Quaternion value, float alpha) | |
| { | |
| hatx = isFirstTime switch | |
| { | |
| true => value, | |
| false => Quaternion.Slerp(hatx, value, alpha) | |
| }; | |
| isFirstTime = false; | |
| return hatx; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment