19
19
` glslc ` 的优点是它使用与 GCC 和 Clang 等知名编译器相同的参数格式,并包含一些额外的功能,例如 includes 。
20
20
它们都已包含在 Vulkan SDK 中,因此您无需下载任何额外的程序。
21
21
22
- > Vulkan SDK 还提供了直接在 C++ 程序中写 GLSL 代码的工具库,我们会在进阶章节介绍。
23
-
24
22
### 2. 编码语言介绍
25
23
26
24
GLSL 是一种具有 C 风格语法的着色语言。
27
25
28
26
此语言也提供了提供了 int/float/bool 等基础类型,还有我们熟悉的 if/for/while 以及结构体和(支持重载的)函数。
29
-
30
- 值得注意的是,它还提供了内置了 vec 向量和 mat 矩阵以及各种数学计算函数。
27
+ 重要的是,它还提供了 vec 向量和 mat 矩阵类型以及各种数学计算函数。
31
28
32
29
它编写的程序包含一个主入口函数\( 可以不是` main ` \) ,该函数会针对每个对象进行调用。
33
30
比如顶点着色器会对每个顶点调用一遍主函数,片段着色器则是对每个片段(像素)调用一遍主函数。
@@ -77,7 +74,7 @@ GLSL 是一种具有 C 风格语法的着色语言。
77
74
然后,这些值将由光栅化器在片段上进行插值,以产生平滑的渐变。
78
75
79
76
** 裁剪坐标** 是来自顶点着色器的四维向量,随后通过将整个向量除以其最后一个分量进行归一化。
80
- 这些归一化设备坐标是 [ 齐次坐标] ( https://en.wikipedia.org/wiki/Homogeneous_coordinates ) ,
77
+ 这些归一化设备坐标是 ** [ 齐次坐标] ( https://en.wikipedia.org/wiki/Homogeneous_coordinates ) ** ,
81
78
它们将帧缓冲映射到 [ -1, 1] \* [ -1, 1] 坐标系,如下所示
82
79
83
80
![ device_coordinates] ( ../../images/0121/normalized_device_coordinates.png )
@@ -108,20 +105,20 @@ void main() {
108
105
}
109
106
```
110
107
111
- ` main ` 函数为每个顶点调用 。
108
+ ` main ` 函数会为每个顶点调用 。
112
109
113
110
内置的 ` gl_VertexIndex ` 变量包含当前顶点的索引。
114
- 这通常是顶点缓冲的索引,但在我们的例子中,它将是硬编码顶点数据数组的索引。
115
-
111
+ 这通常是顶点缓冲的索引,但在我们的例子中,它将是硬编码顶点数组的索引。
116
112
每个顶点的位置从着色器中的常量数组访问,并与虚拟的 ` z ` 和 ` w ` 分量组合以生成裁剪坐标中的位置。
117
113
118
114
内置变量 ` gl_Position ` 用作输出。
119
115
120
116
121
117
### 2. 片段着色器
122
118
123
- 由顶点着色器中的位置形成的三角形在屏幕上填充一个区域,其中包含片段。
124
- 片段着色器在这些片段上调用,以生成帧缓冲(或多个帧缓冲)的颜色和深度。
119
+ 顶点着色器计算好了每个顶点的位置,它们会组成一个个三角形。
120
+ 光栅化阶段会计算出这些三角形投影到图像上的区域,区域包含片段\( 像素\) ,片段着色器将对每个片段调用以确定色彩信息。
121
+
125
122
一个简单的片段着色器,为整个三角形输出红色,如下所示:
126
123
127
124
``` glsl
@@ -139,12 +136,11 @@ void main() {
139
136
GLSL 中的颜色是 4 分量向量,R、G、B 和 alpha 通道,都在 ` [0, 1] ` 范围内。
140
137
141
138
与顶点着色器中的 ` gl_Position ` 不同,没有内置变量来输出当前片段的颜色。
142
- 您必须为每个帧缓冲指定自己的输出变量,其中 ` layout(location = 0) ` 修饰符指定帧缓冲的索引。
143
- 红色被写入此 ` outColor ` 变量,该变量链接到索引 ` 0 ` 处的第一个(也是唯一的)帧缓冲。
139
+ 您必须为每个帧缓冲指定自己的输出变量,其中 ` layout(location = 0) ` 修饰符指定帧缓冲的索引,它链接到了索引为 ` 0 ` 的帧缓冲(将会在后续的“帧缓冲”章节创建)。
144
140
145
- > 注意,in和out的变量名是不重要的,重要的是变量类型和location的值 。
146
- > 只要保证类型和location都一致,就能一个地方out,另一个地方in 。
147
- > 一个location不能同时放置多个数据 。
141
+ > 注意,变量名是不重要的,重要的是变量类型和 location 的值 。
142
+ > 只要保证类型和 location 都一致,就能一个地方 out ,另一个地方 in 。
143
+ > 一个 location 只能放置一种数据 。
148
144
149
145
### 3. 逐顶点颜色
150
146
@@ -186,7 +182,8 @@ void main() {
186
182
}
187
183
```
188
184
189
- ` main ` 函数已修改为输出颜色以及 ` alpha ` 值。图形管线将使用三个顶点的数据,自动插值生成内部片段的` fragColor ` ,从而产生平滑的渐变。
185
+ ` main ` 函数已修改为输出颜色以及 ` alpha ` 值。
186
+ 图形管线将使用三个顶点的数据,自动插值生成内部片段的 ` fragColor ` ,从而产生平滑的渐变。
190
187
191
188
### 4. 编译着色器
192
189
@@ -244,18 +241,19 @@ xxx/VulkanSDK/x.x.x.x/Bin/glslc.exe shader.frag -o frag.spv
244
241
245
242
如果您的着色器包含语法错误,那么编译器会告诉您行号和问题。
246
243
可以尝试省略分号并再次运行编译脚本。
247
-
248
244
还可以尝试在没有任何参数的情况下运行编译器,以查看它支持哪些类型的标志。
249
245
250
246
它还可以将字节码输出为人类可读的格式,以便您可以准确地了解您的着色器正在做什么以及在此阶段已应用的任何优化。
251
247
252
248
在命令行上编译着色器是最直接的选择之一,也是我们将在本教程中使用的选择,但也可以直接从您自己的代码中编译着色器。
253
- Vulkan SDK 包含 libshaderc,这是一个从您的程序中将 GLSL 代码编译为 SPIR-V 的库。
249
+ Vulkan SDK 包含 libshaderc,这是一个从您的程序中将 GLSL 代码编译为 SPIR-V 的库,我们会在进阶章节介绍 。
254
250
255
251
### 5. CMake编译着色器
256
252
257
253
直接使用命令行显然不够优秀,且写明路径导致无法跨平台,所以我们借助CMake执行命令。
258
254
255
+ > 注意此部分是可选的,你完全可以在每次修改后手动编译。
256
+
259
257
现在让我们在 ` shaders/ ` 文件夹中创建新的 ` CMakeLists.txt ` ,内容如下所示:
260
258
261
259
``` cmake
@@ -300,10 +298,12 @@ add_custom_target(CompileShaders ALL
300
298
add_subdirectory(shaders)
301
299
```
302
300
303
- 现在配置与构建项目,shaders下应该生成了 ` frag.spv ` 和 ` vert.spv ` 两个文件。
301
+ 现在配置与构建项目,` shaders/ ` 下应该生成了 ` frag.spv ` 和 ` vert.spv ` 两个文件。
304
302
305
303
## ** 加载着色器**
306
304
305
+ ### 1. 读取文件
306
+
307
307
现在我们有了一种生成 SPIR-V 着色器的方法,是时候将它们加载到我们的程序中,以便在某个时候将它们插入到图形管线中。
308
308
我们首先编写一个简单的辅助函数,从文件中加载二进制数据。
309
309
@@ -333,22 +333,22 @@ size_t fileSize = (size_t) file.tellg();
333
333
std::vector<char> buffer(fileSize);
334
334
```
335
335
336
- 之后,可以seek回到文件开头并一次读取所有字节 。
336
+ 之后,可以 ` seekg ` 回到文件开头并一次读取所有字节 。
337
337
338
338
``` cpp
339
339
file.seekg(0 );
340
340
file.read(buffer.data(), fileSize);
341
341
```
342
342
343
- 最后关闭文件并返回字节
343
+ 最后关闭文件并返回字节:
344
344
345
345
``` cpp
346
- file.close();
346
+ file.close(); // optional
347
347
348
348
return buffer;
349
349
```
350
350
351
- 我们现在将从 ` createGraphicsPipeline ` 调用此函数 ,以加载两个着色器的字节码
351
+ 在 ` createGraphicsPipeline ` 函数中使用它 ,以加载两个着色器的字节码:
352
352
353
353
``` cpp
354
354
void createGraphicsPipeline () {
@@ -364,11 +364,10 @@ void createGraphicsPipeline() {
364
364
> 注意我们使用了相对路径,这要求你运行可执行程序时,当前路径必须位于项目根目录。
365
365
> 或者你可以将着色器文件夹复制一份到你的执行目录。
366
366
367
- ## ** 创建着色器模块**
368
-
369
- ### 1. 读取着色器代码
367
+ ### 2. 创建着色器模块
370
368
371
- 在可以将代码传递给管线之前,我们必须将其包装在 ` vk::ShaderModule ` 对象中。让我们创建一个辅助函数 ` createShaderModule ` 来执行此操作。
369
+ 在将着色器代码传递给管线之前,必须将其包装在 ` vk::ShaderModule ` 对象中。
370
+ 让我们创建一个辅助函数 ` createShaderModule ` 来执行此操作:
372
371
373
372
``` cpp
374
373
vk::raii::ShaderModule createShaderModule (const std::vector<char >& code) {
@@ -381,7 +380,7 @@ vk::raii::ShaderModule createShaderModule(const std::vector<char>& code) {
381
380
创建着色器模块很简单,我们只需要指定字节码缓冲区的开始指针和缓冲区长度。
382
381
此信息在 `vk::ShaderModuleCreateInfo` 结构中指定。
383
382
384
- 需要注意的一点是,字节码的大小以字节为单位指定,但字节码指针是 uint32_t 指针,而不是 char 指针。
383
+ 需要注意的一点是,字节码的大小以字节为单位指定,但字节码指针是 ` uint32_t` 指针,而不是 ` char` 指针。
385
384
因此,我们需要使用 `reinterpret_cast` 强制转换指针,如下所示:
386
385
387
386
```cpp
@@ -393,16 +392,14 @@ createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
393
392
当您执行这样的强制转换时,还需要确保数据满足 ` uint32_t ` 的对齐要求。
394
393
幸运的是,数据存储在 ` std::vector ` 中,其中默认分配器已经确保数据满足最坏情况的对齐要求。
395
394
396
- > 你无法使用` setCode() ` ,它接受 ` uint32_t ` 类型的代理数组。
395
+ > 你无法使用 ` setCode() ` ,它只接受 ` uint32_t ` 类型的代理数组。
397
396
398
397
然后我们创建 ` vk::raii::ShaderModule ` 并返回即可。
399
398
400
399
``` cpp
401
400
return m_device.createShaderModule(createInfo);
402
401
```
403
402
404
- > ` vk::raii ` 内的大部分变量不可复制,只可移动。
405
-
406
403
着色器模块只是我们着色器字节码的薄包装,
407
404
从 SPIR-V 字节码到 GPU 机器码的编译链接过程在图形管线创建才发生,
408
405
这意味着我们可以在管线创建完成后立即销毁着色器模块,
@@ -418,7 +415,7 @@ void createGraphicsPipeline() {
418
415
}
419
416
```
420
417
421
- ### 2 . 创建着色器阶段
418
+ ### 3 . 创建着色器阶段
422
419
423
420
要实际使用着色器,我们需要通过 ` vk::PipelineShaderStageCreateInfo ` 结构将它们分配给特定的管线阶段,作为实际管线创建过程的一部分。
424
421
@@ -461,8 +458,6 @@ std::vector<vk::PipelineShaderStageCreateInfo> shaderStages{ vertShaderStageInfo
461
458
462
459
这就是描述管线的可编程阶段的全部内容,你可以尝试运行程序,不应报错。
463
460
464
- ---
465
-
466
461
本章我们学习了两个可编程阶段。在下一章中,我们将研究图形管线的固定功能阶段。
467
462
468
463
---
0 commit comments