我有一个大世界,大约5,000,000 x 1,000,000单位。相机可以靠近某个物体或足够远,以便看到整个世界。
我通过unprojecting(Z来自深度缓冲区)获得世界坐标中的鼠标位置。
问题是它涉及矩阵逆。当使用大数字和小数字(例如,从原点平移并缩放以查看更多世界)时,计算变得不稳定。
试图看看这个逆矩阵的准确性,我看一下行列式。理想情况下,由于转换矩阵的性质,它永远不会为零。我知道正在' det'一个小值本身没有任何意义,它可能是由于矩阵中的值很小。但它也可能是数字出错的标志。
我也知道我可以通过反转每个变换并乘以它们来计算逆。它提供更准确吗?
如何判断我的矩阵是否退化,是否存在数值问题?
答案 0 :(得分:4)
初学者请参阅Understanding 4x4 homogenous transform matrices
提高累积矩阵的准确度(标准化)
要避免退化变换矩阵选择一个轴作为主轴。我通常选择Z
,因为它通常是我的应用中的查看或前进方向。然后利用交叉乘积来重新计算/标准化其余的轴(它们应该彼此垂直,除非使用比例,然后是单位大小)。这只能用于正交/正交矩阵,因此不会出现偏斜或投影......
每次操作后都不需要执行此操作,只需在每个矩阵上执行操作计数器,如果超过某个阈值,则将其标准化并重置计数器。
要检测此类矩阵的退化,您可以通过任意两个轴之间的点积测试正交性(应该为零或非常靠近它)。对于正交矩阵,您还可以测试轴方向矢量的单位大小......
以下是 C ++ 中我的变换矩阵规范化( orthonormal 矩阵)的样子:
double reper::rep[16]; // this is my transform matrix stored as member in `reper` class
//---------------------------------------------------------------------------
void reper::orto(int test) // test is for overiding operation counter
{
double x[3],y[3],z[3]; // space for axis direction vectors
if ((cnt>=_reper_max_cnt)||(test)) // if operations count reached or overide
{
axisx_get(x); // obtain axis direction vectors from matrix
axisy_get(y);
axisz_get(z);
vector_one(z,z); // Z = Z / |z|
vector_mul(x,y,z); // X = Y x Z ... perpendicular to y,z
vector_one(x,x); // X = X / |X|
vector_mul(y,z,x); // Y = Z x X ... perpendicular to z,x
vector_one(y,y); // Y = Y / |Y|
axisx_set(x); // copy new axis vectors into matrix
axisy_set(y);
axisz_set(z);
cnt=0; // reset operation counter
}
}
//---------------------------------------------------------------------------
void reper::axisx_get(double *p)
{
p[0]=rep[0];
p[1]=rep[1];
p[2]=rep[2];
}
//---------------------------------------------------------------------------
void reper::axisx_set(double *p)
{
rep[0]=p[0];
rep[1]=p[1];
rep[2]=p[2];
cnt=_reper_max_cnt; // pend normalize in next operation that needs it
}
//---------------------------------------------------------------------------
void reper::axisy_get(double *p)
{
p[0]=rep[4];
p[1]=rep[5];
p[2]=rep[6];
}
//---------------------------------------------------------------------------
void reper::axisy_set(double *p)
{
rep[4]=p[0];
rep[5]=p[1];
rep[6]=p[2];
cnt=_reper_max_cnt; // pend normalize in next operation that needs it
}
//---------------------------------------------------------------------------
void reper::axisz_get(double *p)
{
p[0]=rep[ 8];
p[1]=rep[ 9];
p[2]=rep[10];
}
//---------------------------------------------------------------------------
void reper::axisz_set(double *p)
{
rep[ 8]=p[0];
rep[ 9]=p[1];
rep[10]=p[2];
cnt=_reper_max_cnt; // pend normalize in next operation that needs it
}
//---------------------------------------------------------------------------
矢量操作如下所示:
void vector_one(double *c,double *a)
{
double l=divide(1.0,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
c[0]=a[0]*l;
c[1]=a[1]*l;
c[2]=a[2]*l;
}
void vector_mul(double *c,double *a,double *b)
{
double q[3];
q[0]=(a[1]*b[2])-(a[2]*b[1]);
q[1]=(a[2]*b[0])-(a[0]*b[2]);
q[2]=(a[0]*b[1])-(a[1]*b[0]);
for(int i=0;i<3;i++) c[i]=q[i];
}
提高非累积矩阵的准确性
您唯一的选择是使用至少double
矩阵的准确度。最安全的是使用 GLM 或至少基于double
数据类型(例如我的reper
类)的您自己的矩阵数学。
廉价替代方案是使用double
精度函数,如
glTranslated
glRotated
glScaled
...
在某些情况下有帮助但不安全,因为 OpenGL 实施可以将其截断为float
。此外,还没有64位 HW 插值器,因此管道阶段之间的所有迭代结果都被截断为float
s。
有时相对参考帧有帮助(因此保持对类似幅度值的操作)例如见:
此外,如果您使用自己的矩阵数学函数,您还必须考虑操作的顺序,这样您总是会失去最小的精度。
伪逆矩阵
在某些情况下,您可以避免通过行列式或Horner方案或高斯消元法计算逆矩阵,因为在某些情况下,您可以利用正交旋转矩阵的转置也是其逆这一事实。以下是它的完成方式:
void matrix_inv(GLfloat *a,GLfloat *b) // a[16] = Inverse(b[16])
{
GLfloat x,y,z;
// transpose of rotation matrix
a[ 0]=b[ 0];
a[ 5]=b[ 5];
a[10]=b[10];
x=b[1]; a[1]=b[4]; a[4]=x;
x=b[2]; a[2]=b[8]; a[8]=x;
x=b[6]; a[6]=b[9]; a[9]=x;
// copy projection part
a[ 3]=b[ 3];
a[ 7]=b[ 7];
a[11]=b[11];
a[15]=b[15];
// convert origin: new_pos = - new_rotation_matrix * old_pos
x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
a[12]=-x;
a[13]=-y;
a[14]=-z;
}
因此矩阵的旋转部分被转置,投影保持原样并重新计算原点位置,因此A*inverse(A)=unit_matrix
编写此函数,以便它可以作为就地使用,因此调用
GLfloat a[16]={values,...}
matrix_inv(a,a);
也会产生有效的结果。这种计算Inverse的方式更快更安全,因为它可以减少操作(没有递归或减少没有划分)。粗这仅适用于正交均匀4x4矩阵!!! *
检测错误的反向
因此,如果你得到矩阵A
及其倒数B
,那么:
A*B = C = ~unit_matrix
因此,将两个矩阵相乘并检查单位矩阵......
C
的所有非对角线元素的abs总和应接近0.0
C
的所有对角线元素都应接近+1.0
答案 1 :(得分:0)
在一些实验之后,我看到(说到变换,而不是任何矩阵)矩阵的对角线(即比例因子)(m
,在反转之前)是决定性值的主要因素。
所以我将产品p= m[0] · m[5] · m[10] · m[15]
(如果所有这些都是!= 0)与行列式进行比较。如果他们相似0.1 < p/det < 10
我可以&#34;信任&#34;不知何故,在逆矩阵中。否则,我有数字问题,建议改变渲染策略。