[Shader] Unity Shader 内在函数

56 篇文章 2 订阅

4.0.7. | 内在函数

在 Cg 和 HLSL 中,我们都可以找到有助于我们编程效果的内在函数。 这些函数对应于一般的数学运算,我们根据我们希望获得的结果在特定情况下使用它们。 我们可以找到最常见的:

• Abs.
• Ceil.
• Clamp.
• Cos.
• Sin.
• Tan.
• Exp.
• Exp2.
• Floor.
• Step.
• Smoothstep.
• Frac.
• Length.
• Lerp.
• Min.
• Max.
• Pow.

4.0.8. | Abs function.

该函数指的是数字的绝对值,作为参数,我们可以传递标量值和向量。

其语法如下:

// return the absolute value of n
float abs(float n)
{
	return max(-n, n);
}
float2 abs (float2 n);
float3 abs (float3 n);
float4 abs (float4 n);

绝对值将始终返回正数,其数学符号系统由两个框住数字的侧边栏组成。

|-3| = 3
-3的绝对值等于3

|-5| = 5
-5的绝对值等于5

| 6 | = 6
6的绝对值等于它本身

我们的程序可以使用 abs(n) 函数实现多种效果,包括重新创建万花筒或生成三平面映射。 事实上,对于第一种情况,我们可以通过计算UV坐标中的绝对值来实现这样的效果。 同时,在三平面映射中,我们可以确定网格法线的绝对值,以在正轴和负轴上生成投影。

在这里插入图片描述
(图4.0.8a。正如我们在1.0.5节中看到的,UV坐标从0.0f开始,到1.0f结束。在上图中,我们可以看到当我们减去0.5f时U坐标的行为。 纹理已设置为在检查器中clamp)

如果我们关注上图UV坐标中的起始点,我们会注意到U坐标以Quad为中心减去0.5f,最小值变成了负数[-0.5f]。

在示例中,纹理像素被拉伸,因为纹理已设置为在环绕模式下夹紧。 在这种情况下,我们可以应用绝对值来生成“镜像”效果。

在这里插入图片描述
(图4.0.8b.U坐标减去0.5f的绝对值)

接下来,我们将开发万花筒效果以充分理解这个概念。 我们将首先创建一个新的着色器类型“Unlit Shader”,我们将其称为“USB_function_ABS”。 然后,我们在着色器属性中声明一个新属性,稍后我们将使用它来旋转 UV 坐标。

Shader "USB/USB_function_ABS"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		// let's add a property to rotate the UV
		_Rotation ("Rotation", Range(0, 360)) = 0
	}
}

由于完整旋转为 360 度,因此 _Rotation 等于 0 到 360 之间的范围。我们继续使用属性的全局或连接变量

Pass
{
	CGPROGRAM
	…
	sampler2D _MainTex;
	float4 _MainTex_ST;
	float _Rotation;
	…
	ENDCG
}

我们可以计算 U 和 V 坐标的绝对值来生成效果,并在片段着色器阶段使用这些新值作为输出颜色。

fixed4 frag (v2f i) : SV_Target
{
	// let's calculate the absolute value of U
	float u = abs(i.uv.x - 0.5);
	// let's calculate the absolute value of V
	float v = abs(i.uv.y - 0.5);
	fixed col = tex2D(_MainTex, float2(u, v));
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

根据我们分配为 _MainTex 的纹理配置,可能会发生两件主要的事情,

  1. 如果纹理设置为“repeat”,则 UV 坐标的负区域将被相同纹理元素的重复填充。
  2. 另一方面,如果纹理配置对应于“Clamp”,则图像的纹素将以与图4.0.8a相同的方式拉伸。

无论配置如何,镜像效果都会很明显,因为我们使用每个坐标的 abs(n) 函数。

如果我们想要旋转UV坐标,我们可以使用Shader Graph包中包含的Unity_Rotate_Degrees_float函数。

void Unity_Rotate_Degrees_float(
		float2 UV,
		float2 Center,
		float Rotation,
		out float2 Out
	)
{
	Rotation = Rotation * (UNITY_PI/180.0f);
	UV -= Center;
	float s = sin(Rotation);
	float c = cos(Rotation);
	float2x2 rMatrix = float2x2(c, -s, s, c);
	rMatrix *= 0.5;
	rMatrix += 0.5;
	rMatrix = rMatrix * 2 - 1;
	UV.xy = mul(UV.yx, rMatrix);
	UV += Center;
	Out = UV;
}

由于该函数的类型为“void”,因此我们必须在片段着色器阶段的字段中初始化一些变量,然后将它们作为参数传递。

Unity_Rotate_Degrees_float() {}
fixed4 frag (v2f i) : SV_Target
{
	float u = abs(i.uv.x - 0.5);
	float v = abs(i.uv.y - 0.5);
	// we link the rotation property
	float rotation = _Rotation;
	// we center the rotation pivot
	float center = 0.5;
	// let's generate new UV coordinates for the texture
	float2 uv = 0;
	Unity_Rotate_Degrees_float(float2(u,v), center, rotation, uv);
	fixed4 col = tex2D(_MainTex, uv);
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

函数 Unity_Rotate_Degrees_float 中的第一个参数对应于我们要旋转的 UV 坐标,继续旋转中心或枢轴,然后是度数,最后是我们将用于纹理的新坐标值的输出。

4.0.9. | Ceil function.

根据NVIDIA官方文档,

Ceil 返回不小于标量或每个向量分量的最小整数。

这是什么意思? 函数 ceil(n) 将返回一个接近其参数的整数,即不带小数,例如,如果数字等于 0.5f,则 ceil 将返回 1。

ceil (0.1) = 1
ceil (0.3) = 1
ceil (1.7) = 2
ceil (1.3) = 2

0.0f 和 1.0f 之间的所有数字都将返回 1,因为后者将是不小于其参数的最小整数值。

其语法如下:

// it returns an integer value
float ceil(float n)
{
 return -floor(-n);
}
float2 ceil (float2 n);
float3 ceil (float3 n);
float4 ceil (float4 n);

此功能对于在视频游戏中生成“缩放或放大镜”效果非常有帮助。 为此,我们只需计算 U 和 V 坐标的 ceil(n) 值,将最终值乘以 0.5,然后在 UV 的默认值和 ceil(n) 生成的值之间生成线性插值 功能。

为了深入理解这个概念,我们将执行以下操作:我们将创建一个新的着色器类型“Unlit Shader”,我们将其称为 USB_function_CEIL,并且我们将首先在纹理 _MainTex 中声明新的 UV 坐标 片段着色器阶段。

fixed4 frag (v2f i) : SV_Target
{
	// let's ceil the U coordinate
	float u = ceil(i.uv.x);
	// let's ceil the V coordinate
	float v = ceil(i.uv.y);
	// we assign the new values for the texture
	fixed4 col = tex2D(_MainTex, float2(u, v));
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

我们上面执行的操作会返回纹素位置[1, 1],为什么呢? 因为 U 和 V 坐标都从 0.0f 开始并以 1.0f 结束,所以 ceil(n) 将仅返回纹理中的最后一个纹素。 因此,它将生成从纹理的右上角到左下端的缩放效果。

在这里插入图片描述
(图 4.0.9a。Ceil 将返回纹理中找到的最后一个纹理元素;位于位置 1, 1 的纹理元素)

对于这种效果,我们需要一个允许我们增加或减少纹理大小的属性。 为此,我们将转到属性并声明一个浮动范围,我们将其称为 _Zoom

Shader "USB/USB_function_CEIL"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Zoom ("Zoom", Range(0, 1)) = 0
	}
}

在该范围内,“0”代表百分之零的变焦,而“1”则相当于百分之一百。 然后我们在程序中声明连接变量。

Pass
{
	…
	sampler2D _MainTex;
	float4 _MainTex_ST;
	float _Zoom;}

由于我们需要缩放点从纹理的中心开始,因此我们可以通过将运算乘以 0.5 来修改其位置,如下所示:

fixed4 frag (v2f i) : SV_Target
{
	// let's ceil the U coordinate
	float u = ceil(i.uv.x) * 0.5;
	// Let's do the same with the V coordinate
	float v = ceil(i.uv.y) * 0.5;
	// we assign the new values for the texture
	fixed4 col = tex2D(_MainTex, float2(u, v));
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

此时,纹理已经从其中心扩展。 然而,我们仍然无法欣赏缩放效果,因为 ceil(n) 函数继续返回 1; 因此,我们将继续看到单一颜色填充四边形区域。 我们能做的是在 UV 的默认值和 ceil(n) 函数产生的值之间生成线性插值,

fixed4 frag (v2f i) : SV_Target
{
	float u = ceil(i.uv.x) * 0.5;
	float v = ceil(i.uv.y) * 0.5;
	float uLerp = lerp(u, i.uv.x, _Zoom);
	float vLerp = lerp(v, i.uv.y, _Zoom);
	// we assign the new values for the texture
	fixed4 col = tex2D(_MainTex, float2(uLerp , vLerp));
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

在上面的示例中,我们为不同的坐标创建了两个名为 uLerp 和 vLerp 的变量。 我们正在通过属于 Cg/HLSL 语言的 lerp(x, y, s) 函数进行线性插值。 此外,还包括属性 _Zoom,其范围在 0.0f 到 1.0f 之间。 如果我们从 Unity Inspector 修改属性 _Zoom 的值,我们可以看到纹理如何增大或减小其大小,并将其中心点作为参考。
在这里插入图片描述
(图 4.0.9b. Ceil 将返回纹理元素位于纹理的中心)

4.1.0. | Clamp function.

当我们想要限制操作的结果时,这个函数很方便。 默认情况下,它允许我们通过设置最小值和最大值来定义数值范围内的值。 在我们开发函数的过程中,我们会遇到一些导致数字小于“0”或大于“1”的操作,例如,在计算网格法线与光照方向的点积时,我们可以得到一个范围 -1.0f 和 1.0f 之间。 由于负值会在最终效果中产生色彩伪影,因此使用Clamp ,我们可以限制并重新定义0.0f和1.0f之间的范围。

其语法如下:

float clamp (float a, float x, float b)
{
	return max(a, min(x, b));
}
float2 clamp (float2 a, float2 x, float2 b);
float3 clamp (float3 a, float3 x, float3 b);
float4 clamp (float4 a, float4 x, float4 b);

在上面的函数中,参数“a”指的是最小返回值,而参数“b”指的是范围内的最大返回值。 对于参数,“x”对应于我们要根据a和b限制的值。 这是什么意思? 我们假设“a”和“b”有一个设定范围,x 有一个变量。

当“x”等于1.0f时,如果参数“a”等于0.1f并且参数“b”等于0.9f,则“x”的最大返回值为0.9f; 为什么? 因为“b”定义了操作的最高限值。

在这里插入图片描述
(图4.1.0a)

相反的情况也会发生同样的情况。 当“x”等于0.0f时,返回值为0.1f,因为“a”定义了“x”的最小返回值。

在这里插入图片描述
(图4.1.0b)

为了深入理解这个概念,我们将执行以下操作:首先,我们将创建一个新的着色器类型“Unlit Shader”,我们将其称为 USB_function_CLAMP,并在其属性中声明三个浮动范围; 每个参数一个。

Shader "USB/USB_function_CLAMP"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Xvalue ("X", Range(0, 1)) = 0
		_Avalue ("A", Range(0, 1)) = 0
		_Bvalue ("B", Range(0, 1)) = 0
	}
}

然后我们声明全局变量或连接变量以将它们连接到程序。

…
Pass
{
	…
	sampler2D _MainTex;
	float4 _MainTex_ST;
	float _Xvalue;
	float _Avalue;
	float _Bvalue;}

我们将使用刚刚创建的着色器来增加或减少主纹理的伽玛颜色。 因此,此时我们可以做两件事:

  1. 在我们的程序中生成一个简单的函数,将值限制在一个范围内。
  2. 或者使用 Cg/HLSL 语言中包含的函数“clamp”。

我们将从声明一个新函数开始。 为此,我们将自己定位在顶点着色器和片段着色器阶段之间,并编写一个称为“ourClamp”的新方法。

v2f vert(appdata v) {}
float ourClamp(float a, float x, float b)
{
	return max(a, min(x, b));
}
fixed4 frag(v2f i) : SV_Target {}

正如我们在上一个函数的实现中所看到的,ourClamp 将限制两个既定数字(浮点 a 和浮点 b)之间的值(浮点 x)。

然后,在片段着色器阶段,我们创建一个名为“darkness”的浮动变量,并使其等于我们的新函数。 作为参数,我们将按照上述顺序传递上面声明的属性。

float ourClamp(float a, float x, float b) {}
fixed4 frag(v2f i) : SV_Target
{
	float darkness = ourClamp(_Avalue, _Xvalue, _Bvalue);
	fixed4 col = tex2D(_MainTex, i.uv);
	
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

dark 变量是浮点/标量类型,这意味着它只有一维。 因此,将 col 向量乘以上述变量将影响向量 col (RGBA) 的四个通道。

float ourClamp(float a, float x, float b) {}
fixed4 frag(v2f i) : SV_Target
{
	float darkness = ourClamp(_Avalue, _Xvalue, _Bvalue);
	fixed4 col = tex2D(_MainTex, i.uv) * darkness;
	
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

我们可以通过在我们所应用的语言中包含“clamp”函数来简化上述操作。

// float ourClamp(float a, float x, float b) { … }
fixed4 frag(v2f i) : SV_Target
{
	// Cg and HLSL include the clamp function.
	float darkness = clamp(_Avalue, _Xvalue, _Bvalue);
	fixed4 col = tex2D(_MainTex, i.uv) * darkness;
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

总之,我们现在可以定义着色器中输出颜色的最大和最小伽玛。

4.1.1. | Sin and Cos function.

这些三角函数指的是角度的正弦和余弦,即:

• 在余弦的情况下,相邻边与斜边之间的比率。
• 以及对边和斜边之间的比率(在正弦的情况下)。

其语法如下:

float cos (float n);
float2 cos (float2 n);
float3 cos (float3 n);
float4 cos (float4 n);

float sin (float n);
float2 sin (float2 n);
float3 sin (float3 n);
float4 sin (float4 n);

我们可以在标量值和向量上使用“cos 和 sin”。

在这里插入图片描述
(图 4.1.1a。笛卡尔平面上函数的图形表示。在左边,我们可以看到 x 的 sin,在右边,我们可以看到 x 的 cos。)

Cg/HLSL 语言中包含的这些函数在计算机图形学中非常有用。 有了它们,我们可以生成多个几何图形,甚至矩阵变换。 一个实际的实现示例是对象中顶点的旋转

我们已经知道,一个顶点具有三个被视为向量的空间坐标 (XYZ)。 鉴于其性质,我们可以转换这些值并从矩阵生成旋转的错觉。

假设我们想要在二维空间中旋转一个顶点。 在“Y”轴上应用函数“sin”将获得从上到下的波动。 在其“X”轴上使用函数“cos”将重现圆周运动。

在这里插入图片描述
(图4.1.1b。旋转主要是由于sin和cos之间的时间滞后造成的)

为了理解这个概念,我们将执行以下操作:我们将创建一个新的着色器类型“Unlit Shader”,我们将其称为 USB_function_SINCOS。 我们将使用这个着色器在立方体的顶点上生成一个小的旋转动画。 我们将首先声明一个浮动范围,稍后我们将在函数中使用它来确定旋转速度。

Shader "USB/USB_function_SINCOS"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Speed ("Rotation Speed", Range(0, 3)) = 1
	}
	SubShader {}
}

我们需要借助矩阵来旋转坐标; 在第 3.2.7 节中,我们能够使用不同维度的矩阵来回顾其结构。 这次,我们将在对象上使用三次三维矩阵 (float3x3)。

Pass
{
	…
	sampler2D _MainTex;
	float4 _MainTex_ST;
	float _Speed;
	// let's add our rotation function
	float3 rotation(float3 vertex)
	{
		// create a three-dimensional matrix
		float3x3 m = float3x3
		(
			1, 0, 0,
			0, 1, 0,
			0, 0, 1
		);
		// let’s multiply the matrix times the vertex input
		return mul(m, vertex);
	}}

“rotation”函数返回一个三维向量,并且它不执行任何特定操作,因为矩阵“m”只有其单位值。 但是,我们可以稍后在顶点着色器阶段使用它来变换对象的顶点。

我们首先选择一个旋转轴; 我们要注意下图。
在这里插入图片描述
(图4.1.1c.四维矩阵中的旋转轴)

上图中,每个矩阵代表一个旋转变换轴。 举例来说,这次我们将使用“Y”轴(RY 轴)进行练习。 因此,我们必须使用 sin 和 cos 三角函数在旋转方法中添加两个浮点变量。

float3 rotation(float3 vertex)
{
	// let’s add the rotation variables
	float c = cos(_Time.y * _Speed);
	float s = sin(_Time.y * _Speed);
	// create a three-dimensional matrix
	float3x3 m = float3x3
	(
		c, 0, s,
		0, 1, 0,
		-s, 0, c
	);
	// let’s multiply the matrix times the vertex input
	return mul(m, vertex);
}

稍后,在 4.2.4 节中,我们将详细讨论“_Time”属性。 现在,我们只考虑这个变量为操作添加时间,与 Time 的行为非常相似。 C# 中的 timeSinceLevelLoad。

_时间会影响场景中立方体的顶点,因此它们将开始根据旋转轴移动。

旋转功能可以完美运行; 现在,我们必须在顶点着色器阶段实现它。 为此,我们必须考虑 UnityObjectToClipPos 方法,因为如第 3.3.2 节中所述,它允许将对象的顶点从对象空间转换到剪辑空间。 因此,在将顶点坐标转换为屏幕位置之前,我们必须实现顶点的旋转。

v2f vert (appdata v)
{
	v2f o;
	float3 rotVertex = rotation(v.vertex);
	o.vertex = UnityObjectToClipPos(rotVertex);
	o.uv = TRANSFORM_TEX(v.uv, _MainTex);
	return o;
}

在练习中,声明了一个名为“rotVertex”的新三维向量。 其中存储了网格顶点的输入及其在对象空间中的旋转。 然后将该向量用作 UnityObjectToClipPos 方法中的参数。

4.1.2. | Tan function.

这个三角函数指的是角度的正切,即:

  • 对边与邻边的比率。

其语法如下:

float tan (float n);
float2 tan (float2 n);
float3 tan (float3 n);
float4 tan (float4 n);

在这里插入图片描述
(图 4.1.2a. 笛卡尔平面上 tan 函数的图形表示)

与 sin 和 cos 一样,tan 在几何图形和重复图案的计算中非常有用。 一个实际的实现示例是生成网格状程序mask,我们可以使用它来生成对象上的全息投影效果。 为此,我们可以简单地计算片段着色器阶段内某个 UV 坐标处的切线的绝对值。

在这里插入图片描述
(图4.1.2b)

我们将通过生成一个“Unlit Shader”类型的新着色器来举例说明,我们将其称为 USB_function_TAN。 我们将首先声明一个颜色属性和一个范围来增加或减少我们想要投影的线条数。

Shader "USB/USB_function_TAN"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Color ("Color", Color) = (1, 1, 1, 1)
		_Sections ("Sections", Range(2, 10)) = 10
	}
	SubShader {}
}

值得一提的是,我们将将此着色器应用于软件中包含的 3D 对象。 之所以这么说,是因为我们要对UV的V坐标进行操作。

如图4.1.2b所示,这样的视觉效果具有透明度; 因此,我们必须在 SubShader 中添加混合选项,以便黑色将被识别为 Alpha 通道。

SubShader
{
	Tags {"RenderType"="Transparent" "Queue"="Transparent"}
	Blend SrcAlpha OneMinusSrcAlpha
	Pass {}
}

我们将确保声明全局或连接变量,然后我们将进入片段着色器阶段添加允许我们在对象上投影水平线的功能。

float4 _Color;
float _Sections;

fixed4 frag (v2f i) : SV_Target
{
	float4 tanCol = abs(tan(i.uv.y * _Sections));
	tanCol *= _Color;
	fixed4 col = tex2D(_MainTex, i.uv) * tanCol;
	return col;
}

在前面的示例中,我们声明了一个名为 tanCol 的四维向量。 我们已经存储了绝对值的结果,用于计算正切; V 坐标和其中的 _Sections 属性之间的乘积。 随后,纹理和向量 tanCol 之间的因子被存储在名为 col 的四维向量中。 因此,我们分配给 _MainTex 属性的纹理将被插入。

在前面的操作中我们可以发现一个小细节:V 的正切返回一个小于“零”且大于“一”的数值范围。 因此,最终的颜色在电脑屏幕上会饱和。 为了解决这个问题,我们可以使用clamp功能,将值限制在0.0f到1.0f之间。

fixed4 frag (v2f i) : SV_Target
{
	float4 tanCol = clamp(0, abs(tan(i.uv.y * _Sections)), 1);
	tanCol *= _Color;
	fixed4 col = tex2D(_MainTex, i.uv) * tanCol;
	return col;
}

我们可以再次使用 _Time 变量来生成间距移动。 为此,我们只需将此属性减去或添加到之前操作中的 V 坐标即可。

fixed4 frag (v2f i) : SV_Target
{
	float4 tanCol = clamp(0, abs(tan((i.uv.y - _Time.x) * _Sections)), 1);
	tanCol *= _Color;
	fixed4 col = tex2D(_MainTex, i.uv) * tanCol;
	return col;
}

4.1.3. | Exp, Exp2 and Pow function.

这些函数的特点是在运算中使用指数,例如,函数“exp”返回标量和向量值中以 e 为底的指数,也就是说,“e”(2.7182828182846f)升为数字。

exp (2) = 7.3890560986f 和 2.71828182846 2.71828182846 2.71828182846f 2 ^2 2 一样

其语法如下:

float exp (float n)
{
	float e = 2.71828182846;
	float en = pow (e, n);
	return en;
}
float2 exp (float2 n);
float3 exp (float3 n);
float4 exp (float4 n);

在这里插入图片描述
(图 4.1.3a. exp(x) 在笛卡尔平面上的图形表示)

此外,“exp2”返回不同维度值的以 2 为底的指数,即 2 的指数。

exp2 (3) = 8 和 2 3 2^3 23 一样
exp2 (4) = 16
exp2 (5) = 32

float exp2 (float n);
float2 exp2 (float2 n);
float3 exp2 (float3 n);
float4 exp2 (float4 n);

另一方面,“pow”有两个参数:基数 (x) 及其指数 (n)。

pow (3, 2) = 9 和 3 2 3^2 32 一样
pow (2, 2) = 4
pow (4, 2) = 16

float pow (float x, float n);
float2 pow (float2 x, float2 n);
float3 pow (float3 x, float3 n);
float4 pow (float4 x, float4 n);

这些功能的有用性取决于正在执行的操作。 然而,它们通常用于计算噪声、输出颜色的伽玛增加以及重复模式。

4.1.4. | Floor function.

该函数返回一个不大于其参数的整数值,即没有小数位的标量或向量数,向下舍入,例如,1.97f 的下限返回 1; 为什么? 因为该函数从总数中减去数字的小数部分。

floor (1.56) = 1 和 1.56f - 0.56f. 一样
floor (0.34) = 0
floor (2.99) = 2

其语法如下:

float floor (float n)
{
	float fn;
	fn = n - frac(n);
	return fn;
}
float2 floor (float2 n);
float3 floor (float3 n);
float4 floor (float4 n);

在这里插入图片描述
(图 4.1.4a. 笛卡尔平面上的 Floor(x) 的图形表示)

与floor功能相反,floor在使用实色块创建视觉效果时非常有用,例如卡通着色器或一般的重复图案。

为了举例,我们来了解一下卡通着色器的实现原理。 从这个意义上说,我们将生成一个新的着色器类型“Unlit Shader”,我们将其称为 USB_functions_ FLOOR。

Shader "USB/USB_function_FLOOR"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white"{}
		[IntRange]_Sections ("Sections", Range (2, 10)) = 5
		_Gamma ("Gamma", Range (0, 1)) = 0
	}
	SubShader {}
}

由于各 sections 必须统一聚合,因此已为 _Sections 变量定义了 [IntRange]。 和之前的流程一样,我们必须在 Pass 中包含全局变量,这样我们才能实现 ShaderLab 和我们的程序之间的直接通信。

Pass
{
	CGPROGRAM
	…
	sampler2D _MainTex;
	float4 _MainTex_ST;
	float _Sections;
	float _Gamma;
	…
	ENDCG
}

接下来,我们将进入片段着色器阶段并声明一个新变量,该变量将根据UV的V坐标生成纯色块。

fixed4 frag (v2f i) : SV_Target
{
	float fv = floor(i.uv.y);
	fixed4 col = tex2D(_MainTex, i.uv);
	return col;
}

在前面的操作中,变量“fv”已被声明并初始化,只有一维。 其值等于V的取整结果; 因此,它等于“零”。 这是因为 UV 坐标从 0.0f 开始,到 1.0f 结束,并且我们已经知道,该函数返回一个不大于其参数的整数。

我们可以通过指定一个四维向量作为输出来证实该操作,其中前三个向量等于“fv”的值。

fixed4 frag (v2f i) : SV_Target
{
	float fv = floor(i.uv.y);
	// fixed4 col = tex2D(_MainTex, i.uv);
	// return col;
	return float4(fv.xxx, 1);
}

在这里插入图片描述
(图. 4.1.4b)

要添加纯色块,我们可以简单地将运算乘以一定数量的部分,然后除以十进制值。

至于gamma,我们可以直接将属性添加到输出颜色中。

fixed4 frag (v2f i) : SV_Target
{
	float fv = floor(i.uv.y * _Sections) * (_Sections/ 100);
	// fixed4 col = tex2D(_MainTex, i.uv);
	// return col;
	return float4(fv.xxx, 1) + _Gamma;
}

在这里插入图片描述
(图4.1.4c。该材料已配置有六个部分和0.17f的伽玛)

其实现原理与卡通着色器相同,不同之处在于我们在计算中使用全局照明而不是V坐标。

在本书的第二章中,我们将详细回顾自定义照明的实现。

4.1.5. | Step and Smoothstep function.

Step 和 smoothstep 是非常相似的函数,事实上,它们都有一个名为“edge”的参数,负责区分两个值之间的返回值。

我们将从step的压缩开始研究,然后接近smoothstep的操作,它的结构比前一个更复杂。

根据NVIDIA官方文档;

Step 可以为 x 大于或等于边缘的每个分量返回 1。 否则,它返回零。

语法如下:

float step (float x, float edge)
{
	return edge >= x;
}
float2 step (float2 x, float2 edge);
float3 step (float3 x, float3 edge);
float4 step (float4 x, float4 edge);

举例来说,我们可以在片段着色器阶段进行一个简单的操作来了解它是如何工作的。

fixed4 frag (v2f i) : SV_Target
{
	// add the color edge
	float edge = 0.5;
	// let’s return to RGB color
	fixed3 sstep = 0;
	sstep = step (i.uv.y, edge);
	// fixed4 col = tex2D (_MainTex, i.uv);
	return fixed4(sstep, 1);
}

我们首先在上面的操作中声明一个名为“sstep”的三维向量。 您可以在图 4.1.5a 中看到其图形表示。 作为参数,我们使用 UV 的 V 坐标和 0.5 作为“边缘”。 最后,我们返回 RGB 中的 sstep 和 alpha 通道的“one”。

在这里插入图片描述
(图4.1.5a。step 函数用于一些原始图形)。

值得记住的是,U 和 V 坐标都从 0.0f 开始,到 1.0f 结束; 因此,所有小于或等于边缘的将返回“1”(白色),反之则返回“0”(黑色)。 如果我们修改这些值之间的参数“边缘”,它可以平衡到一侧或另一侧。

smoothstep 函数的行为与前一个函数没有太大区别; 它唯一的区别在于返回值之间生成线性插值。

其语法如下:

float smoothstep (float a, float b, float edge)
{
	float t = saturate((edge - a) / (b - a));
	return t * t * (3.0 - (2.0 * t));
}

float2 smoothstep (float2 a, float2 b, float2 edge)
float3 smoothstep (float3 a, float3 b, float3 edge)
float4 smoothstep (float4 a, float4 b, float4 edge)

回到片段着色器阶段的操作,我们可以添加一个新变量来确定返回值之间的插值量。

fixed4 frag (v2f i) : SV_Target
{
	// add the edge
	float edge = 0.5;
	// add the amount of interpolation
	float smooth = 0.1;
	// add the return value in RGB
	fixed3 sstep = 0;
	// sstep = step (i.uv.y, edge);
	sstep = smoothstep((i.uv.y - smooth), (i.uv.y + smooth), edge);
	// fixed4 col = tex2D (_MainTex, i.uv);
	return fixed4(sstep, 1);
}

在之前的练习中,我们可以在 0.0f 和 0.5f 之间修改“smooth”的值以获得不同级别的插值。

在这里插入图片描述
(图4.1.5b。平滑等于0,1f)

4.1.6. | Length function.

正如其标题所提到的,长度是指表达两点之间距离的大小。 此功能在创建几何形状时非常方便,例如,我们可以生成具有圆边的圆形或多边形形状。

其语法如下:

float length (float n)
{
	return sqrt(dot(n,n));
}
float length (float2 n);
float length (float3 n);
float length (float4 n);

像往常一样,我们将创建一个新的着色器类型“Unlit Shader”,我们将其称为 USB_function_ LENGTH。 这次我们将在程序中使用一些函数来表示圆。 我们将首先添加属性,稍后我们将使用这些属性来放大、居中和平滑形状。

Shader "USB/USB_function_LENGTH"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Radius ("Radius", Range(0.0, 0.5)) = 0.3
		_Center ("Center", Range(0, 1)) = 0.5
		_Smooth ("Smooth", Range(0.0, 0.5)) = 0.01
	}
	SubShader
	{
		…
		Pass
		{float _Smooth;
			float _Radius;
			float _Center;}
	}
}

要创建一个圆,我们只需计算 UV 坐标的大小并减去半径即可。 其表示如下所示:

float circle (float2 p, float radius)
{
	// let’s create the circle
	float c = length(p) - radius;
	return c;
}

然而,正如我们在图4.1.6a中看到的,前面的操作返回了一个从点0,0f开始到1,0f结束的模糊圆,而我们正在寻找的结果对应于居中且更紧凑的形状。

在这里插入图片描述
(图4.1.6a。随着UV坐标值的增加,圆圈逐渐消失)

为此,我们可以在函数中添加一个新参数,该参数允许我们将圆以要应用材质的对象为中心。

float circle (float2 p, float center, float radius)
{
	// the argument center is equal to a range
	// between 0,0f and 1,0f
	float c = length(p - center) - radius;
	return c;
}

在这里插入图片描述
(图4.1.6b。中心等于0.5f)

然而,圆圈仍然会变得模糊。 如果我们想控制模糊量,我们可以使用 smoothstep 函数,正如我们所知,它会在两个值之间生成线性插值。

float circle (float2 p, float center, float radius, float smooth)
{
	float c = length(p - center);
	return smoothstep(c - smooth, c + smooth, radius);
}

在之前的操作中,我们添加了一个名为“smooth”的新参数,这将使我们能够控制模糊量。 然后我们可以在片段着色器阶段应用这个函数,如下所示。

float circle (float2 p, float center, float radius, float smooth)
{}

fixed4 frag (v2f i) : SV_Target
{
	float c = circle (i.uv, _Center, _Radius, _Smooth);
	return float4(c.xxx, 1);
}

正如我们所看到的,我们创建了一个名为“c”的一维变量,它已使用圆的默认值进行初始化。 必须注意颜色输出,因为三个输出 (c.xxx) 使用相同的通道 ®。

在这里插入图片描述
(图 4.1.6c.半径 0.3f、中心 0.5f 和平滑 0.023f)

4.1.7. | Frac function.

该函数返回一个值的分数,即其十进制值,例如 1.534f 的 frac 返回 0.534f; 为什么? 这是由于函数中执行的操作造成的。

frac (3,27) = 0,27f 和 3,27f - 3.相同
frac (0,47) = 0,47f
frac (1,0) = 0,0f

其语法如下:

float frac (float n)
{
	return n - floor(n);
}
float2 frac (float2 n);
float3 frac (float3 n);
float4 frac (float4 n);

我们可以在多种操作中使用 Frac,例如噪声计算、随机重复模式等等。

为了理解这个概念,我们将执行以下操作:我们将创建一个“Unlit Shader”类型的新着色器,我们将其称为 USB_function_FRAC。 我们将开始添加一个名为“Size”的属性,稍后我们将使用它来增加或减少圆的大小。

Shader "USB/USB_function_FRAC"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Size ("Size", Range(0.0, 0.5)) = 0.3
	}
	SubShader
	{
		…
		Pass
		{float _Size;}
	}
}

我们将执行的第一个操作是乘以 UV 坐标以获得片段着色器阶段的重复图案。

fixed4 frag (v2f i) : SV_Target
{
	// increase the amount of texture repetitions
	i.uv *= 3;
	fixed4 col = tex2D(_MainTex, i.uv);
	return col;
}

如果我们返回 Unity 并将纹理集指定为“clamp”,我们将获得与图 4.1.7a 类似的结果。 我们可以注意到图像中的边缘纹理像素与其自身一起拉伸。

在这里插入图片描述
(图4.1.7a。左边的图像有默认的UV坐标,而右边的图像有精确的坐标乘以3。Wrap模式对应于clamp)

在这种情况下,我们可以使用“frac”函数返回 UV 坐标的小数值,以生成定义的重复图案。

fixed4 frag (v2f i) : SV_Target
{
	// increase the amount of texture repetitions
	i.uv *= 3;
	float2 fuv = frac(i.uv);
	fixed4 col = tex2D(_MainTex, fuv);
	return col;
}

在这里插入图片描述
(图. 4.1.7b)

值得一提的是,在纹理上执行此操作并不是很有用,因为我们可以轻松地从检查器中将其设置为“Repeat”模式。

作为更实际的示例,让我们使用圆形沿纹理生成图案。

fixed4 frag (v2f i) : SV_Target
{
	// increase the amount of texture repetitions
	i.uv *= 3;
	float2 fuv = frac(i.uv);
	// generate the circle
	float circle = length(fuv - 0.5);
	// flip the colors and return an integer value
	float wCircle = floor(_Size / circle);
	// fixed4 col = tex2D(_MainTex, fuv);
	return float4(wCircle.xxx, 1);
}

如果我们注意返回值,我们会注意到 RGB (wCircle.xxx) 中的输出颜色使用相同的通道。 如果我们修改 _Size 属性的值,我们可以增加或减少圆圈的大小。
在这里插入图片描述
(图4.1.7c。属性_Size已在0,3f中配置)

4.1.8. | Lerp function.

顾名思义,该函数通常用于颜色过渡,它允许在两个值之间进行线性插值,例如,我们可以在其中一个角色上使用 lerp,通过交叉淡入淡出从一种皮肤变为另一种皮肤。

其语法如下:

float lerp (float a, float b, float n)
{
	return a + n * (b - a);
}
float2 lerp (float2 a, float2 b, float2 n);
float3 lerp (float3 a, float3 b, float3 n);
float4 lerp (float4 a, float4 b, float4 n);

我们将创建一个“Unlit Shader”类型的小型着色器来举例说明该函数,我们将其称为 USB_function_LERP。 我们首先声明两个纹理,稍后我们将使用它们作为“皮肤”效果,再加上一个数字范围来执行交叉淡入淡出。

Shader "USB/USB_function_LERP"
{
	Properties
	{
		_Skin01 ("Skin 01", 2D) = "white" {}
		_Skin02 ("Skin 02", 2D) = "white" {}
		_Lerp ("Lerp", Range(0, 1)) = 0.5
	}
	SubShader
	{
		…
		Pass
		{
			…
			sampler2D _Skin01;
			float4 _Skin01_ST;
			sampler2D _Skin02;
			float4 _Skin02_ST;
			float _Lerp;}
	}
}

由于我们将使用两个纹理,因此有必要在每种情况下使用 UV 坐标。 这些必须在顶点输入和输出中声明,主要原因有两个:

  1. 因为纹理会通过TRANSFORM_TEX函数受到“平铺和偏移”的影响。
  2. 因为我们稍后会在片段着色器阶段使用它们。
struct appdata
{
	float4 vertex : POSITION;
	// create the UV coordinates for each case 01 and 02
	float2 uv_s01 : TEXCOORD0;
	float2 uv_s02 : TEXCOORD1;
};
struct v2f
{
	float4 vertex : SV_POSITION;
	// we will use the UV coordinates in the fragment shader stage
	float2 uv_s01 : TEXCOORD0;
	float2 uv_s02 : TEXCOORD1;
};

v2f vert (appdata v)
{
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	// add tiling and offset for each case
	o.uv_s01 = TRANSFORM_TEX(v.uv_s01, _Skin01);
	o.uv_s02 = TRANSFORM_TEX(v.uv_s02, _Skin02);
	return o;
}

在片段着色器阶段,我们可以声明两个四维向量,在每种情况下使用 tex2D 函数,然后使用 lerp 函数在它们之间执行线性插值。

fixed4 frag (v2f i) : SV_Target
{
	// create a vector for each skin
	fixed4 skin01 = tex2D(_Skin01, i.uv_s01);
	fixed4 skin02 = tex2D(_Skin02, i.uv_s02);
	// make a linear interpolation between each color
	fixed4 render = lerp(skin01, skin02, _Lerp);
	return render;
}

如果我们注意 _Lerp 属性,我们会注意到它的值被限制在 0.0f 和 1.0f 之间。 默认值为 0.5f; 因此,如果我们为 _Skin[n] 的每个属性分配两个不同的纹理,则每种情况下的结果将等于 0.5f 的透明度或褪色。

在这里插入图片描述
(图 4.1.8a. 两个纹理之间的 Lerp)

4.1.9. | Min and Max function.

一方面,“min”是指两个向量或标量之间的最小值,而“max”则相反。 我们将在不同的操作中频繁使用这些函数,例如,我们可以使用 max 来计算对象上的扩散,返回“零”与网格法线与光线方向之间的点积之间的最大值。

其语法如下:

// if "a" is less than "b", it returns "a", otherwise returns "b"
float min (float a, float b)
{
	float n = (a < b ? a : b);
	return n;
}
float2 min (float2 a, float2 b);
float3 min (float3 a, float3 b);
float4 min (float4 a, float4 b);
// if "a" is greater than "b", it returns "a", otherwise returns "b"
float max (float a, float b)
{
	float n = (a > b ? a : b);
	return n;
}
float2 max (float2 a, float2 b);
float3 max (float3 a, float3 b);
float4 max (float4 a, float4 b);

我们将在稍后的第二章第 7.0.3 节中详细回顾这个函数,讨论漫反射。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值