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