场景介绍
我们如果在业务场景中,需要给一个模型添加文字,并且文字跟随模型运动,那么就需要把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'; //- 设置地球节点对象的classearthDiv.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.jsvar 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'; //- 设置地球节点对象的classearthDiv.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'; //- 设置地球节点对象的classearthDiv.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.jsvar 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'; //- 设置地球节点对象的classearthDiv.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'; //- 设置地球节点对象的classearthDiv.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 = trueismove = 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 = 0earthElement = 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 = trueismove = 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';}}})}})}
