游戏开发中建议使用半兰伯特光照模型
在基本光照模型中求出漫反射部分的计算公式:
漫反射 = 入射光线的颜色和强度(c light) * 材质漫反射系数 (m diffuse)* 表面法线(n) * 其光源防线 (I)
在shader中为了不让 n和i的点乘结果为负数,即使用了saturate函数让值截取在[0,1]区间之内
首先 ,使用逐顶点渲染的方式渲染模型光照:
顶点着色器渲染流程如下:
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
fixed3 amlibent = 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 = amlibent + diffuse;
return o;
}
将模型局部坐标转换为裁剪坐标是顶点着色器最要基本的功能,在早期版本使用mul(UNITY_MATRIX_MVP , v.vertex); 的方式,现在后期版本使用unity内部的方法UnityObjectToClipPos(v.vertex)来实现即可。
效果如下图:
可见背光面和迎接光面的过度明显有点粗糙
下面使用逐片元漫反射实现:
v2f vert (a2v v)
{
v2f o;
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 worldNormal = 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);
}
顶点着色器这里只要获得裁剪空间,和法线的世界空间。这里使用变换矩阵的逆矩阵 unity_WorldToObject实现
在片元着色器中同样实现功能即可
_
效果如下图:
过度比顶点渲染的效果丝滑更多
再来对比背光面的效果,如下图:
左边是逐片元 右边是逐顶点
可见两个模型效果都是背光面死黑的效果。在半条命的游戏开发公司优化了兰伯特光照模型,即在此基础上做了简单的修改,称半兰伯特光照模型
公式如下:
这里没用用max的操作来防止n和i的点乘为负数,而是使用α 来做为缩放量 、β做为偏移量,一般情况下α和β都为0.5,此技术仅仅为视觉加强技术,没有任何物理依据
实现如下:
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = i.worldNormal;
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
在逐片元漫反射基础上做了半兰伯特光照模型算式计算结果
效果如下图:
效果显著