Unity Shader入门精要笔记(九):Unity 的基础光照——漫反射的实现

本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。

http://blog.csdn.net/lzhq1982/article/details/74898606


上一篇讲了Unity光照的概念,理论和公式。这一篇我们可以利用公式,实现简单的漫反射效果。

在这之前,还是要提一嘴Unity的环境光和自发光。因为很简单,这里顺带提一下。

Unity的环境光可以在Window->Lighting->Ambient Source/Ambient Color中控制(不同版本可能有区别)。在Shader中,我们通过UNITY_LIGHTMODEL_AMBIENT来得到环境光的颜色和强度。

而自发光也非常简单,只需要在片元着色器输出之前,把材质的自发光颜色加到输出颜色即可。

下面进入我们的重点——漫反射的实现。

上一篇我们给出了漫反射的计算公式:


从公式看出,我们需要4个数据:入射光颜色和强度c(light),材质漫反射系数m(diffuse),表面法线n和光源方向I。

max可以防止结果为负值,CG有这样的函数。但这里用saturate函数同样能达到效果。

saturate(x):

参数x:可以是标量或矢量,可以是float,float2,float3等类型。

描述:把x限制在[0, 1]范围内,小于0返回0,大于1返回1,如果x是矢量,每一个分量会执行此操作。

1、逐顶点光照

我们建一个胶囊体,然后新建一个材质,把材质赋给胶囊体,然后选一个Shader模板,开始编辑,代码如下:

Shader "CustomShader/Light/Diffuse Vertex-Level"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	}
	SubShader
	{
		Pass
		{
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"

			fixed4 _Diffuse;

			struct appdata
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				fixed3 color : COLOR;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

				o.color = ambient + diffuse;

				return o;
			}


			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = fixed4(i.color, 1.0);
				return col;
			}
			ENDCG
		}
	}

	FallBack "Diffuse"
}


代码不长,我们看看都做了什么,属性里我们声明了漫反射颜色_Diffuse,然后在Pass第一行我们定义了一个标签"LightMode" = "ForwardBase",LightMode是Pass标签的一种,用于定义该Pass在Unity光照流水线中的角色,这涉及到光照渲染路径,后面单独介绍。这里只需要知道,只有正定义了正确的LightMode,我们才能得到一些Unity的内置光照变量,例如代码里的_LightColor0。为了得到光照的一些内置变量,我们包含了头文件Lighting.cginc。顶点的输入输出结构体不解释,看语义。

我们重点看顶点着色器,因为我们采用的是逐顶点光照,所以把光照计算放在了顶点着色器里,我们可以看到,我们需要计算环境光和漫反射光,环境光的获得就一行代码:用内置的UNITY_LIGHTMODEL_AMBIENT。我们需要注意,我们需要把所有的光照计算都放在世界空间下,所以后面的计算都转换到了世界空间。漫反射也就3行,第一行计算法线:

fixed3 worldNormal = 

normalize(mul(v.normal, (float3x3)unity_WorldToObject));

这里大家不要以为写错了,怎么向量在左,矩阵在右了,如果以为出错的朋友,一定是忘了法线变换的特殊性了,忘了的同学请回顾其他数学相关里法线变换部分,法线变换我们需要法线乘以顶点变换矩阵的逆转置,因为我们要转换到世界空间,矩阵该是unity_ObjectToWorld,但我们要逆转置,首先其逆是unity_WorldToObject,然后转置我们只需要调换法线和矩阵的位置相乘就行了,然后就有了上面的样子。当然别忘了normalize归一化。还有方向矢量我们用三维矩阵和向量就可以了。

第二行获得光源方向:

fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

这里我们用了内置的_WorldSpaceLightPos0来得到世界空间的光源方向,这里我们只是默认有一个光源,且是平行光,所以可以这么用,但如果有多个光源并且类型可能是点光源等其他类型,那就不能这么用了。这里也不要忘了归一化。

第三行我们调用了公式:

fixed3 diffuse = 

_LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

这行纯粹是公式代入,没啥好解释的,_LightColor0可以得到该Pass的光源颜色和强度。_Diffuse是属性里的材质颜色,我们可以在面板上自己调。最后把求出的环境光和漫反射相加传出即可。

片元着色器啥也没干,只是把传入的颜色正常输出了。最后我们把回调Fallback设为内置的Diffuse。我们得到如下效果:


我们可以看到有一些锯齿感,这取决于模型的细致程度,毕竟是逐顶点光照,计算都在顶点上,然后插值出的像素颜色,但优势是计算速度快,下面我们看看逐像素光照。

2、逐像素光照

直接上代码吧:

Shader "CustomShader/Light/Diffuse Pixel-Level"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	}
	SubShader
	{
		Pass
		{
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				fixed3 normal : NORMAL;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

				o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

				return o;
			}
			
			fixed3 _Diffuse;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

				fixed3 color = ambient + diffuse;

				return fixed4(color, 1.0);
			}
			ENDCG
		}
	}

	FallBack "Diffuse"
}

我们主要看区别,其实区别不大,只是把顶点着色器的部分移到片元着色器中了,顶点着色器的输出由颜色变成了转换后的法线,所以把插值的法线放到了片元着色器中参与计算,效果如下:


效果平滑多了,当然计算量也就大了,因为像素数要远大于顶点数。最后我们注意到一个问题,在光照无法到达的区域,模型通常是全黑的,没有明暗变化,有一种改善技术,叫半兰伯特光照模型(Half Lambert)

3、半兰伯特模型

上面的模型我们称之为兰伯特模型,因为它符合兰伯特定律:在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。为了改善上面提出的问题,有一种技术在原兰伯特模型基础上做了一点修改,成为半兰伯特模型,广义的半兰伯特模型公式如下:


与兰伯特模型相比,半兰伯特模型没有用max防止点积为负,而是对其结果进行了α倍的缩放再加上一个β大小的偏移。绝大多数情况下,α和β都为0.5,即公式如下:


这样我们把n和I点积的范围从[-1, 1]映射到[0, 1]。所以对于模型背光面,兰伯特模型点积会得到0,而半兰伯特模型背光面也会有明暗变化。

直接上代码:

Shader "CustomShader/Light/Diffuse HalfLambert"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	}
	SubShader
	{
		Pass
		{
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				fixed3 normal : NORMAL;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

				o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

				return o;
			}
			
			fixed3 _Diffuse;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

				fixed halfLambert = dot(worldNormal, worldLight) * 0.5 + 0.5;
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

				fixed3 color = ambient + diffuse;

				return fixed4(color, 1.0);
			}
			ENDCG
		}
	}

	FallBack "Diffuse"
}
从上面代码可以看出,我们还是采用的逐像素光照,只是公式变成了半兰伯特模型。代码很简单,不解释。最后看一下对比效果:

基础漫反射就介绍到这里,下一篇我们看基础高光反射的实现。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值