写在前头
首先是很感谢https://blog.csdn.net/ithanmang/article/details/80965712?spm=1001.2014.3001.5501
这篇文章的作者,给予了指点。
以下就是对于外部模型点击事件的添加过程。
import错误:
解决**** is not a constructor 问题处理
https://blog.csdn.net/weixin_40532650/article/details/107537017
计算错误:
canvas的
大小不是整个屏幕的大小,参考:https://blog.csdn.net/u013090676/article/details/77188088
无法识别模型:
问题:
- 首先是,关于导入模型类型。看这篇,
https://blog.csdn.net/ithanmang/article/details/80965712?spm=1001.2014.3001.5501
。大体就是说,导入模型是Group
,需要new
一个THREE.Group()
对象来保存,再存入scene
中。具体代码看下面。 - 第二个就是,鼠标点击模型后,会获取到一个数组。这个数组里面保存着射线首先穿透到最后穿透的对象,就是由近到远的对象。这个数组的类型虽然是Mesh类型的,但是无法使用
instanceof
,下面代码的解决方法是看object.type === ‘Mesh’
,也就是判断类型字符串是不是Mesh
。
完整代码(设置画布):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>点击事件</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#label {
position: absolute;
padding: 10px;
background: rgba(255, 255, 255, 0.6);
line-height: 1;
border-radius: 5px;
}
#display {
height: 600px;
width: 600px;
}
</style>
</head>
<body>
<div id="WebGL-output"></div>
<div id="Stats-output"></div>
<div id="label"></div>
<div>
<canvas id="display"></canvas>
</div>
<script src="libs/three/build/three.js"></script>
<script src="libs/jquery/dist/jquery.js"></script>
<script src="libs/three/examples/js/controls/TrackballControls.js"></script>
<script src="libs/three/examples/js/libs/dat.gui.min.js"></script>
<script src="libs/three/examples/js/libs/stats.min.js"></script>
<script type="module">
// import * as THREE from './libs/three/build/three.js'
// import * as $ from './libs/jquery/dist/jquery.js'
// import * as dat from './libs/three/examples/js/libs/dat.gui.min.js'
// import * as Stats from './libs/three/examples/js/libs/stats.min.js'
import {GLTFLoader} from "./libs/three/examples/jsm/loaders/GLTFLoader.js";
import {OrbitControls} from "./libs/three/examples/jsm/controls/OrbitControls.js";
//性能优化插件
let stats = initStats();
let scene, camera, renderer, controls, light, selectObject, canvas;
//加载器
let gltfLoader;
//用来存外部引入的模型
let group = new THREE.Group();
//场景
function initScene() {
scene = new THREE.Scene();
}
//相机
function initCamera() {
//与视点坐标系联动
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(-4, 2, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
//渲染器
function initRenderer() {
canvas = document.querySelector('#display');
renderer = new THREE.WebGLRenderer({
canvas,
antialias: true //抗锯齿
});
renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
renderer.setClearColor(0x050505);
}
//初始化模型
function initContent() {
let helper = new THREE.GridHelper(100, 50, 0xCD3700, 0x4A4A4A);
scene.add(helper);
gltfLoader = new GLTFLoader();
gltfLoader.load('static/seraphine/scene.gltf', (gltf) => {
let model = gltf.scene;
//遍历模型的每一部分
//traverse这个方法可以遍历调用者和调用者的所有后代
//所以这里的o就是模型的每一部分
model.traverse((o) => {
//将图片作为纹理加载
let explosionTexture = new THREE.TextureLoader().load(
'static/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png'
);
//调整纹理方向,默认为真。翻转图像的Y轴以匹配WebGL纹理坐标空间。
//此处不需要反转,当然也可以试试反转以后什么效果
explosionTexture.flipY = false;
//将纹理图生成基础网格材质(meshBasicMaterial)
const material = new THREE.MeshBasicMaterial({
map: explosionTexture
});
//给模型每部分上材质
o.material = material;
});
//加载外部模型时候基本上都是一个组合对象.
group.add(model)
scene.add(group);
});
}
//函数:重新设置渲染器的展示大小
function resizeRendererToDisplaySize(renderer) {
//这里没看明白往上翻
const canvas = renderer.domElement;
let width = window.innerWidth;
let height = window.innerHeight;
//判断css像素分辨率就和物理像素分辨率是否统一
let canvasPixelWidth = canvas.width / window.devicePixelRatio;
let canvasPixelHeight = canvas.height / window.devicePixelRatio;
//判断是否需要调整
const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
//鼠标双击触发的方法
function onMouseDblclick(event) {
//获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
let intersects = getIntersects(event);
// console.log(intersects);
// console.log(intersects[0].object);
//获取选中最近的Mesh对象
//instance坐标是对象,右边是类,判断对象是不是属于这个类的
if (intersects.length !== 0 && intersects[0].object.type === 'Mesh') {
selectObject = intersects[0].object;
//changeMaterial(selectObject)
} else {
console.log('未选中 Mesh!');
}
}
//获取与射线相交的对象数组
function getIntersects(event) {
event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
//console.log("event.clientX:" + event.clientX);
//console.log("event.clientY:" + event.clientY);
//声明 rayCaster 和 mouse 变量
let rayCaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
//通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1;
mouse.y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1; //这里为什么是-号,没有就无法点中
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
rayCaster.setFromCamera(mouse, camera);
//获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
//+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
let intersects = rayCaster.intersectObjects(group.children, true);
//返回选中的对象
return intersects;
}
// 窗口变动触发的方法
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//键盘按下触发的方法
function onKeyDown(event) {
switch (event.keyCode) {
case 13:
initCamera();
initControls();
break;
}
}
//改变对象材质属性
function changeMaterial(object) {
let material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
transparent: object.material.transparent ? false : true,
opacity: 0.8
});
object.material = material;
}
//初始化轨道控制器
function initControls() {
controls = new OrbitControls(camera, renderer.domElement);
//controls.enableDamping = true;
}
// 初始化灯光
function initLight() {
light = new THREE.SpotLight(0xffffff);
light.position.set(-300, 600, -400);
light.castShadow = true;
scene.add(light);
scene.add(new THREE.AmbientLight(0x5C5C5C));
}
//初始化 dat.GUI
function initGui() {
//保存需要修改相关数据的对象
let gui = new function () {
}
//属性添加到控件
let guiControls = new dat.GUI();
}
//初始化性能插件
function initStats() {
let stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
//更新div的位置
// function renderDiv(object) {
// //获取窗口的一半高度和宽度
// let halfWidth = window.innerWidth / 2;
// let halfHeight = window.innerHeight / 2;
//
// //逆转相机求出二维坐标
// let vector = object.position.clone().project(camera);
//
// //修改div的位置
// $("#label").css({
// left: vector.x * halfWidth + halfWidth,
// top: -vector.y * halfHeight + halfHeight - object.position.y
// });
//
// //显示模型信息
// $("#label").text("name:" + object.name);
// }
//更新控件
function update() {
stats.update();
controls.update();
}
//初始化
function init() {
initScene();
initCamera();
initRenderer();
initContent();
initLight();
initControls();
initGui();
addEventListener('click', onMouseDblclick, false);
addEventListener('resize', onWindowResize, false);
addEventListener('keydown', onKeyDown, false);
}
function animate() {
if (selectObject != undefined && selectObject != null) {
//renderDiv(selectObject);
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
update();
//判断渲染器是否调整,若调整,相机也需要调整aspect
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
//重新设置摄像机看视锥体的横纵比,横纵比一般为屏幕宽高比,不然会挤压变形
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
}
init();
animate();
</script>
</body>
</html>
画布为整个屏幕:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>点击事件</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#label {
position: absolute;
padding: 10px;
background: rgba(255, 255, 255, 0.6);
line-height: 1;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="WebGL-output"></div>
<div id="Stats-output"></div>
<div id="label"></div>
<script src="libs/three/build/three.js"></script>
<script src="libs/jquery/dist/jquery.js"></script>
<script src="libs/three/examples/js/controls/TrackballControls.js"></script>
<script src="libs/three/examples/js/libs/dat.gui.min.js"></script>
<script src="libs/three/examples/js/libs/stats.min.js"></script>
<script type="module">
// import * as THREE from './libs/three/build/three.js'
// import * as $ from './libs/jquery/dist/jquery.js'
// import * as dat from './libs/three/examples/js/libs/dat.gui.min.js'
// import * as Stats from './libs/three/examples/js/libs/stats.min.js'
import {GLTFLoader} from "./libs/three/examples/jsm/loaders/GLTFLoader.js";
import {OrbitControls} from "./libs/three/examples/jsm/controls/OrbitControls.js";
//性能优化插件
let stats = initStats();
let scene, camera, renderer, controls, light, selectObject, canvas;
//加载器
let gltfLoader;
//用来存外部引入的模型
let group = new THREE.Group();
//场景
function initScene() {
scene = new THREE.Scene();
}
//相机
function initCamera() {
//与视点坐标系联动
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(-4, 2, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
//渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true //抗锯齿
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x050505);
document.body.appendChild(renderer.domElement);
}
//初始化模型
function initContent() {
let helper = new THREE.GridHelper(100, 50, 0xCD3700, 0x4A4A4A);
scene.add(helper);
gltfLoader = new GLTFLoader();
gltfLoader.load('static/seraphine/scene.gltf', (gltf) => {
let model = gltf.scene;
//遍历模型的每一部分
//traverse这个方法可以遍历调用者和调用者的所有后代
//所以这里的o就是模型的每一部分
model.traverse((o) => {
//将图片作为纹理加载
let explosionTexture = new THREE.TextureLoader().load(
'static/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png'
);
//调整纹理方向,默认为真。翻转图像的Y轴以匹配WebGL纹理坐标空间。
//此处不需要反转,当然也可以试试反转以后什么效果
explosionTexture.flipY = false;
//将纹理图生成基础网格材质(meshBasicMaterial)
const material = new THREE.MeshBasicMaterial({
map: explosionTexture
});
//给模型每部分上材质
o.material = material;
});
//加载外部模型时候基本上都是一个组合对象.
group.add(model)
scene.add(group);
});
}
//函数:重新设置渲染器的展示大小
function resizeRendererToDisplaySize(renderer) {
//这里没看明白往上翻
const canvas = renderer.domElement;
let width = window.innerWidth;
let height = window.innerHeight;
//判断css像素分辨率就和物理像素分辨率是否统一
let canvasPixelWidth = canvas.width / window.devicePixelRatio;
let canvasPixelHeight = canvas.height / window.devicePixelRatio;
//判断是否需要调整
const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
//鼠标双击触发的方法
function onMouseDblclick(event) {
//获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
let intersects = getIntersects(event);
// console.log(intersects);
// console.log(intersects[0].object);
//获取选中最近的Mesh对象
//instance坐标是对象,右边是类,判断对象是不是属于这个类的
if (intersects.length !== 0 && intersects[0].object.type === 'Mesh') {
selectObject = intersects[0].object;
//changeMaterial(selectObject)
} else {
console.log('未选中 Mesh!');
}
}
//获取与射线相交的对象数组
function getIntersects(event) {
event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
//console.log("event.clientX:" + event.clientX);
//console.log("event.clientY:" + event.clientY);
//声明 rayCaster 和 mouse 变量
let rayCaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
//通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; //这里为什么是-号,没有就无法点中
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
rayCaster.setFromCamera(mouse, camera);
//获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
//+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
let intersects = rayCaster.intersectObjects(group.children, true);
//返回选中的对象
return intersects;
}
// 窗口变动触发的方法
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//键盘按下触发的方法
function onKeyDown(event) {
switch (event.keyCode) {
case 13:
initCamera();
initControls();
break;
}
}
//改变对象材质属性
function changeMaterial(object) {
let material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
transparent: object.material.transparent ? false : true,
opacity: 0.8
});
object.material = material;
}
//初始化轨道控制器
function initControls() {
controls = new OrbitControls(camera, renderer.domElement);
//controls.enableDamping = true;
}
// 初始化灯光
function initLight() {
light = new THREE.SpotLight(0xffffff);
light.position.set(-300, 600, -400);
light.castShadow = true;
scene.add(light);
scene.add(new THREE.AmbientLight(0x5C5C5C));
}
//初始化 dat.GUI
function initGui() {
//保存需要修改相关数据的对象
let gui = new function () {
}
//属性添加到控件
let guiControls = new dat.GUI();
}
//初始化性能插件
function initStats() {
let stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
//更新div的位置
// function renderDiv(object) {
// //获取窗口的一半高度和宽度
// let halfWidth = window.innerWidth / 2;
// let halfHeight = window.innerHeight / 2;
//
// //逆转相机求出二维坐标
// let vector = object.position.clone().project(camera);
//
// //修改div的位置
// $("#label").css({
// left: vector.x * halfWidth + halfWidth,
// top: -vector.y * halfHeight + halfHeight - object.position.y
// });
//
// //显示模型信息
// $("#label").text("name:" + object.name);
// }
//更新控件
function update() {
stats.update();
controls.update();
}
//初始化
function init() {
initScene();
initCamera();
initRenderer();
initContent();
initLight();
initControls();
initGui();
addEventListener('click', onMouseDblclick, false);
addEventListener('resize', onWindowResize, false);
addEventListener('keydown', onKeyDown, false);
}
function animate() {
if (selectObject != undefined && selectObject != null) {
//renderDiv(selectObject);
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
update();
//判断渲染器是否调整,若调整,相机也需要调整aspect
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
//重新设置摄像机看视锥体的横纵比,横纵比一般为屏幕宽高比,不然会挤压变形
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
}
init();
animate();
</script>
</body>
</html>