LabHub
学习 学习路径 课程

着色器与 GPU 管线

着色器被调用两次

在 LabHub 中继续学习

一句话总结

顶点着色器每次调用一次,输出每个顶点的剪贴图坐标和要调和的值,片段着色器每次调用一次每个像素,输出颜色。在这期间,网格化器将值混合到重心坐标中。

概念图: 不是向任何地方延伸 · 顶点着色器 · 创建或删除顶点 · 网格化

为什么需要这个?

旧的图形硬件是固定的功能。照明模型和纹理合成方式都嵌入在硬件中,开发者只能转动固定的手柄。要做出新的表现,必须等待硬件发生变化。

遮罩是让这两个点成为可编程的。开发者编写的代码决定了将顶点送往哪里,将像素涂成什么颜色。重要的是不是向任何地方延伸。保留了管道本身的结构,只打开了顶点阶段和片段阶段。这样才能让其余部分(网格化、深度判断、混合)由硬件继续并行处理。

怎么行动

顶点着色器接受一个顶点,输出一个顶点。输入有两种。

attribute  정점마다 다른 값 — 위치, 법선, UV, 정점 색
uniform    드로우 콜 전체에서 같은 값 — MVP 행렬, 광원 방향, 시간

输出必须是剪贴板坐标(gl_Position必须包含),除此之外,将转为片段阶段的值varying(最近的名字是out)发送。顶点着色器不能创建或删除顶点。一个进去一个出来。这样才能提前知道硬件能处理几个,并并行分开处理。

网格化是无法编程的固定步骤。接收三角形,选择其中的像素,从每个像素中将三个顶点的varying混合为重心坐标,然后传递给片段着色器。在前程中直接制作的就是这个步骤。

片段着色器接受一个像素,输出一个颜色。输入是平滑的varying和uniform,还有纹理。这里可以做一件特别的事情,那就是丢弃(discard)。如果宣布完全不使用这个片段,深度缓冲区和颜色缓冲区什么都没有了。用于表示像树叶或铁丝网一样有孔的表面。

需要注意的一点是片段和像素不同。片段是“某个三角形在这个像素位置产生的候选”,同一像素上可以产生多个片段。其中哪个会成为最终像素取决于深度判断和混合。

阶段不仅仅有这两个。在顶点和片段之间可以插入几何着色器网格化,这些可以增加或减少顶点。但因为数量在运行中被设定,所以硬件无法提前分配,所以速度要慢得多。所以在实际工作中,只在必须的地方写。管道外面也有计算着色器,它是一个在GPU上执行任意计算的通道,与网格化无关。

还有一点需要知道的是传递制服和属性所花费的费用。制服每次绘制呼叫只需要设置一次,但绘制呼叫本身很贵,所以用同样的着色器绘制十个物体时,每次都要更换制服并绘制十次,这是不好的设计。实例化是将每个物体传递不同的值作为属性处理一次绘制呼叫的方法,这就是绘制数万棵树木或草地场景的方式。

在现场相遇的样子

从性能来看,之所以单独计数顶点数和片段数,就在这里。顶点着色器被称为顶点数,片段着色器被称为覆盖屏幕的面积。填满屏幕的一个三角形有数十万个片段,对应着三个顶点。因此,如果可能的话,将繁重的计算转移到顶点着色器上,交给调值。但是,调值是线性的,因此需要归一化的值(如法线之类)需要在片段方面重新归一化。

另一个是丢弃不是免费的。有discard的着色器无法使用提前判断深度的优化。因为必须计算颜色才能知道这个片段是否能生存。所以有洞的表面比完全不透明的要贵。

而且着色器之间的合同由名称和类型组成。顶点着色器out vec2 uv如果发送到,片段着色器将in vec2 uv接受。如果这个名称不一致,连接就会中断,虽然编译通过,但只有值为0的情况,所以很难找到。最近GLSL无法用数字固定位置的方法(layout(location = 0)提供)来减少这个问题。

另一个需要知道的是分区的费用。GPU将数十个片段打包成一个,一起执行相同的命令。在打包中if如果结果分开,就会以执行两个分支并丢弃不需要的结果的方式处理,即使有条件语句也不会变快,反而会增加两个分支的成本。如果整个捆绑都朝着同一个方向走,那么到时候就会实际跳转。所以最好设计阴影器的条件语句,让它们在画面上广泛聚集并分开。

下次实习要做的事情

将顶点着色器和片段着色器用Python函数写出来,中间放上网格化器,画一个三角形。只改变uniform,确认以同样的几何画出不同的图片,然后用discard打孔。最后,计算顶点数、三角形数、片段数,用数字看看哪个阶段被调用了几次。