Unity中的基础光照 -- Shader入门精要学习(5)

Unity中的基础光照

1 我们是如何看到这个世界的

1.1 光源

  • 光学中,用**辐照度(irradiance)**来量化光
  • 平行光:计算在垂直于 l l l 的单位面积上单位时间内穿过的能量

因为辐照度是和照射到物体表面时光线之间的距离 d c o s θ \frac{d}{cos\theta} cosθd 成反比的,因此辐照度就和 c o s θ cos\theta cosθ 成正比, c o s θ cos \theta cosθ 可以使用光源方向 l l l 和表面法线 n n n 的点积来得到

1.2 吸收和散射

光线由光源发射出来后,就会与一些物体相交,通过相交的结果只有吸收和散射两个

  • 散射(scattering):只改变光线的方向,不改变光线的密度和颜色
    • 散射到物体内部:被称为折射(refraction)投射(transmission)
    • 散射到物体外部:被称为反射(reflection)
  • 吸收(absorption):只改变光线的密度和颜色,不改变光线的方向

为了区分不同的散射方向,在光照模型中用了不同的部分来计算它们:

  • **高光反射(specular)**表示物体表面是如何反射光线的 – 一般认为只反射某一特定方向的光
  • **漫反射(diffuse)**表示有多少光线会被折射、吸收和散射出表面 – 一般认为没有方向性,光线在所有方向上平均分布

出射度(exitance):根据入射光线的数量和方向,计算出的出射光线的数量和方向

1.3 着色

**着色(shading)**指根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等)使用一个等式去计算沿某个方向的出射度的过程

也将这个等式称为光照模型(Lighting Mode)

BRDF(Bidirectional Reflectance Distribution Function)光照模型:直译过来就是双向反射分布函数,当给定入射光线的方向和辐照度后,BRDF可以给出某个方向上的光照能量分布

2 标准光照模型

也叫Phong光照模型,有裴祥风提出,只关心直接光照,即直接从光源发射出来照射到物体表面后,经过物体表面地一次反射直接进入摄像机的光线,将进入摄像机的光线分为四个部分,每个部分使用一种方法计算它的贡献度

  • 自发光(emissive):使用 c e m i s s i v e c_{emissive} cemissive 表示,描述给定一个方向时,一个表面本身会向该方向发射多少能量(如果没有全局光照,自发光的表面并不会照亮周围物体,因为光照都是通过反射得到的)
  • 高光反射(specular):使用 c s p e c u l a r c_{specular} cspecular 表示,描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量
  • 漫反射(diffuse):使用 c d i f f u s e c_{diffuse} cdiffuse 表示,描述当光线从光源照射到模型表面时该表面会向每个方向散射多少能量
  • 环境光(ambient):使用 c a m b i e n t c_{ambient} cambient 表示,用于描述其他所有间接光照

2.1 环境光

利用环境光来模拟所有的间接光照,场景中的所有物体都使用这个环境光,通常是一个全局变量
c a m b i e n t = g a m b i e n t c_{ambient} = g_{ambient} cambient=gambient

2.2 自发光

光线直接由由光源发射进入摄像机,不需要经过任何物体反射,直接使用该材质的自发光颜色来计算
c e m i s s i v e = m e m i s s i v e c_{emissive} = m_{emissive} cemissive=memissive
通常在实时渲染中,自发光的表面并不会照亮周围的表面,即这个物体并不会被当成一个光源

2.3 漫反射

漫反射完全随机,可以认为在任何反射方向上的分布都是一样的,符合兰伯特定律:反射光线的强度与表面法线和光源方向之间的家教的余弦值成正比:
c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ) m a x ( 0 , n ⋅ I ) c_{diffuse} = (c_{light} \cdot m_{diffuse})max(0,n \cdot I) cdiffuse=(clightmdiffuse)max(0,nI)
其中, n n n 为表面法线, I I I 为指向光源的单位矢量, m d i f f u s e m_{diffuse} mdiffuse 是材质的漫反射颜色, c l i g h t c_{light} clight 是光源颜色

取最大值是为了防止点乘结果为负值,可以防止物体被后面来的光照亮

2.4 高光反射

Phong模型:

对于下图中的四个矢量,我们实际只需要知道三个矢量即可,反射方向可以根据其他信息计算得到

在这里插入图片描述

r = 2 ( n ^ ⋅ I ) n ^ − I r = 2(\hat{n} \cdot I)\hat{n}-I r=2(n^I)n^I
利用Phong模型来计算高光反射的部分:
c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , v ^ ⋅ r ) m g l o s s c_{specular} = (c_{light}\cdot m_{specular})max(0, \hat{v}\cdot r)^{m_{gloss}} cspecular=(clightmspecular)max(0,v^r)mgloss

  • m g l o s s m_{gloss} mgloss:材质的光泽度(gloss),也被称为反光度(shininess),用于控制高光区域的亮点有多宽, m g l o s s m_{gloss} mgloss 越大,亮点就越小
  • m s p e c u l a r m_{specular} mspecular:是材质的高光反射颜色,用于控制该材质对于高光反射的强度和颜色
  • c l i g h t c_{light} clight:光源的颜色和强度

Blinn模型:

为了避免计算反射方向 r ^ \hat{r} r^ ,Blinn模型引入了一个新的矢量 h ^ \hat{h} h^ 它是通过对 v ^ \hat{v} v^ I ^ \hat{I} I^ 的取平均后再归一化得到的,即
h ^ = v ^ + I ∣ v ^ + I ∣ \hat{h} = \frac{\hat{v} + I}{|\hat{v} + I|} h^=v^+Iv^+I
在这里插入图片描述

之后,再使用 n ^ \hat{n} n^ h ^ \hat{h} h^ 之间的夹角进行进行计算,而非 v ^ \hat{v} v^ r ^ \hat{r} r^ 之间的夹角

最后,Blinn模型的公式如下:
c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , n ^ ⋅ h ^ ) m g l o s s c_{specular} = (c_{light} \cdot m_{specular})max(0, \hat{n} \cdot \hat{h})^{m_{gloss}} cspecular=(clightmspecular)max(0,n^h^)mgloss
硬件实现时,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong模型,此时可以认为 v ^ \hat{v} v^ I ^ \hat{I} I^ 均为定值,而 h ^ \hat{h} h^ 是一个常量,但是,当 v ^ \hat{v} v^ I ^ \hat{I} I^ 不是定值时,Phong模型可能反而更快一点

2.5 逐像素和逐顶点

  • 逐像素光照(per-pixel lighting):在片元着色器中计算,以每个像素为基础,得到它的法线,然后进行光照模型的计算,最后在面片之间对顶点法线进行插值(这种技术也被称为Phong着色)
  • 逐顶点光照(per-vertex lighting):在顶点着色器中计算(也被称为高洛德着色),在每个顶点上计算光照,然后再渲染图元内部进行线性插值,最后输出像素颜色

两种方法比较:

  • 计算量:由于顶点数往往小于像素数目,因此逐顶点光照的计算量往往小于逐像素光照
  • 效果:由于逐顶点光照依赖于在顶点之间进行线性插值,当光照模型中有非线性的计算时,逐顶点光照就会出问题

2.6 总结

虽然Blinn-Phong模型并不完全符合真实世界中的光照现象,但由于易用性和计算速度快的特点,使它仍被广泛使用

但该模型也有很大的局限性,例如菲涅尔反射等物理现象就无法用Blinn-Phong模型表现出来,其次Blinn-Phong模型是各向同性的,但是某些物体表面是各向异性的,使用Blinn-Phong模型就无法很好的表现

3 Unity中的环境光和自发光

环境光:Unity中环境光可以在Window->Lighting->Ambient Source/Ambient Color\Ambient Intensity中控制,在Shder中,我们只需要通过Unity的内置变量UNITY_LIGHTIMODEL_AMBIENT就可以得到环境光的颜色和强度信息

自发光:由于大多数物体没有自发光特性,因此在本书中绝大部分Shader都没有计算自发光Shader,只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可

4 在Unity Shader中实现漫反射光照模型

漫反射基本公式:
c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ) m a x ( 0 , n ⋅ I ) c_{diffuse} = (c_{light} \cdot m_{diffuse})max(0,n \cdot I) cdiffuse=(clightmdiffuse)max(0,nI)
要计算漫反射需要知道4个参数:入射光线的颜色和强度 c l i g h t c_{light} clight ,材质的漫反射系数 m d i f f u s e m_{diffuse} mdiffuse ,表面法线 n ^ \hat{n} n^ 以及光源方向 I I I

为防止出现负值,我们需要使用max操作,而CG提供了这样的函数 s a t u r a t e ( x ) saturate(x) saturate(x) 即可将x截取在 [ 0 , 1 ] [0,1] [0,1] 的范围内,如果 x x x 为一个矢量,那么会对它的每一个分量进行这样的操作

4.1 逐顶点光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShader/DiffuseVertexLevel"
{
	 Properties {
		// 声明一个Color属性,初始化为白色,用于控制材质的漫反射颜色 

		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	 }
	 SubShader {
		Pass {
			// LightMode标签是Pass标签中的一种,只有定义了正确的LightMode,才能得到一些Unity的内置光照变量
			Tags {"LightMode" = "UniversalForward"}   // 如果是使用URP,则这里设置为UniversalForward
			// Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			// 分别定义顶点着色器和片元着色器的名字
			#pragma vertex vert
			#pragma fragment frag

			// 为了使用Unity中的一些内置变量,需要包含Unity的内置文件Lighting.cginc
			#include "Lighting.cginc"

			// 为了在CG语句中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
			fixed4  _Diffuse;

			// 定义顶点着色器的输入和输出结构体
			struct a2v {
				float4 vertex : POSITION;   // 模型空间中的顶点位置 
				float3 normal : NORMAL;     // 用于访问顶点的法线
			};
			struct v2f {
				float4 pos : SV_POSITION;    // 裁剪空间中的顶点坐标
				fixed3 color : COLOR;        // 将顶点信息计算的光照颜色传递给片元着色器
			};

			// 漫反射的计算都在顶点着色器中进行
			v2f vert(a2v v) {
				v2f f;
				// 将顶点从模型空间通过MVP矩阵变换到裁剪空间
				f.pos = UnityObjectToClipPos(v.vertex);

				// 获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				// 将表面法线从世界空间转换到模型空间
				fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				
				// 利用_WorldSpaceLightPos0获取光源方向
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

				// 利用_LightColor0来访问该Pass处理的光源的颜色和强度信息
				// 利用_Diffuse.rgb获取材质漫反射颜色
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

				// 输出结果为环境光和漫反射叠加
				f.color = ambient + diffuse;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target {
				return fixed4(f.color, 1.0);
			}

			ENDCG
		}
	 }
	 FallBack "VertexLit"
}

4.2 逐片元光照

原理与逐顶点光照相同,只是把漫反射的计算放在了片元着色器中

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "MyShader/DiffusePixelLevel"
{
    Properties {
		// 声明一个Color属性,初始化为白色,用于控制材质的漫反射颜色 
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	 }
	 SubShader {
		Pass {
			// LightMode标签是Pass标签中的一种,只有定义了正确的LightMode,才能得到一些Unity的内置光照变量
			Tags {"LightMode" = "UniversalForward"}   // 如果是使用URP,则这里设置为UniversalForward
			// Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			// 分别定义顶点着色器和片元着色器的名字
			#pragma vertex vert
			#pragma fragment frag

			// 为了使用Unity中的一些内置变量,需要包含Unity的内置文件Lighting.cginc
			#include "Lighting.cginc"

			// 为了在CG语句中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
			fixed4  _Diffuse;

			// 定义顶点着色器的输入和输出结构体
			struct a2v {
				float4 vertex : POSITION;   // 模型空间中的顶点位置 
				float3 normal : NORMAL;     // 用于访问顶点的法线
			};
			struct v2f {
				float4 pos : SV_POSITION;         // 裁剪空间中的顶点坐标
				fixed3 worldNormal : TEXCOORD0;   // 将顶点信息计算的光照颜色传递给片元着色器
			};

			// 漫反射的计算都在顶点着色器中进行
			v2f vert(a2v v) {
				v2f f;
				// 将顶点从模型空间通过MVP矩阵变换到裁剪空间
				f.pos = UnityObjectToClipPos(v.vertex);
				
				// 将法线从世界空间变换到模型空间
				f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				return f;
			}

			fixed4 frag(v2f f) : SV_Target {
				// 获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				// 获取模型空间法线
				fixed3 worldNormal = normalize(f.worldNormal);

				// 获取光源方向
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				// 计算漫反射
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
				fixed3 color = ambient + diffuse;
				return fixed4(color, 1);
			}

			ENDCG
		}
	 }
	 FallBack "VertexLit"
}

逐像素光照可以得到更平滑的效果

存在的问题:光照无法到达的区域模型外观通常是全黑的,使模型背光的区域看起来和一个平面一样,失去了模型的细节表现,对于此问题,使用半兰伯特(Half Lambert)光照模型改善

4.3 半兰伯特模型

之前使用的漫反射光照模型也被称为兰伯特光照模型,因为它符合兰伯特定律(在平面某点漫反射光的光强与该反射点的法向量和入射光的余弦值成正比),为了改善前文提到的问题,在原兰伯特基础上做了一个简单的修改,因此被称为半兰伯特光照模型

广义的半兰伯特光照模型如下:
c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ) ( α ( n ^ ⋅ I ) + β ) c_{diffuse} = (c_{light} \cdot m_{diffuse})(\alpha(\hat{n} \cdot I) + \beta) cdiffuse=(clightmdiffuse)(α(n^I)+β)
半兰伯特光照模型没有使用max操作防止 n ^ ⋅ I \hat{n} \cdot I n^I 出现负值,而是对其结果进行了 α \alpha α 倍的缩放再加上 β \beta β 的便宜,绝大多数情况下 α \alpha α β \beta β 的值均为 0.5 0.5 0.5,即公式为:
c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ) ( 0.5 ( n ^ ⋅ I ) + 0.5 ) c_{diffuse} = (c_{light} \cdot m_{diffuse})(0.5(\hat{n} \cdot I) + 0.5) cdiffuse=(clightmdiffuse)(0.5(n^I)+0.5)
通过这样的方式,可以将 n ^ ⋅ I \hat{n} \cdot I n^I 的结果从 [ − 1 , 1 ] [-1,1] [1,1] 映射到 [ 0 , 1 ] [0,1] [0,1] 的范围内,这样背光面也可以有明暗变化

Shader "MyShader/DiffusePixelHalfLambertLevel"
{
    Properties {
		// 声明一个Color属性,初始化为白色,用于控制材质的漫反射颜色 
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	}
	SubShader {
		Pass {
			// LightMode标签是Pass标签中的一种,只有定义了正确的LightMode,才能得到一些Unity的内置光照变量
			Tags {"LightMode" = "UniversalForward"}   // 如果是使用URP,则这里设置为UniversalForward
			// Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			// 分别定义顶点着色器和片元着色器的名字
			#pragma vertex vert
			#pragma fragment frag

			// 为了使用Unity中的一些内置变量,需要包含Unity的内置文件Lighting.cginc
			#include "Lighting.cginc"

			// 为了在CG语句中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
			fixed4  _Diffuse;

			// 定义顶点着色器的输入和输出结构体
			struct a2v {
				float4 vertex : POSITION;   // 模型空间中的顶点位置 
				float3 normal : NORMAL;     // 用于访问顶点的法线
			};
			struct v2f {
				float4 pos : SV_POSITION;         // 裁剪空间中的顶点坐标
				fixed3 worldNormal : TEXCOORD0;   // 将顶点信息计算的光照颜色传递给片元着色器
			};

			// 漫反射的计算都在顶点着色器中进行
			v2f vert(a2v v) {
				v2f f;
				// 将顶点从模型空间通过MVP矩阵变换到裁剪空间
				f.pos = UnityObjectToClipPos(v.vertex);
				
				// 将法线从世界空间变换到模型空间
				f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				return f;
			}

			fixed4 frag(v2f f) : SV_Target {
				// 获取环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				// 获取模型空间法线
				fixed3 worldNormal = normalize(f.worldNormal);

				// 获取光源方向
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				// 计算漫反射
				fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
				fixed3 color = ambient + diffuse;
				return fixed4(color, 1);
			}

			ENDCG
		}
	}
	FallBack "VertexLit"
}

5 在Unity Shader中实现高光反射光照模型

高光反射基本公式:
c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , v ^ ⋅ r ) m g l o s s c_{specular} = (c_{light}\cdot m_{specular})max(0, \hat{v}\cdot r)^{m_{gloss}} cspecular=(clightmspecular)max(0,v^r)mgloss
需要知道的参数:

  • c l i g h t c_{light} clight:入射光线的颜色和强度
  • m s p e c u l a r m_{specular} mspecular:材质的高光反射系数
  • v ^ \hat{v} v^:视角方向
  • r r r:反射方向

其中,反射方向 r r r 可以由表面法线 n ^ \hat{n} n^ 和光源方向 I ^ \hat{I} I^ 计算而得:
r = 2 ( n ^ ⋅ I ^ ) n ^ − I ^ r = 2(\hat{n} \cdot \hat{I})\hat{n}-\hat{I} r=2(n^I^)n^I^
为此,CG提供了计算反射方向的函数 r e f l e c t ( i , n ) reflect(i,n) reflect(i,n),其中 i i i 为入射方向, n n n 是法线方向,可以是float,float2,float3等类型

5.1 逐顶点光照

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShader/Specular/SpecularVertexLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            // 获取Properties语句中定义的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;   // 模型空间中顶点的位置
                float3 normal : NORMAL;     // 模型空间中顶点的法线
            };
            struct v2f {
                float4 pos : SV_POSITION;   // 裁剪空间中顶点的位置
                fixed3 color : COLOR;       // 顶点着色器计算得到的颜色
            };

            v2f vert (a2v v)
            {
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);                                                                   // 将顶点坐标从模型空间变化到裁剪空间
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                            // 获取环境光
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));                             // 将顶点法线从模型空间变换到世界空间
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);                                               // 获取环境光的方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));                                      // 获取反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);            // 获取视线方向
                
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));             // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir, reflectDir)), _Gloss);     // 计算高光反射

                f.color = ambient + diffuse + specular;
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                return fixed4(f.color, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

5.2 逐像素光照

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShader/Specular/SpecularPixelLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            // 获取Properties语句中定义的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;   // 模型空间中顶点的位置
                float3 normal : NORMAL;     // 模型空间中顶点的法线
            };
            struct v2f {
                float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置
                fixed3 worldNormal : TEXCOORD0;    // 顶点着色器计算得到的颜色
                fixed3 worldPos : TEXCOORD1;       // 顶点着色器计算得到的颜色
            };

            v2f vert (a2v v)
            {
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间
                f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);   // 将顶点法线从模型空间变换到世界空间
                f.worldPos = mul(unity_ObjectToWorld, v.vertex);                // 将顶点坐标从模型空间变换到世界空间
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                          // 获取环境光
                fixed3 worldNormal = normalize(f.worldNormal);                                                          // 获取顶点法线在世界空间的坐标
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);                                             // 获取环境光的方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));                                    // 获取反射方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);                                  // 获取视线方向
                
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));           // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);   // 计算高光反射

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

5.3 Blinn-Phong光照模型

Blinn模型没有使用反射方向,而是引入了一个新的矢量 h ^ \hat{h} h^,通过对视角方向和光照方向 I ^ \hat{I} I^ 相加再归一化之后得到(见5.2.4):
h ^ = v ^ + I ∣ v ^ + I ∣ \hat{h} = \frac{\hat{v} + I}{|\hat{v} + I|} h^=v^+Iv^+I
Blinn-Phong模型的公式如下:
c s p e c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) m a x ( 0 , n ^ ⋅ h ^ ) m g l o s s c_{specular} = (c_{light} \cdot m_{specular})max(0, \hat{n} \cdot \hat{h})^{m_{gloss}} cspecular=(clightmspecular)max(0,n^h^)mgloss

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShader/Specular/SpecularBlinnPhongLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            // 获取Properties语句中定义的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;   // 模型空间中顶点的位置
                float3 normal : NORMAL;     // 模型空间中顶点的法线
            };
            struct v2f {
                float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置
                float3 worldNormal : TEXCOORD0;    // 顶点着色器计算得到的颜色
                float3 worldPos : TEXCOORD1;       // 顶点着色器计算得到的颜色
            };

            v2f vert (a2v v)
            {
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间
                f.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);   // 将顶点法线从模型空间变换到世界空间
                f.worldPos = mul(unity_ObjectToWorld, v.vertex);                // 将顶点坐标从模型空间变换到世界空间
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                           // 获取环境光
                fixed3 worldNormal = normalize(f.worldNormal);                                                           // 获取顶点法线在世界空间的坐标
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);                                              // 获取光源的方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);                                   // 获取视线方向
                fixed3 halfDir = normalize(worldLightDir + viewDir);                                                     // 获取矢量h

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));            // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);   // 计算高光反射

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

6 使用Unity内置函数

手动计算光源的信息过程相对比较麻烦,UnityCG.cginc里提供了一些常用的函数,用于计算光照模型

需要包含头文件

#include "UnityCG.cginc"
函数名描述
float3 WorldSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。内部实现使用了UnityWorldSpaceViewDir函数
float3 UnityWorldSpaceViewDir(float4 v)输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
float3 ObjectSpaceViewDir(float4 v)输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向
float3 WorldSpaceLightDir(float4 v)仅可用于前向渲染中,输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir,函数没有被归一化
float3 UnityWorldSpaceLightDir(float4 v)仅可用于前向渲染中,输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向,没有被归一化。
float3 ObjectSpaceLightDir(float4 v)仅可用于前向渲染中,输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。
float3 UnityObjectToWorldNormal(float3 norm)把法线方向从模型空间转换到世界空间中
float3 UnityObjectToWorldDir(in float3 dir)把方向矢量从模型空间变换到世界空间中
float3 UnityWorldToObjectDir(float3 dir)把方向矢量从世界空间变换到模型空间中

修改之前的SpecularBlinnPhongLevel的代码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShader/Specular/SpecularBlinnPhongLevel"
{
    Properties
    {
        _Diffuse("Diffuse", Color) = (1, 1, 1, 1)      // 控制漫反射颜色
        _Specular("Specular", Color) = (1, 1, 1, 1)   // 控制高光反射颜色
        _Gloss("Gloss", Range(8.0, 256)) = 20          // 控制高光区域大小
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "UniversalForward" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"
            #include "UnityCG.cginc"

            // 获取Properties语句中定义的变量
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v {
                float4 vertex : POSITION;   // 模型空间中顶点的位置
                float3 normal : NORMAL;     // 模型空间中顶点的法线
            };
            struct v2f {
                float4 pos : SV_POSITION;          // 裁剪空间中顶点的位置
                float3 worldNormal : TEXCOORD0;    // 顶点着色器计算得到的颜色
                float3 worldPos : TEXCOORD1;       // 顶点着色器计算得到的颜色
            };

            v2f vert (a2v v)
            {
                v2f f;
                f.pos = UnityObjectToClipPos(v.vertex);                         // 将顶点坐标从模型空间变化到裁剪空间
                f.worldNormal = UnityObjectToWorldNormal(v.normal) ;            // 将顶点法线从模型空间变换到世界空间
                // f.worldPos = mul(unity_ObjectToWorld, v.vertex);
                f.worldPos = UnityObjectToWorldDir(v.vertex);                // 将顶点坐标从模型空间变换到世界空间
                return f;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                                                           // 获取环境光
                fixed3 worldNormal = normalize(f.worldNormal);                                                           // 获取顶点法线在世界空间的坐标
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(f.worldPos));                                   // 获取光源的方向
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldPos));                                           // 获取视线方向

                fixed3 halfDir = normalize(worldLightDir + viewDir);                                                     // 获取矢量h

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));            // 计算漫反射
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);   // 计算高光反射

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader是一种用于渲染图形的程序,它可以控制对象的表面颜色、纹理、透明度、反射等属性,从而实现特殊的视觉效果。对于游戏开发者来说,掌握Shader编写技巧是非常重要的。 以下是关于Unity Shader入门精要: 1. ShaderLab语言 ShaderLab是Unity用于编写Shader的语言,它是一种基于标记的语言,类似于HTML。ShaderLab可以用于定义Shader的属性、子着色器、渲染状态等信息。 2. CG语言 CG语言是Unity用于编写Shader的主要语言,它是一种类似于C语言的语言,可以进行数学运算、向量计算、流程控制等操作。CG语言可以在ShaderLab嵌入,用于实现Shader的具体逻辑。 3. Unity的渲染管线 Unity的渲染管线包括顶点着色器、片元着色器、几何着色器等组件,每个组件都有不同的作用。顶点着色器用于对对象的顶点进行变换,片元着色器用于计算每个像素的颜色,几何着色器用于处理几何图形的变形和细节等。 4. 模板和纹理 在Shader,我们可以使用纹理来给对象添加图案或者贴图,也可以使用模板来控制对象的透明度、反射等属性。纹理可以通过内置函数tex2D()来获取,模板可以通过内置函数clip()来实现裁剪。 5. Shader的实现 Shader的实现需要注意以下几点: - 在ShaderLab定义Shader的属性、子着色器、渲染状态等信息。 - 在CG语言实现Shader的具体逻辑,包括顶点着色器、片元着色器等内容。 - 使用纹理和模板来实现特定的视觉效果。 - 在对象上应用Shader,通过调整Shader的属性来达到不同的效果。 以上是关于Unity Shader入门精要,希望对你有所帮助。如果你想更深入地了解Shader的编写技巧,可以参考官方文档或者相关教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值