上一节,已经可以看见三角形了,一个白色的三角形,显示在我们正确的顶点位置上,这是怎么出现的呢?
答案就是:如果你没有提供自己的着色器的话,GPU驱动会向你提供默认的着色器。但这事实上是基于你的驱动的。
opengl标准里面没有内容,这实际上只取决于gpu制造商,它们说,嘿,you know what 如果你不提供着色器,那我们就写一个最基本的给你,所以你至少可以比较容易一些地调试你的代码,或者其它一些别的。
第一个问题,着色器是什么?
着色器基本上只是一个程序,它运行在GPU上面,这就是所有了。你应该取考虑,当你通过程序去考虑着色器时,我的意思是这就像一块代码,这块代码我们可以在我们的电脑上写成文本,写成一个字符串,然后我们可以给opengl,我们可以发送到显卡然后像别的程序一样编译它,像别的程序一样链接他,像别的程序一样运行,但不同点在于它是运行在我们的gpu上,在我们的显卡上,而不是像C++程序一样运行在cpu上。
所以为什么我们需要实际运行在gpu上的程序,为什么我们不得不写代码然后运行在gpu上?
很明显,我们在学习图形编程的相关内容,因此显卡扮演一个主要角色。
但明确的原因是,我们想要给GPU编程,是因为我们想要能够告诉GPU要做什么,我们想利用GPU的性能去在屏幕上实际画出图形,这不是说我们要做的每一件事都需要在gpu上做,一些东西是CPU更快的。随着本系列的深入,我们可能会发现一些我们更喜欢在CPU上做的事情。但不可否认的是,有一堆和图形有关的事情,GPU运行起来会更快,这就是着色器派上用场的地方。
现在不仅仅是我们想从CPU获取一些东西把它们放到GPU上,而且从根本上说我们需要能够编程GPU,因为即使以绘画的形式,只是画一个简单的三角形,我仍然想要能够告诉GPU如何去画这个三角形,像是顶点位置在哪,这个三角形的颜色应该是什么,应该怎么画,所有的这些东西。当我们在一个更加复杂的3d场景时,光照是一个很好的为什么我们想要编程GPU的例子,光照应该如何工作,所有的这些东西,我不想我自己说得太超前,但是所有的这些东西需要去编程,gpu是不知道如何做到这些的。我们需要告诉GPU,我们发给它的这些数据要做什么,这就是着色器的本质。
现在这一节还有对于大多数openGL和大多数图形编程来说,我们需要注意两种类型的着色器,顶点着色器和片段着色器。片段着色器有时候也叫像素着色器。所以这两种着色器类型是目前为止(2017)最流行的两种类型,可能是90%的时间都会用到的类型,还有其它类型的着色器:细分着色器、几何着色器,如果你做的和其它事情完全一样的话,那么这种事情就是计算着色器。有很多不同类型的着色器,我们不用去考虑这些,现在我们要做的就是顶点着色器和片段着色器,这就是全部。还有更多,当你开始学习更高级的东西的时候,它们就派上用场了,当然这个系列以后我们也会覆盖到这些类型的着色器,当我们需要获取更复杂的图形时。
但是现在,90%的着色器编程,你要解决的都是顶点着色器,片段着色器(像素着色器)。
什么是顶点着色器、片段着色器?为什么它们是两种类型?我们怎么用它们?
现在我们没有真正地覆盖到opengl的管线,或者是一种图像渲染管线的标准,但是粗略地说是如何工作的呢,在你脑海里面应该有一副画面,就是我们在CPU上写了一大堆数据,我们把数据发给了GPU,我们发出了一个调用,我们绑定了一些状态,在你开始调用之前。最后我们进入了着色阶段,或者更确切的说,GPU开始实际处理调用并在屏幕上绘制一些东西。然后我们在屏幕上得到了三角形,这个具体的过程实际上就是渲染管线。
我们如何从有数据到屏幕上有结果呢?
现在,着色器就派上用场了,当GPU开始绘制三角形时,顶点着色器和片段着色器四两种沿着管线不同类型的着色器,所以当我们发出一个绘制调用。顶点着色器会获得调用,片段着色器会get called,然后我们会在屏幕上看到结果。(为了简单这样叙述,其实在顶点着色器之前有很多阶段,在顶点着色器和片段着色器之间以及片段着色器和恢复阶段之间,所有的这些东西)
顶点着色器
顶点着色器:获取了每一个我们想要渲染的顶点的调用。这个例子上,我我们有三个顶点,这就意味着顶点着色器会调用三次,每个顶点各一次。一个顶点着色器的基本目的就是告诉opengl你想要那个顶点在显示器的哪里,简单的说就是窗口的哪里。在你的电脑上,你有一个窗口开着,这就是你用来渲染图形的地方。
再次说一下,那里有一个顶点着色器,有些人就会认为它像是和光照、阴影或别的的有关,不是的,它只是一个程序,这就是全部了。这甚至于实际的图形没有任何关系,从传统图形的颜色或别的角度来说。无论如何,所有顶点着色器制定了你想要的位置。虽然如此,它也用来解析从属性到下一个阶段的数据,在我们这个例子里面,下一个阶段是片段着色器。顶点着色器实际上会带着所有我们指定在缓冲区里面的顶点的属性。
片段着色器
(片段和像素在术语上有一点点不同,但是我们像现在不深入纠结这个,你可以认为片段就是像素)
片段着色器:片段着色器会为每一个像素运行一次(thats needs to get rasterized?)光栅化,我说的光栅化实际上是画在屏幕上。
我们的窗口实际上是由像素组成的,他就像一个像素数组,需要发生什么,就是我们指定的,组成我们的三角形的三个顶点现在需要去充满我们的像素,这就是光栅化阶段所作的。片段着色器或者是像素着色器会为每一个像素调用一次,在我们的三角形里面这需要用来去填充,并且我们的片段着色器或者是像素着色器的基本目的就是决定像素的颜色是什么。它就只是为我们的像素决定颜色和输出的颜色,因此像素可以获得正确的颜色进行着色。可以把它当成一本彩色书,里面有一些颜色的轮廓,但你需要给它涂上正确的颜色,这就是片段着色器负责的。
顶点着色器和片段着色器的区别
现在你可以注意到这两者之间有一点不一样,顶点着色器调用三次,片段着色器调用成百上千次,这取决于我们的三角形在我们的屏幕上占用了多大空间,如果你有一个细小的三角形,在你的窗口上一个真的很小的三角形,那可能会调用额外的50次。如果你有一个巨大的三角形,这充满了你的屏幕,这可能是有一百万像素或者50万像素,那就意味着片段着色器要调用50万次。
我希望你们从一开始就意识到这一点,如果我做5乘以5的事情,等于是我在顶点着色器里面计算5乘5,然后值是25,这个计算过程会发生三次。因为在渲染这个三角形的过程中,顶点着色器会调用3次。如果我们有一个大的三角形,我们在片段着色器里面做5乘5的计算,然后片段着色器会500000次,突然我们做500000次乘法而不是3次,这就是巨大的不同点。
当涉及优化
当涉及到优化的时候,想想性能,这其实是一致存在的,你会开始注意到我应该在顶点着色器而不是片段着色器中执行一些关键操作,并且你也可以将数据从顶点着色器传递到片段着色器。这里只是想提一下,只是为了让你们记住片段着色器里面的东西往往会花销更多,因为片段着色器是要给每一个像素运行一次的,也就是说很明显这要给每个像素进行计算。
所以我们喜欢提起以一个很好的例子,这个明确地是带有片段着色器的例子,就是光照。如果你计算光照,每一个像素都会有一个颜色值,这取决a number of things,举个例子,光照、环境、纹理以及提供给表面的材料,所有的这些东西组合在一起,决定了一个明确的像素的正确颜色。很明显,这将取决于a number of input,像是相机的位置在哪里和我说得所有的表面属性、环境属性、所有这些东西组合一起,所有你在片段着色器中决定的,只是一个像素的颜色,这就是片段着色器做的。它是一个程序,它运行起来去决定着各像素应该是什么颜色,一旦片段说则其计算了,基本上就会将你的颜色显示到屏幕上。
小零碎
很多游戏引擎,当然还有各种大型游戏引擎,都会根据游戏中发生的事情,还有根据你选择的实践设置,动态的生成着色器,所以在游戏引擎中,实时生成着色器和复杂化是非常常见的,所以总得来说,你可以用着色器做一大堆很酷的事情。
像opengl中的其它东西一样,着色器工作是基于机器的状态,这意味着当你想要实现一个着色器去画一个三角形,你想它用某个着色器去画三角形,你实现了那个着色器,你可能也会发送一个数据到那个服务器,就好像我们在一个顶点缓冲前从cpu给GPU发送顶点数据,我们也可以用一些叫uniform的东西调发送给我们的着色器,这也可以是来自CPU,所以我们设置了所有状态去实现着色器然后我们画出了一个三角形。