场景介绍
我们如果在业务场景中,需要给一个模型添加文字,并且文字跟随模型运动,那么就需要把2D的文字部分添加到模型对象当中.这里我们用月球围绕地球旋转的demo来进行展示,先看最终呈现效果.
创建地球网格模型
每句代码已添加注释,直接读代码,月球的网格模型建立也相同.
//- 多边形缓存几何模型
var earthGeometry = new THREE.SphereBufferGeometry(EARTH_RADIUS, 20, 20); //- 地球模型
//- 网孔材料
var earthMaterial = new THREE.MeshPhongMaterial({
specular: 0x333333, //- 设置高亮颜色
shininess: 5, //- 设置亮度
map: textureLoader.load('https://localhost:8080/images/earth/earth_atmos_2048.jpg'), //- 设置纹理贴图
// specularMap: textureLoader.load('https://localhost:8080/images/earth/earth_specular_2048.jpg'), //- 设置镜面贴图(也称高光贴图)
// normalMap: textureLoader.load('https://localhost:8080/images/earth/earth_normal_2048.jpg'), //- 设置法线贴图,缺省为null。
normalScale: new THREE.Vector2(0.85, 0.85) //- 设置法线贴图比例,缺省为 (1, 1)。
});
earth = new THREE.Mesh(earthGeometry, earthMaterial); //- 创建地球Mesh对象
scene.add(earth); //- 把地球添加至场景中
为地球添加文字2DCSS
var earthDiv = document.createElement('div'); //- 创建一个div节点对象
earthDiv.className = 'label'; //- 设置地球节点对象的class
earthDiv.textContent = '这个是地球'; //- 设置文字
earthDiv.style.marginTop = '-1em'; //- 设置style
//- 创建2dCSS对象
var earthLabel = new THREE.CSS2DObject(earthDiv);
earthLabel.position.set(0, EARTH_RADIUS, 0);
earth.add(earthLabel); //- 把2d文字添加到地球Mesh对象上,以便跟随移动
创建渲染器 - CSS2DRenderer
WebRenderer渲染器作为场景渲染器之前已经用了很多了,渲染模型就用过,这里就不多讲了,直接看代码.
这里需要注意的是CSS2DRenderer渲染器,之前创建了CSS2DObject实例,需要通过调用CSS2DRenderer进行渲染,就像创建了场景实例后需要使用WebRenderer渲染器进行渲染一样.
//- 创建渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//- CSS2DRenderer 渲染器是生成一个DIV容器,它的作用是能把HTML元素绑定到三维物体上,在DIV容器中各自的DOM元素分别封装到CSS2DObject的实例中,并在scene中增加。
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild(labelRenderer.domElement);
//- 控制视觉
var controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
controls.minDistance = 5;
controls.maxDistance = 100;
场景渲染
和之前一样,我们需要持续调用渲染函数,在渲染函数中对场景以及2dCSS进行渲染,才能把模型,光源和2DCSS内容渲染到场景中
function animate() {
requestAnimationFrame(animate); //- 持续渲染
var elapsed = clock.getElapsedTime(); //- 获取时钟已运行时间。
moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5); //- 根据始终运行的时间设置月球位置,达到一直转的目的
renderer.render(scene, camera); //- 渲染场景
labelRenderer.render(scene, camera); //- 渲染2DCSS文字
}
CSS2DRenderer - 完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>three.js css2d - label</title>
<!-- <link type="text/css" rel="stylesheet" href="main.css"> -->
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<style>
.label {
color: #FFF;
font-family: sans-serif;
padding: 2px;
background: rgba(0, 0, 0, .6);
}
</style>
</head>
<body>
<script src="./js-r100/three.js"></script>
<script src="./js-r100/controls/OrbitControls.js"></script>
<script src="./js-r100/renderers/CSS2DRenderer.js"></script>
<script type="module">
var camera, scene, renderer, labelRenderer;
var clock = new THREE.Clock(); //- 跟踪时间的对象。
var textureLoader = new THREE.TextureLoader(); //- 纹理加载器
var earth, moon;
init();
animate();
function init() {
var EARTH_RADIUS = 1; //- 地球半径大小
var MOON_RADIUS = 0.27; //- 月球半径大小
// 创建远景相机
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
camera.position.set(10, 5, 20); //- 设置远景相机位置
//- 实例化场景
scene = new THREE.Scene();
//- 平行光源
var dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(0, 0, 1); //- 设置平行光源位置
scene.add(dirLight); //- 光源添加到场景中
//- 创建三维坐标系参考线,用于辅助编写three.js
var axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper); //- 辅助线添加到场景中
/**
* 地球
*/
//- 多边形缓存几何模型
var earthGeometry = new THREE.SphereBufferGeometry(EARTH_RADIUS, 20, 20); //- 地球模型
//- 网孔材料
var earthMaterial = new THREE.MeshPhongMaterial({
specular: 0x333333, //- 设置高亮颜色
shininess: 5, //- 设置亮度
map: textureLoader.load('https://localhost:8080/images/earth/earth_atmos_2048.jpg'), //- 设置纹理贴图
// specularMap: textureLoader.load('https://localhost:8080/images/earth/earth_specular_2048.jpg'), //- 设置镜面贴图(也称高光贴图)
// normalMap: textureLoader.load('https://localhost:8080/images/earth/earth_normal_2048.jpg'), //- 设置法线贴图,缺省为null。
normalScale: new THREE.Vector2(0.85, 0.85) //- 设置法线贴图比例,缺省为 (1, 1)。
});
earth = new THREE.Mesh(earthGeometry, earthMaterial); //- 创建地球Mesh对象
scene.add(earth); //- 把地球添加至场景中
/**
* 月亮
*/
var moonGeometry = new THREE.SphereBufferGeometry(MOON_RADIUS, 16, 16);
var moonMaterial = new THREE.MeshPhongMaterial({
shininess: 5,
map: textureLoader.load('https://localhost:8080/images/earth/moon_1024.jpg')
});
moon = new THREE.Mesh(moonGeometry, moonMaterial);
scene.add(moon);
//点光源
// var point = new THREE.PointLight(0xffffff);
// point.position.set(400, 200, 300); //点光源位置
// scene.add(point); //点光源添加到场景中
//- 地球2D样式面板
var earthDiv = document.createElement('div'); //- 创建一个div节点对象
earthDiv.className = 'label'; //- 设置地球节点对象的class
earthDiv.textContent = '这个是地球'; //- 设置文字
earthDiv.style.marginTop = '-1em'; //- 设置style
//- 创建2dCSS对象
var earthLabel = new THREE.CSS2DObject(earthDiv);
earthLabel.position.set(0, EARTH_RADIUS, 0);
earth.add(earthLabel); //- 把2d文字添加到地球Mesh对象上,以便跟随移动
//- 月球2D样式面板 与地球相同
var moonDiv = document.createElement('div');
moonDiv.className = 'label';
moonDiv.textContent = '这个转的是月亮';
moonDiv.style.marginTop = '-1em';
var moonLabel = new THREE.CSS2DObject(moonDiv);
moonLabel.position.set(0, MOON_RADIUS, 0);
moon.add(moonLabel);
//- 创建渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//- CSS2DRenderer 渲染器是生成一个DIV容器,它的作用是能把HTML元素绑定到三维物体上,在DIV容器中各自的DOM元素分别封装到CSS2DObject的实例中,并在scene中增加。
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild(labelRenderer.domElement);
//- 控制视觉
var controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
controls.minDistance = 5;
controls.maxDistance = 100;
}
function animate() {
requestAnimationFrame(animate); //- 持续渲染
var elapsed = clock.getElapsedTime(); //- 获取时钟已运行时间。
moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5); //- 根据始终运行的时间设置月球位置,达到一直转的目的
renderer.render(scene, camera); //- 渲染场景
labelRenderer.render(scene, camera); //- 渲染2DCSS文字
}
</script>
</body>
</html>
CSS3DRenderer
部分场景下,我们需要一个3D的文字或者提示框的时候,我们可以使用CSS3DRenderder,使用方法与CSS2DRenderer类似,但是需要注意的是由于CSS3DRenderder渲染的CSS3DObject对象是一个3D效果的对象,那么当视野转到后面的时候,文字就是反向显示的,参考下图:
创建3DCSS对象
//- 地球3D样式面板
var earthDiv = document.createElement('div'); //- 创建一个div节点对象
earthDiv.className = 'label'; //- 设置地球节点对象的class
earthDiv.textContent = '这个是地球'; //- 设置文字
earthDiv.style.marginTop = '0.04em'; //- 设置style
//- 创建3dCSS对象
var earthLabel = new THREE.CSS3DObject(earthDiv);
earthLabel.position.set(0, EARTH_RADIUS, 0);
earthLabel.scale.multiplyScalar(0.02); //- 缩放级别
earth.add(earthLabel); //- 把2d文字添加到地球Mesh对象上,以便跟随移动
创建3D渲染器
//- 创建渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//- CSS3DRenderer 渲染器是生成一个DIV容器,它的作用是能把HTML元素绑定到三维物体上,在DIV容器中各自的DOM元素分别封装到CSS3DObject的实例中,并在scene中增加。
labelRenderer = new THREE.CSS3DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild(labelRenderer.domElement);
//- 控制视觉
var controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
controls.minDistance = 5;
controls.maxDistance = 100;
CSS3DRenderer - 完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>three.js css2d - label</title>
<!-- <link type="text/css" rel="stylesheet" href="main.css"> -->
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<style>
.label {
color: #FFF;
font-family: sans-serif;
padding: 2px;
background: rgba(0, 0, 0, .6);
}
</style>
</head>
<body>
<script src="./js-r100/three.js"></script>
<script src="./js-r100/controls/OrbitControls.js"></script>
<script src="./js-r100/renderers/CSS3DRenderer.js"></script>
<script type="module">
var camera, scene, renderer, labelRenderer;
var clock = new THREE.Clock(); //- 跟踪时间的对象。
var textureLoader = new THREE.TextureLoader(); //- 纹理加载器
var earth, moon;
init();
animate();
function init() {
var EARTH_RADIUS = 1; //- 地球半径大小
var MOON_RADIUS = 0.27; //- 月球半径大小
// 创建远景相机
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
camera.position.set(10, 5, 20); //- 设置远景相机位置
//- 实例化场景
scene = new THREE.Scene();
//- 平行光源
var dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(0, 0, 1); //- 设置平行光源位置
scene.add(dirLight); //- 光源添加到场景中
//- 创建三维坐标系参考线,用于辅助编写three.js
var axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper); //- 辅助线添加到场景中
/**
* 地球
*/
//- 多边形缓存几何模型
var earthGeometry = new THREE.SphereBufferGeometry(EARTH_RADIUS, 20, 20); //- 地球模型
//- 网孔材料
var earthMaterial = new THREE.MeshPhongMaterial({
specular: 0x333333, //- 设置高亮颜色
shininess: 5, //- 设置亮度
map: textureLoader.load('https://localhost:8080/images/earth/earth_atmos_2048.jpg'), //- 设置纹理贴图
// specularMap: textureLoader.load('https://localhost:8080/images/earth/earth_specular_2048.jpg'), //- 设置镜面贴图(也称高光贴图)
// normalMap: textureLoader.load('https://localhost:8080/images/earth/earth_normal_2048.jpg'), //- 设置法线贴图,缺省为null。
normalScale: new THREE.Vector2(0.85, 0.85) //- 设置法线贴图比例,缺省为 (1, 1)。
});
earth = new THREE.Mesh(earthGeometry, earthMaterial); //- 创建地球Mesh对象
scene.add(earth); //- 把地球添加至场景中
/**
* 月亮
*/
var moonGeometry = new THREE.SphereBufferGeometry(MOON_RADIUS, 16, 16);
var moonMaterial = new THREE.MeshPhongMaterial({
shininess: 5,
map: textureLoader.load('https://localhost:8080/images/earth/moon_1024.jpg')
});
moon = new THREE.Mesh(moonGeometry, moonMaterial);
scene.add(moon);
//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
//- 反向点光源,用于展示后面的情况
var point2 = new THREE.PointLight(0xffffff);
point.position.set(-400, -200, -300); //点光源位置
scene.add(point); //点光源添加到场景中
//- 地球3D样式面板
var earthDiv = document.createElement('div'); //- 创建一个div节点对象
earthDiv.className = 'label'; //- 设置地球节点对象的class
earthDiv.textContent = '这个是地球'; //- 设置文字
earthDiv.style.marginTop = '0.04em'; //- 设置style
//- 创建3dCSS对象
var earthLabel = new THREE.CSS3DObject(earthDiv);
earthLabel.position.set(0, EARTH_RADIUS, 0);
earthLabel.scale.multiplyScalar(0.02); //- 缩放级别
earth.add(earthLabel); //- 把2d文字添加到地球Mesh对象上,以便跟随移动
//- 月球3D样式面板 与地球相同
var moonDiv = document.createElement('div');
moonDiv.className = 'label';
moonDiv.textContent = '这个转的是月亮';
moonDiv.style.marginTop = '0.04em';
var moonLabel = new THREE.CSS3DObject(moonDiv);
moonLabel.position.set(0, MOON_RADIUS, 0);
moonLabel.scale.multiplyScalar(0.02); //- 缩放级别
moon.add(moonLabel);
//- 创建渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//- CSS3DRenderer 渲染器是生成一个DIV容器,它的作用是能把HTML元素绑定到三维物体上,在DIV容器中各自的DOM元素分别封装到CSS3DObject的实例中,并在scene中增加。
labelRenderer = new THREE.CSS3DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild(labelRenderer.domElement);
//- 控制视觉
var controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
controls.minDistance = 5;
controls.maxDistance = 100;
}
//- 渲染函数
function animate() {
requestAnimationFrame(animate); //- 持续渲染
var elapsed = clock.getElapsedTime(); //- 获取时钟已运行时间。
moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5); //- 根据始终运行的时间设置月球位置,达到一直转的目的
renderer.render(scene, camera); //- 渲染场景
labelRenderer.render(scene, camera); //- 渲染2DCSS文字
}
</script>
</body>
</html>
点击事件 显示/隐藏信息面板
在业务场景中,我们会经常对移动的几何体或者模型需要点击查看详细信息.这个时候我们需要在这个几何体的上方添加/删除这个信息面板,前面的例子中,我们虽然只显示了一个名称,但是可以理解为简单的信息面板的一种.并且是通过节点的textcentent的方式往节点里添加的文字.现在我们需要构建一个div,用于展示信息.
就像这样:
这里会通过两种方式进行实现点击地球显示和删除面板的方式, 但由于信息面板的展示是通用的,只是点击事件操作不同,所以我们先把信息面板的初始化部分提取出来
前面我们是创建了一个div,然后通过textcentent的方式显示对应的节点文字.我们也可以在页面把信息面板写好,然后给这个元素节点的样式布局都做好之后,通过直接获取document中的这个节点进行转换CSS3Object实例.最后添加到earth的Mesh对象上,和前一种直接创建节点的方式是一样的.
//- 地球3D样式面板
var earthDiv = document.getElementById('earthBox'); //- 创建一个div节点对象
earthDiv.className = 'earthlabel'; //- 设置地球节点对象的class
earthDiv.setAttribute("isshow", '1');
//- 创建3dCSS对象
var earthLabel = new THREE.CSS3DObject(earthDiv);
earthLabel.position.set(0, EARTH_RADIUS, 0);
earthLabel.scale.multiplyScalar(0.02); //- 缩放级别
earth.add(earthLabel); //- 把2d文字添加到地球Mesh对象上,以便跟随移动
Object/Add or remove实现添加/删除信息面板的方法
当我们获取了intsersects对象后(如果不清楚怎么获取intsersects对象可以参考
语雀内容
),通过判定object下的的material的name属性,可以把创建网格模型材质时的name属性获取,这样我们就可以知道具体点击的是哪个模型.然后通过该模型的children属性,我们可以发现模型下add的Object3D对象,这就是我们前面用CSS3D添加的信息面板,此时,我们只需要通过父级,也就是object的层级调用remove方法,传入对应的object就可以删除掉该对象.反过来添加的时候也只需要在模型对象下调用add方法,传入object就可以给children中添加对应的信息面板.
需要注意的是:当remove方法调用后,页面节点也被删除了,此时在添加时直接使用document获取该节点是无法找到的.由于我们这个demo是初始化就显示信息面板的,所以在remove之前,我用了一个变量先把需要删除的信息面板保存了下来,在添加的时候,只需要把保存的信息面板再add进去即可.
//- 点击事件
function clickEventFn(event) {
let isdown = false, ismove = false;
window.addEventListener('mousemove', function () {
if (isdown) {
ismove = true;
//鼠标拖动事件执行函数
}
})
window.addEventListener('mousedown', function () {
isdown = true
ismove = false
})
window.addEventListener('mouseup', function (event) {
if (!isdown && ismove) {
return 0;
}
if (ismove) {
return 0;
}
let intsersects = getIntersects(event)
if (intsersects.length > 0) {
intsersects.forEach((item, index) => {
if (item.object.material.name !== '') {
if (item.object.material.name === 'earth') {
console.log(intsersects[index].object)
//- 点击的地球
//- 判断点击后的地球对象的children的数量,目前children下只有3DCSS对象
if (intsersects[index].object.children.length < 1) {
intsersects[index].object.add(earthElement)
} else {
intsersects[index].object.children.forEach((item2, index2) => {
//- 判断是否是Object3D的节点
if (item2.type === 'Object3D') {
if (intsersects[index].object.children[index2].element.attributes.isshow.value) {
intsersects[index].object.children[index2].element.attributes.isshow.value = 0
earthElement = intsersects[index].object.children[index2]
intsersects[index].object.remove(intsersects[index].object.children[index2]);
}
}
});
}
}
}
})
}
})
}
通过CSS控制 显示/隐藏(推荐)
作为前端,我们当然知道,其实信息面板的显示和隐藏还可以通过CSS来控制,在初次加载完成后,显示/隐藏并不会删除节点,只需要改变display的状态即可,经过尝试发现通过CSS控制信息面板的显示和隐藏代码量更少,还一样解决了业务需求.
//- 点击事件
function clickEventFn(event) {
let isdown = false, ismove = false;
window.addEventListener('mousemove', function () {
if (isdown) {
ismove = true;
//鼠标拖动事件执行函数
}
})
window.addEventListener('mousedown', function () {
isdown = true
ismove = false
})
window.addEventListener('mouseup', function (event) {
if (!isdown && ismove) {
return 0;
}
if (ismove) {
return 0;
}
let intsersects = getIntersects(event)
if (intsersects.length > 0) {
intsersects.forEach((item, index) => {
if (item.object.material.name !== '') {
if (item.object.material.name === 'earth') {
console.log(intsersects[index].object)
document.getElementById('earthBox').style.display === 'none' ? document.getElementById('earthBox').style.display = 'block' : document.getElementById('earthBox').style.display ='none';
}
}
})
}
})
}