原文链接:http://www.arvrschool.com/read.php?tid=299&fid=21
AR/VR技术交流群 129340649
Qualcomm的Vuforia引擎是最强大的增强现实引擎之一。将它和JPCT-AE结合是一个很好的想法,它可以让你的Android设备实现让人惊奇的AR场景。
其中在Android端和iOS端的Vuforia Native版本是需要进行NDK编程,并且对于3D渲染这块做的不是很好,它采用的方案是将模型文件转换成.h或者java文件,将其中的点线面等数据保存,然后使用OpenGL读取并绘制。这种方案的弊端有很多,比如模型文件过大,不适合多贴图,渲染效果不好等等。将Vuforia与3D渲染引擎在原生代码中融合一直是我想做的事情,这篇稿子主要讲述Android端的Vuforia原生代码与jpct-AE得融合。
jpct-AE是一款免费的Android 系统下的3D渲染引擎。他是一款基于OpenGL技术开发的3D图形引擎(PC环境为标准OpenGL,Android为OpenGL ES), 以Java语言为基础的,拥有功能强大的Java 3D解决方案。
Vuforia和jpct都是使用OpenGL es中的GLSurfaceView进行绘制显示 的。打开ImageTargetsRenderer.java文件,这个是OpenGL渲染类,我们需要把Jpct的代码插入进来。
首先,为ImageTargetsRenderer创建一个构造函数。在这个构造函数里,将Activity作为参数传递进来,也在这个构造函数中初始化场景。
public ImageTargetsRenderer(ImageTargets activity){
this.mActivity = activity;
world = new World();
world.setAmbientLight(20, 20, 20);
sun = new Light(world);
sun.setIntensity(250, 250, 250);
// Create a texture out of the icon...:-)
Texture texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(mActivity.getResources().getDrawable(R.drawable.ic_launcher)), 64, 64));
TextureManager.getInstance().addTexture("texture", texture);
cube = Primitives.getCube(10);
cube.calcTextureWrapSpherical();
cube.setTexture("texture");
cube.strip();
cube.build();
world.addObject(cube);
cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 50);
cam.lookAt(cube.getTransformedCenter());
SimpleVector sv = new SimpleVector();
sv.set(cube.getTransformedCenter());
sv.y -= 100;
sv.z -= 100;
sun.setPosition(sv);
MemoryHelper.compact();
}
然后在ImageTargets.java文件中,修改ImageTargetRenderer的初始化将activity作为参数传递进结构体中。
mRenderer = new ImageTargetsRenderer(this);
mRenderer.mActivity = this;
mGlView.setRenderer(mRenderer);
然后在ImageTargetRenderer类中的onSurfaceChanged方法中,把jpct的framebuffer的初始化代码插入进来,同样也是从jpct的例子中得来。
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(width, height);
好了,我们已经初始化场景和framebuffer,下一步就要使用jpct来绘制模型。(我们使用jpct的主要目的就是希望用他来绘制我们的虚拟场景)。需要在onDrawFrame()方法中完成。将下面的方法插入进onDrawFrame()方法中。
world.renderScene(fb);
world.draw(fb);
fb.display();
这里不需要fb.clear()。因为QCAR的本地openGL代码已经清除了framebuffer.如果这里再清除一次,就会清除摄像头拍摄的真实场景。
这时,如果你打开APP,会在真实场景上面看到一个矩形。(当然这个矩形目前还没有完成注册,还需要我们计算的摄像头矩阵)。
这里我们需要修改本地代码,即jni文件中的代码。打开ImageTargets.cpp文件,到JNIEXPORT void JNICALL Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargetsRenderer_renderFrame(JNIEnv *, jobject)这个方法的实现下。
这里是标准的OpenGL的代码。逐帧循环执行的线程。在Android中这是一个单独的线程。在这个方法里,framebuffer被清空,计算得到投影矩阵和模型视图矩阵,并且绘制模型。
这里不需要使用Vuforia渲染,渲染的任务交给jPCT-AE即可。删除一些不必要的语句,最后如下:
{
// Clear color and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Get the state from QCAR and mark the beginning of a rendering section
QCAR::State state = QCAR::Renderer::getInstance().begin();
// Explicitly render the Video Background
QCAR::Renderer::getInstance().drawVideoBackground();
// Did we find any trackables this frame?
for(int tIdx = 0; tIdx < state.getNumTrackableResults(); tIdx++)
{
// Get the trackable:
const QCAR::TrackableResult* result = state.getTrackableResult(tIdx);
const QCAR::Trackable& trackable = result->getTrackable();
QCAR::Matrix44F modelViewMatrix = QCAR::Tool::convertPose2GLMatrix(result->getPose());
}
QCAR::Renderer::getInstance().end();
}
使用NDK进行编译。接下来我们需要将native层计算得出的模型视图矩阵和投影矩阵传递到java层。创建了如下方法接受从native层传递进来的矩阵,它是一个4X4的矩阵。
public void updateModelviewMatrix(float mat[]) {
modelViewMat = mat;
}
由于矩阵(模型视图矩阵和投影矩阵)是每帧都需要计算和检测的,所以这个方法也应该循环调用。所以需要在native层中的renderFrame方法中调用该方法。这里用到Jni编程的一些规则,不清楚的同学可以去参考相关资料。
jclass activityClass = env->GetObjectClass(obj); //获取Activity
jmethodID updateMatrixMethod = env->GetMethodID(activityClass, "updateModelviewMatrix", "([F)V");
接下来就需要将估算的矩阵结果传递到java层。
jfloatArray modelviewArray = env->NewFloatArray(16);
for(int tIdx = 0; tIdx < state.getNumTrackableResults(); tIdx++)
{
// Get the trackable:
const QCAR::TrackableResult* result = state.getTrackableResult(tIdx);
const QCAR::Trackable& trackable = result->getTrackable();
QCAR::Matrix44F modelViewMatrix = QCAR::Tool::convertPose2GLMatrix(result->getPose());
SampleUtils::rotatePoseMatrix(90.0f, 1.0f, 0, 0, &modelViewMatrix.data[0]);
QCAR::Matrix44F inverseMV = SampleMath::Matrix44FInverse(modelViewMatrix);
QCAR::Matrix44F invTranspMV = SampleMath::Matrix44FTranspose(inverseMV);
// 将数据传递到java层
env->SetFloatArrayRegion(modelviewArray, 0, 16, invTranspMV.data);
env->CallVoidMethod(obj, updateMatrixMethod, modelviewArray);
}
// hide the objects when the targets are not detected
if (state.getNumTrackableResults() == 0) {
float m [] = {
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,-10000,1
};
env->SetFloatArrayRegion(modelviewArray, 0, 16, m);
env->CallVoidMethod(obj, updateMatrixMethod, modelviewArray);
}
env->DeleteLocalRef(modelviewArray);
这里对modelViewMatrix矩阵在X轴上进行旋转180°,因为JPCT的坐标系绕X轴旋转了180°。使用SetFloatArrayRegion将矩阵结果设置成浮点数组类型。最后通过native层调用java层方法updateMatrix将结果向Java层传递。
好的,接下来回到Java层,将刚刚从Native层传递过来的数据给Camera。public void updateCamera() {
if (modelViewMat != null) {
float[] m = modelViewMat;
final SimpleVector camUp;
if (mActivity.isPortrait()) {
camUp = new SimpleVector(-m[0], -m[1], -m[2]);
} else {
camUp = new SimpleVector(-m[4], -m[5], -m[6]);
}
final SimpleVector camDirection = new SimpleVector(m[8], m[9], m[10]);
final SimpleVector camPosition = new SimpleVector(m[12], m[13], m[14]);
cam.setOrientation(camDirection, camUp);
cam.setPosition(camPosition);
cam.setFOV(fov);
cam.setYFOV(fovy);
}
}
位置和旋转组成摄像头的称作摄像头的位姿,然而除了这个之外,摄像头所需要设置的参数远不止这些,还有FOV,简称视场,他也会影响摄像头所看到的场景,关于更详细的信息请看:
http://en.wikipedia.org/wiki/Field_of_view
由于不同的设备具有不同的FOV。这个就和摄像头的标定有关了。在QCAR库中提供了计算的函数,包括水平和垂直的FOV,看下面的代码。
这里使用QCAR摄像头标定的方法,获取摄像头的内部物理参数,这个在关于3D模型AR应用中也是必须的。
const QCAR::CameraCalibration& cameraCalibration = QCAR::CameraDevice::getInstance().getCameraCalibration();
QCAR::Vec2F size = cameraCalibration.getSize();
QCAR::Vec2F focalLength = cameraCalibration.getFocalLength();
float fovyRadians = 2 * atan(0.5f * size.data[1] / focalLength.data[1]);
float fovRadians = 2 * atan(0.5f * size.data[0] / focalLength.data[0]);
Java层:
public void setFov(float fov) {
this.fov = fov;
}
public void setFovy(float fovy) {
this.fovy = fovy;
}
jmethodID fovMethod = env->GetMethodID(activityClass, "setFov", "(F)V");
jmethodID fovyMethod = env->GetMethodID(activityClass, "setFovy", "(F)V");
env->CallVoidMethod(obj, fovMethod, fovRadians);
env->CallVoidMethod(obj, fovyMethod, fovyRadians);
源码地址:http://www.arvrschool.com/read.php?tid=298&fid=60