今天我们来看看在 three.js 中,如何快速的创建三维文本效果。在 three.js 中渲染文本非常简单,你所要做的只是指定想用的字体,以及基本的拉伸属性(基本上就是 ExtrudeGeometry 上提到的那些属性,极个别的除外)。我们先看看具体都有哪些属性,如下表所示:
属性 | 描述 |
---|---|
font | 可选。此属性指定要用的字体,它是一个 THREE.Font 对象 |
size | 可选。此属性指定字体的大小,它是一个 float 型,默认值是 100 |
height | 可选。此属性指定拉伸的高度,它是一个 float 型,默认值是 50 |
curveSegments | 可选。此属性指定文字图形中,曲线部分的分段数,默认值是 12 |
bevelEnabled | 可选。此属性指定文字图形是否启用斜角效果,默认值是 false |
bevelThickness | 可选。此属性指定文字图形的斜角深度,它是一个 float 型,默认值是 10 |
bevelSize | 可选。此属性指定从文字图形的轮廓到斜角有多远,它是一个 float 型,默认值是 8 |
bevelSegments | 可选。此属性指定文字图形的斜角分段数,默认值是 3 |
- helvetiker
- optimer
- gentilis
- droid sans
- droid serif
要想创建一个三维文本图形,就需要加载相应的字体,然后再创建 THREE.TextGeometry 对象,代码大体如下:
function loadFont() {
var fontUrl = "../build/fonts/gentilis_regular.typeface.json";
var loader = new THREE.FontLoader();
loader.load( fontUrl, function ( response ) {
var font = response;
// 定义选项
var options = {
amount: 0.1,
//steps: 1,
font: null,
size: 8,
height: 1.5,
curveSegments: 12,
bevelEnabled: false,
bevelThinkness: 10,
bevelSize: 8,
bevelSegments: 3
};
// 定义 text
var textGeometry = new THREE.TextGeometry("Learning \n Three.js", options);
text = new THREE.SceneUtils.createMultiMaterialObject(textGeometry, meshMaterial);
} );
}
下面我们给出一个具体的示例,完整代码如下所示:
<!DOCTYPE html>
<html>
<head>
<title>示例 06.07 - TextGeometry</title>
<script src="../build/three.js"></script>
<script src="../build/js/controls/OrbitControls.js"></script>
<script src="../build/js/libs/stats.min.js"></script>
<script src="../build/js/libs/dat.gui.min.js"></script>
<script src="../jquery/jquery-3.2.1.min.js"></script>
<style>
body {
/* 设置 margin 为 0,并且 overflow 为 hidden,来完成页面样式 */
margin: 0;
overflow: hidden;
}
/* 统计对象的样式 */
#Stats-output {
position: absolute;
left: 0px;
top: 0px;
}
</style>
</head>
<body>
<!-- 用于 WebGL 输出的 Div -->
<div id="webgl-output"></div>
<!-- 用于统计 FPS 输出的 Div -->
<div id="stats-output"></div>
<!-- 运行 Three.js 示例的 Javascript 代码 -->
<script type="text/javascript">
var scene;
var camera;
var render;
var webglRender;
//var canvasRender;
var controls;
var stats;
var guiParams;
var ground;
var text;
var meshMaterial;
var ambientLight;
var spotLight;
var axesHelper;
//var cameraHelper;
$(function() {
stats = initStats();
scene = new THREE.Scene();
webglRender = new THREE.WebGLRenderer( {antialias: true, alpha: true} ); // antialias 抗锯齿
webglRender.setSize(window.innerWidth, window.innerHeight);
webglRender.setClearColor(0xeeeeee, 1.0);
webglRender.shadowMap.enabled = true; // 允许阴影投射
render = webglRender;
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 2147483647); // 2147483647
camera.position.set(-45.5, 68.2, 90.9);
var target = new THREE.Vector3(10, 0 , 0);
controls = new THREE.OrbitControls(camera, render.domElement);
controls.target = target;
camera.lookAt(target);
$('#webgl-output')[0].appendChild(render.domElement);
window.addEventListener('resize', onWindowResize, false);
// 加入一个坐标轴:X(橙色)、Y(绿色)、Z(蓝色)
axesHelper = new THREE.AxesHelper(60);
scene.add(axesHelper);
ambientLight = new THREE.AmbientLight(0x0c0c0c);
scene.add(ambientLight);
spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 260, 230);
spotLight.shadow.mapSize.width = 5120; // 必须是 2的幂,默认值为 512
spotLight.shadow.mapSize.height = 5120; // 必须是 2的幂,默认值为 512
spotLight.castShadow = true;
scene.add(spotLight);
//cameraHelper = new THREE.CameraHelper(spotLight.shadow.camera);
//scene.add(cameraHelper);
// 加入一个地面
var groundGeometry = new THREE.PlaneGeometry(100, 100, 4, 4);
var groundMaterial = new THREE.MeshLambertMaterial({color: 0x777777}); // MeshBasicMaterial 材质不能接收阴影
ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.set(-0.5 * Math.PI, 0, 0); // 沿着 X轴旋转-90°
ground.receiveShadow = true;
scene.add(ground);
// 材质
meshMaterial = [
new THREE.MeshPhongMaterial({
specular: 0xffffff,
color: 0xeeffff,
shininess: 100,
side: THREE.DoubleSide})
//,new THREE.MeshBasicMaterial({wireframe: true})
];
/** 用来保存那些需要修改的变量 */
guiParams = new function() {
this.rotationSpeed = 0.02;
this.options = {
amount: 0.1,
//steps: 1,
font: null,
size: 8,
height: 1.5,
curveSegments: 12,
bevelEnabled: false,
bevelThinkness: 10,
bevelSize: 8,
bevelSegments: 3
};
}
/** 定义 dat.GUI 对象,并绑定 guiParams 的几个属性 */
var gui = new dat.GUI();
gui.add(guiParams.options, "size", 1, 72, 1).onChange(function(e){createText()});
gui.add(guiParams.options, "height", 0.1, 15, 0.1).onChange(function(e){createText()});
gui.add(guiParams.options, "curveSegments", 1, 30, 1).onChange(function(e){createText()});
gui.add(guiParams.options, "bevelEnabled").onChange(function(e){createText()});
gui.add(guiParams.options, "bevelThinkness", 0, 15, 0.01).onChange(function(e){createText()});
gui.add(guiParams.options, "bevelSize", 0, 15, 0.01).onChange(function(e){createText()});
gui.add(guiParams.options, "bevelSegments", 1, 12, 1).onChange(function(e){createText()});
//../build/fonts/gentilis_regular.typeface.json
//../build/fonts/helvetiker_regular.typeface.json
//../build/fonts/droid/droid_sans_regular.typeface.json
//../build/fonts/droid/droid_serif_regular.typeface.json
loadFont('../build/fonts/droid/droid_sans_regular.typeface.json');
renderScene();
});
/** 渲染场景 */
function renderScene() {
stats.update();
rotateMesh(); // 旋转物体
requestAnimationFrame(renderScene);
render.render(scene, camera);
}
/** 初始化 stats 统计对象 */
function initStats() {
stats = new Stats();
stats.setMode(0); // 0 为监测 FPS;1 为监测渲染时间
$('#stats-output').append(stats.domElement);
return stats;
}
/** 当浏览器窗口大小变化时触发 */
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
render.setSize(window.innerWidth, window.innerHeight);
}
/** 旋转物体 */
var step = 0;
function rotateMesh() {
step += guiParams.rotationSpeed;
scene.traverse(function(mesh) {
if (mesh === text) {
//mesh.rotation.x = step;
mesh.rotation.y = step;
//mesh.rotation.z = step;
}
});
}
function createText() {
scene.remove(text);
// 定义 text
var textGeometry = new THREE.TextGeometry("Learning \n Three.js", guiParams.options);
text = new THREE.SceneUtils.createMultiMaterialObject(textGeometry, meshMaterial);
//text.scale.set(0.1, 0.1, 0.1);
textGeometry.computeBoundingBox();
text.position.set(0, Math.abs(textGeometry.boundingBox.min.y), 0);
scene.add(text);
}
/** 加载字体,传入字体 json 路径,如“../build/fonts/helvetiker_bold.typeface.json” */
function loadFont(fontUrl) {
var loader = new THREE.FontLoader();
loader.load( fontUrl, function ( response ) {
var font = response;
guiParams.options.font = font;
createText();
} );
}
</script>
</body>
</html>
在这个示例中,读者可以利用右上角的菜单功能自由地试验各种常用参数的效果。特别地,关于 bevel 斜角的几个参数比较难观察出效果,这里针对本示例给出一个指导试验方案:先启用 bevelEnabled,然后把右上角 size 和 height 两个值调至最大,仔细观察“T”字轮廓处并不断调小这两个值看看有啥不同效果。
以上都是 three.js 发布包里自带的字体。如果需要自定义的字体又该怎么来弄呢?其实并不难,例如我想在 three.js 中使用 Windows 10 系统里的仿宋字体,步骤如下:
- 第一步,我们把操作系统路径下(例如我的电脑是 C:\Windows\Fonts\仿宋 常规)的字体“仿宋 常规”拷贝出来(譬如 D:\myfonts)
- 第二步,打开浏览器并在地址栏输入 https://gero3.github.io/facetype.js/ 链接,然后点击“选择文件”上传刚才我们拷贝出来的字体文件(D:\myfonts\simfang.ttf)
- 第三步,上传完成后,选中“Generate a JSON file (.json)”选项,最后点击“Convert”按钮,等待一会儿会有一个转换后的 xxx.json 文件下载下来(譬如 D:\myfonts\FangSong_Regular.json)
- 第四步,把这个转换好的 xxx.json 文件加载到你的场景中使用即可
未完待续···