Learn OpenGLES: 正交变换

上一节中,我们遗留了一个Android适配的问题。 这个问题表现为我们创建的物体长宽比例如果与屏幕宽高比不一致的画,会被屏幕拉伸。 

为什么会拉伸? 
首先我们来认识齐次座标 
 , 这个就是一个齐次座标, 其中x, y, z 分别表示世界座标系中x, y, z轴的座标。 关于空间中一点的座标,你可以想象下有一个原点,基于这个原点建立一个座标系,而物体相对于该原点的位置就是这个座标系下的座标。 
   而w 简单来说,可以理解为一个尺度空间。 即如果w = 1时, 这个空间是与这个座标系等同的空间;如果w = 2,即这个空间就会缩小一半。w越大,其空间其实越小, w越小,其空间为越大。 如果w为0 时, 那么x, y,z 全部趋于无穷, 即它们在无穷远, 再也回不来了。 


关于齐次座标,它其实可以用空间中的一条直线来表示即 
   
即满足上式的一点 (x, y, z, 1)必在该直线上,如果等式两边同时乘以w 则,所表示的点(wx, wy, wz, w)也在该直线上,且为同一个点。 


其次,了解下OpenGL管线流程中的座标变换 
一次是 clip变换,即通过 w 齐次座标,将座标缩放在尺度为1的空间里 
一次为三维变换,即将clip后的座标,映射为摄像机屏幕上的二维座标。 
一次为glViewport变换,即将二维座标乘以屏幕的宽和高 变为屏幕上的图像。 


对于clip变换,我们已经大体了解了,就是缩放。 
而对于三维变换,又可分为两种: 透视变换 和 正交变换 
关于这两种变换,可以形象的用一下两幅图来表示: 
 



这两个图都是我们看到的三维世界, 但左图具有很强的透视效果,总体表现为近大远小的感觉。 空间中的点与摄像机光心连线与摄像机屏幕所交点即为屏幕的成像点(小孔成像原理), 因此,较近的物体总能形成较大像。   
而右图由于离铁路较近,俯视正对,基本可近似为一个正交变换。 即所有的空间的点都是平行映射到屏幕上的, 这使得空间中点的Z轴失去作用。 所以,正交变换更适合二维图像。 


分析我们之前的屏幕拉伸原因: 
opengl中, clip变换后的点都在[-1. 1]之间, 因此,边界为-1或 1。只考虑宽和高的话, 则为一个 2x2的正方形。 
也就是,物体所在空间的高比为1:1,  假如,我们的屏幕是2:1的, 则最后经过glViewport变换后,宽就被拉伸了2倍。 
下文,我们称clip变换的空间为clip空间。 


最后,来看下,正交变换矩阵: 
 

其中, right, left, top, bottom, far, near 为 clip空间的边界。 
  
我们假定空间中的一点为: ,(这里不考虑z轴座标) 正交变换矩阵对该点作用(变换)后 ,新的座标点为: 

 
上式表示,变换后的x轴座标, 不管x座标为何,其变换后的点总会落在(left, right)之间。 opengl中,left为-1, right 为1。 
同理有轴座标。 
那么,进而可得出,如果屏幕的宽高比为aspect:1,则,应该让clip空间的bottom 为-aspect, top 为aspect。 反之,如果为1:aspect, 则应该让left 为-aspect, right为-aspect。 
这样,经过正交变换后,映射到屏幕中的座标经过glViewport 变换,就与屏幕的宽高比就为1:1,就不会出现拉伸的现象了。 
———————————————————————————————————————————— 
知道了,上述原理后,我们来修改代码: 
首先,在shader中添加 正交变换。 

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
attribute vec4 a_Position;
attribute vec4 a_Color;
       
       
uniform mat4 u_Projection;
       
varying vec4 v_Color;
void main()
{
    v_Color = a_Color;
    gl_Position = u_Projection*a_Position;
}
有线性代数的基本知识,知道 一个4x4的矩阵,乘以一个4x1的座标为一个4x1的座标, 这中间其实完成了一个变换,就像上面我们讲的原理一样。 


接着,定义变量: 

复制代码
1
2
3
private static final String U_PROJECTION ="u_Projection";
private int uProjectionLocation;
private final float[] projectionMatrix = new float[16];
这里定义变换矩阵为一个大小为16的数组,表示4x4的矩阵, 其中前四个元素表示矩阵的第一列,以此类推。 


然后, 在onSurfaceCreated里, 找到shader里投影矩阵的位置, 

复制代码
1
uProjectionLocation = glGetUniformLocation(program, U_PROJECTION);

在onSurfaceChanged,根据屏幕的宽高比定义 正交变换矩阵: 

复制代码
1
2
3
4
5
6
7
8
9
10
final float aspectRatio = width>height ?
                (float) width / (float) height:
                (float) height/(float) width;
       
 if(width > height)
  {
      orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f );
 }else{
      orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
}

orthoM 是我们静态库, android.opengl.Matrix里的函数,它的源代码基本就是上述的公式。它的参数,第一个,是我们传递的数组, 第二个是数组的起始位置,后面依此为clip空间的 left, right, bottom, top , near, far。 

最后,在onDrawFrame里添加: 

复制代码
1
glUniformMatrix4fv(uProjectionLocation, 1false, projectionMatrix, 0); // 将矩阵传递给shader


看一下效果: 
 

比起上一帖的竖屏是不是好多了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章