OpenGL 是⼀一种图形应⽤用程序编程接⼝口(Application Programming Interface, API).简单理理解就是开发的 图形库。主要应用于:
OpenGL 与 OpenGL ES 的主要区别,在于OpenGL ES 主要针对嵌⼊入式设备使⽤用
在OpenGL 3.0之前,OpenGL 包含一个固定功能的管线,它可以在不使⽤着色器的情况下处理几何与像素数据。在3.1版本开始,固定管线从核⼼ 模式去掉。因此现在需要使⽤着色器来完成工作。
OpenGL 中的图元只不过是顶点的集合以预定义的方式结合一起罢了。下图为管线流程:
理解客户机、服务器
管线分为上下2部分,上部分是客户端,而下半部分则是服务端。
渲染过程中,必备的两个着色器:顶点着色器和片元着色器.
透视投影会进⾏透视除法对距离观察者很远的对象进行缩短和收缩。在投影到屏幕之后,视景体背面与视景体正⾯的宽度测量量标准不同。
GLFrustum类通过setPerspective 方法为我们构建一个平截头体。
CLFrustum::SetPerspective(float fFov,float fAspect,float fNear ,float fFar);
参数:
fFov:垂直方向上的视场角度
fAspect:窗口的宽度与高度的纵横比
fNear:近裁剪面距离
fFar:远裁剪面距离
纵横⽐比 = 宽(w)/高(h)
// GLShaderManager 的初始化
GLShaderManager shaderManager;
shaderManager.InitializeStockShaders();
GLShaderManager.UserStockShader(GLeunm shader...);
GLShaderManager.UserStockShader(GLT_ATTRIBUTE_VERTEX,GLfloat vColor[4]);
单位着色器:只是简单地使用默认笛卡尔坐标系(坐标范围(-1.0, 1.0))。所有的片段都应用同一种颜色,几何图形为 实心和未渲染 的。
需要设置存储着⾊器一个属性: GLT_ATTRIBUTE_VERTEX(顶点分量)
参数2:vColor[4],你需要的颜色
GLShaderManager.UserStockShader(GLT_SHADER_FLAT,GLfloat mvp[1 6],GLfloat vColor[4]);
参数1:平⾯着⾊器
参数2:允许变化的4*4矩阵
参数3:颜⾊
它将统一着⾊器进行了拓展。允许为几何图形变换指定一个 4 * 4 变换矩阵。经常被称为“模型视图投影矩阵".
GLShaderManager.UserStockShader(GLT_SHADER_SHADED,GLfloat mvp [16]);
在几何图形中应用的变换矩阵。
需要设置存储着色器的 GLT_ATTRIBUTE_VERTEX(顶点分量) 和
GLT_ATTRIBUTE_COLOR(颜⾊分量) 2个属性。颜⾊色值将被平滑地插⼊顶 点之间(平滑着色)
GLShaderManager.UserStockShader(GLT_SHADER_DEFAULT_LIGHT,GLfl oat mvMatrix[16],GLfloat pMatrix[16],GLfloat vColor[4]);
参数1:默认光源着色器
参数2:模型视图矩阵
参数3:投影矩阵
参数4:颜⾊色值
这种着⾊器,是对象产⽣阴影和光照的效果。 需要设置存储着色器的 GLT_ATTRIBUTE_VERTEX(顶点分量) 和
GLT_ATTRIBUTE_NORMAL(表⾯法线)
GLShaderManager.UserStockShader(GLT_SHADER_DEFAULT_LIGHT_DIEF ,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3] ,GLfloat vColor[4]);
参数1:点光源着⾊器
参数2:模型视图矩阵
参数3:投影矩阵
参数4:视点坐标光源位置
参数5:颜⾊色值
点光源着⾊器和默认光源着⾊器很相似,区别在于:光源位置是特定的。 同样需要设置存储着⾊器的 GLT_ATTRIBUTE_VERTEX(顶点分量) 和
GLT_ATTRIBUTE_NORMAL(表⾯法线)
GLShaderManager.UserStockShader(GLT_SHADER_TEXTURE_REPLACE,GL float mvMatrix[16],GLint nTextureUnit);
着色器通过给定的模型视图投影矩阵,使用绑定到 nTextureUnit (纹理单元) 指定纹理单元的纹理对几何图形进⾏变化。
⽚段颜色:是直接从纹理样本中直接获取的。
需要设置存储着⾊器的 GLT_ATTRIBUTE_VERTEX(顶点分量量) 和
GLT_ATTRIBUTE_NORMAL(表⾯法线)
GLShaderManager.UserStockShader(GLT_SHADER_TEXTURE_POINT_LIGH T_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLight Pos[3],GLfloat vBaseColor[4],GLint nTextureUnit);
参数1:纹理光源着⾊器
参数2:投影矩阵
参数3:视觉空间中的光源位置
参数4:⼏何图形的基本色
参数5:将要使用的纹理单元
将一个纹理通过漫反射照明计算机进⾏调整(相乘)。光线在视觉空间中 的位置是给定的。
需要设置存储着⾊器的 GLT_ATTRIBUTE_VERTEX(顶点分量量) 和
GLT_ATTRIBUTE_TEXTURE0(纹理坐标)、GLT_ATTRIBUTE_NORMAL(表面法线)
点,是最简单的图像。每个特定的顶点在屏幕上都仅仅是一个单独的点。默认情况下,点的大小是一个像素的大小。
//1.最简单也是最常用的,4.0f,表示点的大小
glPointSize(4.0f);
//2. 设置点的范围和点与点的之间的间距
GLfloat sizes[2] = {2.0f,4.0f};
GLfloat step = 1.0f;
//获取点大小范围和最小步长
glGetFloatv(GL_POINT_SIZE_RANGE,sizes); glGetFloatv(GL_POINT_GRAULARITY,&step);
//3.通过使用程序点大小模式来设置点大小
glEnable(GL_PROGRAM_POINT_SIZE);
// gl_PointSize 可以在着色器源码直接写
gl_PointSize = 5.0;
比点更进一步的是独立线段。一个线段就是2个顶点之间绘制的。默认情况下,线段的宽度是一个像素。线段的宽度是一个像素。改变线段唯一的方式通过:
glLineWidth(2.5f);
线段连续从一个顶点到下一个顶点绘制的线段,以形成一个真正链接的点的线段。
线环是线带的一种简单拓展。在线带的基础上额外增加一条将线带闭合。
最简单的实体多边形就是三角形,它只有3个边。光栅化硬件最欢迎三角形。并且现在三角形已经是OpenGL 中支持的唯一一种多边形。
默认情况下,OpenGL认为具有逆时针方向环绕的多边形是正面的。而右侧的顺时针方向三角形是三角形的背面。
//定义前向和背向的多边型
glFrontFace(mode)
参数:GL_CW | GL_CCW
GL_CCW:表示传入的mode会选择逆时针为前向
GL_CW:表示顺时针为前向
默认: GL_CCW,逆向时针为前向。
除了三角形带之外,还可以使用GL_TRINGLE_FAN 创建一组围绕一个中心点的相连三角形。
在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部分是对观察者不可见的。对于不可见的部分,应该及早丢弃。例如,一个不透明的墙壁后面,就不应该渲染,这种情况叫做**“隐藏面消除”**。
先绘制场景中的离观察者比较远的物体,再绘制比较近的物体。
例如下图的图例:先绘制红色部分,再绘制黄色部分,最后再绘制灰色部分,即可解决隐藏面消除的问题。
油画法的弊端:
使用油画算法,只能将场景按照物理距离观察者的距离远近排序,由远及近的绘制。那么会出现一种情况,就是无法判断物体的远近时,就无法处理,如下图:
背景:
- 从一个立方体的任务位置和方向上看,不可能看到多于3个面。
- 那么我们就不需要去绘制那部分多与的的看不到的另外的面。
- 如果能以某种方式去丢弃这部分数据,OpenGL在渲染性能即可提高超过50%。
任何平面都有2个面,正面\背面。意味着你一个时刻只能看到一面。
OpenGL 可以做到检查所有正面朝向观察者的面,并渲染它们。从而丢弃背面朝向的面。
OpenGL是可以通过分析顶点数据的顺序来判断绘制的图形,哪个是正面,哪个是背面。
可以以立方体为例分析其正背面:
分析
- 左侧三角形顶点顺序为:1-2-3,右侧三角形的顶点顺序为:1-2-3.
- 当观察者在右侧时,则右边的三角形方向为逆时针方向为正面,左侧的三角形为顺时针则为背面。
- 当观察者在左侧时,则左侧的三角形为逆时针方面判定为正面,右侧的三角形为顺时针则为背面。
开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
用户选择剔除那个面(正面/背面)
void glCullFace(GLenum mode);
mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK
用户指定绕序那个为正面
void glFrontFace(GLenum mode);
mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
例如,剔除正面实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
例如,剔除正面实现(2)
glCullFace(GL_FRONT);
深度就是该像素点在3D世界中距离摄像机的距离,即Z值
深度缓冲区,就是一块内存区域,专门存储每个像素点(绘制在屏幕上的)深度值。深度值(z值)越大,则离摄像机就越远。
在不使用深度测试的时候,如果先绘制一个距离比较近的物体,再绘制距离比较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。有了深度缓冲区后,绘制物体的顺序就不那么重要。
只要存在深度缓冲区,OpenGL都会把像素的深度值写入到缓冲区中。除非调用glDepthMask(GL_FALSE)来禁止写入。
- 深度缓冲区(DepthBuffer)和颜色缓存区(ColorBuffer)是对应的。
- 颜色缓冲区存储像素的颜色值,而深度缓冲区存储像素的深度信息。
- 在决定是否绘制一个物体的表面时,首先要将表面对应的像素的深度值与当前深度缓冲区中的值进行比较。
- 如果大于深度缓冲区的值,则丢弃这部分,否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区。
- 这个过程称为“深度测试”。
深度缓冲区,一般由窗口管理系统 GLFW创建,深度值有16位、24位、32位表示。通常24位,位数越高,深度精确度越好。
开启深度测试:
glEnable(GL_DEPTH_TEST);在绘制场景前,清除颜色缓冲区、深度缓冲区:
glClearColor(0.0f,0.0f,0.0f,1.0f );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);清除深度缓冲区默认值为1.0,表示最大的深度值,深度值的范围为(0,1)之间。值越小表示越靠近观察者,值越大表达离观察者越远。
打开/阻断 深度缓存去写入
void glDepthMask(GLBool value);
value : GL_TURE 开启深度缓冲区写⼊;
GL_FALSE 关闭深度缓冲区写⼊
- 启用Polygon Offset方式
glEnable(GL_POLYON_OFFSET_FILL)
参数列表:
GL_POLYGON_OFFSET_POINT 对应光栅化模式: GL_POINT
GL_POLYGON_OFFSET_LINE 对应光栅化模式: GL_LINE
GL_POLYGON_OFFSET_FILL 对应光栅化模式: GL_FILL
- 通过glPolyonOffset 来指定.glPolygonOffset;需要两个参数:factor,units
- 每个Fragment 的深度值增加,如下所示的偏移量:
Offset = ( m * factor ) + ( r * units);
m : 多边形的深度的斜率的最大值,理解为一个多边形越是与近裁剪面平行,m 就越接近于0.
r : 能产生于窗口坐标系的深度值中可分辨的差异最小值.
r 是由具体OpenGL 平台指定的 一个常量.- 一个大于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的一个小于0的Offset 会把模型拉近
- 一般⽽言,只需要将-1.0 和 0.0 这样简单赋值给glPolygonOffset 基本可以满足需求.
glDisable(GL_POLYGON_OFFSET_FILL)
- 不用讲两个物体靠的太近,避免渲染时三角形叠在一起。这种方式要求对场景中物体插入一个小量的偏移,那么就可能避免ZFighting现象。当然这种手动插入小的偏移是要付出代价的。
- 尽可能将近裁截面设置得离观察者远一些。上面我们看到,在近裁剪平⾯附近,深度的精确度是很高 的,因此尽可能让近裁剪面远一些的话,会使整个裁剪范围内的精确度变高一些。但是这种方式会使 离观察者较近的物体被裁减掉,因此需要调试好裁剪面参数。
- 使用更高位深度缓冲区,通常使用的深度缓冲区是24位,现在有一个写硬件使用32位的缓冲区,使精确度得到提高。
在OpenGL中提高渲染的一种方式,就是“裁剪”。只刷新屏幕上发生变化的部分。OpenGL允许将要进行渲染的窗口去指定一个裁剪框。
- 在渲染时限制绘制区域,通过此技术可以在屏幕(帧缓冲)指定一个矩形区域。
- 启用裁剪测试之后,不在此矩形区域的片元被丢弃,只有在此矩形区域的片元才有可能进入帧缓冲。
- 因为实际达到的效果就是在屏幕上开辟了一个小窗口,可以再其中进行制定内容的绘制。
**1 .开启裁剪测试 **
glEnable(GL_SCISSOR_TEST);
2.关闭裁剪测试
glDisable(GL_SCISSOR_TEST);
3.指定裁剪窗⼝
void glScissor(Glint x,Glint y,GLSize width,GLSize height);
x,y:指定裁剪框左下⻆角位置;
width , height:指定裁剪尺寸
窗口就是显示界面
(2)视口
视口就是窗口中用来显示图形的一块矩形区域,他可以和窗口等大,也可以比窗口大或者小。只有绘制在视口区域的图形才能被显示,如果图形有一部分超出了视口区域,那么那一部分就看不到的。
通过glViewport()函数设置。
(3)裁剪区域
裁剪区域(平行投影):就是视口矩形区域的最小最大x坐标(left,right)和最小最大y坐标(bottom,top),而不是窗口的最小最大x坐标和y坐标。
通过glOrtho()函数设置,这个函数还需指定最近最远z坐标,形成一个立体的裁剪区域。
Cf = (Cs * S) + (Cd * D)
Cf :最终计算参数的颜色
Cs : 源颜⾊
Cd :目标颜色
S:源混合因子
D:目标混合因子
设置混合引子,需要用到glBlendFun函数:
glBlendFunc(GLenum S,GLenum D);
S:源混合因⼦
D:目标混合因⼦
表中R、G、B、A 分别代表 红、绿、蓝、alpha。
表中下标S、D,分别代表源、⽬目标
表中C 代表常量颜⾊(默认⿊色)
总结:
- 最终颜色是以原先的红色(目标颜色)与 后来的颜色(源颜色)进⾏组合。
- 源颜⾊的alpha值 越高,添加后来颜⾊成分越高,目标颜⾊所保留的成分就会越少。
- 混合函数经常⽤于实现在其他⼀些不透明的物体前面绘制一个透明物体的效果。
要知道,图片在屏幕中显示都是由一个个像素点组成的。当无限放大一张图片时,就会发现颜色的边界都是锯齿状。为了优化这种现象,有了抗锯齿的功能。抗锯齿功能,更多用于点和线中。
抗锯齿混合的2个功能:
- 颜色组合
- 抗锯齿
要知道,抗锯齿只能优化显示效果,但是并不能消除这种现象。
抗锯齿的使用:
//开启混合处理
glEnable(GL_BLEND);
//指定混合因⼦子 GLBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
//指定混合⽅方程式
glBlendEquation(GL_FUNC_ADD);
//对点进⾏抗锯齿处理
glEnable(GL_POINT_SMOOTH);
//对线进⾏抗锯⻮处理
glEnable(GL_LINE_SMOOTH);
//对多边形进⾏行行抗锯⻮处理glEnable(GL_POLYGON_SMOOTH);
多重采样也是解决抗锯齿的一种方式,只不过比上面的抗锯齿方法的不同在于:多重采样更多是应用于多边形中。
多重采样的使用:
- 1.可以调用 glutInitDisplayMode 添加采样缓存区
glutInitDisplayMode(GLUT_MULTISAMPLE);- 2.可以使用glEnable| glDisable组合使⽤GLUT_MULTISAMPLE 打开|关闭 多重采样。
glEnable(GLUT_MULTISAMPLE);
glDisable(GLUT_MULTISAMPLE);
void glPixelStorei(GLenum pname,GLint param);
void glPixelStoref(GLenum pname,GLfloat param);
void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height, GLenum format, GLenum type,const void * pixels);
glReadBuffer(mode);—> 指定读取的缓存 glWriteBuffer(mode);—> 指定写⼊的缓存
void glTexImage1D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLint border,GLenum format,GLenum type,void *data);
void glTexImage2D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLenum format,GLenum type,void * data);
void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);
- target:
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
。- Level:指定所加载的mip贴图层次。⼀般我们都把这个参数设置为0。
- internalformat:每个纹理单元中存储多少颜⾊成分。
- width、height、depth参数:指加载纹理的宽度、⾼度、深度。==注意!==这些值必须是
2的整数次⽅
。(这是因为OpenGL 旧版本上的遗留下的⼀个要求。当然现在已经可以⽀持不是 2的整数次⽅。但是开发者们还是习惯使⽤以2的整数次⽅去设置这些参数。)- border参数:允许为纹理贴图指定⼀个边界宽度。
- format、type、data参数:与我们在讲glDrawPixels 函数对于的参数相同
void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);
void glCopyTexSubImage1D(GLenum target,GLint level,GLint xoffset,GLint x,GLint y,GLsizei width);
void glCopyTexSubImage2D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint x,GLi y,GLsizei width,GLsizei height);
void glCopyTexSubImage3D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint zOffset,GLint x,GLint y,GLsizei width,GLsizei height);
void glCopyTexImage1D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLint border);
void glCopyTexImage2D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLsizei height,GLint border);
其中,x,y 在颜⾊缓存区中指定了开始读取纹理数据的位置;
缓存区⾥的数据,是源缓存区通过glReadBuffer
设置的。
纹理对象的使用
1.使⽤函数分配纹理对象
void glGenTextures(GLsizei n,GLuint * textTures);
- 指定纹理对象的数量
- 指定纹理对象的指针(指针指向⼀个⽆符号整形数组,由纹理对象标识符填充)。
2.绑定纹理状态
void glBindTexture(GLenum target,GLunit texture);
- 参数target:
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
- 参数texture:需要绑定的纹理对象
3.删除绑定纹理对象
void glDeleteTextures(GLsizei n,GLuint *textures)
- 纹理对象
- 纹理对象指针(指针指向⼀个⽆符号整形数组,由纹理对象标识符填充)。
4.测试纹理对象是否有效
GLboolean glIsTexture(GLuint texture);
- 如果texture是⼀个已经分配空间的纹理对象,那么这个函数会返回GL_TRUE,否则会返回GL_FALSE。
5.设置纹理参数
glTexParameterf(GLenum target,GLenum pname,GLFloat param);
glTexParameteri(GLenum target,GLenum pname,GLint param);
glTexParameterfv(GLenum target,GLenum pname,GLFloat *param);
glTexParameteriv(GLenum target,GLenum pname,GLint *param);
- 参数1:target,指定这些参数将要应⽤在那个纹理模式上,⽐如
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
。- 参数2:pname,指定需要设置哪个纹理参数
- 参数3:param,设定特定的纹理参数的值
6.设置纹理过滤方式
(1)邻近过滤(GL_NEAREST):是指取附近一个颜色值返回
(2)线性过滤(GL_LINEAR):取附近周围的点平均颜色返回
3. 两种纹理方式的比较
4.使用方法
- 纹理缩⼩时,使⽤邻近过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST)- 纹理放⼤时,使⽤线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)
- 代码设置如下:
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
- 参数1:
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
- 参数2:
GL_TEXTURE_WRAP_S
、GL_TEXTURE_T
、GL_TEXTURE_R
,针对s,t,r坐标- 参数3:
GL_REPEAT
、GL_CLAMP
、GL_CLAMP_TO_EDGE
、GL_CLAMP_TO_BORDER
(1)GL_REPEAT:OpenGL 在纹理坐标超过1.0的⽅向上对纹理进⾏重复;
(2)GL_CLAMP:所需的纹理单元取⾃纹理边界或TEXTURE_BORDER_COLOR.
(3)GL_CLAMP_TO_EDGE环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后⼀⾏或者最后⼀ 列来进⾏采样。
(4)GL_CLAMP_TO_BORDER:在纹理坐标在0.0到1.0范围之外的只使⽤边界纹理单元。边界纹理单元是 作为围绕基本图像的额外的⾏和列,并与基本纹理图像⼀起加载的。6.设置MIP贴图
- 设置mip贴图基层 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
- 设置mip贴图最⼤层 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);