今天给你们讲讲OpenGL ES
中的坐标系和矩阵变换,OpenGL ES
中的坐标系实际上有不少,在我以前的文章中,由于对应的效果对坐标系的要求不高,所用的坐标其实是跳过的一系列的坐标变换,这点后面会给你们说,而矩阵变换就是将坐标从一个坐标系转换到另外一个坐标系下。android
咱们先来了解一下OpenGL ES
中的虚拟摄像机,在OpenGL ES
中,有一个虚拟的摄像机,咱们渲染出来的景像实际上就是这个虚拟摄像机所拍摄到的景像,这个虚拟摄像机的效果和咱们真实生活中的摄像机效果更相似,咱们能够经过调整虚拟摄像机的位置、朝向等参数来获得不一样的观察结果,进而获得不一样的渲染画面,举个形象一点的例子,例如咱们平时玩的3D游戏,有至关一部分是用OpenGL ES
渲染的,咱们控制角色移动靠近一个物体时,物体就会变大,就像咱们拿着一个摄像机朝一个物体走过去同样,拍摄到的物体就会变大,角色转身时,能看到不一样的画面,就想是咱们拿着一个摄像机朝不一样的方向拍。git
不过这个摄像机最终在OpenGL ES
中是以矩阵的形式呈现出来的,咱们先来看一张总体流程图:github
这张图给咱们展现了OpenGL ES
中的坐标系及矩阵变换过程,具体过程是这样:spa
OpenGL ES
有个世界坐标系,咱们渲染的物体就是在世界坐标系中,咱们的模型须要放到世界坐标系中,那么当咱们还没放的时候,模型就和世界坐标系没有联系,它就还处于本身的坐标系中,咱们叫作模型坐标系、局部空间、局部坐标系,也就是图中的LOCAL SPACE。Camera
的位置、朝向的点坐标、以及Camera
的上方向向量就能将观察状态定下来,而这些设置最终会转换成OpenGL ES
中的视图矩阵,对应图中的VIEW MATRIXView Matrix
的变换后,咱们观察它的结果就肯定了,图中是从距离它必定的距离、上往下观察它,这时候的点坐标就来到了视图坐标系下,对应图中的VIEW SPACE
OpenGL ES
提供正交投影和透视投影,正交投影没有近大远小的效果,无论在什么距离上看,都同样大,透视投影则有近大远小的效果,也是符合咱们实际生活的一种效果,透视投影应用得比较多,可看下面这张经典图:这有一个初学者可能会误解的点,就是认为OpenGL ES
的坐标范围就是0~1,超出0或1就会超出屏幕就看不见,这种理解其实不许确,坐标范围是多少,取决于说的是什么坐标系,咱们在平时作的更可能是2D渲染,经常就是像我以前的教程里写的坐标那样,直接使用0~1的NDC坐标系,不须要矩阵变换,但实际上OpenGL ES
的世界坐标系是没有范围的,是负无穷到正无穷,至于某些坐标下的东西是否最后能渲染出来看到,这就取决于前面说的矩阵变换过程,例如将虚拟摄像机对准一个距离999999的物体,而且物体在裁剪区域内,也是能看到的,并非说坐标必定要是0~1。3d
矩阵变换主要仍是用在3D渲染和一些特殊的2D效果上,例如一个偏转变形的2D平面,若是直接设置NDC坐标,出来的效果会有畸变,须要本身进行透视矫正,关于透视矫正,这里先不展开说了。code
接下来咱们来看一下如何在OpenGL ES
中使用矩阵变换,首先看模型矩阵,前面提到过,模型矩阵是把坐标从模型的局部坐标系转换到世界坐标系,这个变换不只是位置的变换,还能够有旋转和缩放,例如把一个物体缩小一点、旋转一点后放到世界坐标系中的某个位置上,所以模型矩阵实际上包含的平移、旋转和缩放,它就等于平移矩阵、旋转矩阵和缩放矩阵相乘:orm
val translateMatrix = getIdentity()
val rotateMatrix = getIdentity()
val scaleMatrix = getIdentity()
val modelMatrix = getIdentity()
// 模型矩阵计算
// Calculate the Model matrix
Matrix.translateM(translateMatrix, 0, translateX, translateY, translateZ)
Matrix.rotateM(rotateMatrix, 0, rotateX, 1f, 0f, 0f)
Matrix.rotateM(rotateMatrix, 0, rotateY, 0f, 1f, 0f)
Matrix.rotateM(rotateMatrix, 0, rotateZ, 0f, 0f, 1f)
Matrix.scaleM(scaleMatrix, 0, scaleX, scaleY, scaleZ)
Matrix.multiplyMM(modelMatrix, 0, rotateMatrix, 0, scaleMatrix, 0)
Matrix.multiplyMM(modelMatrix, 0, modelMatrix, 0, translateMatrix, 0)
复制代码
视图矩阵则是对应前面说的虚拟摄像机,它共由虚拟摄像机的位置、朝向的点坐标、以及虚拟摄像机的上方向向量肯定,OpenGL ES
提供了方法来获得视图矩阵,咱们只须要给它传递这些参数就好了:cdn
val viewMatrix = getIdentity()
// 视图矩阵计算
// Calculate the View matrix
Matrix.setLookAtM(
viewMatrix,
0,
cameraPositionX, cameraPositionY, cameraPositionZ,
lookAtX, lookAtY, lookAtZ,
cameraUpX, cameraUpY, cameraUpZ
)
复制代码
接下来是投影矩阵,前面提到投影矩阵有正交投影和透视投影两种,本文中使用透视投影,它也是由OpenGL ES
提供的方法来获得,所须要的参数为近平面矩阵的上、下、左、右坐标,近平面距离和远平面矩离(这张图中的Left、Right、Bottom、Top标在了远平面上,实际在OpenGL ES
中生成透视投影矩阵的方法参数中的left
、抄下t
、bottom
、up
指的是近平面):blog
生成透视投影矩阵代码以下:教程
val projectMatrix = getIdentity()
// 透视投影矩阵计算
// Calculate the Project matrix
Matrix.frustumM(
projectMatrix,
0,
nearPlaneLeft, nearPlaneRight, nearPlaneBottom, nearPlaneTop,
nearPlane,
farPlane
)
复制代码
如今,模型矩阵、视图矩阵和投影矩阵都生成了,下面将这三个矩阵相乘获得最终的变换矩阵(MVP):
val mvpMatrix = getIdentity()
// MVP矩阵计算
// Calculate the MVP matrix
Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0)
Matrix.multiplyMM(mvpMatrix, 0, projectMatrix, 0, mvpMatrix, 0)
复制代码
而后将MVP
矩阵传递到Vertex Shader
中与顶点相乘进行矩阵变换:
#version 300 es
precision mediump float;
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_textureCoordinate;
layout(location = 2) uniform mat4 u_mvp;
out vec2 v_textureCoordinate;
void main() {
v_textureCoordinate = a_textureCoordinate;
gl_Position = u_mvp * a_position;
}"
复制代码
我作一了个demo,能够调节各类参数实时查看效果:
这个demo是渲染一个立方体,立方体每一个面上贴上一个花的纹理,上面这张图是一个初始状态,咱们来看一下初始的参数:
var translateX = 0f
var translateY = 0f
var translateZ = 0f
var rotateX = 0f
var rotateY = 0f
var rotateZ = 0f
var scaleX = 1f
var scaleY = 1f
var scaleZ = 1f
var cameraPositionX = 0f
var cameraPositionY = 0f
var cameraPositionZ = 5f
var lookAtX = 0f
var lookAtY = 0f
var lookAtZ = 0f
var cameraUpX = 0f
var cameraUpY = 1f
var cameraUpZ = 0f
var nearPlaneLeft = -1f
var nearPlaneRight = 1f
var nearPlaneBottom = -glSurfaceViewHeight.toFloat() / glSurfaceViewWidth
var nearPlaneTop = glSurfaceViewHeight.toFloat() / glSurfaceViewWidth
var nearPlane = 2f
var farPlane = 100f
复制代码
和模型矩阵Model Matrix相关的参数是translate
、rorate
和scale
,这里初始时咱们不对模型进行变换。
和视图矩阵VIEW MATRIX相关的参数是CameraPosition
、lookAt
和CameraUp
,咱们把虚拟摄像机放在(0, 0, 5)
这个位置,而且让它对向(0, 0, 0)
,而且摄像机的上方向是(0, 1, 0)
,也就是把摄像正立着。
和投影矩阵PROJECT MATRIX相关的参数是近平面nearPlane
上下左右和距离、以及远平面farPlane
距离,咱们将近平面左右设为-1和1,而且上下根据GLSurfaceView
和尺寸设置,这样是为了避免变形,近平面设置为2,远平面设置为100。
咱们渲染的这个立方体顶点坐标都是-1和1,也就是在原点那里,因此上面的图中咱们看到的效果就是从Z轴上的(0, 0, 5)
这个位置正对看向这个立方体,所以只能看到正面。
下面我调节一些参数看看效果:
上面中咱们经过设置模型矩阵的参数将立方体变换到了(3, 4, -5)
这个位置,由于咱们的摄像机没动,仍是对着原点看,那么至关于这个立方体在摄像机的右上方,所以摄像机能看到这个立方体的左面和下面。
再继续看:
这个是把立方体旋转了一下,没什么好解释的。
再来看:
这个是旋转加上了缩放,把x坐标变小、y坐标变大了,因此横向的边就短了,竖向的就长了。
再来:
这个是立方体旋转加上再把虚拟摄像机看的点从(0, 0, 0)
变成了(0, 2, 0)
,所以效果就是立方体就在视野下方了。
再看:
这个是把虚拟摄像机的上方向从(0, 1, 0)
变成了(1, 1, 0)
,也就是原本虚拟摄像机是正立着的,如今变换歪了45度,因此拍摄到的画面也歪了45度。
好了,还有不少种状况,你们能够到demo里玩玩,看看渲染出来的效果是否与本身的理解和预期一致。
这节的内容比较复杂,若是有疑问,欢迎给我留言讨论哈。
代码在我github
的OpenGLESPro
项目中,本文对应的是SampleMatrixTransform
,项目连接:github.com/kenneycode/…
感谢阅读!