Writing HLSL Shaders
Last updated
Last updated
处理顶点着色器的工作就像是把 3D 场景中的物体搬到屏幕(具体来说,clip space,裁剪空间)上来。头脑中应当有一个 Object 的网格,而着手处理的目标是网格的最小单元 —— 顶点。
顶点的信息里包含了顶点位置(局部空间)、法线方向、UV 坐标(是的 UV 坐标是绑定在 3D 物体顶点上的),有的顶点还包含了颜色数据。
顶点着色器是一个正确性工具。操作它以使得物体正确地显示在屏幕上。
Vertex Program 输出要求
顶点着色器至少需要返回齐次坐标下的裁剪空间坐标。
对于和视觉效果有关(需要决策 “长什么样”,包括颜色、光照、法线等)的输出,都要保证 clamp 在 0~1 范围内,以确保在后续流程中视觉效果正确。
片元着色器的主要任务是决定每个像素的最终颜色。这个阶段将专注于每个像素的外观,包括它本身的样式(颜色、纹理、透明度等),以及它如何与光线进行交互。
与顶点着色器不同的是,片元着色器是一个风格化工具。片元着色器的处理使得物体的 “皮肤” 确立。
Semantic(语义) 是 HLSL 语言与 pipeline 中各种寄存器的数据直接对接的机制。“These semantics signifiers communicate the “meaning” of these variables to the GPU.”
该特性也是 shader 的撰写与很多通用编程语言不同之处。
比如 C++ 这种通用的语言,CPU 其实根本不需要知道 用户在干啥。照做就完事了。但是 GPU 需要知道某些数据的意义,因为它的渲染过程有特定的流程和固定的输入输出规则,面向 GPU 编程是个很特定的程序,GPU 是需要知道这些都是干啥的
shader 的使用其实有点像 C++ 中的引用。只是它的使用是和对应的寄存器所绑定。当它被声明时,是从该寄存器中读取数据,而当绑定的变量 / 函数被修改,寄存器中的内容也会被随之修改。
对于不同阶段的 shader,HLSL 会提供不同的 Semantic。比如对于 vertex shader 提供 POSITION(物体空间的顶点坐标),而其在 pixel shader 中并不支持。
函数的输入输出可以写为 structure 的形式。函数绑定的 Semantic 可以在结构体成员后标注。
inout
和 out
关键字适用于函数需要返回多个值时。它们不需要被 return,函数执行结束后会被自动输出。
inout
关键字的行为会比较类似于 C++ 中函数参数的行为,即它可以被读取其中保存的初始值,又可以对改值进行改写:
而 out
关键字并不读取初始值,而是需要在函数内进行赋值:
Uniform 是一种,在整个渲染过程,或至少一个 draw call 期间,其值对于着色器都是不变的量。Uniform 与常量的概念不同在于,Uniform 在着色器内部,其值不可被修改,但是可以在引擎中进行修改。
Properties 传递给 Unity 材质系统,展示在 Inspector 的材质面板。
("Properties" 是特指材质 properties)
文档:https://docs.unity3d.com/Manual/SL-Properties.html
TEXCOORD 实际上是一种通用寄存器。
尽管往往被狭义的理解为用作传递纹理信息,但由于 “纹理” 的本质就是二维信息向复杂三维模型的映射,因此各种信息都可以 “当作纹理”,如光线信息、坐标信息等,从而实现在 vertex shader 与 fragment shader 之间的插值与传递。
寄存器可以保存 4 个 float,每个寄存器对应 4 个分量(xyzw)。因为一个 UV 坐标只需要 x
和 y
两个分量,所以如果同时有 TEXCOORD0
和 TEXCOORD1
,编译器可能会优化:将 TEXCOORD0
的 x
和 y
分量存储在一个寄存器的 xy
位,而将 TEXCOORD1
的 x
和 y
分量存储在同一个寄存器的 zw
位。
这种方式有效地利用了寄存器资源,提高了性能。
路径 Edit - Project Settings - Player - Other Settings - Color Space 可以查看当前使用的色彩空间。这里我们将其设置为线性色彩空间。
此时正在使用的是线性空间,需要进行向 gamma 空间的色彩空间修正。
因为此前在项目中设置的为线性色彩空间,指的是计算过程中使用线性色彩空间进行计算。 而 Unity 依然需要输出 Gamma 空间的颜色。
Gamma 空间的色彩储存,为了适应人眼特性(对暗部感知更强),因此用了更多的数据存储暗部色彩,所以数值上会更大(比如 0 ~ 1,原本 0 为全暗,0.5 为中灰,但是如果变大的话,可能 0.73 才是中灰,此时暗部数据区域从 0 - 0.5 拓展到了 0 - 0.73,数值更大了)。
而计算中使用了线性空间,数值就偏小。
所以在计算最后向 gamma 空间转换时,就是会变大的。
转换公式具体来说:
从 Gamma 空间转换到线性空间(数值变小的过程):
从线性空间转换到 Gamma 空间(数值变大的过程):
其中 x、y 分别为转换前后的空间, (gamma)是个固定数值,约为 2.2, 约为 0.45。
Unity 提供了 unity_ColorSpaceDouble
变量,这个变量会自动根据当前的颜色值与颜色空间选择正确的修正因子。于是,将颜色结果乘以该变量(以替代之前简单 × 2),从而确保最终亮度表现正常。