Unity喷墨效果Shader实现

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

对于游戏中使用的类似喷墨效果,在射击类游戏中经常使用比如玩家射击的子弹会在墙上出现类似喷墨效果,效果如下所示:

Unity喷墨效果Shader实现_第1张图片

在默认情况下,屏幕的整个Alpha通道都是黑色的,直到玩家开始喷射油墨,才会使被油墨溅到区域的Alpha通道变为白色。然后图像效果就是其原有颜色与灰度进行混合。

    实现方式如下所示:

Unity喷墨效果Shader实现_第2张图片

从上图可以看出,使用Projector将喷漆绘制到物体表面并创建颜色遮罩。每个Projector都使用程序化动态生成,在子弹(空中飞行的白点)接触到某个表面时进行初始化。Projector带有一个盒式碰撞体,当子弹落在Projector上时,不会初始化新的Projector,而是让原先的Projector变大。这样漆量会变多,而场景中的Projector数量却保持不变。

 

默认情况下,Unity标准着色器会为所有不透明对象的Alpha通道写入1。所以下面使用自定义着色器来替换Unity标准着色器。新建一个标准表面着色器,将其表面函数替换为如下:

void surf(Input IN, inout SurfaceOutputStandard o)
{
     //Albedo 来自带颜色的纹理
     fixed4 c=tex2D(_MainTex, IN.uv_MainTex) * _Color;
     o.Albedo = c.rgb;
     o.Metallic = _Metallic;
     o.Smoothness = _Glossiness;
     o.Alpha = 0;  //只添加此行
}

下面这行很重要,用于避免Unity更改自定义的Alpha值。将#pragma那行代码改为如下:

CGPROGRAM
#pragma surface surf Standard fullforwardshadows keepalphs
注意,该技巧不可用于Unity中的延迟渲染管线,因为它重写了G-Buffer中的Alpha通道来存储遮罩数据。

当子弹撞击某个表面时就会在撞击处动态生成Unity Projector。这些Projector带有自定义材质与自定义着色器。材质纹理是一张带有Alpha通道喷溅形状图,本文示例使用的纹理如下图:

Unity喷墨效果Shader实现_第3张图片
注意,纹理导入设置中要将Wrap Mode设为“Clamp”而非“Repeat”。用于Projector材质的着色器从Unity提供的ProjectorLight修改而来,代码如下:

Shader "Projector/ProjectAlpha"
{
	Properties
	{
		_ShadowTex("Cookie", 2D) = "gray"{}
	}
	Subshader{
		Tags{"Queue" = "Transparent"}
		Pass
		{
			ZWrite Off
			Blend Zero One,One One
			Offset -1, -1
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fog
			#include "UnityCG.cginc"

			struct Input
			{
				float4 vertex : LagPosition;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 uvShadow : TEXCOORD0;
				UNITY_FOG_COORDS(2)
				float4 pos : SV_POSITION;
				fixed nv : COLOR0;
			};

			float4x4 unity_Projector;
			float4x4 unity_ProjectorClip;

			v2f vert(Input v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uvShadow = mul(unity_Projector, v.vertex);
				UNITY_TRANSFER_FOG(o, o.pos);
				float3 normView = normalize(float3(unity_Projector[2][0], unity_Projector[2][1], unity_Projector[2][2]));
				float nv = dot(v.normal, normView);
				o.nv = nv < 0 ? 1: 0;
				return o;
			}

			sampler2D _ShadowTex;
			sampler2D _FalloffTex;

			fixed4 frag(v2f i) : COLOR
			{
				fixed4 texS = tex2DProj(_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
				fixed4 res = fixed4(1,1,1,texS.a);

				res.a = i.nv;
				UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(1,1,1,1));
				return res;
			}
			EDNCG
		}
	}
}


再介绍一下,关于混合的部分:

当着色器计算某个像素的颜色时,该颜色必须作用于屏幕上该点已经存在的像素颜色之上。默认情况下,新的像素会完全覆盖原有像素,但新像素也可以与原有像素进行混合。混合通常用于让对象呈透明或半透明效果,当然也可以实现很多其它的炫酷特效。

 

关键字Blend可以包含在Subshader或Pass标签中,甚至对同一个着色器的不同Pass进行混合。添加Blend关键字后,必须写入混合因子。混合因子如下:

 Unity喷墨效果Shader实现_第4张图片


Src指向着色器用于计算的颜色。Dst指向屏幕上已有的像素颜色。着色器用于计算的颜色会与第一个因子相乘,而屏幕上已有颜色会与第二个因子相乘。将两个结果相加,就是最终写到屏幕的颜色。

 

所以"Blend SrcAlpha One"会将自身Alpha值与当前着色器计算的颜色相乘,此时屏幕上的颜色暂未改动。然后再将屏幕颜色计算后的结果与前者相加。还可以使用逗号分隔两组因子,逗号前的混合选项用于计算颜色,逗号后的混合选项仅计算Alpha通道。可以查阅Unity文档了解更多关于混合的内容。

 

用于Projector的着色器就是“Blend Zero One, One One”,“Zero One”移除了飞溅纹理的颜色,使用子弹所飞溅到的表面颜色。“One One”将飞溅物的Alpha值与表面Alpha值相加。

 

现在使用上面的着色器与材质来生成Projector,应该将场景视图的Alpha通道设为白色。

现在可以随意修改Alpha通道,但还未达到最终效果。下面利用Alpha遮罩来创建游戏所需的图像特效。

 

首先,创建要使用图像特效的着色器。在Unity中新建默认的Image Effect Shader,然后将片段代码替换为如下:

fixed4 frag(v2f i) : SV_Target
{
   fixed4 col = tex2D(_MainTex, i.uv);
   fixed3 bnw = dot(col.rgb, float3(0.3,0.59,0.11));
   col.rgb = lerp(bnw, col.rgb, col.a);
   
   return col;
}

可以随意更改bnw(Black&White)变量以达到理想的混合效果。最后还需要新建脚本来运行该图像特效。脚本非常简单,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityStandardAssets.ImageEffectBase;

[ExecuteInEditMode]
[ImageEffectAllowedInSceneView]
public class ALphaColorSwitch : ImageEffectBase{
	void OnRenderImage(RenderTexture source, RenderTexture destination)
	{
		Graphics.Blit (source, destination, material);
	}
}


注意,这里用到了ImageEffectBase,该资源在Unity标准资源库中(Unity 5.5及以上版本推荐使用Post Processing Stack资源库代替ImageEffects)。导入标准资源库后,将脚本绑定到相机(确保将相机的渲染模式设为Forward)上,并将公共的着色器变量设为前面提到的着色器。

 

到此就可以向场景中喷射油墨啦!



你可能感兴趣的:(3D引擎)