“Without purpose, the days would have ended, as such days always end, in disintegration.”
这个系列是表面着色器的光照模型,以及对自定义光照模型的深入
编写表面着色器时,您是在描述一个表面的属性(反射率颜色、法线等等),并由光照模型完成光照交互的计算。内置光照模型为 Lambert(漫反射光照)和 BlinnPhong(高光光照)。
有时您可能想使用自定义光照模型,在表面着色器 (Surface Shader) 中这是可以实现的。光照模型不过是与一些惯例匹配的几个 Cg/HLSL 函数。
内置
Lambert
和
BlinnPhong模型
在 Unity 的
Lighting.cginc里都可以找到
其在正向渲染路径中用于非与视线方向相关的光照模型(例如,漫反射)。
half4 LightingName (SurfaceOutput s, half3 lightDir, half atten);
其在正向渲染路径中用于与视线方向相关的光照模型。
half4 LightingName (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);
其用于延时光照路径中。
half4 LightingName_PrePass (SurfaceOutput s, half4 light);
_PrePass 这个函数,所有使用它的着色器都将仅编译成正向渲染。
现在来解释光照模型的定义:必须是Lighting + Name格式
光照模型函数参数:lightDir是光照模型的方向,atten是光照模型的衰减系数,viewDir是摄像机的视线方向
Shader "Example/Diffuse Texture" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
step1
这是Unity3D内置的Lambert(漫反射)光照模型
Lambert光照模型就在Lighting.cginc中,现在用unity Editor打开就可以看到内置的漫反射光照函数是怎么写的了
现在,让我们进行一次完全相同的操作,但要编写出我们自己的光照模型,而不是使用内置的 Lambert 模型。
//
// {
// Properties
// {
// _MainTex ("Texture", 2D) = "white" {}
// }
//
// SubShader
// {
// Tags { "RenderType" = "Opaque" }
// CGPROGRAM
//
//光照模式声明:使用自定义的光照模式
#pragma surface surf SimpleLambert
//光照函数名必须是Lighting + 自定义名字
//它通过在表面法线(s.Normal)和光线方向(LightDir)之间执行一次数量积来计算光照
//然后应用光衰减和颜色。
half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten)
{
//SurfaceOutput s这个就是经过表面计算函数surf处理后的输出,我们讲对其上的点根据光线进行处理
//点积的结果在-1至1之间,这个值越大表示法线与光线间夹角越小,这个点也就应该越亮。余弦值计算不多说了
half NdotL = dot (s.Normal, lightDir);
half4 c;
//接下来我们将surf输出的颜色(s.Albedo)与光线的颜色_LightColor0.rgb的乘积,
//然后再与刚才计算的光强系数(Ndotl)和输入的衰减系数相乘,
//为什么后面还要乘以2? 大家可以试试把乘以2去掉,其实就是为了进行光强补偿
c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten * 2);
c.a = s.Alpha;
return c;
}
// struct Input
// {
// float2 uv_MainTex;
// };
//
// sampler2D _MainTex;
// void surf (Input IN, inout SurfaceOutput o)
// {
// o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
// }
// ENDCG
// }
// Fallback "Diffuse"
// }
step2
下面进行改进,漫反射遮蔽
#pragma surface surf WrapLambert
half4 LightingWrapLambert (SurfaceOutput s, half3 lightDir, half atten)
{
half NdotL = dot (s.Normal, lightDir);
//在光照的基础上加上这句,增加光强
//其实官方的说法更准确:其中照明“环绕”在对象边缘。它对模拟子面散射效果有用。
half diff = NdotL * 0.5 + 0.5;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
c.a = s.Alpha;
return c;
}
step3
卡通渐变
通过一个不同的渐变纹理 实现不同的效果,
这是一个“渐变”(Ramp) 光照模型,它使用纹理渐变来定义表面如何对光线与法线之间的夹角作出反应。
Shader"Example / AutisticPatient SurfaceShader001"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//渐变纹理,初始值为gray
_Ramp("Ramp",2D) = "gray"{}
}
SubShader
{
//子着色器标签
Tags { "RenderType" = "Opaque" }
CGPROGRAM
//光照模式声明:使用自定义的光照模式
#pragma surface surf Ramp
//变量声明
sampler2D _Ramp;
//实现光照函数
half4 LightingRamp (SurfaceOutput s, half3 lightDir, half atten)
{
//点乘反射光线法线和光线方向
half NdotL = dot (s.Normal, lightDir);
//增强光强
half diff = NdotL * 0.5 + 0.5;
//从纹理中定义渐变效果,官方的例子中是float2(diff)这样写的,很明显报错了
half3 ramp = tex2D (_Ramp, float2(diff,diff)).rgb;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
c.a = s.Alpha;
return c;
}
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o)
{
//从主纹理获取颜色值
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
没有效果图,如果大家想找模型看看效果的话,asset store是个好地方,有很多免费的模型可以用,但一定要找有对应贴图的
step4
**简单高光**specular lighting model
Shader"Example / AutisticPatient SurfaceShader001"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//渐变纹理,初始值为gray
_Ramp("Ramp",2D) = "gray"{}
}
SubShader
{
//子着色器标签
Tags { "RenderType" = "Opaque" }
CGPROGRAM
//定义光照模型
#pragma surface surf SimpleSpecular
//实现自定义光照模型
half4 LightingSimpleSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
//normalize是来自CG语言中的函数,作用是归一化向量。
half3 h = normalize (lightDir + viewDir);
//点积后取最大值,当法线方向和灯光方向一致时,它们的点积结果达到最大值1,最亮
//当两者方向垂直时,达到0,大于90度将为负值,max函数的作用便是保证光强不为负
half diff = max (0, dot (s.Normal, lightDir));
float nh = max (0, dot (s.Normal, h));
//nh的 48次方
float spec = pow (nh, 48.0);
half4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
c.a = s.Alpha;
return c;
}
struct Input
{
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o)
{
//从主纹理获取颜色值
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
这里面有些数学计算我没注释,大家可以改动其中的一些值,在unity 中进行编译查看效果,就会明白数学计算的作用
好了,这节就到这吧