OpenGL.ES在Android上的简单实践:16-全景(视野变换 完结)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a360940265a/article/details/79847712

OpenGL.ES在Android上的简单实践:16-全景(视野变换 完结)

让我们继续完成视野变换,先回顾一下之前我们所做的内容:

当我们在屏幕上双击测试页面的glsurfaceview,会触发渲染器的双击事件,进而向全景球模型请求下一个模型的变换。在全景球模型中,我们定义了两个观察视口CameraViewport,一个是当前的,另外一个是目标的;还有两个模型模式的标志位RENDER_MODE,也是分为当前的和目标的。每次请求模型的变换,就可以根据当前模型的模式指定->目标模式的标志位RENDER_MODE和观察视口CameraViewport。

以上就是上节文章的主要内容,现在我们正式来分析本节内容。视野变换的动态效果,实质就是动画,动画效果说到底其实也是每帧的连续动画播放。所以,我们可以在onDrawFrame增加一套更新观察视口和焦距的方法,当需要视野变换的时候(当前标志位!=目标标志位)我们就按照目标的观察视口逐渐的变换当前的观察视口,最终达到目标后更新标志位,整个视野随即变换成功。

概念代码如下:

    public void onDrawFrame() {
        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        updateBallControlMode(); // 时刻观察当前模型视野和目标模型视野
        ballShaderProgram.userProgram();
        setAttributeStatus();
        updateBallMatrix();
        ballShaderProgram.setUniforms(getFinalMatrix(), textureId);
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.getIndexBufferId());
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, numElements, GLES20.GL_UNSIGNED_SHORT, 0);
        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    private void updateBallControlMode() {
        if(currentControlMode != targetControlMode){
            //从 全景球 切换成 透视
            if(currentControlMode == Constants.RENDER_MODE_CRYSTAL &&
                    targetControlMode == Constants.RENDER_MODE_PERSPECTIVE){
                    ... ...
            }
            //从 透视 切换成 小行星
            if(currentControlMode == Constants.RENDER_MODE_PERSPECTIVE &&
                    targetControlMode == Constants.RENDER_MODE_PLANET){
                    ... ...
            }
            //从 小行星 切换成 全景球
            if(currentControlMode == Constants.RENDER_MODE_PLANET &&
                    targetControlMode == Constants.RENDER_MODE_CRYSTAL){
                    ... ...
            }
            //矩阵生效
            float ratio = (float)mSurfaceWidth / (float)mSurfaceHeight; 
            // 在onSurfaceChanged预先保留的渲染屏幕长宽
            MatrixHelper.perspectiveM(this.mProjectionMatrix,
                    currentViewport.overlook, ratio, 0.01f, 1000f);
            Matrix.setLookAtM(this.mViewMatrix,0,
                    currentViewport.cx,  currentViewport.cy,  currentViewport.cz, 
                    currentViewport.tx,  currentViewport.ty,  currentViewport.tz, 
                    currentViewport.upx, currentViewport.upy, currentViewport.upz);
        }
    }

接下来,我们去完善updateBallControlMode方法,让整个变换效果跑起来。

    private void updateBallControlMode() {
        if(currentControlMode != targetControlMode){
            //从 全景球 切换成 透视
            if(currentControlMode == Constants.RENDER_MODE_CRYSTAL &&
                    targetControlMode == Constants.RENDER_MODE_PERSPECTIVE){
                if(!CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)){
                    currentViewport.overlook += (CameraViewport.PERSPECTIVE_OVERLOOK - 
                                                              CameraViewport.CRYSTAL_OVERLOOK) / 20f;
                }else{
                    currentViewport.overlook = CameraViewport.PERSPECTIVE_OVERLOOK;
                }
                if(!currentViewport.equals(targetViewport)){
                    float diff = calculateDist(currentViewport.cz, targetViewport.cz, 10f);
                    // 从 2.8 -> 1.0
                    currentViewport.setCameraVector(currentViewport.cx, currentViewport.cy, 
                                                    currentViewport.cz-=diff);
                    if(currentViewport.cz < 1.0f)
                        currentViewport.setCameraVector(0, 0, 1.0f);
                }else{
                    currentViewport.setCameraVector(0, 0, 1.0f);
                }
                if(CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)
                        && currentViewport.equals(targetViewport)){
                    //切换完成
                    currentControlMode = Constants.RENDER_MODE_PERSPECTIVE;
                }
            }
            //从 透视 切换成 小行星
            if(currentControlMode == Constants.RENDER_MODE_PERSPECTIVE &&
                    targetControlMode == Constants.RENDER_MODE_PLANET){
                // TODO
            }
            //从 小行星 切换成 全景球
            if(currentControlMode == Constants.RENDER_MODE_PLANET &&
                    targetControlMode == Constants.RENDER_MODE_CRYSTAL){
                // TODO
            }
            //矩阵生效
            float ratio = (float)mSurfaceWidth / (float)mSurfaceHeight;
            MatrixHelper.perspectiveM(this.mProjectionMatrix,
                    currentViewport.overlook, ratio, 0.01f, 1000f);
            Matrix.setLookAtM(this.mViewMatrix,0,
                    currentViewport.cx,  currentViewport.cy,  currentViewport.cz, //摄像机位置
                    currentViewport.tx,  currentViewport.ty,  currentViewport.tz, //摄像机目标视点
                    currentViewport.upx, currentViewport.upy, currentViewport.upz);//摄像机头顶方向向量
        }
    }

我们先来分析,从全景球切换成透视模式:首先我们处理焦距,判断当前的焦距与目标焦距是否相等,如果不相等我们就用两个目标之间的差值缩小20倍当作步伐,直到两者相等。同理我们处理观察视口,其中calculateDist方法我们使用了单摆的公式,求出一个非线性的步伐,公式如下。随后鉴定焦距 视口都相等后,即达到目标视野,转换完成。

    // 单摆公式。
    private float calculateDist(float current, float target, float divisor) {
        if(divisor==0) return 0;
        float absCurrent = Math.abs(current);
        float absTarget = Math.abs(target);
        float diff = Math.abs(absCurrent - absTarget);
        float dist = (float) (Math.sqrt(Math.pow(diff, 2.0)) / divisor);
        return Math.abs(dist);
    }

OK!按照这一模板代码,我们可以很快的就完成从透视到小行星,小行星恢复到全景球的变换过程,下面贴出完整的updateBallControlMode代码:

    private void updateBallControlMode() {
        if(currentControlMode != targetControlMode){
            //从 全景球 切换成 透视
            if(currentControlMode == Constants.RENDER_MODE_CRYSTAL &&
                    targetControlMode == Constants.RENDER_MODE_PERSPECTIVE){

                if(!CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)){
                    currentViewport.overlook += (CameraViewport.PERSPECTIVE_OVERLOOK - 
                                                 CameraViewport.CRYSTAL_OVERLOOK)/20f; // 0.f
                }else{
                    currentViewport.overlook = CameraViewport.PERSPECTIVE_OVERLOOK;
                }
                if(!currentViewport.equals(targetViewport)){
                    float diff = calculateDist(currentViewport.cz, targetViewport.cz, 10f);
                    // 从 2.8 -> 1.0
                    currentViewport.setCameraVector(currentViewport.cx, currentViewport.cy, 
                                                    currentViewport.cz-=diff);
                    if(currentViewport.cz < 1.0f)
                        currentViewport.setCameraVector(0, 0, 1.0f);
                }else{
                    currentViewport.setCameraVector(0, 0, 1.0f);
                }
                if(CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)
                        && currentViewport.equals(targetViewport)){
                    //切换完成
                    currentControlMode = Constants.RENDER_MODE_PERSPECTIVE;
                }
            }
            //从 透视 切换成 小行星
            if(currentControlMode == Constants.RENDER_MODE_PERSPECTIVE &&
                    targetControlMode == Constants.RENDER_MODE_PLANET){
                if(!CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)){
                    // 70 -> 150f
                    currentViewport.overlook += (CameraViewport.PLANET_OVERLOOK - 
                                                 CameraViewport.PERSPECTIVE_OVERLOOK)/40f; //2.0f;
                }else{
                    currentViewport.overlook = CameraViewport.PLANET_OVERLOOK;
                }
                if(!currentViewport.equals(targetViewport)){
                    float diff = calculateDist(currentViewport.cz, targetViewport.cz, 10f);
                    currentViewport.setCameraVector(currentViewport.cx,currentViewport.cy,currentViewport.cz-=diff);
                    if(currentViewport.cz < 1.0f)
                        currentViewport.setCameraVector(0, 0, 1.0f);
                }else{
                    currentViewport.setCameraVector(0, 0, 1.0f);
                }
                if(CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)
                        && currentViewport.equals(targetViewport)){
                    //切换完成
                    currentControlMode = Constants.RENDER_MODE_PLANET;
                }
            }
            //从 小行星 切换成 全景球
            if(currentControlMode == Constants.RENDER_MODE_PLANET &&
                    targetControlMode == Constants.RENDER_MODE_CRYSTAL){
                if(!CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)){
                    currentViewport.overlook -= (CameraViewport.PLANET_OVERLOOK - 
                                                 CameraViewport.PERSPECTIVE_OVERLOOK)/40f;//2.0f;
                }else{
                    currentViewport.overlook = CameraViewport.CRYSTAL_OVERLOOK;
                }
                //currentViewport.overlook = CameraViewport.CRYSTAL_OVERLOOK;

                if(!currentViewport.equals(targetViewport)){
                    float diff = calculateDist(currentViewport.cz, targetViewport.cz, 10f);
                    currentViewport.setCameraVector(currentViewport.cx, currentViewport.cy, 
                                                    currentViewport.cz+=diff);
                }else{
                    currentViewport.setCameraVector(0, 0, 2.8f);
                }
                if(CameraViewport.beEqualTo(currentViewport.overlook,targetViewport.overlook)
                        && currentViewport.equals(targetViewport)){
                    currentControlMode = Constants.RENDER_MODE_CRYSTAL;
                }
            }
            //Log.w(Constants.TAG, "currentOverture : "+currentViewport.overlook);
            //Log.w(Constants.TAG, "current mViewMatrix: " + "\n" +
            //        currentViewport.cx + " " +  currentViewport.cy + " " +  currentViewport.cz + "\n" +
            //        currentViewport.tx + " " +  currentViewport.ty + " " +  currentViewport.tz + "\n" +
            //        currentViewport.upx + " " + currentViewport.upy + " " + currentViewport.upz + "\n");
            //Log.w(Constants.TAG, "=========================  " + "\n");
            //如果是在变换过程中,就时刻更新透视和视图矩阵。 
            float ratio = (float)mSurfaceWidth / (float)mSurfaceHeight;
            MatrixHelper.perspectiveM(this.mProjectionMatrix,
                    currentViewport.overlook, ratio, 0.01f, 1000f);
            Matrix.setLookAtM(this.mViewMatrix,0,
                    currentViewport.cx,  currentViewport.cy,  currentViewport.cz, //摄像机位置
                    currentViewport.tx,  currentViewport.ty,  currentViewport.tz, //摄像机目标视点
                    currentViewport.upx, currentViewport.upy, currentViewport.upz);//摄像机头顶方向向量
        }
    }

记得在onDrawFrame接口处调用updateBallControlMode,时刻监听是否请求目标视野的变换。到此,我们视野变换的效果已经基本完成,可能全景球的衔接效果不太好,毕竟这个效果是原创的,insta360Player中没有这个效果。如果希望在变换过程中增加旋转效果的,这个我们只需要在updateBallControlMode中增加rotationY/rotationX即可。

到此,又要结束一个主题的内容,这次的主题新知识不多,主要是代码设计上的封装和提炼,毕竟知识都是滚雪球式的,我们需要的就是提炼精髓。希望通过这一主题,大家对OpenGL在Android的普通应用设计流程和代码有十足的把握。往后的内容,就会更加深入Android内部和OpenGL的关系,更好地了解Android上的OpenGL,涉足内容包括 视频硬件渲染编解码,短视频录像,摄像头滤镜预览,视频处理(类似添加水印,视频调节帧率播放)等内容。还会有一些自己的学习笔记。

项目工程: https://github.com/MrZhaozhirong/BlogApp ->PanoramaActivity

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/79847712