有手就行?Three.js实现在三维场景中拖拽添加3D文本标签

前言

在Three.js开发中,相信大家一定遇到过这样的需求吧:给场景中的模型添加标签文本 之类的东西,用来注解当前模型或者场景的一些相关信息,又或者是需要自定义一些文本内容在三维场景中去展示吧。

下面给大家分享一下Three.js 中如何实现拖拽添加3D文本标签,并且实现文本可编辑功能

相关API使用

这里通过 CSS3DObjectCSS3DRenderer 这两个api来创建和渲染3d标签。

  1. CSS3DObject:CSS3DObject是Three.js中用于表示一个CSS元素的类。通过创建一个CSS3DObject,你可以将一个普通的DOM元素嵌入到Three.js的3D场景中。你可以通过CSS3DObject的element属性来访问和操作对应的DOM元素,比如设置样式、添加事件监听器等。CSS3DObject可以在3D空间中进行变换、旋转和缩放,与其他3D对象一起渲染在屏幕上。
  2. CSS3DRenderer:CSS3DRenderer是Three.js中用于渲染CSS3DObject的渲染器。它负责将CSS3DObject转换为在3D场景中正确显示的2D元素。CSS3DRenderer会根据CSS3DObject的位置、旋转和缩放等属性,将对应的DOM元素进行变换并渲染到屏幕上。通过使用CSS3DRenderer,你可以在Three.js的3D场景中实现与普通的HTML/CSS元素交互。

创建3D文本渲染器

通过 CSS3DRenderer 创建一个3d文本渲染器

注意:这里需要创建两个渲染 WebGLRenderer 用来渲染3d场景,CSS3DRenderer 用来渲染3D文本

js 复制代码
	// 创建渲染器
	initRender() {
		this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true }) //设置抗锯齿
		//设置屏幕像素比
		this.renderer.setPixelRatio(window.devicePixelRatio)
		//渲染的尺寸大小
		const { clientHeight, clientWidth } = this.container
		this.renderer.setSize(clientWidth, clientHeight)
		this.container.appendChild(this.renderer.domElement)

		// 创建一个CSS3DRenderer
		this.css3DRenderer = new CSS3DRenderer();
		this.css3DRenderer.setSize(clientWidth, clientHeight);
		this.css3DRenderer.domElement.style.position = 'absolute';
		this.css3DRenderer.domElement.style.pointerEvents = 'none';
		this.css3DRenderer.domElement.style.top = 0;
           this.container.appendChild(this.css3DRenderer.domElement);
	}

创建3D文本标签方法

将创建3D文本标签的方法单独抽离在一个函数中去 create3dTags , 传入三个参数 clientX (鼠标拖拽结束时的X坐标), clientY (鼠标拖拽结束时的X坐标), name(当前element-plus icon组件的name)

1.这里通过 THREE.Raycaster() 检测鼠标在三维场景中的位置(x,y,z)

2.通过Vue3 jsx语法的 render 函数和 createApp 方法 创建3D文本节点

3.通过 CSS3DObject 创建一个可以在3D场景中展示的元素

4.将获取到的鼠标坐标 (x,y,z) 赋值给 CSS3DObject 创建的文本标签

5.最后通过 scene.add(cssObject) 添加到场景中去

js 复制代码
import { CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
import { ElIcon, ElMessage } from 'element-plus';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { h, createApp } from "vue";

function create3dTags(clientX, clientY, name) {

	const { clientHeight, clientWidth, offsetLeft, offsetTop } = this.container
	// 计算鼠标在屏幕上的坐标
	this.mouse.x = ((clientX - offsetLeft) / clientWidth) * 2 - 1
	this.mouse.y = -((clientY - offsetTop) / clientHeight) * 2 + 1
  const raycaster = THREE.Raycaster()
	raycaster.setFromCamera(this.mouse, this.camera);
	const intersects = raycaster.intersectObjects(this.scene.children, true);

	if (intersects.length > 0) {
		var element = document.createElement('div');
		// 创建容器标签
		const backgroundColor = 'rgba(0,127,127,' + (Math.random() * 0.5 + 0.25) + ')'
		const len = this.dragTagList.length + 1
		// 创建3d标签
		const tagvMode = createApp({
			render() {
				return (
					<div>
						<div className="element-tag"
							style={{ width: 60 + 'px', height: 40 + 'px', fontSize: 6 + 'px', color: '#ffffffbf', backgroundColor }}>
							<span className='tag-txt'>
								{`标签-${len}`}
							</span>
						</div>
						<div className='tag-icon' ><ElIcon >{h(ElementPlusIconsVue[name])}</ElIcon></div>
					</div>
				)
			}
		});

		const vNode = tagvMode.mount(document.createElement('div'))
		element.appendChild(vNode.$el)

		var cssObject = new CSS3DObject(element);
          // 获取到鼠标坐标
		const { x, y, z } = intersects[0].point
		cssObject.position.set(x, y, z);
		cssObject.scale.set(.01, .01, .01)
		this.scene.add(cssObject)

	} else {
		ElMessage.warning('当前角度无法获取鼠标位置请调整"相机角度"在添加')
	}
}

手动调用 create3dTags 方法

调用多次创建不同icon的文本标签

js 复制代码
create3dTags(100,444,"More")
create3dTags(120,644,"MuteNotification")
create3dTags(200,544,"RemoveFilled")
create3dTags(200,144,"Document")
create3dTags(150,744,"FolderAdd")
create3dTags(300,54,"KnifeFork")

定义拖拽方法实现拖拽添加

  1. 这里使用的是H5的拖拽API来实现的,dragstartdragdrop
  2. 获取到 ElementPlusIconsVue 中所有图标的 name
  3. 拖拽结束在 drop 函数中获取当前鼠标的坐标
  4. 调用 create3dTags 方法 实现拖拽创建文本标签
js 复制代码
<template>
	<el-scrollbar max-height="150px">
	  <el-row class="tag-row">
		<el-col draggable="true" v-for="tag in tagList" :key="tag.name" :span="4" @dragstart="(e) => onDragstart(e, tag)" @drag="(e) => onDrag(e)">					
		     <div class="tag-box">
			<div class="icon">
			   <el-icon size="16px">
				<component :is="tag.name"></component>
			   </el-icon>
			</div>
		   </div>
		</el-col>
	    </el-row>
    </el-scrollbar>
    <div id="model" @drop="dropEnd" ref="model" @dragover.prevent>  </div>
</template>

<script setup>
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { useMeshEditStore } from '@/store/meshEditStore'
import {ref} from 'vue'

const store = useMeshEditStore();
const tagList = computed(() => {
	const arr = []
	for (const [key] of Object.entries(ElementPlusIconsVue)) {
		arr.push({ name: key })
	}
	return arr
})

const dragTag = ref(null)


// 拖拽开始
const onDragstart = (e, tag) => {
   dragTag.value = tag.name
};
// 拖拽中
const onDrag = (event) => {
	event.preventDefault();
};

// 拖拽结束
const dropEnd = (event) => {
   const { clientX, clientY } = e
    store.modelApi.create3dTags(clientX,clientY,dragTag.value);
};
</script>

界面显示

新增标签参数更新方法,实现标签内容自定义

在标签创建成功之后,我们可能需要动态的修改文本的相关信息如:文字颜色,背景颜色,图标大小,文本高度,文本宽度等信息。

为了让这个3d文本标签更加的灵活化,这里通过新增 updateTagElement 方法来实现

  1. 通过 getObjectByProperty 传入 uuid 获取到当前文本节点的内容
  2. 通过 querySelector 获取到需要改变的元素节点信息
  3. 将传入的参数赋值给对应元素的内容
js 复制代码
import TWEEN from "@tweenjs/tween.js";
function updateTagElement(tag) {
	const object = this.scene.getObjectByProperty('uuid', tag.uuid)
	if (object) {
		const element = object.element.querySelector('.element-tag')
		const iconElement = object.element.querySelector('.tag-icon')
		const txtElement = object.element.querySelector('.tag-txt')
		const { height, backgroundColor, width,
			fontSize, color, innerText, iconColor,
			iconSize, positionY, positionX, positionZ } = tag

		element.style.height = height + 'px'
		element.style.backgroundColor = backgroundColor
		element.style.boxShadow = `0px 0px 4px ${backgroundColor}`
		element.style.width = width + 'px'
		element.style.fontSize = fontSize + 'px'
		element.style.color = color

		txtElement.innerText = innerText

		iconElement.style.fontSize = iconSize + 'px'
		iconElement.style.color = iconColor

		// 修改坐标位置
		const Tween = new TWEEN.Tween(object.position)
		const endPosition = {
			x: positionX,
			y: positionY,
			z: positionZ
		}
		Tween.to(endPosition, 500)
		Tween.onUpdate((val) => {
			object.position.set(val.x || 0, val.y || 0, val.z || 0)
		})
		Tween.start();
	}

}

效果图

带有模型的效果图

结语

ok这样一个有手就行的Three.js拖拽添加和更新3D文本标签的功能就实现了

如果你有更好的方法欢迎评论区留言交流讨论

完整的功能代码地址可参考

threejs 3d模型可视化编辑器 项目的标签配置模块的功能

gitee.com/ZHANG_6666/...

相关推荐
谢尔登9 分钟前
defineProperty如何弥补数组响应式不足的缺陷
前端·javascript·vue.js
蓝瑟忧伤31 分钟前
前端技术新十年:从工程体系到智能化开发的全景演进
前端
Baklib梅梅1 小时前
员工手册:保障运营一致性与提升组织效率的核心载体
前端·ruby on rails·前端框架·ruby
IT_陈寒1 小时前
Redis性能翻倍的5个冷门技巧,90%开发者都不知道第3个!
前端·人工智能·后端
jingling5553 小时前
vue | 在 Vue 3 项目中集成高德地图(AMap)
前端·javascript·vue.js
油丶酸萝卜别吃3 小时前
Vue3 中如何在 setup 语法糖下,通过 Layer 弹窗组件弹出自定义 Vue 组件?
前端·vue.js·arcgis
J***Q2929 小时前
Vue数据可视化
前端·vue.js·信息可视化
ttod_qzstudio11 小时前
深入理解 Vue 3 的 h 函数:构建动态 UI 的利器
前端·vue.js
_大龄11 小时前
前端解析excel
前端·excel
一叶茶12 小时前
移动端平板打开的三种模式。
前端·javascript