在android上如果手机屏幕由竖屏切换到横屏,这个时候Ui会发生变化,变形,通常在Android中我们可以通过写不同的xml来适配这种屏幕变化引起的ui布局变化。然而,在opengl es也会存在由于屏幕切换导致ui问题,为此,opengl提出了归一化坐标来解决这个问题。
归一化设备坐标:
具体说来就是把较小(端)边固定在[-1,1]范围,而较大边(端)按屏幕尺寸比例缩放。
比如1280x720的手机屏幕,在竖屏时,我们把较小边即宽范围限定在【-1,1】,则高在范围[-1280/720,1280/720]或[-1.78,1.78]范围内。同理在横屏时把较小边即高范围限定在【-1,1】,则宽在范围[-1280/720,1280/720]或[-1.78,1.78]。很明显这样坐标系发生了变化,
这样无论屏幕是横屏还是竖屏,物体看上去都是一样的。
opengl两种投影:正交投影和透视投影。正交投影,不管远近物体大小不会发生变化即不会因为是距离远而物体投影变小,是平行的。
向量:
一个向量是一个有多个元素的数组。在opengl中,一个位置通常是一个4维的向量通常是x,y,z,w这4个分量,颜色也是4维(红,绿,蓝,alpha)。
矩阵与矩阵/向量相乘:
满足第一个矩阵的列数等于第二个矩阵的行数,相乘后的新矩阵行数等于第一个矩阵的行数,列数等于第二个矩阵列数。如图:
单位矩阵:
主对角线上的元素都为1,其余元素全为0的n阶矩阵称为n阶单位矩阵。任何矩阵和单位矩阵相乘都是它本身。
平移矩阵:
假定有坐标点(2,2,0,1)沿x平移3单位,y也是3个单位,z还是0,w默认是1,opengl默认w分量是1,则平移公式是:
所以可以得出平移矩阵,
再说一次,z下面w分量是1,是因为opengl默认w分量是1。
其实,正交投影可以理解为一种平移矩阵,所以正交投影又叫平行投影。
opengl两种投影:
正交投影和透视投影,正交投影不会因为远近而物体投影大小发生变化,是一种平行投影。在Android opengl中如本例代码AirHockeyRenderer类的onSurfaceChanged方法中android.opengl.Matrix.orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)中float[] m为正交投影矩阵坐标,按照上面之前分析至少需要16个长度才能存的下正交投影坐标值,其他参数意义如下贴图:
即android.opengl.Matrix.orthoM方法相当于进行了如上正交矩阵运算,这将使物体左右,上下,前后坐标归一化设备坐标后始终在[-1,1]之间,相当于做了这种正交投影做了一种关系映射,把物体坐标归一化到[-1,1]之间,当然不在这之间的是看不到的。
分析了理论,接下来是具体怎么代码使用?
为了使用这种正交投影,打开本节的simple_vertex_shader.glsl文件,发现有个uniform mat4 u_Matrix;这正是我们在顶点着色器里面来使用它,代表一个4x4矩阵。
在AirHockeyRenderer类,定义了String U_MATRIX = “u_Matrix”,来保持顶点着色器里面定义的uniform 名字, private final float[] projectionMatrix = new float[16];这个用于存储正交投影矩阵,private int uMatrixLocation;存储矩阵的位置。在 onSurfaceCreated去获取这个位置uMatrixLocation = glGetUniformLocation(program, U_MATRIX)。
/**
* onSurfaceChanged is called whenever the surface has changed. This is
* called at least once when the surface is initialized. Keep in mind that
* Android normally restarts an Activity on rotation, and in that case, the
* renderer will be destroyed and a new one created.
*
* @param width
* The new width, in pixels.
* @param height
* The new height, in pixels.
*/
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
// Set the OpenGL viewport to fill the entire surface.
glViewport(0, 0, width, height);
final float aspectRatio = width > height ?
(float) width / (float) height :
(float) height / (float) width;
if (width > height) {
// Landscape
orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
} else {
// Portrait or square
orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
}
}
这端代码会把屏幕当前方向计算在内创建正交投影建立这种映射。如果横屏模式下,会扩展宽度坐标空间到【-aspectRatio, aspectRatio】同时高度范围在【-1,1】。如果竖屏模式下,会扩展高度坐标空间到【-aspectRatio, aspectRatio】同时宽度范围在【-1,1】。这个实质是物体坐标其实无论横竖都没有变化,只是在横竖屏发生后坐标系发生了变化,看上去的效果物体的宽高还是没有变化,这就达到了不会因为横竖屏导致物体被压扁的视觉效果。
最后一步在onDrawFrame中 // Assign the matrix
glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0)是将正交投影矩阵传递给着色器。
到此,本文完成了横竖屏切换物体变形的问题。本文代码如下:
https://github.com/pangrui201/OpenGlesProject/tree/master/OpenGlesProject_lesson4