在OpenGL ES开发中,有些概念会经常用到,在这里进行一个总结。
什么是OpenGL ES
OpenGL(Open Graphics Library)定义了一个跨编程语言、跨平台编程的专业图形程序接口。可用于二维或三维图像的处理和渲染,它是一个功能强大、调用方便的底层图形库。对于嵌入式设备,其提供了OpenGL ES(OpenGL for Embeddled Systems)版本,该版本是针对手机、Pad等嵌入式设备而设计的,是OpenGL的一个子集。
OpenGL 渲染流程
主要流程可概括为:
1、创建定点数组
2、写顶点着色器和片元着色器
3、gl初始化相关操作
4、将java声明的顶点数组 颜色数组 通过类似于jni接口传递给glsl语言的变量
GLSL(OpenGL Shading Language)着色器语言
开发人员利用这种语言编写程序运行在GPU(Graphic Processor Unit,图形图像处理单元,可以理解为是一种高并发的运算器)上以进行图像的处理和渲染。GLSL着色器代码分为两个部分,即Vertex Shader(顶点着色器)与Fragment Shade(片元着色器)两部分,分别完成各自在OpenGL渲染管线中的功能。
OpenGL 坐标系
OpenGL中有6种坐标系:
-- World Coordinates(世界坐标系)
-- Object Coordinates(对象坐标系、模型坐标系、局部坐标系或当前绘图坐标系)
-- Eye Coordinates(眼坐标系或照相机坐标系)
-- Clip Coordinates(裁剪坐标系)
-- Normalized Device Coordinates (NDC) (归一化设备坐标系)
-- Window Coordinates (Screen Coordinates)(屏幕坐标)
如上图所示是OpenGL里的世界坐标系,世界坐标系始终是固定不变的。OpenGL使用右手坐标,这里有一个形象的方法:使用右手定则
X 是你的拇指
Y 是你的食指
Z 是你的中指
如果你把你的拇指指向右边,食指指向天空,那么中指将指向你的背后。观察方向是Z轴负半轴的方向。
世界坐标系又称全局坐标系或宇宙坐标系。
当前绘图坐标系:是绘制物体时的坐标系。程序刚初始化时,世界坐标系和当前绘图坐标系是重合的。对当前绘图坐标系进行平移、伸缩、旋转变换之后,世界坐标系和当前绘图坐标系不再重合。
透视投影
左图是透视投影,右图是正交投影。
透视投影:视景体--平截体、近大远小、适合3D图像渲染
正交投影:正方形/长方形、不存在近大远小、适合平面图形/2D图形渲染
视点、视平面关系如上图所示。
Android使用OpenGL ES绘制3D图像或者加载3D模型时,为了达到立体效果往往需要设置视见转换矩阵和投影转换矩阵。
Matrix.setLookAtM
/**
* Defines a viewing transformation in terms of an eye point, a center of
* view, and an up vector.
*
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param eyeX eye point X
* @param eyeY eye point Y
* @param eyeZ eye point Z
* @param centerX center of view X
* @param centerY center of view Y
* @param centerZ center of view Z
* @param upX up vector X
* @param upY up vector Y
* @param upZ up vector Z
*/
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)
eyeX, eyeY, eyeZ是摄像机的位置,及观察者眼睛的位置。centerX, centerY, centerZ为目标物的中心坐标,如果都为0,则设置到原点。upX、upY、upZ为摄像机顶部的方向,相机旋转up方向就会改变。
摄像机位置及目标物位置坐标,通常要结合Matrix.frustumM()的near、far参数才能呈现具体效果。
摄像机up方向:
例1:设置up为y轴正方向,upx = 0,upy = 1,upz = 0。这时相机正对着目标图像:
例2:设置up方向为x轴正方向,upx = 1,upy = 0,upz = 0。则成像如下:
Matrix.frustumM
/**
* Defines a projection matrix in terms of six clip planes.
*
* @param m the float array that holds the output perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void frustumM(float[] m, int offset,
float left, float right, float bottom, float top,
float near, float far)
设视点E位于原点,视平面P垂直于Z轴,且四边分别平行于x轴和y轴,如上图所示,将该模型称为透视投影的标准模型,其中视椎体的近截面离视点的距离为n,远截面离视点的距离为f,且一般取近截面为视平面。
参考前面的相机-视平面-物体位置关系图
float left, //near面的left
float right, //near面的right
float bottom, //near面的bottom
float top, //near面的top
float near, //near面距离
float far //far面距离
left,right和bottom,top,这4个参数会影响图像左右和上下缩放比。
float ratio = (float) width/height;
//设置透视投影
Matrix.frustumM(mProjectMatrix,0,-ratio,ratio,-1,1,3,20);
一般计算ratio=width/height,然后left、right分别做如上设置,然后bottome、top分别取-1、1。near和far分别代表近截面距离和远截面距离。
如何理解透视投影及视锥体
实际情况总是限定P为一定大小的矩形平面,透视结果位于P之外的透视结果将被裁减。可以想象视平面为透明的玻璃窗,视点为玻璃窗前的观察者,观察者透过玻璃窗看到的外部世界,便等同于外部世界在玻璃窗上的透视投影。
所有在近平面和远平面内且处于平截头体内的顶点都会被渲染,在平截头体外的顶点会被剔除!
由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。
矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
对于整个矩阵变换先执行model(平移、旋转、缩放)等变换,再执行view(视见)变换,确定视点和物体的距离。最后执行projection(投影)变换,此时视点和物体的距离已经确定。projection变换会确定近平面和视点的距离,和远平面的距离。这就决定了哪些点可以最终被渲染。
例如:在上面视锥体模型图中,调大近平面(near plane)距离,使近平面位于绿色立方体之后,则绿色立方体就不会成像到近平面上,因为此时绿色立方体在视椎体之外。
当你把透视矩阵的 near 值设置太大时(如10.0f),OpenGL会将靠近摄像机的坐标(在0.0f和10.0f之间)都裁剪掉,这会导致一个你在游戏中很熟悉的视觉效果:在太过靠近一个物体的时候你的视线会直接穿过去。
整个矩阵变换流程如下
局部坐标是对象相对于原点的坐标,也是物体的起始坐标。
下一步将局部坐标转化为世界空间坐标,世界空间坐标是一个处于更大空间范围内的。这些坐标相对于世界的全局原点,它们会和其他物体一起相对于世界原点进行摆放。
接下来将世界坐标转化为观测坐标,使得每个坐标都是从摄像机或者说观察者角度进行观察的。
坐标到达观测空间后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理到−1.0到1.0范围内,并判断哪些点将会出现在屏幕上。
最后将裁剪坐标变换为屏幕坐标,使用一视口变换(Viewport Transform)。视口变换将位于−1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。
基本图元单位是三角形
三角图元绘制方式
grDrawArrays与glDrawElements区别:
区别在于:glDrawArrays 是直接绘制真实的顶点数据,而 glDrawElements 是按照指定的索引顺序取出真实数据再绘制。
于是对于顶点存在共享的场景时,使用 glDrawElements 对于重复的顶点数据只需要传输一份数据,绘制时通过索引反复的获取其值,最终降低内存占用和内存带宽需求。
GL_TRIANGLES:通过顶点数组可以组成三角形的数目为数组长度/3。
GL_TRIANGLE_STRIP:每个小组的三个临近的顶点组成一个三角形,通过顶点数组可以组成三角形的数目为数组长度-2。
纹理坐标
2D纹理坐标系
2D纹理坐标在x和y轴(也叫u,v)上,范围为0.0到1.0之间。
OpenGL 坐标系
而OpenGL坐标系中x坐标、y坐标的取值范围都是(-1,1),所以要在范围为(0,1)的纹理坐标系中找到对应的纹理坐标点。
纹理坐标在纹理图像中指定与要为其指定顶点的点相对应的点。纹理映射时只需要为物体的顶点指定纹理坐标即可,其余部分由片元着色器插值完成。
参考:
//坐标系
https://www.jianshu.com/p/21bcfae7480a
//坐标系与矩阵变换
https://www.jianshu.com/p/79435ff64b25
//纹理坐标系
https://blog.csdn.net/gongxiaoou/article/details/89344561
//摄像机距离及视锥体空间
https://blog.csdn.net/jamesshaoya/article/details/54342241
https://www.pianshen.com/article/2665680991/
https://blog.csdn.net/qq_16334327/article/details/81228679
https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/