OpenGL基础渲染

来源:互联网 发布:万网云新建数据库 编辑:程序博客网 时间:2024/06/16 00:32

    • 基础图形管线
      • 客户端-服务器
      • 着色器
      • 创建坐标系
      • 使用存储着色器
        • 存储着色器的分类
      • 将点连接起来
        • 点和线
        • 单独的三角形
        • 一个简单的批次容器
        • 不希望出现的图形
        • 多边形偏移
        • 裁剪
        • 混合
        • 多重采样

基础图形管线

渲染管线:也称为渲染流水线,OpenGL实现的一系列相关操作的处理阶段。

如何渲染一个三角形

客户端-服务器

就OpenGL而言,客户端是存储在CPU存储器中的,并且在应用程序中执行,或者在主系统内存的驱动程序中执行。
驱动程序将渲染命令与数据组合起来,并发送到服务器执行。

客户端和服务器在功能上是异步的,
工作流程:

  1. 客户端不断地将数据块和命令块组合在一起并送入缓存区。
  2. 这些缓存区会发送到服务器上执行。
  3. 服务器执行这些缓存区内容,同时客户机又做好了发送下一个渲染的数据或信息的准备。

管线停滞:客户端等待服务器或服务器等待客户端。


着色器

着色器是使用GLSL编写的程序。上图中有顶点着色器和片段着色器,这些着色器必须从源代码中编译和链接到一起才能使用(详细请看我的另一篇文章OpenGL着色器语言)。

顶点着色器:
处理从客户端输入的数据,应用变换,或者进行其他类型的数学运算来计算光照效果、位移、颜色值,等等。
每个顶点都得执行一次顶点着色器。

图元组合(Primitive Assembly):
把每个顶点组合在一起。

光栅化:
负责接收一个图元经过处理的点,并把它转换为片段。每个片段对应屏幕的一个像素。

片段着色器:
每个片段通过执行片段着色器进行填充,片段着色器会输出我们将在屏幕上看到的最终颜色值。


我们需要首先需要为着色器提供数据:

  • 属性:
    属性就是一个对每个顶点都要做改变的数据元素。总是以四维向量的方式进行内部存储的。
    属性就是一种对整个批次的属性都取统一值得单个值。
    属性会从办呢第客户机内存中复制存储到图形硬件中的一个缓冲区中。
  • Uniform值:
    通常设置完Uniform变量就紧接着发出渲染一个批次的命令。Uniform变量可以无次数限制地使用,可以用于整个表面的单个颜色值,也可以设置一个时间值。
    Uniform变量一个最常见的应用是在顶点渲染中设置变换矩阵。
    顶点和片段着色器中都可以使用。

  • 纹理:
    从顶点着色器和片段着色器中都可以对纹理之进行采样和筛选。
    典型情况下,片段着色器对一个纹理进行采样,并在图形的表面上应用图形数据。
    图形文件格式也可以以无符号字节(每个颜色通道8位)形式对颜色分量进行存储的。

  • 输出:
    输出数据是作为一个阶段着色器的输出(Out)定义的,而在后续阶段的着色器是作为输入(In)定义的。
    输出类型的数据可以简单地从一个阶段传递到下一个阶段。
    客户端代码接触不到这些内部变量,但在顶点着色器和片段着色器中都进行了声明。
    顶点着色器为输出变量分配一个值,这个值是常量,也可以在图元被光栅化时插入到顶点之间。片段着色器对应的同名输入值接受这个常量或插入值。


创建坐标系

正投影和透视投影,或者说是坐标系类型,实际上是一种特定的4x4变换矩阵。我们需要这3中矩阵类型的一种在适当的坐标系中渲染几何图形,若不采用这些矩阵中的一种,则会默认获得一个坐标范围-1.0到1.0之间的正投影。

可以使用GLFrustum类作为投影矩阵的容器。

  • 正投影
    通常在2D绘图中使用正投影,并将几何图形z坐标设为0。但z轴可以延伸到任何我们想要的长度。
    下面为一个正投影的例子:
    正投影
    上述这个区域被称为视景体,在正投影中,不存在照相机坐标或视点坐标系的概念,所以在这个范围之内的东西都会被显示在屏幕上,视景体外的图型将被裁剪掉,通过调用GLFrustum方法完成。
GLFrustum::SetOrthographic(GLfloat xMin, GLfloat xMax,                                GLfloat yMin, GLfloat yMax,                          GLfloat zMin, GLfloat zMax);
  • 透视投影
    透视投影会进行透视除法对观察很远的对象进行缩短和收缩。下图是一个平截头体的几何示例:
    平截头体
    GLFrustum类通过调用SetPerspective方法创建一个平截头体:
GLFrustum::SetPerspective(float fFov, float fAspect,                             float fNear, float fFar);

其中参数分别是视场角度,窗口宽度与高度的纵横比,以及到近截面和远截面的距离。如下图所示:
构建的平截头体影


使用存储着色器

在OpenGL核心框架中,并没有提供任何内建渲染管线,在提交一个几何图形进行渲染之前,必须指定一个着色器。
存储着色器有GLTools的C++类GLShaderManager进行管理,能够满足进行通常渲染的基本要求。
GLShaderManager在使用前必须初始化,

shaderManager.InitializeStockShaders();
  • 属性
    OpenGL支持多达16种可以为每个顶点设置的不同类型参数。并且可以与顶点着色器中的任何指定变量相关联。存储着色器为每个变量都使用一致的内部命名规则和相同的属性槽。
    GLShaderManager预定义的标识符:
标识符 描述 GLT_ATTRIBUTE_VERTEX 3分量(x,y,z)顶点位置 GLT_ATTRIBUTE_COLOR 4分量的(r, g, b, a)颜色值 GL_ATTRIBUTE_NORMAL 3分量(x,y,z)表面法线 GL_ATTRIBUTE_TEXTURE0 第一对2分量(s,t)纹理坐标 GL_ATTRIBUTE_TEXTURE1 第二对2分量(s,t)纹理坐标
  • Uniform值
    要对几何图形进行渲染,需要为对象提交属性矩阵,但首先要绑定到我们想要使用的着色器程序上,并提供程序的Uniform值,GLShaderManager类可以(暂时)为我们完成这些工作。UseStockShader函数会选择一个存储着色器并提供这个着色器的Uniform值:
GLShaderManager::UseStockShader(GLenum shader, ......);

这个函数根据我们选择的存储着色器从堆栈中取出正确的参数,这些参数就是特定着色器要求的Uniform值。


存储着色器的分类


  • 单位着色器
    单位着色器只是简单地使用默认的笛卡尔坐标系(在所以坐标轴上的范围都是-1.0到1.0),所以片段都应用同一种颜色,几何图形为实心和未渲染的,这种着色器只使用一个属性GLT_ATTRIBUTE_VERTEX。vColor参数包含了要求的颜色。
GLShaderManager::UseStockShader(GL_SHADER_IDENTITY, GLfloat vColor[4])

  • 平面着色器
    平面(Flat)允许为几何图形变换指定一个4x4变换矩阵,这种着色器只使用一个属性GLT_ATTRIBUTE_VERTEX。
GLShaderManager::UseStockShader(GL_SHADER_FLAT, GLfloat mvp[16], GLfloat vColor[4])

  • 上色着色器
    这种着色器唯一的Uniform值就是几何图形中应用的变换矩阵。GLT_ATTRIBUTE_VERTRX和GLT_ATTRIBUTE_COLOR都会用到,颜色被平滑地插入到顶点之间(称为平滑色)。
GLShaderManager::UseStockShader(GL_SHADER_SHADED, GLfloat mvp[16])

  • 默认光源着色器
    这种着色器使对象产生阴影和光照的效果,需要模型视图投影矩阵、投影矩阵和作为基本色的颜色值等Uniform值,所需要的属性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_NORMAL。大多数光照着色器都需要正规矩阵(normal matrix)作为Uniform值。着色器从模型视图矩阵中推导出正规矩阵很方便,但效率不太高。
GLShaderManager::UseStoclShader(GLT_SHADER_DEFAULT_LIGHT, GLfloat mvMatirx[16], Glfloat pMatrix[16], GLfloat vColor[4]);

  • 点光源着色器
    与默认光源着色器类似,但光源位置可能是特定的,这种着色器接受4Uniform值,即模型视图矩阵、投影矩阵、视点坐标系中的光源位置和对象的基本漫反射颜色。所需属性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_NORMAL。
GLShaderManager::UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, GLfloat mvMatrix[16], GLfloat pMatrix[16], GLfloat vLightPos[3], GLfloat vColor[4]);

  • 纹理替换矩阵
    着色器通过给定的模型视图投影矩阵,使用绑定到nTextureUnit纹理单元的纹理对几何图形进行变换。片段颜色直接从纹理样本中直接获取。
    所需属性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_NORMAL。
GLShaderManager::UseStockShader(GLT_SHADEr_TEXTURE_REPLACE, GLfloat mvpMatrix[16], GLint nTextureUnit);

  • 纹理调整着色器
    这种着色器将一个基本色乘以一个取自纹理单元你TextureUnit的纹理。
    所需的属性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_NORMAL。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_MODULATE, GLfloat mvpMatrix[16], GLfloat vColor, GLint nTextureUnit);

  • 纹理光源着色器
    这种着色器将一个纹理经过漫反射照明计算进行调整(相乘),光线在视觉坐标系中的位置是给定的。它接受5个Uniform值,即模型视图矩阵、投影矩阵、视觉空间中的光源位置、几何图形基本色和将要使用的纹理单元。
    所需的属性有GLT_ATTRIBUTE_VERTEX、GLT_ATTRIBUTE_NORMAL和GLT_ATTRIBUTE_TEXTURE_TEXTURE0。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, GLfloat mvMatrix, GLfloat pMatrix[16], GLfloat vLightPos[3], GLfloat vBaseColor[4], Gint nTextureUnit);

将点连接起来

像素是计算机屏幕上显示的最小元素,在OpenGL中绘图,我们关心得是视景体的坐标。将这些点、线和三角形从创建的3D空间投影到计算机屏幕的2D图形则是着色器程序和光栅化硬件所做的工作。

点和线

OpenGL几何图元:

图元 描述 GL_POINTS 每个顶点在屏幕上都是一个独立的点 GL_LINES 每一对顶点定义了一个线段 GL_LINE_STRIP 一个从第一个顶点一次经过每个后续顶点而绘制的线条 GL_LINE_LOOP 和GL_LINE_STRIP相同,但最后一个顶点和第一个顶点连接 GL_TRIANGLES 每3个顶点定义一个新的三角形 GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形 GL_TRIANGLE_FAN 以一个圆点为中心呈扇形排列,共用相邻顶点的一组三角形

  • 默认下点的大小是一个像素。
    下面函数改变默认点的大小
void glPointSize(GLfloat size);

点总是正方形的像素,为了获得圆点,我们必须在抗锯齿模式下绘制点。

还可以通过使用程序点大小模式来设置点大小,

glEnable(GL_PROGRAM_POINT_SIZE);

这种模式下允许我们通过编程在顶点着色器或几何着色器中设置点的大小,需要注意的是,着色器的内置变量为gl_PointSize,并且在着色器源码中,只要如下设置即可:gl_PointSize = 5.0;


  • 线
    默认情况下线宽为一个像素。
    下面函数可以改变线宽:
void glLineWidth(GLfloat width);

  • 线带

  • 线环

单独的三角形

最简单的实体多边形就是三角形,光栅化硬件最欢迎三角形,也是OpenGL中支持的唯一一种多边形。每3个顶点定义一个新的三角形。

  • 环绕
    顺序与方向结合来指定顶点的方式成为环绕。
    三角形的两种环绕
    默认下,OpenGL认为具有逆时针方向环绕的多边形是正面的。
    这个问题是重要的o,我们常常希望为一个多边形的正面和背面分别设置不同的纹理特征。可以隐藏一个多边形的背面,或给他设置一种不同的颜色和反射属性。
    可以调用以下函数改变这个默认行为:
glFrontFace(GL_CW);

参数GL_CW告诉OpenGL顺时针的多边形被认为是正面的,GL_CCW参数则是逆时针。

  • 三角形带
    GL_TRIANGLE_STRIP
    有两个优点:
    1、用前三个顶点指定第1个三角形后,对于接下来的每个三角形,只需再指定一个顶点。需要绘制大量三角形时,这种方法可以节省大量的程序代码和数据存储空间。
    2、提高性能和节省带宽。
  • 三角形扇
    GL_TRIANGLE_FAN

一个简单的批次容器

GLTools库包含一个简单的容器,叫GBatch。这个类可以作为前面提到过的7中图元简单批次的容器使用,而且它知道在使用GLShaderManager支持的任意存储着色器时如何对图元进行渲染。

使用GLBatch的步骤:

  1. 对批次进行初始化,告诉这个类它代表哪种图元,其中包括顶点数,以及(可选)一组或两组纹理坐标。void GLBatch::Begin(GLenum primitive, GLuint nVerts, GLuint nTextureUnits = 0);
  2. 然后至少要复制一个由3分量(x、y、z)顶点组成的数组。
void GLBatch::CopyVertexData3f(GLfloat *vVerts);

还可以选择复制表面法线、颜色和纹理坐标。

void GLBatch::CopyNormalDataf(GLfloat *vNormal);void GLBatch::COpyColorData4f(GLfloat *vColors);

最后调用End来表明已经完成了数据复制工作:

void GLBatch::End(void);

下面为使用这个类的渲染一个三角形的例子:

GLBatch triangleBatch;//Load up triangleGLfloat vVerts[] = {-0.5f, 0.0f, 0.0f,                     0.5f, 0.0f, 0.0f,                     0.0f, 0.5f, 0.0f};triangleBatch.Begin(GL_TRIANGLES, 3);triangleBatch.CopyVertexData3f(vVerts);triangleBatch.End();

最后,在RenderScene函数中,我们选择适当的存储着色器并调用Draw函数。

GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);triangleBatch.Draw();

不希望出现的图形

在默认情况下,我们进行渲染图形时,例如我们绘制一个由很多个三角形组成的试题对象时,第一个绘制的三角形可能被后面绘制的三角形覆盖。
解决方法:
1、对这些三角形就行排序,首先渲染较远的三角形。这种方式称为“油画法”。非常低效 。
2、剔除。
3、深度测试。能够实现真实视觉并提高性能。


三种方法的比较;

背面剔除不能消除重叠的独立对象。深度测试则可以。
油画法是先绘制较远的对象,
深度测试是先绘制那些离观察者较近的对象,然后再绘制那些较远的对象。深度测试将消除那些应该被已存在像素覆盖的像素。


  • 正面和背面剔除:
    剔除可以对正面和背面三角形区分。非常高效,在渲染图元装配阶段就整体抛弃一些三角形,并且没有执行任何不恰当的光栅化操作。
//开启表面剔除glEnable(GL_CULL_FACE);
//关闭glDisable(GL_CULL_FACE);

指明剔除正面和是背面:

void glCullFace(GLenum mode);

mode参数的可用值为GL_FRONT、GLBACK或GL_FRONT_AND_BACK。

终上所述,要剔除不透明物体的内部稽核图形就需要以下两行代码:

glCullFace(GL_BACK);glEnable(GL_CULL_FACE);

  • 深度测试
    概念:在绘制一个像素时,将一个z值分配给它,这个值表示它到观察者的距离。当另外一个像素需要在同一位置进行绘制时,两个像素的z值进行比较。z值大的表示离观察者比较近。
    在内部,这个任务是通过深度缓存区实现的,它存储了屏幕上每个像素的深度值。
    可以用以下方式申请一个颜色缓存区和一个深度缓存区。
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
//开启深度测试glEnable(GL_DEPTH_TEST);

  • 多边形模式
    多边形不一定是实心的。
    默认情况下,多边形是作为实心图形绘制的,但我们可以将三角形指定为显示轮廓或只显示顶点来改变。用下面函数进行完成:
void glPolygonMode(GLenum face, GLenum mode);

face参数和表面剔除一样,mode参数的可用值为GL_FILL(默认值)、GL_LINE或GL_POINT。


多边形偏移

当两个对象的深度值z相同(如贴花)时,即z冲突时,深度测试就会出现麻烦。
解决方法:
1. 第二次绘制时在z方向稍微做一点偏移。但z只能沿这z轴向镜头移动。而且必须要移动足够多以使深度测试产生偏移,但又不能移动太多防止几何图层之间产生缝隙。
2. glPolygonOffset函数可以使我们调节片段的深度值,而不改变3D空间中的物理位置。

void glPolygonOffset(GLfloat factor, GLfloat units);

应用到片段上的总偏移

Depth Offset = (DZ x factor) + (r x units)

其中DZ是深度值相对于多边形屏幕区域的变化量。而r则是是深度缓存区值产生变化的最小值。
负值将使z距离我们更近。
除了使用glPolygonOffset设置偏移值外,还必须启用多变性单独偏移来填充几何图形(GL_POYGON_OFFSET_FILL)、线(GL_POLYGON_OFFSET_LINE)和点(GL_POLYGON_OFFSET_POINT)。


裁剪

另一种提高渲染的方法时只刷新 屏幕上发生变化的部分。OpenGL允许我们在将要进行渲染的窗口中指定一个裁剪框。默认下,裁剪框和窗口大小相同,
开启裁剪测试:

glEnable(GL_SCISSOR_TEST);

在执行渲染的窗口中,裁剪框用下面函数指定(以像素为单位):

void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);

其中,x、y指定了裁剪框的最下角,宽高指定了裁剪框的尺寸。


混合

颜色值是放在颜色缓存区中的。
在启动深度测试时,如果打开了OpenGL混合,那么下层的颜色值就不会被清除。

glEnable(GL_BLEND);

开启混合后,新颜色值会与已经存在的颜色值在颜色缓存区中就行组合。

  • 组合颜色
    已经存在的颜色值称为目标颜色,作为当前渲染命令的结果进入颜色缓存区的颜色值称为源颜色
    组合方式是由混合方程式控制的。
    默认的混合方程式 :

    CF = (CS * S) + (CD * D)


CF是最终计算产生的颜色,CS源颜色,CD目标颜色,S和D分别是源颜色和目标颜色的混合因子。设置混合因子:
glBlendFunc(GLenum S, GLenum D);
OpenGL混合因子:![OpenGl混合因子](http://img.blog.csdn.net/20160715103153053)![这里写图片描述](http://img.blog.csdn.net/20160715103229569)其中下标表示源、目标和颜色,R、G、B和A分别表示红、绿、蓝和alpha。
例子:
glBlendGFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
这个函数告诉OpenGL接受源颜色并将这个颜色(RGB值)与alpha值相乘,然后把这个结果加上目标颜色乘以“1减去源颜色的alpha值”的结果。这个函数经常用于实现在其他一些不透明的物体前面绘制一个透明物体的效果。
  • 改变混合方式
void glBlendEqation(GLenum mode);

可用的混合方程式:
混合方程式


  • 抗锯齿
    OpenGL混合功能的一个用途是抗锯齿。
    像素是正方形的,通常可以清楚地看到两种颜色的分界,称为锯齿
    为了消除图元之间的锯齿状边缘,OpenGL使用混合功能来混合片段 的颜色,也就是把向所的目标颜色与周围像素的颜色进行混合。从本质来说,像素的颜色会稍微延伸到相邻的像素。

开启抗锯齿功能:

//首先开启混合glEnable(GL_SRC_ALPHA, GL_ONE_SRC_ALPHA);

还需保证混合方程式设置为GL_ADD(不过这个为默认的设置,也是最常见的),

//最后对点、直线或多边形(任何实心图元)进行抗锯齿处理。glEnable(GL_POINT_SMOOTH);glEnable(GL_LINE_SMOOTH);glEnable(GL_POLYGON_SMOOTH);

多重采样

最大优点之一是能够使多边形边缘更为平滑,使渲染效果更加逼真。不过多边形的平滑处理并不是所有平台上都得到实现。
多重采样,可以用来解决这个问题。
OpenGL如果支持多重采样,就会添加一个额外的缓冲区。所有的图元在每个像素上都进行了多次采样,其结果就存储在这个缓存区中,
为了请求一个多重采样、完全颜色、带深度的双缓存区,可以调用:

glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH|GLUT_MULTISAMPLE);

打开多重采样

glEnable(GL_MULTISAMPLE);

注意:当多次采样启动时,点、直线和多边形的平滑特性都将被忽略。


状态排序:
打开或关闭不同的OpenGL特性将会修改驱动程序的内部状态,这中状态的改变可能对渲染的性能造成影响。对相同状态的几何图形就可以在一起绘制。这种状态排序是游戏中最常用的提高速度的方法之一。


多重采样缓存区在默认情况下使用片段的RGB值,并不包括alpha成分,可以通过glEnable(使用下面3个值之一)来修改这个行为。
GL_SAMPLE_ALPHA_COVERAGE——使用alpha
GL_SAMPLE_ALPHA_TO_ON——将alpha值设为1并使用它
GL_SAMPLE_COVERAGE——使用glSampleConverage所设置的值。
当启用GL_SAMPLE_COVERAGE——-使用glSampleCoverage函数允许指定一个特定的值,它是与片段覆盖值进行按位与操作的结果。

void glSampleCoverage(GLclampf value, GLboolean invert);

0 0