vue2+three.js做出一个精美的3D地图——5.使用射线(Raycaster)和Canvas贴图来做一些交互
鼠标单击位置纵坐标//屏幕坐标转WebGL标准设备坐标//WebGL标准设备横坐标//WebGL标准设备纵坐标//创建一个射线投射器`Raycaster`//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray//返回.intersectObjects()参数中射线选中的网格模型对象// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
文章目录
前言
代码地址 : https://gitee.com/txcst/3-dmap.git 只想好好做开源。
上一期我们已经把精灵(sprite)和模型引入进来了,这一期主要使用射线 去捕捉几何体,产生一些交互,顺便添加一张背景图片
一、如何与模型产生交互
光线投射Raycaster:主要用于在三维物体中拾取几何体,你可以把他当做一把枪,鼠标按下的时候就是扣下了扳机。发射出一颗子弹打穿你场景里的所有几何体并呈现给你
setRaycaster() {
let onMouseMove = (event) => {
var Sx = event.clientX; //鼠标单击位置横坐标
var Sy = event.clientY; //鼠标单击位置纵坐标
//屏幕坐标转WebGL标准设备坐标
var x = (Sx / window.innerWidth) * 2 - 1; //WebGL标准设备横坐标
var y = -(Sy / window.innerHeight) * 2 + 1; //WebGL标准设备纵坐标
//创建一个射线投射器`Raycaster`
var raycaster = new THREE.Raycaster();
//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
//返回.intersectObjects()参数中射线选中的网格模型对象
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
//加上true 代表遍历子元素
var intersects = raycaster.intersectObjects(scene.children, true);
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
}
};
// document.addEventListener('click', onMouseMove, false)
document
.getElementsByTagName("canvas")[0]
.addEventListener("click", onMouseMove, false);
},
intersects[0].object 就代表着你射线透射穿透的第一个模型
接下来我们给我们要找的几何体加一个标识,好让我们去识别到它
让我们回到 getSpiritAndGltf函数,
在生成兵马俑的精灵的时候 给他添加上name,叫做 bingmayong
let sprite = new THREE.Sprite(spriteMaterial);
sprite.position.set(item.arr[0][0], item.arr[0][1], 8);
sprite.scale.set(8, 8, 8);
sprite.name = "bingmayong";
在setRaycaster方法 射线中找到名字叫bingmayong的 sprite
var intersects = raycaster.intersectObjects(scene.children, true);
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
//由于我的sprite 前面没有几何体 所以第一个就是他 如果你想找的几何体有遮挡,你可以使用find方法
if (intersects[0].object.name == 'bingmayong') {
console.log('点击了 bingmayong')
}
}
点击的效果已经出来了
二、使用Canvas贴图
1.随便创建一个画布贴图,绑定一个平面几何体,并让这个几何体隐藏起来
visible属性决定了几何体的显示和隐藏
//创建canvans贴图
getCanvas() {
canvas = document.createElement("canvas");
canvas.width = 195
canvas.height = 130
//创建canvas贴图
let canvasTexture = new THREE.CanvasTexture(canvas);
let material = new THREE.MeshBasicMaterial({ map: canvasTexture });
//创建平面几何体
let planeGeometry = new THREE.PlaneGeometry(20, 21);
//创建网格对象
canvasPlane = new THREE.Mesh(planeGeometry, material);
canvasPlane.position.set(0, 0, -20)
canvasPlane.rotation.x = -5.5;
//隐藏
canvasPlane.visible = false
map.add(canvasPlane);
},
接下来的逻辑 ,当点到bingmayong的sprite时 把兵马俑的资料和位置传递出来,修改几何体 planeGeometry 的位置和canvas贴图 并让他显示
point属性代表着 当前点击的对象的位置
if (intersects[0].object.name == 'bingmayong') {
// console.log('点击了 bingmayong')
let bmyText = '兵马俑,亦简称秦兵马俑或秦俑,是第一批全国重点文物保护单位、第一批中国世界遗产,位于今陕西省西安市临潼区秦始皇陵以东1.5千米处的兵马俑坑内。'
this.setCanvas(bmyText, [intersects[0].point.x, intersects[0].point.y])
}
获取canvas的上下文对象
setCanvas(text, position) {
let ctx = canvas.getContext('2d')
this.drawText(text, ctx, position)
},
接下里就是根据传的资料 画新的贴图并且修改位置
因为canvas不支持换行,所以要计算一下字符长度 并手动换行
drawText(text, ctx, position) {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除整个canvas
// 设置背景颜色为蓝色
ctx.fillStyle = '#36FFFF';
// 绘制一个矩形,该矩形的背景颜色为前面设置的蓝色
ctx.fillRect(0, 0, 200, 130);
let promise = new Promise(function (resolve, reject) {
ctx.font = "15px Arial";
ctx.fillStyle = "#000000";
var lineWidth = 200; // 每行文本的最大宽度,可根据实际需求进行调整
var lineHeight = 20; // 每行文本的高度差值,可根据实际需求进行调整
var x = 10; // 文本起始位置的x坐标,可根据实际需求进行调整
var y = 20; // 文本起始位置的y坐标,可根据实际需求进行调整
var lineCount = 0; // 当前行数,用于控制换行次数,可根据实际需求进行调整
for (var i = 0; i < text.length; i++) {
var charWidth = ctx.measureText(text[i]).width; // 当前字符的宽度,用于判断是否需要换行,并计算该字符占用空间的大小
if (x + charWidth > lineWidth) { // 如果当前字符无法容纳在当前行中,需要换行并增加一行的高度差值(即多增加一行)
x = 10; // 重置x坐标,开始新的一行,即从下一行的起始位置开始重新绘制字符(相当于清空当前行的绘制)
y += lineHeight; // 增加一行的高度差值
lineCount++; // 增加一行数
}
ctx.fillText(text[i], x, y); // 绘制当前字符
x += charWidth; // 更新x坐标,准备绘制下一个字符
}
resolve('成功')
});
promise.then(res => {
//从新创建一个canvas贴图
let canvasTexture = new THREE.CanvasTexture(canvas);
let newMaterial = new THREE.MeshBasicMaterial({ map: canvasTexture });
//替换掉贴图
canvasPlane.material = newMaterial
//显示
canvasPlane.visible = true
//根据传过来的位置,给几何体从新定位
canvasPlane.position.set(...position, 14)
})
},
现在的效果就算初步完成了 ,然后我们再给故宫的模型加上判断
如果点击到别的地方没有识别到物体 就让canvas隐藏
if (intersects.length > 0) {
if (intersects[0].object.name == 'bingmayong') {
// console.log('点击了 bingmayong')
let bmyText = '兵马俑,亦简称秦兵马俑或秦俑,是第一批全国重点文物保护单位、第一批中国世界遗产,位于今陕西省西安市临潼区秦始皇陵以东1.5千米处的兵马俑坑内。'
this.setCanvas(bmyText, [intersects[0].point.x, intersects[0].point.y])
} else if (intersects[0].object.name.includes('Object')) {
let ggText = '故宫是中国明清两代的皇家宫殿,旧称紫禁城,位于北京中轴线的中心。它占地面积约72万平方米,建筑面积约15万平方米,有大小宫殿七十多座。'
this.setCanvas(ggText, [intersects[0].point.x, intersects[0].point.y])
} else {
canvasPlane.visible = false
}
}
我们再给他 加上一个背景图
getScene() {
scene = new THREE.Scene();
//设置背景
new THREE.TextureLoader().load("/data/sceneBack.png",(texture)=>{
scene.background = texture
});
},
背景图会受到双通道渲染的影响,导致发光 ,这里需要从写一下render方法,在渲染的时候把背景筛出来
render() {
scene.traverse((obj) => {
if ( obj instanceof THREE.Scene) {
this.materials = obj.background
obj.background = null
}
if (bloomLayer.test(obj.layers) === false) {
materials[obj.uuid] = obj.material;
obj.material = darkMaterial;
}
});
bloomComposer.render();
scene.traverse((obj) => {
if (materials[obj.uuid]) {
obj.material = materials[obj.uuid];
delete materials[obj.uuid];
}
if (obj instanceof THREE.Scene) {
obj.background = this.materials
delete this.materials
}
});
finalComposer.render();
},
最终效果
额 感觉有点丑…暂时还没想好样式,先这样把 主要是鼠标点击几何体的一个交互
总结
提示:这里对文章进行总结:
下一期做什么还没想好 … 有可能会开一个Cesium的新坑,好了就这样

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)