大紮好,我係狗猥。當你們都覺得我鴿了的時候,我又出現了,這也是一種鴿。創業兩年失敗後歸來,今天想給你們分享一個我最近研究出來的好康的,比遊戲還刺激,還能夠教你登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了,有對象的)的時候我真的受到了極大刺激。一個妹子都能鑽得這麼深,我應該更加努力啊。學習是從搖籃到墳墓的過程,但願你們無論學什麼都要堅持。
還有一點就是創業真的要謹慎。最近了解到國家出了條例要對國產遊戲限量發行,對各個遊戲公司想必都是一記悶錘。加之統一徵收社保,引發的連鎖反應必然會波及到遊戲行業。惟一欣慰的是咱們還能作遊戲,還能在這條路上繼續走。那麼就繼續走下去吧,不要停下來啊!(指加班)
很慚愧,就作了一點微小的工做,謝謝你們!