Надежный atan (y, x) на GLSL для преобразования координаты XY в угол
В GLSL (в частности, 3,00, который я использую) есть две версии
atan()
: atan(y_over_x)
может возвращать только углы между -PI/2, PI/2, в то время как atan(y/x)
может принимать во внимание все 4 квадранта, поэтому диапазон углов охватывает все: -PI, PI, что очень похоже на atan2()
в С++.
Я хотел бы использовать второй atan
для преобразования координат XY в угол.
Однако atan()
в GLSL, к тому же неспособный обрабатывать, когда x = 0
, не очень стабилен. Особенно там, где x
близок к нулю, деление может переполняться, что приводит к противоположному результирующему углу (вы получаете что-то близкое к -PI/2, где вы предполагаете получить приблизительно PI/2).
Какая хорошая, простая реализация, которую мы можем построить поверх GLSL atan(y,x)
, чтобы сделать ее более надежной?
Ответы
Ответ 1
Я собираюсь ответить на свой вопрос, чтобы поделиться своими знаниями. Прежде всего заметим, что неустойчивость возникает, когда x
находится вблизи нуля. Однако мы можем также перевести это как abs(x) << abs(y)
. Итак, сначала мы разделим плоскость (предполагая, что мы находимся на единичном круге) на две области: одну, где |x| <= |y|
и другую, где |x| > |y|
, как показано ниже:
![two regions]()
Мы знаем, что atan(x,y)
является гораздо более устойчивым в зеленой области - когда x близок к нулю, мы просто имеем что-то близкое к atan (0.0), которое очень устойчиво численно, тогда как обычный atan(y,x)
более устойчив в оранжевой области. Вы также можете убедить себя, что эти отношения:
atan(x,y) = PI/2 - atan(y,x)
выполняется для всех non-origin (x, y), где это undefined, и мы говорим о atan(y,x)
, который может вернуть значение угла во всем диапазоне -PI, PI, а не atan(y_over_x)
который только возвращает угол между -PI/2, PI/2. Поэтому наша надежная процедура atan2()
для GLSL довольно проста:
float atan2(in float y, in float x)
{
bool s = (abs(x) > abs(y));
return mix(PI/2.0 - atan(x,y), atan(y,x), s);
}
В качестве побочной заметки тождество для математической функции atan(x)
на самом деле:
atan(x) + atan(1/x) = sgn(x) * PI/2
что верно, потому что его диапазон равен (-PI/2, PI/2).
![graph]()
Ответ 2
Ваше предложенное решение все еще не работает в случае x=y=0
. Здесь обе функции atan()
возвращают NaN.
Далее я бы не стал полагаться на микс, чтобы переключаться между двумя случаями. Я не уверен, как это реализовано/скомпилировано, но правила float IEEE для x * NaN и x + NaN снова приводятся в NaN. Поэтому, если ваш компилятор действительно использовал микс/интерполяцию, результат должен быть NaN для x=0
или y=0
.
Вот еще одно решение, которое решило проблему для меня:
float atan2(in float y, in float x)
{
return x == 0.0 ? sign(y)*PI/2 : atan(y, x);
}
При x=0
угол может быть ± π/2. Какая из двух зависит только от y
. Если y=0
тоже, угол может быть произвольным (вектор имеет длину 0). sign(y)
возвращает 0
в этом случае, это нормально.
Ответ 3
В зависимости от вашей целевой платформы это может быть проблемой. Спецификация OpenGL для atan (y, x) указывает, что она должна работать во всех квадрантах, оставляя поведение undefined только тогда, когда x и y равны 0.
Итак, было бы ожидать, что любая достойная реализация будет стабильной вблизи всех осей, так как это целая цель для 2-аргумента atan (или atan2).
Ответчик/ответчик правилен в том, что некоторые реализации делают быстрые клавиши. Однако принятое решение делает предположение, что плохая реализация всегда будет неустойчивой, когда x близок к нулю: на каком-то оборудовании (например, у моей Galaxy S4) значение стабильно, когда x близок к нулю, но неустойчиво, когда y равно около нуля.
Чтобы протестировать реализацию рендеринга GLSL atan(y,x)
, здесь тестовый шаблон WebGL. Следуйте приведенной ниже ссылке и до тех пор, пока ваша реализация OpenGL будет достойной, вы должны увидеть что-то вроде этого:
![GLSL atan(y,x) test pattern]()
Тестовый шаблон с использованием native atan(y,x)
: http://glslsandbox.com/e#26563.2
Если все хорошо, вы должны увидеть 8 различных цветов (игнорируя центр).
Связанные демо-образцы atan(y,x)
для нескольких значений x и y, включая 0, очень большие и очень маленькие значения. Центральный блок atan(0.,0.)
- undefined математически, а реализации различаются. Я видел 0 (красный), PI/2 (зеленый) и NaN (черный) на оборудовании, которое я тестировал.
Здесь тестовая страница для принятого решения. Примечание: в версии WebGL хоста отсутствует mix(float,float,bool)
, поэтому я добавил реализацию, соответствующую спецификации.
Тестовый шаблон с использованием atan2(y,x)
из принятого ответа: http://glslsandbox.com/e#26666.0
Ответ 4
Иногда лучший способ улучшить производительность фрагмента кода - это не называть его в первую очередь. Например, одной из причин, по которой вы можете определить угол вектора, является то, что вы можете использовать этот угол для построения матрицы вращения с использованием комбинаций угла синуса и косинуса. Однако синус и косинус вектора (относительно начала координат) уже скрыты в прямом направлении внутри самого вектора. Все, что вам нужно сделать, это создать нормализованную версию вектора, разделив каждую векторную координату на общую длину вектора. Здесь двумерный пример вычисления синуса и косинуса угла вектора [x y]:
double length = sqrt(x*x + y*y);
double cos = x / length;
double sin = y / length;
После того, как у вас есть значения синуса и косинуса, теперь вы можете напрямую заполнить матрицу вращения этими значениями для вращения по часовой стрелке или против часовой стрелки произвольных векторов на один и тот же угол, или вы можете объединить вторую матрицу вращения, чтобы вращать ее угол, отличный от нуля. В этом случае вы можете представить матрицу вращения как "нормализующую" угол к нулю для произвольного вектора. Этот подход можно расширить и в трехмерном (или N-мерном) случае, хотя, например, у вас будет три угла и шесть пар sin/cos для вычисления (один угол на плоскость) для 3D-вращения.
В ситуациях, когда вы можете использовать этот подход, вы получаете большой выигрыш, полностью обходя вычисления атана, что возможно, поскольку единственная причина, по которой вы хотели определить угол, заключалась в вычислении значений синуса и косинуса. Пропуская преобразование в угловое пространство и обратно, вы не только избегаете беспокоиться о делении на ноль, но также улучшаете точность углов, которые находятся вблизи полюсов, и в противном случае они пострадали бы от умножения/деления на большие числа. Я успешно использовал этот подход в программе GLSL, которая поворачивает сцену до нуля градусов, чтобы упростить вычисление.
Легко попасть в непосредственную проблему, чтобы вы могли упустить из виду, почему вам нужна эта информация в первую очередь. Не то, чтобы это срабатывало в каждом случае, но иногда это помогает думать из коробки...
Ответ 5
Формула, которая дает угол в четырех квадрантах для любого значения
координат х и у. Для x = y = 0 результат не определен.
f (x, y) = pi() -pi()/2 * (1 + знак (x)) * (1-знак (y ^ 2)) -pi()/4 * (2 + знак ( х)) * знак (у)
-sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))