opengles-坐标系和矩阵
坐标系和矩阵是opengl的基础也是难点,本文总结了opengl的各种坐标系和矩阵,如果有错误欢迎指正。
1.什么是opengl和opengl es
OpenGL(全写Open Graphics Library)是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。OpenGL是个与硬件无关的软件接口,可以在不同的平台如Windows 95、Windows NT、Unix、Linux、MacOS、OS/2之间进行移植。因此,支持OpenGL的软件具有很好的移植性,可以获得非常广泛的应用。
Opengl2.0开始支持glsl(OpenGL Shading Language),opengl1.x是固定渲染管线的已经被淘汰无需考虑。Opengles是OpenGL裁剪定制而来的,专为手机,游戏机等嵌入式设备而设计,在安卓设备上的opengl就是opengles。
安卓使用GLSurfaceView封装和简化opengl的开发,它帮助我们建立单独的opengl线程,并且支持连续或者按需渲染,它使用EGL来处理屏幕设置,Egl是OpenGL与底层窗口系统之间的接口。
- It provides a dedicated render thread for OpenGL so that the main thread is not stalled.
- It supports continuous or on-demand rendering.
- It takes care of the screen setup for you using EGL, the interface between OpenGL and the underlying window system.
2.opengl的各种坐标系和矩阵
坐标系
-
安卓设备坐标系
屏幕左上点为原点,x轴向右,y轴向下,跟随屏幕方向变化而变化。
-
纹理坐标系 uv coordinates
2d纹理坐标系也就是UV坐标系,水平方向是U,垂直方向是V,通过这个平面的,二维的UV坐标系。我们可以定位图象上的任意一个象素。左下角为原点,U轴向右,V轴向上,范围[0,1]。
-
模型坐标系 object coordinates
归一化的右手坐标系,以模型内某一点为原点建立的坐标系,其它模型内的点的位置可以用相对于模型内的原点来表示的坐标系,该坐标系仅在模型内有效。
-
世界坐标系 world coordinates
右手坐标系,屏幕中心点位原点x轴向右,y轴向上,z轴向屏幕外。可以理解为无限大的坐标系,用来描述物体或者光源的位置,跟随屏幕方向变化而变化。。
-
视点坐标系 eye coordinates
以视点(相机)为原点,以视线的<u>负方向为Z轴正方向</u>的坐标系,注意是相机看向的其实是
-z
轴。 -
齐次坐标系 clip coordinates
齐次坐标就是用N+1维来代表N维坐标,在opengl中以第四个值w表示,齐次坐标系表示同一个点并不唯一[8,4,2]、[4,2,1]表示的都是二维点[4,2]。
齐次坐标系可以使用(x,y,z,w)表示一个点,也可以表示向量。当w==1时表示点(仅限eye坐标系及之前的坐标系),当w==0时表示向量。
齐次坐标系使得点和向量可以用统一的vec4来表示。
-
opengl设备标准化坐标系 ndc coordinates
Normalized device coordinates(ndc坐标系),采用的是左手三维坐标系(也就是z轴其实是指向屏幕里的),三个坐标轴都是[-1,1],是opengl服务端唯一理解的内部坐标系,跟随屏幕方向变化而变化。
opengl是server-client模式,应用程序是客户端,图形硬件厂商提供的OpenGL实现是服务器。而client也就是应用程序内部采用的都是右手坐标系,只是是在使用Ortho或者Perspective来生成投影矩阵的时候,近远视点分别加了负号做了转换。
glViewport(x,y,width,height)用来函数指明标准坐标系和屏幕坐标映射计算关系,在安卓中一般用在onSurfaceChanged的回调里。一般这个计算不需要我们来做,此处只是作为补充说明。
标准坐标转屏幕坐标公式
矩阵
opengl的矩阵是4*4的矩阵,可以用16个元素的数组表示,opengl采用column-major order(列主序矩阵),也就是一列一列从数组读取数据。由于列主序的存在,导致4×4矩阵作用于vector4(例如shader中的位置、颜色等,实际为4×1矩阵),所以vector4只能左乘变换矩阵(线性代数矩阵乘法规定,它只有在第一个矩阵的列数(column)和第二个矩阵的行数(row)相同时才有意义)。
列主序矩阵-
modelMatrix模型矩阵:
模型矩阵可以包括对模型的缩放、旋转、平移操作。先缩放再旋转再平移,由于矩阵乘法不满足交换律,顺序不能变化。例如先向左转再走两步,和先走两步再像左转是完全不一样的结果。
<u>如果要改变顺序一定要清楚其带来的影响</u>
scaleMatrix在矩阵中的表示:
缩放变换矩阵rotationMatrix矩阵中比较复杂,暂时不列出
translateMatrix矩阵变换
平移变换矩阵表示综上,最终的modelMatrix = TranslationMatrix * RotationMatrix * ScaleMatrix,从右向左乘,先变换的最先乘。
通过左乘模型矩阵可以将局部物体坐标系坐标转化为世界坐标系的坐标。
vec4 worldCoordinateVector = modelMatrix * modelCoordinateVector;
-
viewMatrix视图矩阵
setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)
用来生成视图矩阵,主要规定视点位置、焦点位置、相机y轴up朝向。
世界坐标系的坐标值左乘视图矩阵可以将世界坐标系转化为视点相机坐标系。
vec4 cameraCoordinateVector = viewMatrix * worldCoordinateVector;
理论上viewMatrix和modelMatrix是可以相互替代的,如变相机位置向左移动5厘米和改变物体位置像右移动5厘米都可以实现相同的结果,但存在多个物体的话显然移动物体合适。
-
projectMatrix投影矩阵
投影矩阵并不是用来计算射线碰撞的,也不是所谓的将3d物体投射到2d平面上。投影矩阵真正作用来是把相机坐映射为标准的[-1,1]的齐次坐标,也就是带w值的标准设备坐标系,并且可以clip视锥之内的点,投影内的点记为vertex(clip)。
opengl负责将齐次坐标系转化为标准坐标,也就是PerspectiveDivide(透视除)由opengl内部调用,无需我们关心。透视除其实就是将x/y/z值分别除以w来得到屏幕标准坐标。
vertex(ndc) = PerspectiveDivide(vertext(clip));
一旦拿到ndc坐标,opengl会使用公式将它映射到viewport屏幕坐标,屏幕上就显示了当前点。
关于w
w主要是用在透视投影中的,在视点坐标系下其值一般初始化为1,而经过透视矩阵变换之后,其中会变为-z(注意负号,因为需要转为左手坐标系),经过正交投影变换后其值仍是原值,即1。
所谓透视矩阵,就是通过各个分量divide by w来实现近大远小的透视效果的。w虽然对正交矩阵没有什么用,但是统一了正交和透视矩阵的处理,避免了直接divide z。
正交投影
orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)
用来生成正交投影,near和far分别表示近视点和远视点的距离需要为正,正交矩阵视锥是一长方体,物体没有近大远小特性,同一个物体无论远近都是一样大小的,这看起来并不自然,但是却可以保证物体无论远近都保持一致的外观,某些游戏画面就需要这个特点。
正交矩阵应该考虑品屏幕比例,不然会导致横竖屏切换后画面变形。
//横屏,例如800*600,横屏width是800,竖屏width为600 float aspectRatio = (float) width / (float) height; orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1, 1, -1, 1); //竖屏 orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1, 1, -1, 1);
安卓没有任何配置的情况下,默认相机处于世界坐标系(0,0)点,默认使用的是[-1,1]的立方体正交投影,投影矩阵内的所有点在屏幕上都可见,故可以看到(0,0,0)点的物体,即使默认相机在(0,0,0)点也可以看到。
透视投影
perspectiveM(float[] m, float yFovInDegrees, float aspect, float n, float f)
用来定义透视矩阵
透视矩阵也应该考虑品屏幕比例,不然会导致横竖屏切换后画面变形,同样near和far需要为正数。
//横屏,例如800*600,横屏width是800,竖屏width为600 perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f);
投影矩阵小结:
1.无论是正交还是透视投影,投影矩阵作用于EyeVertex点后,会将该点转化为齐次式表示的标准化设备坐标。使用正交投影的的w并没有用,只是为了保持和透视投影处理的一致性,w值经过左乘之后没有任何变化。
2.透视投影的w经过矩阵左乘之后会变成-z。
3.正交投影和透视投影作用后的x,y,z值不相同(表达式不同),具体表达式可以参考下方链接。
矩阵左乘导致坐标系的转换过程
关于投影矩阵的数学计算,请参考如下网站,讲的非常清晰,耐心看完一定会理解的。
http://www.songho.ca/opengl/gl_projectionmatrix.html#perspective