GPU流水线

本篇大部分内容引用自《UnityShader入门精要》基础部分

时隔多年后,发现知识需要形成自己的输出才可以很好的防止自己遗忘(在快要忘记的时候对自己进行一次知识的输入)。虽然这次是捧起这本书算是“二进宫”,但是,在阅读一些了OpenGL,Vulkan相关的资料后再次捧起这本书,对于作者输出的一些知识观点的理解也更透彻了,这也许就是孔子口中的学而时习之,不亦说乎?

image-20220613220841381

CPU和GPU之间的通信

渲染流水线的起点是CPU,即应用阶段。应用阶段大致可分为下面3 个阶段:

  1. 把数据加载到显存中。
  2. 设置渲染状态。
  3. 调用Draw Call。

把数据加载到显存中

一般的加载步骤都是从硬盘加载到内存,然后从内存加载到显存(显卡对于显存的访问速度更快)。之所以说是一般的,是因为大多数显
卡对于RAM 没有直接的访问权利。

在Vulkan中,专门划分出来了CPU管理的对GPU可见的内存(Host Local Device Memory)和GPU管理的对CPU可见的内存(Device Local Host Memory)用于提升访问速率的(这里是个人理解,这样做视乎并不是简单的空间换时间,感觉还有可能是为了适配大部分硬件的兼容而做的一个低耦合的设计或者说是提供选择让用户可以更好的根据自己的需求做出对应的优化策略)。

设置渲染状态

这一步的工作是进行一些需要的设置,用于告知GPU流水线该如何解析这些顶点数据。

调用Draw Call

通用的来理解,这一步是将我们上一步的设置放入命令缓冲区(Command Buffer)中,并用命令(Draw Call)通知GPU(其实Draw Call只是众多命令中的一个)。

image-20220613005300378

GPU流水线

对应几何阶段和光栅化阶段。所有的顶点数据在CPU阶段就加载到显存中了,流水线通过命令缓冲区的命令进行GPU渲染工作。

image-20220613221049320

顶点着色器

定点着色器的处理单元是顶点,意味着每个顶点都会调用一次顶点着色器。

这一步会把顶点坐标从模型空间转换到齐次裁剪空间,最终得到归一化的设备坐标。

image-20220613223338822

裁剪

image-20220613223313419

屏幕映射(Screen Mapping)

image-20220613223255268

这一步输入的坐标仍然是三维坐标系下的坐标(范围在单位立方体内)。屏幕映射的任务是把每个图元的 x 和 y 坐标转换到屏幕坐标系(Screen Coordinates)下。屏幕 坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系。

三角形设置(Triangle Setup)

上一个阶段输出的都是三角网格的顶点,即我们得到的是三角网格每条边的两个端点。但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程就叫做三角形设置。它的输出是为了给下一个阶段做准备。

三角形遍历(Triangle Traversal)

image-20220613223221817

片元着色器

这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。

为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的 3 个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。

image-20220613223426822

逐片元操作

image-20220613223624660

以上是整个渲染流水线的大概过程,这里我只贴出了图解(因为只是起一个笔记的作用,如果需要深一步了解,还是需反复要看第二章的内容,虽然我看过了很多其他资料,但是现在回来看第二章也会发现其实当时看的时候还是有很多地方理解不是很到位,这一次看第二章给人的感觉就非常愉悦,所有的知识点都有一种似曾相识的感觉)。

ShaderLab基础结构解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
仅是着色器代码。
一个 Unity Shader 的基础结构如下所示:
Shader "ShaderName" {
Properties {
// 属性
_Int ("Int", Int) = 2
_Float ("Float", Float) = 1.5
_Range("Range", Range(0.0, 5.0)) = 3.0
// Colors and Vectors
_Color ("Color", Color) = (1,1,1,1)
_Vector ("Vector", Vector) = (2, 3, 6, 1)
// Textures
_2D ("2D", 2D) = "" {}
_Cube ("Cube", Cube) = "white" {}
_3D ("3D", 3D) = "black" {}

}
// 一个 Unity Shader 文件可以包含多个 SubShader 语义块,但最少要有一个。
SubShader {
// 真正意义上的 Shader 代码会出现在这里
// 表面着色器(Surface Shader)或者
// 顶点/片元着色器(Vertex/Fragment Shader)或者
// 固定函数着色器(Fixed Function Shader)
// 显卡 A 使用的子着色器
// 可选的标签设置
[Tags]
Tags { "TagName1" = "Value1" "TagName2" = "Value2" }
// 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染,我们也可以自定义使用的渲染队列来控制物体的渲染顺序
Tags { "Queue" = "Transparent" }
// 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。这可以被用于着色器替换(Shader Replacement)功能
Tags { "RenderType" = "Opaque" }
// 一些SubShader在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画。这时可以通过该标签来直接指明是否对该SubShader使用批处理
Tags { "DisableBatching" = "True" }
// 可选的状态设置
[RenderSetup]
Cull Back | Front | Off
ZTest Less Greater | LEqual | GEqual | Equal | NotEqual | Always
ZWrite On | Off
Blend SrcFactor DstFactor

Pass {
[Name]
Name "MyPassName"
// 由于 Unity 内部会把所有 Pass 的名称转换成大写字母的表示,因此,在使用 UsePass 命令时必须使用大写形式的名字
UsePass "MyShader/MYPASSNAME"
[Tags] 这里的Tags和SubShader的Tags是不一样的。
// 定义该 Pass 在 Unity 的渲染流水线中的角色
Tags { "LightMode" = "ForwardBase" }
[RenderSetup]
// Other code
}
// Other Passes
}
SubShader {
// 显卡 B 使用的子着色器
}
Fallback "VertexLit"
}

数学基础

这部分作者开源了的,可以直接白嫖,这里就不做笔记了。