游戏里经常需要在角色上做描边,这里总结一下平时几种常见的描边做法。

优点是简单,效果直接,性价比高。

1. 定点对着法线方向外移,缺点是可以看出顶点之间有断裂

 

  1. Shader "ly/Outline_2Pass_1"
  2. {
  3. Properties
  4. {
  5. _MainTex("Texture", 2D) = "white"{}
  6. _Outline("Outline", range(0, 1)) = 0.02
  7. _OutlineColor("Outline Color", Color) = (1,1,1,1)
  8. }
  9. SubShader
  10. {
  11. //第一个批次,画描边
  12. Pass
  13. {
  14. //Cull掉前面的一半,只让描边显示在后面
  15. Cull Front
  16. CGPROGRAM
  17. #pragma vertex vert
  18. #pragma fragment frag
  19. #include "UnityCG.cginc"
  20. fixed _Outline;
  21. fixed4 _OutlineColor;
  22. struct v2f
  23. {
  24. float4 pos : SV_POSITION;
  25. float4 color : COLOR;
  26. };
  27. v2f vert (appdata_full v)
  28. {
  29. v2f o;
  30. //源顶点位置添加法线方向乘以参数的偏移量
  31. v.vertex.xyz += v.normal * _Outline;
  32. //位置从自身坐标系转换到投影空间
  33. //旧版本o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  34. o.pos = UnityObjectToClipPos(v.vertex);
  35. //描边颜色
  36. o.color = _OutlineColor;
  37. return o;
  38. }
  39. float4 frag (v2f i) : COLOR
  40. {
  41. return i.color; //描边
  42. }
  43. ENDCG
  44. }
  45. //第二个批次
  46. Pass
  47. {
  48. CGPROGRAM
  49. #pragma vertex vert
  50. #pragma fragment frag
  51. #include "UnityCG.cginc"
  52. sampler2D _MainTex;
  53. half4 _MainTex_ST;
  54. struct v2f
  55. {
  56. float4 pos : SV_POSITION;
  57. float2 uv : TEXCOORD0;
  58. fixed4 color : COLOR;
  59. };
  60. v2f vert(appdata_base v)
  61. {
  62. v2f o;
  63. o.pos = UnityObjectToClipPos(v.vertex);
  64. o.uv = v.texcoord;
  65. o.color = fixed4(0, 0, 0, 1);
  66. return o;
  67. }
  68. fixed4 frag(v2f i) : SV_Target
  69. {
  70. fixed4 col = tex2D(_MainTex, i.uv);
  71. return col;
  72. }
  73. ENDCG
  74. }
  75. }
  76. }

 

2. 得到法线在投影空间上的xy轴,作为偏移方向将顶点外移,得到的结果类似1,也有断裂

 

3. 顶点的位置作为方向矢量,则不会因为方向差距较大而断裂

 

  1. Shader "ly/Outline_2Pass_2"
  2. {
  3. Properties
  4. {
  5. _MainTex("Texture", 2D) = "white"{}
  6. _Outline("Outline", range(0, 1)) = 0.02
  7. _OutlineColor("Outline Color", Color) = (1,1,1,1)
  8. }
  9. SubShader
  10. {
  11. //第一个批次,画描边
  12. Pass
  13. {
  14. //Cull掉前面的一半,只让描边显示在后面
  15. Cull Front
  16. CGPROGRAM
  17. #pragma vertex vert
  18. #pragma fragment frag
  19. #include "UnityCG.cginc"
  20. fixed _Outline;
  21. fixed4 _OutlineColor;
  22. struct v2f
  23. {
  24. float4 pos : SV_POSITION;
  25. float4 uv : TEXCOORD0;
  26. };
  27. v2f vert (appdata_full v)
  28. {
  29. v2f o;
  30. //位置从自身坐标系转换到投影空间
  31. //旧版本o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
  32. o.pos = UnityObjectToClipPos(v.vertex);
  33. //方式二,扩张顶点位置
  34. //法线变换到投影空间
  35. //float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
  36. //得到投影空间的偏移
  37. //float2 offset = TransformViewToProjection(normal.xy);
  38.  
  39. ////方式三,把顶点当做方向矢量,在方向矢量的方向偏移
  40. float3 dir = normalize(v.vertex.xyz);
  41. dir = mul((float3x3)UNITY_MATRIX_IT_MV, dir);
  42. float2 offset = TransformViewToProjection(dir.xy);
  43. //有一些情况下,侧边看不到,所以把方式一和二的算法相结合
  44. //float3 dir = normalize(v.vertex.xyz);
  45. //float3 dir2 = v.normal;
  46. //float D = dot(dir, dir2);
  47. //D = (1 + D / _Outline) / (1 + 1 / _Outline);
  48. //dir = lerp(dir2, dir, D);
  49. //dir = mul((float3x3)UNITY_MATRIX_IT_MV, dir);
  50. //float2 offset = TransformViewToProjection(dir.xy);
  51. //offset = normalize(offset);
  52. //在xy两个方向上偏移顶点的位置
  53. o.pos.xy += offset * o.pos.z * _Outline;
  54. return o;
  55. }
  56. float4 frag (v2f i) : COLOR
  57. {
  58. return _OutlineColor; //描边
  59. }
  60. ENDCG
  61. }
  62. //第二个批次,略
  63. }

 

顶点的视角dir和法线dir点乘,得出偏离度,越靠近边缘,颜色的强度越高。

优点是节约批次。

  1. v2f vert (appdata_full v)
  2. {
  3.   v2f o;
  4.   //
      //_RimColor边缘光颜色  
      //_RimPower边缘光强度
  5.   float3 viewDir = normalize(ObjSpaceViewDir(v.vertex));
  6.   float dotProduct = 1 - dot(normalize(v.normal), viewDir);
  7.   fixed3 rimCol = smoothstep(1 - _RimPower, 1.0, dotProduct) * _RimColor;
  8.   o.color = rimCol;
  9.   //
  10.  return o;
  11. }

  

优点是效果完美,缺点是消耗性能。

摄像机上挂一个脚本,处理后处理的步骤,outlineCamera 为临时摄像机,参数与主摄像机相同,看着同样的Unit层。

临时摄像机渲染到RT上,先画剪影,然后用自定义的描边shader画上去。

  1. using UnityEngine;
  2. using UnitySampleAssets.ImageEffects;
  3. [RequireComponent(typeof(Camera))]
  4. [AddComponentMenu("Image Effects/Other/Post Effect Outline")]
  5. class PostEffectOutline : PostEffectsBase
  6. {
  7. public enum OutLineMethod
  8. {
  9. eIteration,
  10. eScale,
  11. }
  12. private Camera attachCamera;
  13. private Camera outlineCamera;
  14. private Shader simpleShader;
  15. private Shader postOutlineShader;
  16. private Material postOutlineMat;
  17. private RenderTexture mTempRT;
  18. public Color outlineColor = new Color(0, 1f, 0, 1f);// Color.green;
  19. [Range(0, 10)]
  20. public int outlineWidth = 1;
  21. [Range(1, 9)]
  22. public int iterations = 1;
  23. public OutLineMethod outlineMethod = OutLineMethod.eIteration;
  24. void Awake()
  25. {
  26. FindShaders();
  27. }
  28. void FindShaders()
  29. {
  30. if (!simpleShader)
  31. simpleShader = Shader.Find("ly/DrawSimple");
  32. if (outlineMethod == OutLineMethod.eIteration)
  33. {
  34. if (!postOutlineShader)
  35. postOutlineShader = Shader.Find("ly/PostOutlineIteration");
  36. }
  37. else
  38. {
  39. if (!postOutlineShader)
  40. postOutlineShader = Shader.Find("ly/PostOutlineScale");
  41. }
  42. }
  43. protected override void Start()
  44. {
  45. base.Start();
  46. attachCamera = GetComponent<Camera>();
  47. if (outlineCamera == null)
  48. {
  49. outlineCamera = new GameObject().AddComponent<Camera>();
  50. outlineCamera.enabled = false;
  51. outlineCamera.transform.parent = attachCamera.transform;
  52. outlineCamera.name = "outlineCam";
  53. }
  54. postOutlineMat = new Material(postOutlineShader);
  55. }
  56. public override bool CheckResources()
  57. {
  58. CheckSupport(false);
  59. if (!isSupported)
  60. ReportAutoDisable();
  61. return isSupported;
  62. }
  63. private void OnRenderImage(RenderTexture source, RenderTexture destination)
  64. {
  65. if (CheckResources() == false)
  66. {
  67. Graphics.Blit(source, destination);
  68. return;
  69. }
  70. outlineCamera.CopyFrom(attachCamera);
  71. outlineCamera.clearFlags = CameraClearFlags.Color;
  72. outlineCamera.backgroundColor = Color.black;
  73. outlineCamera.cullingMask = 1 << LayerMask.NameToLayer("Unit");
  74. if (mTempRT == null)
  75. mTempRT = RenderTexture.GetTemporary(source.width, source.height, source.depth);
  76. mTempRT.Create();
  77. outlineCamera.targetTexture = mTempRT;
  78. outlineCamera.RenderWithShader(simpleShader, "");
  79. postOutlineMat.SetTexture("_SceneTex", source);
  80. postOutlineMat.SetColor("_Color", outlineColor);
  81. postOutlineMat.SetInt("_Width", outlineWidth);
  82. postOutlineMat.SetInt("_Iterations", iterations);
  83. //画描边混合材质
  84. Graphics.Blit(mTempRT, destination, postOutlineMat);
  85. mTempRT.Release();
  86. }
  87. }

 

先用简单的shader画出剪影

  1. Shader "ly/DrawSimple"
  2. {
  3. FallBack OFF
  4. }

 

然后就是这个自定义的描边shader画的过程。

第一种是类似高斯模糊的方式来迭代,迭代次数越多则越细腻。

  1. // ly 类似高斯模糊方式迭代循环处理描边
  2. Shader "ly/PostOutlineIteration"
  3. {
  4. Properties
  5. {
  6. _MainTex("Main Texture", 2D) = "black"{} //画完物体面积后的纹理
  7. _SceneTex("Scene Texture", 2D) = "black"{} //原场景纹理
  8. _Color("Outline Color", Color) = (0,1,0,0.8) //描边颜色
  9. _Width("Outline Width", int) = 1 //描边宽度
  10. _Iterations("Iterations", int) = 1 //描边迭代次数(越多越平滑,消耗越高)
  11. }
  12. SubShader
  13. {
  14. Pass
  15. {
  16. CGPROGRAM
  17. sampler2D _MainTex;
  18. float2 _MainTex_TexelSize;
  19. sampler2D _SceneTex;
  20. fixed4 _Color;
  21. float _Width;
  22. int _Iterations;
  23. #pragma vertex vert
  24. #pragma fragment frag
  25. #include "UnityCG.cginc"
  26.  
  27. struct v2f
  28. {
  29. float4 pos : SV_POSITION;
  30. float2 uv : TEXCOORD0;
  31. };
  32. v2f vert(appdata_base v)
  33. {
  34. v2f o;
  35. o.pos = UnityObjectToClipPos(v.vertex);
  36. o.uv = v.texcoord.xy;
  37. return o;
  38. }
  39. half4 frag(v2f i) : COLOR
  40. {
  41. //迭代次数为奇数,保证对称
  42. int iterations = _Iterations * 2 + 1;
  43. float ColorIntensityInRadius;
  44. float Tx_x = _MainTex_TexelSize.x * _Width;
  45. float Tx_y = _MainTex_TexelSize.y * _Width;
  46. //计算是否大于0,则此像素属于外边的范围内
  47. for (int k=0; k<iterations; k+=1)
  48. {
  49. for (int j=0; j<iterations; j+=1)
  50. {
  51. ColorIntensityInRadius += tex2D(_MainTex, i.uv.xy + float2((k - iterations / 2) * Tx_x, (j - iterations / 2) * Tx_y));
  52. }
  53. }
  54. //如果有颜色,或者不在外边的范围内,则渲染原场景。否则,在外边内,渲染描边。
  55. if (tex2D(_MainTex, i.uv.xy).r > 0 || ColorIntensityInRadius == 0)
  56. return tex2D(_SceneTex, i.uv);
  57. else
  58. return _Color.a * _Color + (1 - _Color.a)*tex2D(_SceneTex, i.uv);
  59. }
  60. ENDCG
  61. }
  62. }
  63. }

第二种方法简单些,直接把剪影的部分uv扩大,再把原图叠上去。

  1. // ly 扩张剪影uv来填充描边
  2. Shader "ly/PostOutlineScale"
  3. {
  4. Properties
  5. {
  6. _MainTex("Main Texture", 2D) = "black"{} //画完物体面积后的纹理
  7. _SceneTex("Scene Texture", 2D) = "black"{} //原场景纹理
  8. _Color("Outline Color", Color) = (0,1,0,1) //描边颜色
  9. _Width("Outline Width", float) = 1 //描边宽度
  10. }
  11. SubShader
  12. {
  13. Pass
  14. {
  15. CGPROGRAM
  16. sampler2D _MainTex;
  17. sampler2D _SceneTex;
  18. float2 _SceneTex_TexelSize;
  19. fixed4 _Color;
  20. float _Width;
  21. #pragma vertex vert
  22. #pragma fragment frag
  23. #include "UnityCG.cginc"
  24.  
  25. struct v2f
  26. {
  27. float4 pos : SV_POSITION;
  28. half2 uv[2] : TEXCOORD0;
  29. half2 uv2[4] : TEXCOORD2;
  30. };
  31. v2f vert(appdata_base v)
  32. {
  33. v2f o;
  34. o.pos = UnityObjectToClipPos(v.vertex);
  35. o.uv[0] = v.texcoord.xy;
  36. o.uv[1] = v.texcoord.xy;
  37. half2 offs = _SceneTex_TexelSize.xy * _Width;
  38. o.uv2[0].x = v.texcoord.x - offs.x;
  39. o.uv2[0].y = v.texcoord.y - offs.y;
  40. o.uv2[1].x = v.texcoord.x + offs.x;
  41. o.uv2[1].y = v.texcoord.y - offs.y;
  42. o.uv2[2].x = v.texcoord.x + offs.x;
  43. o.uv2[2].y = v.texcoord.y + offs.y;
  44. o.uv2[3].x = v.texcoord.x - offs.x;
  45. o.uv2[3].y = v.texcoord.y + offs.y;
  46. if (_SceneTex_TexelSize.y < 0)
  47. {
  48. o.uv[1].y = 1 - o.uv[1].y;
  49. o.uv2[0].y = 1 - o.uv2[0].y;
  50. o.uv2[1].y = 1 - o.uv2[1].y;
  51. o.uv2[2].y = 1 - o.uv2[2].y;
  52. o.uv2[3].y = 1 - o.uv2[3].y;
  53. }
  54. return o;
  55. }
  56. half4 frag(v2f i) : COLOR
  57. {
  58. fixed4 stencil = tex2D(_MainTex, i.uv[1]);
  59. // 有剪影的部分,显示原图
  60. if (any(stencil.rgb))
  61. {
  62. fixed4 framebuffer = tex2D(_SceneTex, i.uv[0]);
  63. return framebuffer;
  64. }
  65. // 没有剪影的部分,先把剪影扩张,扩张出颜色的部分用剪影,没有颜色的用原图
  66. else
  67. {
  68. fixed4 color1 = tex2D(_MainTex, i.uv2[0]);
  69. fixed4 color2 = tex2D(_MainTex, i.uv2[1]);
  70. fixed4 color3 = tex2D(_MainTex, i.uv2[2]);
  71. fixed4 color4 = tex2D(_MainTex, i.uv2[3]);
  72. fixed4 color;
  73. color.rgb = max(color1.rgb, color2.rgb);
  74. color.rgb = max(color.rgb, color3.rgb);
  75. color.rgb = max(color.rgb, color4.rgb);
  76. if (any(color.rgb))
  77. {
  78. return _Color;
  79. //color.a = (color1.a + color2.a + color3.a + color4.a) * 0.25;
  80. //return color;
  81. }
  82. else
  83. {
  84. fixed4 framebuffer = tex2D(_SceneTex, i.uv[0]);
  85. return framebuffer;
  86. }
  87. }
  88. }
  89. ENDCG
  90. }
  91. }
  92. SubShader
  93. {
  94. Pass
  95. {
  96. SetTexture[_MainTex]{}
  97. }
  98. }
  99. Fallback Off
  100. }

 

版权声明:本文为xiaohutu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xiaohutu/p/10834491.html