门户网站是指提供什么的网站,网站优化需要工具,关键词优化推广公司,网站后台无法上传本地图片如果你想用HTML元素作为标签标注三维场景中模型信息#xff0c;需要考虑定位的问题。比如一个模型#xff0c;在代码中你可以知道它的局部坐标或世界坐标xyz#xff0c;但是你并不知道渲染后在canvas画布上位置#xff0c;距离web页面顶部top和左侧的像素px值。自己写代码把…如果你想用HTML元素作为标签标注三维场景中模型信息需要考虑定位的问题。比如一个模型在代码中你可以知道它的局部坐标或世界坐标xyz但是你并不知道渲染后在canvas画布上位置距离web页面顶部top和左侧的像素px值。自己写代码把世界坐标xyz转化为像素px表示屏幕坐标比较麻烦不过threejs扩展库CSS2DRenderer.js可以帮助你实现坐标转化给HTML元素标签定位下面给大家演示如何实现。
CSS2DRenderer
CSS2DRenderer是Three.js的扩展库用于在 WebGL 场景中渲染 HTML 元素。它允许开发者将DOM元素如div、span等直接集成到Three.js的3D场景中从而实现2D元素与3D物体的交互和结合。总的来说CSS2DRenderer为Three.js提供了强大的功能使得在3D环境中集成2D界面元素成为可能这对于创建具有丰富视觉效果和交互性的3D应用非常有用。
CSS2DRenderer的主要特点是它支持将HTML元素作为3D对象进行处理这些HTML元素可以通过CSS进行样式化并且可以在Three.js的渲染循环中进行更新和动画处理。
使用CSS2DRenderer的基本步骤
引入扩展库CSS2DRenderer.js
threejs扩展库CSS2DRenderer.js提供了两个类CSS2渲染器CSS2DRenderer、CSS2模型对象CSS2DObject。
// 引入CSS2渲染器CSS2DRenderer和CSS2模型对象CSS2DObject
import { CSS2DObject, CSS2DRenderer } from three/examples/jsm/renderers/CSS2DRenderer.js;HTML元素创建标签
div idtag标签内容/divCSS2模型对象CSS2DObject
将HTML元素如div标签添加到场景中这些元素将被视为CSS2DObject实例通过CSS2DObject类把一个HTML元素转化为一个类似threejs网格模型的对象可以像操作其他Three.js对象一样对它们进行变换如位置、旋转等或者添加到场景中。
const div document.getElementById(tag);
// HTML元素转化为threejs的CSS2模型对象
const tag new CSS2DObject(div);通过.position属性设置标签模型对象的xyz坐标。
tag.position.set(50,0,50);把HTML元素对应的CSS2模型对象添加到其它模型对象或三维场景中。
// 添加到场景
scene.add(tag);
// 添加到组
const group new THREE.Group();
group.add(tag);
// 添加几何体上
const geometry new THREE.BoxGeometry(50, 50, 50);
const material new THREE.MeshBasicMaterial({color: 0xfaf33a,
});
const mesh new THREE.Mesh(geometry, material);
mesh.add(tag);标签位置的不同设置方式
CSS2模型标签对象位置和要标注的mesh放在同一个位置这样HTML标签就可以标注mesh。
const group new THREE.Group();
const geometry new THREE.BoxGeometry(1, 1, 1);
const material new THREE.MeshBasicMaterial({color: 0xfaf33a,
});
const mesh new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);
const div document.getElementById(tag);
// HTML元素转化为threejs的CSS2模型对象
const tag new CSS2DObject(div);
tag.position.set(0, 0, 0);
group.add(mesh, tag);如果需要的mesh有多个父对象且都有自己的位置属性.position设置mesh标签对象位置CSS2DObject.position的时候就需要考虑mesh父对象的位置对mesh的影响。 let group new THREE.Group();const geometry new THREE.BoxGeometry(1, 1, 1);const material new THREE.MeshBasicMaterial({color: 0xfaf33a,});const mesh new THREE.Mesh(geometry, material);mesh.position.set(0, 0, 0);// mesh设置一个父对象meshGroupconst meshGroup new THREE.Group();meshGroup.add(mesh);// mesh位置受到父对象局部坐标.positionn影响meshGroup.position.x -1;const div document.getElementById(tag);// HTML元素转化为threejs的CSS2模型对象const tag new CSS2DObject(div);tag.position.set(0, 0, 0);group.add(meshGroup, tag);上面可以看出不考虑mesh父元素.position对mesh的影响设置标签位置标签偏离。此时标签的位置设置应根据Mesh父元素的位置设置。
// tag.position.set(0, 0, 0);
tag.position.set(-1, 0, 0);.getWorldPosition()方法计算世界坐标。
mesh.position.set(0, 0, 0);
// mesh设置一个父对象meshGroup
const meshGroup new THREE.Group();
meshGroup.add(mesh);
// mesh位置受到父对象局部坐标.positionn影响
meshGroup.position.x -1;const tag new CSS2DObject(div);
const worldPosition new THREE.Vector3();
// 获取mesh的世界坐标(meshGroup.position和mesh.position累加结果)
mesh.getWorldPosition(worldPosition);
// mesh世界坐标复制给tag
tag.position.copy(worldPosition);const group new THREE.Group();
// 最后meshGroup和tag放在同一个父对象中即可
group.add(meshGroup, tag);CSS2模型对象作为mesh子对象。无论mesh有多少个父对象CSS2模型对象作为mesh子对象,可以直接继承mesh的世界坐标相比通过.getWorldPosition()方法获取世界坐标再设置标签模型位置CSS2DObject.position更方便。
const div document.getElementById(tag);
// HTML元素转化为threejs的CSS2模型对象
const tag new CSS2DObject(div);
// 标签tag作为mesh子对象默认受到父对象位置影响
mesh.add(tag);一个模型对象不管是一个mesh还是多个mesh组成的模型本身是有尺寸的如果你把标签模型对象CSS2DObject作为该模型对象的子元素标签默认是标注在模型的局部坐标系坐标原点。
loader.load(小人.glb, function (gltf) {const obj gltf.scene.getObjectByName(Bai);const div document.getElementById(tag);// HTML元素转化为threejs的CSS2模型对象const tag new CSS2DObject(div);// 标签tag作为obj子对象obj.add(tag);
})CSS2渲染器CSS2DRenderer
CSS2渲染器CSS2DRenderer和常用的WebGL渲染器WebGLRenderer一样都是渲染器只是渲染模型对象不同WebGLRenderer主要是渲染threejs自身的网格、点、线等模型CSS2DRenderer用来渲染HTML元素标签对应的CSS2模型对象CSS2DObject。
// 创建一个CSS2渲染器CSS2DRenderer
const css2Renderer new CSS2DRenderer();CSS2Renderer.render()
CSS2渲染器CSS2DRenderer和WebGL渲染器WebGLRenderer虽然不同但是有些属性和方法是相似的比如.domElement、.setSize()、.render()。
// 用法和webgl渲染器渲染方法类似
css2Renderer.render(scene, camera);CSS2Renderer.setSize()
设置CSS2Renderer.render()渲染输出标签的尺寸范围一般和threejs canvas画布宽高度一致即可。
const width window.innerWidth; // canvas画布宽度
const height window.innerHeight; // canvas画布高度
renderer.setSize(width, height); // 设置渲染区域尺寸
css2Renderer.setSize(width, height);CSS2Renderer.domElement
CSS2Renderer.render()渲染会输出标签对应的HTML元素也就是css2Renderer.domElement你可以插入到web网页中任何你想放的位置。
document.body.appendChild(css2Renderer.domElement);
// 放在指定元素中
document.getElementById(box).appendChild(css2Renderer.domElement);threejs执行css2Renderer.render()之后你打开浏览器控制台元素选项找到你创建的HTML标签div idtag标签内容/div你可以发现div idtag/div外面多了一层div父元素有两个DOM(一个存放三维模型一个存放CSS2D对象)CSS2Renderer.domElement是一个HTML元素对应的就是div idtag/div外面的父元素。
!-- div idtag/div外面多了一层div父元素 --
div styleoverflow: hidden; width: 1087px; height: 1069px;div idtag标签内容/div
/div还可以发现你创建的HTML标签div idtag/div不在原来的位置了其实是被CSS2Renderer改变了位置。css2Renderer.render()渲染HTML元素对应的CSS2模型对象本质上就是根据CSS2模型对象的xyz世界坐标计算HTML标签元素在canvas画布上的屏幕像素坐标位置。
CSS2Renderer.domElement重新定位
CSS2Renderer.domElement重新定位将外面div父元素重新定位叠加到canvas画布上与canvas画布重合即可可以看到HTML标签的标注效果。注意这里css2Renderer尺寸一定要和渲染器canvas的尺寸保持一致不然位置不好对应。
// HTML标签div idtag/div外面父元素叠加到canvas画布上且重合
css2Renderer.domElement.style.position absolute;
css2Renderer.domElement.style.top 0px;!-- div idtag/div外面多了一层div父元素 --
div styleoverflow: hidden; width: 1087px; height: 1069px; position: absolute; top: 0px;div idtag标签内容/div
/divHTML标签遮挡Canvas画布事件
HTML元素标签div idtag/div外面div父元素遮挡了Canvas画布鼠标事件会造成相机控件OrbitControls的旋转、缩放等操作无效也有可能会影响你的射线拾取等等任何与canvas画布有关的鼠标事件都有可能受到影响。
设置.style.pointerEvents none就可以解决HTML元素标签对threejs canvas画布鼠标事件的遮挡。
css2Renderer.domElement.style.pointerEvents none;设置.style.zIndex改变css2Renderer.domElement层级这种方式如果标签层级在下threejs canvas画布在上标签被canvas画布遮挡看不到标签
renderer.domElement.style.zIndex -1;
css2Renderer.domElement.style.zIndex 1;相机控件操作失效还可通过设置OrbitControls监听的HTML元素为css2Renderer.domElement解决。
controls new OrbitControls(camera, css2Renderer.domElement);Canvas尺寸变化(HTML标签)
canvas画布完全填充浏览器文档区域如果窗口尺寸变化了通过renderer.setSize()设置canvas画布尺寸HTML标签相关的CSS渲染器代码也要同步设置css2Renderer.setSize()。执行css2Renderer.setSize()设置CSS2渲染器输出的HTML标签.domElement的尺寸保持和canvas画布尺寸一样。
// 画布跟随窗口变化
window.onresize function () {const width window.innerWidth;const height window.innerHeight;// cnavas画布宽高度重新设置renderer.setSize(width,height);// HTML标签css2Renderer.domElement尺寸重新设置css2Renderer.setSize(width,height);camera.aspect width / height;camera.updateProjectionMatrix();
};HTML标签渲染前隐藏
在CSS2渲染器渲染HTML标签重新定位标签之前threejs执行代码和加载gltf模型也是需要时间的这时候标签对应的HTML、CSS代码会显示在web页上面。可以先把标签隐藏display: none;等gltf模型加载完成HTML元素转化CSS2模型对象以后再取消HTML隐藏状态CSS2渲染器默认会把标签设置为display: block;这样就不用自己代码恢复HTML标签元素的隐藏状态了。
!-- CSS2渲染器渲染器之前隐藏标签 --
div idtag styledisplay: none;div鼠标选中模型添加标签
当发生鼠标事件如果射线拾取到模型对象就把标签做为选中模型的子对象或作为选中模型对应标注点空对象的子对象。
let modelObj null;
addEventListener(click, function (event) {// ...射线拾取的代码// 射线交叉计算拾取模型const intersects raycaster.intersectObjects(scene);if (intersects.length 0) {// tag会标注在intersects[0].object.parent模型的局部坐标系原点位置intersects[0].object.ancestors.add(tag);modelObj intersects[0].object.parent; // 被选中模型}else{//把原来选中模型对应的标签隐藏if(modelObj){modelObj.remove(tag); // 从场景移除}}
})单击关闭HTML标签
前面我们了解到如果你的项目要求三维场景中添加标签时不能影响canvas画布的事件必须设置css2Renderer.domElement.style.pointerEvents none此刻你单击按钮去关闭HTML元素标签会发现无效可以考虑把标签的子元素关闭按钮单独设置.style.pointerEvents auto或者stylepointer-events: auto;从而解决点击无效的问题。 div idtag style{{ background: #FFFFFF, display: none }}span标签内容/spanspan classNameclose style{{ pointerEvents: auto }} onClick{closeEventInfo}×/span
/div// 关闭事件
function closeEventInfo() {// 获取标签对象let tagObj scene.getObjectByName(tag);if (tagObj) {let tagParent tagObj.parent;// 将标签从父元素中移除tagParent.remove(tagObj);}
}CSS3DRenderer渲染HTML标签
CSS3渲染器CSS3DRenderer和CSS2渲染器CSS2DRenderer整体使用流程基本相同只是在HTML标签渲染效果方面不同比如CSS3渲染的标签会跟着场景相机同步缩放而CSS2渲染的标签默认保持自身像素值。
// 引入CSS3渲染器CSS3DRenderer和CSS3模型对象CSS3DObject
import { CSS3DObject, CSS3DRenderer } from three/examples/jsm/renderers/CSS3DRenderer.js;// 创建一个CSS3渲染器CSS3DRenderer
const css3Renderer new CSS3DRenderer();
css3Renderer.setSize(width, height);
// HTML标签div idtag/div外面父元素叠加到canvas画布上且重合
css3Renderer.domElement.style.position absolute;
css3Renderer.domElement.style.top 0px;
//设置.pointerEventsnone解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
css3Renderer.domElement.style.pointerEvents none;
document.body.appendChild(css3Renderer.domElement);// 渲染循环
function render() {css3Renderer.render(scene, camera);requestAnimationFrame(render);
}window.onresize function () {// HTML标签css3Renderer.domElement尺寸重新设置css3Renderer.setSize(width,height);
};通过CSS3DObject类可以把一个HTML元素转化为一个CSS3模型对象就像threejs的网格模型一样可以添加到场景中可以设置位置可以作为其它模型对象的子对象。
const div document.getElementById(tag);
// HTML元素转化为threejs的CSS3模型对象
const tag new CSS3DObject(div);
// 标签tag作为mesh子对象默认标注在模型局部坐标系坐标原点
mesh.add(tag);
// 相对父对象局部坐标原点偏移100
tag.position.y 100;
// 如果标签覆盖区域过大可以适当缩小缩放标签尺寸
tag.scale.set(0.5, 0.5, 1);CSS3模型对象CSS3DObject渲染结果就像一个矩形平面网格模型一样。你通过相机控件OrbitControls旋转、缩放三维场景CSS3模型对象CSS3DObject跟着旋转、缩放。旋转过程中HTML元素标签的正反面都可以看到但是旋转到背面是一个对称的效果。
禁止CSS3DObject标签对应HTMl元素背面显示
可是禁止展示对应HTML元素的背面直观感受就是当相机控件OrbitControls旋转到CSS3模型对象CSS3DObject的背面时CSS3模型对象CSS3DObject不可见。
div idtag stylebackface-visibility: hidden;标签内容/div批量创建标签
// 需要批量标注的标签数据arr
const arr [设备A,设备B,停车场];
for (let i 0; i arr.length; i) {// 注意是多个标签需要克隆复制一份const div document.getElementById(tag).cloneNode();div.innerHTML arr[i]; // 标签数据填写// HTML元素转化为threejs的CSS3对象const tag new CSS3DObject(div);div.style.pointerEvents none; // 避免标签遮挡canvas鼠标事件// obj是建模软件中创建的一个空对象const obj gltf.scene.getObjectByName(arr[i]);// tag会标注在空对象obj对应的位置obj.add(tag);tag.scale.set(0.1, 0.1, 1); // 适当缩放模型标签tag.position.y 40/2 * 0.1;// 标签底部和空对象标注点重合偏移高度像素值一半*缩放比例
}CSS3精灵模型CSS3DSprite
CSS3对象模型CSS3DObject渲染效果类似矩形平面网格模型Mesh。CSS3精灵模型CSS3DSprite渲染效果精灵模型对象Sprite。CSS3精灵模型CSS3DSprite对应的HTML标签可以跟着场景缩放位置可以跟着场景旋转但是自身的姿态角度始终平行于canvas画布不受旋转影响。
// 引入CSS3精灵模型对象CSS3DSprite
import { CSS3DSprite } from three/addons/renderers/CSS3DRenderer.js;const div document.getElementById(tag);
// HTML元素转化为threejs的CSS3模型对象
const tag new CSS3DSprite(div);
// 标签tag作为mesh子对象默认标注在模型局部坐标系坐标原点
const geometry THREE.BoxGeometry(100, 100, 100);
geometry.translate(0, 50, 0);
const material new THREE.MeshBasicMaterial({color: 0xfaf33a,
});
const mesh new THREE.Mesh(geometry, material);
mesh.add(tag);
// 相对父对象局部坐标原点偏移100标签高16标签偏移108刚好标签的底部在几何体的顶部
tag.position.y 108;CSS2DRenderer和CSS3DRenderer的区别
从上面可以看出CSS3DRenderer和CSS2DRenderer的主要区别在于它们面向摄像机的方向、场景缩放时的表现、是否被模型遮挡以及如何处理DOM事件。
CSS2DRenderer主要用于在Three.js场景中渲染2D元素。它允许开发者在3D空间中放置和操作2D对象这些对象的大小和位置不会随着摄像机的视角的变化而改变保持固定大小始终面向屏幕。CSS2DRenderer基于Web技术使用HTML元素和CSS样式来创建2D对象这些对象可以与3D对象进行交互如点击事件等。CSS3DRenderer用于在Three.js场景中渲染具有深度信息的3D对象。它允许开发者创建和操作真正的3D对象这些对象的大小和位置会随着摄像机的视角的变化而改变提供更加立体和逼真的效果。CSS3DRenderer可以处理更复杂的3D模型和动画适用于需要深度感知和立体效果的场景。
总的来说CSS2DRenderer适合添加简单的2D元素到3D场景中如文字标签和图像而CSS3DRenderer则更适合处理复杂的3D模型和动画。选择使用哪种Renderer取决于项目的具体需求和预期的视觉效果如果元素的大小需要随摄像机的位置和角度变化或者希望元素能够响应摄像机的缩放那么CSS3DRenderer是合适的选择。如果元素的大小需要保持固定或者希望元素能够像传统2D图形一样不受摄像机影响那么CSS2DRenderer是更合适的选择。
完整示例代码
射线拾取方法参考
import React, { useRef, useEffect } from react;
import * as THREE from three;
import { OrbitControls } from three/examples/jsm/controls/OrbitControls;
import { GLTFLoader } from three/examples/jsm/loaders/GLTFLoader;
// 引入CSS2渲染器CSS2DRenderer和CSS2模型对象CSS2DObject
import { CSS2DObject, CSS2DRenderer } from three/examples/jsm/renderers/CSS2DRenderer.js;
// 引入CSS3渲染器CSS3DRenderer和CSS3模型对象CSS3DObject
import { CSS3DObject, CSS3DSprite, CSS3DRenderer } from three/examples/jsm/renderers/CSS3DRenderer.js;
import { getCanvasIntersects } from /common/three/index.js; // three自定义公共方法射线穿透let scene, camera, renderer, css2Renderer, css3Renderer, controls, tag;export default function Css2DOr3DRenderer() {const box useRef(); // canvas盒子// 加载模型function setGltfModel() {// 导入GlTF模型let gltfLoader new GLTFLoader();gltfLoader.load(模型.glb, (gltf) {// 添加标签// create2DTag(gltf.scene);create3DTag(gltf.scene);scene.add(gltf.scene);});}// 添加2D标签function create2DTag(modelObj) {// 添加标签if (!tag) {const tagDom document.getElementById(tag);// HTML元素转化为threejs的CSS2模型对象tag new CSS2DObject(tagDom);tag.name tag;tag.position.y 100;}// 避免重复添加if (modelObj.getObjectByName(tag)) {return;} else {modelObj.add(tag);}}// 添加3D标签function create3DTag(modelObj) {// 添加标签if (!tag) {const tagDom document.getElementById(tag);// 避免标签遮挡canvas鼠标事件tagDom.style.pointerEvents none;// HTML元素转化为threejs的CSS3模型对象tag new CSS3DSprite(tagDom);// tag new CSS3DObject(tagDom);tag.name tag;tag.position.y 10;tag.scale.set(0.5, 0.5, 1); // 缩放标签尺寸}// 避免重复添加if (modelObj.getObjectByName(tag)) {return;} else {modelObj.add(tag);}}// 关闭标签事件function closeEventInfo() {// 获取标签对象let tagObj scene.getObjectByName(tag); if (tagObj) { let tagParent tagObj.parent; // 将标签从父元素中移除 tagParent.remove(tagObj); }}// 渲染动画function renderFn() {requestAnimationFrame(renderFn);controls.update();// 用相机渲染一个场景renderer renderer.render(scene, camera);// 渲染HTML标签对应的CSS2DObject模型对象// css2Renderer css2Renderer.render(scene, camera);// 渲染HTML标签对应的CSS3DObject模型对象css3Renderer css3Renderer.render(scene, camera);}// 监听窗体变化、自适应窗体事件function onWindowResize() {let width box.current.offsetWidth;let height box.current.offsetHeight;camera.aspect width / height;// 更新相机投影矩阵在相机任何参数被改变以后必须被调用camera.updateProjectionMatrix();renderer.setSize(width, height);// css2Renderer.setSize(width, height); // 设置css2D渲染区域尺寸css3Renderer.setSize(width, height); // 设置css3D渲染区域尺寸}// 监听事件 窗体监听、点击事件监听useEffect(() {// 监听窗体变化window.addEventListener(resize, onWindowResize, false);// 监听点击事件box.current.addEventListener(click, (event) {let selectObj getCanvasIntersects(event, box.current, camera, scene);if (selectObj[0]) {let modelObj selectObj[0].object.parent;create2DTag(modelObj);// create3DTag(modelObj);} else {closeEventInfo();}}, false);}, []);// 初始化环境、灯光、相机、渲染器useEffect(() {scene new THREE.Scene();// 添加光源const ambitlight new THREE.AmbientLight(0x404040);scene.add(ambitlight);const sunlight new THREE.DirectionalLight(0xffffff); sunlight.position.set(-20, 1, 1); scene.add(sunlight);// 加载模型setGltfModel();let axisHelper new THREE.AxesHelper(100);scene.add(axisHelper); // 坐标辅助线加入到场景中// 获取宽高设置相机和渲染区域大小let width box.current.offsetWidth;let height box.current.offsetHeight;let k width / height;// 投影相机camera new THREE.PerspectiveCamera(45, k, 1, 100000);camera.position.set(32, 122, 580);camera.lookAt(scene.position);// 创建一个webGL对象renderer new THREE.WebGLRenderer({//增加下面两个属性可以抗锯齿antialias: true,alpha: true});renderer.setSize(width, height); // 设置渲染区域尺寸renderer.setClearColor(0x333333, 1); // 设置颜色透明度renderer.outputEncoding THREE.sRGBEncoding; // 解决纹理贴图颜色偏差box.current.appendChild(renderer.domElement);// 创建一个CSS2渲染器CSS2DRenderer// css2Renderer new CSS2DRenderer(); // css2Renderer.setSize(width, height);// // HTML标签div idtag/div外面父元素叠加到canvas画布上且重合// css2Renderer.domElement.style.position absolute;// css2Renderer.domElement.style.top 0px;// css2Renderer.domElement.style.pointerEvents none; // 避免标签遮挡canvas鼠标事件// box.current.appendChild(css2Renderer.domElement);// 创建一个CSS3渲染器CSS3DRenderercss3Renderer new CSS3DRenderer();css3Renderer.setSize(width, height);// // HTML标签div idtag/div外面父元素叠加到canvas画布上且重合css3Renderer.domElement.style.position absolute;css3Renderer.domElement.style.top 0px;css3Renderer.domElement.style.pointerEvents none; // 避免标签遮挡canvas鼠标事件box.current.appendChild(css3Renderer.domElement);// 监听鼠标事件controls new OrbitControls(camera, renderer.domElement);// 渲染renderFn();}, []);useEffect(() {return () {// 清除数据scene null;camera null;renderer null;css2Renderer null;css3Renderer null;controls null;tag null;}}, []);return div classNameui_container_boxdiv style{{ position: relative, width: 100%, height: 100% }} ref{box}/divdiv idtag style{{ background: #FFFFFF, display: none }}span标签内容/spanspan classNameclose style{{ pointerEvents: auto }} onClick{closeEventInfo}×/span/div/div ;
}