Three.js中如何使用CSS3DRenderer和CSS3DSprite實(shí)現(xiàn)模型標(biāo)簽文字
導(dǎo)語(yǔ)
在Three.js中,使用CSS3DRenderer和CSS3DSprite可以輕松地實(shí)現(xiàn)模型標(biāo)簽文字的效果,為場(chǎng)景中的模型提供更直觀的信息展示。本文將介紹如何使用這兩個(gè)工具來(lái)實(shí)現(xiàn)模型標(biāo)簽文字,并提供相應(yīng)的代碼示例。
引言
Three.js是一款強(qiáng)大的JavaScript 3D庫(kù),用于在Web上創(chuàng)建交互式的3D圖形應(yīng)用程序。在Three.js中,CSS3DRenderer和CSS3DSprite是兩個(gè)重要的工具,它們可以用于在3D場(chǎng)景中渲染HTML元素,為用戶提供更豐富的交互體驗(yàn)。
實(shí)現(xiàn)的效果

實(shí)現(xiàn)模型標(biāo)簽文字的步驟
步驟一、導(dǎo)入所需庫(kù)
// Three.js庫(kù)
import * as THREE from 'three';
// CSS3DRenderer用于渲染CSS3D對(duì)象
import { CSS3DRenderer, CSS3DSprite } from "three/examples/jsm/renderers/CSS3DRenderer.js";步驟二、初始化CSS3DRenderer
const labelRenderer = new CSS3DRenderer(); labelRenderer.setSize(window.innerWidth, window.innerHeight); // 設(shè)置渲染器尺寸 labelRenderer.domElement.style.position = 'absolute'; // 設(shè)置渲染器樣式 labelRenderer.domElement.style.top = '0'; // 設(shè)置渲染器樣式 document.body.appendChild(labelRenderer.domElement); // 將渲染器掛載到頁(yè)面上
步驟三、創(chuàng)建css3D標(biāo)簽
const div = document.createElement('div');
div.className = 'workshop-text'; // 添加樣式類
div.innerHTML = '<p>浮法車間</p>'; // 添加標(biāo)簽文字步驟四、創(chuàng)建CSS3DSprite對(duì)象
const sprite = new CSS3DSprite(div); sprite.position.set(8, 3, 42); // 設(shè)置標(biāo)簽位置,這里根據(jù)模型具體位置調(diào)整
步驟五、將CSS3DSprite添加到模型中,并通過GUI控制器控制文字位置
object.add(sprite); // object是模型對(duì)象,這里需要替換為實(shí)際的模型對(duì)象
// 在GUI中添加文件夾用于調(diào)整標(biāo)簽位置
const tagFolder = gui.addFolder('浮法車間標(biāo)簽');
tagFolder.add(sprite.position, 'x', -200, 200).name('X Position'); // 調(diào)整標(biāo)簽x位置
tagFolder.add(sprite.position, 'y', -200, 200).name('Y Position'); // 調(diào)整標(biāo)簽y位置
tagFolder.add(sprite.position, 'z', -200, 200).name('Z Position'); // 調(diào)整標(biāo)簽z位置
tagFolder.open(); // 打開文件夾,默認(rèn)顯示控制器所有代碼:
<template>
<!-- 用于展示Three.js場(chǎng)景的HTML容器 -->
<div id="my-three"></div>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue'
import * as THREE from 'three' // 導(dǎo)入Three.js庫(kù)
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' // 導(dǎo)入軌道控制器以實(shí)現(xiàn)場(chǎng)景的旋轉(zhuǎn)、縮放等交互
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; // 導(dǎo)入GLTF模型加載器
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
import { CSS3DRenderer, CSS3DSprite } from "three/examples/jsm/renderers/CSS3DRenderer.js";
const { proxy } = getCurrentInstance(); // 獲取當(dāng)前Vue組件實(shí)例
const gui = new GUI();
// 設(shè)置cube紋理加載器,立方體紋理加載器
const cubeTextureLoader = new THREE.CubeTextureLoader();
// 設(shè)置環(huán)境貼圖
const envMapTexture = cubeTextureLoader.load([
"../../public/posx.jpg",
"../../public/negx.jpg",
"../../public/posy.jpg",
"../../public/negy.jpg",
"../../public/posz.jpg",
"../../public/negz.jpg",
]);
onMounted(() => {
document.getElementById('my-three')?.appendChild(renderer.domElement) // 將渲染器的DOM元素掛載到頁(yè)面上
init() // 初始化場(chǎng)景、相機(jī)和光源
renderModel() // 設(shè)置渲染參數(shù)
gltfModel1() // 加載GLTF模型
render() // 啟動(dòng)渲染循環(huán)
})
// 定義場(chǎng)景寬度和高度
const width = window.innerWidth, height = window.innerHeight;
const scene = new THREE.Scene(); // 創(chuàng)建場(chǎng)景
const renderer = new THREE.WebGLRenderer() // 創(chuàng)建渲染器
const loader = new GLTFLoader(); // 創(chuàng)建GLTF加載器
const camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000); // 創(chuàng)建透視相機(jī)
const controls = new OrbitControls(camera, renderer.domElement) // 創(chuàng)建控制器
let glbModel;
function init() {
// 光源設(shè)置
const ambient = new THREE.AmbientLight(0xffffff, 0.5); // 添加環(huán)境光
scene.add(ambient);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);// 添加平行光
directionalLight.position.set(95, 585, 39); // 設(shè)置光源位置
scene.add(directionalLight);
//設(shè)置相機(jī)位置
camera.position.set(-20, 300, 700); // 設(shè)置相機(jī)位置
//設(shè)置相機(jī)方向
camera.lookAt(0, 0, 0); // 設(shè)置相機(jī)朝向場(chǎng)景中心
//輔助坐標(biāo)軸
const axesHelper = new THREE.AxesHelper(200);//參數(shù)200標(biāo)示坐標(biāo)系大小,可以根據(jù)場(chǎng)景大小去設(shè)置
scene.add(axesHelper);
// 設(shè)置場(chǎng)景背景色
// scene.background = envMapTexture;
// 設(shè)置渲染器像素比,適應(yīng)設(shè)備分辨率
renderer.setPixelRatio(window.devicePixelRatio);
renderer.antialias = true;
// 初始化 GUI 控件
const camFolder = gui.addFolder('Camera');
camFolder.add(camera.position, 'x', -6500, 6500, 10).name('X Axis');
camFolder.add(camera.position, 'y', -6500, 6500, 10).name('Y Axis');
camFolder.add(camera.position, 'z', -6500, 6500, 10).name('Z Axis');
camFolder.open();
const lightFolder = gui.addFolder('Light');
lightFolder.add(directionalLight.position, 'x', -6500, 6500).name('X Axis');
lightFolder.add(directionalLight.position, 'y', -6500, 6500).name('Y Axis');
lightFolder.add(directionalLight.position, 'z', -6500, 6500).name('Z Axis');
lightFolder.add(directionalLight, 'intensity', 0, 1).name('Intensity');
lightFolder.open();
// 設(shè)置渲染器參數(shù)
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 2.5;
// 初始化 CSS3DRenderer
const labelRender = new CSS3DRenderer();
labelRender.setSize(window.innerWidth, window.innerHeight);
labelRender.domElement.style.position = 'absolute';
labelRender.domElement.style.top = '0px';
labelRender.domElement.style.pointerEvents = 'none';
document.getElementById('my-three').appendChild(labelRender.domElement);
proxy.labelRender = labelRender;
// 更新控制器
controls.update();
}
function gltfModel1() {
// 加載GLTF模型
loader.load("../../public/yuanqu.glb",
function (gltf) { // 模型加載完成后的回調(diào)函數(shù)
glbModel = gltf.scene;
scene.add(gltf.scene) // 將模型添加到場(chǎng)景中
glbModel.traverse((object) => {
if (object.name === "fufachejian") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>浮法車間</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 42); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('浮法車間標(biāo)簽');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "bangongqu") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>辦公樓</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 78); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('辦公區(qū)標(biāo)簽');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "qiye_01") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>企業(yè)1</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 38); // 調(diào)整標(biāo)簽位置
object.add(tag);
}
if (object.name === "qiye_002") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>企業(yè)2</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(12, 5, 54); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('qiye--------');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "chejian_06") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>車間6</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 38); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('食堂標(biāo)簽');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "chejian_03") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>倉(cāng)庫(kù)3</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 38); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('食堂標(biāo)簽');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "chejian_02") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>倉(cāng)庫(kù)2</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 38); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('食堂標(biāo)簽');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "chejian_01") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>倉(cāng)庫(kù)1</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(8, 3, 38); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('食堂標(biāo)簽');
// // 將tag.position對(duì)象中的x, y, z屬性添加到GUI中
// tagFolder.add(tag.position, 'x', -200, 200).name('X Position');
// tagFolder.add(tag.position, 'y', -200, 200).name('Y Position');
// tagFolder.add(tag.position, 'z', -200, 200).name('Z Position');
// tagFolder.open(); // 打開此文件夾以默認(rèn)顯示控制器
}
if (object.name === "yuanliaolou") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>原料樓</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(-10, -1, 38); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
const tagFolder = gui.addFolder('食堂標(biāo)簽');
}
if (object.name === "qizhan") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>氣站</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(-10, -1, 20); // 調(diào)整標(biāo)簽位置
object.add(tag);
// 在GUI中添加一個(gè)用于調(diào)整tag位置的文件夾
// const tagFolder = gui.addFolder('食堂標(biāo)簽');
}
if (object.name === "yuanpian") {
const div = document.createElement('div');
div.className = 'workshop-text';
div.innerHTML = '<p>原片倉(cāng)庫(kù)</p>';
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(-6, 7, 30); // 調(diào)整標(biāo)簽位置
object.add(tag);
}
if (object.name === "yuchuli002" || object.name === "yuchuli003" || object.name === "yuchuli") {
const div = document.createElement('div');
div.className = 'workshop-text';
if (object.name === "yuchuli002") {
div.innerHTML = '<p>預(yù)處理車間1</p>';
} else if(object.name === "yuchuli003") {
div.innerHTML = '<p>預(yù)處理車間2</p>';
}else if(object.name === "yuchuli"){
div.innerHTML = '<p>預(yù)處理車間1</p>';
}
// 創(chuàng)建CSS3DSprite
const tag = new CSS3DSprite(div);
tag.position.set(-6, 7, 30); // 調(diào)整標(biāo)簽位置
object.add(tag);
}
})
},
function (xhr) { // 加載進(jìn)度回調(diào)
const percent = Math.floor((xhr.loaded / xhr.total) * 100); // 計(jì)算加載進(jìn)度百分比
// console.log(`模型加載進(jìn)度:${percent}%`);
}
)
}
function renderModel() {
//渲染
renderer.setSize(width, height)//設(shè)置渲染區(qū)尺寸
renderer.render(scene, camera)//執(zhí)行渲染操作、指定場(chǎng)景、相機(jī)作為參數(shù)
// renderer.setClearColor(0x00ff00); // 設(shè)置背景顏色為綠色/
renderer.toneMapping = THREE.ACESFilmicToneMapping;
// 設(shè)置曝光度
renderer.toneMappingExposure = 3; // 適當(dāng)調(diào)整曝光度
// 設(shè)置控制器的角度限制
controls.minPolarAngle = Math.PI / 4; // 最小極角為 45 度
controls.maxPolarAngle = Math.PI / 2; // 最大極角為 90 度
}
//渲染循環(huán)函數(shù)
function render() {
renderer.render(scene, camera); // 執(zhí)行渲染操作
controls.update() // 更新控制器
proxy.labelRender.render(scene, camera); // 渲染 CSS3D 標(biāo)簽
requestAnimationFrame(render); // 請(qǐng)求下一幀
}
// 畫布跟隨窗口變化
window.onresize = function () {
renderer.setSize(window.innerWidth, window.innerHeight); // 重設(shè)渲染器尺寸
camera.aspect = window.innerWidth / window.innerHeight; // 更新相機(jī)的長(zhǎng)寬比
camera.updateProjectionMatrix(); // 更新相機(jī)的投影矩陣
};
window.addEventListener('keydown', function (event) {
switch (event.key) {
case 'ArrowLeft': // 按左箭頭鍵
moveLeft();
break;
case 'ArrowRight': // 按右箭頭鍵
moveRight();
break;
}
});
// 交互事件
addEventListener('dblclick', onMouseDblclick, false)
function onMouseDblclick(event) {
console.log("event")
let intersects = getIntersects(event);
if (intersects.length !== 0 && intersects[0].object instanceof THREE.Mesh) {
const selectedObject = intersects[0].object;
let selectedObjects = [];
selectedObjects.push(selectedObject);
console.log(selectedObjects)
// outlinePass.selectedObjects = selectedObjects;
}
}
function moveLeft() {
if (glbModel) {
glbModel.position.x -= 10; // 向左移動(dòng)10單位
}
}
function moveRight() {
if (glbModel) {
glbModel.position.x += 10; // 向右移動(dòng)10單位
}
}
//獲取與射線相交的對(duì)象數(shù)組
function getIntersects(event) {
let rayCaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
//通過鼠標(biāo)點(diǎn)擊位置,計(jì)算出raycaster所需點(diǎn)的位置,以屏幕為中心點(diǎn),范圍-1到1
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; //這里為什么是-號(hào),沒有就無(wú)法點(diǎn)中
//通過鼠標(biāo)點(diǎn)擊的位置(二維坐標(biāo))和當(dāng)前相機(jī)的矩陣計(jì)算出射線位置
rayCaster.setFromCamera(mouse, camera);
return rayCaster.intersectObjects(scene.children);
}
</script>
<style>
.workshop-text {}
.workshop-text p {
font-size: 0.9rem;
font-weight: bold;
padding: 10px;
color: #0ff;
}
#tag {
padding: 0px 10px;
border: #00ffff solid 1px;
height: 40px;
border-radius: 5px;
width: 65px;
}
</style>效果:

到此這篇關(guān)于在Three.js中使用CSS3DRenderer和CSS3DSprite實(shí)現(xiàn)模型標(biāo)簽文字的文章就介紹到這了,更多相關(guān)Three.js模型標(biāo)簽文字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談JS獲取元素的N種方法及其動(dòng)靜態(tài)討論
這篇文章主要介紹了淺談JS獲取元素的N種方法及其動(dòng)靜態(tài)討論,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-08-08
javascript對(duì)下拉列表框(select)的操作實(shí)例講解
這篇文章主要介紹了javascript對(duì)下拉列表框(select)的操作。需要的朋友可以過來(lái)參考下,希望對(duì)大家有所幫助2013-11-11
實(shí)現(xiàn)隔行換色效果的兩種方式【實(shí)用】
本文主要介紹了實(shí)現(xiàn)隔行顏色交替 鼠標(biāo)經(jīng)過高亮顏色的兩種方式的具體實(shí)例,有助于理解和使用。方案一:純CSS編寫;方案二:js代碼編寫。需要的朋友可以參考下2016-11-11
JavaScript使用小插件實(shí)現(xiàn)倒計(jì)時(shí)的方法講解
今天小編就為大家分享一篇關(guān)于JavaScript使用小插件實(shí)現(xiàn)倒計(jì)時(shí)的方法講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
如何理解JS函數(shù)防抖和函數(shù)節(jié)流
函數(shù)防抖和函數(shù)節(jié)流都是對(duì)函數(shù)進(jìn)行特殊的設(shè)置,減少該函數(shù)在某一時(shí)間段內(nèi)頻繁觸發(fā)帶來(lái)的副作用。二者只是采用的設(shè)置方式和原理不一樣,其最終的目的是一樣的。2021-05-05

