Unity描边[Unity Shader]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cubesky/article/details/38588723

原理:给物体设置自发光,远离边缘的地方自发光减弱,这样看上去,物体的边缘就会有一个自发光的颜色,从而起到一个描边的效果

那么,这里有一个问题,我们怎么来判断什么地方是边缘呢?

首先让我们来看一下示意图


我们可以看出,面向摄像头的物体一面,物体表面法线和视角向量的夹角逐渐增大(从0到90度),那么我们可以根据这个夹角(设为A)来进行处理,比如说夹角越大,则发射光越强。

这里我们使用shader来处理,因为我们是要对物体的表面各个点做处理,所以使用surface shader是一个不错的选择。

假如我们设定描边的颜色为_RimColor,对于不同的点,我们给它乘以一个衰减系数fact(范围0-1),那么我们可以用o.Emission = _RimColor.rgb *(1- fact)来设置所有点的自发光。然后我们要做的就是,要使得越远离边缘,fact越大。通过前面分析,我们知道,越远离边缘,夹角越小,那么我们怎么由前面的夹角A来得到fact呢?

因为我们的夹角A的取值范围为0-90度,我们的fact取值为0-1.所以我们可以对角度A做一个变换,使其变换到0-1的范围,比如说我们可以取余弦。这里我们可以直接根据已知的normal和viewDir两个向量求点积,得到一个0-1的值。fact = dot (normalize(IN.viewDir), o.Normal)(先对viewDir做归一化处理,使得取值在0-1之间)。这里考虑到viewDIr和normal之间的夹角可能大于90度(如背面的点),所以得到的fact可能为负,所以我们再处理一下,fact = saturate(fact)(其中saturate相当于mathf.clamp(value,0,1)).

现在fact = saturate(dot (normalize(IN.viewDir), o.Normal))。

现在我们可以看一下效果


已经很不错了,现在我们可以看到越靠近边缘,描边颜色越明显了。但是现在还不是特别让人满意,因为远离边缘的地方也上上了描边的颜色。这是因为现在1-fact基本上是接近线性衰减的。我们可以对1-fact在做一下处理,使其在fact从0-1的变化过程中(也就是逐渐趋向边缘),刚开始的一断缓慢增加,使其保持在0左右,然后再即将趋向边缘的时候,迅速增长到1.这正与数学中的幂函数的变化趋势一致,我们设定幂函数y=xa的a设定为_RimPower,所以我们可以得到:o.Emission = _RimColor.rgb * pow((1- fact),_RimPower). 再让我们来看看最终的效果。(我们可以通过调节_RimPower来控制衰减的速度)。


好了,文章就写到这了,大家如果对效果还不太满意,可以自己扩展一下。最后,附上shader的代码

Shader "Example/Rim" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
      _RimColor ("Rim Color", Color) = (0.26,0.19,0.16,0.0)
      _RimPower ("Rim Power", Range(0.5,8.0)) = 3.0
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex;
          float2 uv_BumpMap;
          float3 viewDir;
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      float4 _RimColor;
      float _RimPower;
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
          half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
          o.Emission = _RimColor.rgb * pow (rim, _RimPower);
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

扩展:其实上面的shader来自官方的一个example,本文只是希望能通过这样的一种描述方式,让大家更能明白其中的原理以及碰到问题应该如果去解决。上面的这种方法,也存在一定的局限,它主要是对比较规则的曲面,如果是对于平面或者是方体,则需要做一些处理,因为平面或者方体因为他们表面并不满足上面所说的夹角的变化趋势。如果对于立方体,上面的shader效果如下:


可以看出,基本上没有任何的描边效果,这里我提供一个效果比较好的描边的shader,大家如果有需要可以参考一下

Shader "Outlined/Diffuse" {
	Properties {
		_Color ("Main Color", Color) = (.5,.5,.5,1)
		_OutlineColor ("Outline Color", Color) = (0,0,0,1)
		_Outline ("Outline width", Range (.002, 0.03)) = .005
		_MainTex ("Base (RGB)", 2D) = "white" { }
	}
 
CGINCLUDE
#include "UnityCG.cginc"
 
struct appdata {
	float4 vertex : POSITION;
	float3 normal : NORMAL;
};
 
struct v2f {
	float4 pos : POSITION;
	float4 color : COLOR;
};
 
uniform float _Outline;
uniform float4 _OutlineColor;
 
v2f vert(appdata v) {
	// just make a copy of incoming vertex data but scaled according to normal direction
	v2f o;
	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
 
	float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
	float2 offset = TransformViewToProjection(norm.xy);
 
	o.pos.xy += offset * o.pos.z * _Outline;
	o.color = _OutlineColor;
	return o;
}
ENDCG
 
	SubShader {
		//Tags {"Queue" = "Geometry+100" }
CGPROGRAM
#pragma surface surf Lambert
 
sampler2D _MainTex;
fixed4 _Color;
 
struct Input {
	float2 uv_MainTex;
};
 
void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
}
ENDCG
 
		// note that a vertex shader is specified here but its using the one above
		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			ZWrite On
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha
			//Offset 50,50
 
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			half4 frag(v2f i) :COLOR { return i.color; }
			ENDCG
		}
	}
 
	SubShader {
CGPROGRAM
#pragma surface surf Lambert
 
sampler2D _MainTex;
fixed4 _Color;
 
struct Input {
	float2 uv_MainTex;
};
 
void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
}
ENDCG
 
		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			ZWrite On
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha
 
			CGPROGRAM
			#pragma vertex vert
			#pragma exclude_renderers gles xbox360 ps3
			ENDCG
			SetTexture [_MainTex] { combine primary }
		}
	}
 
	Fallback "Diffuse"
}
然后我们来看一下效果


我们可以看到,不管是什么形状,基本上都能有一个比较OK的描边效果了。

参考:

http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

http://wiki.unity3d.com/index.php/Outlined_Diffuse_3

http://wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse

http://rbwhitaker.wikidot.com/toon-shader

http://en.wikipedia.org/wiki/Sobel_operator

http://unitygems.com/noobs-guide-shaders-6-toon-shader/


写在最后:发现自己确实不太善于文字,写博客真的比写代码更累。原来发博文的时候,习惯只附上自己能懂的效果图和代码就完事了。现在为了方便他人阅读,尝试解释一下其他的原理。不过由于文字水平有限,还请大家谅解。当然,以上纯属自己理解,如果有误的地方,还请指正。

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页