使用两个3D单位长度向量,有没有办法计算它们之间的单位长度向量,而无需重新标准化? (更具体地说,没有平方根)。
目前我只是添加它们并进行规范化,但为了提高效率,我认为可能有更好的方法。
(出于这个问题的目的,忽略两个向量正好相反的情况)
答案 0 :(得分:1)
首先,找到两个向量之间的角度。根据{{3}}的原则,我们知道
|a| * cos(theta) = a . b_hat
.
是scalar projection运算符,|a|
是a
的长度,theta
是a
和{{1}之间的角度} {},b
是b_hat
的规范化形式。
在您的情况下,a和b已经是单位向量,因此这简化为:
b
我们可以重新安排到:
cos(theta) = a . b
将矢量A和B端对端放置,并通过从第一个矢量的开头到第二个矢量的末尾绘制一条线来完成三角形。由于两边长度相等,我们知道三角形是等角线,所以如果你已经知道了θ,那么很容易确定所有的角度。
长度为N的那条线是中间向量。如果我们将它除以N,我们可以将其标准化。
从dot product,我们知道
theta = acos(a . b)
我们可以重新排列以获取
sin(theta/2)/1 = sin(180-theta)/N
请注意,如果A和B相等,则在计算N时将除以零,因此在开始之前检查角落情况可能会有用。
要点:
N = sin(180-theta) / sin(theta/2)
答案 1 :(得分:1)
这不是原始问题的答案;我宁愿尝试解决两个答案之间的问题,也不适合评论。
在{em> my 计算机(Linux,Intel Core i5)上使用平方根功能,trigonometric approach比原始版本慢4倍。您的里程会有所不同。
asm ("");
与他的兄弟姐妹volatile
和(void) x
一直是个难闻的气味。
多次运行紧密循环是一种非常不可靠的基准测试方法。
该怎么做?
分析生成的汇编代码,看看编译器实际对源代码做了什么。
使用分析器。我可以推荐perf
或Intel VTune
。
如果你看一下your micro-benchmark的汇编代码,你会看到编译器非常聪明,并且发现v1和v2没有改变和消除尽可能多的工作在编译时。在运行时,未对sqrtf
或acosf
和cosf
进行任何调用。这就解释了为什么你没有看到这两种方法之间有任何区别。
以下是您的基准测试的编辑版本。我把它加了一点,用1.0e-6f
防止零除零。 (它不会改变结论。)
#include <stdio.h>
#include <math.h>
#ifdef USE_NORMALIZE
#warning "Using normalize"
void mid_v3_v3v3_slerp(float res[3], const float v1[3], const float v2[3])
{
float m;
float v[3] = { (v1[0] + v2[0]), (v1[1] + v2[1]), (v1[2] + v2[2]) };
m = 1.0f / sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + 1.0e-6f);
v[0] *= m;
v[1] *= m;
v[2] *= m;
res[0] = v[0];
res[1] = v[1];
res[2] = v[2];
}
#else
#warning "Not using normalize"
void mid_v3_v3v3_slerp(float v[3], const float v1[3], const float v2[3])
{
const float dot_product = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
const float theta = acosf(dot_product);
const float n = 1.0f / (2.0f * cosf(theta * 0.5f) + 1.0e-6f);
v[0] = (v1[0] + v2[0]) * n;
v[1] = (v1[1] + v2[1]) * n;
v[2] = (v1[2] + v2[2]) * n;
}
#endif
int main(void)
{
unsigned long long int i = 20000000;
float v1[3] = {-0.8659117221832275, 0.4995948076248169, 0.024538060650229454};
float v2[3] = {0.7000154256820679, 0.7031427621841431, -0.12477479875087738};
float v[3] = { 0.0, 0.0, 0.0 };
while (--i) {
mid_v3_v3v3_slerp( v, v1, v2);
mid_v3_v3v3_slerp(v1, v, v2);
mid_v3_v3v3_slerp(v1, v2, v );
}
printf("done %f %f %f\n", v[0], v[1], v[2]);
return 0;
}
我使用gcc -ggdb3 -O3 -Wall -Wextra -fwhole-program -DUSE_NORMALIZE -march=native -static normal.c -lm
对其进行了编译,并使用perf
对代码进行了分析。
三角函数方法慢了4倍,这是因为昂贵的cosf
和acosf
函数。
我也测试了英特尔C ++编译器:icc -Ofast -Wall -Wextra -ip -xHost normal.c
;结论是一样的,尽管gcc生成的代码大约慢了10%(对于-Ofast
也是如此)。
我甚至不会尝试实现一个近似的sqrtf
:它已经是一个内在的可能性,你的近似只会更慢...
说了这些之后,我不知道原问题的答案。我想到了它,我也怀疑可能有另一种方式不涉及平方根功能。
理论上有趣的问题;在实践中,我怀疑摆脱那个平方根会对你的应用程序的速度产生任何影响。
答案 2 :(得分:0)
根据答案我做了一些速度比较。
修改的。通过这个nieve基准测试,GCC优化了三角法,两种方法的速度大致相同,请阅读@Ali的帖子以获得更完整的解释。
总之,使用重新标准化大约快4倍。
#include <stdio.h>
#include <math.h>
/* gcc mid_v3_v3v3_slerp.c -lm -O3 -o mid_v3_v3v3_slerp_a
* gcc mid_v3_v3v3_slerp.c -lm -O3 -o mid_v3_v3v3_slerp_b -DUSE_NORMALIZE
*
* time ./mid_v3_v3v3_slerp_a
* time ./mid_v3_v3v3_slerp_b
*/
#ifdef USE_NORMALIZE
#warning "Using normalize"
void mid_v3_v3v3_slerp(float v[3], const float v1[3], const float v2[3])
{
float m;
v[0] = (v1[0] + v2[0]);
v[1] = (v1[1] + v2[1]);
v[2] = (v1[2] + v2[2]);
m = 1.0f / sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
v[0] *= m;
v[1] *= m;
v[2] *= m;
}
#else
#warning "Not using normalize"
void mid_v3_v3v3_slerp(float v[3], const float v1[3], const float v2[3])
{
const float dot_product = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
const float theta = acosf(dot_product);
const float n = 1.0f / (2.0f * cosf(theta * 0.5f));
v[0] = (v1[0] + v2[0]) * n;
v[1] = (v1[1] + v2[1]) * n;
v[2] = (v1[2] + v2[2]) * n;
}
#endif
int main(void)
{
unsigned long long int i = 10000000000;
const float v1[3] = {-0.8659117221832275, 0.4995948076248169, 0.024538060650229454};
const float v2[3] = {0.7000154256820679, 0.7031427621841431, -0.12477479875087738};
float v[3];
while (--i) {
asm (""); /* prevent compiler from optimizing the loop away */
mid_v3_v3v3_slerp(v, v1, v2);
}
printf("done %f %f %f\n", v[0], v[1], v[2]);
return 0;
}