Unity Shader-Decal贴花(SelfDecal,Alpha Blend,Mesh Decal,Projector,Deferred Decal)

前言

最近通关了《What Remains of Edith Finch》(艾迪芬奇的记忆),总体来说应该算是一个剧情+解密向的游戏,故事+表现手法十分出色。

游戏主要是叙述一个神秘的家族遭遇了一系列类似《死神来了》的故事,家族的人离奇死亡,每一个成员的故事都是不同的游戏模式和表现风格,比如下面的连环画小游戏的卡通渲染风格,游戏中的书,书中的游戏:

虽然我在入手之前还曾经犹豫过,看到大多数玩家的游戏时长才两个多小时,敢卖六十八块大洋,不过这两个小时玩下来,整体给我的视觉+感受冲击一波高过一波,到最后的罐头厂工人和他的理想王国那一段给我的印象最为深刻,甚至有种《千与千寻》的那种天马行空的感觉,不得不说开发者的脑洞太给力啦。

恩,游戏体验记录到此结束,下面才是本文的正题。今天来玩一个好玩的效果,Decal。

简介

Decal(贴花)。这个效果其实非常常见,比如人物脚底的选中圈,还有最著名的就是CS里面的喷漆效果了,当年大战的时候没少和人互喷,要是说到CS(Quake),那这个技术真的是相当古老了。不过Decal的实现方式有很多种,今天来分别实现一下Self Decal,Additive贴片叠加,Projector投影,Mesh Decal,Deferred Decal(Depth Normal Decal)这几种方式。

Self Decal

我在网上查了半天,似乎也没有对这种贴花方式的命名,索性就叫它Self Decal吧。也可能是这种方式太捞(low)了,大佬们都不care了。所谓Self,也就是直接在同一个Shader里面在叠加一张贴图方式。可以直接采样uv,也可以将mesh制作第二套uv。相比于直接把贴图都做在Albedo中,使用Decal可以更加方便的替换贴图,而且二套uv方便控制特殊的效果,比如捏脸系统里面经常有的脑门上画个咒印之类的。

关于这种Decal,不做太多介绍了。Unity官方资源也包含一个名叫Decal的Surface Shader,我这里就直接简单粗暴地用Unlit类型的vf shader了。最后的叠加使用的是类似Alpha Blend的SrcAlpha OneMinusSrcAlpha,将Decal的颜色和原始颜色根据Decal的Alpha值进行插值:

/********************************************************************
 FileName: SelfDecal.shader
 Description: SelfDecal二套uv自身贴花效果
 history: 12:7:2018 by puppet_master
 https://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Decal/SelfDecal"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_DecalTex ("Decal", 2D) = "black" {}
	}
	
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float2 uv_decal : TEXCOORD1;
			};

			struct v2f
			{
				float4 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			sampler2D _DecalTex;
			float4 _DecalTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv.xy = v.uv;
				//offset,tiling二套uv调整不同的decal
				o.uv.zw = TRANSFORM_TEX(v.uv_decal, _DecalTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv.xy);
				fixed4 decal = tex2D(_DecalTex, i.uv.zw);
				
				col.rgb = lerp(col.rgb, decal.rgb, decal.a);

				return col;
			}
			ENDCG
		}
	}
}

这里我们直接用二套uv进行decal贴花,可以更加容易控制需要细节部分的贴花同时也不影响主纹理的效果。于是我掏出了使用十分不熟练的3DMax给我最近常用的小狮子展了二套uv,贴图的话就使用我最喜欢的羊驼表情包啦:

效果如下,在石狮子的胸前就贴上了羊驼的头像,2333:

由于我们通过TRANSFORM_TEX对二套uv进行了处理,所以我们可以很容易地通过二套uv的Tiling和Offset来修改贴花显示的内容,来一个羊驼表情幻灯片:

AlphaBlend贴片Decal

上面我们实现了自身Decal效果。但是也有很大的限制,比如我们希望在两个对象连接处叠加一个Decal,我们不方便给每个东西都额外去展uv,直接用世界空间或者物体空间坐标采样也不一定靠谱。其实我们可以考虑一种更简单的方法实现Decal,甚至不需要写代码。因为这个Shader实在太常见啦。可以说是最简单粗暴,而又很容易实现贴花细节的一种方法。

我们可以使用Particle Alpha Blended或者Mobile版本的Alpha Blended Shader,简单来说就是最基本的采样贴图输出的AlphaBlend的Shader就可以达到效果,比如下面的Shader:

/********************************************************************
 FileName: AlphaBlendedDecal.shader
 Description: AlphaBlend Shader
 history: 7:12:2018 by puppet_master
 https://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Decal/AlphaBlendedDecal"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		 Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
		
		Blend SrcAlpha OneMinusSrcAlpha
		Lighting Off
		ZWrite Off

		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);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}

这一次,我们就可以找一个面片,然后把我们的需要作为Decal的内容赋给面片,然后把面片贴在地面上或者墙上就可以得到效果啦。很多游戏场景中的地面,墙面细节,如特殊的污渍,血迹,如果不用SelfDecal或者地形刷混合权重的方式的话,直接叠上去一个面片也是一个不错的选择。比如下面我在地面上叠了两个血迹的Decal,即使在两个地砖接缝也可以使用:

Projector Decal

上面的两种Decal可以实现很多贴花的效果了,但是二者都有自身的局限。Self Decal可以贴合任意物体,但是需要物体自身展uv,无法实现一个Decal覆盖多个物体,仅适用于一些预制的贴花替换;而Alpha Blend叠片方式的Decal,可以任意摆放位置,也可以跨物体摆放,但是有一个很重要的问题,如果是平面,如墙面和地面,我们可以直接使用一个面片叠加,而如果是复杂物体之间的贴花效果,直接用面片叠加无法完美贴合,自己做一个贴合的模型也不现实。所以,是时候找一找普适性更高一些的贴花方法了(不过,上面两种贴花也很常用,毕竟如果用简单的方法就能达到效果,何必要浪费时间和性能呢)。

Unity内置的Projector是一个不错的选择。Projector可以做很多好玩的效果,不仅包括贴花,还可以实现阴影等效果。

Projector的使用

使用Projector,相当于Unity会对需要投影的对象使用投影材质重新渲染一遍。不过这个材质的Shader需要是特制的,而不是随便的Shader都可以得到正确的效果。因为我们需要在Shader里面根据投影器传入的Project矩阵进行uv计算,得到采样的uv值。我们可以参考Unity官方aras-p大佬的Projector Shader来编写我们自己的投影Shader,我将Blend模式改为了Blend SrcAlpha OneMinusSrcAlpha,然后直接输出颜色。Shader中采样并没有使用正常的uv坐标,而是通过两个Unity内置矩阵变换顶点,进而在fragment shader中采样,关于这两个内置矩阵的解释,可以参考这里。另外,我们可以通过FalloffTex实现在投影距离内的颜色渐变,通过采样一张falloff贴图,通过纹理的uv横向实现投影的衰减,使用Falloff有两个好处,一方面可以防止Projector背面物体也被投影,出现穿帮的问题;另一方面,通过远距离衰减可以得到更加自然的投影效果,而且也可以防止远处超出投影“视锥体”远裁剪面时出现的调变问题。投影的Shader代码如下:

/********************************************************************
 FileName: ProjectorDecal.shader
 Description: Projector Decal Shader
 history: 7:12:2018 by puppet_master
 https://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Decal/ProjectorDecal" 
{ 
	Properties 
	{
		_Color ("Main Color", Color) = (1,1,1,1)
		_DecalTex ("Cookie", 2D) = "" {}
		_FalloffTex ("FallOff", 2D) = "white" {}
	}

	Subshader 
	{
		Pass 
		{
			ZWrite Off
			Fog { Color (0, 0, 0) }
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha
			Offset -1, -1

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			struct v2f 
			{
				float4 uvDecal : TEXCOORD0;
				float4 uvFalloff : TEXCOORD1;
				float4 pos : SV_POSITION;
			};
			
			float4x4 unity_Projector;
			float4x4 unity_ProjectorClip;
			fixed4 _Color;
			sampler2D _DecalTex;
			sampler2D _FalloffTex;
			
			v2f vert (float4 vertex : POSITION)
			{
				v2f o;
				o.pos = UnityObjectToClipPos (vertex);
				o.uvDecal = mul (unity_Projector, vertex);
				o.uvFalloff = mul (unity_ProjectorClip, vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 decal = tex2Dproj (_DecalTex, UNITY_PROJ_COORD(i.uvDecal));
				decal *= _Color;

				fixed falloff = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff)).r;
				decal *= falloff;
				return decal;
			}
			
			ENDCG
		}
	}
}

这样,我们直接在场景中挂一个Projector组件,然后使用上面Shader的材质,将投影器对准我们需要投影的对象,就可以得到完美贴合多个不规则对象的投影效果了,如下图,血迹的效果完美贴合在了地面和石狮子上:

通过FrameDebugger我们可以看到,Projector的Decal是通过额外的Pass渲染的,上图的石狮子和地面都额外进行了一次渲染,所以Projector也是由一定的性能代价的,如果一个Projector覆盖太多的物体可能会导致批次上升:

Projector的原理

Unity内置的Projector基本已经可以满足我们的需求了,不过还是了解一下Projector的原理更好。注,这里本人暂时先不考虑剔除相关的内容,仅考虑Projector渲染本身的原理。

首先我们观察一下默认的Projector组件的各种参数,远裁剪面,近裁剪面,FOV,Orthographic等,很明显这几个参数跟Camera的参数是一致的,可以认为Projector实际上就是在Projector位置摆放一个相机不过并非使用这个相机渲染,还是使用原始的相机进行渲染,只是通过这个相机的视矩阵和投影矩阵对uv进行转化。我们可以自定义一个脚本实现这一步的转化:

/*************
  • 75
    点赞
  • 156
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值