Three - 3D文字

3D 文字 示例解读

从简单的文字开始学习吧

Code

<script>

if (!Detector.webgl) Detector.addGetWebGLMessage();
// enable cache for loaders
THREE.Cache.enabled = true;

var container, stats, permalink, hex, color;

var camera, cameraTarget, scene, renderer;

var group, textMesh1, textMesh2, textGeo, materials, pointLight;

// default func for rotate light
var rotateFunc = () => { }

var firstLetter = true;
// as the step for the light
var seed = 10
// displayed text
var text = "TAL",

height = 30,
size = 100,
// 字距离镜面的高度
hover = 30,
// 字的分割部分,越多越平滑
curveSegments = 4,
//bevelEnabled 为true时候, 斜角厚度
bevelThickness = 20,
// 边缘斜角的大小
bevelSize = 1.5,
// 边缘斜角的过渡层数
bevelSegments = 3,
// 是否开启斜角
bevelEnabled = true,
// 后面会load font
font = undefined,

fontName = "optimer", // helvetiker, optimer, gentilis, droid sans, droid serif
fontWeight = "bold"; // normal bold
// 是否要显示镜像文字
var mirror = true;

// 字体集合
var fontMap = {

"helvetiker": 0,
"optimer": 1,
"gentilis": 2,
"droid/droid_sans": 3,
"droid/droid_serif": 4

};
// 字重集合
var weightMap = {

"regular": 0,
"bold": 1

};

var reverseFontMap = [];
var reverseWeightMap = [];
// 数据整理
// ["helvetiker","optimer","gentilis","droid/droid_sans","droid/droid_serif"]
for (var i in fontMap) reverseFontMap[fontMap[i]] = i;
//["regular","bold"]
for (var i in weightMap) reverseWeightMap[weightMap[i]] = i;

var targetRotation = 0;
var targetRotationOnMouseDown = 0;

var mouseX = 0;
var mouseXOnMouseDown = 0;

var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;

var fontIndex = 1;
var controls

init();
animate();

function decimalToHex(d) {

var hex = Number(d).toString(16);
hex = "000000".substr(0, 6 - hex.length) + hex;
return hex.toUpperCase();

}

function init() {

container = document.createElement('div');
document.body.appendChild(container);

permalink = document.getElementById("permalink");

// CAMERA

camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
camera.position.set(0, 400, 700);

cameraTarget = new THREE.Vector3(0, 150, 0);

controls = new THREE.OrbitControls(camera)

// SCENE
scene = new THREE.Scene();
// black bg
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0x000000, 250, 1400);

// AxesHelper
var axesHelper = new THREE.AxesHelper(500);
// scene.add(axesHelper);

// LIGHTS
// var dirLight = new THREE.DirectionalLight(0xffffff, 0.125);
// dirLight.position.set(0, 0, 0).normalize();
// scene.add(dirLight);


// var helper = new THREE.DirectionalLightHelper(dirLight, 150);
// scene.add(helper);

// 点光源 强度 颜色
pointLight = new THREE.PointLight(0xffffff, 1.5);
pointLight.position.set(0, 100, 90);
scene.add(pointLight);
var pointLightHelper = new THREE.PointLightHelper(pointLight, 10);
// scene.add(pointLightHelper);

// Get text from hash

var hash = document.location.hash.substr(1);

if (hash.length !== 0) {

var colorhash = hash.substring(0, 6);
var fonthash = hash.substring(6, 7);
var weighthash = hash.substring(7, 8);
var bevelhash = hash.substring(8, 9);
var texthash = hash.substring(10);

hex = colorhash;
console.log(colorhash, fonthash, weighthash, bevelhash, texthash, parseInt(colorhash, 16))
pointLight.color.setHex(parseInt(colorhash, 16));

fontName = reverseFontMap[parseInt(fonthash)];
fontWeight = reverseWeightMap[parseInt(weighthash)];

bevelEnabled = parseInt(bevelhash);

text = decodeURI(texthash);

updatePermalink();

} else {

pointLight.color.setHSL(Math.random(), 1, 0.5);
hex = decimalToHex(pointLight.color.getHex());

}

// 字的材料, 金属反光效果
materials = [
new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true }), // front
new THREE.MeshPhongMaterial({ color: 0xffffff }) // side
];

group = new THREE.Group();
group.position.y = 100;

scene.add(group);

loadFont();

var plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry(10000, 10000),
new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true })
);
plane.position.y = 100;
// 变成横向的 转半圈, 正面是透明的 朝上面
plane.rotation.x = - Math.PI / 2;
scene.add(plane);

// RENDERER
// 光滑边缘
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);

// STATS

stats = new Stats();
container.appendChild(stats.dom);

// EVENTS

document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('touchstart', onDocumentTouchStart, false);
document.addEventListener('touchmove', onDocumentTouchMove, false);
document.addEventListener('keypress', onDocumentKeyPress, false);
document.addEventListener('keydown', onDocumentKeyDown, false);

// 旋转等过节流函数
rotateFunc = _.throttle(lightRotate, 20)

document.getElementById("color").addEventListener('click', function () {

pointLight.color.setHSL(Math.random(), 1, 0.5);
hex = decimalToHex(pointLight.color.getHex());

updatePermalink();

}, false);

document.getElementById("font").addEventListener('click', function () {

fontIndex++;

fontName = reverseFontMap[fontIndex % reverseFontMap.length];

loadFont();

}, false);


document.getElementById("weight").addEventListener('click', function () {

if (fontWeight === "bold") {

fontWeight = "regular";

} else {

fontWeight = "bold";

}

loadFont();

}, false);

document.getElementById("bevel").addEventListener('click', function () {

bevelEnabled = !bevelEnabled;

refreshText();

}, false);

//

window.addEventListener('resize', onWindowResize, false);

}

function onWindowResize() {
// 窗口缩放的时候视角自适应
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize(window.innerWidth, window.innerHeight);

}

//

function boolToNum(b) {

return b ? 1 : 0;

}

function updatePermalink() {

var link = hex + fontMap[fontName] + weightMap[fontWeight] + boolToNum(bevelEnabled) + "#" + encodeURI(text);

permalink.href = "#" + link;
window.location.hash = link;

}

function onDocumentKeyDown(event) {

if (firstLetter) {

firstLetter = false;
text = "";

}

var keyCode = event.keyCode;

// backspace

if (keyCode == 8) {

event.preventDefault();

text = text.substring(0, text.length - 1);
refreshText();

return false;

}

}

function onDocumentKeyPress(event) {

var keyCode = event.which;

// backspace

if (keyCode == 8) {

event.preventDefault();

} else {

var ch = String.fromCharCode(keyCode);
text += ch;

refreshText();

}

}

function loadFont() {
// 加载对应字体资源
var loader = new THREE.FontLoader();
loader.load('fonts/' + fontName + '_' + fontWeight + '.typeface.json', function (response) {
console.log("load font", response)
font = response;

refreshText();

});

}

function createText() {

textGeo = new THREE.TextGeometry(text, {

font: font,

size: size,
height: height,
curveSegments: curveSegments,

bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelEnabled: bevelEnabled

});
// 根据配置 从新计算
textGeo.computeBoundingBox();
textGeo.computeVertexNormals();

// "fix" side normals by removing z-component of normals for side faces
// (this doesn't work well for beveled geometry as then we lose nice curvature around z-axis)

if (!bevelEnabled) {

var triangleAreaHeuristics = 0.1 * (height * size);

for (var i = 0; i < textGeo.faces.length; i++) {

var face = textGeo.faces[i];

if (face.materialIndex == 1) {

for (var j = 0; j < face.vertexNormals.length; j++) {

face.vertexNormals[j].z = 0;
face.vertexNormals[j].normalize();

}

var va = textGeo.vertices[face.a];
var vb = textGeo.vertices[face.b];
var vc = textGeo.vertices[face.c];

var s = THREE.GeometryUtils.triangleArea(va, vb, vc);

if (s > triangleAreaHeuristics) {

for (var j = 0; j < face.vertexNormals.length; j++) {

face.vertexNormals[j].copy(face.normal);

}

}

}

}

}

var centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
// 创建文字
textMesh1 = new THREE.Mesh(textGeo, materials);

textMesh1.position.x = centerOffset;
textMesh1.position.y = hover;
textMesh1.position.z = 0;

textMesh1.rotation.x = 0;
textMesh1.rotation.y = Math.PI * 2;

group.add(textMesh1);

if (mirror) {

textMesh2 = new THREE.Mesh(textGeo, materials);

textMesh2.position.x = centerOffset;
textMesh2.position.y = -hover;
textMesh2.position.z = height;

textMesh2.rotation.x = Math.PI;
textMesh2.rotation.y = Math.PI * 2;

group.add(textMesh2);

}

}

function refreshText() {

updatePermalink();

group.remove(textMesh1);
if (mirror) group.remove(textMesh2);

if (!text) return;

createText();

}

function onDocumentMouseDown(event) {

event.preventDefault();

document.addEventListener('mousemove', onDocumentMouseMove, false);
document.addEventListener('mouseup', onDocumentMouseUp, false);
document.addEventListener('mouseout', onDocumentMouseOut, false);

mouseXOnMouseDown = event.clientX - windowHalfX;
targetRotationOnMouseDown = targetRotation;

}

function onDocumentMouseMove(event) {

mouseX = event.clientX - windowHalfX;

targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02;

}

function onDocumentMouseUp(event) {

document.removeEventListener('mousemove', onDocumentMouseMove, false);
document.removeEventListener('mouseup', onDocumentMouseUp, false);
document.removeEventListener('mouseout', onDocumentMouseOut, false);

}

function onDocumentMouseOut(event) {

document.removeEventListener('mousemove', onDocumentMouseMove, false);
document.removeEventListener('mouseup', onDocumentMouseUp, false);
document.removeEventListener('mouseout', onDocumentMouseOut, false);

}

function onDocumentTouchStart(event) {

if (event.touches.length == 1) {

event.preventDefault();

mouseXOnMouseDown = event.touches[0].pageX - windowHalfX;
targetRotationOnMouseDown = targetRotation;

}

}

function onDocumentTouchMove(event) {

if (event.touches.length == 1) {

event.preventDefault();

mouseX = event.touches[0].pageX - windowHalfX;
targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05;

}

}


function animate() {
requestAnimationFrame(animate);

render();
stats.update();

}
// 灯光围绕文字转
function lightRotate() {
const x = Math.sin(seed + 1) * 10
const y = Math.cos(seed + 1) * 10
// const z = Math.sin(seed + 1) * 10
seed += 0.1
console.log(x, y)
// debugger
pointLight.position.x += x
pointLight.position.y += y
// pointLight.position.z += z
}

function render() {
// group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
// group.rotation.y += 0.05;
// console.log(group)
controls.update()
// lightRotate()
rotateFunc()

camera.lookAt(cameraTarget);

renderer.clear();
renderer.render(scene, camera);

}

</script>

效果预览