OpenGL初步认识

前言

关于OpenGL的一些基本认识,以下为两个很好的现代OpenGL教程,其链接以及GitHub代码地址如下:

OpenGL的理解

OpenGL(Open Graphics Library)是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口。为了跨平台,实际上狭义上的OpenGL只是一个标准,各种硬件公司遵循其标准来生产硬件,在软件层面上按照该标准也有多种代码实现,所以才会类似GLEW、GLUT、GLFW这些库的存在。

OpenGL是一种客户端-服务器(client-server)类型的系统。我们编写的程序就是一个客户端,而我们的计算机图形硬件制造商提供的OpenGL的实现就是服务器。在一些OpenGL的实现里(例如一些和XWindow System相关的应用),客户端和服务器可能会在不同的机器上运行,中间用网络连接。

状态机

OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。

基本图形绘制方式

1. 传统方式绘制

传统绘制方式在OpenGL新版本中已经废弃,虽然在兼容模式下还能工作,但不建议使用。传统绘制方式的包括以下两方面的内容:

  • 立即模式(Immediate Mode):使用glBegin…glEnd方式制定绘制方式,在这两个函数对之间给出绘制的数据,固定渲染管线,这种方式称为立即模式
  • 显示列表(Display List): 显示列表是一组存储在一起的OpenGL函数,可以再以后执行。调用一个显示列表时,它所存储的函数就会按照顺序执行。显示列表通过存储OpenGL函数,可以提高性能。如果需要多次重复绘制同一个几何图形,或者如果有一些需要多次调用的用于更改状态的函数,就可以把他们存储在显示列表中。

2. 现代绘制方式

现代的绘制方式主要有三种方法:

  • 顶点数组绘图:使用顶点数组方式,需要利用glEnableClientState开启一些特性,这里开启顶点数组特性使用glEnableClientState(GL_VERTEX_ARRAY)。使用顶点数组时,用户定义好存储顶点的数据,在调用glDrawArrays、glDrawElements之类的函数时,通过glVertexPointer设定的指针,传送数据到GPU。当调用完glDrawArrays后,GPU中已经有了绘图所需数据,用户可以释放数据空间。
  • VBO和VAO绘图:VAO即Vertex Array Object,是一个包含一个或多个VBO的对象,被设计用来存储一个完整被渲染对象所需的信息。VBO即Vertex Buffer Object,是一个在高速视频卡中的内存缓冲,用来保存顶点数据,也可用于包含诸如归一化向量、纹理和索引等数据。
  • 结合Shader绘图

在旧版本的OpenGL中,是通过glVertex,glTexCoord和glNormal函数把每帧数据发送给GPU的。在现代OpenGL中,所有数据必须通过VBO在渲染之前发送给显卡。当你需要渲染某些数据时,通过设置VAO来描述该获取哪些VBO数据推送给shader变量。

3. 各种中绘图方式利弊

  1. 使用立即模式,缺点很明显,数据量大一点的话,代码量增加,而且数据发送到服务端需要开销
  2. 使用显示列表,显示列表是一个服务端函数,因此它免除了传送数据的额外开销。但是,显示列表一旦编译后,其中的数据无法修改
  3. 使用顶点数组,可以减少函数调用和共享顶点数据的冗余。但是,使用顶点数组时,顶点数组相关函数是在客户端,因此数组中数据在每次被解引用时必须重新发送到服务端,额外开销不可忽视
  4. 使用VBO在服务端创建缓存对象,并且提供了访问函数来解引用数组,如:例如在顶点数组中使用的函数如glVertexPointer(),glNormalPointer(), glTexCoordPointer();另外,不像显示列表,VBO中数据可以通过映射到客户端内存空间而被用户读取和更新;VBO的另外一个优势是它像显示列表和纹理一样,能和多个客户端共享缓存对象。可见使用VBO优势很明显

OpenGL pipeline

3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分的作用:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

具体到不同版本的OpenGL中pipeline的过程会有不同,现代OpenGL程序中允许用户自己定制着色器,这使得绘图更灵活,下面是一个比较典型的示意图:

其中:顶点着色器和片元着色器是OpenGL整个渲染管线必须要有的过程,其他的着色器可以根据需要进行选择,图元是基本的绘制形状,如点、线、三角形;另外除了第一阶段准备定点数据是在CPU中做,其他的一般都是在GPU中工作的。

opengl pipeline

其中顶点着色器(Vertex Shader)比较重要,通常会有如下作用:

  • 顶点变换 根据模型视图和投影矩阵变换
  • 光照计算
  • 纹理坐标变换(纹理矩阵)
  • 材质状态、纹理坐标生成

Shaders

Shaders在现代OpenGL中是个很重要的概念。Shaders是一段GLSL小程序,运行在GPU上而非CPU。它们使用OpenGL Shading Language (GLSL)语言编写,看上去像C或C++,但却是另外一种不同的语言。使用shader就像你写个普通程序一样:写代码,编译,最后链接在一起才生成最终的程序。在旧版本的OpenGL中,shaders是可选的,在现代OpenGL中,为了能在屏幕上显示出物体,shaders是必须的。

Shader的中文是着色器,但是它的作用不仅仅只做着色,shaders实际上干了啥,这取决于是哪种shader。

纹理

要使渲染的物体更加逼真,一方面我们可以使用更多的三角形来建模,通过复杂的模型来逼近物体,但是这种方法会增加绘制流水线的负荷,而且很多情况下不是很方便的。使用纹理,将物体表面的细节映射到建模好的物体表面,这样不仅能使渲染的模型表面细节更丰富,而且比较方便高效。纹理映射就是这样一种方法,在程序中通过为物体指定纹理坐标,通过纹理坐标获取纹理对象中的纹理,最终显示在屏幕区域上,已达到更加逼真的效果。

光照

要模拟现实的光照是困难的,例如实际光照中,一束光可以经过场景中若干物体反射后,照射到目标物体上,也可以是直接照射到目标物体上。其中经过其他物体反射后再次照射到目标物体上,这是一个递归的过程,将会无比复杂。因此实际模拟光照过程中,总是采用近似模型去接近现实光照。Phong Reflection Model是经典的光照模型,它计算光照包括三个部分:环境光+漫反射光+镜面光。

  • 环境光:环境光是场景中光源给定或者全局给定的一个光照常量,它一般很小,主要是为了模拟即使场景中没有光照时,也不是全部黑屏的效果
  • 漫反射光:漫反射光成分,是光照中的一个主要成分,漫反射光强度与光线入射方向和物体表面的法向量之间的夹角相关
  • 镜面反射光:镜面光成分模拟的是物体表面光滑时反射的高亮的光,镜面光反映的通常是光的颜色,而不是物体的颜色,计算镜面光成分时,要考虑光源和顶点位置之间向量L、法向量N、反射方向R、观察者和顶点位置之间的向量V之间的关系

我们实现的光照计算是在片元着色器中进行的,这种是基于片元计算的,称之为Phong
shading,在过去OpenGL编程中实现的是在顶点着色器中进行光照计算,这是基元顶点的计算的,称之为Gouraud Shading。

参考

  1. OpenGL学习脚印:基本图形绘制方式比较
    2.【OpenGL】理解一些基本问题
  2. OpenGL渲染管线(rendering pipeline)
  3. Rendering Pipeline Overview
  4. OpenGL学习脚印:绘制一个三角形
  5. OpenGL学习脚印: 光照基础(basic lighting)
  6. 现代OpenGL教程 01 - 入门指南
  7. learnOpenGL CN - OpenGL
  8. learnOpenGL CN - 你好,三角形
Compartir