OpenGL是什么

640 阅读6分钟

image.png

不知道大家在学习OpenGL的时候是什么感受,本人完全是一团乱麻,刚刚开始只会机械地照抄别人已经写好的代码,原理什么的完全不懂。还会有如下灵魂三问:

  • 这是什么语法,怎么这么难理解
  • glsl是什么,写错了完全没有提示,大哭。。。
  • 原理究竟是什么,有能说清楚的吗?

今天咱们就来争取把opengl的原理说清楚,并且说明怎么学习这件事

1、OpenGL是什么

OpenGL(英语:Open Graphics Library,译名:开放图形库或者“开放式图形库”)是用于渲染2D3D矢量图形的跨语言跨平台应用程序编程接口(API)

以上内容摘抄自百度百科,说的非常清楚,OpenGL只是接口,它的实现由各显卡公司实现,它只是一个标准,所以OpenGL能够跨平台,android可用,ios和桌面端都可以用

OpenGL,最重要的两个概念:

  • 图形渲染管线:可以理解为OpenGL的工作流程,非常非常重要的概念
  • 状态机:OpenGL并没有采用面向对象的编程思维,它是面向过程的,而且是用状态机来实现面向过程的,这也是它难以理解的最重要原因

2、图形渲染管线

image.png

上图就概括了OpenGL的工作流程。

假想一下,如果你要绘制一个三角形该怎么办?

  • 确定三个点的坐标
  • 把三个坐标用线连起来
  • 把三角形区域着色

巧了,OpenGL也是这么做的。

如上图所示,开发者确定三个点的坐标之后,把坐标交给“顶点着色器”处理,顶点着色器负责解析开发者传来的坐标数据,并且把它映射到屏幕上

图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。说人话就是,连线,把顶点数据连成我们要的形状

几何着色器我们暂时不管,现在还用不上。

几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

光栅化阶段,从上面这段话来看有点难以理解,我们说人话,就是把图形的所有区域(三角形内)分成一个个小格子(即是一个个像素),这个小格子让你分别涂色,可以这个格子是黑色,下个格子是另一个颜色

片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

片段着色器简单来讲,就是把光栅化阶段产生的小格子着色,怎么着色由片段着色器说了算。

混合和测试阶段:暂时也不用管,还用不上。

请记住:OpenGL的流程我们无法更改,如上图所示,每一步产生的数据都是下一步的输入

细心的同学可能看到了,上个图中为什么三个着色器的颜色是蓝色的,其它步骤的底图是灰色的,因为以上的步骤中,只有三个着色器是需用开发者自定义的,其它的事OpenGL自己来。这也是我们无法更改OpenGL流程的原因。而且以上三个着色器都是运行在GPU中的

3、状态机

提一个问题,cpu和gpu的区别是什么呢?

image.png

绿色是计算单元,橙色是存储单元,黄色是控制单元。如图所示,cpu要处理所有情况,所以它有非常复杂的控制单元和存储单元。但gpu工况相对简单的多,它只要处理图形图像方面即可,所以它并不需要大量的存储单元和控制单元,所以它的计算能力非常强。

OpenGL的着色器运行在Gpu中,gpu的控制模块相对非常简单,所以OpenGL用上了状态机。什么是状态机,请看代码:

void TextureSample::initTextureData(void* data, int width, int height) {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_TextureId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glBindTexture(GL_TEXTURE_2D, GL_NONE);
}


glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
//如果只有一个纹理,那么这里可以不指定,系统默认会为我们赋值。
glUniform1i(m_SamplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);

以上两段代码看着有点类似,尤其是第一段,看方法名字是先激活纹理,再绑定纹理到一个id上,再给纹理设置图片buf,最后再绑定空纹理

第二段代码又激活纹理了,再绑定纹理到一个id上,指定参数,再绘制三角形了

为什么要激活两次,绑定两次?

image.png

//绑定某种类型
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
//中间所有的操作都是针对刚刚的绑定数据。后面解绑为空了,之间做的逻辑仍然有效
...
//绑定某种类型为GL_NONE
glBindTexture(GL_TEXTURE_2D, GL_NONE);

第一次绑定里,做的所有操作,最后都保存到m_TextureId这个id关联的纹理中了。 第二次绑定中,其实就是来启用第一次绑定里已经定义好的纹理了

以下是我自己的理解:

gpu是个头脑简单的肌肉男,只会按流程办事,要给某项元素赋值,只能先绑定,再操作,再解绑,后面再使用,你不能直接赋值,它看不懂,因为gpu的控制单元实在太少了,它不像cpu,为了应付所有场面而生的,gpu是个大直男,直男太难懂了。。。

这就是OpenGL的状态机,各种类型的状态(比如设置纹理、设置顶点)都已经给你准备好了,你要设置这些数据的时候就得调用相应的接口,告诉GPU,我来处理这个数据了,一个状态一个状态地处理,按OpenGL的要求的格式来处理。

4、学习资料、路线

最强的学习资料在这里:Learn OpenGL

最全面,最深刻,如果有不懂的地方,来这里看看。

最后提一句,本人是android开发,此次学习是学习OpenGL的c++版本,并不是 java 版本,而且学习的是3.0的版本。

android用的是OpenGL ES,它是OpenGL的阉割版本,为了照顾移动端性能而设计的。