Unity Shader入门教程(三) :实现逐顶点和逐像素的漫反射效果

在利用shader实现漫反射这种基本光照效果时,我们需要先知道漫反射的计算公式:

其中Clight为光照的强度和颜色,Mdiffuse为材质的漫反射系数,n为表面法向量,I为光源的方向。max操作是防止n和I的点积出现负数,控制其结果在[0,1]范围内。

而在unity shader中有一些内置的参数可以直接获取到这个公式所用到的变量。光照的强度和颜色可以用_LightColor0这个变量中获取,材质的漫反射系数可以用_Diffuse获取。而shader中能直接获取的表面法向量是模型空间中的,则需要另外转换成世界空间中的表面法向量,具体函数下面代码可以体现,而光源方面和强度则可直接从内置的_WorldSpaceLightPos0变量中获取。(如果场景中有多个光源并且类型可能是点光源等其他类型这不能用_WorldSpaceLightPos0获取)

逐顶点:即对每个片元(模型中的三角面)的顶点进行光照计算,然后进行插值计算填充整个面,在顶点着色器中进行。在细分程度较低的模型中,使用此着色效果会在背光面和向光面交界处有一些锯齿,此时采用逐像素光照可以解决此问题。

逐像素:即对每个片元直接进行填充,在片元着色器中进行。其计算量比逐顶点的光照计算量大。

关于代码中unity shader内置的矩阵,可以参考该文章:https://blog.youkuaiyun.com/qq826364410/article/details/81539321

具体代码如下:

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

Shader "Custom/diffuse" {
	Properties {
		_Diffuse("Diffuse", Color) = (1, 1, 1)
	}
	SubShader {

		//逐顶点光照
		Pass{
			Tags{"LightMode" = "ForwardBase"}

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			fixed4 _Diffuse;

			struct a2v{
				float4 vertex : POSITION;//获取模型空间中的坐标
				float3 normal : NORMAL;  //获取模型空间中的法向量
			};

			struct v2f{
				float4 pos : SV_POSITION;  //世界空间中的坐标
				fixed3 color : COLOR;
			};

			v2f vert(a2v v)
			{
				v2f o;
				//从模型空间坐标转换到裁剪空间坐标
				o.pos = UnityObjectToClipPos(v.vertex);

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

				//v.normal为模型空间中的法线向量,这里转化为世界空间中的法线单位向量
				//(float3x3)unity_WorldToObject,法线向量为三维矢向量,取前三行三列即可
				fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				//获取unity中世界空间中的光照方向和强度
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//漫反射公式:光照强度和颜色*材质漫反射系数*(世界空间法向量和光源方向的点积)(saturate函数是防止点积出现负数)
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight)*0.5f + 0.5f);
				//光照结果为环境光+漫反射光
				o.color = ambient + diffuse;

				return o;
			}

			//光照计算在顶点着色器中已经计算完,这里片元着色器中直接输出即可
			fixed4 frag(v2f i) : SV_Target{
				return fixed4(i.color, 1.0);
			}

			ENDCG
		}

		//逐像素光照
		Pass{
			Tags{ "LightMode" = "ForwardBase" }

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			fixed4 _Diffuse;

			struct a2v {
				float4 vertex : POSITION;//获取模型空间中的坐标
				float3 normal : NORMAL;  //获取模型空间中的法向量
			};

			struct v2f {
				float4 pos : SV_POSITION;       //世界空间中的坐标
				fixed3 worldNormal : TEXCOORD0; //世界空间中的法向量
			};

			v2f vert(a2v v)
			{
				v2f o;
				
				//片面着色器中无法直接获取v.normal,只能在顶点着色器中进行转换并作为参数传入片面着色器中
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

				return o;
			}

			fixed4 frag(v2f i) : SV_Target{

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight)*0.5f + 0.5f);

				fixed3 color = diffuse + ambient;

				return fixed4(color, 1.0);
			}

			ENDCG
		}
	}
	FallBack "Diffuse"
}

 公式中_LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight)*0.5f + 0.5f)的两个0.5f是利用了半兰伯特模型的计算公式,目的是为了使背光面也有明暗变化。不至于模型背光区域全黑,看起来像一个平面一样。

注意:这里我将两个pass段代码写到一个脚本里了,目的是方便大家理解。实际中两个pass代码段都起了作用,实际运用中取其一即可。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值