Three.js點(diǎn)模型、線模型、精靈模型拾取實(shí)現(xiàn)方法代碼
一、點(diǎn)模型(Points)拾取實(shí)現(xiàn)
實(shí)現(xiàn)步驟:
- 創(chuàng)建點(diǎn)模型:使用
THREE.Points和點(diǎn)材質(zhì) - 設(shè)置點(diǎn)大小:在材質(zhì)中設(shè)置
size屬性 - Raycaster配置:設(shè)置
Points的拾取閾值 - 拾取檢測:使用
intersectObjects檢測相交
完整案例:
<template>
<div class="container" ref="containerRef"></div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
const containerRef = ref(null);
// 創(chuàng)建場景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 創(chuàng)建相機(jī)
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
// 創(chuàng)建點(diǎn)模型
function createPointCloud() {
// 創(chuàng)建100個(gè)隨機(jī)點(diǎn)
const vertices = [];
for (let i = 0; i < 100; i++) {
const x = (Math.random() - 0.5) * 10;
const y = (Math.random() - 0.5) * 10;
const z = (Math.random() - 0.5) * 10;
vertices.push(x, y, z);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
// 創(chuàng)建點(diǎn)材質(zhì) - 關(guān)鍵:設(shè)置點(diǎn)大小
const material = new THREE.PointsMaterial({
color: 0xff0000,
size: 0.2, // 點(diǎn)的大小
sizeAttenuation: true // 點(diǎn)大小是否隨距離衰減
});
// 創(chuàng)建點(diǎn)云對象
const points = new THREE.Points(geometry, material);
points.name = 'pointCloud';
scene.add(points);
return points;
}
// 創(chuàng)建點(diǎn)模型
const pointCloud = createPointCloud();
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
scene.add(directionalLight);
// 添加坐標(biāo)軸輔助
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 動畫循環(huán)
function animate() {
requestAnimationFrame(animate);
// 讓點(diǎn)云緩慢旋轉(zhuǎn)
pointCloud.rotation.y += 0.005;
renderer.render(scene, camera);
}
animate();
// 點(diǎn)模型拾取函數(shù)
function pickPoints(event) {
// 1. 獲取鼠標(biāo)歸一化設(shè)備坐標(biāo)
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 2. 創(chuàng)建Raycaster
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// 3. 關(guān)鍵:設(shè)置點(diǎn)模型的拾取閾值
// 這個(gè)值決定點(diǎn)擊距離點(diǎn)多近才算選中,值越大越容易選中
raycaster.params.Points = { threshold: 0.2 };
// 4. 檢測相交
const intersects = raycaster.intersectObjects([pointCloud]);
// 5. 處理結(jié)果
if (intersects.length > 0) {
const intersect = intersects[0];
console.log('選中了點(diǎn):', intersect);
// 獲取選中的點(diǎn)的索引
const pointIndex = intersect.index;
// 創(chuàng)建高亮點(diǎn)(在該位置添加一個(gè)更大的點(diǎn))
const highlightGeometry = new THREE.BufferGeometry();
const position = intersect.object.geometry.attributes.position;
const pointPosition = [
position.getX(pointIndex),
position.getY(pointIndex),
position.getZ(pointIndex)
];
highlightGeometry.setAttribute('position',
new THREE.Float32BufferAttribute(pointPosition, 3));
const highlightMaterial = new THREE.PointsMaterial({
color: 0xffff00,
size: 0.5,
sizeAttenuation: true
});
const highlight = new THREE.Points(highlightGeometry, highlightMaterial);
highlight.name = 'highlightPoint';
// 移除之前的高亮點(diǎn)
const oldHighlight = scene.getObjectByName('highlightPoint');
if (oldHighlight) scene.remove(oldHighlight);
scene.add(highlight);
// 顯示信息
console.log('點(diǎn)位置:', pointPosition);
console.log('點(diǎn)索引:', pointIndex);
}
}
// 添加點(diǎn)擊事件
window.addEventListener('click', pickPoints);
// 掛載到DOM
onMounted(() => {
const controls = new OrbitControls(camera, containerRef.value);
controls.enableDamping = true;
containerRef.value.appendChild(renderer.domElement);
// 窗口大小變化處理
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
});
</script>
<style>
.container {
width: 100%;
height: 100vh;
}
</style>
關(guān)鍵點(diǎn)說明:
PointsMaterial.size:控制點(diǎn)的大小raycaster.params.Points.threshold:設(shè)置點(diǎn)拾取的敏感度intersect.index:獲取選中點(diǎn)的索引intersect.point:獲取選中點(diǎn)的具體位置
二、線模型(Line)拾取實(shí)現(xiàn)
實(shí)現(xiàn)步驟:
- 創(chuàng)建線模型:使用
THREE.Line或THREE.LineSegments - Raycaster配置:設(shè)置
Line的拾取閾值 - 提高拾取精度:通過輔助方法增加拾取成功率
- 處理拾取結(jié)果:獲取線段信息
完整案例:
<template>
<div class="container" ref="containerRef"></div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
const containerRef = ref(null);
// 創(chuàng)建場景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
// 創(chuàng)建相機(jī)
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
// 創(chuàng)建復(fù)雜的線模型
function createComplexLine() {
// 創(chuàng)建曲線路徑
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-4, 0, 0),
new THREE.Vector3(-2, 3, 1),
new THREE.Vector3(0, 0, 2),
new THREE.Vector3(2, 3, 1),
new THREE.Vector3(4, 0, 0)
]);
// 獲取曲線上的點(diǎn)
const points = curve.getPoints(50);
// 創(chuàng)建線幾何體
const geometry = new THREE.BufferGeometry().setFromPoints(points);
// 創(chuàng)建線材質(zhì)
const material = new THREE.LineBasicMaterial({
color: 0x00aaff,
linewidth: 3 // 注意:大多數(shù)瀏覽器不支持大于1的線寬
});
// 創(chuàng)建線對象
const line = new THREE.Line(geometry, material);
line.name = 'complexLine';
scene.add(line);
return line;
}
// 創(chuàng)建網(wǎng)格線(更容易拾?。?
function createGridLines() {
const group = new THREE.Group();
// 創(chuàng)建水平線
for (let i = -5; i <= 5; i++) {
const geometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(-5, i, 0),
new THREE.Vector3(5, i, 0)
]);
const material = new THREE.LineBasicMaterial({
color: 0x666666,
linewidth: 2
});
const line = new THREE.Line(geometry, material);
line.userData.type = 'gridLine';
line.userData.index = i;
group.add(line);
}
// 創(chuàng)建垂直線
for (let i = -5; i <= 5; i++) {
const geometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(i, -5, 0),
new THREE.Vector3(i, 5, 0)
]);
const material = new THREE.LineBasicMaterial({
color: 0x666666,
linewidth: 2
});
const line = new THREE.Line(geometry, material);
line.userData.type = 'gridLine';
line.userData.index = i;
group.add(line);
}
scene.add(group);
return group;
}
// 創(chuàng)建線模型
const complexLine = createComplexLine();
const gridLines = createGridLines();
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
scene.add(directionalLight);
// 添加坐標(biāo)軸輔助
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 動畫循環(huán)
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
// 線模型拾取函數(shù)
function pickLines(event) {
// 獲取鼠標(biāo)位置
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 創(chuàng)建Raycaster
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// 關(guān)鍵:設(shè)置線模型的拾取閾值(增大以提高拾取成功率)
// 這個(gè)值表示距離線多遠(yuǎn)的點(diǎn)擊算選中(單位:世界單位)
raycaster.params.Line = { threshold: 0.3 };
// 收集所有線對象
const lineObjects = [];
scene.traverse((object) => {
if (object.type === 'Line' || object.type === 'LineSegments') {
lineObjects.push(object);
}
});
// 檢測相交
const intersects = raycaster.intersectObjects(lineObjects);
if (intersects.length > 0) {
const intersect = intersects[0];
const line = intersect.object;
console.log('選中了線:', line);
console.log('相交點(diǎn):', intersect.point);
console.log('距離:', intersect.distance);
console.log('線段索引:', intersect.faceIndex);
// 改變線的顏色
line.material.color.set(Math.random() * 0xffffff);
// 在相交點(diǎn)添加標(biāo)記
addIntersectionMarker(intersect.point);
// 如果是網(wǎng)格線,顯示信息
if (line.userData.type === 'gridLine') {
console.log(`網(wǎng)格線類型: ${line.userData.type}, 索引: ${line.userData.index}`);
}
}
}
// 添加相交點(diǎn)標(biāo)記
function addIntersectionMarker(position) {
// 移除舊的標(biāo)記
const oldMarker = scene.getObjectByName('intersectionMarker');
if (oldMarker) scene.remove(oldMarker);
// 創(chuàng)建標(biāo)記幾何體
const markerGeometry = new THREE.SphereGeometry(0.1, 16, 16);
const markerMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000
});
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
marker.position.copy(position);
marker.name = 'intersectionMarker';
scene.add(marker);
// 3秒后移除標(biāo)記
setTimeout(() => {
if (marker.parent) scene.remove(marker);
}, 3000);
}
// 添加點(diǎn)擊事件
window.addEventListener('click', pickLines);
// 掛載到DOM
onMounted(() => {
const controls = new OrbitControls(camera, containerRef.value);
controls.enableDamping = true;
containerRef.value.appendChild(renderer.domElement);
// 窗口大小變化處理
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
});
</script>
<style>
.container {
width: 100%;
height: 100vh;
}
</style>
關(guān)鍵點(diǎn)說明:
raycaster.params.Line.threshold:控制線拾取的敏感度- 線寬限制:WebGL中大多不支持大于1的
linewidth intersect.faceIndex:獲取選中線段的索引- 使用輔助標(biāo)記:通過添加標(biāo)記點(diǎn)顯示拾取位置
三、精靈模型(Sprite)拾取實(shí)現(xiàn)
實(shí)現(xiàn)步驟:
- 創(chuàng)建精靈模型:使用
THREE.Sprite和精靈材質(zhì) - 設(shè)置精靈大小:通過
scale屬性控制 - Raycaster配置:Sprite會自動被檢測,無需特殊配置
- 處理拾取結(jié)果:獲取精靈信息
完整案例:
<template>
<div class="container" ref="containerRef"></div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
const containerRef = ref(null);
// 創(chuàng)建場景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
// 創(chuàng)建相機(jī)
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 15);
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
// 創(chuàng)建精靈材質(zhì)(使用Canvas繪制紋理)
function createSpriteMaterial(text, color = '#ff0000') {
// 創(chuàng)建Canvas
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const context = canvas.getContext('2d');
// 繪制圓形背景
context.beginPath();
context.arc(128, 128, 120, 0, 2 * Math.PI);
context.fillStyle = color;
context.fill();
// 添加描邊
context.lineWidth = 8;
context.strokeStyle = '#ffffff';
context.stroke();
// 添加文字
context.font = 'bold 60px Arial';
context.fillStyle = '#ffffff';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(text, 128, 128);
// 創(chuàng)建紋理
const texture = new THREE.CanvasTexture(canvas);
// 創(chuàng)建精靈材質(zhì)
const material = new THREE.SpriteMaterial({
map: texture,
transparent: true
});
return material;
}
// 創(chuàng)建多個(gè)精靈
const sprites = [];
const spriteGroup = new THREE.Group();
function createSprites() {
const positions = [
{ x: -5, y: 0, z: 0, text: 'A', color: '#ff0000' },
{ x: -2.5, y: 3, z: -2, text: 'B', color: '#00ff00' },
{ x: 0, y: -2, z: 2, text: 'C', color: '#0000ff' },
{ x: 2.5, y: 3, z: -2, text: 'D', color: '#ffff00' },
{ x: 5, y: 0, z: 0, text: 'E', color: '#ff00ff' },
{ x: 0, y: 5, z: 0, text: 'F', color: '#00ffff' }
];
positions.forEach((pos, index) => {
const material = createSpriteMaterial(pos.text, pos.color);
const sprite = new THREE.Sprite(material);
// 設(shè)置位置
sprite.position.set(pos.x, pos.y, pos.z);
// 設(shè)置大小 - 精靈的大小通過scale控制
sprite.scale.set(2, 2, 1);
// 添加自定義數(shù)據(jù)
sprite.userData = {
type: 'interactiveSprite',
id: index,
text: pos.text,
originalColor: pos.color,
originalScale: { x: 2, y: 2, z: 1 }
};
sprite.name = `sprite_${pos.text}`;
sprites.push(sprite);
spriteGroup.add(sprite);
});
scene.add(spriteGroup);
}
// 創(chuàng)建精靈
createSprites();
// 添加一個(gè)立方體作為參考
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x888888, wireframe: true });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(10, 10, 5);
scene.add(directionalLight);
// 添加坐標(biāo)軸輔助
const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
// 動畫循環(huán)
function animate() {
requestAnimationFrame(animate);
// 讓精靈組緩慢旋轉(zhuǎn)
spriteGroup.rotation.y += 0.005;
renderer.render(scene, camera);
}
animate();
// 精靈模型拾取函數(shù)
function pickSprites(event) {
// 獲取鼠標(biāo)位置
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 創(chuàng)建Raycaster
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// 注意:精靈模型會自動被Raycaster檢測,無需特殊配置
// 檢測相交
const intersects = raycaster.intersectObjects(sprites);
if (intersects.length > 0) {
const intersect = intersects[0];
const sprite = intersect.object;
console.log('選中了精靈:', sprite.name);
console.log('精靈數(shù)據(jù):', sprite.userData);
console.log('相交點(diǎn):', intersect.point);
console.log('距離:', intersect.distance);
// 高亮效果:放大精靈
const originalScale = sprite.userData.originalScale;
sprite.scale.set(
originalScale.x * 1.5,
originalScale.y * 1.5,
originalScale.z
);
// 3秒后恢復(fù)原大小
setTimeout(() => {
sprite.scale.set(
originalScale.x,
originalScale.y,
originalScale.z
);
}, 300);
// 顯示選中信息
showSelectionInfo(sprite.userData);
}
}
// 顯示選中信息
function showSelectionInfo(spriteData) {
// 移除舊的信息顯示
const oldInfo = scene.getObjectByName('selectionInfo');
if (oldInfo) scene.remove(oldInfo);
// 創(chuàng)建信息精靈
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 128;
const context = canvas.getContext('2d');
// 繪制背景
context.fillStyle = 'rgba(0, 0, 0, 0.8)';
context.fillRect(0, 0, canvas.width, canvas.height);
// 繪制文字
context.font = 'bold 40px Arial';
context.fillStyle = '#ffffff';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(`選中: 精靈${spriteData.text} (ID: ${spriteData.id})`,
canvas.width / 2, canvas.height / 2);
// 創(chuàng)建紋理和精靈
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({ map: texture });
const infoSprite = new THREE.Sprite(material);
// 設(shè)置位置(相機(jī)上方)
infoSprite.position.set(0, 8, 0);
infoSprite.scale.set(8, 2, 1);
infoSprite.name = 'selectionInfo';
scene.add(infoSprite);
// 5秒后移除信息
setTimeout(() => {
if (infoSprite.parent) scene.remove(infoSprite);
}, 5000);
}
// 添加點(diǎn)擊事件
window.addEventListener('click', pickSprites);
// 掛載到DOM
onMounted(() => {
const controls = new OrbitControls(camera, containerRef.value);
controls.enableDamping = true;
containerRef.value.appendChild(renderer.domElement);
// 窗口大小變化處理
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
});
</script>
<style>
.container {
width: 100%;
height: 100vh;
}
</style>
關(guān)鍵點(diǎn)說明:
- 精靈創(chuàng)建:使用
THREE.Sprite和SpriteMaterial - 大小控制:通過
sprite.scale.set()控制精靈大小 - 朝向:精靈始終面向相機(jī)(這是Sprite的特性)
- 拾取:Sprite會自動被Raycaster檢測,無需特殊配置
- 紋理創(chuàng)建:通常使用Canvas創(chuàng)建動態(tài)紋理
總結(jié)對比
| 模型類型 | 關(guān)鍵配置 | 特點(diǎn) | 拾取難度 |
|---|---|---|---|
| 點(diǎn)模型 | raycaster.params.Points.threshold | 需要設(shè)置閾值,可獲取點(diǎn)索引 | 中等 |
| 線模型 | raycaster.params.Line.threshold | 需要增大閾值,線寬有限制 | 較高 |
| 精靈模型 | 無需特殊配置 | 始終面向相機(jī),自動檢測 | 容易 |
| 網(wǎng)格模型 | 無需特殊配置 | 最常見的3D物體 | 最容易 |
每個(gè)模型類型都有其特定的應(yīng)用場景和拾取配置,根據(jù)實(shí)際需求選擇合適的模型類型和拾取策略。
到此這篇關(guān)于Three.js點(diǎn)模型、線模型、精靈模型拾取實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Three.js點(diǎn)模型、線模型、精靈模型拾取內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于JS組件實(shí)現(xiàn)拖動滑塊驗(yàn)證功能(代碼分享)
拖動滑塊驗(yàn)證功能在支付寶,微信各大平臺都能見到這樣的功能,那么基于js組件是如何實(shí)現(xiàn)此功能的呢?今天小編就給大家分享下js 拖動滑塊 驗(yàn)證功能的實(shí)現(xiàn)代碼,需要的朋友參考下吧2016-11-11
Javascript中arguments對象的詳解與使用方法
ECMAScript中的函數(shù)并不介意傳遞的參數(shù)有多少,也不介意是什么類型。由于JavaScript允許函數(shù)有不定數(shù)目的參數(shù),所以我們需要一種機(jī)制,可以在 函數(shù)體內(nèi) 部讀取所有參數(shù)。這就是arguments對象的由來。這篇文章將詳細(xì)介紹Javascript中的arguments對象和使用方法。2016-10-10
微信小程序中使用ECharts 異步加載數(shù)據(jù)的方法
這篇文章主要介紹了微信小程序中使用ECharts 異步加載數(shù)據(jù)的方法,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06
js判斷一個(gè)元素是否為另一個(gè)元素的子元素的代碼
用js判斷一個(gè)元素是否為另一個(gè)元素的子元素,再做一些效果的時(shí)候經(jīng)常用到,特別是和鼠標(biāo)事件相關(guān)的應(yīng)用中,比如一個(gè)浮層,在鼠標(biāo)操作浮層內(nèi)元素的時(shí)候浮層顯示,當(dāng)點(diǎn)擊浮層外的元素的時(shí)候隱藏浮層2012-03-03
使用layui 渲染table數(shù)據(jù)表格的實(shí)例代碼
今天小編就為大家分享一篇使用layui 渲染table數(shù)據(jù)表格的實(shí)例代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
細(xì)數(shù)localStorage的用法及使用注意事項(xiàng)
這篇文章主要介紹了細(xì)數(shù)localStorage的用法及使用注意事項(xiàng),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04

