Threejs文字与css2d/css3d技术
学习之前先搞清楚自己想要什么样的效果
threejs有多种添加文字的方式
- 添加带文字的贴图到物体上
- 文字几何体
- css2d和css3d技术
threejs中,有N种文字的效果
最常见的,2D文字,这种文字会始终面向你,无论你怎么看,文字都是正着的,这种文字一般是2D文字,实现思路是上面的,1和2
伪3D文字,这里只讲一种,在你平视它的时候,它是不变形的,但是当你抬高了视角后,在看向文字,则会有3D效果,实现方式有1和2
3D文字,会根据你视角旋转,改变形态的文字,实现方式有1,2,3
我们下面按照贴图法,css2d/3d法,和文字几何体法三种方式来介绍如何加载不同种类的文字
贴图文字
准备一张带文字的png贴图
使用sprite来进行贴图实现2D始终面朝相机的文字
let loader = new THREE.TextureLoader();
loader.load('./文字贴图.png',texture=>{
let material = new THREE.SpriteMaterial({
map:texture,
transparent:true,//使用png贴图就习惯性的设置透明为开
//设置sizeAttenuation = false后,sprite的大小不会因为视角拉进拉远而改变
//但是对位移还有效
//sizeAttenuation:false
});
let sprite = new THREE.Sprite(material);
scene.add(sprite);
})
精灵方面的内容,在【ThreeJS基础教程-点线精灵篇】4.1 Sprite精灵 已介绍完毕,这里不做过多介绍
上面代码纯属是基本功,打好基础的都能看懂,我就不多介绍了
使用planeGeometry来贴图实现3D文字
let loader = new THREE.TextureLoader();
loader.load('./文字贴图.png',texture=>{
let geometry = new THREE.PlaneGeometry(10,10);
let material = new THREE.MeshBasicMaterial({
map:texture,
transparent:true,
side:THREE.DoubleSide,//如果你希望两面都能看到,设置这个即可
});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
})
又是个超级简单,不需要多讲的代码
使用planeGeometry来贴图实现伪3D文字
let loader = new THREE.TextureLoader();
loader.load('./文字贴图.png',texture=>{
let geometry = new THREE.PlaneGeometry(10,10);
let material = new THREE.MeshBasicMaterial({
map:texture,
transparent:true,
side:THREE.DoubleSide,//如果你希望两面都能看到,设置这个即可
});
let mesh = new THREE.Mesh(geometry,material);
//mesh.onBeforeRender会跟随renderer.render(scene,camera) 执行
mesh.onBeforeRender = ()=>{
let px = camera.position.x;//获取相机的x位置
let pz = camera.position.z;//获取相机的z位置
mesh.lookAt(px,0,pz);//观察相机所在位置且高度为0的点
}
scene.add(mesh);
})
在上面的基础上,让物体始终观察一个方向即可,可以看出,这样的文字,在你相机正视它的时候,它是完整的,但是你俯视它的时候,就会有3D感,这种文字我称它为“伪3D文字”
实现方式很简单,首先设置物体的onBeforeRender函数,这个函数,会跟随 renderer.render() 来执行,且在渲染前执行,所以我们用它来监控相机,每渲染一阵,让其观察一次相机,但是不是像sprite一样观察相机,我们只需要以相机位置,创建一个与mesh等高的点即可,这样即可保证相机无论哪个方向,从正面看,都是完整的文字
伪3D文字有很多种,这里仅讲这一种,本质上伪3D文字,就是在控制在不同视角下,文字的渲染效果
动态贴图文字
动态贴图文字的本质,是 canvasTexture + canvas绘制来实现的
我们以上面用planeGeometry的3D文字举例
//创建canvas
let canvas = document.createElement('canvas');
//canvasTexture使用的<canvas>的宽高,尽量一致,且宽高取值为2的n次幂
canvas.style.width = "512px";
canvas.style.height = "512px";
let ctx = canvas.getContext('2d');
ctx.fillStyle = "rgb(111,189,255)";//设定填充颜色
ctx.font = "bolder 24px arial";//设定字体
ctx.fillText("示例CanvasTexture文字",24,24); //在画布的24,24的位置添加文字
ctx.globalAlpha = 0;//设定背景透明
//创建画布贴图
let canvasTexture = new THREE.Texture(canvas);
canvasTexture.needsUpdate = true;//上面创建的canvas每更新一次,贴图更新一次
let geometry = new THREE.PlaneGeometry(10,10);
let material = new THREE.MeshBasicMaterial({
map:canvasTexture,
transparent:true
});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
创建过程看注释吧
关于canvas绘图技术,请自行百度
注意:只要是个< canvas > 都可以用到CanvasTexture上,比如说:Renderer.domElement,本质上就是一个< canvas>,pixijs,konvajs等一众2D框架,也具有一个< canvas>,当你想做复杂的2D效果时,可以不必拘泥于 < canvas>画图技术,可以去考虑一下WebG2D框架,是否能够满足你的需要
html2Canvas
插播一条小科普,这个工具名称中的2,不能读成中文的二,应该读成英文单词 to,本质上这个工具的名称,应该叫: html to canvas,为了方便简写,所以省下to写成2
没用的知识+1
这个技术可以把dom转换为一个< canvas>,所以也可以通过这个,把dom做成贴图贴给CanvasTexture,但是请注意,这个转换需要很长的时间,太复杂的dom可能不适用,演示效果与上面的canvasTexture基本一致,本质上还是生成贴图 => 使用贴图的过程
文字几何体(半数以上的新人都会踩一遍文字几何体的坑)
认识3D几何体以及新人误区
一进入文档,我们就应该看清楚上面的继承关系
文字几何体,本质上是挤压几何体ExtrudeGeometry
挤压几何体的基础,又是shape
所以,本质上,文字几何体,就是,从字体文件转换过来的,系统绘制文字时使用的顶点,绘制的Shape,然后生成ExtrudeGeometry
既然是几何体,那么一定要考虑几何体的点线面问题,如果你的文字非常多,那用文字几何体等于作死。。。
本人曾经也作过这个死,生成的文字几何体json高达上百M,然后就加载转换后的json就得消耗巨量的性能,加上用了几百个文字去生成文字几何体。。。结果是懂得都懂
第一大坑,文字几何体会大量占用你的点线面,尤其是中文,尤其是那些高清字体
文字几何体,仅适用于,你的场景需要添加文字模型时使用
字体JSON如何获取
facetype
将任意ttf丢入到这里就可以生成字体json
这里是第二大坑!中文字体包转换出来的json,文件巨大,非常不实用,笔者曾经转换过来的最小的字体json都得20M左右
解决中文字体大的方法也有,从 TTF格式入手,TTF的字体文件大小减小,就可以解决此问题
这个问题是个偏传统前端的问题,使用字蛛(font-spider),然后把你需要的文字,用字蛛筛选出来,导出一个干净的TTF,这样转换的字体JSON会小很多
创建文字几何体
步骤如下:
- 引入字体json包
- 引入FontLoader和TextGeometry
- 创建文字几何体
这里我们使用threejs开发包中的字体文件,路径和文件名如下
examples/fonts/gentilis_bold.typeface.json
import {FontLoader} from "../three/examples/jsm/loaders/FontLoader.js";
import {TextGeometry} from "../three/examples/jsm/geometries/TextGeometry.js"
function addMesh() {
let fontLoader = new FontLoader();
fontLoader.load('./gentilis_bold.typeface.json',font=>{
const geometry = new TextGeometry( 'Hello three.js!', {
font: font,//字体
size: 80,//几何体大小
depth: 5,//几何体挤出深度
curveSegments: 12,//曲线上的分段数,分段数越高,字体越圆滑,但是占用点线面越多
bevelEnabled: true,//挤出圆角
bevelThickness: 10,//圆角厚度
bevelSize: 8,//圆角宽度
bevelSegments: 5//圆角分段数,分段数越高,字体越圆滑,但是占用点线面越多
} );
let material = new THREE.MeshStandardMaterial({
color:0xffffff * Math.random(),//随机色
});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
})
}
可以看得出,文字几何体创建出来的文字是3D文字模型,而且3D感极强
css2d/css3d技术
这两个技术本质上是同一个技术
理解什么是css2d/3d
css2d,看名字就知道,和css有关系,css2d本质上是使用了,css技术中的translate样式,threejs借助视角切换,来更新dom的translate值,实现< div >位置的改变
所以,css2d,本质上就是< div> 使用了3D坐标的 css技术
css2d的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
}
body{
width:100vw;
height: 100vh;
overflow: hidden;
}
</style>
</head>
<body>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../three/build/three.module.js",
"three/addons/": "../three/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "../three/build/three.module.js";
import {OrbitControls} from "../three/examples/jsm/controls/OrbitControls.js";
//显式引入css2dRenderer和css2dObject
import {CSS2DRenderer,CSS2DObject} from "../three/examples/jsm/renderers/CSS2DRenderer.js";
window.addEventListener('load',e=>{
init();
addMesh();
render();
})
let scene,renderer,camera;
let orbit;
//1 创建全局的css2d渲染器
let csS2DRenderer;
function init(){
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias:true
});
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
//2.和renderer一样需要设置大小,同时还要调整<div>样式
csS2DRenderer = new CSS2DRenderer();
csS2DRenderer.setSize(window.innerWidth,window.innerHeight)
csS2DRenderer.domElement.style.position = "absolute";//绝对定位和fixed定位都可
csS2DRenderer.domElement.style.left = 0;
csS2DRenderer.domElement.style.top = 0;//保证css2d要完美覆盖<canvas>
csS2DRenderer.domElement.style.zIndex = 1;//比<canvas>高即可
csS2DRenderer.domElement.style.pointerEvents = "none";//设置无事件,以防遮挡画布上的事件
document.body.appendChild(csS2DRenderer.domElement);// 别忘了添加到<body>或者你选择的容器中
//还没结束,还有两步走
camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);
camera.add(new THREE.PointLight());
camera.position.set(10,10,10);
scene.add(camera);
orbit = new OrbitControls(camera,renderer.domElement);
orbit.enableDamping = true;
scene.add(new THREE.GridHelper(10,10));
}
function addMesh() {
//3.添加css2d物体
//3.1 创建css2d物体需要一个dom
let div = document.createElement('div');
div.style.cssText = `
width:200px;
height:200px;
border:1px solid #ffffff;
color:#ffffff;
`;
div.innerText = "测试css2d文字";
//3.2 创建css2dObject
let csS2DObject = new CSS2DObject(div);
scene.add(csS2DObject);
}
function render() {
renderer.render(scene,camera);
//4. 更新css2d绑定的<div>
csS2DRenderer.render(scene,camera);
orbit.update();
requestAnimationFrame(render);
}
</script>
</body>
</html>
创建过程:
0. 显示引入css2dObject和css3dObject
- 创建全局的css2DRenderer
- 配置css2dRenderer
- 创建css2dObject
- 渲染和更新css2dRenderer
不少人第一次入坑的时候,都是在抄官方的demo,但是总有抄漏的,如果遇到css2d无效的,可以参考本人这里写的四步走来排查问题
css2d 事件
function addMesh() {
//3.添加css2d物体
//3.1 创建css2d物体需要一个dom
let div = document.createElement('div');
div.style.cssText = `
width:200px;
height:200px;
border:1px solid #ffffff;
color:#ffffff;
pointer-events:auto
`;
div.innerText = "测试css2d文字";
div.addEventListener('click',e=>{
alert('1');
})
//3.2 创建css2dObject
let csS2DObject = new CSS2DObject(div);
scene.add(csS2DObject);
}
这里是css的知识了
父级如果携带了 pointer-events: none; 的样式,则所有子项都会继承,所以单独给需要开事件的子项开pointer-events: auto,先允许它接收事件
然后,按照我们传统的,onclick,addEventListener都可以添加事件上去,尽量用addEventListener
css2D/3D制作各种文字效果
这里仅做介绍,有兴趣的自己去了解即可,css3d和css2d使用上几乎没有区别
如果你想做3D文字,请使用css3DRenderer和css3DObject
如果你想做2D且跟随拉进拉远会变大变小,请使用css3DRenderer和css3dSprite
如果你想做2D仅跟随旋转改变位置但是不改变大小,请使用css2dRenderer和css2dObject
如果你想做伪3D文字,如上面那种,也可以用css3Dobject,然后一样的设置即可
css2d/3d需要注意的点
- 创建的dom,宽高值是以屏幕为例的,如果你的场景,非常小,比如说场景长宽高就10的单位,那么你创建出来的css2d会十分巨大,反之,如果场景非常小,那么你创建出来的css2d,会十分渺小,酌情选择是否需要你的dom是否要跟随变化
- css2d/css3d,只要你按照上面的方式设置了,那它必定在屏幕最前,一定会遮挡模型,这个是个无解问题,如果你一定需要面板与模型有正确的遮挡关系,请选择上面的动态贴图文字
- css2d/css3d同样与vue相性很差,这个如果你们感兴趣可以自行研究