这篇博客记录一下常用的几种效果:内发光、外发光以及描边的几种实现方法
一般实现方法就是使用Empricial菲涅尔近似公式来实现:
F (v,n)=saturate(base + pow(scale * (1 - v·n), power))
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
Shader "Custom/Effect/Shine_Out"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
_ShineColor ("Shine Color", Color) = (1,1,1,1)
_ShineScale ("Shine Scale", Range(0, 1)) = 0.5
_ShinePower ("Shine Power", Range(0, 1)) = 0.5
}
SubShader
{
Tags {
"Queue"="Transparent" }
LOD 200
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
half4 _Color;
half4 _ShineColor;
float _ShineScale;
float _ShinePower;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half3 normal = normalize(i.normal);
half3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
half3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 col = fixed4(0,0,0,1);
col.rgb = _LightColor0.rgb * _Color.rgb * (dot(lightDir, normal)*0.5+0.5);
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
return col;
}
ENDCG
}
}
}
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
shine = step(_Threshold, shine);
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
这种方法比较常用,用两个Pass实现,一个Pass只渲染背面,且把物体背面沿法线方向向外扩张,第二个Pass正常渲染物体。
v2f vert (appdata v)
{
v2f o;
float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);
viewNormal.z = -0.5;//防止内凹模型背面扩张后遮挡正面
float3 normal = normalize(viewNormal);
viewPos += float4(normal, 1.0) * _OutlineWidth;
o.pos = mul(UNITY_MATRIX_P, viewPos);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
优点
缺点
全部代码如下
Shader "Custom/Effect/Outline"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
[HDR]_OutlineColor ("Outline Color", Color) = (1,1,1,1)
_OutlineWidth ("Outline Width", Range(0, 2)) = 1
}
SubShader
{
Tags {
"Queue"="Transparent" }
LOD 200
Pass
{
Cull Front
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
};
half4 _OutlineColor;
float _OutlineWidth;
v2f vert (appdata v)
{
v2f o;
float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);
viewNormal.z = -0.5;
float3 normal = normalize(viewNormal);
viewPos += float4(normal, 1.0) * _OutlineWidth;
o.pos = mul(UNITY_MATRIX_P, viewPos);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 pos : SV_POSITION;
};
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _Color;
return col;
}
ENDCG
}
}
}
基于后处理图像处理的边缘检测方法:常用的一般是Sobel算子和Roberts算子,边缘处的梯度绝对值会较大,也就是计算每个像素的梯度,并用梯度对初始颜色和描边颜色进行插值,也就完成了描边的操作。
half Sobel(v2f i)
{
const half Gx[9] = {
-1,-2,-1,
0, 0, 0,
1, 2, 1 };
const half Gy[9] = {
-1, 0, 1,
-2, 0, 2,
-1, 0, 1 };
half texColor;
half edgeX = 0;
half edgeY = 0;
for (int it = 0; it < 9; it++)
{
texColor = luminance(tex2D(_MainTex, i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
half edge = lerp(0, abs(edgeX) + abs(edgeY), _EdgeWidth);
return edge;
}
优点
缺点
全部代码如下
Shader "Custom/Postprocess/EdgeDetection"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {
}
_EdgeColor ("EdgeColor", Color) = (1,0,0,1)
_BackgroundColor ("BackgroundColor", Color) = (1,1,1,1)
_EdgeOnly ("Edge Only", Range(0,1)) = 0.5
_EdgeWidth ("Edge Width", Range(0,1)) = 1
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
half2 uv[9] : TEXCOORD0;
};
sampler2D _MainTex;
float2 _MainTex_TexelSize;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
fixed _EdgeOnly;
fixed _EdgeWidth;
v2f vert (appdata_img v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y *-1);
o.uv[1] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y *-1);
o.uv[2] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y *-1);
o.uv[3] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y * 0);
o.uv[4] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y * 0);
o.uv[5] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y * 0);
o.uv[6] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y * 1);
o.uv[7] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y * 1);
o.uv[8] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y * 1);
return o;
}
fixed luminance(fixed4 color)
{
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
half Sobel(v2f i)
{
const half Gx[9] = {
-1,-2,-1,
0, 0, 0,
1, 2, 1 };
const half Gy[9] = {
-1, 0, 1,
-2, 0, 2,
-1, 0, 1 };
half texColor;
half edgeX = 0;
half edgeY = 0;
for (int it = 0; it < 9; it++)
{
texColor = luminance(tex2D(_MainTex, i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
half edge = lerp(0, abs(edgeX) + abs(edgeY), _EdgeWidth);
return edge;
}
fixed4 frag (v2f i) : SV_Target
{
half edge = Sobel(i);
fixed4 withEdgeColor = lerp(tex2D(_MainTex, i.uv[4]), _EdgeColor, edge);
fixed4 onlyEdgeColor = lerp(_BackgroundColor, _EdgeColor, edge);
half4 col = lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
return col;
}
ENDCG
}
}
}
在上文的沿法线外扩背面实现的描边方法基础上,给背面的alpha像内发光一样,乘以视角与法线点积。不过要稍作改动,让背面从中心向外逐渐透明,模拟光晕的效果。
float ndotv = max(pow(-dot(normal, viewDir),_OutlineFeather), 0.001);
fixed4 col = _OutlineColor;
col.a *= ndotv;
注意:
全部代码如下
Shader "Custom/Effect/Shine_In"
{
Properties
{
_Color ("Main Color", Color) = (1,1,1,1)
[HDR]_ShineColor ("Shine Color", Color) = (1,1,1,1)
_ShineWidth ("Shine Width", Range(0, 2)) = 1
_ShineFeather ("Shine Feather", Range(0, 10)) = 0
}
SubShader
{
Tags {
"Queue"="Transparent" }
LOD 200
Pass
{
Cull Front
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 normal : TEXCOORD1;
};
half4 _ShineColor;
float _ShineWidth;
float _ShineFeather;
v2f vert (appdata v)
{
v2f o;
float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);
viewNormal.z = -0.5;
float3 normal = normalize(viewNormal);
viewPos += float4(normal, 1.0) * _ShineWidth;
o.pos = mul(UNITY_MATRIX_P, viewPos);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 normal = normalize(i.normal);
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
float ndotv = max(pow(-dot(normal, viewDir),_ShineFeather), 0.001);
fixed4 col = _ShineColor;
col.a *= ndotv;
return col;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 pos : SV_POSITION;
};
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _Color;
return col;
}
ENDCG
}
}
}