title: OpenGL ES 2.0 —— 坐标系统 permalink: opengl-es-2.0/coordinates key: opengl-es-2.0-coordinates tags: OpenGL —
前言
了解了着色语言之后, 我们便可以编译 glsl 的程序了, 但是为了将我们想要绘制的效果, 转化为 glsl 的代码, 这需要一个将三维坐标转变为二维坐标, 是一种降维思考的过程
理解 GL 的坐标系统, 可以帮助我们更好的处理这一过程
概览
在 Open GL 渲染管线的过程中一共存在五个坐标系统

- 局部坐标系统(Local Space)
    
- 是对象相对于局部原点的坐标, 也是对象开始的坐标
 
 - 世界坐标系统(Word Space)
    
- 世界坐标是作为一个更大空间范围的坐标系统, 这些坐标是相对于世界的原点的
 
 - 观察坐标系统(View Space)
    
- 观察坐标是指以 摄像机 或 观察者 的角度观察的坐标
 
 - 裁剪坐标系统(Clip Space)
    
- 裁剪坐标是处理 [-1.0, 1.0] 范围内并判断哪些顶点将会出现在屏幕上
 
 - 屏幕坐标系统(Screen Space)
    
- 视口变换将位于 [-1.0, 1.0] 范围的坐标转换到由 glViewport 函数所定义的坐标范围内
 - 最后转换的坐标将会送到光栅器, 由光栅器将其转化为片段
 
 
这五个坐标系统均为右手坐标系统, 范围均为 [-1, 1], 如下图所示

接下来逐一的了解一下这五个坐标系统
一. 局部坐标系统
局部坐标系统(Local Space)是 GL 绘制的起点坐标, 所有的转换操作都是在局部空间坐标基础上进行的
局部空间坐标就是我们自己定义的起始坐标点
- 3D 游戏中, 在商店中审视自身角色所处的坐标系统
 
二. 世界坐标系统
如果我们想将我们所有的对象导入到程序当中, 它们有可能会全挤在世界的原点上(0,0,0), 然而这并不是我们想要的结果
因此我们需要一个更大的坐标系统, 用于处理我们在不同的局部坐标中定义的物体之间的交互, 这个坐标系统称之为世界坐标系统
- 3D 游戏中, 查看世界地图时所处的坐标系统
 
局部到世界坐标的转换
通过 Model Matrix(模型矩阵) 可以将我们定义在局部的坐标系统中的物体, 转换到世界坐标系中
三. 观察坐标系统
观察坐标系统就是从观察者的角度审视空间时, 看到的坐标系统
- 3D 游戏中, 第一人称视角的坐标系统
 
世界到观察坐标的转换
通过 View Matrix(观察矩阵), 可以将世界坐标系统坐标系统转为观察坐标系统
Android 提供的 api 如下
public class Matrix {
    
    public static void setLookAtM(
            float[] rm, int rmOffset,       
            float eyeX, float eyeY, float eyeZ,             // 描述眼睛位置
            float centerX, float centerY, float centerZ,    // 描述眼睛看向的位置
            float upX, float upY, float upZ                 // 描述视线的垂线
    );
    
}
四. 裁剪坐标系统
在一个顶点着色器运行的最后, OpenGL 期望所有的坐标都能落在一个给定的范围内, 且任何在这个范围之外的点都应该被裁剪掉(Clipped)
被裁剪之后所以剩下的坐标就将变为屏幕上可见的片段即裁剪坐标系统
- 3D 游戏中, 调节分辨率之后视野被裁剪了
 
从观察到裁剪坐标系统
通过 Projection Matrix(投影矩阵), 可以将世界坐标变为裁剪坐标, 投影的方式主要有 正交投影 和 透视投影
1. 正交投影
正交投影 (Orthographic Projection) 定义了一个类似立方体的平截头体,指定了一个裁剪空间,每一个在这空间外面的顶点都会被裁剪

- Near Plane: 近平面
 - Far Plane: 远平面
 
正交投影矩阵直接将 近平面 和 远平面之间的物体, 直接坐标映射到屏幕的二维平面内, 不符合人眼近大远小的视觉感受
Android 系统中提供的 Java api 如下
public class Matrix {
    public static void orthoM(
        float[] m, int mOffset,
        float left, float right, float bottom, float top, // 近远平面的大小
        float near,                                       // 近平面
        float far                                         // 远平面
    );
}
2. 透视投影
透视投影(Perspective Projection) 即以符合人眼观察到的近大远小的方式将三维物体投影到 2D 屏幕的投影方式

Android 提供的 API 如下
public class Matrix {
   
    public static void frustumM(
        float[] m, int offset,
        // 近平面左下角 (left, bottom, -near)
        // 近平面的右上角(right, top, -near)
        float left, float right, float bottom, float top,
        float near,                                       // 近平面
        float far                                         // 远平面
    );
    
    
    public static void perspectiveM(
        float[] m, int offset,
        float fovy,               // 视角的度数
        float aspect,             // 平面的宽高比
        float zNear,              // 近平面与视点的距离
        float zFar                // 远平面与视点的距离
    );
    
}
有了裁剪坐标之后, 其他的事情便交由 OpenGL 自行处理了
总结
坐标系统和整体的变化方式如下所示

将局部坐标转为可以栅格化的裁剪坐标的公式如下
\[V_{clip} = M_{projection} * M_{view} * M_{model} * V_{local}\]Android 上关于矩阵合并的 API 如下
public class Matrix {
 
    // result = lhs * rhs
    public static native void multiplyMM(
        float[] result, int resultOffset,
        float[] lhs, int lhsOffset, 
        float[] rhs, int rhsOffset
    );
}
经过上面的学习, 我们对 OpenGL 坐标系统有了个感性的认知, 可以发现很多地方都与日常生活中的 3D 游戏相互吻合, 这正好可以帮助我们更好的理解
学习了渲染管线, glsl 和 GL 坐标系统, 接下来便是进行真正的绘制了