【安卓开发系列 -- APP】OPENGL ES -- 投影与变换
【1】OPENGL ES 相机设置
摄像机的设置需要 3 方面的信息,即摄像机的位置、观察的方向以及 up 方向;
1. 摄像机的位置即其在 3D 空间中的坐标;
2. 摄像机观察的方向为摄像机镜头的指向,用一个观察目标点来表示(通过摄像机位置与观察目标点可以确定一个向量,此向量便代表了摄像机观察的方向);
3. 摄像机的 up 方向为摄像机顶端的指向,用一个向量来表示;
相机位置与人眼类比
OPENGL ES 设置相机相关代码
//设置摄像机的方法
public static void setCamera
(
float cx,
float cy,
float cz,
float tx,
float ty,
float tz,
float upx,
float upy,
float upz
)
{
// 通过调用 Matrix 类的 setLookAtM 方法来完成对摄像机的设置
Matrix.setLookAtM
(
mVMatrix, //存储生成矩阵元素的float[]类型数组
0, //填充起始偏移量
cx,cy,cz, //摄像机位置的X、Y、Z坐标
tx,ty,tz, //观察目标点X、Y、Z坐标
upx,upy,upz //up向量在X、Y、Z轴上的分量
);
}
【2】OPENGL ES 中常见的投影方式
OpenGL ES 3.0 中常用的投影模式分别为正交投影与透视投影;
【2.1】正交投影
OpenGL ES 3.0 中,根据应用程序中提供的投影矩阵,管线会确定一个可视空间区域即视景体;视景体是由 6 个平面确定的,这 6 个平面分别为,上平面(up)、下平面(down)、左平面(left)、右平面(right)、远平面(far)、近平面(near);场景中处于视景体内的物体会被投影到近平面上(视景体外面的物体将被裁剪掉),然后再将近平面上投影出的内容映射到屏幕上的视口中;
正交投影是平行投影的一种,其投影线(物体的顶点与近平面上投影点的连线)是平行的,故其视景体为长方体,投影到近平面上的图形不会产生真实世界中“近大远小”的效果;
//设置正交投影的方法
public static void setProjectOrtho
(
float left,
float right,
float bottom,
float top,
float near,
float far
)
{
Matrix.orthoM
(
mProjMatrix, //存储生成矩阵元素的float[]类型数组
0, //填充起始偏移量
left, right, //near面的left、right
bottom, top, //near面的bottom、top
near, far //near面、far面与视点的距离
);
}
其中,参数 x、 y 为视口矩形左下侧点在视口用屏幕坐标系内的坐标, width、 height 为视口的宽度与高度,视口用屏幕坐标系的原点位于应用程序中用于渲染 3D 场景的 GLSurfaceView 类或其子类的对象所占屏幕中矩形区域的左下角,坐标系中 x 轴向右, y 轴向上;
【2.2】透视投影
透视投影的投影线是不平行的,投影线相交于视点,通过透视投影,可以产生现实世界中“近大远小”的效果,透视投影中,视景体为锥台形区域;
//设置透视投影
public static void setProjectFrustum(
float left, // near面的left
float right, // near面的right
float bottom, // near面的bottom
float top, // near面的top
float near, // near面与视点的距离
float far // far面与视点的距离
) {
Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
}
【3】OPENGL ES 中常见的变换方式
【3.1】基本变换公式
基本变换都是通过将表示点坐标的向量与特定的变换矩阵相乘完成的,进行基于矩阵的变换时,三维空间中点的位置需要表示成齐次坐标形式;即在 x、 y、 z 3 个坐标值后面增加第四个量 w,未变换时 w 值一般为 1;P(
) 与一个特定的变换矩阵 M 相乘即可以完成一次基本变换,得到变换后点 Q 的齐次坐标向量(
);
【3.2】平移变换
变换矩阵
上述矩阵中的
、
、
分别表示平移变换中沿 x、 y、 z 轴方向的位移
【3.3】旋转变换
变换矩阵
OpenGL ES 中,旋转角度的正负由右手螺旋定则确定,所谓右手螺旋定则即右手握住旋转轴,使大姆指指向旋转轴的正方向, 4 指环绕的方向即为旋转的正方向,即旋转角度为正值;
【3.4】缩放变换
变换矩阵
上述矩阵中的
、
、
分别表示缩放变换中的沿 x、 y、 z 轴方向的缩放率;
【3.5】OPENGL ES 中变换的实现机制
OpenGL ES 中变换的实现机制可以理解为首先通过矩阵对坐标系进行变换,然后根据传入渲染管线的原始顶点坐标在最终变换结果坐标系中的位置来进行绘制;
左侧的是原始坐标系,原始坐标系首先向右沿 x 轴进行了平移(得到上标为“'”的坐标系),然后绕 z 轴旋转了30°(得到上标为“''”的坐标系),接着沿 x、 y、 z 轴分别按不同的倍数缩放得到了最终的结果坐标系(上标为“'''”);
最后渲染管线按照物体的原始顶点坐标值在最终结果坐标系里的位置进行绘制,这就得到了场景中右侧变换过的立方体,虽然看起来已经变为斜着的长方体,但对于其绘制坐标系而言其还是立方体,只是由于坐标系变化了才产生这种效果;
【3.6】OPENGL ES 中空间概念与转换流程
【3.6.1】OPENGL ES 中的几种空间
物体空间
物体空间就是需要绘制的 3D 物体所在的原始坐标系代表的空间;
世界空间
世界空间就是物体在最终 3D 场景中的摆放位置对应的坐标所属的坐标系代表的空间;
摄像机空间
物体经摄像机观察后,进入摄像机空间,摄像机空间其指的是以观察场景的摄像机为原点的一个特定坐标系代表的空间,在这个坐标系中,摄像机位于原点,视线沿z 轴负方向, y 轴方向与摄像机 UP 向量方向一致;
剪裁空间
注意只有在视景体里面的物体才能够最终被用户观察到,即并不是摄像机空间中所有的物体都能最终被观察到,只有在摄像机空间中位于视景体内的物体才能最终被观察到;因此,将摄像机空间内视景体内的部分独立出来经过处理后就成为了剪裁空间;
标准设备空间
对剪裁空间执行透视除法后得到的就是标准设备空间,对于 OpenGL ES 而言标准设备空间 3 个轴的坐标范围都是-1.0~1.0;
实际窗口空间
实际窗口空间一般代表的是设备屏幕上的一块矩形区域,其坐标以像素为单位,即视口对应的空间;
【3.6.2】OPENGL ES 中空间转换流程
要绘制出屏幕上绚丽多姿的 3D 场景,就需要将每个物体从自己所属的物体空间依次经世界空间、摄像机空间、剪裁空间、标准设备空间进行变换,最终到达实际窗口空间,从一个空间到另一个空间的变换就是通过乘以各种变换矩阵以及进行一些必要的计算来完成;
1. 从物体空间到世界空间的变换是通过乘以基本变换总矩阵来实现;基本变换总矩阵就是将用于实现各种基本变换(缩放、平移、旋转)的矩阵,根据变换需要依次相乘而得到的结果矩阵;
2. 从世界空间到摄像机空间的变换是通过乘以摄像机观察矩阵来实现;
3. 从摄像机空间到剪裁空间的变换通过乘以投影矩阵来完成,根据需求的不同可以选用正交投影或透视投影的相关变换矩阵;乘以投影矩阵后,任何一个点的坐标[x,y,z,w]中的 x、y、z 分量都将在-w~w 内;
4. 从剪裁空间到标准设备空间的变换通过执行透视除法来完成;所谓透视除法就是将齐次坐标[x,y,z,w]的 4 个分量都除以 w,结果为[x/w,y/w,z/w,1],本质上就是对齐次坐标进行了规范化;
5. 从标准设备空间到实际窗口空间变换的主要工作是将执行透视除法后的 x、y 坐标分量转换为实际窗口的 xy 像素坐标;主要的思路是将标准设备空间的 xy 平面(两个轴的坐标范围都是−1.0~1.0,构成一个矩形)对应到视口(也是一个矩形)上,将-1.0~1.0 内的 x、 y 坐标折算为视口上的像素坐标;
【4】OPENGL ES 中的绘制方式
OpenGL ES 中支持的绘制方式大致分 3 类,包括点、线段、三角形,每类中包括一种或多种具体的绘制方式,各种具体绘制方式的说明如下所列;
GL_POINTS,此方式是点类下的唯一一个绘制方式,其将传入渲染管线的一系列顶点单独进行绘制,不组装成更高一级的图元(如线段、三角形等);
GL_LINES,此方式是线段类的一种,其将传入渲染管线的一系列顶点按照顺序两两组织成线段进行绘制;注意若顶点个数为奇数,管线会自动忽略最后一个顶点;
GL_LINE_STRIP,此方式是线段类的一种,其将传入渲染管线的一系列顶点按照顺序依次组织成线段进行绘制;
GL_LINE_LOOP,此方式是线段类的一种,其将传入渲染管线的一系列顶点按照顺序依次组织成线段进行绘制;与 GL_LINE_STRIP 方式的区别是,其将最后一个顶点与第一个顶点相连,形成线段环;
GL_TRIANGLES,此方式是三角形类之一,其将传入渲染管线的一系列顶点按照顺序每 3 个组织成一个三角形进行绘制;此绘制方式下,多个三角形有共用顶点时会造成冗余;
GL_TRIANGLE_STRIP,此方式是三角形类之一,其将传入渲染管线的一系列顶点按照顺序依次组织成三角形进行绘制,最后实际形成的是一个三角形条带,若共有 N 个顶点,则将绘制出 N-2 个三角形;
GL_TRIANGLE_FAN,此方式是三角形类之一,其将传入渲染管线的一系列顶点中的第一个顶点作为中心点,其他顶点作为边缘点绘制出一系列组成扇形的相邻三角形;
【5】OPENGL ES 变换绘制相关的常用技巧
【5.1】顶点常量属性
顶点常量属性是指给一个需绘制的物体中的所有顶点指定同样的某方面(如颜色)属性值,在绘制时可以减小内存的占用以及 IO 传输的时间,以提高绘制效率;与顶点常量属性相关的方法主要有两套,这些方法的名称都以 glVertexAttrib 开头;
1. glVertexAttribNf 方法,将 N 个浮点数传入管线,以备管线传递给由 N 个浮点数分量组成的属性变量,N 可能的取值为 1、 2、 3 或 4;
2. glVertexAttribNfv 方法,将长度为 N 的浮点数组或者长度为 N 的数据缓冲传入管线,以备管线传递给由 N 个浮点数分量组成的属性变量,N 可能的取值为 1、 2、 3 或 4;
【5.2】视角的设置
视角的计算方法
水平方向视角的计算公式,α =2arctan(left/near);垂直方向的视角计算公式,α =2arctan(top/near);
视角与场景的关系
注意,当视角很大时,往往有比较严重的变形,因此,开发中要选择合理的组合,使得既能观察到指定范围的场景,又能满足对物体不变型的需要;
【5.3】3D 绘制时两个距离很近的面出现的深度计算不准确的问题解决方案
1. 设置合理的透视参数;
2. 多边形偏移,所谓多边形偏移就是在绘制时对深度值的计算进行扰动,以获得正确的遮挡效果;
public static void glPolygonOffset (float factor, float units)
参数 factor 为自定义的,用于计算深度值的比例;
参数 units 为自定义的,用于计算深度值的单位;
glPolygonOffset 方法中的两个参数进行深度偏移的计算公式,深度偏移值=m* factor+r* units;
参数 m 是三角形的最大深度斜率,
或
;
参数 r 是 OpenGL ES 实现中定义的常量,代表深度值中可以保证产生差异的最小值;
【5.4】卷绕和背面裁剪
背面剪裁是指渲染管线在对构成立体物体的三角形图元进行绘制时,仅当摄像机观察点位于三角形正面的情况下才绘制三角形,若观察点位于背面则不进行绘制;
确定摄像机位于一个面的正面或反面的方法
在没有做出特殊设置的情况下,OpenGL ES 中规定当摄像机观察一个三角形面时,若三角形中 3 个顶点的卷绕顺序是逆时针则摄像机观察其正面,反之摄像机观察其反面;
GLES30.glEnable(GLES30.GL_CULL_FACE); //打开背面剪裁
GLES30.glDisable(GLES30.GL_CULL_FACE); //关闭背面剪裁
GLES30.glFrontFace(GLES30.GL_CCW); //设置逆时针卷绕为正面,默认的
GLES30.glFrontFace(GLES30.GL_CW); //设置顺时针卷绕为正面,非默认,需要时采用
参考
本博客为博主的学习实践总结,并参考了众多博主的博文,在此表示感谢,博主若有不足之处,请批评指正。
【1】OpenGL ES 3.x 游戏开发