Rim 模型,即边缘高光模型,其在游戏里的应用比较广泛。本节主要实现为其抽象出一个shader模型,并阐明原理,并附上简单应用案例。
一、Rim长啥样
先上几个图,了解一下这个Rim到底是什么样的。
火炬之光里骷髅怪被建筑物遮挡时呈现出的X光效果
boss进入某些特殊状态时的效果
某些场景为了凸显某些东西时的效果
以上这些都是Rim模型的功劳。那么下面先说说其实现的原理。
二、Rim原理及其模型公式
观察上面的图片,会发现他们都有些共同点就是模型的边缘呈现出了特殊的颜色。那么这是如何做到的??
其实上图就已经阐明了Rim的实现原理。
设我们的视线方向与法线方向的夹角 α(α ∈ [0°,90°]),当 α
的值越小,边缘光越弱。
换言之就是:
当我们的视线与法线垂直时(α = 90°),边缘光最强;
当视线与法线重合时(α = 0°),边缘最弱。
公式:
rim = 1 - max(0,dot( v → , n → \overrightarrow{v},\overrightarrow{n} v,n))
另外大家都喜欢再添加一个变量来控制一下这个rim的值,比如叫 rimPower,还是用的指数函数,所以最后 rim 其实是等于:
rim = pow( 1 - max(0,dot( v → , n → \overrightarrow{v},\overrightarrow{n} v,n),rimPower)
三、应用
以下代码仅仅是为了验证上诉理论,并不能真正用于项目中,看官们酌情取之!!
1、实现这种类型的
shader:
Shader "Unlit/SimpleRim"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_RimColor("RimColor",Color) = (0.0,0.0,0.0,0.0)
_RimPow("RimPow",Range(0,5)) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _RimColor;
float _RimPow;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(v.vertex));
fixed rim = (1 - saturate(dot(worldNormal,worldViewDir)));
fixed4 rimColor = _RimColor * pow(rim,_RimPow);
o.color = rimColor;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
UNITY_APPLY_FOG(i.fogCoord, col);
return col + i.color;
}
ENDCG
}
}
}
效果:
2、实现这种类型的
这种会稍微复杂些,其实懂得原理了,也简单,只是会多一个步骤。
先简单说下原理:
这个例子为了得到比较好的效果,一般都会开启混合(开启混合就意味着要关闭深度写入,如果这个看不太明白的童鞋需要去补补基础知识了,本文不解释。),所以性能也会低一些。
1、先用一个开了混合的pass单独渲染Rim的效果,为了保证在所有不透明物体之后渲染(场景的物体),改pass的QUEUE 应该在 Gemetry (2000) 和 Transparent(3000)之间。这样我们深度测试的操作自然就是选择 Greater。
2、再用一个pass去正常渲染(包括纹理采样,高光,等等效果)模型就行了。
代码:
Shader "Unlit/XRay"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_RimColor("Rim Color",Color) = (1.0,1.0,1.0,1.0)
_RimPow("Rim Power",float) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"Queue" = "Gemetry + 500"}
Blend SrcAlpha One
ZWrite off
ZTest Greater
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
};
fixed4 _RimColor;
float _RimPow;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(v.vertex));
fixed rim = (1 - saturate(dot(worldNormal,worldViewDir)));
fixed4 rimColor = _RimColor * pow(rim,_RimPow);
o.color = rimColor;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
效果: