流光效果
首先来看一下流光效果。流光效果是一个非常常见的效果,不仅仅是游戏,一些广告之类的也都会有这种效果。流光的原理还是比较简单的:首先就是需要一张流光图,这张流光图的大部分都是黑色,然后有一条亮线,然后我们在采样的时候,最终输出叠加上这张图的采样值,并根据时间调整采样的UV就可以有流光的效果啦。下面是一个比较简单的流光效果实现:
-
//流光效果
-
//by:puppet_master
-
//2017.7.29
-
Shader "ApcShader/FlashEffect"
-
{
-
Properties
-
{
-
_MainTex( "MainTex(RGB)", 2D) = "white" {}
-
_FlashTex( "FlashTex", 2D) = "black" {}
-
_FlashColor( "FlashColor",Color) = ( 1, 1, 1, 1)
-
_FlashSpeedX( "FlashSpeedX", Range( -5, 5)) = 0
-
_FlashSpeedY( "FlashSpeedY", Range( -5, 5)) = 0.5
-
_FlashFactor ( "FlashFactor", Range( 0, 5)) = 1
-
}
-
-
CGINCLUDE
-
-
uniform sampler2D _MainTex;
-
uniform float4 _MainTex_ST;
-
uniform sampler2D _FlashTex;
-
uniform fixed4 _FlashColor;
-
uniform fixed _FlashSpeedX;
-
uniform fixed _FlashSpeedY;
-
uniform fixed _FlashFactor;
-
-
struct v2f
-
{
-
float4 pos : SV_POSITION;
-
float3 worldNormal : NORMAL;
-
float2 uv : TEXCOORD0;
-
float3 worldLight : TEXCOORD1;
-
};
-
-
v2f vert(appdata_base v)
-
{
-
v2f o;
-
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
-
o.worldNormal = UnityObjectToWorldNormal(v.normal);
-
o.worldLight = UnityObjectToWorldDir(_WorldSpaceLightPos0.xyz);
-
return o;
-
}
-
-
fixed4 frag(v2f i) : SV_Target
-
{
-
half3 normal = normalize(i.worldNormal);
-
half3 light = normalize(i.worldLight);
-
fixed diff = max( 0, dot(normal, light));
-
fixed4 albedo = tex2D(_MainTex, i.uv);
-
//通过时间将采样flash的uv进行偏移
-
half2 flashuv = i.uv + half2(_FlashSpeedX, _FlashSpeedY) * _Time.y;
-
fixed4 flash = tex2D(_FlashTex, flashuv) * _FlashColor * _FlashFactor;
-
fixed4 c;
-
//将flash图与原图叠加
-
c.rgb = diff * albedo + flash.rgb;
-
c.a = 1;
-
return c;
-
}
-
ENDCG
-
-
SubShader
-
{
-
-
Pass
-
{
-
Tags{ "RenderType" = "Opaque" }
-
-
CGPROGRAM
-
-
-
ENDCG
-
}
-
}
-
FallBack "Diffuse"
-
}
更通用的流光效果
我们把流光shader用于一个3D模型,效果却并不像我们预期的那样会出现一条扫描线,而是亮起来的地方让人捉摸不透,如下图所示:
为什么会这样呢?其实主要是我们采样的方式导致的,正常的纹理采样都是使用uv坐标进行采样的,也就是说这个坐标是与模型有关,在这个模型上这个点需要采样纹理的哪部分是由美术展uv时决定的。我们如果用uv来进行采样,对于正常的diffuse贴图或者法线贴图等是对的,但是对于一些其他特殊的效果,uv采样不能达到我们的需求了,所以我们就需要研究一下,用一个其他的东东作为采样的坐标,这也是本篇文章主要研究的内容。
如果不用uv进行采样,那么我们就需要一些其他的值作为采样值,之前的文章我们也有使用过类似的方法,比如在
热空气扭曲效果中我们使用了屏幕空间采样,采样时计算该点在屏幕空间的坐标值反过来去采样全屏GrabPass图。不过这里我们不需要屏幕空间,毕竟这样的话这个流动效果就会随着我们观察的角度而变化,所以我们选择用世界空间采样:
-
//流光效果
-
//by:puppet_master
-
//2017.7.30
-
Shader "ApcShader/FlashEffect"
-
{
-
Properties
-
{
-
_MainTex( "MainTex(RGB)", 2D) = "white" {}
-
_FlashTex( "FlashTex", 2D) = "black" {}
-
_FlashColor( "FlashColor",Color) = ( 1, 1, 1, 1)
-
_FlashFactor( "FlashFactor", Vector) = ( 0, 1, 0.5, 0.5)
-
_FlashStrength ( "FlashStrength", Range( 0, 5)) = 1
-
}
-
-
CGINCLUDE
-
-
uniform sampler2D _MainTex;
-
uniform float4 _MainTex_ST;
-
uniform sampler2D _FlashTex;
-
uniform fixed4 _FlashColor;
-
//改为一个vector4,减少传参次数消耗
-
uniform fixed4 _FlashFactor;
-
uniform fixed _FlashStrength;
-
-
struct v2f
-
{
-
float4 pos : SV_POSITION;
-
float3 worldNormal : NORMAL;
-
float2 uv : TEXCOORD0;
-
float3 worldLight : TEXCOORD1;
-
float4 worldPos : TEXCOORD2;
-
};
-
-
v2f vert(appdata_base v)
-
{
-
v2f o;
-
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
-
//顶点转化到世界空间
-
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
-
o.worldNormal = UnityObjectToWorldNormal(v.normal);
-
o.worldLight = UnityObjectToWorldDir(_WorldSpaceLightPos0.xyz);
-
return o;
-
}
-
-
fixed4 frag(v2f i) : SV_Target
-
{
-
half3 normal = normalize(i.worldNormal);
-
half3 light = normalize(i.worldLight);
-
fixed diff = max( 0, dot(normal, light));
-
fixed4 albedo = tex2D(_MainTex, i.uv);
-
//通过时间偏移世界坐标对flashTex进行采样
-
half2 flashuv = i.worldPos.xy * _FlashFactor.zw + _FlashFactor.xy * _Time.y;
-
fixed4 flash = tex2D(_FlashTex, flashuv) * _FlashColor * _FlashStrength;
-
fixed4 c;
-
//将flash图与原图叠加
-
c.rgb = diff * albedo + flash.rgb;
-
c.a = 1;
-
return c;
-
}
-
ENDCG
-
-
SubShader
-
{
-
-
Pass
-
{
-
Tags{ "RenderType" = "Opaque" }
-
-
CGPROGRAM
-
-
-
ENDCG
-
}
-
}
-
FallBack "Diffuse"
-
}
然后我们也可以换一张贴图,再调整一下参数,让流光换个方向:
按照方向消失或重现效果
我们再来看一个用模型空间坐标作为采样的uv的栗子,也是一种比较好玩的效果。比如我们需要一个模型身体按照一定的方向逐渐消失,直至全部消失掉的一个效果。下面说一下思路,与世界空间采样的流光效果一样,我们在vertex阶段记录一下vertex坐标,传递给fragment阶段,在fragment阶段用这个值和一个设定好的阈值进行比较,不满足条件的像素点直接discard,逐渐调整阈值,就可以得到让模型按照某个方向消失的效果了。代码如下:
-
//按照方向消失的效果
-
//by:puppet_master
-
//2017.8.10
-
Shader "ApcShader/DissolveEffectX"
-
{
-
Properties
-
{
-
_MainTex( "MainTex(RGB)", 2D) = "white" {}
-
_DissolveVector( "DissolveVector", Vector) = ( 0, 0, 0, 0)
-
}
-
-
CGINCLUDE
-
-
uniform sampler2D _MainTex;
-
uniform float4 _MainTex_ST;
-
uniform float4 _DissolveVector;
-
-
struct v2f
-
{
-
float4 pos : SV_POSITION;
-
float3 worldNormal : NORMAL;
-
float2 uv : TEXCOORD0;
-
float3 worldLight : TEXCOORD1;
-
float4 objPos : TEXCOORD2;
-
};
-
-
v2f vert(appdata_base v)
-
{
-
v2f o;
-
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
-
//顶点转化到世界空间
-
o.objPos = v.vertex;
-
o.worldNormal = UnityObjectToWorldNormal(v.normal);
-
o.worldLight = UnityObjectToWorldDir(_WorldSpaceLightPos0.xyz);
-
return o;
-
}
-
-
fixed4 frag(v2f i) : SV_Target
-
{
-
half3 normal = normalize(i.worldNormal);
-
half3 light = normalize(i.worldLight);
-
fixed diff = max( 0, dot(normal, light));
-
fixed4 albedo = tex2D(_MainTex, i.uv);
-
//不满足条件的discard
-
clip(i.objPos.xyz - _DissolveVector.xyz);
-
fixed4 c;
-
c.rgb = diff * albedo;
-
c.a = 1;
-
return c;
-
}
-
ENDCG
-
-
SubShader
-
{
-
-
Pass
-
{
-
Tags{ "RenderType" = "Opaque" }
-
-
CGPROGRAM
-
-
-
ENDCG
-
}
-
}
-
FallBack "Diffuse"
-
}
在这里,我们没有像流光效果那样使用世界空间坐标采样,而是使用了模型空间坐标采样,其实还可以使用屏幕空间坐标或者视口空间坐标采样等等,几种方式各有各的优点和缺点。使用世界空间采样,消失的方向是绝对的,比如是从上向下,那么这个模型如果趴在地上,消失的方向就会是从模型后背到前胸的方向,而且坐标阈值会随着模型处于世界中的位置不同而不同;使用模型空间采样,消失的方向与模型本身有关,比如站着的话消失方向是从头到脚,那么趴着也是从头到脚,而且坐标的阈值与模型的高矮胖瘦有关(也与模型的原点位置有点关系);使用屏幕空间采样的话,消失的方向就可能会与我们观察的方向有关,这种可能不太可控。
还有一个小问题,其实上图中的例子里面,模型从上到下,理想情况应该是调整Y轴,不过例子里面调整的确实X轴,原因应该与Unity导入之后会绕着X轴旋转90度有关,也就是原本在max里面的Y轴变成Unity里面的X轴。
下面,我们再看一下增加了边缘高亮的消失效果,为了让模消失的型边缘高亮,我们通过将用于clip的factor值与另一个高亮阈值值进行比较,如果factor小于高亮阈值,则返回一个高亮的颜色值,否则正常渲染。这样模型就总共有三种显示状态:clip状态,高亮状态,正常状态。代码如下:
-
//消失效果
-
//by:puppet_master
-
//2017.8.11
-
-
Shader "ApcShader/DissolveEffectX"
-
{
-
Properties{
-
_Diffuse( "Diffuse", Color) = ( 1, 1, 1, 1)
-
_DissolveColor( "Dissolve Color", Color) = ( 0, 0, 0, 0)
-
_MainTex( "Base 2D", 2D) = "white"{}
-
_ColorFactor( "ColorFactor", Range( 0, 1)) = 0.7
-
_DissolveThreshold( "DissolveThreshold", Float) = 0
-
}
-
-
CGINCLUDE
-
-
uniform fixed4 _Diffuse;
-
uniform fixed4 _DissolveColor;
-
uniform sampler2D _MainTex;
-
uniform float4 _MainTex_ST;
-
uniform float _ColorFactor;
-
uniform float _DissolveThreshold;
-
-
struct v2f
-
{
-
float4 pos : SV_POSITION;
-
float3 worldNormal : TEXCOORD0;
-
float2 uv : TEXCOORD1;
-
float4 objPos : TEXCOORD2;
-
};
-
-
v2f vert(appdata_base v)
-
{
-
v2f o;
-
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
-
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
-
o.objPos = v.vertex;
-
return o;
-
}
-
-
fixed4 frag(v2f i) : SV_Target
-
{
-
float factor = i.objPos.x - _DissolveThreshold;
-
clip(factor);
-
//Diffuse + Ambient光照计算
-
fixed3 worldNormal = normalize(i.worldNormal);
-
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
-
fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
-
fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
-
fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;
-
//等价于下面注释代码的操作
-
fixed lerpFactor = saturate(sign(_ColorFactor - factor));
-
return lerpFactor * _DissolveColor + ( 1 - lerpFactor) * fixed4(color, 1);
-
/*
-
if (factor < _ColorFactor)
-
{
-
return _DissolveColor;
-
}
-
return fixed4(color, 1);*/
-
}
-
ENDCG
-
-
SubShader
-
{
-
Tags{ "RenderType" = "Opaque" }
-
Pass
-
{
-
//不让模型穿帮,关掉了背面裁剪
-
Cull Off
-
CGPROGRAM
-
-
-
ENDCG
-
}
-
}
-
FallBack "Diffuse"
-
}
溶解效果进阶版
之前的文章里,我们研究过
溶解效果,不过这个效果是基于全身的,我们来尝试一下,把上面按照方向消失的效果与溶解效果结合起来,做成一个按照某个方向逐渐溶解的效果。要得到随机的溶解效果,我们需要采样一张噪声图,然后在原本会直接clip掉的部分根据采样的噪声图进行clip,就能得到按照方向的溶解效果啦。
-
//溶解效果
-
//by:puppet_master
-
//2017.8.11
-
-
Shader "ApcShader/DissolveEffectX"
-
{
-
Properties{
-
_Diffuse( "Diffuse", Color) = ( 1, 1, 1, 1)
-
_DissolveColor( "Dissolve Color", Color) = ( 1, 1, 1, 1)
-
_MainTex( "Base 2D", 2D) = "white"{}
-
_DissolveMap( "DissolveMap", 2D) = "white"{}
-
_DissolveThreshold( "DissolveThreshold", Range( 0, 1)) = 0
-
_DissolveSpeedFactor( "DissolveSpeed", Range( 0, 5)) = 2
-
_DissolveControl( "ColorFactorB", Float) = 0
-
}
-
-
CGINCLUDE
-
-
uniform fixed4 _Diffuse;
-
uniform fixed4 _DissolveColor;
-
uniform sampler2D _MainTex;
-
uniform float4 _MainTex_ST;
-
uniform sampler2D _DissolveMap;
-
uniform float _DissolveThreshold;
-
uniform float _DissolveSpeedFactor;
-
uniform float _DissolveControl;
-
-
struct v2f
-
{
-
float4 pos : SV_POSITION;
-
float3 worldNormal : TEXCOORD0;
-
float2 uv : TEXCOORD1;
-
float4 objPos : TEXCOORD2;
-
};
-
-
v2f vert(appdata_base v)
-
{
-
v2f o;
-
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
-
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
-
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
-
o.objPos = v.vertex;
-
return o;
-
}
-
-
fixed4 frag(v2f i) : SV_Target
-
{
-
fixed4 dissolve = tex2D(_DissolveMap, i.uv);
-
//Diffuse + Ambient光照计算
-
fixed3 worldNormal = normalize(i.worldNormal);
-
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
-
fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
-
fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
-
fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;
-
float factor = i.objPos.x - _DissolveControl;
-
if(factor < 0)
-
{
-
clip(_DissolveThreshold - dissolve.r * abs(factor) * _DissolveSpeedFactor);
-
}
-
return fixed4(color, 1);
-
}
-
ENDCG
-
-
SubShader
-
{
-
Tags{ "RenderType" = "Opaque" }
-
Pass
-
{
-
Cull Off
-
CGPROGRAM
-
-
-
ENDCG
-
}
-
}
-
FallBack "Diffuse"
-
}