这个特效拿去表白,CL都免了~

2,556 阅读8分钟

源码及演示地址

前言

本文主要讲解怎么将ttf文字文件转成threejs可用的json文件,从而通过加载器将指定文字的路径和顶点信息获取到,并且兼容孔洞的处理,既然有路径和顶点信息,可做的效果有很多,比如你可以做一个立体字,做一个霓虹灯,等等等等...,代码中提供了两种字体,和两种字体对应的精简版,你也可以找UI要一个比较可爱的字体替换成原文的字体效果~

另:考虑到每个人的电脑性能不一样,所以演示代码中不加发光效果,可以在下载后自己运行(本地运行默认开启发光特效)

字体转json

众所周知,在网上下载的字体,都是ttf或者woff格式的,并不能直接给threejs用,所以就需要一个工具转成threejs的FontLoader支持的json文件,这里介绍一个工具:ttf_to_json,内含html文件,打开即用,双击打开后需要上传一个文件,如果想转全量字体,文件上传完毕之后点击convert按钮,等待下载,这样的话,文件比较大,如果是复杂的字体,文件能达到18MB,这时就需要选择,如果你只需要几个字而已,则可以通过下面的Restrict character set.选项去限制字符,这样转换出来的文件就会小很多,示例提供了4种字体文件,两款不同的字体,和他们的限制版,下面是工具样式的展示:

1 字体.jpg 2 字体.jpg 3 转换.jpg

加载字体

通过同居的转换,我们得到了一个json格式的文字数据,下面就来将字体加载到threejs中吧!

import { Font, FontLoader } from 'three/addons/loaders/FontLoader.js';
// 字体加载器
const loader = new FontLoader();

// 加载字体
const loadFont = (url: string): Promise<Font> => {
    return new Promise((res) => {
        loader.load(`${import.meta.env.VITE_ASSETS_URL}assets/font/${url}.json`, function (response: Font) {
            res(response)
        });
    })
}

加载器的load方法支持4个回调,分别是url(文件路径),onLoad(加载完毕),onProgress(加载进度)和onError(加载失败),这些通过源码也可以了解到

 load(
    url: string,
    onLoad?: (data: Font) => void,
    onProgress?: (event: ProgressEvent) => void,
    onError?: (err: unknown) => void,
): void;

字体文件结构

其中onLoad的回调接受一个参数Font,这个是加载字体方法的实例,支持方法generateShapes,可以通过这个方法得到字体文件的形状Shapes

generateShapes(text: string, size: number): Shape[];

第一个参数是字符,第二个参数是尺寸,就是加载后字体的尺寸,决定了形状的大小。

加载字体

所以根据Font提供的generateShapes方法,我们可以将指定字符并获得这个字符的形状

const shapes = font.generateShapes(t, 4);
console.log('shapes',shapes);
4 形状结构.jpg

看这结构眼熟不,加载svg文件也是得到的这些形状,接下来就是根据形状生成形状几何体ShapeGeometry,我们需要加几行代码,用于绘制形状

const color = 0x006699;

const matDark = new THREE.LineBasicMaterial({
    color: color,
    side: THREE.DoubleSide
});
    
for (let i = 0; i < shapes.length; i++) {

    const shape = shapes[i];

    const points = shape.getPoints();

    const geometry = new THREE.BufferGeometry().setFromPoints(points);

    const lineMesh = new THREE.Line(geometry, matDark);
    lineText.add(lineMesh);

}

第一步遍历形状内的线段,第二步通过getPoints获取到线段的顶点信息,将顶点信息赋值给geometry,第三步根据线条的材质和顶点信息生成一个线段Line,于是得到了以下的文字

5 猫 无框.jpg

镂空点位

发现问题了么,我加载的是“猫” 字,得到的只有外框,没有孔洞的形状,所以需要进一步加工一下,将孔洞加载出来,在前面打印shapes时候是包含holes孔洞信息的。

6 孔洞.jpg

所以我们需要单独将这些孔洞遍历一下并应用到shapes中,在遍历shapes之前,我们需要先获取到孔洞信息

for (let i = 0; i < shapes.length; i++) {
    const shape = shapes[i];
    if (shape.holes && shape.holes.length > 0) {
        for (let j = 0; j < shape.holes.length; j++) {
            const hole = shape.holes[j];
            holeShapes.push(hole);
        }
    }
}
// 将孔洞信息添加到shapes中
shapes.push.apply(shapes, holeShapes);

通过这样的遍历,shapes就包含了外框的顶点信息和孔洞的信息,再去遍历shapes就会将孔洞的线也加载出来,看一下效果吧。

7 猫 孔洞.jpg

调整位置

当然,我们不能只加载一个文字,如果是多个字该怎么办?将generateShapes的第一个参数写成多个字?

对!哈哈哈

那我们以猫啃什锦黑这几个字作为示例搞一下

8 多字.jpg

确实可以将多个字一起绘制成Line,那么还有一个问题,线条的原点是左下角,但是我想要的是居中的效果,这样比较好操作,什么?用position修改?也可以,那就需要通过box3来计算出线条的尺寸再用公式去改变position,相对我下面说的方法比较复杂,直接修改geometry,在前面遍历shapes用来生成线段的时候就进行修改,通过 geometry.translate去修改形状的偏移量

首先需要获取偏移量的具体数值,主要通过BufferGeometry中的boundingBox属性去获取尺寸,因为需要获取文字整体的尺寸来计算偏移量,所以我们需要计算shapes所有的顶点。在遍历它之前


// 根据形状获得几何体
const geometry = new THREE.ShapeGeometry(shapes);
// 更新几何体边界
geometry.computeBoundingBox();
// 获取几何体尺寸 目的是居中
const xMid = - 0.5 * ((geometry?.boundingBox?.max?.x || 0) - (geometry?.boundingBox?.min.x || 0));
const yMid = - 0.5 * ((geometry?.boundingBox?.max?.y || 0) - (geometry?.boundingBox?.min.y || 0));

调用computeBoundingBox是必须的,不然的话geometry?.boundingBox是默认值null,获取到shapes的x和y轴的偏移量就要在遍历的时候去应用了:

 for (let i = 0; i < shapes.length; i++) {

    ...
    
    let offset = new THREE.Vector3(xMid, yMid, 0)

    const geometry = new THREE.BufferGeometry().setFromPoints(points);

    geometry.translate(offset.x, offset.y, offset.z);
    
    ...

}

这样的话,文字整体就在整个坐标系的正中间了。

9 纠偏.jpg

调整相机

现在文字已经加载到坐标系的中心,但是还有一个问题,为了适配更多文字的显示,相机不能是固定的角度去观察视图,这样会导致文字太多会显示不下,文字太小或者太少,又显得视图很空,所以需要动态调整,又两种方式可以去修改相机的视角,代码里的方法是根据条件修改fov(摄像机视锥体垂直视野角度) 可以参考官网-常见问题-如何在窗口调整大小时保持场景比例不变?。这里给的方案是屏幕尺寸改变后怎么调整fov,同理咱们的内容修改了,也可以参照这个方法,只不过不是调整当前屏幕尺寸和修改前屏幕尺寸的比例,文中修改的是文字的宽度和上次文字宽度的比例,let lastHeight = 27.339999496936798这个数字是在fov为45,文字单个尺寸为4时,固定猫啃什锦黑这几个文字的尺寸,在保证这个尺寸的文字能完全在视图中显示为准,做一个标准数据,下面文字不管多少,都按照这个统一尺寸去衡量,

camera.aspect = window.innerWidth / window.innerHeight;

camera.fov = ( 360 / Math.PI ) * Math.atan( tanFOV * ( (xMid * 2) / lastHeight ) );

camera.updateProjectionMatrix();

第一行:修改屏幕比例的代码,其实可以去掉 第二行:根据初始尺寸lastHeight和当前文字尺寸做的比例,tanFOV是初始fov比例,默认为 tanFOV = Math.tan(((Math.PI / 180) * camera.fov / 2));, 第三行:更新相机配置

支持动态输入文字

现在我们来写一个input输入框,在点击按钮的时候获取到输入的内容并绘制到canvas里,

<div class="input-btn">
  <form action="#">
    <input type="text" id="text" /><br />
    <input type="submit" value="提交" />
  </form>
</div>
var form = document.querySelector('form');
if(form) {
    form.addEventListener('submit', function (e) {
      e.preventDefault();
      var text =( document.getElementById('text') as any)?.value;
    //   alert("你输入的内容是: " + text);
      createText(text)
    });
}

通过input获得到text后通过封装好的createText 方法进行绘制,大概如下效果

10 动态输入.gif

在初始文字后重新渲染了一个文字,文字也绘制出来了,相机fov也调整了,那么问题来了,前面的文字没有清空,lineText.removeFromParent()添加这行代码,就将原有的组清空掉,再新建一个组添加到scene中,lineText是用来存放绘制好的文字线段的,直接删除就可以了。

轮廓飞线

飞线的代码在源码中src/utils/fly.ts文件中,具体讲解在之前的文章threejs开发可视化数字城市效果,源码在gitee上,可以自行下载克隆

11 飞线.gif

let Fly = new TFly()实例化构造函数后,需要在render方法中调用update方法,Fly.upDate(),使用也是老少皆宜,调用setFly方法,传入相应参数即可

const createFly = (points: THREE.Vector3[]) => {
    const flyGroup = Fly.setFly({
        index: 0, 
        num: Math.floor(points.length * .2),
        points: points,
        spaced: 1000, // 要将曲线划分为的分段数。默认是 5
        starColor: new THREE.Color(color),
        endColor: new THREE.Color(color),
        size: 0.2
    })
    flyLineGroup.add(flyGroup)
}

参数讲解

interface SetFly {
    index: number, // 截取起点
    num: number, // 截取长度 // 要小于length
    points: Vector3[],
    spaced: number // 控制速度,要将曲线划分为的分段数。默认是 5
    starColor: Color,
    endColor: Color,
    size: number, // 飞线顶端顶点尺寸,后面会依次变小
}

发光体

参照[threejs渲染高级感可视化涡轮模型](juejin.cn/post/730148…

发光效果

12 动态发光.gif 13 切换.gif

历史文章

three.js 专栏

threejs——从实战出发之智慧塔吊大屏

高德地图+threejs打造智慧景区大屏

three.js——完整3d大屏展示超详细讲解

threejs——可视化风力发电车物联交互效果 内附源码

three.js——商场楼宇室内导航系统 内附源码

three.js——可视化高级涡轮效果+警报效果 内附源码