unity shader 实现通用描边shader -文字描边-字体描边

这篇具有很好参考价值的文章主要介绍了unity shader 实现通用描边shader -文字描边-字体描边。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在制作游戏时,可以遇到要对字体添加描边的需求,unity 的UGUI自带的OutLine组件,描边效果不好,宽度过大会出现穿帮,顶点数量也会增加,性能不好,如果对于有几百字,顶点数量会很多,而且无法扩展功能
unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序
可以看出Outline创建了4个方向的文字
unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序
Unity5.2以前的版本要求,每一个Canvas下至多只能有2^16-1=65535个顶点(使用2个字节(16位)存储顶点索引),超过就会报错
以上的种种原因,让我们不得不自己编写文字图片的描边shader
在网上找了一圈资料后,发现一篇不错的文章,这个应该是自己实现文本shader最经典的文章
但是好像都没有对uv偏移进行说明(最难的地方)

效果展示

unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序
unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序

Shader实现基础描边

基本思路,将uv在片元着色器进行偏移,比较a通道,偏移后的图片.a-原来的图片.a
这里只是用ase可视化演示一下,实际用shader代码实现
unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序

沿着8或12个方向进行偏移,偏移的方向越多,描边宽度大时不容易穿帮
在基础描边这里暂时声明其它变量
shader属性这里不声明其它的属性,在C#脚本中不使用material.SetColor(“_描边颜色”, _描边颜色);
因为这会导致材质不同,无法让unity动态批合并,会增加drawcall

Properties
{
//这个特性可以和Unity组件中的属性产生关联
//表示该属性是与每个渲染器相关的数据
//在Shader中使用此特性声明的属性会在每个渲染器实例中共享
    [PerRendererData]_MainTex ("Texture", 2D) = "white" {}
    _OutLineColor("OutLineColor",Color)=(1,0,0,0)
    _OutLineWidth("OutLineWidth",Float)=1
}

Tags设置透明渲染队列和透明渲染模式,开启透明Blend

SubShader
{
    Tags
    {
        "Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"
    }
    Lighting Off
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha

顶点着色器,基础普通的顶点着色器

v2f vert(appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    return o;
}

片元着色器
在12个方向进行采样,OffsetX取cos值,OffsetY取sin值作为权重

fixed SampleTex(v2f i,fixed ii,fixed color_a)
{
    const fixed OffsetX[12] = {1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866};
    const fixed OffsetY[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5};
    fixed2 offset_uv = i.uv + fixed2(OffsetX[ii], OffsetY[ii])* _MainTex_TexelSize.xy * _OutLineWidth;
    fixed sample_a = tex2D(_MainTex, offset_uv).a;
    fixed a = sample_a;//-color.a;不减去color.a,因为在文字边缘a=0-1的部分,会为0
    //比如向上偏移,下面边缘的a为0-1,减去1,负数,saturate后为0,这个值应该保留,否则边缘颜色由原来的片元决定,没有混合过渡
    return a;
}
fixed4 frag(v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    fixed sum_a = 0;//叠加各个方向采样a的结果
    sum_a += SampleTex(i, 0, col.a);
    sum_a += SampleTex(i, 1, col.a);
    sum_a += SampleTex(i, 2, col.a);
    sum_a += SampleTex(i, 3, col.a);
    sum_a += SampleTex(i, 4, col.a);
    sum_a += SampleTex(i, 5, col.a);
    sum_a += SampleTex(i, 6, col.a);
    sum_a += SampleTex(i, 7, col.a);
    sum_a += SampleTex(i, 8, col.a);
    sum_a += SampleTex(i, 9, col.a);
    sum_a += SampleTex(i, 10, col.a);
    sum_a += SampleTex(i, 11, col.a);
    sum_a=saturate(sum_a);
    fixed4 outLineColor=fixed4(_OutLineColor.rgb,sum_a);
    fixed4 finalCol=lerp(outLineColor,col,col.a);//没有文字的地方a为0,由描边决定,文字边界a为0-1由文字颜色和描边共同决定
    return finalCol;
}

问题:

  • 可以发现文字边缘有其它的文本,只是因为文本对于的纹理被打包到一个大的图集中,uv偏移后,会采样到临近的文本像素
  • 观察N的左侧可以看出,描边的区域有一部分超出范围为裁剪了
    解决方案:
  1. 要在C#里面得到偏移之前原来的uv范围,根据uv范围,将不在范围的片元a设置为即可
  2. 要对顶点进行扩展,让三角形范围变大,同时要等比例扩展uv,只扩展顶点,仅仅只是图片被放大了,还是会被裁剪,
    ,同时扩展顶点和uv,这样就不会被裁剪了
    unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序

实现通用的shader

C#部分

在C#脚本要进行顶点的扩展,进行uv的扩展
使用C#脚本传递描边颜色和文本颜色
大致看一下BaseMeshEffect

  1. 在Canvas中要开启uv1和uv2
protected override void Start()
{
    UseUVChannels();
}
private void UseUVChannels()
{
    var shader = Shader.Find("DSShader/TextOutline");
    base.graphic.material = new Material(shader);
    AdditionalCanvasShaderChannels 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;
    }
}

上面的 |=操作不明白可以学习一下|=位或操作
比如

int a = 5;  // 二进制表示: 0000 0101
int b = 3;  // 二进制表示: 0000 0011
a |= b;    // 执行位或运算,并赋值给 a,有1则1
Console.WriteLine(a);  // 输出: 7 (二进制表示: 0000 0111)

可以发现AdditionalCanvasShaderChannels枚举定义的数字刚好是2^n为了位或操作

public enum AdditionalCanvasShaderChannels
{
  None = 0,
  TexCoord1 = 1,
  TexCoord2 = 2,
  TexCoord3 = 4,
  Normal = 8,
  Tangent = 16, // 0x00000010
}
[ExecuteAlways]
public abstract class BaseMeshEffect : UIBehaviour, IMeshModifier
{
    [NonSerialized]
    private Graphic m_Graphic;

    /// <summary>
    /// The graphic component that the Mesh Effect will aplly to.
    /// </summary>
    protected Graphic graphic
    {
        get
        {
            if (m_Graphic == null)
                m_Graphic = GetComponent<Graphic>();

            return m_Graphic;
        }
    }

已经BaseMeshEffect 带有[ExecuteAlways],子类会在编辑器模式运行
我们要使用BaseMeshEffect , BaseMeshEffect 是一个抽象类,用于实现自定义的 Mesh 效果.用于扩展和修改 UI 元素的网格Mesh数据.通过继承 BaseMeshEffect 类并实现其中的方法,可以对 UI 元素的网格进行自定义的修改和效果应用.
2. 重载ModifyMesh,更改UIVertex数据

public class TextOutline : BaseMeshEffect
{
	List<UIVertex> _uiVertices = new List<UIVertex>();
	[Range(0, 6)]
	public float outLineWidth = 1;
	public Color EdgeColor = Color.red;
	public Color TextColor = Color.red;
	public override void ModifyMesh(VertexHelper vh)
	{
	    vh.GetUIVertexStream(_uiVertices);
	    ModifyUIVertexs(_uiVertices);//下面的函数
	    vh.Clear();
	    vh.AddUIVertexTriangleStream(_uiVertices);
    }
}

ModifyUIVertexs遍历UIVertex,每次拿到3个顶点数据
偏移顶点pos的思路,判断顶点是否大于三角形的中心点,x或y大于则向上或向右偏移,加上描边宽度outLineWidth

void ModifyUIVertexs(List<UIVertex> uiVertices)
{
    for (int i = 0; i <= uiVertices.Count - 3; i += 3)
    {
        UIVertex uiVertex1 = uiVertices[i];
        UIVertex uiVertex2 = uiVertices[i + 1];
        UIVertex uiVertex3 = uiVertices[i + 2];
        Vector3 pos1 = uiVertex1.position;
        Vector3 pos2 = uiVertex2.position;
        Vector3 pos3 = uiVertex3.position;

        Vector2 uv1 = uiVertex1.uv0;
        Vector2 uv2 = uiVertex2.uv0;
        Vector2 uv3 = uiVertex3.uv0;
        //得到三角形的中心点,用于顶点的偏移
        Vector3 pos_center = (pos1 + pos2 + pos3) / 3;
        Vector2 uv_min = new Vector2(Mathf.Min(uv1.x, uv2.x, uv3.x), Mathf.Min(uv1.y, uv2.y, uv3.y));
        Vector2 uv_max = new Vector2(Mathf.Max(uv1.x, uv2.x, uv3.x), Mathf.Max(uv1.y, uv2.y, uv3.y));
        Vector4 uv_border = new Vector4(uv_min.x, uv_min.y, uv_max.x, uv_max.y);
        //得到uv的范围,传递给shader判断
        //以pos和uv都以pos2或uv2为原点
        Vector2 pos_base1 = pos1 - pos2;
        Vector2 pos_base2 = pos3 - pos2;

        Vector2 uv_base1 = uv1 - uv2;
        Vector2 uv_base2 = uv3 - uv2;
        
        uiVertices[i] = ModifyPosUV(uiVertex1, pos_center, pos_base1, pos_base2, uv_base1, uv_base2, uv_border);
        uiVertices[i + 1] = ModifyPosUV(uiVertex2, pos_center, pos_base1, pos_base2, uv_base1, uv_base2, uv_border);
        uiVertices[i + 2] = ModifyPosUV(uiVertex3, pos_center, pos_base1, pos_base2, uv_base1, uv_base2, uv_border);
    }
}

计算pos顶点的偏移很好理解,但是uv的计算比较复杂,要使用线性代数的知识,对旋转缩放矩阵有一定的理解

UIVertex ModifyPosUV(UIVertex uiVertex, Vector3 pos_centor,
    Vector2 pos_base1, Vector2 pos_base2,
    Vector2 uv_base1, Vector2 uv_base2, Vector4 uv_border)
{
    //偏移pos
    Vector3 pos = uiVertex.position;
    float offsetX = pos.x > pos_centor.x ? outLineWidth : -outLineWidth;
    float offsetY = pos.y > pos_centor.y ? outLineWidth : -outLineWidth;
    pos.x += offsetX;
    pos.y += offsetY;
    uiVertex.position = pos;
    Vector2 offset = new Vector2(offsetX, offsetY);
    //uv偏移
    Vector2 uv = uiVertex.uv0;
    Matrix2x2 pos_m = new Matrix2x2(pos_base1.x,pos_base2.x,pos_base1.y,pos_base2.y);
     pos_m=pos_m.Inverse();
    Matrix2x2 uv_m = new Matrix2x2(uv_base1.x, uv_base2.x, uv_base1.y, uv_base2.y);
    Vector2 uv_offset = uv_m * pos_m * offset;
    uv += uv_offset;
    //设置偏移后的uv,uv0.z设置为描边宽度
    uiVertex.uv0 = new Vector4(uv.x, uv.y, outLineWidth, 0);
    //设置原始uv范围
    uiVertex.uv1 = uv_border;
    //设置文本颜色
    uiVertex.uv2 = new Vector4(EdgeColor.r,EdgeColor.g,EdgeColor.b,EdgeColor.a);
    //Color=>Color32,UIVertex的color类型是Color32
    Color32 color32 = (Color32)TextColor;
    uiVertex.color =color32;
    return uiVertex;
}

下面重点讲解uv偏移部分
这里要自己实现Matrix2x2类,实现矩阵×矩阵,矩阵×向量,矩阵取逆

	//uv偏移
    Vector2 uv = uiVertex.uv0;
    Matrix2x2 pos_m = new Matrix2x2(pos_base1.x,pos_base2.x,pos_base1.y,pos_base2.y);
     pos_m=pos_m.Inverse();
    Matrix2x2 uv_m = new Matrix2x2(uv_base1.x, uv_base2.x, uv_base1.y, uv_base2.y);
    Vector2 uv_offset = uv_m * pos_m * offset;
    uv += uv_offset;

对于uv映射

unity字体描边是哪个,unity,游戏引擎,着色器,技术美术,游戏程序
Matrix2×2类

public class Matrix2x2
{
    private float[,] matrix = new float[2, 2];

    public Matrix2x2(float a, float b, float c, float d)
    {
        matrix[0, 0] = a;
        matrix[0, 1] = b;
        matrix[1, 0] = c;
        matrix[1, 1] = d;
    }

    public float Determinant()
    {
        return matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0];
    }

    public Matrix2x2 Inverse()
    {
        float det = Determinant();
        float invDet = 1 / det;
        float a = matrix[1, 1] * invDet;
        float b = -matrix[0, 1] * invDet;
        float c = -matrix[1, 0] * invDet;
        float d = matrix[0, 0] * invDet;

        return new Matrix2x2(a, b, c, d);
    }

    public static Matrix2x2 operator *(Matrix2x2 m1, Matrix2x2 m2)
    {
        float a = m1.matrix[0, 0] * m2.matrix[0, 0] + m1.matrix[0, 1] * m2.matrix[1, 0];
        float b = m1.matrix[0, 0] * m2.matrix[0, 1] + m1.matrix[0, 1] * m2.matrix[1, 1];
        float c = m1.matrix[1, 0] * m2.matrix[0, 0] + m1.matrix[1, 1] * m2.matrix[1, 0];
        float d = m1.matrix[1, 0] * m2.matrix[0, 1] + m1.matrix[1, 1] * m2.matrix[1, 1];

        return new Matrix2x2(a, b, c, d);
    }

    public static Vector2 operator *(Matrix2x2 m, Vector2 v)
    {
        float x = m.matrix[0, 0] * v.x + m.matrix[0, 1] * v.y;
        float y = m.matrix[1, 0] * v.x + m.matrix[1, 1] * v.y;

        return new Vector2(x, y);
    }
}

Shader部分

在Properties只说明_MainTex ,其它参数由C#传入

Properties
    {
        [PerRendererData]_MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"
        }
        Cull Off
        Lighting Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

顶点和片元结构体

struct appdata {
    float4 vertex : POSITION;
    float4 uv : TEXCOORD0;
    float4 uv1 : TEXCOORD1;
    float4 uv2 : TEXCOORD2;
    float4 color:COLOR;
};

struct v2f {
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
    float4 border : TEXCOORD1;
    float4 color:COLOR;
    float width: TEXCOORD2;
    float4 edgeColor: TEXCOORD3;
};

顶点z着色器

v2f vert(appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    o.width = v.uv.z;//得到描边宽度
    o.color = v.color;//得到文本颜色
    o.border = v.uv1;//得到原始uv范围
    o.edgeColor=v.uv2;//得到描边颜色
    return o;
}

采样偏移uv对纹理采样

fixed SampleTex(v2f i,fixed ii,fixed color_a)
 {
     const fixed OffsetX[12] = {1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866};
     const fixed OffsetY[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5};
     float2 offset_uv = i.uv + float2(OffsetX[ii], OffsetY[ii]) * _MainTex_TexelSize.xy * i.width;
     fixed sample_a = (tex2D(_MainTex, offset_uv)).a;
     fixed a = sample_a;
     a *= isInRange(i.border.xy, i.border.zw, offset_uv);
     return a;
 }

判断uv是否在原始uv范围

fixed isInRange(fixed2 uv_min,fixed2 uv_max,fixed2 uv)
{
    fixed2 rs = step(uv_min, uv) * step(uv, uv_max);
    return rs.x * rs.y;
}

片元着色器

fixed4 frag(v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv).a*i.color;
    col.a *= isInRange(i.border.xy, i.border.zw, i.uv);
    fixed sum_a = 0;
    sum_a += SampleTex(i, 0, col.a);
    sum_a += SampleTex(i, 1, col.a);
    sum_a += SampleTex(i, 2, col.a);
    sum_a += SampleTex(i, 3, col.a);
    sum_a += SampleTex(i, 4, col.a);
    sum_a += SampleTex(i, 5, col.a);
    sum_a += SampleTex(i, 6, col.a);
    sum_a += SampleTex(i, 7, col.a);
    sum_a += SampleTex(i, 8, col.a);
    sum_a += SampleTex(i, 9, col.a);
    sum_a += SampleTex(i, 10, col.a);
    sum_a += SampleTex(i, 11, col.a);
    sum_a = saturate(sum_a);
    fixed4 outLineColor = fixed4(i.edgeColor.rgb,sum_a);
    fixed a=step(i.width,0.001);//宽度为0时,a为1,颜色由原来颜色决定
    fixed4 finalCol = lerp(outLineColor, col, saturate(a+col.a));
    return finalCol;
}

完整的C#代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TextOutline : BaseMeshEffect
{
    List<UIVertex> _uiVertices = new List<UIVertex>();
    [Range(0, 6)]
    public float outLineWidth = 1;
    public Color EdgeColor = Color.red;
    public Color TextColor = Color.white;
    public override void ModifyMesh(VertexHelper vh)
    {
        vh.GetUIVertexStream(_uiVertices);
        ModifyUIVertexs(_uiVertices);
        vh.Clear();
        vh.AddUIVertexTriangleStream(_uiVertices);
    }
    protected override void Start()
    {
        UseUVChannels();
    }
    private void UseUVChannels()
    {
        var shader = Shader.Find("DSShader/TextOutline");
        base.graphic.material = new Material(shader);
        AdditionalCanvasShaderChannels 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;
        }
    }
    void ModifyUIVertexs(List<UIVertex> uiVertices)
    {
        for (int i = 0; i <= uiVertices.Count - 3; i += 3)
        {
            UIVertex uiVertex1 = uiVertices[i];
            UIVertex uiVertex2 = uiVertices[i + 1];
            UIVertex uiVertex3 = uiVertices[i + 2];
            Vector3 pos1 = uiVertex1.position;
            Vector3 pos2 = uiVertex2.position;
            Vector3 pos3 = uiVertex3.position;

            Vector2 uv1 = uiVertex1.uv0;
            Vector2 uv2 = uiVertex2.uv0;
            Vector2 uv3 = uiVertex3.uv0;

            Vector3 pos_center = (pos1 + pos2 + pos3) / 3;
            Vector2 uv_min = new Vector2(Mathf.Min(uv1.x, uv2.x, uv3.x), Mathf.Min(uv1.y, uv2.y, uv3.y));
            Vector2 uv_max = new Vector2(Mathf.Max(uv1.x, uv2.x, uv3.x), Mathf.Max(uv1.y, uv2.y, uv3.y));
            Vector4 uv_border = new Vector4(uv_min.x, uv_min.y, uv_max.x, uv_max.y);
            
            Vector2 pos_base1 = pos1 - pos2;
            Vector2 pos_base2 = pos3 - pos2;

            Vector2 uv_base1 = uv1 - uv2;
            Vector2 uv_base2 = uv3 - uv2;

            uiVertices[i] = ModifyPosUV(uiVertex1, pos_center, pos_base1, pos_base2, uv_base1, uv_base2, uv_border);
            uiVertices[i + 1] = ModifyPosUV(uiVertex2, pos_center, pos_base1, pos_base2, uv_base1, uv_base2, uv_border);
            uiVertices[i + 2] = ModifyPosUV(uiVertex3, pos_center, pos_base1, pos_base2, uv_base1, uv_base2, uv_border);
        }
    }
    UIVertex ModifyPosUV(UIVertex uiVertex, Vector3 pos_centor,
        Vector2 pos_base1, Vector2 pos_base2,
        Vector2 uv_base1, Vector2 uv_base2, Vector4 uv_border)
    {
        //偏移pos
        Vector3 pos = uiVertex.position;
        float offsetX = pos.x > pos_centor.x ? outLineWidth : -outLineWidth;
        float offsetY = pos.y > pos_centor.y ? outLineWidth : -outLineWidth;
        pos.x += offsetX;
        pos.y += offsetY;
        uiVertex.position = pos;
        Vector2 offset = new Vector2(offsetX, offsetY);
        //uv偏移
        Vector2 uv = uiVertex.uv0;
        Matrix2x2 pos_m = new Matrix2x2(pos_base1.x,pos_base2.x,pos_base1.y,pos_base2.y);
         pos_m=pos_m.Inverse();
        Matrix2x2 uv_m = new Matrix2x2(uv_base1.x, uv_base2.x, uv_base1.y, uv_base2.y);
        Vector2 uv_offset = uv_m * pos_m * offset;
        uv += uv_offset;
        //设置偏移后的uv,uv0.z设置为描边宽度
        uiVertex.uv0 = new Vector4(uv.x, uv.y, outLineWidth, 0);
        //设置原始uv范围
        uiVertex.uv1 = uv_border;
        //设置文本颜色
        uiVertex.uv2 = new Vector4(EdgeColor.r,EdgeColor.g,EdgeColor.b,EdgeColor.a);
        //Color=>Color32,UIVertex的color类型是Color32
        Color32 color32 = (Color32)TextColor;
        uiVertex.color =color32;
        return uiVertex;
    }


}

public class Matrix2x2
{
    private float[,] matrix = new float[2, 2];

    public Matrix2x2(float a, float b, float c, float d)
    {
        matrix[0, 0] = a;
        matrix[0, 1] = b;
        matrix[1, 0] = c;
        matrix[1, 1] = d;
    }

    public float Determinant()
    {
        return matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0];
    }

    public Matrix2x2 Inverse()
    {
        float det = Determinant();
        float invDet = 1 / det;

        float a = matrix[1, 1] * invDet;
        float b = -matrix[0, 1] * invDet;
        float c = -matrix[1, 0] * invDet;
        float d = matrix[0, 0] * invDet;

        return new Matrix2x2(a, b, c, d);
    }

    public static Matrix2x2 operator *(Matrix2x2 m1, Matrix2x2 m2)
    {
        float a = m1.matrix[0, 0] * m2.matrix[0, 0] + m1.matrix[0, 1] * m2.matrix[1, 0];
        float b = m1.matrix[0, 0] * m2.matrix[0, 1] + m1.matrix[0, 1] * m2.matrix[1, 1];
        float c = m1.matrix[1, 0] * m2.matrix[0, 0] + m1.matrix[1, 1] * m2.matrix[1, 0];
        float d = m1.matrix[1, 0] * m2.matrix[0, 1] + m1.matrix[1, 1] * m2.matrix[1, 1];

        return new Matrix2x2(a, b, c, d);
    }

    public static Vector2 operator *(Matrix2x2 m, Vector2 v)
    {
        float x = m.matrix[0, 0] * v.x + m.matrix[0, 1] * v.y;
        float y = m.matrix[1, 0] * v.x + m.matrix[1, 1] * v.y;

        return new Vector2(x, y);
    }
}

完整的Shader代码文章来源地址https://www.toymoban.com/news/detail-774852.html

Shader "DSShader/TextOutline"
{
    Properties
    {
        [PerRendererData]_MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"
        }
        Cull Off
        Lighting Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
                float4 uv1 : TEXCOORD1;
                float4 uv2 : TEXCOORD2;
                float4 color:COLOR;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 border : TEXCOORD1;
                float4 color:COLOR;
                float width: TEXCOORD2;
                float4 edgeColor: TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.width = v.uv.z;
                o.color = v.color;
                o.border = v.uv1;
                o.edgeColor=v.uv2;
                return o;
            }

            fixed isInRange(fixed2 uv_min,fixed2 uv_max,fixed2 uv)
            {
                fixed2 rs = step(uv_min, uv) * step(uv, uv_max);
                return rs.x * rs.y;
            }

            fixed SampleTex(v2f i,fixed ii,fixed color_a)
            {
                const fixed OffsetX[12] = {1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866};
                const fixed OffsetY[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5};
                float2 offset_uv = i.uv + float2(OffsetX[ii], OffsetY[ii]) * _MainTex_TexelSize.xy * i.width;
                fixed sample_a = (tex2D(_MainTex, offset_uv)).a;
                fixed a = sample_a ;
                a *= isInRange(i.border.xy, i.border.zw, offset_uv);
                return a;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv).a*i.color;
                col.a *= isInRange(i.border.xy, i.border.zw, i.uv);
                fixed sum_a = 0;
                sum_a += SampleTex(i, 0, col.a);
                sum_a += SampleTex(i, 1, col.a);
                sum_a += SampleTex(i, 2, col.a);
                sum_a += SampleTex(i, 3, col.a);
                sum_a += SampleTex(i, 4, col.a);
                sum_a += SampleTex(i, 5, col.a);
                sum_a += SampleTex(i, 6, col.a);
                sum_a += SampleTex(i, 7, col.a);
                sum_a += SampleTex(i, 8, col.a);
                sum_a += SampleTex(i, 9, col.a);
                sum_a += SampleTex(i, 10, col.a);
                sum_a += SampleTex(i, 11, col.a);
                sum_a = saturate(sum_a);
                fixed4 outLineColor = fixed4(i.edgeColor.rgb,sum_a);
                fixed a=step(i.width,0.001);
                fixed4 finalCol = lerp(outLineColor, col, saturate(a+col.a));
                return finalCol;
            }
            ENDCG
        }
    }
}

到了这里,关于unity shader 实现通用描边shader -文字描边-字体描边的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity3D Shader系列之描边

    总结下描边效果的实现方式,主要有以下几种: ①法线外拓+ZTest Always ②法线外拓+Cull Front ③法线外拓+ZWrite Off ④法线外拓+模板测试 ⑤基于屏幕后处理 法线外拓的原理如下: 基本原理还是很简单的:模型渲染两次,第一次渲染时将模型的顶点沿法线方向外拓,然后绘制描边

    2023年04月08日
    浏览(49)
  • Unity文字描边

    思路: 寻找边界点,均值平滑: Shader “UI-Effect/ImgOutline” { Properties { // [NoScaleOffset] _MainTex (“Main Texture”, 2D) = “black” {} }

    2024年02月12日
    浏览(48)
  • [Unity]给场景中的3D字体TextMesh增加描边方案一

    取你的文本对象,简单地添加以下脚本:

    2024年02月05日
    浏览(48)
  • CSS实现文字描边效果

    一、介绍 最近在一个项目的宣传页中,设计师使用了文字描边效果,之前我确实没有实现过文字的描边效果,然后我在查阅资料后,知道了实现方法。文字描边分为两种:内外双描边和单外描边,也就是指在给文字加上描边效果后,描边的方向是向内外同时占用文字空间还是

    2023年04月09日
    浏览(77)
  • 【CSS】文字描边的三种实现方式

    text-shadow –webkit-text-stroke svg MDN text-shadow 代码 用 text-shadow 实现八个方向的文字阴影。 优缺点 优点 兼容性好 缺点 文字边缘会有锯齿。 如上图,当文字很大时,尤其明显。因为我们只设置了8个方向的阴影,这些方向交界处容易出问题。 文字必须设置颜色 如果我们把文字设

    2024年02月02日
    浏览(60)
  • CSS中如何实现文字描边效果(Text Stroke)?

    前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而又亲切的学习平台。在这个

    2024年02月09日
    浏览(55)
  • Unity中Shader序列图动画(UV流动的通用起始点)

    我们在Shader中实现序列帧动画。可以实现一些简单特效或动画节省性能用。 我们在这篇文章中,实现一下UV流动的通用起始点。 先左到右,再从上到下 Unity中 URP Shader 的纹理与采样器的分离定义 属性面板 _MainTex(“MainTex”,2D) = “white”{} 定义纹理 TEXTURE2D(_MainTex); 定义采样器

    2024年01月17日
    浏览(64)
  • Unity 基于法线和深度实现完美描边,可独立控制物体描边

    最近项目需要快速出一版卡通渲染风格进行吸量测试。但是原来的模型非常不适合使用back face 的描边方案(很难看),不得已寻求其他的描边方案,于是有了现在这篇基于法线和深度的后处理描边。 优点: 描边宽度一致。 重叠部分也能有描边。 不会出现断裂 缺点: 后处理时

    2024年01月15日
    浏览(55)
  • Unity UGUI 边缘泛光 描边 的简单实现

    先看效果  该效果是RawImage组件下实现。单纯Shader实现,不用c#辅助,当然,肯定也有缺点,在一些场合下或许不适用,我也希望能最大化适用,奈何技术有限。网上看过一些实现有些只适合3D,并且不适合棱角的,有些适用UI,效果也不错,但是为了泛光范围加大,性能指数

    2024年02月12日
    浏览(50)
  • [Unity] ShaderGraph实现Sprite图片描边/发光效果

    使用版本为:2022.3.10f1  [原始图]      [运行前]      [运行后] 更详细的实现逻辑及步骤参考CodeMonkey的视频: https://youtu.be/FvQFhkS90nI?si=zy6XRlqGnzIdQkqD OS:猴子老师,我永远的神!!! 目录 一、准备工作 二、偏移效果与颜色叠加 三、单侧描边与原理 四、另一侧与Sub管理 五、

    2024年01月20日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包