文章目录
一、参考
- threejs 基础教程, 郭隆邦
- threejs API 文档和案例
- CSS2DRenderer(HTML标签)
二、学习遇到的问题
在学习官方案例 css2d — label 发现直接给 div dom添加事件一直不生效,经过努力排查,有两个阶段:
- 分析到的原因是受到了 OrbitControls 控件的影响,删掉了就可以正常使用了 (
错误结论
) - OrbitControls 控制的DOM 节点对象不对,应该要控制 WebGLRenderer 对象
OrbitControls api 介绍
OrbitControls API
三、案例说明
<template><divclass="box-card-left"><divid="threejs"style="border: 1px solid red;position: relative;"></div></div></template><script>import*asTHREEfrom'three';// 引入轨道控制器扩展库OrbitControls.jsimport{ OrbitControls }from"three/examples/jsm/controls/OrbitControls.js";import{ CSS2DObject, CSS2DRenderer }from"three/examples/jsm/renderers/CSS2DRenderer.js";exportdefault{data(){return{// name: "",// scene: null,// camera: null,// renderer: null,// css2DRenderer: null,// effectComposer: null,// mesh: null,// geometry: null,// group: null,// material: null,// texture: null,// position: null,// outlinePass: null,canvasWidth:1000,canvasHeight:800,// color: [],// meshArr: [],};},created(){},mounted(){this.name =this.$route.query.name;this.init();},methods:{goBack(){this.$router.go(-1);},init(){// 创建场景对象this.scene =newTHREE.Scene();// 调用方法创建盒模型 1this.careateBox1();// 调用方法创建盒模型 2this.careateBox2();this.createCone();// 创建圆锥// 创建辅助坐标轴对象const axesHelper =newTHREE.AxesHelper(200);this.scene.add(axesHelper);// 创建透视投影相机对象this.camera =newTHREE.PerspectiveCamera(60,1,0.01,2000);// 设置相机位置this.camera.position.set(200,200,200);// 设置相机指向this.camera.lookAt(0,0,0);// 创建WebGL渲染器对象this.renderer =newTHREE.WebGLRenderer();// 设置渲染器渲染的尺寸this.renderer.setSize(this.canvasWidth,this.canvasHeight);// 调用渲染器的render方法开始渲染,参数是 场景,相机 对象this.renderer.render(this.scene,this.camera);// 获取id 是 threejs 的元素并添加domElement
window.document.getElementById("threejs").appendChild(this.renderer.domElement);this.renderEventBinding()// 创建 CSS2D 渲染器对象(主要:)this.css2DRenderer =newCSS2DRenderer();this.css2DRenderer.setSize(1000,800);this.css2DRenderer.render(this.scene,this.camera);this.css2DRenderer.domElement.style.position ="absolute";this.css2DRenderer.domElement.style.top =0;this.css2DRenderer.domElement.style.pointerEvents ='none';// 取消标签的点击事件
window.document.getElementById("threejs").appendChild(this.css2DRenderer.domElement);this.renderFun();// 创建空间轨道控制器对象const controls =newOrbitControls(this.camera,this.renderer.domElement);
controls.addEventListener("change",()=>{this.renderer.render(this.scene,this.camera);})},// 绑定点击事件renderEventBinding(){this.renderer.domElement.addEventListener('click',(event)=>{// .offsetY、.offsetX以canvas画布左上角为坐标原点,单位pxconst px = event.offsetX;const py = event.offsetY;//屏幕坐标px、py转WebGL标准设备坐标x、y//width、height表示canvas画布宽高度const x =(px /this.canvasWidth)*2-1;const y =-(py /this.canvasHeight)*2+1;//创建一个射线投射器`Raycaster`const raycaster =newTHREE.Raycaster();//.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray// 形象点说就是在点击位置创建一条射线,用来选中拾取模型对象
raycaster.setFromCamera(newTHREE.Vector2(x, y),this.camera);//.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算// 未选中对象返回空数组[],选中一个对象,数组1个元素,选中多个对象,数组多个元素const intersects = raycaster.intersectObjects([this.mesh,this.cone_mesh]);
console.log("射线器返回的对象", intersects);// intersects.length大于0说明,说明选中了模型if(intersects.length >0){// console.log("交叉点", intersects[0].point);// console.log("交叉对象",intersects[0].object)// 选中模型的第一个模型,设置为红色
intersects[0].object.material.color.set(0xff0000);}})},// 创建一个盒模型,父组件是 scene 场景;盒模型设置了位置careateBox1(){// 创建立方缓存几何体对象this.geometry =newTHREE.BoxGeometry(50,50,60);// 创建网格基础材质对象this.material =newTHREE.MeshBasicMaterial({color:0xfafa,});// 创建网格对象this.mesh =newTHREE.Mesh(this.geometry,this.material);// 设置网格对象位置this.mesh.position.set(-50,60,60);// 调用方法创建html元素let dom =this.createDiv("父对象是Scene的模型的标签");const tag =newCSS2DObject(dom);// 创建Vector3对象const wp =newTHREE.Vector3();// 获得this.mesh的世界坐标this.mesh.getWorldPosition(wp);
tag.position.set(wp.x, wp.y, wp.z);// this.mesh.add(tag);this.scene.add(this.mesh);this.scene.add(tag);},// 创建一个盒模型,父组件是 组对象,盒模型与组对象都设置了位置careateBox2(){const group =newTHREE.Group();
group.position.set(30,0,0);const geometry =newTHREE.BoxGeometry(50,50,60);const material =newTHREE.MeshBasicMaterial({color:0xfaf33a,});const mesh =newTHREE.Mesh(geometry, material);// 网格模型设置了位置,且父对象是组对象,这样css2模型对象添加到mesh中后,会直接继承mesh的世界坐标
mesh.position.set(50,60,-60);let dom =this.createDiv("父对象是Group的模型标签");const obj =newCSS2DObject(dom);
mesh.add(obj);
group.add(mesh);this.scene.add(group);},// 圆锥createCone(){// 创建圆锥缓冲几何体对象const coneGeometry =newTHREE.ConeGeometry(20,100,32);// 创建材质对象const material =newTHREE.MeshBasicMaterial({color:0xffaadd});// 创建网格模型对象const cone_mesh =newTHREE.Mesh(coneGeometry, material);const pos = coneGeometry.attributes.position;const dom =this.createDiv("我是圆锥的标注标签");const tag =newCSS2DObject(dom);
cone_mesh.add(tag);
cone_mesh.translateY(50);// 圆锥几何体Y轴平移50
tag.position.y +=50;// css2模型对象this.cone_mesh = cone_mesh // 用来允许用户点击this.scene.add(cone_mesh);},createDiv(innerText='盒子模型标签'){let dom = window.document.createElement("div");
dom.innerHTML = innerText;
dom.style.border ='1px solid blue';
dom.style.height ='20px';
dom.style.color ='#0A58F6';
dom.style.padding ='5px 10px';
dom.style.borderRadius ="5px";
dom.style.top ='-20px';
dom.style.background ="#F7F2EE";
dom.style.pointerEvents ='auto'// 重点,因为css2d渲染器我们设置了pointerEvents = none
dom.addEventListener('click',function(event){
console.log('click', event.currentTarget)// 注意event的坐标,因为css2dobject是绝对定位 + transform})return dom;},renderFun(){this.renderer.render(this.scene,this.camera);this.css2DRenderer.render(this.scene,this.camera);
window.requestAnimationFrame(this.renderFun);},},};</script>
//
<stylelang="scss"scoped>.box-card-left{display: flex;align-items: flex-start;flex-direction: row;position: relative;width: 100%;.box-right{img{width:500px;user-select: none;}}}</style>
四、官方案例 (月亮围绕地球转)
<!DOCTYPEhtml><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"><title>three.js css2d - label</title><linktype="text/css"rel="stylesheet"href="main.css"><style>.label{color: #FFF;font-family: sans-serif;padding: 2px;background:rgba(0, 0, 0, .6);}</style></head><body><divid="info"><ahref="https://threejs.org"target="_blank"rel="noopener">three.js</a> css2d - label</div><scripttype="importmap">{"imports":{"three":"../build/three.module.js","three/addons/":"./jsm/"}}</script><scripttype="module">import*asTHREEfrom'three';import{ OrbitControls }from'three/addons/controls/OrbitControls.js';import{ CSS2DRenderer, CSS2DObject }from'three/addons/renderers/CSS2DRenderer.js';import{GUI}from'three/addons/libs/lil-gui.module.min.js';let gui;let camera, scene, renderer, labelRenderer;const layers ={'Toggle Name':function(){
camera.layers.toggle(0);},'Toggle Mass':function(){
camera.layers.toggle(1);},'Enable All':function(){
camera.layers.enableAll();},'Disable All':function(){
camera.layers.disableAll();}};const clock =newTHREE.Clock();const textureLoader =newTHREE.TextureLoader();let moon;init();animate();functioninit(){constEARTH_RADIUS=1;constMOON_RADIUS=0.27;
camera =newTHREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight,0.1,200);
camera.position.set(10,5,20);
camera.layers.enableAll();
scene =newTHREE.Scene();const dirLight =newTHREE.DirectionalLight(0xffffff,3);
dirLight.position.set(0,0,1);
dirLight.layers.enableAll();
scene.add( dirLight );const axesHelper =newTHREE.AxesHelper(5);
axesHelper.layers.enableAll();
scene.add( axesHelper );//const earthGeometry =newTHREE.SphereGeometry(EARTH_RADIUS,16,16);const earthMaterial =newTHREE.MeshPhongMaterial({specular:0x333333,shininess:5,map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'),specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'),normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'),normalScale:newTHREE.Vector2(0.85,0.85)});
earthMaterial.map.colorSpace =THREE.SRGBColorSpace;const earth =newTHREE.Mesh( earthGeometry, earthMaterial );
scene.add( earth );const moonGeometry =newTHREE.SphereGeometry(MOON_RADIUS,16,16);const moonMaterial =newTHREE.MeshPhongMaterial({shininess:5,map: textureLoader.load('textures/planets/moon_1024.jpg')});
moonMaterial.map.colorSpace =THREE.SRGBColorSpace;
moon =newTHREE.Mesh( moonGeometry, moonMaterial );
scene.add( moon );//
earth.layers.enableAll();
moon.layers.enableAll();const earthDiv = document.createElement('div');
earthDiv.className ='label';
earthDiv.textContent ='Earth';
earthDiv.style.backgroundColor ='transparent';const earthLabel =newCSS2DObject( earthDiv );
earthLabel.position.set(1.5*EARTH_RADIUS,0,0);
earthLabel.center.set(0,1);
earth.add( earthLabel );
earthLabel.layers.set(0);const earthMassDiv = document.createElement('div');
earthMassDiv.className ='label';
earthMassDiv.textContent ='5.97237e24 kg';// earthMassDiv.style.backgroundColor = 'transparent';
earthMassDiv.style.backgroundColor ='red';/*
由于父组件即 labelRenderer.domElement的 pointerEvents 样式设置为 'none',导致子DOM节点选中不到
解决办法: 将子DOM 节点的样式pointerEvents 设置为 'all' 或者 'auto'
* */
earthMassDiv.style.pointerEvents ='all';// 取消标签的点击事件
earthMassDiv.addEventListener('click',function(e){
console.log('我被点击了', e, e.srcElement );});const earthMassLabel =newCSS2DObject( earthMassDiv );
earthMassLabel.position.set(1.5*EARTH_RADIUS,0,0);
earthMassLabel.center.set(0,0);
earth.add( earthMassLabel );
earthMassLabel.layers.set(1);const moonDiv = document.createElement('div');
moonDiv.className ='label';
moonDiv.textContent ='Moon';
moonDiv.style.backgroundColor ='transparent';const moonLabel =newCSS2DObject( moonDiv );
moonLabel.position.set(1.5*MOON_RADIUS,0,0);
moonLabel.center.set(0,1);
moon.add( moonLabel );
moonLabel.layers.set(0);const moonMassDiv = document.createElement('div');
moonMassDiv.className ='label';
moonMassDiv.textContent ='7.342e22 kg';
moonMassDiv.style.backgroundColor ='transparent';const moonMassLabel =newCSS2DObject( moonMassDiv );
moonMassLabel.position.set(1.5*MOON_RADIUS,0,0);
moonMassLabel.center.set(0,0);
moon.add( moonMassLabel );
moonMassLabel.layers.set(1);//
renderer =newTHREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
labelRenderer =newCSS2DRenderer();
labelRenderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.domElement.style.position ='absolute';
labelRenderer.domElement.style.top ='0px';/*
* 由于 labelRenderer.domElement 是在renderer.domElement 的下面,会遮盖到canvas上,导致 鼠标无法控制3D 场景的旋转
* 解决办法:将labelRenderer.domElement的 pointerEvents 样式设置为 'none'
* */
labelRenderer.domElement.style.pointerEvents ='none';// 取消标签的点击事件
document.body.appendChild( labelRenderer.domElement );/*
OrbitControls( object : Camera, domElement : HTMLDOMElement )
object: (必须)将要被控制的相机。该相机不允许是其他任何对象的子级,除非该对象是场景自身。
domElement: 用于事件监听的HTML元素。
* */// 摄像头 影响的是 canvasconst controls =newOrbitControls( camera, renderer.domElement );
controls.minDistance =5;
controls.maxDistance =100;
window.addEventListener('resize', onWindowResize );initGui();}functiononWindowResize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.setSize( window.innerWidth, window.innerHeight );}functionanimate(){requestAnimationFrame( animate );const elapsed = clock.getElapsedTime();
moon.position.set( Math.sin( elapsed )*5,0, Math.cos( elapsed )*5);
renderer.render( scene, camera );
labelRenderer.render( scene, camera );}//functioninitGui(){
gui =newGUI();
gui.title('Camera Layers');
gui.add( layers,'Toggle Name');
gui.add( layers,'Toggle Mass');
gui.add( layers,'Enable All');
gui.add( layers,'Disable All');
gui.open();}</script></body></html>
关键点:
- new OrbitControls( camera, renderer.domElement ); 是监听 WebGLRenderer 的dom事件,即允许鼠标控制3D场景
- CSS2DRenderer对象的dom要设置为 pointerEvents 样式设置为 ‘none’,这样就不会影响鼠标控制WebGLRenderer的渲染场景
- 要将HTML对象监听事件的DOM节点样式pointerEvents 设置为 ‘all’ 或者 ‘auto’,这样是保证他不受到父组件pointerEvents 样式设置为 'none’的影响
本文转载自: https://blog.csdn.net/hbiao68/article/details/140319241
版权归原作者 胖鹅68 所有, 如有侵权,请联系我们删除。
版权归原作者 胖鹅68 所有, 如有侵权,请联系我们删除。