OpenGL.ES在Android上的简单实践:11-全景(正方体-索引-深度测试)

0、全景图要怎么看?

What is 全景?可能很多人单看这名字不太清楚。但看到下面的图的时候就噢的一声~瞬间廓然开朗

大家在欣赏大视野的同时,有些人可能也觉得和普通的照片没啥区别嘛╮(╯▽╰)╭就比普通的照片大一点嘛。是这样吗?再看看下面的连接:http://720yun.com/t/242jO7smen4?pano_id=3330421 (这是我比较喜欢的陕西榆林黄土流沙地貌的全景图 ( ̄▽ ̄)/) 怎么样,打开新世界了是吧?所以这就是全世界了吗?错了,有空的同学可以找一个叫Insta360的app,它是国外社交软件Instagram的衍生工具Insta360 ONE摄像头的附属应用,里面才是全世界啊,兄die!

本篇开始,我们将基于OpenGL,自己做一个Insta360 Player,实现其中的水晶球-鱼眼-小行星等效果,增加对OpenGL的认识,包括不仅限以下内容:索引,VBO,FBO,三大矩阵的深入使用,各种功能标志的理解和使用,特效动画,最后还会延伸一些模拟视频渲染的知识。

1、认识索引

在开始之前,我们有这么一个简单的需求:用我们之前学过的知识,画出一个彩色的正方体。我们如下图一一分析。

一个正方体有八个面,每个面四个点,And笛卡尔坐标系把空间分成了8个区间,所以这些点都分别坐落在坐标系的其中一个区间;我们各取一个单位1长度,这样就按如图显示呈现出8个坐标点。

下一步我们开始拼凑正方体Cube的每个平面的三角形,数据有点多,注意是否多了少了个负号,颜色值有没写错(# ̄~ ̄#)

public class Cube {private static final float[] CUBE_DATA = {//x,    y,    z     R, G, B1f,   1f,   1f,    1, 0, 1,   //近平面第一个三角形-1f,   1f,   1f,    1, 0, 0,-1f,  -1f,   1f,    0, 0, 1,1f,   1f,   1f,    1, 0, 1,   //近平面第二个三角形-1f,  -1f,   1f,    0, 0, 1,1f,  -1f,   1f,    0, 1, 0,1f,   1f,  -1f,    0, 0, 1,   //远平面第一个三角形-1f,   1f,  -1f,    0, 1, 0,-1f,  -1f,  -1f,    1, 0, 1,1f,   1f,  -1f,    0, 0, 1,   //远平面第二个三角形-1f,  -1f,  -1f,    1, 0, 1,1f,  -1f,  -1f,    1, 0, 0,-1f,   1f,  -1f,    0, 1, 0,   //左平面第一个三角形-1f,   1f,   1f,    1, 0, 0,-1f,  -1f,   1f,    0, 0, 1,-1f,   1f,  -1f,    0, 1, 0,   //左平面第二个三角形-1f,  -1f,   1f,    0, 0, 1,-1f,  -1f,  -1f,    1, 0, 1,1f,   1f,  -1f,    0, 0, 1,   //右平面第一个三角形1f,   1f,   1f,    1, 0, 1,1f,  -1f,   1f,    0, 1, 0,1f,   1f,  -1f,    0, 0, 1,   //右平面第二个三角形1f,  -1f,   1f,    0, 1, 0,1f,  -1f,  -1f,    1, 0, 0,1f,   1f,  -1f,    0, 0, 1,   //上平面第一个三角形-1f,   1f,  -1f,    0, 1, 0,-1f,   1f,   1f,    1, 0, 0,1f,   1f,  -1f,    0, 0, 1,   //上平面第二个三角形-1f,   1f,   1f,    1, 0, 0,1f,   1f,   1f,    1, 0, 1,1f,  -1f,  -1f,    1, 0, 0,   //下平面第一个三角形-1f,  -1f,  -1f,    1, 0, 1,-1f,  -1f,   1f,    0, 0, 1,1f,  -1f,  -1f,    1, 0, 0,   //下平面第二个三角形-1f,  -1f,   1f,    0, 0, 1,1f,  -1f,   1f,    0, 1, 0,};
}

顶点创建完后,我们准备对应的着色器。因为我们的顶点数据包括位置和颜色,所以我们顶点着色器如下:

uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec4 a_Color;varying vec4 v_Color;void main()
{v_Color = a_Color;gl_Position = u_Matrix * a_Position;gl_PointSize = 10.0;
}

u_Mateix存放三大矩阵的乘积结果,通过varying把颜色值传递到片段着色器,通过OpenGL内置的颜色混合效果显示出来

precision mediump float;varying vec4 v_Color;void main()
{gl_FragColor = v_Color;
}

我们建立对应的着色器程序CubeShaderProgram:

public class CubeShaderProgram extends ShaderProgram {protected static final String U_MATRIX = "u_Matrix";public final int uMatrixLocation;protected static final String A_POSITION = "a_Position";public final int aPositionLocation;protected static final String A_COLOR = "a_Color";public final int aColorLocation;public CubeShaderProgram(Context context) {super(context, R.raw.cube_one_vs, R.raw.cube_one_fs);uMatrixLocation = GLES20.glGetUniformLocation(programId, U_MATRIX);aColorLocation = GLES20.glGetAttribLocation(programId, A_COLOR);aPositionLocation = GLES20.glGetAttribLocation(programId, A_POSITION);}public void setUniforms(float[] matrix){GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);}}

再下一步,我们加入三大矩阵,为Cube绑定着色器程序,准备好一切之后,我们就可以在测试Activity的页面上替换Renderer画出来了。最终的代码如下:

public class Cube {private static final int POSITION_COMPONENT_COUNT = 3;private static final int COLOR_COMPONENT_COUNT = 3;private static final int STRIDE = (POSITION_COMPONENT_COUNT+COLOR_COMPONENT_COUNT)* Constants.BYTES_PER_FLOAT;private final VertexArray vertexArray;public float[] modelMatrix = new float[16];public Cube() {vertexArray = new VertexArray(CUBE_DATA);Matrix.setIdentityM(modelMatrix,0);}public void bindData(CubeShaderProgram shaderProgram){vertexArray.setVertexAttributePointer(shaderProgram.aPositionLocation,POSITION_COMPONENT_COUNT,STRIDE,0);vertexArray.setVertexAttributePointer(shaderProgram.aColorLocation,COLOR_COMPONENT_COUNT,STRIDE,POSITION_COMPONENT_COUNT);}public void draw() {GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6*2*3);}private static final float[] CUBE_DATA = { ... ... };
}
public class CubeRenderer implements GLSurfaceView.Renderer {private final Context context;private final float[] modelViewProjectionMatrix = new float[16];private final float[] viewProjectionMatrix = new float[16];private final float[] projectionMatrix = new float[16];private final float[] viewMatrix = new float[16];Cube cube;CubeShaderProgram cubeShaderProgram;public CubeRenderer(Context context) {this.context = context;Matrix.setIdentityM(projectionMatrix,0);Matrix.setIdentityM(viewMatrix,0);Matrix.setIdentityM(viewProjectionMatrix,0);Matrix.setIdentityM(modelViewProjectionMatrix,0);}@Overridepublic void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);cube = new Cube();cubeShaderProgram = new CubeShaderProgram(context);}@Overridepublic void onSurfaceChanged(GL10 gl10, int width, int height) {GLES20.glViewport(0,0,width,height);MatrixHelper.perspectiveM(projectionMatrix, 45, (float)width/(float)height, 1f, 100f);Matrix.setLookAtM(viewMatrix, 0,4f, 4f, 4f,0f, 0f, 0f,0f, 1f, 0f);Matrix.multiplyMM(viewProjectionMatrix,0,  projectionMatrix,0, viewMatrix,0);}@Overridepublic void onDrawFrame(GL10 gl10) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);Matrix.multiplyMM(modelViewProjectionMatrix,0, viewProjectionMatrix,0, cube.modelMatrix,0);cubeShaderProgram.userProgram();cubeShaderProgram.setUniforms(modelViewProjectionMatrix);cube.bindData(cubeShaderProgram);cube.draw();}
}

到此,我们复习了整个OpenGL的渲染流程 和 其中的关键知识点,如果以上代码还有疑问的同学,请重新阅读系列文章1~10或者评论私信留言。

那么,问题来了,正方体点的数据那么复杂,而且数据量占位庞大,不符合我们实际的使用逻辑啊。我们正常的使用逻辑是怎样的呢?我们正式引入索引这个概念。

一个立方体只有8个相互独立的顶点,每个顶点都会在不同的平面重复的使用,就像以上给出的顶点数据,共6个平面,每个平面2个三角形,每个三角形3个顶点数据,每组顶点数据是3个位置分量+3个颜色分量,共6*2*3*(3+3)=216个浮点数,其中有很大一部分数据是重复的。通过使用索引数组,就不用重复的顶点数据了,我们只需要重复使用那些索引值,这使得我们减少了数据的整体大小。下面我们开始改造Cube,学习怎样使用索引。

首先我们修改顶点数组,只保存每个独立的顶点数据,

    private static final float[] CUBE_DATA = {//x,   y,    z     R,  G,  B-1f,   1f,   1f,   1f, 0f, 0f, // 0 left top near1f,   1f,   1f,   1f, 0f, 1f, // 1 right top near-1f,  -1f,   1f,   0f, 0f, 1f, // 2 left bottom near1f,  -1f,   1f,   0f, 1f, 0f, // 3 right bottom near-1f,   1f,  -1f,   0f, 1f, 0f, // 4 left top far1f,   1f,  -1f,   0f, 0f, 1f, // 5 right top far-1f,  -1f,  -1f,   1f, 0f, 1f, // 6 left bottom far1f,  -1f,  -1f,   1f, 0f, 0f, // 7 right bottom far};

接下来,我们开始创建正方体的索引数组:

ByteBuffer indexArray = ByteBuffer.allocateDirect(6 * 2 * 3).put(new byte[]{//front1, 0, 2,1, 2, 3,//back5, 4, 6,5, 6, 7,//left4, 0, 2,4, 2, 6,//right5, 1, 3,5, 3, 7,//top5, 4, 0,5, 0, 1,//bottom7, 6, 2,7, 2, 3});

这个索引数组试用偏移值指向每个顶点。比如,0指向数组中第一个顶点,且1指向第二个顶点。通过这个索引数组,我们把所有顶点分别绑定成三角形组,每个有立方体上每个面的两个三角形。通过一个索引数组,我们可以用位置指向每个顶点,而不用一遍又一遍地重复同一个顶点数据。

最后,我们还要修正draw方法,注意参数的说明:

public void draw() {// GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6*2*3);GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6*2*3, GLES20.GL_UNSIGNED_BYTE, indexArray);
}

void glDrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
其中:
mode指定绘制图元的类型,我们学过的类型有这些:GL_POINTS, GL_LINE_STRIP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES。
count为绘制图元的数量乘上一个图元的顶点数:这里数量=6个面*2个三角形*3个顶点。

type为索引值的类型,只能是下列值之一:GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT。
我们这里顶点数量不多,用BYTE就满足了,所以我就直接使用ByteBuffer存储索引值。在一些大项目工程中,一个模型就上达几十万个顶点数据,这时候就要注意使用SHORT或者更大的INT。
indices:指向索引存贮位置的指针。

好了,修改完毕了,就这么简单,so easy嘛o(* ̄︶ ̄*)o  此时我们运行项目,是不是和修改之前的一样?效果如下图:

2、深度测试

观察仔细的同学可能已经发现了,观察矩阵定义在(4f,4f,4f)的位置上,就是近平面的右上角往下观察正方体,但是这个颜色的混合过滤貌似有点毛病啊,这好像就是底部平面的颜色都浮现出来了。这是为啥啊?

其实这和我们画三角形的顺序有关,我们看看我们的三角形索引数组,我们画三角形的顺序如下:近平面->远平面->左平面->右平面->顶部平面->底部平面,此时先画的三角形着色,会被后画的所覆盖,导致颜色线性混合出毛病了。那么这个问题怎么解?难不成我们还要自己排列这个画图的顺序咯,太反人类了吧?

此时我们要引入深度测试。简单的说,所谓深度,就是在openGL坐标系中,像素点Z坐标距离摄像机(观察矩阵)的距离。摄像机可能放在坐标系的任何位置,那么,就不能简单的说Z数值越大或越小,就是越靠近摄像机。OpenGL中的深度测试是采用深度缓存器算法,消除场景中的不可见面。在默认情况下,深度缓存中深度值的范围在0.0到1.0之间。

深度缓冲区原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素相关联。首先,使用glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值设置为最大值(一般是远裁剪面)。然后,在场景中以任意次序绘制所有物体。硬件或者软件所执行的图形计算把每一个绘制表面转换为窗口上一些像素的集合,此时并不考虑是否被其他物体遮挡。然后,OpenGL会计算这些表面和观察平面的距离。如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把它的深度值和已经存储在这个像素的深度值进行比较。新像素深度值<原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,他颜色值和深度将被丢弃。

为了启动深度缓冲区,必须先启动它,即glEnable(GL_DEPTH_TEST)。每次绘制场景之前,需要先清除深度缓冲区,即glClear(GL_DEPTH_BUFFER_BIT),然后以任意次序绘制场景中的物体。

让我们更新CubeRenderer的代码,开启深度测试:

    @Overridepublic void onSurfaceChanged(GL10 gl10, int width, int height) {GLES20.glViewport(0,0,width,height);GLES20.glEnable(GLES20.GL_DEPTH_TEST);MatrixHelper.perspectiveM(projectionMatrix, 45, (float)width/(float)height, 1f, 100f);Matrix.setLookAtM(viewMatrix, 0,4f, 4f, 4f,0f, 0f, 0f,0f, 1f, 0f);Matrix.multiplyMM(viewProjectionMatrix,0,  projectionMatrix,0, viewMatrix,0);}@Overridepublic void onDrawFrame(GL10 gl10) {GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);... ...}

运行项目看看效果是否如下:

小结:这章内容不多,主要是复习了渲染流程,和其流程环节的知识点,毕竟这个流程是模板代码。然后我们新增加了索引和深度测试,都不难比较好理解。

项目源码:https://github.com/MrZhaozhirong/BlogApp   参考子目录cube/CubeActivity

OpenGL.ES在Android上的简单实践:11-全景(索引-深度测试)相关推荐

  1. OpenGL.ES在Android上的简单实践:10-曲棍球(拖动物体、碰撞测试)

    OpenGL.ES在Android上的简单实践:10-曲棍球(拖动物体.碰撞测试) 1.让木槌跟随手指移动 继续上一篇文章9的内容.既然可以测试木槌是否被触碰了,我们将继续努力下去:当我们来回拖动木槌 ...

  2. OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏渲染,解决透明冲突,画中画)

    OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏录制,解决透明冲突) 1.水印签名罢工了? 不知道大家有没注意到,之前我们使用MediaCodec录制的视频,水印签名那部分区 ...

  3. OpenGL.ES在Android上的简单实践:21-水印录制(MediaCodec输出h264+MediaMuxer合成mp4 上)

    OpenGL.ES在Android上的简单实践:21-水印录制(MediaCodec输出h264+MediaMuxer合成mp4 上) 1.录制视频需要什么? 在上篇文章,我们已经成功的满足了需求,在 ...

  4. OpenGL.ES在Android上的简单实践:20-水印录制(预览+透明水印 表情 弹幕 gl_blend)

    OpenGL.ES在Android上的简单实践:20-水印录制(预览 gl_blend) 1.继续画出预览帧 紧接着上篇文章,既然是要画出预览帧,按照之前其他项目的架构组成.我们是通过模型FrameR ...

  5. SDL2源码分析之OpenGL ES在windows上的渲染过程

    SDL2源码分析之OpenGL ES在windows上的渲染过程 更新于2018年11月4日. 更新于2018年11月21日. ffmpeg + SDL2实现的简易播放器 ffmpeg和SDL非常强大 ...

  6. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

  7. OpenGL ES for Android 绘制旋转的地球

    No 图 No Code,我们先来欣赏下旋转的地球: 是不是很酷炫,要想绘制出上面酷炫的效果需要3个步骤: 计算球体顶点数据 地球纹理贴图 通过MVP矩阵旋转地球 计算球体顶点数据 我们知道OpenG ...

  8. android opengl流程,【Android OpenGL ES】Android Opengl ES创建流程

    在android 1.0rc2 sdk中,提供了以下包支持Opengl ES 编程: 一.openglES包 android.opengl Class: GLDebugHelper:用于调试OpenG ...

  9. OpenGL ES for Android 绘制立方体

    立方体有6个面,8个顶点,因此绘制立方体其实就是绘制6个面. 顶点shader attribute vec4 a_Position; attribute vec4 a_color; varying v ...

最新文章

  1. 什么是棉绒,它如何节省您的时间?
  2. js win10语音朗读文字
  3. SAP MM公司间STO里的一步法转库?
  4. linux 5.8 yum源,Centos5.8 |linux yum源不能用报404错误
  5. linux下利用openssl来实现证书的颁发(详细步骤)--转载和修改
  6. java八种包装_Java的八种基本数据类型及其包装类
  7. Jupyter Notebook入门攻略
  8. Python中np.sum()对axis的个人理解,超详细
  9. matlab最速下降法例子,matlab 最速下降法 steepest descent (实例并附有详细说明)
  10. vant-ui 有赞ui官网打不开?
  11. 我的日程安排系列问题(区间重叠问题)
  12. 64位Sql Server 2005开发版于64位Windows7旗舰版 安装过程
  13. 《阿凡达》重夺全球影史冠军;区块链或推动“疫苗护照”国际互认 | 美通企业日报...
  14. Pycharm中如何将界面上的英文换为中文与Pycharm的背景设置与更换
  15. 那些所倚靠的利器记载
  16. 北京科技大学871计算机真题,(NEW)北京科技大学871计算机综合一(含计算机组成原理、数据结构)历年考研真题汇编(350页)-原创力文档...
  17. Build Your Own Angularjs 读书笔记(AngularJS牛逼的地方在于它内嵌了一个表达式到Function对象的编译器。。。当然还有DI框架)
  18. WT588F34B语音芯片单曲更换语音功能的实现与应用
  19. 又一个短小精悍的软件包管理器-pnpm
  20. Spss Modeler关联规则Apriori模型、Carma算法分析超市顾客购买商品数据挖掘实例

热门文章

  1. 程序员修炼之路(三)一个清华大学毕业生做猎头的感受(转)
  2. PayPal轮询收款的那些事儿
  3. 我学习Android的一些套路
  4. (转载)VC的内存泄漏检查
  5. 逻辑树与视觉树基本概念
  6. 解决网站出现“Error establishing a database connection“的方法
  7. python斗鱼抽奖_python3爬取斗鱼某些版块的主播人气
  8. 【Windows】Windows10 无法登录 Microsoft 账户的解决方案
  9. 什么是SpringMVC中的@ModelAttribute?
  10. 【STL】unordered_set和unordered_map