three.js如何实现3D动态文字效果


Posted in Javascript onMarch 03, 2021

前言

大家好,这里是 CSS 魔法使——alphardex。

之前在逛国外网站的时候,发现有些网站的文字是刻在3D图形上的,并且能在图形上运动,视觉效果相当不错,于是笔者就也想用three.js来尝试复现出这种效果

three.js如何实现3D动态文字效果

上图只是所有效果的其中之一,接下来让我们一起开干吧~

准备工作

笔者自行封装的three.js模板:Three.js Starter

读者可以点击右下角fork一份后再开始本项目

本项目需要用到位图字体,可以直接复制demo的HTML里的font字体代码

一个注意点:three-bmfont-text这个库依赖全局的three.js,因此要在JS里额外引入一次three.js,如下图

three.js如何实现3D动态文字效果

实现思路

  1. 加载位图字体文件,将其转化为文字对象所需要的形状和材质
  2. 创建文字对象
  3. 创建渲染目标,可以理解为canvas中的canvas,因为接下来我们要将文字对象本身当做贴图
  4. 创建承载字体的容器,将文字对象作为贴图贴上去
  5. 动画

正片

搭好架子

<div class="relative w-screen h-screen">
 <div class="kinetic-text w-full h-full bg-blue-1"></div>
 <div class="font">
 <font>
  一坨从demo里CV而来的字体代码
 </font>
 </div>
</div>
:root {
 --blue-color-1: #2c3e50;
}

.bg-blue-1 {
 background: var(--blue-color-1);
}
import createGeometry from "https://cdn.skypack.dev/three-bmfont-text@3.0.1";
import MSDFShader from "https://cdn.skypack.dev/three-bmfont-text@3.0.1/shaders/msdf";
import parseBmfontXml from "https://cdn.skypack.dev/parse-bmfont-xml@1.1.4";

const font = parseBmfontXml(document.querySelector(".font").innerHTML);
const fontAtlas = "https://i.loli.net/2021/02/20/DcEhuYNjxCgeU42.png";

const kineticTextTorusKnotVertexShader = `(顶点着色器代码,先空着,具体见下文)`;

const kineticTextTorusKnotFragmentShader = `(片元着色器代码,先空着,具体见下文)`;

class KineticText extends Base {
 constructor(sel: string, debug: boolean) {
 super(sel, debug);
 this.cameraPosition = new THREE.Vector3(0, 0, 4);
 this.clock = new THREE.Clock();
 this.meshConfig = {
  torusKnot: {
  vertexShader: kineticTextTorusKnotVertexShader,
  fragmentShader: kineticTextTorusKnotFragmentShader,
  geometry: new THREE.TorusKnotGeometry(9, 3, 768, 3, 4, 3)
  }
 };
 this.meshNames = Object.keys(this.meshConfig);
 this.params = {
  meshName: "torusKnot",
  velocity: 0.5,
  shadow: 5,
  color: "#000000",
  frequency: 0.5,
  text: "ALPHARDEX",
  cameraZ: 2.5
 };
 }
 // 初始化
 async init() {
 this.createScene();
 this.createPerspectiveCamera();
 this.createRenderer(true);
 await this.createKineticText(this.params.text);
 this.createLight();
 this.createOrbitControls();
 this.addListeners();
 this.setLoop();
 }
 // 创建动态文字
 async createKineticText(text: string) {
 await this.createFontText(text);
 this.createRenderTarget();
 this.createTextContainer();
 }
}

加载和创建字体

首先加载字体文件,并创建出形状和材质,有了这两样就能创建出字体对象了

class KineticText extends Base {
 loadFontText(text: string): any {
 return new Promise((resolve) => {
  const fontGeo = createGeometry({
  font,
  text
  });
  const loader = new THREE.TextureLoader();
  loader.load(fontAtlas, (texture) => {
  const fontMat = new THREE.RawShaderMaterial(
   MSDFShader({
   map: texture,
   side: THREE.DoubleSide,
   transparent: true,
   negate: false,
   color: 0xffffff
   })
  );
  resolve({ fontGeo, fontMat });
  });
 });
 }
 async createFontText(text: string) {
 const { fontGeo, fontMat } = await this.loadFontText(text);
 const textMesh = this.createMesh({
  geometry: fontGeo,
  material: fontMat
 });
 textMesh.position.set(-0.965, -0.525, 0);
 textMesh.rotation.set(ky.deg2rad(180), 0, 0);
 textMesh.scale.set(0.008, 0.025, 1);
 this.textMesh = textMesh;
 }
}

着色器

顶点着色器

通用模板,直接CV即可

varying vec2 vUv;
varying vec3 vPosition;

void main(){
 vec4 modelPosition=modelMatrix*vec4(position,1.);
 vec4 viewPosition=viewMatrix*modelPosition;
 vec4 projectedPosition=projectionMatrix*viewPosition;
 gl_Position=projectedPosition;
 
 vUv=uv;
 vPosition=position;
}

片元着色器

利用fract函数创建重复的贴图,加上位移距离displacement使得贴图能随着时间的增加而动起来,再用clamp函数来根据z轴大小限定阴影的范围,意思是离画面越远则阴影越重,反之离画面越近则阴影越轻

uniform sampler2D uTexture;
uniform float uTime;
uniform float uVelocity;
uniform float uShadow;

varying vec2 vUv;
varying vec3 vPosition;

void main(){
 vec2 repeat=vec2(12.,3.);
 vec2 repeatedUv=vUv*repeat;
 vec2 displacement=vec2(uTime*uVelocity,0.);
 vec2 uv=fract(repeatedUv+displacement);
 vec3 texture=texture2D(uTexture,uv).rgb;
 // texture*=vec3(uv.x,uv.y,1.);
 float shadow=clamp(vPosition.z/uShadow,0.,1.);// farther darker (to 0).
 vec3 color=vec3(texture*shadow);
 gl_FragColor=vec4(color,1.);
}

此时文本显示到了屏幕上

创建渲染目标

为了将字体对象本身作为贴图,创建了一个渲染目标

class KineticText extends Base {
 createRenderTarget() {
 const rt = new THREE.WebGLRenderTarget(
  window.innerWidth,
  window.innerHeight
 );
 this.rt = rt;
 const rtCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
 rtCamera.position.z = this.params.cameraZ;
 this.rtCamera = rtCamera;
 const rtScene = new THREE.Scene();
 rtScene.add(this.textMesh);
 this.rtScene = rtScene;
 }
}

创建字体容器

创建一个容器,并将字体对象本身作为贴图贴上去,再应用动画即可完成

class KineticText extends Base {
 createTextContainer() {
 if (this.mesh) {
  this.scene.remove(this.mesh);
  this.mesh = null;
  this.material!.dispose();
  this.material = null;
 }
 this.rtScene.background = new THREE.Color(this.params.color);
 const meshConfig = this.meshConfig[this.params.meshName];
 const geometry = meshConfig.geometry;
 const material = new THREE.ShaderMaterial({
  vertexShader: meshConfig.vertexShader,
  fragmentShader: meshConfig.fragmentShader,
  uniforms: {
  uTime: {
   value: 0
  },
  uVelocity: {
   value: this.params.velocity
  },
  uTexture: {
   value: this.rt.texture
  },
  uShadow: {
   value: this.params.shadow
  },
  uFrequency: {
   value: this.params.frequency
  }
  }
 });
 this.material = material;
 const mesh = this.createMesh({
  geometry,
  material
 });
 this.mesh = mesh;
 }
 update() {
 if (this.rtScene) {
  this.renderer.setRenderTarget(this.rt);
  this.renderer.render(this.rtScene, this.rtCamera);
  this.renderer.setRenderTarget(null);
 }
 const elapsedTime = this.clock.getElapsedTime();
 if (this.material) {
  this.material.uniforms.uTime.value = elapsedTime;
 }
 }
}

别忘了把相机调远一些

this.cameraPosition = new THREE.Vector3(0, 0, 40);

风骚的动态文字出现了:)

three.js如何实现3D动态文字效果

项目地址

Kinetic Text

demo里不止本文创建的这一种形状,大家可以随意把玩。

总结

到此这篇关于three.js如何实现3D动态文字效果的文章就介绍到这了,更多相关three.js 3D动态文字内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
模拟select的代码
Oct 19 Javascript
浅谈JavaScript之事件绑定
Jul 08 Javascript
css配合jquery美化 select
Nov 29 Javascript
JS获取月份最后天数、最大天数与某日周数的方法
Dec 08 Javascript
js调用webservice构造SOAP进行身份验证
Apr 27 Javascript
ajax的分页查询示例(不刷新页面)
Jan 11 Javascript
node.js学习之断言assert的使用示例
Sep 28 Javascript
微信小程序实现简单评论功能
Nov 28 Javascript
vue-cli3 karma单元测试的实现
Jan 18 Javascript
vue在自定义组件中使用v-model进行数据绑定的方法
Mar 25 Javascript
微信小程序实现的picker多级联动功能示例
May 23 Javascript
原生js生成图片验证码
Oct 11 Javascript
Webpack3+React16代码分割的实现
Mar 03 #Javascript
微信小程序input抖动问题的修复方法
Mar 03 #Javascript
微信小程序组件生命周期的踩坑记录
Mar 03 #Javascript
vite2.0+vue3移动端项目实战详解
Mar 03 #Vue.js
基于JavaScript实现简单的轮播图
Mar 03 #Javascript
js面向对象方式实现拖拽效果
Mar 03 #Javascript
Vue多选列表组件深入详解
Mar 02 #Vue.js
You might like
详解Yii实现分页的两种方法
2017/01/14 PHP
php登录超时检测功能实例详解
2017/03/21 PHP
thinkphp5使html5实现动态跳转的例子
2019/10/16 PHP
PHP实现创建一个RPC服务操作示例
2020/02/23 PHP
prettify 代码高亮着色器google出品
2010/12/28 Javascript
JavaScript将相对地址转换为绝对地址示例代码
2013/07/19 Javascript
jQuery中:contains选择器用法实例
2014/12/30 Javascript
理解javascript中的严格模式
2016/02/01 Javascript
jQuery图片左右滚动代码 有左右按钮实例
2016/06/20 Javascript
jQuery如何获取动态添加的元素
2016/06/24 Javascript
实用jquery操作表单元素的简单代码
2016/07/04 Javascript
js通过keyCode值判断单击键盘上某个键,然后触发指定的事件方法
2017/02/19 Javascript
CentOS 安装NodeJS V8.0.0的方法
2017/06/15 NodeJs
详解如何实现一个简单的 vuex
2018/02/10 Javascript
浅析Vue项目中使用keep-Alive步骤
2018/07/27 Javascript
JS实现数组的增删改查操作示例
2018/08/29 Javascript
jQuery轻量级表单模型验证插件
2018/10/15 jQuery
详解vue如何使用rules对表单字段进行校验
2018/10/17 Javascript
layui 数据表格 点击分页按钮 监听事件的实例
2019/09/02 Javascript
layui文件上传控件带更改后数据传值的方法
2019/09/23 Javascript
Win7上搭建Cocos2d-x 3.1.1开发环境
2014/07/03 Python
完美解决Pycharm无法导入包的问题 Unresolved reference
2018/05/18 Python
python使用循环打印所有三位数水仙花数的实例
2018/11/13 Python
pytorch使用指定GPU训练的实例
2019/08/19 Python
Win10里python3创建虚拟环境的步骤
2020/01/31 Python
如何使用Python发送HTML格式的邮件
2020/02/11 Python
Javascript 高级手势使用介绍
2013/04/21 HTML / CSS
伦敦香水公司:The London Perfume Company
2019/11/13 全球购物
仓库理货员岗位职责
2013/12/18 职场文书
运动会解说词200字
2014/02/06 职场文书
竞选班长自荐书范文
2014/03/09 职场文书
副总经理任命书
2014/06/05 职场文书
2014教师专业技术工作总结
2014/12/03 职场文书
2015年保安个人工作总结
2015/04/02 职场文书
2015年大学班主任工作总结
2015/04/30 职场文书
团委副书记工作总结
2015/08/14 职场文书