Threejs是一个非常棒的图形库,可是对于一些交互逻辑,实现起来就没有那么容易了,这篇我们来讲解下如何实现一些常见的交互操作,比如点击、mouseover等效果。
首先来看看Threejs是如何实现点选交互的。在3d的世界中,我们选取一个物体不像2d平面上那么简单,因为我们所对应的是一个3维的世界,而鼠标所在的屏幕是一个2d的世界。如何2d的鼠标屏幕点选与3d的webgl世界关联起来呢?我们有两种方式达到鼠标拾取3d物体的目的。
raycast射线拾取
使用three.js自带的光线投射器(Raycaster)选取物体非常简单,代码如下所示
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove(event) {
// 计算鼠标所在位置的设备坐标
// 三个坐标分量都是-1到1
mouse.x = event.clientX / window.innerWidth * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
function pick() {
// 使用相机和鼠标位置更新选取光线
raycaster.setFromCamera(mouse, camera);
// 计算与选取光线相交的物体
var intersects = raycaster.intersectObjects(scene.children);
}
它是采用包围盒过滤,计算投射光线与每个三角面元是否相交实现的。
但是,当模型非常大,比如说有40万个面,通过遍历的方法选取物体和计算碰撞点位置将非常慢,用户体验不好。
但是使用gpu选取物体不存在这个问题。无论场景和模型有多大,都可以在一帧内获取到鼠标所在点的物体和交点的位置。
使用GPU拾取
实现方法也很简单:
- 创建选取几何体,给每个几何体顶点赋值不同的颜色
- 读取鼠标的位置像素颜色,根据颜色判断当前的位置
- 根据物体位置信息映射出当前的物体
具体实现:
- 传递进来一个需要交互的mesh物体集合
- 遍历集合,把初始的物体信息添加到字典中做映射关系
- clone所有物体的geometry,并且把位置从初始坐标赋值给它们
- 添加顶点颜色,添加到geometriesPicking集合中
- 遍历完之后,用
BufferGeometryUtils.mergeBufferGeometries() 方法合并成一个mesh,添加到pickingScene场景中,渲染即可
apply(meshList) {
const color = new THREE.Color();
let quaternion = new THREE.Quaternion();
const matrix = new THREE.Matrix4();
for (let i = 0, item; item = meshList[i++];) {
this.pickingData[i] = {
position: item.userData.position,
rotation: item.userData.rotation,
scale: item.userData.scale,
object: item
};
let geometry = item.geometry.clone();
quaternion.setFromEuler(item.rotation);
matrix.compose(item.position, item.quaternion, item.scale);
geometry.applyMatrix4(matrix);
this.applyVertexColors(geometry, color.setHex(i));
this.geometriesPicking.push(geometry);
}
let mesh = new THREE.Mesh(BufferGeometryUtils.mergeBufferGeometries(this.geometriesPicking), this.pickingMaterial)
this.pickingScene.add(mesh);
this.highlightBox = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshLambertMaterial({
color: 0xffff00
}));
this.scene.add(this.highlightBox)
}
applyVertexColors(geometry, color) {
const position = geometry.attributes.position;
const colors = [];
for (let i = 0; i < position.count; i++) {
colors.push(color.r, color.g, color.b);
}
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
}
渲染函数
render() {
this.camera.setViewOffset(this.renderer.domElement.width, this.renderer.domElement.height, this.pointer.x * window.devicePixelRatio | 0, this.pointer.y * window.devicePixelRatio | 0, 1, 1);
this.renderer.setRenderTarget(this.pickingTexture);
this.renderer.render(this.pickingScene, this.camera);
this.camera.clearViewOffset();
const pixelBuffer = new Uint8Array(4);
this.renderer.readRenderTargetPixels(this.pickingTexture, 0, 0, 1, 1, pixelBuffer);
const id = (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | (pixelBuffer[2]);
const data = this.pickingData[id];
if (!this.highlightBox) return;
if (data) {
if (data.position && data.rotation && data.scale) {
// data.object.material.color=new THREE.Color("")
this.highlightBox.position.copy(data.position);
this.highlightBox.rotation.copy(data.rotation);
this.highlightBox.scale.copy(data.scale).add(this.offset);
this.highlightBox.visible = true;
}
} else {
this.highlightBox.visible = false;
}
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
请登录后查看评论内容