当前,我正在尝试使用Unity在c#中实现双重轮廓,但是我在QEF实现方面遇到麻烦。 QEF实现对于一个简单的多维数据集就可以很好地工作,但是只要我给它一个球体,它就会完全中断。
我发现解决此问题的方法是调整在github上其他QEF求解器中找到的"PSUEDO_INVERSE_THRESHOLD"
变量。但是,一旦提高此值,我就会失去所有敏锐的功能!但是,如果它太低,则任何光滑的东西(例如地形圆)都会完全破裂。我的QEF求解器在这里:
void svd_mul_matrix_vec(ref float4 result, float3x3 a, float4 b)
{
result.x = dot(float4(a[0][0], a[0][1], a[0][2], 0.0f), b);
result.y = dot(float4(a[1][0], a[1][1], a[1][2], 0.0f), b);
result.z = dot(float4(a[2][0], a[2][1], a[2][2], 0.0f), b);
result.w = 0.0f;
}
void givens_coeffs_sym(float a_pp, float a_pq, float a_qq, ref float c, ref float s)
{
if (a_pq == 0.0f)
{
c = 1.0f;
s = 0.0f;
return;
}
float tau = (a_qq - a_pp) / (2.0f * a_pq);
float stt = sqrt(1.0f + tau * tau);
float tan = 1.0f / ((tau >= 0.0f) ? (tau + stt) : (tau - stt));
c = rsqrt(1.0f + tan * tan);
s = tan * (c);
}
void svd_rotate_xy(ref float x, ref float y, float c, float s)
{
float u = x; float v = y;
x = c * u - s * v;
y = s * u + c * v;
}
void svd_rotateq_xy(ref float x, ref float y, ref float a, float c, float s)
{
float cc = c * c; float ss = s * s;
float mx = 2.0f * c * s * (a);
float u = x; float v = y;
x = cc * u - mx + ss * v;
y = ss * u + mx + cc * v;
}
void svd_rotate(ref float3x3 vtav, ref float3x3 v, int a, int b)
{
if (vtav[a][b] == 0.0) return;
float c = 0, s = 0;
givens_coeffs_sym(vtav[a][a], vtav[a][b], vtav[b][b], ref c, ref s);
float x, y, z;
x = vtav[a][a]; y = vtav[b][b]; z = vtav[a][b];
svd_rotateq_xy(ref x, ref y, ref z, c, s);
vtav[a][a] = x; vtav[b][b] = y; vtav[a][b] = z;
x = vtav[0][3 - b]; y = vtav[1 - a][2];
svd_rotate_xy(ref x, ref y, c, s);
vtav[0][3 - b] = x; vtav[1 - a][2] = y;
vtav[a][b] = 0.0f;
x = v[0][a]; y = v[0][b];
svd_rotate_xy(ref x, ref y, c, s);
v[0][a] = x; v[0][b] = y;
x = v[1][a]; y = v[1][b];
svd_rotate_xy(ref x, ref y, c, s);
v[1][a] = x; v[1][b] = y;
x = v[2][a]; y = v[2][b];
svd_rotate_xy(ref x, ref y, c, s);
v[2][a] = x; v[2][b] = y;
}
void svd_solve_sym(float3x3 a, ref float4 sigma, float3x3 v)
{
// assuming that A is symmetric: can optimize all operations for
// the upper right triagonal
float3x3 vtav = 0;
vtav[0][0] = a.c0.x; vtav[0][1] = a.c1.x; vtav[0][2] = a.c2.x;
vtav[1][0] = 0.0f; vtav[1][1] = a.c1.y; vtav[1][2] = a.c2.y;
vtav[2][0] = 0.0f; vtav[2][1] = 0.0f; vtav[2][2] = a.c2.z;
// assuming V is identity: you can also pass a matrix the rotations
// should be applied to. (U is not computed)
for (int i = 0; i < 5; ++i)
{
svd_rotate(ref vtav, ref v, 0, 1);
svd_rotate(ref vtav, ref v, 0, 2);
svd_rotate(ref vtav, ref v, 1, 2);
}
sigma = float4(vtav[0][0], vtav[1][1], vtav[2][2], 0.0f);
}
float svd_invdet(float x, float tol)
{
return (abs(x) < tol || abs(1.0f / x) < tol) ? 0.0f : (1.0f / x);
}
void svd_pseudoinverse(ref float3x3 o, float4 sigma, float3x3 v)
{
float d0 = svd_invdet(sigma.x, 10000f);
float d1 = svd_invdet(sigma.y, 10000f);
float d2 = svd_invdet(sigma.z, 10000f);
o[0][0] = v[0][0] * d0 * v[0][0] + v[0][1] * d1 * v[0][1] + v[0][2] * d2 * v[0][2];
o[0][1] = v[0][0] * d0 * v[1][0] + v[0][1] * d1 * v[1][1] + v[0][2] * d2 * v[1][2];
o[0][2] = v[0][0] * d0 * v[2][0] + v[0][1] * d1 * v[2][1] + v[0][2] * d2 * v[2][2];
o[1][0] = v[1][0] * d0 * v[0][0] + v[1][1] * d1 * v[0][1] + v[1][2] * d2 * v[0][2];
o[1][1] = v[1][0] * d0 * v[1][0] + v[1][1] * d1 * v[1][1] + v[1][2] * d2 * v[1][2];
o[1][2] = v[1][0] * d0 * v[2][0] + v[1][1] * d1 * v[2][1] + v[1][2] * d2 * v[2][2];
o[2][0] = v[2][0] * d0 * v[0][0] + v[2][1] * d1 * v[0][1] + v[2][2] * d2 * v[0][2];
o[2][1] = v[2][0] * d0 * v[1][0] + v[2][1] * d1 * v[1][1] + v[2][2] * d2 * v[1][2];
o[2][2] = v[2][0] * d0 * v[2][0] + v[2][1] * d1 * v[2][1] + v[2][2] * d2 * v[2][2];
}
void svd_solve_ATA_ATb(
float3x3 ATA,
float4 ATb,
ref float4 x)
{
float3x3 V = 0;
V[0][0] = 1.0f; V[0][1] = 0.0f; V[0][2] = 0.0f;
V[1][0] = 0.0f; V[1][1] = 1.0f; V[1][2] = 0.0f;
V[2][0] = 0.0f; V[2][1] = 0.0f; V[2][2] = 1.0f;
float4 sigma = 0;
svd_solve_sym(ATA, ref sigma, V);
// A = UEV^T; U = A / (E*V^T)
float3x3 Vinv = 1;
svd_pseudoinverse(ref Vinv, sigma, V);
svd_mul_matrix_vec(ref x, Vinv, ATb);
}
void svd_vmul_sym(ref float4 result, float3x3 A, float4 v)
{
float4 A_row_x = float4(A.c0.x, A.c1.x, A.c2.x,0);//{ A[0], A[1], A[2], 0.f };
result.x = dot(A_row_x, v);
result.y = A.c1.x * v.x + A.c1.y * v.y + A.c2.y * v.z;
result.z = A.c2.x * v.x + A.c2.y * v.y + A.c2.z * v.z;
}
// QEF
////////////////////////////////////////////////////////////////////////////////
void qef_add(
float4 n, float4 p,
ref float3x3 ATA,
ref float4 ATb,
ref float4 pointaccum)
{
ATA.c0.x += n.x * n.x;
ATA.c1.x += n.x * n.y;
ATA.c2.x += n.x * n.z;
ATA.c1.y += n.y * n.y;
ATA.c2.y += n.y * n.z;
ATA.c2.z += n.z * n.z;
float b = dot(p, n);
ATb.x += n.x * b;
ATb.y += n.y * b;
ATb.z += n.z * b;
pointaccum.x += p.x;
pointaccum.y += p.y;
pointaccum.z += p.z;
pointaccum.w += 1.0f;
}
float qef_calc_error(float3x3 A, float4 x, float4 b)
{
float4 tmp = 0;
svd_vmul_sym(ref tmp, A, x);
tmp = b - tmp;
return dot(tmp, tmp);
}
float qef_solve(
float3x3 ATA,
float4 ATb,
float4 pointaccum,
ref float4 x)
{
float4 masspoint = pointaccum / pointaccum.w;
float4 A_mp = 0;
svd_vmul_sym(ref A_mp, ATA, masspoint);
A_mp = ATb - A_mp;
svd_solve_ATA_ATb(ATA, A_mp, ref x);
float error = qef_calc_error(ATA, x, ATb);
x += masspoint;
return error;
}
float4 qef_solve_from_points(
NativeSlice<float4> positions,
NativeSlice<float4> normals,
int count,
ref float error)
{
float4 pointaccum = 0;
float4 ATb = 0;
float3x3 ATA = 0;
for (int i= 0; i<count; ++i) {
qef_add(normals[i], positions[i], ref ATA, ref ATb, ref pointaccum);
}
float4 solved_position = 0;
error = qef_solve(ATA, ATb, pointaccum, ref solved_position);
return solved_position;
}