在利用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代码段都起了作用,实际运用中取其一即可。