我在Android上使用Vuforia进行AR开发。我们可以使用
获取modelViewMatrix
Matrix44F modelViewMatrix_Vuforia = Tool.convertPose2GLMatrix(trackableResult.getPose());
这很有效。任何几何乘以此矩阵,然后乘以投影矩阵,都会按预期显示在屏幕上,(0,0,0)位于被跟踪目标的中心。
但我还想做的是同时绘制相对于用户设备的几何图形,所以要实现这一点,我们可以使用以下方法计算逆模型ViewMatrix:
Matrix44F inverseMV = SampleMath.Matrix44FInverse(invTranspMV);
Matrix44F invTranspMV = SampleMath.Matrix44FTranspose(modelViewMatrix_Vuforia);
modelViewMatrixInverse = invTranspMV.getData();
这非常有效,例如如果我使用这个矩阵绘制一个立方体,那么当我上下倾斜手机时,立方体也会正确地上下倾斜,但是当我左右转动时会出现问题。左转会导致立方体转向错误的方式,就好像我正在寻找它的右侧。与右转相似。应该发生的事情是,立方体应该看起来“卡在”屏幕上,即我转向哪个方向我应该能够看到同样的脸“卡在”屏幕上。
我认为问题可能与Vuforia投影矩阵有关,我将创建自己的投影矩阵(使用指南here)来试验不同的设置。正如this帖子所说,它可能与特定设备的内在相机校准有关。
我是否在正确的轨道上?任何可能出错的想法以及我如何解决这个问题?
更新
我认为它不再是投影矩阵(由于实验和皮尔迪的答案评论如下)
看了this帖后,我觉得我已经取得了一些进展。我现在使用以下代码:
Matrix44F modelViewMatrix_Vuforia = Tool.convertPose2GLMatrix(trackableResult.getPose());
Matrix44F inverseMV = SampleMath.Matrix44FInverse(modelViewMatrix_Vuforia);
Matrix44F invTranspMV = SampleMath.Matrix44FTranspose(inverseMV);
modelViewMatrixInverse = invTranspMV.getData();
float [] position = {0, 0, 0, 1};
float [] lookAt = {0, 0, 1, 0};
float [] cam_position = new float[16];
float [] cam_lookat = new float[16];
Matrix.multiplyMV(cam_position, 0, modelViewMatrixInverse, 0, position, 0);
Matrix.multiplyMV(cam_lookat, 0, modelViewMatrixInverse, 0, lookAt, 0);
Log.v("QCV", "posx = " + cam_position[0] + ", posy = " + cam_position[1] + ", posz = " + cam_position[2]);
Log.v("QCV", "latx = " + cam_lookat[0] + ", laty = " + cam_lookat[1] + ", latz = " + cam_lookat[2]);
当您将相机移动到目标位置时,这会成功返回相机位置和相机的法线。我想我应该可以用它来按照我想要的方式投影几何体。如果有效,将在以后更新。
UPDATE2
好的,取得了一些进展。我现在使用以下代码。它与前一个代码块完全相同,但使用Matrix类而不是SampleMath类。
float [] temp = new float[16];
temp = modelViewMatrix_Vuforia.getData();
Matrix.invertM(modelViewMatrixInverse, 0, temp, 0);
float [] position = {0, 0, 0, 1};
float [] lookAt = {0, 0, 1, 0};
float [] cam_position = new float[16];
float [] cam_lookat = new float[16];
Matrix.multiplyMV(cam_position, 0, modelViewMatrixInverse, 0, position, 0);
Matrix.multiplyMV(cam_lookat, 0, modelViewMatrixInverse, 0, lookAt, 0);
Log.v("QCV", "posx = " + cam_position[0] / kObjectScale + ", posy = " + cam_position[1] / kObjectScale + ", posz = " + cam_position[2] / kObjectScale);
Log.v("QCV", "latx = " + cam_lookat[0] + ", laty = " + cam_lookat[1] + ", latz = " + cam_lookat[2]);
下一段代码给出(几乎)所需的结果:
modelViewMatrix = modelViewMatrix_Vuforia.getData();
Matrix.translateM(modelViewMatrix, 0, 0, 0, kObjectScale);
Matrix.scaleM(modelViewMatrix, 0, kObjectScale, kObjectScale, kObjectScale);
line.setVerts(cam_position[0] / kObjectScale,
cam_position[1] / kObjectScale,
cam_position[2] / kObjectScale,
cam_position[0] / kObjectScale + 0.5f,
cam_position[1] / kObjectScale + 0.5f,
cam_position[2] / kObjectScale - 30);
这定义沿着负z轴的线,其位置矢量等于摄像机位置(根据实际物理设备的位置计算)。由于矢量是正常的,我已经偏移了X / Y,因此实际上可以看到法线。
当您重新定位物理设备时,法线随您移动。太好了!
但是,将手机保持在相同位置,但是向前/向后倾斜手机或向左/向右转动,线路不会保持在相机显示屏中的中心位置。我想要的效果是当我倾斜/转动时,线在世界空间中旋转,这样在相机/屏幕空间中线条看起来正常并且是物理显示的中心。
注意 - 您可能想知道我为什么不使用以下内容:
line.setVerts(cam_position[0] / kObjectScale,
cam_position[1] / kObjectScale,
cam_position[2] / kObjectScale,
cam_position[0] / kObjectScale + cam_lookat[0] * 30,
cam_position[1] / kObjectScale + cam_lookat[1] * 30,
cam_position[2] / kObjectScale + cam_lookat[2] * 30);
简单的答案是我尝试过它不起作用!所有这一切都实现了线的一端保持原样,而另一端指向屏幕设备的方向正常。我们需要的是根据从cam_lookat获得的角度在世界空间中旋转线条,以使线条实际出现在相机前面的中心位置并垂直于相机。
下一阶段是根据从cam_lookat单位矢量计算的角度调整世界空间中线的位置。这些可以用来更新线的顶点,这样无论你定向手机的方向,法线总是出现在相机的中心。
我认为这是正确的方法。如果有效,我会再次更新!
答案 0 :(得分:2)
好的,这是一个难以破解的难题,但成功太可爱了!
一个关键部分是它使用SampleMath
中的函数来计算从物理设备中心到目标的交叉线的起点。我们将它与相机法线向量相结合,得到我们想要的线条!
如果你想深入挖掘,我相信你可以在getPointToPlaneLineStart
函数背后挖掘/锻炼矩阵数学。
这是有效的代码。它不是最佳的,所以你可以整理一下/很多!
modelViewMatrix44F = Tool.convertPose2GLMatrix(trackableResult.getPose());
modelViewMatrixInverse44F = SampleMath.Matrix44FInverse(modelViewMatrix44F);
modelViewMatrixInverseTranspose44F = SampleMath.Matrix44FTranspose(modelViewMatrix44F);
modelViewMatrix = modelViewMatrix44F.getData();
Matrix.translateM(modelViewMatrix, 0, 0, 0, kObjectScale);
Matrix.scaleM(modelViewMatrix, 0, kObjectScale, kObjectScale, kObjectScale);
modelViewMatrix44F.setData(modelViewMatrix);
projectionMatrix44F = vuforiaAppSession.getProjectionMatrix();
projectionMatrixInverse44F = SampleMath.Matrix44FInverse(projectionMatrix44F);
projectionMatrixInverseTranspose44F = SampleMath.Matrix44FTranspose(projectionMatrixInverse44F);
// work out camera position and direction
modelViewMatrixInverse = modelViewMatrixInverseTranspose44F.getData();
position = new float [] {0, 0, 0, 1}; // camera position
lookAt = new float [] {0, 0, 1, 0}; // camera direction
float [] rotate = new float [] {(float) Math.cos(angle_degrees * 0.017453292f), (float) Math.sin(angle_degrees * 0.017453292f), 0, 0};
angle_degrees += 10;
if(angle_degrees > 359)
angle_degrees = 0;
float [] cam_position = new float[16];
float [] cam_lookat = new float[16];
float [] cam_rotate = new float[16];
Matrix.multiplyMV(cam_position, 0, modelViewMatrixInverse, 0, position, 0);
Matrix.multiplyMV(cam_lookat, 0, modelViewMatrixInverse, 0, lookAt, 0);
Matrix.multiplyMV(cam_rotate, 0, modelViewMatrixInverse, 0, rotate, 0);
Vec3F line_start = SampleMath.getPointToPlaneLineStart(projectionMatrixInverse44F, modelViewMatrix44F, 2*kObjectScale, 2*kObjectScale, new Vec2F(0, 0), new Vec3F(0, 0, 0), new Vec3F(0, 0, 1));
float x1 = line_start.getData()[0];
float y1 = line_start.getData()[1];
float z1 = line_start.getData()[2];
float x2 = x1 + cam_lookat[0] * 3 + cam_rotate[0] * 0.1f;
float y2 = y1 + cam_lookat[1] * 3 + cam_rotate[1] * 0.1f;
float z2 = z1 + cam_lookat[2] * 3 + cam_rotate[2] * 0.1f;
line.setVerts(x1, y1, z1, x2, y2, z2);
注意 - 我添加了cam_rotate
向量,以便您可以看到该行,否则您无法看到它 - 或者至少您只在屏幕上看到一个斑点 - 因为它被定义为垂直于屏幕!
周五所以我可能会去酒吧庆祝: - )
<强>更新强>
实际上getPointToPlaneLineStart
Java SampleMath方法调用以下代码(C ++),因此如果您不想使用SampleMath类,可以从中解密矩阵数学(cf this post )
SampleMath::projectScreenPointToPlane(QCAR::Matrix44F inverseProjMatrix, QCAR::Matrix44F modelViewMatrix,
float contentScalingFactor, float screenWidth, float screenHeight,
QCAR::Vec2F point, QCAR::Vec3F planeCenter, QCAR::Vec3F planeNormal,
QCAR::Vec3F &intersection, QCAR::Vec3F &lineStart, QCAR::Vec3F &lineEnd)
{
// Window Coordinates to Normalized Device Coordinates
QCAR::VideoBackgroundConfig config = QCAR::Renderer::getInstance().getVideoBackgroundConfig();
float halfScreenWidth = screenHeight / 2.0f;
float halfScreenHeight = screenWidth / 2.0f;
float halfViewportWidth = config.mSize.data[0] / 2.0f;
float halfViewportHeight = config.mSize.data[1] / 2.0f;
float x = (contentScalingFactor * point.data[0] - halfScreenWidth) / halfViewportWidth;
float y = (contentScalingFactor * point.data[1] - halfScreenHeight) / halfViewportHeight * -1;
QCAR::Vec4F ndcNear(x, y, -1, 1);
QCAR::Vec4F ndcFar(x, y, 1, 1);
// Normalized Device Coordinates to Eye Coordinates
QCAR::Vec4F pointOnNearPlane = Vec4FTransform(ndcNear, inverseProjMatrix);
QCAR::Vec4F pointOnFarPlane = Vec4FTransform(ndcFar, inverseProjMatrix);
pointOnNearPlane = Vec4FDiv(pointOnNearPlane, pointOnNearPlane.data[3]);
pointOnFarPlane = Vec4FDiv(pointOnFarPlane, pointOnFarPlane.data[3]);
// Eye Coordinates to Object Coordinates
QCAR::Matrix44F inverseModelViewMatrix = Matrix44FInverse(modelViewMatrix);
QCAR::Vec4F nearWorld = Vec4FTransform(pointOnNearPlane, inverseModelViewMatrix);
QCAR::Vec4F farWorld = Vec4FTransform(pointOnFarPlane, inverseModelViewMatrix);
lineStart = QCAR::Vec3F(nearWorld.data[0], nearWorld.data[1], nearWorld.data[2]);
lineEnd = QCAR::Vec3F(farWorld.data[0], farWorld.data[1], farWorld.data[2]);
linePlaneIntersection(lineStart, lineEnd, planeCenter, planeNormal, intersection);
}
答案 1 :(得分:1)
我绝不是专家,但对我来说这应该是预期的左/右反转。在我看来,世界空间中的物体朝向相机的正z轴方向看,而相机空间朝向面向相机的负z轴方向。坐标系的这种变换必然会反转其中一个x / y轴,以保持坐标系的一致性。
ELI5:当你站在某人面前告诉他们“在3点钟我们都向左走”时,你不会再站在对方面前了。
我认为如你所说,投影矩阵不太可能出现问题。投影矩阵仅将3d对象转换为2D屏幕。相机内在函数听起来也不适合我。该矩阵将纠正由相机镜头形状和位置引起的小扭曲,没有像左/右反转那样剧烈。
不幸的是,我现在也不知道如何解决它,但我不得不说的是评论太久了。对不起: - (