unity 3d物体描边效果_[Unity+shader]重现战场女武神系列的手绘风格(下)

9bece45dab3cef655e1f4d273af249f0.png

在前两篇中,我们完成了 基于纹理的边缘检测

以及 水彩纸边缘效果 的制作,接下来就是最后一道工序,为图像的暗部加阴影线


添加阴影线同样有基于纹理和基于模型的几种不同方法,实际可以根据喜好和需求选择。

首先在properties中添加一些用得上的元素:阴影纹理、添加阴影的阈值,以及未来让效果调节起来容易一些,增加一个阴影强度的参数。

		_MainTexB ("Sketch Texture", 2D) = "black" {}

		_ShadowThreshold("Threshold",Range(0, 1))=0
		_ShadowS("Strength",Range(0, 1))=0

基于纹理的方法很简单,直接使用之前计算亮度的函数,并直接将其与ShadowThreshold进行比较,就可以得到一个比较生硬的效果。

	fixed4 frag(v2f i) : SV_Target {
		fixed4 texMain=tex2D(_MainTex, i.uvTA[0]);
		fixed4 texBack=tex2D(_MainTexB, i.uvTB);

		if(sh>_ShadowThreshold){
			return texMain;
		}else{
			return texBack;
		}
	}

f0532da0bd724f4d12b24bc44fb667ab.gif
这个结果好像和想象的不一样呢

以上就是通过某个纹理来控制一张图是否需要显示的方法,市面上的很多消融效果使用的就是同样的原理。但是这样简单粗暴的方法显然是达不到一个好看的效果的,于是我们做一些修改。任务有二,第一要让阴影融合更平滑,第二点是要阴影的叠加更加自然。

			fixed4 sum=lerp (texMain,texBack,saturate(_ShadowThreshold-luminance(texMain)) );	
			return sum;

把刚才的简单判断替换成上一章使用的方法,果然自然了一些。不过虽然效果看起来可以,但是实际并不正确。通过上面的式子计算下来,直到在threshold到达1的时候,亮度为0的部分才是完全显示阴影也纹理的,虽然可能也没什么必要。

于是这里换一种计算方法,首先定义 一个property来表示柔化边缘的宽度。

  _SoftEdge ("Soft Edge length", Range(0, 1)) = 1

之后,在亮度低于threshold的部分,引入softedge的宽度进行计算。

				fixed soft=saturate((_ShadowThreshold-sh)/_SoftEdge) ;
				sh=step(sh,_ShadowThreshold)*soft;
				fixed4 sum=lerp(texMain,texBack,sh);		
				return sum;

看看效果,果然好了很多吧。

d39f88ecce48925387a0d524e033dfc8.gif

接下来解决第二点的问题。

在上面的内容中,我们实际上做到了使用透明度进行融合,这种简单的融合效果在制作两张图片的切换时是比较合适的,但是用作阴影还是不够。

如果有画图或使用图形处理工具的经验的话,应该对图层的混合模式不太陌生,例如常见的柔光、正片叠底、叠加等等。

关于 混合模式 ,在知乎上就能搜到很多对其介绍的文章。

关于 混合模式的算法 ,能搜索到的文章也有很多。

由于这是一篇用于激发人们学习兴趣的文章,所以这里就以简单相加或相乘为例。

先增加一个表示阴影整体强度的property,和一个表示两种混合方式的keyword。

		_ShadowStrength("Strength",Range(0, 1))=0
		[KeywordEnum(multiply, lighten)] _TYPE ("Type", Float) = 0

接着在frag中做一点修改

   	fixed4 frag(v2f i) : SV_Target {
		fixed4 texMain=tex2D(_MainTex, i.uvTA[0]);
		fixed4 texBack=tex2D(_MainTexB, i.uvTB);
		fixed sh=luminance(texMain);
				
		//fixed soft=_SoftEdge>0? clamp(_ShadowThreshold-sh,0,_SoftEdge)/_SoftEdge : 1 ;
		fixed soft=saturate((_ShadowThreshold-sh)/_SoftEdge) ;
		sh=step(sh,_ShadowThreshold)*soft;
		fixed4 sum=texMain;
	
		#if _TYPE_MULTIPLY
			//Multiply
			fixed4 Csketch=texMain*lerp(1,texBack,_ShadowStrength);
			sum=lerp(texMain,Csketch,sh);
		#endif

		#if _TYPE_LIGHTEN
			//Lighten
			fixed4 Csketch=max( texMain,lerp(0,texBack,_ShadowStrength));
			sum=lerp(texMain,Csketch,sh);
		#endif
		return sum;
	}

并且不要忘了添加这一条来让两个keyword生效。

 			#pragma multi_compile _TYPE_LIGHTEN _TYPE_MULTIPLY

于是来看看效果

4381eec8cbdb8e1bf715356d74ba7e30.gif
使用相乘的方法时,阴影材质的暗部使原图片更加暗。使用相加的方法时,阴影材质的亮部使原图更亮了。

这样,一个基于纹理的暗部阴影效果就完成了。

如果把计算亮度的部分换成跟一个颜色的对比,再增加一个表示暗部颜色的property,甚至还可以将阴影叠加到特定颜色上哦。

				fixed4 xx =texMain-_Darkcolor;
				sh=dot(xx,xx);

659d665dc7abffb52d7d119d0afe664c.gif
这里用于计算相似度的sh使用了两个颜色的差的rgb三个分量的平方和,实际使用的时候还有使用绝对值等等的多种方法

基于纹理的方法虽然看起来效果比较细腻,但它只能贴在物体上或挂在摄像机上,识别出的暗部也只是颜色比较深的部分。那么如何让它能够找出真正的阴影部分呢,这就需要使用基于模型的方法。

由于不再需要通过颜色计算描边,在结构体v2f中已经不需要再保存周围点的uv坐标了,于是我们把uvTA只保留一个。同时,由于是基于模型来添加阴影线,阴影的强度是可以在vert函数中完成的,添加一个sketchWeights,用来将阴影线的强度传递到下一工序中。当然,这个数值存到其中某个uv的z通道里也没问题。

			struct v2f {
				float4 pos : SV_POSITION;
				fixed2 uvTB:TEXCOORD0;
				fixed2 uvTA[1] : TEXCOORD2;
				fixed sketchWeights : TEXCOORD3;
			};

在vert函数中,计算完主要纹理和阴影纹理的uv之后,就可以开始进行阴影的计算了。

			v2f vert(appdata_full v) {
				v2f o;
				//o.pos = UnityObjectToClipPos(v.vertex);//mul(UNITY_MATRIX_MVP, v.vertex);
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uvTB=(v.texcoord-_MainTexB_ST.zw)*_MainTexB_ST.xy ;
				half2 uvTA = (v.texcoord-_MainTex_ST.zw)*_MainTex_ST.xy ;
				o.uvTA[0] = uvTA;// + _MainTex_TexelSize.xy * half2(0, 0)  * size;
				//……
				return o;
			}

首先使用WorldSpaceLightDir获取当前顶点到光源的方向,接着使用UnityObjectToWorldNormal将当前的法线从模型空间转换为世界空间。然后使用点乘的方法计算光源和法线这两个向量之间的夹角。

				fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
				fixed lightDifference =dot(worldLightDir, worldNormal); //from -1 to 1

两个normalize过的向量点乘,得出的结果是在-1到1之间,那么我们只要把它映射到0和1之间,后面的计算就可以和之前的一样了。

				lightDifference=(lightDifference+1)/2; //from 0 to 1
				fixed soft=saturate((_ShadowThreshold-lightDifference)/_SoftEdge) ;
				lightDifference=step(lightDifference,_ShadowThreshold)*soft;
				o.sketchWeights=saturate(lightDifference);

a66bdbebd14063bff61035b1b072bd85.gif
基于模型的暗部阴影线就完成了

接下来加上描边部分。

我们使用将顶点沿着法线方向外扩的方法来实现描边的效果,这一步骤需要新增加一个pass来完成。

			v2f vertOutline (appdata_base v) {
				v2f o;		
				float4 offset = float4(v.normal, 0) * _OutlineSize;
				o.pos  = mul(UNITY_MATRIX_MVP, v.vertex+offset); 									
				return o;
			}
			
			float4 fragOutline(v2f i) : SV_Target { 
				return _OutlineColor;         
			}

增加一对vert和frag函数来供负责描边的新pass使用,在vert中,使用顶点的法线方向计算出描边外扩的偏移量,再与原本的顶点位置相加得出偏移后的位置,之后在frag函数中直接绘制设定好的描边颜色。

这个时候,可能会弹出一条说vert函数没有完全初始化的warning,不过众所周知大家都不在乎warning所以放着不管就好了。那么再加一个新的结构体,只保留描边用frag函数需要的顶点位置就解决了~

 			struct v2foutline {
			    float4 pos : SV_POSITION;
			};

最后,添加新的pass。

		Pass {
			Cull Front
			Blend SrcAlpha OneMinusSrcAlpha
			
			CGPROGRAM			
			#pragma vertex vertOutline
			#pragma fragment fragOutline
			ENDCG
		}

制作描边的时候,大家通常会使用Cull Front把正面剔除,不过这里对效果的的影响并不大,要不要剔除或者怎么剔除都可以随需求来决定。

看一下成果。

af2691035b1e1d424cdca3b9fbdcaf9c.gif

方块的描边似乎断了,没有办法,这是unity提供的默认方块实际上是由6个独立的平面构成所导致的。也正是因为这一点,才让构成每个平面的4个顶点的法线方向能够一致。

765f1cdd405c140547f7d5faede38257.png
直接将顶点位置按法线方向偏移,就可以看出,方块相邻的三个面并没有共用顶点。

此外,添加一条blend命令来控制描边的混合方式,这里选择了通过透明度混合的方法,这样就可以调节描边的浓度了。

cf250dcfe72d4c42d2f578b4b5d75d8a.gif
试试使用相加、变暗等多种方式,就可以看到好多不同的效果哦

好了,重现女武神系列的canvas效果系列文章到此结束,希望能给大家带来一些启发哦。

0a0ff62b9bd0225fab9fca47e1590d1f.gif

这一章中使用到的资源仍然在这里获取:

sakuraplus/make-terrain-with-google-elevation​github.com

获取更多特效,Buy 松鼠 a coffee(由于不太常用,只在插件中作为一个例子收录,跟卡比这篇一样):

slideshow effect​assetstore.unity.com

如果有其他感兴趣的特效,大家一定要告诉松鼠哦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值