当前位置: 首页 > 工具软件 > jPCT > 使用案例 >

(Android)Vuforia Native版本与jpct-ae结合

姚建树
2023-12-01

原文链接: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得融合。

Vuforia Native版本替换模型

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方法中,把jpctframebuffer的初始化代码插入进来,同样也是从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;
	}

Native层:

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





 类似资料: