Unity Shader-非主流纹理采样研究(流光,溶解,隐身效果)

流光效果


首先来看一下流光效果。流光效果是一个非常常见的效果,不仅仅是游戏,一些广告之类的也都会有这种效果。流光的原理还是比较简单的:首先就是需要一张流光图,这张流光图的大部分都是黑色,然后有一条亮线,然后我们在采样的时候,最终输出叠加上这张图的采样值,并根据时间调整采样的UV就可以有流光的效果啦。下面是一个比较简单的流光效果实现:
  1. //流光效果
  2. //by:puppet_master
  3. //2017.7.29
  4. Shader "ApcShader/FlashEffect"
  5. {
  6. Properties
  7. {
  8. _MainTex( "MainTex(RGB)", 2D) = "white" {}
  9. _FlashTex( "FlashTex", 2D) = "black" {}
  10. _FlashColor( "FlashColor",Color) = ( 1, 1, 1, 1)
  11. _FlashSpeedX( "FlashSpeedX", Range( -5, 5)) = 0
  12. _FlashSpeedY( "FlashSpeedY", Range( -5, 5)) = 0.5
  13. _FlashFactor ( "FlashFactor", Range( 0, 5)) = 1
  14. }
  15. CGINCLUDE
  16. #include "Lighting.cginc"
  17. uniform sampler2D _MainTex;
  18. uniform float4 _MainTex_ST;
  19. uniform sampler2D _FlashTex;
  20. uniform fixed4 _FlashColor;
  21. uniform fixed _FlashSpeedX;
  22. uniform fixed _FlashSpeedY;
  23. uniform fixed _FlashFactor;
  24. struct v2f
  25. {
  26. float4 pos : SV_POSITION;
  27. float3 worldNormal : NORMAL;
  28. float2 uv : TEXCOORD0;
  29. float3 worldLight : TEXCOORD1;
  30. };
  31. v2f vert(appdata_base v)
  32. {
  33. v2f o;
  34. o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  35. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  36. o.worldNormal = UnityObjectToWorldNormal(v.normal);
  37. o.worldLight = UnityObjectToWorldDir(_WorldSpaceLightPos0.xyz);
  38. return o;
  39. }
  40. fixed4 frag(v2f i) : SV_Target
  41. {
  42. half3 normal = normalize(i.worldNormal);
  43. half3 light = normalize(i.worldLight);
  44. fixed diff = max( 0, dot(normal, light));
  45. fixed4 albedo = tex2D(_MainTex, i.uv);
  46. //通过时间将采样flash的uv进行偏移
  47. half2 flashuv = i.uv + half2(_FlashSpeedX, _FlashSpeedY) * _Time.y;
  48. fixed4 flash = tex2D(_FlashTex, flashuv) * _FlashColor * _FlashFactor;
  49. fixed4 c;
  50. //将flash图与原图叠加
  51. c.rgb = diff * albedo + flash.rgb;
  52. c.a = 1;
  53. return c;
  54. }
  55. ENDCG
  56. SubShader
  57. {
  58. Pass
  59. {
  60. Tags{ "RenderType" = "Opaque" }
  61. CGPROGRAM
  62. #pragma vertex vert
  63. #pragma fragment frag
  64. ENDCG
  65. }
  66. }
  67. FallBack "Diffuse"
  68. }
shader比较简单,flashuv是随着时间逐渐增大的,这个值肯定会大于1,而正常纹理的范围是0-1,所以,要想让贴图在采样时大于0-1也有效果,我们就必须要把贴图的WrapMode设置为Repeat,否则当这个值大于1之后,边缘就被截断了,我们也就不会看到流光效果了。注意,流光的贴图是一个方向,采样uv偏移是另一个方向,比如我的流光图是水平方向的,那么流光运动的方向就是竖直方向,效果如下面动图所示:


更通用的流光效果


我们把流光shader用于一个3D模型,效果却并不像我们预期的那样会出现一条扫描线,而是亮起来的地方让人捉摸不透,如下图所示:

为什么会这样呢?其实主要是我们采样的方式导致的,正常的纹理采样都是使用uv坐标进行采样的,也就是说这个坐标是与模型有关,在这个模型上这个点需要采样纹理的哪部分是由美术展uv时决定的。我们如果用uv来进行采样,对于正常的diffuse贴图或者法线贴图等是对的,但是对于一些其他特殊的效果,uv采样不能达到我们的需求了,所以我们就需要研究一下,用一个其他的东东作为采样的坐标,这也是本篇文章主要研究的内容。
如果不用uv进行采样,那么我们就需要一些其他的值作为采样值,之前的文章我们也有使用过类似的方法,比如在 热空气扭曲效果中我们使用了屏幕空间采样,采样时计算该点在屏幕空间的坐标值反过来去采样全屏GrabPass图。不过这里我们不需要屏幕空间,毕竟这样的话这个流动效果就会随着我们观察的角度而变化,所以我们选择用世界空间采样:
  1. //流光效果
  2. //by:puppet_master
  3. //2017.7.30
  4. Shader "ApcShader/FlashEffect"
  5. {
  6. Properties
  7. {
  8. _MainTex( "MainTex(RGB)", 2D) = "white" {}
  9. _FlashTex( "FlashTex", 2D) = "black" {}
  10. _FlashColor( "FlashColor",Color) = ( 1, 1, 1, 1)
  11. _FlashFactor( "FlashFactor", Vector) = ( 0, 1, 0.5, 0.5)
  12. _FlashStrength ( "FlashStrength", Range( 0, 5)) = 1
  13. }
  14. CGINCLUDE
  15. #include "Lighting.cginc"
  16. uniform sampler2D _MainTex;
  17. uniform float4 _MainTex_ST;
  18. uniform sampler2D _FlashTex;
  19. uniform fixed4 _FlashColor;
  20. //改为一个vector4,减少传参次数消耗
  21. uniform fixed4 _FlashFactor;
  22. uniform fixed _FlashStrength;
  23. struct v2f
  24. {
  25. float4 pos : SV_POSITION;
  26. float3 worldNormal : NORMAL;
  27. float2 uv : TEXCOORD0;
  28. float3 worldLight : TEXCOORD1;
  29. float4 worldPos : TEXCOORD2;
  30. };
  31. v2f vert(appdata_base v)
  32. {
  33. v2f o;
  34. o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  35. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  36. //顶点转化到世界空间
  37. o.worldPos = mul(unity_ObjectToWorld, v.vertex);
  38. o.worldNormal = UnityObjectToWorldNormal(v.normal);
  39. o.worldLight = UnityObjectToWorldDir(_WorldSpaceLightPos0.xyz);
  40. return o;
  41. }
  42. fixed4 frag(v2f i) : SV_Target
  43. {
  44. half3 normal = normalize(i.worldNormal);
  45. half3 light = normalize(i.worldLight);
  46. fixed diff = max( 0, dot(normal, light));
  47. fixed4 albedo = tex2D(_MainTex, i.uv);
  48. //通过时间偏移世界坐标对flashTex进行采样
  49. half2 flashuv = i.worldPos.xy * _FlashFactor.zw + _FlashFactor.xy * _Time.y;
  50. fixed4 flash = tex2D(_FlashTex, flashuv) * _FlashColor * _FlashStrength;
  51. fixed4 c;
  52. //将flash图与原图叠加
  53. c.rgb = diff * albedo + flash.rgb;
  54. c.a = 1;
  55. return c;
  56. }
  57. ENDCG
  58. SubShader
  59. {
  60. Pass
  61. {
  62. Tags{ "RenderType" = "Opaque" }
  63. CGPROGRAM
  64. #pragma vertex vert
  65. #pragma fragment frag
  66. ENDCG
  67. }
  68. }
  69. FallBack "Diffuse"
  70. }
好了,下面我们找个帅帅哒模型,看一下修改之后的流光效果:
然后我们也可以换一张贴图,再调整一下参数,让流光换个方向:



按照方向消失或重现效果


我们再来看一个用模型空间坐标作为采样的uv的栗子,也是一种比较好玩的效果。比如我们需要一个模型身体按照一定的方向逐渐消失,直至全部消失掉的一个效果。下面说一下思路,与世界空间采样的流光效果一样,我们在vertex阶段记录一下vertex坐标,传递给fragment阶段,在fragment阶段用这个值和一个设定好的阈值进行比较,不满足条件的像素点直接discard,逐渐调整阈值,就可以得到让模型按照某个方向消失的效果了。代码如下:
  1. //按照方向消失的效果
  2. //by:puppet_master
  3. //2017.8.10
  4. Shader "ApcShader/DissolveEffectX"
  5. {
  6. Properties
  7. {
  8. _MainTex( "MainTex(RGB)", 2D) = "white" {}
  9. _DissolveVector( "DissolveVector", Vector) = ( 0, 0, 0, 0)
  10. }
  11. CGINCLUDE
  12. #include "Lighting.cginc"
  13. uniform sampler2D _MainTex;
  14. uniform float4 _MainTex_ST;
  15. uniform float4 _DissolveVector;
  16. struct v2f
  17. {
  18. float4 pos : SV_POSITION;
  19. float3 worldNormal : NORMAL;
  20. float2 uv : TEXCOORD0;
  21. float3 worldLight : TEXCOORD1;
  22. float4 objPos : TEXCOORD2;
  23. };
  24. v2f vert(appdata_base v)
  25. {
  26. v2f o;
  27. o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  28. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  29. //顶点转化到世界空间
  30. o.objPos = v.vertex;
  31. o.worldNormal = UnityObjectToWorldNormal(v.normal);
  32. o.worldLight = UnityObjectToWorldDir(_WorldSpaceLightPos0.xyz);
  33. return o;
  34. }
  35. fixed4 frag(v2f i) : SV_Target
  36. {
  37. half3 normal = normalize(i.worldNormal);
  38. half3 light = normalize(i.worldLight);
  39. fixed diff = max( 0, dot(normal, light));
  40. fixed4 albedo = tex2D(_MainTex, i.uv);
  41. //不满足条件的discard
  42. clip(i.objPos.xyz - _DissolveVector.xyz);
  43. fixed4 c;
  44. c.rgb = diff * albedo;
  45. c.a = 1;
  46. return c;
  47. }
  48. ENDCG
  49. SubShader
  50. {
  51. Pass
  52. {
  53. Tags{ "RenderType" = "Opaque" }
  54. CGPROGRAM
  55. #pragma vertex vert
  56. #pragma fragment frag
  57. ENDCG
  58. }
  59. }
  60. FallBack "Diffuse"
  61. }
还是上面的模型,我们逐渐调整X,Y,Z轴三个方向的阈值,就可以有逐渐消失或者出现的效果啦,如下面动图所示:

在这里,我们没有像流光效果那样使用世界空间坐标采样,而是使用了模型空间坐标采样,其实还可以使用屏幕空间坐标或者视口空间坐标采样等等,几种方式各有各的优点和缺点。使用世界空间采样,消失的方向是绝对的,比如是从上向下,那么这个模型如果趴在地上,消失的方向就会是从模型后背到前胸的方向,而且坐标阈值会随着模型处于世界中的位置不同而不同;使用模型空间采样,消失的方向与模型本身有关,比如站着的话消失方向是从头到脚,那么趴着也是从头到脚,而且坐标的阈值与模型的高矮胖瘦有关(也与模型的原点位置有点关系);使用屏幕空间采样的话,消失的方向就可能会与我们观察的方向有关,这种可能不太可控。
还有一个小问题,其实上图中的例子里面,模型从上到下,理想情况应该是调整Y轴,不过例子里面调整的确实X轴,原因应该与Unity导入之后会绕着X轴旋转90度有关,也就是原本在max里面的Y轴变成Unity里面的X轴。

下面,我们再看一下增加了边缘高亮的消失效果,为了让模消失的型边缘高亮,我们通过将用于clip的factor值与另一个高亮阈值值进行比较,如果factor小于高亮阈值,则返回一个高亮的颜色值,否则正常渲染。这样模型就总共有三种显示状态:clip状态,高亮状态,正常状态。代码如下:
  1. //消失效果
  2. //by:puppet_master
  3. //2017.8.11
  4. Shader "ApcShader/DissolveEffectX"
  5. {
  6. Properties{
  7. _Diffuse( "Diffuse", Color) = ( 1, 1, 1, 1)
  8. _DissolveColor( "Dissolve Color", Color) = ( 0, 0, 0, 0)
  9. _MainTex( "Base 2D", 2D) = "white"{}
  10. _ColorFactor( "ColorFactor", Range( 0, 1)) = 0.7
  11. _DissolveThreshold( "DissolveThreshold", Float) = 0
  12. }
  13. CGINCLUDE
  14. #include "Lighting.cginc"
  15. uniform fixed4 _Diffuse;
  16. uniform fixed4 _DissolveColor;
  17. uniform sampler2D _MainTex;
  18. uniform float4 _MainTex_ST;
  19. uniform float _ColorFactor;
  20. uniform float _DissolveThreshold;
  21. struct v2f
  22. {
  23. float4 pos : SV_POSITION;
  24. float3 worldNormal : TEXCOORD0;
  25. float2 uv : TEXCOORD1;
  26. float4 objPos : TEXCOORD2;
  27. };
  28. v2f vert(appdata_base v)
  29. {
  30. v2f o;
  31. o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  32. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  33. o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
  34. o.objPos = v.vertex;
  35. return o;
  36. }
  37. fixed4 frag(v2f i) : SV_Target
  38. {
  39. float factor = i.objPos.x - _DissolveThreshold;
  40. clip(factor);
  41. //Diffuse + Ambient光照计算
  42. fixed3 worldNormal = normalize(i.worldNormal);
  43. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  44. fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
  45. fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
  46. fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;
  47. //等价于下面注释代码的操作
  48. fixed lerpFactor = saturate(sign(_ColorFactor - factor));
  49. return lerpFactor * _DissolveColor + ( 1 - lerpFactor) * fixed4(color, 1);
  50. /*
  51. if (factor < _ColorFactor)
  52. {
  53. return _DissolveColor;
  54. }
  55. return fixed4(color, 1);*/
  56. }
  57. ENDCG
  58. SubShader
  59. {
  60. Tags{ "RenderType" = "Opaque" }
  61. Pass
  62. {
  63. //不让模型穿帮,关掉了背面裁剪
  64. Cull Off
  65. CGPROGRAM
  66. #pragma vertex vert
  67. #pragma fragment frag
  68. ENDCG
  69. }
  70. }
  71. FallBack "Diffuse"
  72. }
这一次我们只让模型在上下方向上逐渐消失,调整ColorFactor可以控制高亮区域的高度:



溶解效果进阶版


之前的文章里,我们研究过 溶解效果,不过这个效果是基于全身的,我们来尝试一下,把上面按照方向消失的效果与溶解效果结合起来,做成一个按照某个方向逐渐溶解的效果。要得到随机的溶解效果,我们需要采样一张噪声图,然后在原本会直接clip掉的部分根据采样的噪声图进行clip,就能得到按照方向的溶解效果啦。
  1. //溶解效果
  2. //by:puppet_master
  3. //2017.8.11
  4. Shader "ApcShader/DissolveEffectX"
  5. {
  6. Properties{
  7. _Diffuse( "Diffuse", Color) = ( 1, 1, 1, 1)
  8. _DissolveColor( "Dissolve Color", Color) = ( 1, 1, 1, 1)
  9. _MainTex( "Base 2D", 2D) = "white"{}
  10. _DissolveMap( "DissolveMap", 2D) = "white"{}
  11. _DissolveThreshold( "DissolveThreshold", Range( 0, 1)) = 0
  12. _DissolveSpeedFactor( "DissolveSpeed", Range( 0, 5)) = 2
  13. _DissolveControl( "ColorFactorB", Float) = 0
  14. }
  15. CGINCLUDE
  16. #include "Lighting.cginc"
  17. uniform fixed4 _Diffuse;
  18. uniform fixed4 _DissolveColor;
  19. uniform sampler2D _MainTex;
  20. uniform float4 _MainTex_ST;
  21. uniform sampler2D _DissolveMap;
  22. uniform float _DissolveThreshold;
  23. uniform float _DissolveSpeedFactor;
  24. uniform float _DissolveControl;
  25. struct v2f
  26. {
  27. float4 pos : SV_POSITION;
  28. float3 worldNormal : TEXCOORD0;
  29. float2 uv : TEXCOORD1;
  30. float4 objPos : TEXCOORD2;
  31. };
  32. v2f vert(appdata_base v)
  33. {
  34. v2f o;
  35. o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
  36. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  37. o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
  38. o.objPos = v.vertex;
  39. return o;
  40. }
  41. fixed4 frag(v2f i) : SV_Target
  42. {
  43. fixed4 dissolve = tex2D(_DissolveMap, i.uv);
  44. //Diffuse + Ambient光照计算
  45. fixed3 worldNormal = normalize(i.worldNormal);
  46. fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
  47. fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
  48. fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
  49. fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;
  50. float factor = i.objPos.x - _DissolveControl;
  51. if(factor < 0)
  52. {
  53. clip(_DissolveThreshold - dissolve.r * abs(factor) * _DissolveSpeedFactor);
  54. }
  55. return fixed4(color, 1);
  56. }
  57. ENDCG
  58. SubShader
  59. {
  60. Tags{ "RenderType" = "Opaque" }
  61. Pass
  62. {
  63. Cull Off
  64. CGPROGRAM
  65. #pragma vertex vert
  66. #pragma fragment frag
  67. ENDCG
  68. }
  69. }
  70. FallBack "Diffuse"
  71. }
效果如下:

猜你喜欢

转载自blog.csdn.net/qq_14914623/article/details/80957556