unity实现描边的问题汇总

在游戏中,我们都喜欢加一些描边效果,来凸显人物的边缘,提高识别度。美术一般都喜欢加。描边方式一般有两种,一种的模型边缘描边,一种的人物的转折点描边(这种需要用到卷轴)

在游戏中比较常用的就是模型边缘描边了,shader一般是这样

Shader "Outline"
{
	//属性
	Properties{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
		_OutlineCol("OutlineCol", Color) = (1,0,0,1)
		_OutlineFactor("OutlineFactor", Range(0,1)) = 0.1
		_MainTex("Base 2D", 2D) = "white"{}
	}
 
	//子着色器	
	SubShader
	{
		
		//描边使用两个Pass,第一个pass沿法线挤出一点,只输出描边的颜色
		Pass
		{
			//剔除正面,只渲染背面,对于大多数模型适用,不过如果需要背面的,就有问题了
			Cull Front
			
			CGPROGRAM
			#include "UnityCG.cginc"
			fixed4 _OutlineCol;
			float _OutlineFactor;
			
			struct v2f
			{
				float4 pos : SV_POSITION;
			};
			
			v2f vert(appdata_full v)
			{
				v2f o;
				//在vertex阶段,每个顶点按照法线的方向偏移一部分,不过这种会造成近大远小的透视问题
				//v.vertex.xyz += v.normal * _OutlineFactor;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				//将法线方向转换到视空间
				float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
				//将视空间法线xy坐标转化到投影空间,只有xy需要,z深度不需要了
				float2 offset = TransformViewToProjection(vnormal.xy);
				//在最终投影阶段输出进行偏移操作
				o.pos.xy += offset * _OutlineFactor;
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target
			{
				//这个Pass直接输出描边颜色
				return _OutlineCol;
			}
			
			//使用vert函数和frag函数
			#pragma vertex vert
			#pragma fragment frag
			ENDCG
		}
		
		//正常着色的Pass
		Pass
		{
			...
		}
	}
	//前面的Shader失效的话,使用默认的Diffuse
	FallBack "Diffuse"
}

第一个问题:但是,如果这样放到游戏中回有一定的问题

可以看到,一些边缘的地方其实没显示边缘色,导致看起来是断层的。

原因是我们剔除了前面,朝向前方的定点被剔除,所以显示不完整。然后我设置为后,

就比较完美了。然后我一位这样就解决了。

第二个问题:但是后面在主场景中发现一个问题可以看到角色整个黑掉了,开始找原因,首先找到的是,人物身体的mesh多了一份,然后把他移除掉一份,其实还是不行。然后因为我们用了两个pass,两个pass都是transparent的,但在frame debug中看渲染顺序也是正常的,说明和渲染顺序无关。

再后来,发现出现这个问题的情景时两个人物叠加在一起。

由于渲染了front,两个英雄的向前的点会重叠(因为渲染完一个人物的两个pass,再到另一个人物的)

然后我想到的解决办法时,加一个模板,但大于等于0的点就设置为Zero。最终解决了这个问题。

可以看到没有显示黑的一团。

第三个问题:在渲染3d和2d结合的界面,如果我们直接渲染3d(而不是用rt来间接实现)的话会出现看不到描边的问题。这个的原因就是因为ui是会在3d模型之后渲染的,然后我们描边是不回写深度的所以会看不到描边(因为由于没写深度,描边在ui后面)。

因为我们游戏用的是ngui,用uitexture来显示背景,所以会按照ui的方式显示在前面。所以我用了一个quad去实现背景,并且把渲染顺序改为geometry,这样就会显示在后面。

至此就解决了描边的遇到的问题了。

当然很多人可能会说这个挡板也会使描边显示不出来。

对,是会显示不出来,因为他也是ui。但是要根据实际情况并且消耗情况,制作复杂情况来决定。这个挡板没描边是可以接受的,所以我们用一个比较简单消耗低的方式实现。

当然如果你要完全显示描边,那就可以把rt放到camera的target上,然后用uitexture或quad去装载这个rt,就能完全实现,但这样会对一个相机,并且每帧都要去做这样的操作。具体需要自己权衡。

注意:

1。描边的对象菱角最好不要多,因为描边是通过法线拓开面的方式做的,所以面会往法线上移,所以菱角多可能会出现有些地方空了

2.计算过程如果一方归一化后另一方也要归一化,不然计算会涉及一些不相关的部位。

data.NdotV.x = saturate(dot(normalize(v.normal), normalize(V)));

3.其实这里光照计算不需要用到真实光照,只需要知道一个光照方向就好了,模拟出真实光照感觉。因为如果用真实光,如果你每个场景人物的旋转角度不一致,那么看上去是不一致的,那么这时要调整光照。那么调整光照方向就是最直接的。

float3 lightDirNorm = normalize(_LightDir.xyz);
data.lightDir = lightDirNorm;

之后只需要用这个光照方向去计算边缘光,暗部渲染,高光等就好了,注意如果用到matcap,matcao的高亮部位要跟这个方向一致。

4.做matcap的时候我们会发现摄像机越远他会出现越多的不相关暗部。

这里是因为摄像机角度渲染时mask图涉及的区域可能会超过范围,这里渲染的就会超过一点影响到贴图的其他部位。那么要减少这个影响,就应该根据摄像机的远近来处理uv。减少影响。

(step(uvx1, s.uv_main.x) * step(s.uv_main.x, uvx2)) > 0 ? (_UseMatCap > 0 ? ((mask.a != 0) ? float3(c.rgb * matMaskACapColor * 1.5) : float3(c.rgb * matCapColor * _MatCapColor * 1.5)) : c.rgb) : c.rgb;

(step(uvx1, s.uv_main.x) * step(s.uv_main.x, uvx2))是说uv的x限制在uvx1和uvx2之间。

当超过了这个像素 就用回c.rgb。其他部分都是matcap部分,就不细说了。

猜你喜欢

转载自blog.csdn.net/llsansun/article/details/83744775#comments_24938542