【Unity】Shader学习笔记

自己总结的小规律

数据类型

在Shader中,共有4种数据类型:

  • 数学数据类型:floatintbool;向量:Vector2Vector3Vector4;矩阵:float2×2float3×3float4×4
  • UV坐标
  • Texture纹理:人物(物体)蒙皮、法线、Mask遮罩(黑白分明有规则的图案)、Noise噪声(黑白混乱的图案)
  • 像素点的世界坐标

尤其需要注意UV坐标和纹理这两类:

数学数据类型、世界坐标自不用说,在学习的过程中经常容易把UV坐标、蒙皮、Mask遮罩搞混。
尤其是UV坐标,UV坐标在ShaderGraph中是以图片展示其每个像素的数值,但其实UV坐标与Texture完全不一样。

  • UV坐标是用于映射2D纹理到3D模型的坐标,举例来说:UV坐标是中介,类似地图,2D纹理需要根据UV坐标找到在3D模型上自己应该对号入座的位置
  • 物体蒙皮:草地、金属棒球棒、墙面等纹理与形状关系不大的纹理可以随意复用;但是人体蒙皮的纹理与形状强关联,就不能随意复用
  • 法线:一般也是人体这种与形状关联性强的纹理需要使用,用来计算光照
  • Mask遮罩:黑白分明且有规则的图案,通常用来裁剪其他纹理,因为是黑白分明的图案,所以在计算的时候能发酵出很奇妙的结果
  • Noise噪声:黑白混乱的图案,通常与其他纹理组合,无规则的黑白图案能实现随机的效果

每个作用不一样,在计算的时候要各司其职,不要混淆

总之

  • UV坐标是一个正经的二维正方形,初始状态左下角为(0, 0),右上角为(1, 1)。其主要作用是告诉纹理该如何对号入座到3D模型上
  • 纹理存储着纹理数据,每个像素点上存储着RGBA

用类来区分UV坐标和纹理:

1
2
3
4
5
6
7
8
9
10
11
12
class UV
{
int u;
int v;
}

class Texture
{
int u;
int v;
Vector4 color; // `RGBA`颜色数据
}

显隐/透明

显隐

该状态为默认状态。只有两个选择要么显示,要么隐藏。

  • Alpha(float):与阈值比较,控制物体显隐
  • Alpha Clip Threshold(float):显影阈值,Alpha大于这个数就显示,Alpha小于这个数就隐藏

除了需要设置Alpha外,还需要设置阈值。阈值没有默认值,如果不设置阈值,就无法控制显隐。

透明

需要将Surface设置为Transparent

  • Alpha(float):范围为[0, 1],从0完全透明,到1完全显示。如果设置了阈值需单独与阈值比较,比较方式与显隐一样
  • Alpha Clip Threshold(float):显影阈值,Alpha大于这个数就显示,Alpha小于这个数就隐藏

只需要设置Alpha。如果设置了阈值,则还需与阈值相比较,控制显隐。


UV坐标

UV坐标是用于映射2D纹理到3D模型的坐标。在3D模型上的每个顶点都有一个对应的UV坐标,它告诉引擎在纹理上的哪个位置找到该顶点的颜色。

  • UV坐标的范围通常是从(0,0)到(1,1)。左下角是原点(0,0),右上角是(1,1)。

  • 在三角形上,UV坐标会在三个顶点之间插值,以确保纹理正确地贴在整个三角形表面上。


节点

运算本身并不难,难的是需要将运算与图形相结合,并且不要把关键性的几个概念搞混

========数学运算符========

——加乘减除——

Add(相加):叠加两个纹理

将两个Texture相加,通常是将一个纹理与遮罩(Mask)相加:

  • 白色RGB为1:同理,使颜色更亮

  • 黑色RGB为0:任何数加0等于本身,所以黑色部分显示内容不变

Multiply(乘法)

  • 数值乘法:如下图

  • 纹理乘法:与遮罩相乘可以裁剪图片;与颜色相乘可以叠加颜色

NOTE:单数值乘法可交换位置,但是矩阵乘法不可交换位置

Subtract(减法)

更改SurfaceTransparent可以设置透明度百分比,而不是单纯的显示/透明

这里只是一个扩展,继续我们的学习,将Surface改回Opaque

设置透明通道阈值Alpha Clip Threshold = 0,意味着当Alpha小于阈值使就会隐藏

如下图所示,物体的每个像素都会计算一遍这个减法,如果该像素的Y坐标减去1.75小于0,就会变透明

Divide(除法):设置锐利度

红框①的内容:

  1. 原本U输出的内容是:从左到右逐渐远离0到达1
  2. Float1 = 0.5f相减:
    • 左边:0 - 0.5 = 0.5,依然是黑色
    • 中间:0.5 - 0.5 = 0,变为黑色
    • 右边:1 - 0.5 = 0.5,变为灰色

绿框②的内容:

  1. Float2 = 0相除:
    • 左边:0 / 0 = 0,依然是黑色
    • 中间:0 / 0 = 0,依然是黑色
    • 中间偏右:0.00001 / 0 = 1,变为白色
    • 右边:0.5 / 0 = 1,变为白色

除以0时,若分子小于等于0 => 商为负无限大;若分子大于0 => 商为无限大。不建议除以0

从下图可以看出除法一般是用来设置锐利度的

从下图可看出除法在某些情况下还与光照有关

——比较——

Abs(绝对值)

  • input < 0 => output = -input
  • input = 0 => output = 0
  • input > 1 => output = input

Tips:

Relay节点没有任何操作,只是为了Debug出图形

Clamp(限制):控制显示的区域

  • input < Min => output = Min
  • Min < input < Max => output = input
  • input > Max => output = Max

将输入的值,控制在范围内

如果与纹理坐标配合,就能控制显示的区域

Tips:

需要与Scale节点区分开

  • Scale:裁剪掉其他区域
  • Clamp:其他区域依然显示,只是显示的不是原来的纹理,而是单色

Saturate(饱和):归一化

  • input < 0 => output = 0
  • 0 < input < 1 => output = input
  • input > 1 => output = 1

常于Lerp节点配合

从下图可以看出,在使用Saturate节点之后,Y坐标大于1的值也是按1来计算的

Sign(符号):硬化

  • input < 0 => output = -1
  • input = 0 => output = 0
  • input > 0 => output = 1

Step(比较):硬化

  • A<B => output = 1
  • A>B => output = 0

Floor(去尾):向下取整

Fract(取小数)

内部所作的事类似于output = input - Floor(input)

——限制范围——

Remap(映射):归一化

数据区间映射,将[Omin, Omax]上每个数映射到区间[Nmin, Nmax]上
$$
N_{xy} = \frac{N_{max} - N_{min}}{O_{max} - O_{min}} \times (O_{xy} - O_{min}) + N_{min}
$$

上述公式的Latex表达式:

1
N_{xy} = \frac{N_{max} - N_{min}}{O_{max} - O_{min}} \times (O_{xy} - O_{min}) + N_{min}

为方便理解,这里我们使用浮点数代替二维数,运作方式和结果是一样的
$$
\begin{split}
output &= \frac{1 - 0}{2 - 1} \times (1.5 - 1) + 0 \
&= 1 \times 0.5 \
&= 0.5
\end{split}
$$
最后输出的结果是0.5,与右边的颜色一样。

其实这里可以看出来,这里的数据很友好。Float1的值正好将[1, 2]映射到[0, 1]上了

  • 1 对应 0
  • 1.5 对应 0.5
  • 2 对应 1

小技巧:

在控制数值范围的时候,只用考虑

  • 最小值如何才能达到目标最大值
  • 最大值如何才能达到目标最小值

因为我们已经把最极端的两个例子给列出来了,其他的自然会满足需求

Lerp(线性插值):融合两个纹理

融合两个纹理

—-反转—-

One Minus(1-):1-input

out = 1 - input,对与UV坐标和遮罩很有用,可以将Texture纹理变为负片

  • UV坐标:翻转两次,右上角翻转到左下角,左上角翻转到右下角
  • 遮罩Mash:黑白颜色翻转

  • 纹理:变为负片

Negate(相反):相反数

将输入的值变为相反数

Tips:

  • 世界坐标输出的Global Preview为球形

  • UV坐标和纹理是方形

——三角函数——

Tan(正切)

ATan(反正切)

——其他——

Scale(缩放):裁剪区域

这个节点的Global Preview显示有问题,改变缩放数值后需要重新连接输入接口才能正常显示

  • UV坐标:改变纹理缩放大小(与Tilling作用一样,再与offset可以选择展示的位置)
  • 纹理:改变颜色的亮度

========UV坐标========

Texture Coordinates(UV坐标)

纹理坐标:定义纹理如何映射到3D资源上

  • Tiling:纹理填充
  • Offset:偏移参数

从下图可看出,设置Tiling为(2,2)之后,图片纹理在水平和垂直方向上各增加了一个纹理(前提是需要将Wrap Mode设置为Repeat

输出完整的纹理坐标

  • 左下角为(0, 0),为黑色
  • 左上角为(0, 1),为绿色
  • 右下角为(1, 0),为红色
  • 右上角为(1, 1),为黄色

这里的颜色只是为了方便表述描绘二维数值的大小,没有什么特殊的含义,不要被颜色给迷惑了(顺带一提颜色正好对应着RGBA中的(R, G)

输出纹理的横坐标

  • 左边为0,右边为1
  • 从左到右逐渐远离0,到达1

输出纹理的纵坐标

  • 下边为0,上边为1
  • 从下到上逐渐远离0,到达1

Panner(平移):平移UV坐标

平移节点:根据Time按指定Speed移动UV坐标。若没指定速度,则使用默认速度

NOTE:使用的Texture必须将Wrap Mode设置为Repeat

========顶点========

Vertex Position

顶点位置坐标节点,输出每个顶点相对物体原点的坐标

  • 对于基础形状,如球形的最上方为(0, 0.5, 0)
  • 对于导入的物体,如人物,最上方为(0, 人物高度, 0)(并不是所有物体都是归一化的)
  • 每个像素点的值不会随着物体的移动、旋转改变,但是缩放会改变

========Time========

Time Parameters(时间)

时间参数节点:输出Unity内部经过的时间(以秒为单位)