简介
今天来研究几个效果,最近比较忙,所以来弄几个比较简单的效果玩一下。不过也是游戏中常用的一些效果,流光效果,按照方向的溶解效果。其实这几个效果主要运用的就是世界空间坐标或者模型空间坐标用于采样的一个方法,总之就是有些非主流的纹理采样方式。不多说,下面进入正题。
流光效果
首先来看一下流光效果。流光效果是一个非常常见的效果,不仅仅是游戏,一些广告之类的也都会有这种效果。流光的原理还是比较简单的:首先就是需要一张流光图,这张流光图的大部分都是黑色,然后有一条亮线,然后我们在采样的时候,最终输出叠加上这张图的采样值,并根据时间调整采样的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
- #include "Lighting.cginc"
- 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
- #pragma vertex vert
- #pragma fragment frag
- 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
- #include "Lighting.cginc"
- 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
- #pragma vertex vert
- #pragma fragment frag
- 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
- #include "Lighting.cginc"
- 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
- #pragma vertex vert
- #pragma fragment frag
- 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
- #include "Lighting.cginc"
- 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
- #pragma vertex vert
- #pragma fragment frag
- 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
- #include "Lighting.cginc"
- 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
- #pragma vertex vert
- #pragma fragment frag
- ENDCG
- }
- }
- FallBack "Diffuse"
- }
当然,也可以参考之前溶解效果,增加溶解描边高亮的效果: