Rip*_*pi2 5 opengl math matrix
我有一个大世界,大约5,000,000 x 1,000,000单位.相机可以靠近某个物体或足够远,以便看到整个世界.
我通过unprojecting(Z来自深度缓冲区)获得世界坐标中的鼠标位置.问题是它涉及矩阵逆.当使用大数字和小数字(例如,从原点平移并缩放以查看更多世界)时,计算变得不稳定.
试图看到这个逆矩阵的准确性,我看一下行列式.理想情况下,由于转换矩阵的性质,它永远不会为零.我知道,'det'一个小值本身并不意味着它,它可能是由于矩阵中的值很小.但它也可能是数字出错的标志.
我也知道我可以通过反转每个变换并乘以它们来计算逆.它提供更准确吗?
如何判断我的矩阵是否退化,是否存在数值问题?
对于初学者,请参阅了解 4x4 齐次变换矩阵
提高累积矩阵的准确性(归一化)
为了避免变换矩阵的退化,选择一个轴为主。我通常选择,Z因为它通常是我的应用程序中的视图或前进方向。然后利用叉积来重新计算/归一化其余的轴(它们应该彼此垂直,除非使用比例尺,否则也是单位尺寸)。这只能用于正交矩阵,因此没有偏斜或投影......正交矩阵必须缩放到正交矩阵,然后倒置,然后再缩放以使其可用。
您不需要在每次操作后都执行此操作,只需在每个矩阵上进行操作的计数器,如果超过某个阈值,则对其进行归一化并重置计数器。
要检测此类矩阵的退化,您可以通过任意两个轴(应该为零或非常接近)之间的点积来测试正交性。对于正交矩阵,您还可以测试轴方向向量的单位大小......
这是我的变换矩阵归一化在C++ 中的样子(对于正交矩阵):
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
}
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
向量操作如下所示:
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];
}
Run Code Online (Sandbox Code Playgroud)
提高非累积矩阵的准确性
您唯一的选择是至少使用double矩阵的准确性。最安全的是使用GLM或您自己的矩阵数学至少基于double数据类型(如我的reper班级)。
便宜的替代方法是使用double精度函数,如
glTranslated
glRotated
glScaled
...
Run Code Online (Sandbox Code Playgroud)
这在某些情况下会有所帮助,但并不安全,因为OpenGL实现可以将其截断为float. 此外,还没有 64 位硬件插值器,因此流水线级之间的所有迭代结果都被截断为floats。
有时相对参考系会有所帮助(因此请保持对相似幅度值的操作),例如参见:
此外,如果您使用自己的矩阵数学函数,您还必须考虑运算顺序,因此您总是会损失尽可能小的准确度。
伪逆矩阵
在某些情况下,您可以避免通过行列式或霍纳方案或高斯消元法计算逆矩阵,因为在某些情况下,您可以利用正交旋转矩阵的转置也是其逆矩阵的事实。这是如何完成的:
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;
}
Run Code Online (Sandbox Code Playgroud)
因此矩阵的旋转部分被转置,投影保持原样并重新计算原点位置,因此A*inverse(A)=unit_matrix编写此函数以便它可以就地使用,因此调用
GLfloat a[16]={values,...}
matrix_inv(a,a);
Run Code Online (Sandbox Code Playgroud)
也导致有效的结果。这种计算 Inverse 的方法更快且数值更安全,因为它挂起的操作更少(没有递归或减少没有除法)。粗略这仅适用于正交同质 4x4 矩阵!!!*
检测错误的逆
因此,如果您有矩阵A及其逆矩阵,B则:
A*B = C = ~unit_matrix
Run Code Online (Sandbox Code Playgroud)
因此,将两个矩阵相乘并检查单位矩阵...
C应该接近于0.0C应该接近+1.0