什么是明暗器?

介绍

所以,你决定尝试一下。你可能听说它们可以用来创造有趣的效果,运行速度非常快。你也可能听说他们很恐怖。两者都是真的。

明暗器可用于创建各种效果(实际上,现代渲染引擎中绘制的所有内容都是使用明暗器完成的)。

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

但他们是什么?

明暗器是一种特殊的程序,运行在图形处理单元(GPU)上。大多数计算机都有某种GPU,要么集成到CPU中,要么是离散的(这意味着它是一个单独的硬件组件,例如典型的图形卡)。GPU对于渲染特别有用,因为它们针对并行运行数千条指令进行了优化。

明暗器的输出通常是绘制到视区的对象的彩色像素。但有些着色器允许专门的输出(这对于像vulkan这样的API尤其适用)。明暗器在明暗器管道内操作。标准过程是顶点->片段着色管道。顶点明暗器用于确定每个顶点(三维模型中的点或精灵的角)的位置,片段明暗器决定单个像素接收的颜色。

假设您要将纹理中的所有像素更新为给定的颜色,那么在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;
}

您无法控制如何调用此函数。因此,您必须以不同于在CPU上设计程序的方式来设计您的明暗器。

明暗器管道的一个结果是,无法访问明暗器上一次运行的结果,无法访问正在绘制的像素中的其他像素,并且无法在当前正在绘制的像素之外写入。这使GPU能够并行地对不同像素执行着色,因为它们彼此不依赖。这种缺乏灵活性的设计是为了与GPU一起使用,它允许着色速度极快。

他们能做什么

  • 快速定位顶点

  • 快速计算颜色

  • 计算照明非常快

  • 数不清

他们不能做什么

  • 绘制外部网格

  • 从当前像素(或顶点)访问其他像素

  • 存储以前的迭代

  • 即时更新(可以,但需要编译)

明暗器的结构

在godot中,着色器由3个主要功能组成: vertex() 函数 fragment() 函数和 light() 功能。

这个 vertex() 函数在网格中的所有顶点上运行,并设置它们的位置以及其他一些逐顶点变量。

这个 fragment() 对网格覆盖的每个像素运行函数。它使用来自 vertex() 要运行的函数。变量来自 vertex() 在顶点之间插入函数,以提供 fragment() 功能。

这个 light() 功能适用于每个像素和每个灯光。它从 fragment() 函数和以前运行的函数本身。

有关着色器如何在Godot中具体操作的详细信息,请参见 Shaders 博士。

技术概述

由于一些原因,GPU能够比CPU更快地呈现图形,但最显著的是,它们能够并行运行大量计算。一个CPU通常有4或8个内核,而一个GPU通常有数千个。这意味着一个GPU可以同时完成数百个任务。GPU架构师以一种允许快速进行许多计算的方式来利用这一点,但只有当许多或所有核心同时进行相同的计算,但使用不同的数据时。

这就是阴户进来的地方。GPU将同时调用该明暗器很多次,然后对不同的数据位(顶点或像素)进行操作。这些数据束通常被称为波前。对于波阵面中的每个线程,一个明暗器将运行相同的。例如,如果一个给定的GPU可以处理每个波前100个线程,那么一个波前将一起在一个10x10像素块上运行。它将继续对波前的所有像素运行,直到它们完成。因此,如果有一个像素比其他像素慢(由于过度分支),则整个块的速度将减慢,从而导致渲染时间大大减慢。这与基于CPU的操作不同,在CPU上,如果可以加快甚至一个像素的速度,整个渲染时间将减少。在GPU上,您必须加速整个波前以加速渲染。