归海一啸
光明使者
光明使者
  • 社区居民
  • 最爱沙发
  • 忠实会员
  • 喜欢达人
  • 原创写手
阅读:36360回复:1

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

楼主#
更多 发布于:2015-08-23 10:01
AR/VR学院技术交流③群:470653189
AR/VR学院技术交流②群: 346836719(已满)
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
AR学院(www.arvrschool.com),从这里感触未来!

欢迎分享

pwlcodb69694
禁止发言
禁止发言
  • UID4361
  • 粉丝0
  • 关注0
  • 发帖数7
沙发#
发布于:2017-08-06 20:45
北京辟谷空间-你对辟谷是不是有误解4
用户被禁言,该主题自动屏蔽!
游客

返回顶部