CPU、GPU、初学者的感受(OpenGL学习笔记)

图形学是当代电子游戏之魂,但同时也是在整个游戏开发中最难的一部分。对于一个图形学的初学者来说,其需要的知识储备非常广,包括了线性代数、计算机结构、GPU编程、CPU编程等。如果要开发出一个高画质的游戏,则也必须想方设法优化整个渲染流程以便其能够在大多是的PC端甚至移动端也能有较好的游戏运行效率。

对了,我上面提到了一个可能对于很多初学者来说的一个非常全新的概念:GPU编程。那么,这个又是什么一个神奇的新东西呢?

GPU和CPU最大的不同就是GPU有比CPU多得多的核心数。一般来说,一个4核心的CPU已经在现在很普遍了,甚至大多数的移动端CPU的核心数也已经到了8核心。多核心意味着其能够并行处理更多的任务。比如说计算一个矩阵的加法,如果编程中只用单核心的思路,那么就只能一个一个从左往右、从上往下加。这样的计算方式是极其缓慢的。如果让处理器能够同时矩阵中计算每一个对应的元素呢?矩阵加法的其中一个特点就是每一个对应元素的加法或者减法是与其它元素无关的,也就是说一行一列的元素相加的结果与八行五列的元素是完全没有关系的。也即是说,我们可以同时计算一行一列和八行五列的元素。这样的计算任务是可并行的。如果一个任务是可并行的,那么这个任务就可以通过多核心的方式将任务分配出去。每个核心同时计算矩阵的每块区域,这样可以大大提升计算效率。

但是,如果遇到很大的矩阵运算,或者很多的矩阵运算,CPU因为其核心数较少,很难将任务均匀分布出去。比如一个4K显示器,其像素个数就有4096 x 2160=8847360个像素点。如果让CPU在每1/60秒(60FPS)的时间要计算出8847360个像素点的信息,这几乎是不可能的。因此,GPU的概念就被提出来以应对这一需求。

拿nVidia(英伟达,N卡)的GTX 1080Ti举例,其拥有的核心数达到了3584颗,是一般CPU的近千倍。在并行运算中,其效率也是成倍的比CPU要高。GPU可以同时处理上千的像素点信息,因此可以快速在显示屏中渲染游戏画面。所以,在图形领域,GPU是扮演了非常重要的角色。

可能大家会对GPU和显卡这两个概念搞混,区别是什么呢?显卡是一个包含了GPU和显存等硬件的大集合,GPU是显卡的核心计算元件。而计算过程中需要储存的数据将会被存放在显存上。主要原因是因为显存经过特殊设计,并且离显卡最近,所以一般来说,图形渲染时的数据会存放在显存上而不是内存上。(当然也有例外,PS的架构就是显卡和CPU共用一块内存)

既然GPU那么强大,为什么不把GPU当作计算机的核心计算元件呢?刚才讲到了,显卡只能在可并行的任务中发挥最高的效率,但事实上有非常多的任务是不可并行的。

以煮面为例,一个简单的煮面流程:

0.烧水

1.等水烧开后下面

2.在碗里放调料

3.捞出面,装盘

这个流程中,我们不可能在水烧开前就去下面(经验告诉我们),这一块任务就是不可并行的任务。也即是需要计算的参数是上一计算任务的结果,我们必须等待上一计算完毕才能执行下一计算。GPU的计算能力其实是很差的,一个1080Ti的GPU的计算频率也只有1582MHZ,而一般的台式GPU已经到了3000+MHZ了。其体现形式就为,GPU烧水速度慢,CPU烧水速度快。但由于下面需要等到水烧开,就算是GPU用不同的核心同时执行烧水与下面的操作,那么下面的核心也必须等到烧水的核心完成后才能执行。这一点上,用CPU来执行该任务会更有优势。这就是为什么GPU不能取代CPU的原因。当然,这项任务有一个地方是可并行的,我们可能在等烧水的过程中便可以往碗里放调料了。不过,因为可并行的部分不多,依靠现在CPU的多核心计算,足以以很快的速度来完成此项任务。当然,有些很极端的情况,也就是我想要同时煮10000碗面,这个时候的话,就需要使用到GPU了。因为每碗面的任务是于其它碗面无关的,所以“同时煮10000碗面的任务是可并行的,但煮一碗面的任务是不可并行的。”(确实这段话听起来很奇怪)简单来说,GPU就像一大批小学生一起计算大批简单的计算,而CPU是几个大学教授来计算复杂的运算。

还有一个很重要的不同,GPU是没有控制能力的,计算机所有的硬件的调度必须由CPU去完成。(特例:显示器的输出甚至声音的输出需要GPU来控制(HDMI)不过GPU也是收到了CPU给的指令才执行的)从硬件的设计上,GPU的就是为了高并行的运算而设计的,所以为了这个目标,所有的设计都往该目标走。CPU是最核心的控制元件,也是最核心的计算元件,其当初的硬件设计目标也就是为了这个。

GPU的绘制依赖CPU给的指令。大家可能经常会听到一个概念叫绘制调用(DrawCall)。简而言之,一个绘制调用就是CPU发送数据到GPU,命令其绘制的一个过程。平常使用的OpenGL,其代码是在CPU上运行的,需要绘制的时候,CPU会把所有绘制需要的参数传递给GPU,然后GPU才能开始绘制工作。传递过程很耗时,所以现在初学图形学的第一大优化目标就是尽量减少绘制调用。尽量每一次发送绘制指令,就把所有数据一次性发完。这样可以大大提升绘制效率。刚才说了,OpenGL的代码是运行在CPU上的,那么是什么程序操控GPU去绘制我们想要的东西呢?答案是着色器“Shader”。着色器是一个运行在GPU的程序,其主要的着色器开发语言有GLSL(OpenGL)、HLSL(DirectX)、ShaderLab(Unity)。着色器的任务看起来非常简单:就是输入一大堆数据,然后输出像素点的颜色。虽然看起来简单,但其中的计算过程可以非常复杂,这也是图形学为什么难度很高的一个原因。(着色器相关的笔记会在以后细讲)

总之,学习图形学开发需要兼备CPU编程(也就是C++开发)和GPU编程(GLSL开发)两种能力。光是这点,门槛就已经很高了。图形学程序的Hello World(初学者写的第一个程序)99%的都是在屏幕上画一个三角形。(以后会讲到,万物皆可三角形。我们生活在一个三角形的世界里)画一个三角形有两种办法,一种是传统的办法,但传统的办法画出来的三角形很难看,现在基本不用了(即便传统的办法不需要写GPU程序)。还有就是现代的办法,同时写CPU和GPU的程序,这样可以自定义各种特效。

不过,其实基本概念了解之后,初学者掌握的知识在工业领域也是很广泛的应用。(初学者一上来就是画三角形,其实现在的3A大作也是画三角形,很多个三角形拼在一起,就是各种栩栩如生的游戏元素)而且,学会了基础的知识也是可以做出很多很酷炫的效果的。这也是我觉得学习图形学的乐趣和动力之一。

发表评论