Блог программиста
Математика Unity (Урок 17): Quaternion.
19.12.2017Математика Unity

Кватернионы используются для представления вращений.
В Unity все вращения представлены в виде кватернионов. Их использование решает проблему "шарнирного замка" (gimbal lock).

Вникать во внутреннее устройство кватернионов нам нет нужды, мы можем просто их использовать :)
И для начала разберемся как получить кватернион из привычных векторов и углов.

Первый способ (углы указываются в градусах):

//Изменяем вращение объекта
transform.rotation.eulerAngles = new Vector3(0f, 90f, 0f);

Второй способ (значения вращения по каждой оси в градусах):

Quaternion.Euler(float x, float y, float z);
//или
Quaternion.Euler(Vector3 euler);

И в случае с методом и в случае со свойством вращение применяется в порядке: Z, X, Y. Стоит это учитывать, чтобы не было неожиданных поворотов

Третий способ (через ось и угл вращения вокруг неё в градусах):

//По оси и углу поворота
Quaternion.AngleAxis(Vector3 axis, float angle);

Получить углы Эйлера из кватерниона можно через свойство eulerAngles:

//Выводим вращение объекта в привычных значениях
Debug.Log(transform.rotation.eulerAngles);

Или можно получить ось и угол вращения вокруг нее:

float angle = 0f;
Vector3 axis;
//Получение оси и угла поворота
transform.rotation.ToAngleAxis(out angle, out axis);
Debug.Log("Angle: " + angle + " Axis: " + axis);

Теперь разберемся, как с их помощью вращать объекты.
Для этого нужно кватернион, из которого мы хотим повернуть (текущее положение), умножить на кватернион, на который хотим повернуть.
Пример:

//Поворачиваем объект на 90 градусов
transform.rotation *= Quaternion.Euler(0f, 90f, 0f);

Но кватернионы - это тот случай, когда от перемены мест множетелей произведение меняется.
Поэтому такой код даст иной результат:

transform.rotation = Quaternion.Euler(0f, 90f, 0f) * transform.rotation;

Интерполяция

Есть несколько методов интерполяции для кватернионов, первый из них - Lerp:

Quaternion.Lerp(Quaternion a, Quaternion b, float t);

Данный метод возвращает промежуточное вращение между a и b на основе t. При этом значение t ограничивается диапазоном от 0 до 1.
Вернёт a, при t равное или меньше 0.
Вернёт b, при t равное или больше 1.

Второй метод интерполяции - LerpUnclamped:

Quaternion.LerpUnclamped(Quaternion a, Quaternion b, float t);

От предыдущего он отличается только отсутствием ограничений t. Т.е результат выходит за пределы a и b, если t < 0 или t > 1.

Картинка для наглядности:

Отличие Lerp от LerpUnclamped

Следующие два метода интерполяции - Slerp и SlerpUnclumped:

Quaternion.Slerp(Quaternion a, Quaternion b, float t);
Quaternion.SlerpUnclumped(Quaternion a, Quaternion b, float t);

Разница между ними такая же, как и между двумя предыдущими.
А отличие Lerp от Slerp поможет понять это видео (синее - lerp, белое - slerp):

Пример:

Имеется прожектор, который нужно вращать по кругу, чтобы освещать всю территорию вокруг него.
Используем для решения сей задачи LerpUnclamped:

public class ProjectorRotater : MonoBehaviour
{ 
	public Transform projector = null;

	private Quaternion start;
	private Quaternion end;

	void Start()
	{
		start = Quaternion.identity;
		end = Quaternion.Euler (0f, 90f, 0f);
	}

	void Update()
	{
		projector.rotation = Quaternion.LerpUnclamped (start, end, Time.time);
	}
}
В данном случае прожектор пройдёт полный круг за 4 сек.

Немного изменим задачу.
Прожектор должен освещать не всё вокруг, а только сектор в 90 градусов.
Для этого заменим LerpUnclamped на Lerp. А чтобы прожектор не стопорился в конце, обвернём значение времени функцией Mathf.PingPong:

projector.rotation = Quaternion.Lerp(start, end, Mathf.PingPong(Time.time, 1f));

Задаём направления

Функция FromToRotation (или ее аналог SetFromToRotation) создает такой кватернион, что если его применить к fromDirection, то направление этого вектора совпадёт с toDirection:

Quaternion.FromToRotation(Vector3 fromDirection, Vector3 toDirection);
Обычно используется, чтобы повернуть объект так, чтобы одна из его осей смотрела в нужном направлении.

Функция LookRotation (или SetLookRotation) создает вращение, при котором ось Z сонаправленна forward, а ось Y сонаправленна upwards.

Quaternion.LookRotation(Vector3 forward, Vector3 upwards = Vector3.up);

Можно использовать, например, чтобы поворачивать персонажа к целе:

public class LookExample : MonoBehaviour
{ 
	public Transform target = null;

	void Update()
	{
		Vector3 relativePos = target.position - transform.position;
		transform.rotation = Quaternion.LookRotation(relativePos);
	}
}

Добавим интерполяцию и регулятор скорости для плавности:

public class LookExample : MonoBehaviour
{ 
	public Transform target = null;
	public float speed = 10f;

	void Update()
	{
		Vector3 relativePos = target.position - transform.position;
		transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(relativePos), Time.deltaTime * speed);
	}
}
На этом всё :)

97899