Unity Shader零基础入门2:环境光、漫反射、高光

本文接上文的Unity Shader零基础入门1:纯色物体

Unity Shader零基础入门1:纯色物体_qq_21315789的博客-CSDN博客

先讲几个知识点:

float、half和fixed类型的区别:
在子着色器中float可用half、fixed代替(比如用fixed3代替float3)
float为32位存储,half 为16位(大约可存储正负6万之间的数),fixed 11位(存储-2到2的数)。比如颜色只需要4个0~1的数来表示,一般用fixed4,这样可以比用float4节约资源。后面这个数字代表几维,比如float为一维,float3为3维。

属性的格式与类型:

上一课没有用到属性,这一课需要用到了。属性中新建变量的格式:

变量名("显示变量名",类型)=初始值

比如:_Diffuse("Diffuse Color",Color)=(1,1,1,1)

_Diffuse是变量名,新变量通常都加"_"防止和关键字冲突。Diffuse Color是在工程面板中显示的名称。Color是一个类型。(1,1,1,1)是初始值,代表白色。属性中的多维初始值只需要加括号。属性中的语句结束时没有";"。

属性中的常见类型举例:

_Color("_Color",Color)=(1,1,1,1) //颜色
_Vector("Vector",Vector)=(1,2,3,4) //矢量
_Int("Int",Int)=3434 //整型
_Float("Float",Float)=5.5 //浮点数,数字后不用加f
_Range("Range",Range(1,11))=1 //有范围限制的Float。范围自定义。这里定成了1~11。
_2D("Texture",2D)=""{} //图片。可赋值为"颜色"{}如"white"{}。当赋值了"white"{}后,如果没有再给图片赋值,那读取时取到的就只有白色。而如果给图片赋值了就能取到图片颜色。

属性中的变量,可以在工程面板中赋值。

属性中定义的变量,在SubShader中的pass块中不能直接使用,而是要再次定义才能使用。且两边用到的类型名并不完全相同。对属性中的变量在pass中可用如下方法重新定义:

fixed4 _Color;//SubShader里没有Color类型,需要用其它类型定义颜色。因为颜色只需要用到0-1的数,所以用fixed4。属性中结尾无需分号,这里需要。
float4 _Vector; //用float4 定义 Vector
float _Int; //int也定义为float
float _Range;//Range型也定义为float
sampler2D _2D; //图片类型在这里定义为sampler2D类型

标准光照模型:

在标准光照模型里面,我们把进入摄像机的光分为下面四个部分:

自发光、高光反射、漫反射、环境光。

环境光用来模拟间接光照,比如光照到物体时会反射给附件的物体。

颜色的融合与叠加:

当需要融合两个颜色时,直接用"*"将两个颜色的值相乘。新颜色会受到旧的两个颜色的影响,但亮度一般不增加。当需要叠加两个颜色时,将两个颜色的值相加,同时亮度会增加。

漫反射的计算:

当物体的某个区域被照射时,如果该区域正对着光,那么接收的光就多,亮度就高。而如果是侧向的光,那么亮度就低。就好像正午时最明亮一样。

所以我们使用cos (光源方向和法线的夹角)来控制漫反射的亮度(三角函数原理这里不多讲)。对向量的cos运算直接使用点乘即可。即dot(光源方向,法线)。

但如果光源在模型的另一面,cos (光源方向和法线的夹角)会变为负数。我们不需要背面为负数,只需要背面为黑(0),所以我们用一个max函数保证最小值为0:max(0,cos (光源方向和法线的夹角))。

同时漫反射的颜色一般与光线颜色有关,所以再乘照射物体的直射光颜色而得到漫反射颜色。我们自己也可以添加一个颜色用以影响漫反射颜色,所以漫反射为:

直射光颜色 * max(0,cos (光源方向和法线的夹角))*设置的颜色。

半兰伯特光照模型:

上面这个漫反射的计算方法有一个缺点为模型的背面完全是黑色的,因为max(0,cos (光源方向和法线的夹角))在背面只能得到0。如果希望背面是逐渐变深的灰色,可以将max(0,cos (光源方向和法线的夹角))改为cos (光源方向和法线的夹角)*0.5+0.5,这样在背面时就是0.5~0。

高光的计算:

通过指数(pow)来计算高光。

高光颜色=直射光颜色*pow(max(0,cosθ),高光的参数x),θ是反射光方向和视野方向的夹角。由于max(0,cosθ)不大于1,x越大,导致得到的值越小。高光越不明显。视野方向指反光点指向相机的方向。
反射光方向= normalize(reflect(入射光方向,法线方向))。通过_WorldSpaceLightPos0.xyz可得到光源方向,即由物体指向光源的方向。然后再乘负号得到光源指向物体的方向。

开始制作shader:

 先新建一个shader。在工程面板中Create-Shader-Standard Surface Shader创建一个shader文件并重命名。

双击打开shader文件后删除原内容,改为如下内容:

Shader "Tutorial/02Specular" {
	Properties{
			//漫反射的颜色
			_DiffuseColor("Diffuse Color",Color)=(1,1,1,1)
			//_Gloss用于控制高光的程度,数字越小高光越明显、范围越大
			_Gloss("Gloss",Range(8,200))=10
			//高光颜色
			_SpecularColor("Specular Color",Color)=(1,1,1,1)
	}

	SubShader{
		pass{
			//CGPROGRAM上面的 Tags{ "LightMode"="ForwardBase" } 和下面的#include "Lighting.cginc" 两句是为了引入和能在之后使用unity内置的一些光照变量
			Tags{ "LightMode"="ForwardBase" }
			CGPROGRAM
			#include "Lighting.cginc"
			//属性中的变量在pass中需要重新定义才能使用。颜色为0-1,一般使用fixed4即可。
			fixed4 _DiffuseColor;
			//pass中没有Range型,且因为_Gloss不会很大,所以定义为half。
			half _Gloss;
			fixed4 _SpecularColor;


			//声明顶点函数名为vert
			#pragma vertex vert
			//声明片元函数名为frag
			#pragma fragment frag 

			//这里定义了结构体方便存储和传输数据。
			//a2v的含义是application to vertex,也就是从系统传输内容给顶点函数用。
			struct a2v{
				//将模型空间下的法线向量赋值给normal。方向一般用三维向量。
				float3 normal:NORMAL;
				//将模型空间下的顶点坐标赋值给vertex。坐标一般用四维向量。
				//存储坐标其实只用到前3维,第4维是转换时用到的。
				float4 vertex:POSITION;

				float3 tan:TANGENT;
			};
			//v2f表示vertex to fragment,即由顶点函数传递给片元函数。该结构体将作为顶点函数的返回值
			//作为顶点函数返回值的结构体,其中的每一个量都必须被语义解释。
			struct v2f{
				//将position作为剪切空间中的顶点坐标。
				//这个值是顶点函数的返回值中一定要包含的,但可以是直接返回,也可以放在结构体中。
				//SV 表示system value,系统值。
				float4 position:SV_POSITION;
				//建立了一个存储和传递颜色用的变量。
				//颜色的最后一维是阿尔法值,所以也可以用三维向量存储一个没阿尔法值的颜色。
				fixed3 color:COLOR; 
			};

			//顶点函数,参数为a2v类型,返回值为v2f类型
			v2f vert(a2v v){
				v2f f;
				//将模型空间下的顶点坐标转换到剪切空间并存储到f.position
				f.position= mul(UNITY_MATRIX_MVP,v.vertex);

				//使用UNITY_LIGHTMODEL_AMBIENT即可得到环境光。.rgb表示取不包含阿尔法值(透明度)的前3位颜色值。
				//可以看到我们这里对颜色的计算主要用3维向量,因为这里不需要阿尔法值。
				fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.rgb;//取得环境光
				//将模型空间下的法线转换到世界空间。
				//用unity_ObjectToWorld 可得到从模型空间转换到世界空间需要的矩阵。unity_WorldToObject则是从世界空间转换到模型空间需要的矩阵。
				//normalize()表示转换为模为1的单位向量。因为已经是不超过1的单位向量,所以用fixed3即可储存。
				//将一个普通向量从模型空间转换到世界空间,使用mul(unity_ObjectToWorld,向量),但对法线却要用mul(法线,(float3x3)unity_WorldToObject),看起来仿佛是与普通向量相反
				//这里的x非常难打,还存在看起来一样但不完全一样的乘号,建议直接复制。
				fixed3 normalDir=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				//第一个直射光的位置(被照射点接收到的光的方向)。.xyz表示取坐标向量的前3维。
				fixed3 lightDir=normalize( _WorldSpaceLightPos0.xyz);
				//计算漫反射颜色。_LightColor0用于得到光线颜色。_DiffuseColor是之前在属性中定义的。
				fixed3 difuse= _LightColor0.rgb*max(dot(normalDir,lightDir),0)*_DiffuseColor.rgb;
				//得到反射光方向
				fixed3 reflectDir= normalize(reflect(-lightDir,normalDir));
				//视野方向。_WorldSpaceCameraPos.xyz用于得到相机位置。
				//mul(unity_ObjectToWorld,v.vertex).xyz则为世界空间下的顶点坐标。
				//通过相机位置减顶点位置,得到近似的视野方向。
				fixed3 viewDir= normalize(_WorldSpaceCameraPos.xyz-mul(unity_ObjectToWorld,v.vertex).xyz);
				//计算高光颜色。_SpecularColor也是在属性中定义的。
				fixed3 specular=_LightColor0.rgb*pow(max(0,dot(reflectDir,viewDir)),_Gloss)*_SpecularColor.rgb;
				//将漫反射、环境光、高光叠加作为显示的颜色
				f.color=difuse+ambient+specular;
				//将这个结构体作为返回值

    			return f;
			}
			//片元函数
			//:SV_TARGET用于解释片元函数的返回值。这个返回值就是最终显示的颜色。
			float4 frag(v2f f):SV_TARGET{
				//片元函数的返回值需要是一个4维向量,所以后面补个1作为阿尔法值。
				return fixed4(f.color,1);
			}
			ENDCG
		}
	}
	Fallback "VertexLit"
}

之后新建一个胶囊体,为其添加一个新建的材质,然后就可以为其更换新写的shader:

然后就得到了一个能显示环境光、漫反射、和高光的胶囊体:

并且可以通过修改属性中添加的这几个变量修改漫反射颜色、高光系数、高光颜色:

如果需要修改环境光,在Window-Ambient Source-Color修改:

 效果:

猜你喜欢

转载自blog.csdn.net/qq_21315789/article/details/125421415