使用菲涅尔公式实现边缘光
边缘光的实现参考菲涅尔公式,Empricial 菲涅耳近似等式:
v 是视角方向, n 是表面法线,bias, scale 和 power 是控制项
对上面的公式略作修改得到边缘光的计算公式
RimLight = (1.0 - dot(v, n))^power * intensity * RimColor
在物体边缘,视角方向和法线方向是垂直的,两者点积为0,(1.0 - dot(v, n)) 就使得靠近边缘处数值趋近于1,RimColor是边缘光的颜色,power可以理解为中心到边缘颜色过渡的快慢,intensity是边缘光颜色的强度。
下面是边缘光的shader,这里为了简单,只考虑边缘光,实际应该叠加模型贴图颜色,漫反射,环境光等。
Shader "MyCustom/RimLight"
{
Properties
{
_RimLightColor ("_RimLightColor", Color) = (1, 1, 1, 1)
_RimLightPower ("_RimLightPower", Range(0, 10)) = 1
_RimLightIntensity ("_RimLightIntensity", Range(0, 10)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _RimLightColor;
float _RimLightPower;
float _RimLightIntensity;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldView : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//法线转换到世界空间
//UnityObjectToWorldNormal支持物体的非均匀缩放,即scale不一致
o.worldNormal = UnityObjectToWorldNormal(v.normal);
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
//世界空间中视角方向
o.worldView = normalize(UnityWorldSpaceViewDir(worldPos));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//点乘返回-1到1的值,用max截取0到1的值
float nv = max(0, dot(i.worldNormal, i.worldView));
//套用上面公式
float3 rimLight = pow(1 - nv, _RimLightPower) * _RimLightIntensity * _RimLightColor.rgb;
return float4(rimLight, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
使用贴图实现边缘光
通过贴图可以设置物体两侧边缘光的颜色,模拟在两侧打光的效果,更换贴图就可以实现不同效果,方便美术控制,上面左侧是采样的贴图。
原理是将世界空间中法线的x,y分量作为贴图的uv坐标对贴图进行采样,对上面的shader进行简单的修改就可以实现。
Shader "MyCustom/FakeRimLight"
{
Properties
{
_FakeRimLightTex ("_FakeRimLightTex", 2D) = "white" {}
_RimLightColor ("_RimLightColor", Color) = (1, 1, 1, 1)
_RimLightPower ("_RimLightPower", Range(0, 10)) = 1
_RimLightIntensity ("_RimLightIntensity", Range(0, 10)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _RimLightColor;
float _RimLightPower;
float _RimLightIntensity;
sampler2D _FakeRimLightTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float3 worldView : TEXCOORD2;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
o.worldView = normalize(UnityWorldSpaceViewDir(worldPos));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//点乘返回-1到1的值,用max截取0到1的值
float nv = max(0, dot(i.worldNormal, i.worldView));
float3 rimLight = pow(1 - nv, _RimLightPower) * _RimLightIntensity * _RimLightColor.rgb;
//将worldNormal的xy分量作为贴图的uv坐标,并从-1到1转到0到1
float u = i.worldNormal.x * 0.5 + 0.5;
float v = i.worldNormal.y * 0.5 + 0.5;
float3 fakeRimLight = tex2D(_FakeRimLightTex, float2(u, v)).rgb;
float3 finalColor = fakeRimLight * rimLight;
return float4(finalColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}