什么是着色器?

简介

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

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

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

可是它是什么?

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

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

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

  1. for x in range(width):
  2. for y in range(height):
  3. set_color(x, y, some_color)

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

  1. // function called for each pixel
  2. void fragment() {
  3. COLOR = some_color;
  4. }

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

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

它能做什么

  • 快速定位顶点

  • 快速计算颜色

  • 快速计算光照

  • 进行大量的数学计算

它不能做什么

  • 绘制外部网格

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

  • 储存之前的迭代

  • 动态更新(可以,但是需要编译)

着色器的结构

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

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

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

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

更多关于着色器在Godot中具体操作的信息,请参见 Shaders 文档.

警告

如果启用了``vertex_lighting``渲染模式,或者在项目设置中启用了**Rendering渲染>Quality质量>Shading着色>强制顶点着色**,则不会运行``light()``函数.(在移动平台上默认启用.)

技术概述

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

这就是着色器的作用.GPU会同时调用着色器,然后对不同的数据位(顶点或像素)进行操作.这些成束的数据通常被称为wavefront.着色器将对wavefront中的每一个线程运行相同的内容.例如,如果一个给定的GPU可以处理每个wavefront的100个线程,一个wavefront将一起运行在10×10的像素块上.它将继续为该wavefront的所有像素运行,直到它们完成.相应地,如果你有一个像素比其他像素慢(由于过多的分支),整个块将被减慢,导致渲染时间大量减慢.

这与基于CPU的操作不同.在CPU上,如果您可以将速度提高哪怕只有一个像素,整个渲染时间就会减少.在GPU上,您必须加快整个wavefront的速度才能加快渲染速度.