Я поворачиваю своего игрового персонажа, чтобы посмотреть на цель, используя следующий код:
transform.rotation = Quaternion.Slerp(startQuaternion, lookQuaternion, turningNormalizer*turningSpeed/10f)
startQuaternion - это текущее вращение персонажа, когда дается новая цель.
lookQuaternion - это направление, в котором должен смотреть персонаж, и оно установлено так:
destinationVector = currentWaypoint.transform.position - transform.position;
lookQuaternion = Quaternion.LookRotation(destinationVector, Vector3.up);
turnNormalizer просто Time.deltaTime
увеличивается и turningSpeed
является статическим значением, заданным в редакторе.
Проблема в том, что, хотя персонаж поворачивается, как и должно быть, большую часть времени, у него есть проблемы, когда он должен приблизиться к 180 градусам. Тогда это иногда дрожит и отражает вращение:
На этом плохо нарисованном изображении персонаж (справа) начинает поворачиваться к кругу слева. Вместо того, чтобы просто повернуть налево или направо, начинается «зеркальный танец»:
- Он начинает вращаться в направлении новой облицовки
- Затем он внезапно поворачивается на тот же угол, но на другой стороне и продолжает вращаться
Он делает это «зеркальное отображение» так долго, пока не смотрит на цель. Это что-то с кватернионами, сползанием / лепрпингом или чем-то еще?
РЕДАКТИРОВАТЬ 1: Очевидно, проблема не возникает из-за самого вращения. Более вероятная проблема заключается в том, что персонаж движется по направлению к лицу во время его вращения. Ограничение угла между лицом и целью, когда персонажу позволено двигаться, иногда уменьшает вращение дрожания / отражения.
Это, конечно, вызывает больше вопросов, почему персонаж не может двигаться и вращаться одновременно без проблем? Код, используемый для движения:
transform.Translate(Vector3.forward * runningSpeed/10f * Time.deltaTime);
Ответы:
Каждая ориентация в трехмерном пространстве может быть представлена двумя отдельными единичными кватернионами
q
и-q
(по компонентам отрицаетсяq
). Например, ориентация, представленная единичной матрицей 3х3,I
может быть представлена двумя кватернионами:Оба представляют одинаковую ориентацию в трехмерном пространстве, их точечное произведение точно
-1
, каждый из них лежит в другом полушарии, точно на противоположных сторонах гиперсферы.Результат, который вы наблюдаете, когда slerp берет более длинную дугу для вращения, это когда slerping между 2 кватернионами, которые не лежат в одном и том же полушарии. Если это произойдет, просто отвергните одного из них перед тем, как его сперлинговать, тогда сперльса займет более короткую дугу.
Точечный продукт - простой инструмент, чтобы узнать, происходит ли это. Если скалярное произведение обоих кватернионов меньше, чем
0
, то они не лежат в одном и том же полушарии. Таким образом, если скалярное произведение у них обоих меньше0
, просто по компонентам отрицаем другой кватернион перед тем, как сваливать их.источник
if(Quaternion.Dot (lookQuaternion, startQuaternion) < 0) { startQuaternion = Quaternion.Inverse(startQuaternion); }
но это не решило проблему. Извините за плохое форматирование, я не могу приручить этот раздел комментариев.Inverse
совсем другая операцияNegate
.if(Quaternion.Dot (lookQuaternion, q) < 0) { q.x = -q.x; q.y = -q.y; q.z = -q.z; q.w = -q.w; }
Я не использую C #, но, судя по документу, вы также можете использовать функцию Negate напрямую.if(Quaternion.Dot (lookQuaternion, startQuaternion) < 0) { startQuaternion = Quaternion.Negate(startQuaternion); }
Ну, ваша проблема в том, что эти два вектора, которые образуют ваши кватернионы, составляют 180 градусов, поэтому вопрос, который следует задать при интерполяции, какую дугу он должен принять? верхняя дуга или нижняя ??
Это то, что в основном вызывает зеркальное отображение, каждый раз, когда вы интерполируете, оно идет наоборот, сохраняя при этом угол.
По этой ссылке .
Что вам нужно сделать, это интерполировать ваш начальный кватернион с промежуточным кватернионом (чтобы определить, какую дугу взять), и когда достигните, используйте промежуточный кватернион, чтобы перейти к фактическому целевому кватерниону.
источник
Я подозреваю, что проблема, которую вы видите, не имеет ничего общего с вашим кодом, а скорее является фундаментальной проблемой с интерполяцией по поверхности сферы (в данном случае для viewDirection). Между (почти) любыми двумя точками на сфере есть один большой круг - например, произвольно фиксируя нашу «начальную» точку на северном полюсе, большой круг будет меридианом, на котором лежит конечная точка. Интерполяция из одной точки в другую перемещается по этому большому кругу.
Теперь, для большинства точек на сфере, близлежащие точки соответствуют близким большим кругам; например, меридианы, на которых лежат Лос-Анджелес и Феникс, довольно близки друг к другу. Но когда пункт назначения является антиподом исходной точки - с южного полюса к северному полюсу начальной точки - больше нет ни одного большого круга через них обоих, а вместо этого все большие круги одного проходят через другой. Что еще хуже, это означает, что точки около южного полюса не являются «большинством точек»; две точки рядом друг с другом и у южного полюса могут иметь сильно расходящиеся меридианы.
Я подозреваю, что то, что вы видите, является проявлением этой нестабильности в меридиане - или, другими словами, дуге интерполяции. Когда цель взгляда находится непосредственно за персонажем, то такие вещи, как неточность первого порядка в вашей «интеграции Эйлера» в позиции персонажа и способ изменения направления взгляда от кадра к кадру, приведут к этому дикому различные дуги интерполяции от одного кадра к другому, и это, вероятно, приведет к «шатанию», которое вы видите.
Что касается того, что с этим делать, самое лучшее, что приходит на ум, это не пересчитывать «целевое» направление взгляда в каждом кадре; вместо этого используйте один и тот же для диапазона нескольких кадров, затем вычислите новый и используйте его для нескольких кадров и т. д. При необходимости вы можете даже интерполировать от одной цели к следующей по промежутку в несколько кадров, чтобы направление движения не меняется слишком резко.
источник