我们来看一下如何在Unity中实现这些基本光照模型。首先我们来实现标准光照模型中的漫反射部分
从公式可以看出,要计算漫反射部分需要知道4个参数:入射光的颜色和强度clight,材质的漫反射系数mdiffise,表面法线n以及光源方向l。未来房子点积结果为负值,我们需要使用max操作,而Cg提供了这样的函数。在本例中使用C搞定另一个函数可以达到同样目的,即saturate函数。
我们先新建好一个场景,在新建有个材质,最后在新建有个Shader,把这个Shader赋给刚刚建好的材质
再在Lighting窗口中把天空盒去掉
打开Lighting窗口的方法在这:打开Lighting窗口的方法在此文章最底下
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Unity Shaders Book/Chapter 6/6-4"
{
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1 )
}
SubShader
{
Pass
{
//指明光照模式
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//为了使用Unity内置的一些变量,需要包含进Unity的内置文件“Lighting.cginc”
#include "Lighting.cginc"
//为了在Shader中使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
//通过这样的方式我们就可以得到漫反射公式中最重要的参数之一——材质的漫反射属性
fixed4 _Diffuse;
//为了访问顶点的法线,我们在a2v中定义了一个normal变量,并通过使用NORMAL语义来告诉Unity要把模型顶点的法线信息储存到nornal变量中
//为了把在顶点着色器中计算得到的光照颜色传递给片元着色器,我们需要在v2f中定义一个color变量,且并不是必须使用COLOR语义,一些资料中也会用TEXCOORD0语义
struct a2v
{
float4 vertex : POSITION;
float4 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 color : COLOR;
};
//以下是顶点着色器,我们需要关注如何实现一个逐顶点的漫反射光照,因此漫反射部分在顶点着色器中进行
v2f vert (a2v v)
{
v2f o;
//Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
//Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//Transform the normal framn object space to world space
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//Get the ligth direction in world space
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
o.color = ambient + diffuse;
return o;
}
//所有的计算都在顶点着色器中完成了,因此片元着色器代码比较简单,我把颜色输出即可
fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
对于细分程度比较高的模型,逐顶点光照已经可以得到比较好的光照效果了。但对于一些细分程度较低的模型,逐顶点关照会出现一些视觉问题,例如在背光没雨向光面交界处会有一些锯齿。为了解决这些问题,我们可以使用漫反射光照。
我们只需要对Shade进行一些更改就可以实现逐像素的漫反射效果
和上面一样,我们新建有个材质和Shader,把材质赋给胶囊体,再把Shader赋给材质,我们的代码和上面的代码非常相似,我们直接吧代码复制到新建的Shader文件中
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert (a2v v)
{
v2f o;
//Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
//Transform the normal fram object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
//所有的计算都在顶点着色器中完成了,因此片元着色器代码比较简单,我把颜色输出即可
fixed4 frag(v2f i) : SV_Target
{
//Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
//Get the light direnction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//Compute diffuse trem
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot (worldNormal, worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
其它部分和上面的逐顶点光照代码完全相同
逐像素光照可以得到更加平滑的光照效果。但是计时是用了逐像素漫反射光照,有一个问题任然存在。在关照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这回使模型的背光区域看起来像一个平面一样,失去了模型的细节表现,我们可以挺过正价环境光来得到非全黑的效果,但任然无法解决背光面明暗一样的缺点,为此,有一种改善技术被提了出来,这就是半兰伯特光照模型
之前我们所使用的的模型也称为兰伯特光照模型,为了改善上面的问题,Valve公司在开发游戏《半条命》时剔除了一种技术,由于该技术是在原来兰伯特关照模型的基础上进行了一个简单的修改,因此被称为半兰伯特光照模型
广义的班兰伯特光照模型公式如下:
可以看出,与原兰伯特模型相比,版兰伯特模型没有使用max操作来防止n和l的点积为负值,而是对其结果进行了一个α被的缩放再加上一个β大小的偏移。绝大多数情况下,α和β的值均为0.5.公式为:
和上面一样,我们新建有个材质和Shader,把材质赋给胶囊体,再把Shader赋给材质,我们的代码和上面的代码非常相似,我们直接吧代码复制到新建的Shader文件中
我们只需要修改片元着色器中计算漫反射光照部分:
fixed4 frag(v2f i) : SV_Target
{
//Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
//Get the light direnction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//Compute diffuse term
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}