OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影

OpenGL ES 3.0学习实践

  • android平台下OpenGL ES 3.0从零开始
  • android平台下OpenGL ES 3.0绘制纯色背景
  • android平台下OpenGL ES 3.0绘制圆点、直线和三角形
  • android平台下OpenGL ES 3.0绘制彩色三角形
  • android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
  • android平台下OpenGL ES 3.0着色语言基础知识(上)
  • android平台下OpenGL ES 3.0着色语言基础知识(下)
  • android平台下OpenGL ES 3.0实例详解顶点属性、顶点数组
  • android平台下OpenGL ES 3.0实例详解顶点缓冲区对象(VBO)和顶点数组对象(VAO)
  • android平台下OpenGLES3.0绘制立方体的几种方式
目录
  • 绘制矩形
  • 宽高比的问题
  • 适应宽高比
  • 使用虚拟坐标空间
  • 矩阵和向量
  • 正交投影
  • 左手与右手坐标系
绘制矩形 新建一个矩形渲染器:
public class RectangleRenderer implements GLSurfaceView.Renderer

定义顶点着色器:
#version 300 es layout (location = 0) in vec4 vPosition; layout (location = 1) in vec4 aColor; out vec4 vColor; void main() { gl_Position=vPosition; gl_PointSize = 10.0; vColor = aColor; }

定义片段着色器:
#version 300 es precision mediump float; in vec4 vColor; out vec4 fragColor; void main() { fragColor = vColor; }

定义坐标点数据:
private float[] vertexPoints = new float[]{ //前两个是坐标,后三个是颜色RGB 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f,0.0f, 0.25f, 0.5f, 0.5f, 0.5f, 0.0f, -0.25f, 0.5f, 0.5f, 0.5f, };

public RectangleRenderer() { //分配内存空间,每个浮点型占4字节空间 vertexBuffer = ByteBuffer.allocateDirect(vertexPoints.length * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer(); //传入指定的坐标数据 vertexBuffer.put(vertexPoints); vertexBuffer.position(0); }

开始编译和链接着色器:
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { //设置背景颜色 GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f); //编译 final int vertexShaderId = ShaderUtils.compileVertexShader(vertextShader); final int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShader); //鏈接程序片段 mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId); //在OpenGLES环境中使用程序片段 GLES30.glUseProgram(mProgram); aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition"); aColorLocation = GLES30.glGetAttribLocation(mProgram, "aColor"); vertexBuffer.position(0); //获取顶点数组 (POSITION_COMPONENT_COUNT = 2) GLES30.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, STRIDE, vertexBuffer); GLES30.glEnableVertexAttribArray(aPositionLocation); vertexBuffer.position(POSITION_COMPONENT_COUNT); //颜色属性分量的数量 COLOR_COMPONENT_COUNT = 3 GLES30.glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GLES30.GL_FLOAT, false, STRIDE, vertexBuffer); GLES30.glEnableVertexAttribArray(aColorLocation); }

注意:glVertexAttribPointer这个方法的第5个参数stride,这个参数表示:
每个顶点由size指定的顶点属性分量顺序存储。stride指定顶 点索引I和(I+1),表示的顶点数据之间的位移。如果stride为0,则每个顶点的属性数据顺序存储。如果stride大于0, 则使用该值作为获取下一个索引表示的顶点数据的跨距。
//之前定义的坐标数据中,每一行是5个数据,前两个表示坐标(x,y),后三个表示颜色(r,g,b) private static final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT; //所以这里实际是 STRIDE = (2 + 3) x 4

开始绘制:
@Override public void onDrawFrame(GL10 gl) { GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT); //绘制矩形 GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 6); //绘制两个点 GLES30.glDrawArrays(GLES30.GL_POINTS, 6, 2); }

竖屏状态下显示:
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

横屏模式下显示:
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

宽高比的问题 假设实际手机分辨率以像素为单位是720x1280,我们默认使用OpenGL占用整个显示屏。
设备在竖屏模式下,那么[-1,1]的范围对应的高有1280像素,而宽却只有720像素
图像会在x轴显得扁平,如果在横屏模式,图像会在y轴显得扁平。通过上面的例子可以看到竖屏横屏模式下就有种被拉伸的感觉。
其实在OpenGL中,我们要渲染的一切物体都要映射到x轴y轴[-1, 1]的范围内,对z轴也是一样的。这个范围内的坐标被称为归一化设备坐标,其独立于屏幕实际的尺寸或形状,但是因为它们独立于实际的屏幕尺寸,如果直接使用它们,我们就会遇到刚才的问题。
归一化设备坐标假定坐标空间是一个正方形,然而,我们实际的视口viewport可能不是一个正方形,就像我刚刚手机上显示的一样,图像在一个方向上被拉伸,在另外一个方向上被压扁。因此在一个竖屏设备上,归一化设备坐标上定义的图像看上去就是在水平方向上被压扁,在横屏模式下,同样的图像就在垂直方向上看起来是压扁的。
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

适应宽高比 这个时候我们就需要调整坐标空间,让它把屏幕的形状考虑在内,可行的一个方法是把较小的范围固定在[-1,1]内,而按屏幕尺寸的比例调整较大的范围。
举例来说,在竖屏情况下,其宽度是720,而髙度是1280,因此我们可以把宽度范围限定在[-1,1],并把高度范围调整为[-1280/720,1280/720][-1.78,1.78]。同理,在横屏模式情况下,把宽度范围设为[-1.78,1.78],而把高度范围设为[-1,1]
通过调整已有的坐标空间,最终会改变我们可用的空间,通过这个方法,不论是竖屏模式还是横屏模式,物体看起来就都一样了。
使用虚拟坐标空间 我们需要?调整坐标空间,以便我们把屏幕方向考虑进来,需要停止直接在归一化设备坐标上工作,而开始在虚拟坐标空间里工作。需要找到某种可以把虚拟空间坐标转换回归一化设备坐标的方法,让OpenGL可以正确地渲染它们,这个操作叫作正交投影,不管多远 或多近,所有的物体看上去大小总是相同的。
矩阵和向量 在正交投影之前,可以先来复习一下矩阵以及向量相关的知识,因为在OpenGL中大量地使用了向量和矩阵,矩阵的最重要的用途之一就是建立正交和透视投影。其原因之一是,使用矩阵做投影只涉及对一组数据按顺序执行大量的加法和乘法,这些运算在现代GPU上执行得非常快。
  • 向量
一个向量是一个有多个元索的一维数组。在OpenGL里,一个位置通常是一个四元素向量,颜色也是一样。我们使用的大多数向量一般都有四个元素。一个位置向量,它有一个x、一个y、一个z和一个w分量。
[ x y z w ] \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} ?????xyzw??????
我们在三维空间中,x,y,z分量用的比较多
  • 矩阵
—个矩阵(Matrix)是一个有多个元素的二维数组。在OpenGL里,我们一般使用矩阵作向量投影,如正交或者透视投影,并且也用它们使物体旋转(rotation)、平移(translatum)以及缩放(scaling)。我们把矩阵与每个要变换的向最相乘即可实现这些变换。
[ x x x y x z x w y x y y y z y w z x z y z z z w w x w y w z w w ] \begin{bmatrix} {x_{x}}& {x_{y}}& {x_{z}}& {x_{w}}\\ {y_{x}}& {y_{y}}& {y_{z}}& {y_{w}}\\ {z_{x}}& {z_{y}}& {z_{z}}& {z_{w}}\\ {w_{x}}& {w_{y}}& {w_{z}}& {w_{w}}\\ \end{bmatrix} ?????xx?yx?zx?wx??xy?yy?zy?wy??xz?yz?zz?wz??xw?yw?zw?ww???????
矩阵与向量相乘
[ x x x y x z x w y x y y y z y w z x z y z z z w w x w y w z w w ] [ x y z w ] = [ x x x x y y x z z x w w y x x y y y y z z y w w z x x z y y z z z z w w w x x w y y w z z w w w ] \begin{bmatrix} {x_{x}}& {x_{y}}& {x_{z}}& {x_{w}}\\ {y_{x}}& {y_{y}}& {y_{z}}& {y_{w}}\\ {z_{x}}& {z_{y}}& {z_{z}}& {z_{w}}\\ {w_{x}}& {w_{y}}& {w_{z}}& {w_{w}}\\ \end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix}= \begin{bmatrix} {x_{x}x}& {x_{y}y}& {x_{z}z}& {x_{w}w}\\ {y_{x}x}& {y_{y}y}& {y_{z}z}& {y_{w}w}\\ {z_{x}x}& {z_{y}y}& {z_{z}z}& {z_{w}w}\\ {w_{x}x}& {w_{y}y}& {w_{z}z}& {w_{w}w}\\ \end{bmatrix} ?????xx?yx?zx?wx??xy?yy?zy?wy??xz?yz?zz?wz??xw?yw?zw?ww????????????xyzw??????=?????xx?xyx?xzx?xwx?x?xy?yyy?yzy?ywy?y?xz?zyz?zzz?zwz?z?xw?wyw?wzw?www?w??????
对于第一行,我们让 x x {x_{x}} xx?和 x {x} x相乘、 x y {x_{y}} xy?和 y {y} y相乘、 x z {x_{z}} xz?和 z {z} z相乘以及 x w {x_{w}} xw?和 w {w} w相乘,然后把 所有四个结果加起来得到这个结果的 x {x} x分量。
矩阵第一行的所有四个分都影响了那个结果x,第二行的所有4个分量都影响了那个结果y,以此类推。在矩阵的每一行内,第一个分量与向量的x相乘,第二个分量也与向虽的y相乘,以此类推。
  • 单位矩阵
[ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} ?????1000?0100?0010?0001??????
这个矩阵乘以任何向量都是得到与之前结果相同的向量
[ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] [ 1 2 3 4 ] = [ 1 × 1 + 0 × 2 + 0 × 3 + 0 × 4 0 × 1 + 1 × 2 + 0 × 3 + 0 × 4 0 × 1 + 0 × 2 + 1 × 3 + 0 × 4 0 × 1 + 0 × 2 + 0 × 3 + 1 × 4 ] \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix}= \begin{bmatrix} 1 \times1 + 0\times2 + 0 \times3 +0\times4\\ 0 \times1 + 1\times2 + 0 \times3 +0\times4\\ 0 \times1 + 0\times2 + 1 \times3 +0\times4\\ 0 \times1 + 0\times2 + 0 \times3 +1\times4\\ \end{bmatrix} ?????1000?0100?0010?0001???????????1234??????=?????1×1+0×2+0×3+0×40×1+1×2+0×3+0×40×1+0×2+1×3+0×40×1+0×2+0×3+1×4??????
结果
[ 1 2 3 4 ] \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} ?????1234??????
  • 平移矩阵
平移矩阵可以把一个物体沿着指定的方向移动
[ 0 0 0 x t r a n s l a t i o n 0 1 0 y t r a n s l a t i o n 0 0 1 z t r a n s l a t i o n 0 0 0 1 ] \begin{bmatrix} 0 & 0 & 0 & x_{translation} \\ 0 & 1 & 0 & y_{translation} \\ 0 & 0 & 1 & z_{translation} \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} ?????0000?0100?0010?xtranslation?ytranslation?ztranslation?1??????
计算过程:
[ 1 0 0 3 0 1 0 3 0 0 1 0 0 0 0 1 ] [ 2 2 0 1 ] = [ 1 × 2 + 0 × 2 + 0 × 0 + 3 × 1 0 × 2 + 1 × 2 + 0 × 0 + 3 × 1 0 × 0 + 0 × 0 + 1 × 0 + 0 × 1 0 × 2 + 0 × 2 + 0 × 0 + 1 × 1 ] \begin{bmatrix} 1 & 0 & 0 & 3\\ 0 & 1 & 0 & 3\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 2 \\ 2 \\ 0 \\ 1 \end{bmatrix}= \begin{bmatrix} 1 \times2 + 0\times2 + 0 \times0 +3\times1\\ 0 \times2 + 1\times2 + 0 \times0 +3\times1\\ 0 \times0 + 0\times0 + 1 \times0 +0\times1\\ 0 \times2 + 0\times2 + 0 \times0 +1\times1\\ \end{bmatrix} ?????1000?0100?0010?3301???????????2201??????=?????1×2+0×2+0×0+3×10×2+1×2+0×0+3×10×0+0×0+1×0+0×10×2+0×2+0×0+1×1??????
最终的结果:
[ 5 5 0 1 ] \begin{bmatrix} 5 \\ 5 \\ 0 \\ 1 \end{bmatrix} ?????5501??????
正交投影 在android中可以使用Matrix类来定义正交投影,这个类有一个称为orthoM的方法,它可以为我们生成一个正交投影。我们将使用这个投影调整坐标空间。正交投影与平移矩阵是非常相似的。
方法的参数描述:
参数 描述
float[] m 目标数组,这个数组的长度至少有16个元素,这样它才能存储正交投影矩阵
int mOffset 结果矩阵起始的偏移值
float left x轴的最小范围
float right x轴的最大范围
float bottom y轴的最小范围
float top y轴的最大范围
float near z轴的最小范围
float far z轴的最大范围
这个方法会产生下面的正交投影矩阵:
[ 2 r i g h t ? l e f t 0 0 ? r i g h t + l e f t r i g h t ? l e f t 0 2 t o p ? b o t t o m 0 ? t o p + b o t t o m t o p ? b o t t o m 0 0 ? 2 f a r ? n e a r ? f a r + n e a r f a r ? n e a r 0 0 0 1 ] \begin{bmatrix} \cfrac{\mathbf2}{\mathbf {right-left}} & \mathbf0 & \mathbf0 & -\cfrac{\mathbf{right+left}}{\mathbf {right-left}}\\ \\ \mathbf0 & \cfrac{\mathbf2}{\mathbf {top-bottom}} & \mathbf0 & -\cfrac{\mathbf{top+bottom}}{\mathbf {top-bottom}}\\ \\ \mathbf0 & \mathbf0 & \cfrac{-\mathbf2}{\mathbf {far-near}} & -\cfrac{\mathbf{far+near}}{\mathbf {far-near}}\\ \\ \mathbf0 & \mathbf0 & \mathbf0 & \mathbf{1} \end{bmatrix} ?????????????????right?left2?000?0top?bottom2?00?00far?near?2?0??right?leftright+left??top?bottomtop+bottom??far?nearfar+near?1??????????????????
经过上述正交投影矩阵转换之后,转换回归一矩阵
[ 2 1.78 ? ( ? 1.78 ) 0 0 ? 1.78 + ( ? 1.78 ) 1.78 ? ( ? 1.78 ) 0 2 1 ? ( ? 1 ) 0 ? 1 + ( ? 1 ) 1 ? ( ? 1 ) 0 0 ? 2 1 ? ( ? 1 ) ? 1 + ( ? 1 ) 1 ? ( ? 1 ) 0 0 0 1 ] [ 1.78 1 0 1 ] = \begin{bmatrix} \cfrac{\mathbf2}{\mathbf {1.78-(-1.78)}} & \mathbf0 & \mathbf0 & -\cfrac{\mathbf{1.78+(-1.78)}}{\mathbf {1.78-(-1.78)}}\\ \\ \mathbf0 & \cfrac{\mathbf2}{\mathbf {1-(-1)}} & \mathbf0 & -\cfrac{\mathbf{1+(-1)}}{\mathbf {1-(-1)}}\\ \\ \mathbf0 & \mathbf0 & \cfrac{-\mathbf2}{\mathbf {1-(-1)}} & -\cfrac{\mathbf{1+(-1)}}{\mathbf {1-(-1)}}\\ \\ \mathbf0 & \mathbf0 & \mathbf0 & \mathbf{1} \end{bmatrix} \begin{bmatrix} 1.78 \\ 1 \\ 0 \\ 1 \end{bmatrix}= ?????????????????1.78?(?1.78)2?000?01?(?1)2?00?001?(?1)?2?0??1.78?(?1.78)1.78+(?1.78)??1?(?1)1+(?1)??1?(?1)1+(?1)?1???????????????????????1.78101??????=
[ 2 1.78 ? ( ? 1.78 ) × 1.78 + 0 × 1 + 0 × 0 + ? 1.78 + ( ? 1.78 ) 1.78 ? ( ? 1.78 ) × 0 0 × 1 + 2 1 ? ( ? 1 ) × 1 + 0 × 0 + ? 1 + ( ? 1 ) 1 ? ( ? 1 ) × 1 0 × 1.78 + 0 × 1 + ? 2 1 ? ( ? 1 ) × 0 + ? 1 + ( ? 1 ) 1 ? ( ? 1 ) × 1 0 × 1.78 + 0 × 1 + 0 × 0 + 1 × 1 ] \begin{bmatrix} \cfrac{\mathbf2}{\mathbf {1.78-(-1.78)}}\times\mathbf1.78 + \mathbf0\times\mathbf1 + \mathbf0 \times\mathbf0 + -\cfrac{\mathbf{1.78+(-1.78)}}{\mathbf {1.78-(-1.78)}}\times\mathbf0\\ \\ \mathbf0\times\mathbf1 + \cfrac{\mathbf2}{\mathbf {1-(-1)}}\times\mathbf1 +\mathbf0\times\mathbf0 + -\cfrac{\mathbf{1+(-1)}}{\mathbf {1-(-1)}}\times\mathbf1\\ \\ \mathbf0 \times\mathbf1.78+ \mathbf0\times\mathbf1 + \cfrac{-\mathbf2}{\mathbf {1-(-1)}}\times\mathbf0 + -\cfrac{\mathbf{1+(-1)}}{\mathbf {1-(-1)}}\times\mathbf1\\ \\ \mathbf0\times\mathbf1.78 + \mathbf0\times\mathbf1 + \mathbf0\times\mathbf0 + \mathbf{1}\times\mathbf1 \end{bmatrix} ?????????????????1.78?(?1.78)2?×1.78+0×1+0×0+?1.78?(?1.78)1.78+(?1.78)?×00×1+1?(?1)2?×1+0×0+?1?(?1)1+(?1)?×10×1.78+0×1+1?(?1)?2?×0+?1?(?1)1+(?1)?×10×1.78+0×1+0×0+1×1??????????????????
输出结果
[ 1 1 0 1 ] \begin{bmatrix} 1 \\ 1 \\ 0 \\ 1 \end{bmatrix} ?????1101??????
【OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影】RectangleRenderer类中定义目标数组
private final float[] mMatrix = new float[16];

修改顶点着色器
#version 300 es layout (location = 0) in vec4 vPosition; layout (location = 1) in vec4 aColor; uniform mat4 u_Matrix; out vec4 vColor; void main() { gl_Position= u_Matrix * vPosition; gl_PointSize = 10.0; vColor = aColor; }

修改onSurfaceCreated回调
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { ...... //在OpenGLES环境中使用程序片段 GLES30.glUseProgram(mProgram); uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix"); aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition"); aColorLocation = GLES30.glGetAttribLocation(mProgram, "aColor"); ...... }

onSurfaceChanged回调中
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES30.glViewport(0, 0, width, height); final float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width; if (width > height) { //横屏 Matrix.orthoM(mMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f); } else { //竖屏 Matrix.orthoM(mMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f); } }

onDrawFrame回调中
@Override public void onDrawFrame(GL10 gl) { GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT); GLES30.glUniformMatrix4fv(uMatrixLocation, 1, false, mMatrix, 0); //绘制矩形 GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 6); //绘制两个点 GLES30.glDrawArrays(GLES30.GL_POINTS, 6, 2); }

我们来观察一下最终的横竖屏状态
竖屏状态
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

横屏状态
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

左手与右手坐标系 左手坐标系
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

右手坐标系
OpenGL|android平台下OpenGL ES 3.0从矩形中看矩阵和正交投影
文章图片

项目地址:
https://github.com/byhook/opengles4android
参考:
《OpenGL ES 3.0 编程指南第2版》
《OpenGL ES应用开发实践指南Android卷》

    推荐阅读