大扎好,我系狗猥。当你们都觉得我鸽了的时候,我又出现了,这也是一种鸽。创业两年失败后归来,今天想给你们分享一个我最近研究出来的好康的,比游戏还刺激,还能够教你登dua郎喔(大误html
此次给你们带来的是基于Shader实现的UGUI描边,也支持对Text
组件使用。git
首先请你们看看最终效果(上面放了一个Image
和一个Text
):
github
(8102年了怎么还在舰算法
接下来,我会向你们介绍思路和具体实现过程。若是你想直接代到项目里使用,请自行跳转到本文最后,那里有完整的C#和Shader代码。canvas
本方案在Unity 2017.3.1p1下测试经过。c#
本文参考了http://blog.sina.com.cn/s/blog_6ad33d350102xb7v.htmlapp
转载请注明出处:http://www.javashuo.com/article/p-dupzxqqa-ge.html编辑器
就我参加工做这些年接触到的UI美术来看,他们都挺喜欢用描边效果。诚然这个效果可让文字更加突出,看着也挺不错。对美术来讲作描边简单的一比,PS里加个图层样式就搞定,可是对咱们程序来讲就是一件很痛苦的事。ide
UGUI自带的Outline
组件用过的同窗都知道,本质上是把元素复制四份,而后作一些偏移绘制出来。可是把偏移量放大,瞬间就穿帮了。若是美术要求作一个稍微宽一点的描边,这个组件是没法实现的。
函数
而后有先辈提出按照Outline
实现方式,增长复制份数的方法。请参考https://github.com/n-yoda/unity-vertex-effects。确实很是漂亮。可是这个作法有一个很是严重的问题:数量如此大的顶点数,对性能会有影响。咱们知道每一个字符是由两个三角形构成,总共6个顶点。若是文字数量大,再加上一个复制N份的脚本,顶点数会分分钟炸掉。
以复制8次为例,一段200字的文本在进行处理后会生成200 * 6 * (8+1) = 10800 个顶点,多么可怕。而且,Unity5.2之前的版本要求,每个Canvas
下至多只能有65535个顶点,超过就会报错。
TextMeshPro能作不少漂亮的效果。可是它的作法相似于图字,要提供全部会出现的字符。对于字符不多的英语环境,这没有问题,但对于中文环境,把全部字符弄进去是不现实的。还有最关键的是,它是做用于TextMesh
组件,而不是UGUI的Text
。
因而乎,使用Shader变成了最优解。
归纳讲,这个实现就是在C#代码中对UI顶点根据描边宽度进行外扩,而后在Shader的像素着色器中对像素的一周以描边宽度为半径采N个样,最后将颜色叠加起来。一般须要描边的元素尺寸都不大,故多重采样带来的性能影响几乎是能够忽略的。
建立一个OutlineEx.shader
。对于描边,咱们须要两个参数:描边的颜色和描边的参数。因此首先将这两个参数添加到Shader的属性中:
_OutlineColor("Outline Color", Color) = (1, 1, 1, 1) _OutlineWidth("Outline Width", Int) = 1
采样坐标用圆的参数方程计算。在Shader中进行三角函数运算比较吃性能,而且这里采样的角度是固定的,因此咱们能够把坐标直接写死。在Shader中添加采样的函数。由于最终进行颜色混合的时候只须要用到alpha值,因此函数不返回rgb:
fixed SampleAlpha(int pIndex, v2f IN) { const fixed sinArray[12] = { 0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 }; const fixed cosArray[12] = { 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866 }; float2 pos = IN.texcoord + _MainTex_TexelSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * _OutlineWidth; return (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w; }
而后在像素着色器中增长对方法的调用。
fixed4 frag(v2f IN) : SV_Target { fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; half4 val = half4(_OutlineColor.x, _OutlineColor.y, _OutlineColor.z, 0); // 注意:这里为了简化代码用了循环 // 尽可能不要在Shader中使用循环,多复制几回代码都行 for (int i = 0; i < 12; i++) { val.w += SampleAlpha(i, IN); } color = (val * (1.0 - color.a)) + (color * color.a); return color; }
接下来,在Unity中新建一个材质球,把Shader赋上去,挂在一个UGUI组件上,而后调整描边颜色和宽度,能够看到效果:
能够看到描边已经出现了,可是超出图片范围的部分被裁减掉了。因此接下来,咱们须要对图片的区域进行调整,保证描边的部分也被包含在区域内。
要扩展区域,就得修改顶点。Unity提供了BaseMeshEffect
类供开发者对UI组件的顶点进行修改。
建立一个OutlineEx
类,继承于BaseMeshEffect
类,实现其中的ModifyMesh(VertexHelper)
方法。参数VertexHelper
类提供了GetUIVertexStream(List<UIVertex>)
和AddUIVertexTriangleStream(List<UIVertex>)
方法用于获取和设置UI物件的顶点。
这里咱们能够把参数须要的List
提出来作成静态变量,这样可以避免每次ModifyMesh
调用时建立List
对象。
public class OutlineEx : BaseMeshEffect { public Color OutlineColor = Color.white; [Range(0, 6)] public int OutlineWidth = 0; private static List<UIVertex> m_VetexList = new List<UIVertex>(); protected override void Awake() { base.Awake(); var shader = Shader.Find("TSF Shaders/UI/OutlineEx"); base.graphic.material = new Material(shader); var v1 = base.graphic.canvas.additionalShaderChannels; var v2 = AdditionalCanvasShaderChannels.Tangent; if ((v1 & v2) != v2) { base.graphic.canvas.additionalShaderChannels |= v2; } this._Refresh(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); if (base.graphic.material != null) { this._Refresh(); } } #endif private void _Refresh() { base.graphic.material.SetColor("_OutlineColor", this.OutlineColor); base.graphic.material.SetInt("_OutlineWidth", this.OutlineWidth); base.graphic.SetVerticesDirty(); } public override void ModifyMesh(VertexHelper vh) { vh.GetUIVertexStream(m_VetexList); this._ProcessVertices(); vh.Clear(); vh.AddUIVertexTriangleStream(m_VetexList); } private void _ProcessVertices() { // TODO: 处理顶点 } }
如今已经能够获取到全部的顶点信息了。接下来咱们对它进行外扩。
咱们知道每三个顶点构成一个三角形,因此须要对构成三角形的三个顶点进行处理,而且要将它的UV坐标(决定图片在图集中的范围)也作对应的外扩,不然从视觉上看起来就只是图片被放大了一点点。
因而完成_ProcessVertices
方法:
private void _ProcessVertices() { for (int i = 0, count = m_VetexList.Count - 3; i <= count; i += 3) { var v1 = m_VetexList[i]; var v2 = m_VetexList[i + 1]; var v3 = m_VetexList[i + 2]; // 计算原顶点坐标中心点 // var minX = _Min(v1.position.x, v2.position.x, v3.position.x); var minY = _Min(v1.position.y, v2.position.y, v3.position.y); var maxX = _Max(v1.position.x, v2.position.x, v3.position.x); var maxY = _Max(v1.position.y, v2.position.y, v3.position.y); var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f; // 计算原始顶点坐标和UV的方向 // Vector2 triX, triY, uvX, uvY; Vector2 pos1 = v1.position; Vector2 pos2 = v2.position; Vector2 pos3 = v3.position; if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right)) > Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right))) { triX = pos2 - pos1; triY = pos3 - pos2; uvX = v2.uv0 - v1.uv0; uvY = v3.uv0 - v2.uv0; } else { triX = pos3 - pos2; triY = pos2 - pos1; uvX = v3.uv0 - v2.uv0; uvY = v2.uv0 - v1.uv0; } // 为每一个顶点设置新的Position和UV // v1 = _SetNewPosAndUV(v1, this.OutlineWidth, posCenter, triX, triY, uvX, uvY); v2 = _SetNewPosAndUV(v2, this.OutlineWidth, posCenter, triX, triY, uvX, uvY); v3 = _SetNewPosAndUV(v3, this.OutlineWidth, posCenter, triX, triY, uvX, uvY); // 应用设置后的UIVertex // m_VetexList[i] = v1; m_VetexList[i + 1] = v2; m_VetexList[i + 2] = v3; } } private static UIVertex _SetNewPosAndUV(UIVertex pVertex, int pOutLineWidth, Vector2 pPosCenter, Vector2 pTriangleX, Vector2 pTriangleY, Vector2 pUVX, Vector2 pUVY) { // Position var pos = pVertex.position; var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth; var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth; pos.x += posXOffset; pos.y += posYOffset; pVertex.position = pos; // UV var uv = pVertex.uv0; uv += pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1); uv += pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1); pVertex.uv0 = uv; return pVertex; } private static float _Min(float pA, float pB, float pC) { return Mathf.Min(Mathf.Min(pA, pB), pC); } private static float _Max(float pA, float pB, float pC) { return Mathf.Max(Mathf.Max(pA, pB), pC); }
而后能够在编辑器中调整描边颜色和宽度,能够看到效果:
OJ8K,如今范围已经被扩大,能够看到上下左右四个边的描边宽度没有被裁掉了。
在上一步的效果图中,咱们能够注意到图片的边界出现了被拉伸的部分。若是使用了图集或字体,在UV扩大后图片附近的像素也会被包含进来。为何会变成这样呢?(先打死)
由于前面说过,UV裁剪框就至关于图集中每一个小图的范围。直接扩大必然会包含到小图邻接的图的像素。因此这一步咱们须要对最终绘制出的图进行裁剪,保证这些不要的像素不被画出来。
裁剪的逻辑也很简单。若是该像素处于被扩大前的UV范围外,则设置它的alpha为0。这一步须要放在像素着色器中完成。如何将原始UV区域传进Shader是一个问题。对于Text
组件,全部字符的顶点都会进入Shader处理,因此在Shader中添加属性是不现实的。
好在Unity为咱们提供了门路,能够看UIVertex
结构体的成员:
public struct UIVertex { public static UIVertex simpleVert; public Vector3 position; public Vector3 normal; public Color32 color; public Vector2 uv0; public Vector2 uv1; public Vector2 uv2; public Vector2 uv3; public Vector4 tangent; }
而Unity默认只会使用到position
、normal
、uv0
和color
,其余成员是不会使用的。因此咱们能够考虑将原始UV框的数据(最小x,最小y,最大x,最大y)赋值给tangent
成员,由于它恰好是一个Vector4
类型。
固然,你想把数据分别放在uv1
和uv2
中也是能够的。
这里感谢真木网友的指正,UI在缩放时,tangent
的值会被影响,致使描边显示不全甚至彻底消失,因此应该赋值给uv1
和uv2
。经测试,Unity 5.6自身有bug,uv2
和uv3
不管怎么设置都不会被传入shader,但在2017.3.1p1和2018上测试经过。若是必需要使用低版本Unity,能够考虑使用uv1
和tangent.zw
存储原始UV框的四个值,但要求UI的Z轴不能缩放,且Canvas和摄像机必须正交。
须要注意的是,在Unity5.4(大概是这个版本吧,记不清了)以后,UIVertex的非必须成员的数据默认不会被传递进Shader。因此咱们须要修改UI组件的Canvas
的additionalShaderChannels
属性,让uv1
和uv2
成员也传入Shader。
var v1 = base.graphic.canvas.additionalShaderChannels; var v2 = AdditionalCanvasShaderChannels.TexCoord1; if ((v1 & v2) != v2) { base.graphic.canvas.additionalShaderChannels |= v2; } v2 = AdditionalCanvasShaderChannels.TexCoord2; if ((v1 & v2) != v2) { base.graphic.canvas.additionalShaderChannels |= v2; }
将原始UV框赋值给uv1
和uv2
成员
var uvMin = _Min(v1.uv0, v2.uv0, v3.uv0); var uvMax = _Max(v1.uv0, v2.uv0, v3.uv0); vertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y); vertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w); private static Vector2 _Min(Vector2 pA, Vector2 pB, Vector2 pC) { return new Vector2(_Min(pA.x, pB.x, pC.x), _Min(pA.y, pB.y, pC.y)); } private static Vector2 _Max(Vector2 pA, Vector2 pB, Vector2 pC) { return new Vector2(_Max(pA.x, pB.x, pC.x), _Max(pA.y, pB.y, pC.y)); }
而后在Shader的顶点着色器中获取它:
struct appdata { // 省略 float2 texcoord1 : TEXCOORD1; float2 texcoord2 : TEXCOORD2; }; struct v2f { // 省略 float2 uvOriginXY : TEXCOORD1; float2 uvOriginZW : TEXCOORD2; }; v2f vert(appdata IN) { // 省略 o.uvOriginXY = IN.texcoord1; o.uvOriginZW = IN.texcoord2; // 省略 }
断定一个点是否在给定矩形框内,能够用到内置的step
函数。它经常使用于做比较,替代if/else
语句提升效率。它的逻辑是:顺序给定两个参数a和b,若是 a > b 返回0,不然返回1。
添加断定函数:
fixed IsInRect(float2 pPos, float2 pClipRectXY, float2 pClipRectZW) { pPos = step(pClipRectXY, pPos) * step(pPos, pClipRectZW); return pPos.x * pPos.y; }
而后在采样和像素着色器中添加对它的调用:
fixed SampleAlpha(int pIndex, v2f IN) { // 省略 return IsInRect(pos, IN.uvOriginXY, IN.uvOriginZW) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w; } fixed4 frag(v2f IN) : SV_Target { // 省略 if (_OutlineWidth > 0) { color.w *= IsInRect(IN.texcoord, IN.uvOriginXY, IN.uvOriginZW); // 省略 } }
那么如今就能够获得最终效果了。在个人代码中,对每一个像素作了12次采样。若是美术要求对大图片进行比较粗的描边,须要增长采样次数。固然,若是字自己小,也能够下降次数。
因为这个Shader是给UI用的,因此须要将UI-Default.shader
中的一些属性和设置复制到咱们的Shader中。
//———————————————————————————————————————————— // OutlineEx.cs // // Created by Chiyu Ren on 2018/9/12 23:03:51 //———————————————————————————————————————————— using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; namespace TooSimpleFramework.UI { /// <summary> /// UGUI描边 /// </summary> public class OutlineEx : BaseMeshEffect { public Color OutlineColor = Color.white; [Range(0, 6)] public int OutlineWidth = 0; private static List<UIVertex> m_VetexList = new List<UIVertex>(); protected override void Start() { base.Start(); var shader = Shader.Find("TSF Shaders/UI/OutlineEx"); base.graphic.material = new Material(shader); var v1 = base.graphic.canvas.additionalShaderChannels; var v2 = AdditionalCanvasShaderChannels.TexCoord1; if ((v1 & v2) != v2) { base.graphic.canvas.additionalShaderChannels |= v2; } v2 = AdditionalCanvasShaderChannels.TexCoord2; if ((v1 & v2) != v2) { base.graphic.canvas.additionalShaderChannels |= v2; } this._Refresh(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); if (base.graphic.material != null) { this._Refresh(); } } #endif private void _Refresh() { base.graphic.material.SetColor("_OutlineColor", this.OutlineColor); base.graphic.material.SetInt("_OutlineWidth", this.OutlineWidth); base.graphic.SetVerticesDirty(); } public override void ModifyMesh(VertexHelper vh) { vh.GetUIVertexStream(m_VetexList); this._ProcessVertices(); vh.Clear(); vh.AddUIVertexTriangleStream(m_VetexList); } private void _ProcessVertices() { for (int i = 0, count = m_VetexList.Count - 3; i <= count; i += 3) { var v1 = m_VetexList[i]; var v2 = m_VetexList[i + 1]; var v3 = m_VetexList[i + 2]; // 计算原顶点坐标中心点 // var minX = _Min(v1.position.x, v2.position.x, v3.position.x); var minY = _Min(v1.position.y, v2.position.y, v3.position.y); var maxX = _Max(v1.position.x, v2.position.x, v3.position.x); var maxY = _Max(v1.position.y, v2.position.y, v3.position.y); var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f; // 计算原始顶点坐标和UV的方向 // Vector2 triX, triY, uvX, uvY; Vector2 pos1 = v1.position; Vector2 pos2 = v2.position; Vector2 pos3 = v3.position; if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right)) > Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right))) { triX = pos2 - pos1; triY = pos3 - pos2; uvX = v2.uv0 - v1.uv0; uvY = v3.uv0 - v2.uv0; } else { triX = pos3 - pos2; triY = pos2 - pos1; uvX = v3.uv0 - v2.uv0; uvY = v2.uv0 - v1.uv0; } // 计算原始UV框 // var uvMin = _Min(v1.uv0, v2.uv0, v3.uv0); var uvMax = _Max(v1.uv0, v2.uv0, v3.uv0); var uvOrigin = new Vector4(uvMin.x, uvMin.y, uvMax.x, uvMax.y); // 为每一个顶点设置新的Position和UV,并传入原始UV框 // v1 = _SetNewPosAndUV(v1, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin); v2 = _SetNewPosAndUV(v2, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin); v3 = _SetNewPosAndUV(v3, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin); // 应用设置后的UIVertex // m_VetexList[i] = v1; m_VetexList[i + 1] = v2; m_VetexList[i + 2] = v3; } } private static UIVertex _SetNewPosAndUV(UIVertex pVertex, int pOutLineWidth, Vector2 pPosCenter, Vector2 pTriangleX, Vector2 pTriangleY, Vector2 pUVX, Vector2 pUVY, Vector4 pUVOrigin) { // Position var pos = pVertex.position; var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth; var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth; pos.x += posXOffset; pos.y += posYOffset; pVertex.position = pos; // UV var uv = pVertex.uv0; uv += pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1); uv += pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1); pVertex.uv0 = uv; // 原始UV框 pVertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y); pVertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w); return pVertex; } private static float _Min(float pA, float pB, float pC) { return Mathf.Min(Mathf.Min(pA, pB), pC); } private static float _Max(float pA, float pB, float pC) { return Mathf.Max(Mathf.Max(pA, pB), pC); } private static Vector2 _Min(Vector2 pA, Vector2 pB, Vector2 pC) { return new Vector2(_Min(pA.x, pB.x, pC.x), _Min(pA.y, pB.y, pC.y)); } private static Vector2 _Max(Vector2 pA, Vector2 pB, Vector2 pC) { return new Vector2(_Max(pA.x, pB.x, pC.x), _Max(pA.y, pB.y, pC.y)); } } }
Shader
Shader "TSF Shaders/UI/OutlineEx" { Properties { _MainTex ("Main Texture", 2D) = "white" {} _Color ("Tint", Color) = (1, 1, 1, 1) _OutlineColor ("Outline Color", Color) = (1, 1, 1, 1) _OutlineWidth ("Outline Width", Int) = 1 _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "OUTLINE" CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _MainTex_TexelSize; float4 _OutlineColor; int _OutlineWidth; struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float2 texcoord1 : TEXCOORD1; float2 texcoord2 : TEXCOORD2; fixed4 color : COLOR; }; struct v2f { float4 vertex : SV_POSITION; float2 texcoord : TEXCOORD0; float2 uvOriginXY : TEXCOORD1; float2 uvOriginZW : TEXCOORD2; fixed4 color : COLOR; }; v2f vert(appdata IN) { v2f o; o.vertex = UnityObjectToClipPos(IN.vertex); o.texcoord = IN.texcoord; o.uvOriginXY = IN.texcoord1; o.uvOriginZW = IN.texcoord2; o.color = IN.color * _Color; return o; } fixed IsInRect(float2 pPos, float4 pClipRect) { pPos = step(pClipRect.xy, pPos) * step(pPos, pClipRect.zw); return pPos.x * pPos.y; } fixed SampleAlpha(int pIndex, v2f IN) { const fixed sinArray[12] = { 0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 }; const fixed cosArray[12] = { 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866 }; float2 pos = IN.texcoord + _MainTex_TexelSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * _OutlineWidth; return IsInRect(pos, IN.uvOriginXY, IN.uvOriginZW) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w; } fixed4 frag(v2f IN) : SV_Target { fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; if (_OutlineWidth > 0) { color.w *= IsInRect(IN.texcoord, IN.uvOriginXY, IN.uvOriginZW); half4 val = half4(_OutlineColor.x, _OutlineColor.y, _OutlineColor.z, 0); val.w += SampleAlpha(0, IN); val.w += SampleAlpha(1, IN); val.w += SampleAlpha(2, IN); val.w += SampleAlpha(3, IN); val.w += SampleAlpha(4, IN); val.w += SampleAlpha(5, IN); val.w += SampleAlpha(6, IN); val.w += SampleAlpha(7, IN); val.w += SampleAlpha(8, IN); val.w += SampleAlpha(9, IN); val.w += SampleAlpha(10, IN); val.w += SampleAlpha(11, IN); val.w = clamp(val.w, 0, 1); color = (val * (1.0 - color.a)) + (color * color.a); } return color; } ENDCG } } }
最终效果:
能够看到在最后的像素着色器中使用了if语句。由于我比较菜,写出来的颜色混合算法在描边宽度为0的时候看起来效果很很差。
若是有大神能提供一个更优的算法,欢迎在评论中把我批判一番。把if语句去掉,能够提高必定的性能。
还有一点是,若是将图片或文字自己的透明度设为0,并不能获得镂空的效果。若是美术提出要这个效果,请绝不犹豫打死(误
最后一点,仔细观察上面最终效果的Ass,能够发现它们的字符自己被后一个字符的描边覆盖了一部分。使用两个Pass能够解决,一个只绘制描边,另外一个只绘制自己。
Pass1
fixed4 frag(v2f IN) : SV_Target { // 省略 val.w = clamp(val.w, 0, 1); return val; }
Pass2
fixed4 frag(v2f IN) : SV_Target { fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; color.w *= IsInRect(IN.texcoord, IN.uvOriginXY, IN.uvOriginZW); return color; }
改动很简单,具体实现就留给读者了。
首先要感谢提供这个思路的原做者。否则我还真想不出能够这么作。看来我毕竟仍是图样。
但愿这篇博文能帮到须要的朋友,由于网上几乎没有这个的教程。以前在别人的博客看到一句话:人生就是水桶,前三十年你们给你灌水,后三十年你给你们灌水。感受挺有意思。从此会继续分享一些本身搞出的、网上少有的东西(虽然我还没到30)。
最近却是没有特别在作什么,不过有在学习Shader,进入了未知♂领域。买了一些书,想给你们推荐冯乐乐的《Unity Shader入门精要》(博客https://blog.csdn.net/candycat1992/),对入门挺有帮助。知道该书做者是比我小一岁可是比我牛逼太多的美女程序媛(不要YY了,有对象的)的时候我真的受到了极大刺激。一个妹子都能钻得这么深,我应该更加努力啊。学习是从摇篮到坟墓的过程,但愿你们无论学什么都要坚持。
还有一点就是创业真的要谨慎。最近了解到国家出了条例要对国产游戏限量发行,对各个游戏公司想必都是一记闷锤。加之统一征收社保,引发的连锁反应必然会波及到游戏行业。惟一欣慰的是咱们还能作游戏,还能在这条路上继续走。那么就继续走下去吧,不要停下来啊!(指加班)
很惭愧,就作了一点微小的工做,谢谢你们!