Unity中实现溶解(Dissolve)特效及其原理解析

Unity中实现溶解(Dissolve)特效及其原理解析

今天我们分享一个Unity中非常常见的特效: 溶解.

下面先来看看效果.

在这里插入图片描述

大家可能和我一样, 在不了解实现方式的时候, 会觉得这个效果很神奇, 实现起来十分复杂.

但是一旦你了解之后, 会觉得…更神奇, 简单到神奇.

溶解效果的原理和其实现

溶解特效的实现原理非常非常简单, 一句话概况就是: 给定一个变化量, 在值变化时, 抛弃对象的一部分像素不进行渲染.

所谓给定一个变化量, 我们可以将其抽象为一个进度条Slider, 代表溶解程度, 从0到1, 0代表一点都不溶解, 1代表全部溶解.

也就是说, Slider从0到1, 溶解效果从无到有.

那么怎么做到抛弃对象一部分像素不进行渲染呢.

依然很简单, 在片段着色器中使用clip函数, 抛弃掉不满足要求的片段.

clip(value)函数的效果是: 当传入的value小于0时, 则抛弃本片段, 内部的实现类似:

void clip(int value)
{
    if (value < 0)
        discard;
}

那么我们唯一的问题就是这个value该怎么传了.

我们通过Slider取值, 作为一个阈值(threshold value), 所有小于该阈值的片段颜色(任选颜色向量x, y, z, w的一个值作为比较)丢弃.

fixed4 color = tex2D(_MainTex, data.uv);
clip(color.r - threshold);

这样, 随着阈值的增大, 对象身上的颜色值大的片段会慢慢被丢弃, 在视觉上就形成了溶解效果. 代码如下:

Shader "Dissolve_Origin"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
        
        // 通过Slider取阈值
        _DissolveThreshold("DissolveThreshold", Range(0, 1)) = 0
	}
	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;
			};

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

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
            fixed _DissolveThreshold;

			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);
                fixed4 dissolveCol = col;

                // 从噪声纹理采样颜色, 如果该值[小于阈值]则丢弃本片段
                // 比如阈值为0.1, 则在噪声纹理上采样的所有像素r值小于0.1的片段都会被丢弃
                // 即噪声纹理上偏黑的颜色(->0)首先开始溶解, 偏白的颜色(->1)最后溶解
                clip(dissolveCol.r - _DissolveThreshold);

				return col;
			}
			ENDCG
		}
	}
}

大家可以看到, 只是在最基本的着色器代码上加了区区4行代码就达到的一个酷炫的效果, 甚至真正起效的只有一行代码.

在这里插入图片描述

这个效果和第一张图有所差别, 是因为仅仅使用了对象自身的纹理信息, 效果比较受限.

业界常用的实现是还需要添加另一张噪声纹理, 用来指定溶解的区域和程度, 我们下面就开始介绍.

使用噪声纹理指定溶解信息

首先我们将纹理直接替换为另一张纹理, Shader不做任何变化, 可以看到下面的效果:

在这里插入图片描述

可以看到溶解是从黑色(0, 0, 0)的部分开始的, 渐渐溶解到白色的部分.

如果将对象的纹理信息和溶解信息分开, 也就是说使用另一张"溶解纹理(实际上被称为噪声纹理)", 我们就可以操作溶解的区域和速度.

噪声纹理的用途可谓是十分广, 很多特效都是使用噪声纹理, 比如水的流动, 旗帜的飘动, 裂缝等, 后面有机会我们会一一看到.

这里简单理解噪声纹理的意义就是: 将原来对象的纹理进行"扰动", 这些扰动信息使用单独的一张纹理来表示.

那么这是怎么实现呢?

其实也很简单, 在片段着色器中对对象本身的纹理正常采样, 然后使用同一个uv坐标在噪声纹理上采样, 使用该值来判断是否丢弃:

fixed4 frag (v2f i) : SV_Target
{
    fixed4 dissolveCol = tex2D(_DissolveTex, i.uv);

    // 从噪声纹理采样颜色, 如果该值[小于阈值]则丢弃本片段
    // 比如阈值为0.1, 则在噪声纹理上采样的所有像素r值小于0.1的片段都会被丢弃
    // 即噪声纹理上偏黑的颜色(->0)首先开始溶解, 偏白的颜色(->1)最后溶解
    clip(dissolveCol.r - _DissolveThreshold);

    fixed4 col = tex2D(_MainTex, i.uv);
    return col;
}

相应的Shader属性面板如下:

在这里插入图片描述
这样就可以获得和第一张图一样的效果了, 从指定的位置开始溶解.

细心的同学可能看到, 最终效果会从怪物的嘴巴部分开始溶解, 但是噪声纹理最黑的部分明明是左下角:

在这里插入图片描述

这是因为本文使用的怪物纹理是基于PBR制作的, 和普通的纹理布局不太一样, 大家在实验的时候, 找一个正常的纹理就行了.

或者制作噪声纹理时也使用PBR的方式制作也可以达到相同的目的, 这里我是为了说明这种情况, 故意如此.

下面是完整的代码:

Shader "Dissolve"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
        
        _DissolveTex("DissolveTex", 2D) = "white" {}
        _DissolveThreshold("DissolveThreshold", Range(0, 1)) = 0
	}
	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;
			};

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

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
            sampler2D _DissolveTex;
            fixed _DissolveThreshold;

			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 dissolveCol = tex2D(_DissolveTex, i.uv);

                // 从噪声纹理采样颜色, 如果该值[小于阈值]则丢弃本片段
                // 比如阈值为0.1, 则在噪声纹理上采样的所有像素r值小于0.1的片段都会被丢弃
                // 即噪声纹理上偏黑的颜色(->0)首先开始溶解, 偏白的颜色(->1)最后溶解
                clip(dissolveCol.r - _DissolveThreshold);

				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}

总结

今天我们介绍了游戏中比较常见的溶解特效, 比如怪物在被击杀后, 可以溶解消失, 或者角色穿过传送门, 溶解消失等.

对于初次接触的同学来说, 溶解特效比较酷炫, 但是实现却超级简单, 不需要复杂的数学知识和图形学知识, 我认为这是一个非常好的学习Shader示例, 所以在此特意共享给大家.

后续我可能也会继续给大家分享一些简单却有意思的Shader, 让大家领略Shader的魅力, 坚定学习下去的信心.

我认为图形学的基础固然重要, 但是一上来就各种矩阵, 各种管线, 容易让人产生劝退效果, 所以我更喜欢从一些简单的特效来学习, 在学习和实现了效果之后, 再去探究其背后的原理, 这样比较容易产生成果, 也更能加深理解.

我觉得对于前端的同学, 做出各种绚丽的特效也是大家最初想做游戏的目的之一 , 不然做服务器不是更舒服? 虽然做游戏之后才知道, 这部分是美术的活儿, 哈哈.

好了, 今天就是这些啦, 希望对大家有所帮助.

猜你喜欢

转载自blog.csdn.net/woodengm/article/details/125501899