NGUI直接在UILabel组件中接入了Shadow、Gradient和outline选项,但在UGUI中是通过另外的组件单独提供,比如outline、shader等。这篇文章主要记录这几个文字效果实现的思路和流程。

1. 实现思路

1. UGUI源码分析

UGUI 的 Text 渲染的过程是由 TextGenerator 产生顶点数据,配合字体产生的贴图最终显示在屏幕上 . 下图为Text组件的继承树。



UGUI中很多渲染相关的组件都是继承自Graphics,而Graphics还在Canvas绘制之前进行重建。Graphics中定义了渲染框架,核心代码如下。Text、Image组件的需要自己实现 protected virtual void OnPopulateMesh(VertexHelper vh) 方法来填充需要的数据。Unity提供了IMeshModifier接口供外部使用,如果在Text组件所在物体中存在IMeshModifier类型的组件,则会调用ModifyMesh方法允许你获得渲染数据。也就是说可以通过这种方式进行mesh、贴图等数据的修改。

 protected virtual void UpdateGeometry()
{
if (useLegacyMeshGeneration)
DoLegacyMeshGeneration();
else
DoMeshGeneration();
} private void DoMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
//获取数据
OnPopulateMesh(s_VertexHelper);
else
s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw. var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components); //检测是否存在IMeshModifier接口类型组件
for (var i = 0; i < components.Count; i++)
((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper); ListPool<Component>.Release(components);
//填充渲染数据
s_VertexHelper.FillMesh(workerMesh);
canvasRenderer.SetMesh(workerMesh);
}

2. 实现接口

Unity提供了BaseMeshEffect类型,继承自IMeshModifier,提供 public abstract void ModifyMesh(VertexHelper vh); 接口。不过从4.7到现在这个接口修改了很多次,5.3版本是这个,5.5版本似乎又做了修改。文字的各种特效就可以通过这个接口获得渲染数据并进行修改即可。

2. 颜色渐变(Gradient)

渐变其实就是根据需要进行渐变的方向和颜色修改顶点的颜色值。 如果只考虑上下方向的渐变,可以计算字符上下最大和最小值,然后进行插值即可计算出需要的颜色。对于多方向的渐变实现稍稍麻烦点,原理类似。核心代码如下:

 public override void ModifyMesh(VertexHelper vh)
{
var vertexList = new List<UIVertex>();
vh.GetUIVertexStream(vertexList);
int count = vertexList.Count; ApplyGradient(vertexList, 0, count);
vh.Clear();
vh.AddUIVertexTriangleStream(vertexList);
} private void ApplyGradient(List<UIVertex> vertexList, int start, int end)
{
float bottomY = vertexList[0].position.y;
float topY = vertexList[0].position.y;
for (int i = start; i < end; ++i) {
float y = vertexList[i].position.y;
if (y > topY) {
topY = y;
} else if (y < bottomY) {
bottomY = y;
}
} float uiElementHeight = topY - bottomY;
for (int i = start; i < end; ++i) {
UIVertex uiVertex = vertexList[i];
uiVertex.color = Color32.Lerp(bottomColor, topColor, (uiVertex.position.y - bottomY)/uiElementHeight);
vertexList[i] = uiVertex;
}
}

3. 阴影(Shadow)

UGUI 中默认带有Shadow的组件,也是对ModifyMesh进行重载。然后将网格数据复制一份并向指定方向移动指定像素,然后填充到顶点数据中。也就是说,Shadow实现是通过增加顶点数据实现的。

// X y 为 shadow大小
protected void ApplyShadowZeroAlloc(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
UIVertex vt;
var neededCapacity = verts.Count + end - start;
if (verts.Capacity < neededCapacity)
verts.Capacity = neededCapacity;
for (int i = start; i < end; ++i)
{
vt = verts[i];
verts.Add(vt);
Vector3 v = vt.position;
v.x += x;
v.y += y;
vt.position = v;
var newColor = color;
if (m_UseGraphicAlpha)
newColor.a = (byte)((newColor.a * verts[i].color.a) / 255);
vt.color = newColor;
verts[i] = vt;
}
}

4. 勾边 (Outline)

1. 基于Shadow

outline的实现传统的做法是在4个方向或者8个方向进行Shodow操作。换句话说顶点数需要增加8倍,大量使用请谨慎。

public override void ModifyMesh(VertexHelper vh)
{
var verts = ListPool<UIVertex>.Get();
vh.GetUIVertexStream(verts);
var neededCpacity = verts.Count * 5;
if (verts.Capacity < neededCpacity)
verts.Capacity = neededCpacity;
var start = 0;
var end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, -effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, -effectDistance.y)
vh.Clear();
vh.AddUIVertexTriangleStream(verts);
ListPool<UIVertex>.Release(verts);
}

2 . 效果扩展

4方向Outlin美术经常不满足的其效果,所以有时候会需要8方向描边的outline8, 甚至更多。基于shadow方式的outline实现可以参考开源代码:https://github.com/n-yoda/unity-vertex-effects 。换汤不换药吧,越好的效果顶点增加的越多。

几种效果的图示,中文字边缘比较明显。Circle的边缘更圆滑一些,Shadow操作的次数和8方向相同。

3. 基于Mesh实现

基于Shadow的实现方式内存占用比较高,当然还有别的思路,可以参考网页中描述的方式。其详细流程:

  • 提取文字UV区域
  • 扩大文字绘图区域并记录增长量
  • 在pixel处理阶段,在每个像素点,对周围区域(受增长量以及原有uv区域控制)进行采样并作为这个点的颜色和alpha
  • 对原始纹理、alpha以及扩大后的区域进行融合

5.结论

outline的几种实现方式根据具体需求使用,云风在博客中也给出一种优化策略可参考

总之,UGUI对字体效果的支持不算很好,像图文混排等等都需要自己做扩展,据说收购了TexmeshPro对自身text系统进行扩展,期待。

UGUI 文字效果实现(Shadow\Gradient\Outline)的更多相关文章

  1. CSS Gradient文字效果

    你想创建的标题没有渲染和Photoshop每个标题吗?这里是一个简单的CSS技巧向您展示如何创建渐变文字效果,PNG图像(纯CSS,没有Javascript或Flash).你所需要的是一个空的< ...

  2. Qt qml 模拟iphone slide to unlock 的聚光动画文字效果

    模拟iphone slide to unlock 的聚光动画文字效果    /底层放淡文字    /前景放高亮文字+半透明遮罩    /动画移动遮罩 Author: surfsky.cnblogs.c ...

  3. as3.0:文字 效果

    //文字描边效果var tf1 = _root.createTextField("tf1", _root.getNextHighestDepth(), 10, 10, 0, 0); ...

  4. 如何通过PS制作图片文字效果

    如图这是最终效果,下面我为大家介绍如何制作这种图片文字效果 准备一张图: 方法,步骤: 首先我们打开PHOTOSHOP,插入一张图片. 之后按键盘上面的"T"键快捷键启用文字工具, ...

  5. JAVA 跑马灯文字效果

    JAVA跑马灯文字效果的实现: 1. 首先创建一个继承JFrame类的HorseRaceLightTextFrame窗体类,代码如下: package com.example.horseracelig ...

  6. 移动端 iphone锁屏文字效果

    简易的仿照iphone 效果 笔记备份 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Conten ...

  7. 精通CSS+DIV网页样式与布局--CSS文字效果

    上篇文章,小编简单的介绍了一下CSS的一些基本语法,学习内容不是很复杂,都是CSS的一些入门知识,但是万丈高楼平地起,搭好地基,高楼大厦不在话下,学习任何课程,都必须从基础开始,一步一个脚印,踏实坚定 ...

  8. CSS3动画:流彩文字效果+图片模糊效果+边框伸展效果实现

    前言 首先第一步,先布局html代码如下: <div class="wrap"> <img src="images/1.jpg" class= ...

  9. CSS3下的渐变文字效果实现

    如下,第一种方法已实践 一.方法一:借助mask-image属性 可以狠狠地点击这里:CSS3下的渐变文字效果方法一demo 如果您手头上的浏览器是Chrome或是Safari,则您可以在demo页面 ...

随机推荐

  1. Linux_CentOS-服务器搭建 <五> 补充

    O:文件的编码格式 1.文件转码问题 Windows中默认的文件格式是GBK(gb2312),而Linux一般都是UTF-8. 那么先说,如何查看吧.这时候强大的vi说,I can do that.( ...

  2. docker学习系列(四):数据持久化

    需要搞清楚一个概念的是,docker的容器设计理念是可以即开即用,用完可以随意删除,而新建容器是根据镜像进行渲染,容器的修改是不会影响到镜像,但是有时候容器里面运行的产生的数据(如mysql)或者配置 ...

  3. 自己动手实现java数据结构(五)哈希表

    1.哈希表介绍 前面我们已经介绍了许多类型的数据结构.在想要查询容器内特定元素时,有序向量使得我们能使用二分查找法进行精确的查询((O(logN)对数复杂度,很高效). 可人类总是不知满足,依然在寻求 ...

  4. 看懂「www.google.com」背后的逻辑

    在前两篇文章中,我们完整的描述了计算机网络 OSI 五层模型的相关内容.那么,本篇将会从一个实践案例开始,带你从整体上重新认识我们的计算机网络. 我们以访问 Google 为例,当我们在浏览器地址栏中 ...

  5. Linux的进程线程及调度

    本文为宋宝华<Linux的进程.线程以及调度>学习笔记. 1 进程概念 1.1 进程与线程的定义 操作系统中的经典定义: 进程:资源分配单位. 线程:调度单位. 操作系统中用PCB(Pro ...

  6. Task.Run Vs Task.Factory.StartNew 【收藏】

    在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...

  7. 探秘小程序(7):view组件

    小程序中最基础,最常用的组件--view,类似于html中div的存在有四个属性: ①hover-class:指定按下去的样式类.当 hover-class="none" 时,没有 ...

  8. 使用Django创建网站项目<二>

    上一篇:Windows安装diango框架<一> 创建项目,启动服务器 利用安装好的django包的django-admin.py创建项目,命令 python  django-admin. ...

  9. Java使用for循环输出菱形

    /** * This program would print out a diamond * @param row the row of diamond * @version 2018-7-23 * ...

  10. linux下vscode的c++工程配置

    准备 安装vscode,可直接下载deb包进行安装,完成后安装C/C++ for Visual Studio Code插件,安装后重启(最新1.3版本以后不需要重启). 生成目录和文件 新建文件夹[t ...