客户端-服务器
客户端是存储在CPU存储器中的,并且在应用程序中执行(或者驱动程序),驱动程序将渲染命令和数据组合起来,发动到服务器执行。
服务器和客户机在功能上是异步的,他们是各自独立的软件模块或者硬件模块。
OpenGL渲染管线
数据先传给顶点着色器,然后是片段着色器,几何着色器(可选择)出现在两者之间
- 顶点着色器(
Vertex Shader
) - 片段着色器(
Fragment Shader
)
片段(fragment)不是最后的像素数据,但和像素对应
片段(fragment)需要经过处理,blend,texture,lighting...,才会得到最后的像素。
顶点着色器(Vertex Shader)
绘图命令指定了一组顶点属性,描述了图元的几何形状和图元类型。顶点着色器使用这些顶点属性计算顶点的位置、颜色以及纹理坐标,这样才能传到片段着色器。
比如为了渲染一个三角形,顶点着色器执行3次,也就是为每个顶点执行一次。
在目前硬件上有多个执行单元同时运行,所以这3个顶点可以同时进行处理
图元装配(Primitive Assembly)
这些着色器处理过的顶点被组装到一个个独立的几何图元中,例如:三角形、线、点
对于每个图元,必须确定它是否位于视椎体内(3维空间显示在屏幕上的可见区域)
背面剔除操作也会执行,它根据图元是正面还是背面,进行背面剔除
光栅化(Rasterization)
光栅化阶段把图元转换成片段集合,之后会提交给片段着色器处理,这些片段集合表示可以被绘制到屏幕的像素。
输出的时每个片段对应的屏幕坐标,和属性(颜色,纹理坐标)
片段着色器(Fragment Shader)
每个片段执行片段着色器进行填充,片段着色器会输出我们将在屏幕上看到的颜色
当前的硬件是大规模并行运算的,同时执行上百个甚至更多这种着色器程序并不困难
有3种向OpenGL着色器传递渲染数据的方法:
- 属性
- uniform值
- 纹理
属性
每个顶点都要存储的数据元素。
顶点本身就是一种属性,还有纹理坐标,颜色值和用于光照计算的表面法线。
属性会从本地客户机内存中复制存储到图形硬件中得一个缓冲区中。
这些属性只供顶点着色器使用,对片段着色器无意义
Uniform值
Uniform可以使用多次,可以使用它设置一个应用于整个表面的单个颜色值
顶点着色器和片段着色器都有
纹理
顶点着色器和片段着色器中都可以对纹理值进行采样和筛选
我们关心的不是屏幕坐标和像素,而是视景体中的位置属性。将这些点,线和三角形从3D空间投影到计算机屏幕的2D图形则是着色器程序和光栅化所要完成的
七种绘图模式
GL_TRIANGLE_STRIP
渲染效果图:
GL_TRIANGLE_FAN
渲染效果图
点:
点是最简单的图元,默认点的大小是一个像素,可以通过glPointSize改变
void glPointSize(GLfloat size);
点的大小是存在限制的,我们可以获得支持的点尺寸的范围
GLfloat sizes[2]; GLfloat step; // 获取支持的点的尺寸范围和步长 glGetFloatv(GL_POINT_SIZE_RANGE, sizes); glGetFloatv(GL_POINT_SIZE_GRANULARITY, &step);
指定的点在范围外并不会发生错误,只是限定到最接近的范围内的值
默认情况下,点和其他图元不同,它并不会受到透视除法的影响,也就是说不会因为离视点远看上去变小。
另外,点总是正方形的,即使改变了大小点也还是正方形,为了获得圆点,必须在抗锯齿模式下绘点。
线(line),线带(line strip),线环(line loop):
默认情况下线段宽度为一个像素。改变线段宽度的唯一方式是使用glLineWidth
void glLineWidth(GLfloat width);
三角形(triangle)
绘制三角形顶点的顺序就是指定顶点的顺序。
OpenGL默认认为具有逆时针方向环绕的多边形是正面的,反之就是多边形背面。
一个三角形如果是正面的,那么它旋转后可能变成背面(笛卡尔坐标系,绕y轴180度)
可以使用下面函数改变
// GL_CW 顺时针环绕的多边形被认为是正面 // GL_CCW glFrontFace(GL_CW)
三角形带(triangle strip)
用前3个顶点指定第一个三角形,对于接下来的每个三角形,都只需要再指定一个顶点。
优势:
- 可以节省大量代码和数据存储空间
- 提高运算性能和节省带宽(顶点数据减少,数据传输和计算就会减少)
三角形扇(triangle fan)
+ 可以创建一组围绕一个中心点得相连三角形。
+ 第一个顶点是扇形的原点。
+ 前3个顶点指定最初的一个三角形,接下来的三角形只需指定一个顶点,它将和它相邻的顶点还有原点组成三角形。
正面和背面剔除
1.开启表面剔除
glEnable(GL_CULL_FACE);
glDisable(GL_CULL_FACE);
2.指定正面还是背面剔除
void glCullFace(Glenum mode);
深度测试
绘制一个像素时,将一个代表它到观察者距离的Z值,分配给像素.
然后,当第二次在此位置绘制像素时,比较其Z值,然后决定是否绘制。
这里使用了OpenGL的深度缓冲区,它存储了屏幕上每个像素的深度值。
启用深度测试:
glEnable(GL_DEPTH_TEST)
如果没有深度缓冲区,那么启用深度测试的命令将被忽略。
深度测试在绘制多个对象时能进一步解决性能问题,此时先绘制离观察者近的对象,那么就不用绘制距离远的对象了。
多边形模式
多边形(含三角形)不一定是实心的。默认下多边形是实心绘制,可以通过下面函数指定多边形渲染模式,实体,轮廓,点。
也可以指定多边形的面,正面,背面,两面
void glPolygonMode(GLeum face, GLenum mode);
Z冲突(z-fighting)
有意将多个几何图形绘制到同一位置或在一个图形上绘制另一个图形称为贴花(decal
),这样会导致深度缓冲区值相同,导致片段深度测试不可预料的通过或失败,称为Z冲突(z-fighting
)
有时为了绘制实体图形,又想在其上绘制线框图形,这是就会出现Z冲突。 这时可以在绘制线框时修改Z的偏移来解决这个问题。
注意:只能沿Z轴向镜头方向移动,偏移量必须大小适中,否则没有效果或者出现缝隙。
下面函数可以调节片段的深度值,这样就能使深度值偏移而并不实际并不改变控件中的物理位置。
void glPolygonOffset(GLfloat factor, GLfloat units);
深度偏移公式:
Depth Offset=(DZ x factor) + (r x units)
其中DZ是深度值相对于多边形屏幕区域的变化量,r 是使深度缓冲区值产生变化的最小值。并没有硬性规定能够找到一个万无一失的值。
负值使z轴距离我们更近,正值相反。
处理设置偏移值,还必须启用多边形单独偏移
// GL_POLYGON_OFFSET_FILL 填充几何图形 // GL_POLYGON_OFFSET_LINE 线 // GL_POLYGON_OFFSET_POINT 点 glEnable(GL_POLYGON_OFFSET_LINE);
裁剪(scissor)
这种提高渲染性能的方法就是只刷新屏幕上发生变化的部分,可能还需要将OpenGL的渲染限制在窗口中一个较小的矩形区域中。
OpenGL允许我们在将要渲染的窗口中制定一个裁剪框。默认情况下,裁剪框与窗口大小一样,并且不会进行裁剪测试。
开启裁剪测试
glEnable(GL_SCISSOR_TEST);
指定裁剪框,要绘制的区域
void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
混合(Blend)
通常OpenGL渲染时会把颜色值放在颜色缓冲区中,每个片段的深度值放在深度缓冲区中
当打开OpenGL混合功能时,下层颜色不会被清除,而是和已存在的颜色值在颜色缓冲区进行组合,不同组合方式产生不同特殊效果
启用混合
glEnable(GL_BLEND);
已经存在颜色缓冲区的颜色值叫目标颜色
作为新输入颜色缓冲区的颜色叫源颜色
颜色包含4个部分,红,绿,蓝,Alpha值,忽略的Alpha默认为1.0
默认混合公式如下:
Cf=(Cs*S)+(Cd*D)
其中,Cf是最终的颜色,Cs是源颜色,Cd是目标颜色,S和D分别是源颜色和目标颜色的混合因子
可以设置混合因子:
glBlendFunc(GLenum S, GLenum D);
还可以使用下面函数灵活进行选择
void glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
在设定因子的时候,某些带CONSTANT
字样的参数值,需要一个另外的颜色常量来参与计算,这个常量通过下面函数指定的
void glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
改变混合公式:
void glBlendEquation(GLenum mode);
抗锯齿(Anti-aliasing):
混合功能的另一个用途是抗锯齿,多数情况,一个独立的渲染片段将会映射到计算机屏幕上的一个像素。
这些像素是正方形的,通常可以相当清楚的看到两种颜色的分界,称为锯齿
。
为了消除图元之间的锯齿状边缘,OpenGL使用混合功能来混合片段颜色,也就是把像素的目标颜色和周围像素的颜色进行混合。
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 还需要确保吧混合公式设置为GL_ADD(默认) // 现在对图元分别进行抗锯齿处理 glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); glEnable(GL_POLYGON_SMOOTH); // 可以设置抗锯齿质量(GL_FASTEST) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
多重采样(multisampling)
点和直线的抗锯齿处理支持较好,但是多边形的平滑处理并没有在所有平台上都得到实现。
多重采样就可以解决多边形平滑处理的问题
存在一个缓冲区,所有的图元在每个像素上都进行多次采样,结果就存储在这个缓冲区中。每次当某个像素更新时,会对其进行重新采样,并更新到缓冲区中
首先必须存在多重采样缓冲区,这个不同平台可能不同
如glut提供的方式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULT I SAMPLE)
打开多重采样。如果没有多重采样缓冲区,那么无效
glEnable(GL_MULTISAMPLE)
注意:如果开启多重采样,那么点,直线和多边形的平滑属性(GL_LINE_SMOOTH
)将无效,意味着如果使用多重采样,就不能同时使用点和直线的平滑处理。
那么我们可以这样处理:
绘制点和直线时,关闭多重采样,绘制多边形时,开启多重采样。
Tips:
状态排序
打开或关闭状态将会修改驱动程序的内部状态,这种改变可能会对渲染性能造成影响
可以将需要相同状态的图元一起绘制,这样就可以提高性能(游戏性能优化)
多重采样的缓冲区默认使用片段的RGB值,并无alpha值,可以通过下面方法改变这个行为
GL_SAMPLE_ALPHA_TO_COVERAGE |
使用alpha值 |
GL_SAMPLE_ALPHA_TO_ON | 将alpha设置1,并使用它 |
GL_SAMPLE_COVERAGE | 使用glSampleCoverage设置的值 |
当启用GL_SAMPLE_COVERAGE时,glSampleCoverage函数允许指定一个特定值,与片段覆盖值进行位与操作
void glSampleCoverage(GLclampf value, GLboolean invert)