第一章 OpenGL常见概念梳理
OpenGL简述
OpenGL是什么?
至今已有20余年的历史,跨平台计算机图形应用程序接口规范,被广泛使用在游戏、影视、军事、航空航天、地理、医学、机械设计以及各类科学数据可视化领域。
就是一个开发的图形库。
OpenGL可以来干什么?
(应用领域:视频 图形 图片处理,2D/3d游戏引擎开发,科学可视化,医学软件的开发,CAD(计算机辅助技术),虚拟实境(AR VR),AI人工智能)等等。
OpenGL和OpenGL ES有什么关系?
OpenGL ES是OpenGL的子集,两者主要区别在于OpenGL ES主要针对嵌入式设备使用。
其他
OpenGL pc端
OpenGL ES 嵌入式(移动端:ios、android)
OpenGL 非常接近底层!
OpenGL相关概念
1.顶点着色器
着色器(Shader)是在GPU上运行的小程序。从名称可以看出,可通过处理它们来处理顶点。此程序使用OpenGL ES SL语言来编写。它是一个描述顶点或像素特性的简单程序。
对于发送给GPU的每一个顶点,都要执行一次顶点着色器。其功能是把每个顶点在虚拟空间中的三维坐标变换为可以在屏幕上显示的二维坐标,并带有用于z-buffer的深度信息。顶点着色器可以操作的属性有:位置、颜色、纹理坐标,但是不能创建新的顶点。
【输入输出模型】
请记住这几个子模块,后面大有用处
2.片元着色器
片元着色器计算每个像素的颜色和其它属性。它通过应用光照值、凹凸贴图,阴影,镜面高光,半透明等处理来计算像素的颜色并输出。它也可改变像素的深度(z-buffering)或在多个渲染目标被激活的状态下输出多种颜色。一个片元着色器不能产生复杂的效果,因为它只在一个像素上进行操作,而不知道场景的几何形状。
【输入输出模型】
3.GLSL
GLSL语言作为着色器语言,是我们必须要掌握的,详情可以参考这篇博客:
GLSL语法介绍
GLSL语法
4.图形的绘制
OpenGL的世界里只有点、线、三角形,其他较为复杂的图形,比如正方形、圆形、正方体、球体等都是由三角形组成的。但更为复杂的图形构建,就需要通过加载利用其他软件,比如3DMax构建了。
5.投影
OpenGL ES的世界是3D的,但手机屏幕智能展示2D效果,所以只能在绘制的过程中利用色彩和线条让画面呈现出3D的效果。这种3D到2D的转换过程就是通过投影来实现的。
OpenGL ES中有两种投影方式:正交投影和透视投影。
正交投影,物体不会随距离观测点的位置而大小发生变化。
透视投影,距离观测点越远,物体越小,距离观测点越近,物体越大。
6.【OpenGL ES 2.0渲染过程】
①读取顶点数据
②执行顶点着色器
③组装图元
④光栅化图元
⑤执行片元着色器
⑥写入帧缓冲区
⑦显示到屏幕上
此外有几点还必须明白:
①OpenGL作为本地库直接运行在硬件上,没有虚拟机,也没有垃圾回收或者内存压缩。在Java层定义图像的数据需要能被OpenGL存取,因此,需要把内存从Java堆复制到本地堆。(所以基本每个OpenGL程序中都有ByteBuffer的存在)
②顶点着色器是针对每个顶点都会执行的程序,是确定每个顶点的位置。同理,片元着色器是针对每个片元都会执行的程序,确定每个片元的颜色。
③着色器需要进行编译,然后链接到OpenGL程序中。一个OpenGL的程序就是把一个顶点着色器和一个片段着色器链接在一起变成单个对象。(多个纹理叠加可能有多个OpenGL程序存在)
3D术语
3D = 2D+透视
二维物体:
高度、宽度
2D图形使用2D笛卡尔坐标系即可绘制。
三维物体:
高度、宽度、【深度】
3D图形对应3D笛卡尔坐标系绘制。
光栅化:实际绘制或填充每个顶点之间的像素形成线程
着色:沿着顶点之间改变颜色值,能够轻松创建光照照射到一个立方体的效果
纹理贴图:将纹理图片附着到你绘图的图像上
混合:颜色混合效果
【为什么要用OpenGL?】
CPU和GPU操纵的内存空间是不同的,数据传输可能造成数据饥饿。
【着色器】
对于着色器有个初步认识的话,需要先明白下面几个简单的概念:
图元:组成图像的基本单元
OpenGL渲染管线:一系列有序的处理阶段的序列,用于把我们应用中的数据转到OpenGL生成一个最终的图像的一个过程。
GLSL:专门为图像开发设计的编程语言。
着色器的渲染流程:
着色器的渲染:
①顶点着色器(必选)
比如绘制一个三角形,顶点着色器接收顶点数据,单独处理每个顶点,那么一个三角形实际上就是三个顶点,就会执行三次。
②细分着色器(可选)
③几何着色器(可选)
④片元着色器(必选)
处理片元颜色以及深度值,然后传递到片元测试和混合模块。有多少像素点就会执行多少次
渲染管线/固定管线/可编程管线
概念梳理
渲染管线:
渲染管线又称渲染流水线,它是图形图像从数据一步一步形成最终输出的画面所要经历的各种操作过程
固定管线:
固定渲染管线的OpenGLES不需要也不允许你自己去定义顶点渲染和像素渲染的具体逻辑,它内部已经固化了一套完整的渲染流程,只需要开发者在CPU代码端输入渲染所需要的参数并指定特定的开关,就能完成不同的渲染
可编程管线(GLES):
可编程渲染管线的OpenGLES版本必须由开发者自行实现渲染流程,否则无法绘制出最终的画面。开发者可以根据自己的具体需要来编写顶点渲染和像素渲染中的具体逻辑,可最大程度的简化渲染管线的逻辑以提高渲染效率,也可自己实现特定的算法和逻辑来渲染出固定管线无法渲染的效果。具有很高的可定制性,但同时也对开发者提出了更高的要求。
【矩阵】
GLES20类和Matrix类
矩阵变换
投影及各种变换
在OpenGL中实现图形的操作大量使用了矩阵,在OpenGL中使用向量为列向量(列向量是一个 n×1 的矩阵,即矩阵由一个含有n个元素的列所组成:列向量的转置是一个行向量,反之亦然。
列向量是一个 n×1 的矩阵,即矩阵由一个含有n个元素的列所组成:列向量的转置是一个行向量,反之亦然。
)。我们通过利用矩阵与列向量(颜色、坐标都可看做列向量)相乘,得到一个新的列向量。利用这点,我们构建一个矩阵,与图形所有的顶点坐标相乘,得到新的顶点坐标集合。当这个矩阵构造恰当的话,新得到的顶点坐标集合形成的图形相对原图形就会出现平移、旋转、缩放、拉伸、或扭曲的效果。
如参考文章中所列:
投影的两种方式
1.正交投影
正交投影
正交投影是平行投影的一种,其投影线是平行的,投影到近平面上的图形不会产生真实世界中“近大远小”的效果。
效果图如下:
可以看到物体前后大小维持原样。
通过Matrix#orthoM方法设置正交投影。
Matrix.orthoM(
mProjMatrix, //存储生成矩阵元素的float[]类型数组
0, //填充起始偏移量
left, right, //near面的left,right
bottom, top, //near面的bottom,top
near, far //near面,far面与视点的距离
);
//设置视口
GLES20.glViewport(x, y, width, height);
【那这个正交投影具体用处到底是什么呢?】
这里就要提到归一化设备坐标了。要显示的所有物体映射到手机屏幕上,都要映射到x/y/z轴上的[-1,1]范围内,这个范围内的坐标称为归一化设备坐标,独立于屏幕的实际尺寸和形状。
比如上面参考资料中:
按理说0.5配0.5应该是个正方形,但实际上却可以看到因为宽高比不是1,导致是个长方形。要创建一个正方形就很困难,因为要创建正方形就必须考虑手机的宽高比,传入数据的时候就比较复杂了:不能仅仅站在要绘制物体的自身角度来看了。
要绘制一个正方形,传入的顶点数据的y坐标要按照比例进行一点转换,比如对16:9的屏幕,将上面传入的顶点数据的y坐标都乘以9/16即可。但同时会发现当处于横屏时,又要处理传入的x坐标的值,显然这不是一个好的方案。
这时候就要引入投影了。
对于一个物体来说他有他自身的坐标,这个空间称为物体空间,这就是设计物体的时候采用的一个坐标空间,物体的几何中间在坐标原点上,归一化后坐标范围在[-1,1]之间,x轴和y轴分度是一致的。
在这个空间的物体直接往手机屏幕的归一化坐标绘制时,由于屏幕的宽高比问题,就会出现和预料效果不一样的问题,但我们只要对物体空间的坐标做一个映射就可以解决。
正交投影就是为了解决这个问题而存在的:
orthoM(float[] m, //存储生成矩阵元素的float[]类型数组
int mOffset, //填充起始偏移量
float left,float right, //near面的left、right
float bottom,float top, //near面的bottom、top
float near,float far) //near面、far面与视点的距离
使用正交投影
①操作顶点着色器
在顶点着色器中添加 uniform类型的变量u_Matrix,并把它定义成为一个mat4类型。意思是这个uniform代表一个44的矩阵,随后我们把这个矩阵与传递的位置进行相乘。这意味着顶点数组不再被翻译为归一化设备坐标了,可以理解为存在于正交矩阵所定义的虚拟坐标空间中。*
如下所示:
uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main()
{
v_Color = a_Color;
gl_Position = u_Matrix * a_Position;
gl_PointSize = 20.0;
}
gl_Position = u_Matrix * a_Position;
gl_PointSize = 20.0;
}
也就是这两行:
unifrom mat4 u_Matrix;
glPosition = u_Matrix*a_Position;//注意这两个变量顺序不能反转
接着在Renderer中添加:
float[] mProjection = new float[16];
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = width > height ? (float) width / height : (float) height / width;
if (width > height) {//横屏
//left、right比较大一些
Matrix.orthoM(mProjection, 0, -ratio, ratio, -1, 1, 0, 5);
} else {//竖屏
//bottom、height要大一些
Matrix.orthoM(mProjection, 0, -1, 1, -ratio, ratio, 0, 5);
}
}
setLookAtM()
setLookAtM
setLookAtM函数详解
上面这篇文章介绍的很详细,我们主要根据这篇博客来进行分析。
我们常见的setLookAtM函数使用场景如下:
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//计算宽高比
float ratio=(float)width/height;
//设置透视投影
Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
//eyex,eyey,eyez 代表相机在坐标轴的位置,centerX,centerY,centerZ代表物体的中心坐标,upX,upY,upZ代表相机往哪边看
Matrix.setLookAtM(mViewMatrix,0,
0, 0, 7.0f, //代表眼睛位置,eyeX ,eyeY ,eyeZ
0f, 0f, 0f, //代表观察对象中间点位置,一般选原点中心
0f, 1.0f, 0.0f);//代表相机观察方向,x,y,z,一般是从y轴看
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
第一类 相机的坐标 eyex,eyey,eyez
就是照相机在上图坐标系的位置
第二类 目标的位置 centerX,centerY,centerZ
就是图标在上图的位置(注意:9个数据是存在同一个空间坐标之中的)
第三类 相机的视觉向量 upx,upy,upz
也就是相机的方向,叫做向量
它有三个属性,第一个就是起始点、第二个方向、第三个就是量
就这里而言,起始点就是照相机,方向,,是由upx,upy,upz三个数据分别指向3个方向,,然后 合成所形成的方向。
前期我们就可以按照上面的方式使用这个函数,后期我们遇到其他情况再具体分析。
2.透视投影
正交投影/透视投影详解
透视投影的投影线是不平行的,他们相较于视点。和现实世界一下,存在“近大远小”的视觉效果。
如下图:
透视投影通过Matrix#frustumM来实现。
Matrix.frustumM(
mProjMatrix, //要填充矩阵元素的float[]类型数组
0, //填充起始偏移量
left, right, //near面的left,right
bottom, top, //near面的bottom, top
near, far //near面,far面与视点的距离
);
近平面的坐标原点位于中心,向右为 X 轴正方向,向上为 Y 轴正方向,所以我们的 left、bottom 要为负数,而 right、top 要为正数。同时,近平面和远平面的距离都是指相对于视点的距离,所以 near、far 要为正数,而且 far>near。
需要注意的是near和far变量的值必须要大于0。(因为它们都是相对于视点的距离,也就是照相机的距离。)
当用视图矩阵确定了照相机的位置时,要确保物体距离视点的位置在near和far的区间范围内,否则就会看不到物体。由于透视投影会产生近大远小的效果,当照相机位置不变,改变near的值时也会改变物体大小,near越小,则离视点越近,相当于物体越近,那么显示的物体也就越小了。
当然也可以near和far的距离不同,改变摄像机的位置来改变观察到的物体大小。