OpenGL 渲染管线与显卡可执行程序

渲染管线的六个步骤

OpenGL 渲染管线的六个步骤,从指定几何图元到帧缓冲区写入像素,图像就被 OpenGL 引擎一步步地渲染到屏幕(FBO)上去了。

指定几何对象

OpenGL 引擎会根据开发者的指令去绘制几何图元。OpenGL(ES)提供给开发者的绘制方法 glDrawArrays 的第一个参数 mode,就是指定绘制方式。绘制方式的可选值有点、线、三角形三种,分别应用于不同的场景中。
粒子效果的场景中,我们一般用点(GL_POINTS)来绘制;直线的场景中,我们主要用线(GL_LINES)来绘制;所有二维图形图像的渲染,都用三角形(GL_TRIANGLE_STRIP)来绘制。
我们根据不同的场景选择的绘制方式,就决定了 OpenGL(ES)渲染管线的第一阶段怎么去绘制几何图元。

顶点变换

不论阶段一指定的是哪种几何对象,所有的几何数据都要来到顶点处理阶段,顶点处理阶段的操作就是变换顶点的位置:一是根据模型视图和投影矩阵的计算来改变物体(顶点)坐标的位置;二是根据纹理坐标与纹理矩阵来改变纹理坐标的位置。这里的输出是用 gl_Position 来表示具体的顶点位置的,如果是用点(GL_POINTS)来绘制几何图元的话,还应该输出 gl_PointSize。

图元组装

在阶段二的时候,我们已经确定了物体坐标和顶点坐标,然后到这个阶段进行图元组装。这里我们根据应用程序送往图元的规则(如 GL_POINTS 、GL_TRIANGLES 等)以及具体的纹理坐标,把纹理组装成图元。

栅格化操作

由上一阶段传递过来的图元数据,在这里会被分解成更小的单元并对应帧缓冲区的各个像素。这些单元叫做“片元”,一个片元可能包含窗口颜色、纹理坐标等属性。片元的属性则是图元上顶点数据经过插值而确定的,这其实就是栅格化操作,这个操作能够确定每一个片元是什么。

片元处理

通过纹理坐标取得纹理中相对应的片元像素值,根据自己的需要改变这个片元,比如调节饱和度、锐化等,最终输出的是一个四维向量 gl_FragColor,我们用它来表示修改之后的片元像素。

帧缓冲操作

帧缓冲操作是渲染管线的最后一个阶段,执行帧缓冲的写入等操作,OpenGL 引擎负责把最终的像素值写入到帧缓冲区中。

创建显卡执行程序

把 Shader 扔给 OpenGL 的渲染管线,这一部分叫做接口程序部分,就是利用 OpenGL 提供的接口来驱动 OpenGL 进行渲染。

在这里插入图片描述

创建 Shader

第一步是调用 glCreateShader 方法创建一个对象,作为 Shader 的容器,这个函数返回一个容器的句柄,函数原型如下:

GLuint glCreateShader(GLenum shaderType); 

函数原型中的参数 shaderType 有两种类型:

  • 一是 GL_VERTEX_SHADER,创建顶点着色器时开发者应传入的类型;
  • 二是 GL_FRAGMENT_SHADER,创建片元着色器时开发者应传入的类型。

第二步就是给创建的这个 Shader 添加源代码,对应图片最右边的两个 Shader Content,它们就是根据 GLSL 语法和内嵌函数书写出来的两个着色器程序,都是字符串类型。这个函数的作用就是把开发者的着色器程序加载到着色器句柄所关联的内存中,函数原型如下:

void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings)

最后一步就是来编译这个 Shader,编译 Shader 的函数原型如下:

void glCompileShader(GLuint shader);

编译完成之后,怎么知道这个 Shader 被编译成功了呢?我们可以使用下面这个函数来验证。

void glGetShaderiv (GLuint shader, GLenum pname, GLint* params);

其中,第一个参数 GLuint shader 就是我们要验证的 Shader 句柄;第二个参数 GLenum pname 是我们要验证的这个 Shader 的状态值,我们一般在这里验证是否编译成功,这个状态值选择 GL_COMPILE_STATUS。第三个参数 GLint* params 就是返回值,当返回值是 1 的时候,说明这个 Shader 编译成功了;如果是 0,就说明这个 shader 没有被编译成功。
这个时候,我们就需要知道着色器代码中的哪一行出了问题,所以还需要开发者再次调用这个函数,只不过是获取这个 Shader 的另外一种状态,这个时候状态值应该选择 GL_INFO_LOG_LENGTH,那么返回值返回的就是错误原因字符串的长度,我们可以利用这个长度分配出一个 buffer,然后调用获取出 Shader 的 InfoLog 函数,原型如下:

void glGetShaderInfoLog(GLuint object, int maxLen, int *len, char *log);

之后我们可以把 InfoLog 打印出来,帮助我们来调试实际的 Shader 中的错误。按照上面的步骤我们可以创建出 Vertex Shader 和 Fragment Shader,那么接下来我们看图片中的左半部分,也就是如何通过这两个 Shader 来创建 Program。

创建 Program

首先我们创建一个程序的实例作为程序的容器,这个函数返回程序的句柄。函数原型如下:

GLuint glCreateProgram(void);

紧接着将把上面部分编译的 shader 附加(Attach)到刚刚创建的程序中,调用的函数名称如下:

void glAttachShader(GLuint program, GLuint shader);

其中,第一个参数 GLuint program 就是传入在上面一步返回的程序容器的句柄,第二个参数 GLuint shader 就是编译的 Shader 的句柄,当然要为每一个 shader 都调用一次这个方法才能把两个 Shader 都关联到 Program 中去。
当顶点着色器和片元着色器都被附加到程序中之后,最后一步就是链接程序,链接函数原型如下:

void glLinkProgram(GLuint program);

传入参数就是程序容器的句柄,那么具体这个程序到底有没有链接成功呢?OpenGL 也提供了一个函数用来检查这个程序的状态,函数原型如下:

glGetProgramiv (GLuint program, GLenum pname, GLint* params);

第一个参数就是传入程序容器的句柄,第二个参数代表我们要检查这个程序的哪一个状态,这里面我们传入 GL_LINK_STATUS,最后一个参数就是返回值。返回值是 1 则代表链接成功,如果返回值是 0 则代表链接失败。类似于编译 Shader 的操作,如果链接失败了,可以获取错误信息,以便修改程序。
获取错误信息时,也得先调用这个函数,但是第二个参数我们传递的是 GL_INFO_LOG_LENGTH,代表获取出这个程序的 InfoLog 的长度,拿到长度之后我们分配出一个 char* 的内存空间以获取出 InfoLog,函数原型如下:

void glGetProgramInfoLog(GLuint object, int maxLen, int *len, char *log);

调用这个函数返回的 InfoLog,可以作为 Log 打印出来,以便定位问题。

到这里我们就创建出了一个 Program。回顾一下整个过程和 C 语言的编译和链接阶段非常相似,对比构造 OpenGL Program,编译阶段做了编译顶点着色器和片元着色器,并且把两个着色器 Attach 到 Program 中,链接阶段就是链接这个 Program。

创建显卡执行程序之后就是如何使用这个程序,其实使用这个构建出来的程序也很简单,调用 glUseProgram 这个方法即可,然后将一些内存中的数据与指令传递给这个程序,让这个程序运行起来。但是要想完全运行到手机上,还需要为 OpenGL ES 的运行提供一个上下文环境。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值