What are shaders?

简介

你已经决定尝试编写着色器了。你可能已经听说过,它们可用来快速创造非常有趣的效果。可能也听说过它们很可怕。以上两种说法都对。

着色器可以用来创建广泛的效果(事实上,现代渲染引擎中绘制的所有东西都是用着色器绘制的)。

对于不熟悉着色器的人来说,编写着色器也是非常困难的。Godot试图通过公开许多有用的内置特性,并为您处理一些较低级别的初始化工作,使编写着色器变得更容易一些。然而,GLSL (OpenGL着色语言,也是Godot使用的)仍然是不直观和受限制的,特别是对于习惯使用GDScript的用户。

可是它是什么?

着色器是一种在图形处理单元(GPU)上运行的特殊程序。大多数电脑都有GPU,一个集成到他们的CPU或离散的(这意味着它是一个单独的硬件组件,例如,标准显卡)。GPU对于呈现特别有用,因为它们被优化为并行运行数千条指令。

着色器的输出通常是绘制到视图端口的对象的彩色像素。但是一些着色器允许特殊的输出(对于像Vulkan这样的应用程序接口尤其如此)。着色器在着色器管道中操作。标准流程是顶点->片段着色器管道。顶点着色器用于决定每个顶点的位置(3D模型中的点,或者精灵的一角)去和片段着色器决定什么颜色个别像素接收。

假设你想要将纹理中的所有像素更新为给定的颜色,你可以在CPU上这样写:

for x in range(width):
  for y in range(height):
    set_color(x, y, some_color)

在着色器中,你只能访问循环的内部,所以你写的是这样的:

// function called for each pixel
void fragment() {
  COLOR = some_color;
}

您无法控制如何调用此函数。您必须设计不同于在中央处理器上设计程序的着色器。

着色器管道的一个后果是,您无法访问着色器上次运行的结果,您无法从正在绘制的像素中访问其他像素,而且不能在当前绘制的像素之外编写。这使得GPU能够并行地为不同的像素执行着色器,因为他们彼此不耦合。这种灵活性的缺乏是为GPU的工作设计,使着色器难以置信的快。

它能做什么

  • 快速定位顶点
  • 快速计算颜色
  • 快速计算光照
  • 进行大量的数学计算

它不能做什么

  • 绘制外部网格
  • 从当前像素(或顶点)访问其他像素
  • 储存之前的迭代
  • 动态更新(可以,但是需要编译)

着色器的结构

在Godot中,着色器由3个主要函数组成:“vertex()”函数,“fragment()”和“light()”函数。

“vertex()”函数运行在网格中的所有顶点上,并设置它们的位置以及部分其他每个顶点的变量。

函数的作用是:为网格所覆盖的每个像素运行“fragment()”函数。它使用“vertex()”函数中的变量来运行。“vertex()”函数中的变量在顶点之间进行插值,以提供“fragment()”函数的值。

“light()”函数用于每个像素和每束光照。它从“fragment()”函数和以前的运行中获取变量。

有关着色器如果在Godot中工作的更多信息,请参阅 着色器

技术概述

GPU渲染图形的速度要比CPU快得多,原因如下:但最值得注意的是,它们能够大规模并行运行计算。CPU通常有4或8个内核,而GPU通常有数千个。这意味着GPU可以一次完成数百项任务。GPU架构师已经利用了这种方式,允许快速地进行许多计算,但只有当多个或所有核心同时进行相同的计算时,但数据不同。

这就是着色器的作用。GPU会同时调用着色器很多次,然后对不同的数据位(顶点或像素)进行操作。这些数据束通常被称为波前。一个着色器将在波前运行相同的每一个线程。例如,如果给定的GPU可以处理每个波前100个线程,波前将一起运行在一个10x10像素块上。它将继续运行在波前的所有像素,直到它们完成。因此,如果你有一个像素比其他像素慢(由于过度分支),整个区域都会减速,导致渲染时间大大降低。这与基于CPU的操作不同,在CPU上,如果你能加快哪怕一个像素,整个渲染时间都会减少。在GPU上,你必须加速整个波前来加速渲染。