【工作记录】Threejs学习笔记-引入OrbitControls

2024-03-06 06:52

本文主要是介绍【工作记录】Threejs学习笔记-引入OrbitControls,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

前一篇文章我们介绍了three.js中的基础概念,并给出了展示整体流程的一个简单示例, 本文我们继续研究。

问题

我们在很多3d效果图上都能看到鼠标移动或者缩进实现旋转或者放大缩小的效果,这个在three.js中是通过OrbitControls这个组件实现的。

早上在使用threejs引入OrbitControls的时候发现新版本(大约r159以后)的引入方式都是通过import来引入的,而我想在纯html中做简单测试,

然而如果在html中直接使用import又会提示出各种各样的错误,经过反复的踩坑和实验以及百度,终于解决了这个模块化导入的问题。

所以本文姑且算是一篇踩坑记录吧。

期望目标

本文我们还是通过简单示例来展示要描述的问题和解决方案。

最终实现的效果是两个虚线圆环,自动转动,也可以根据鼠标拖拽或者缩放来触发旋转和缩放效果。

关于虚线圆环的实现方式和参数设置不是本文重点,可参考本文后面的代码进行实现和测试。

开始

前面提到要实现鼠标控制旋转和缩放需要用到OrbitControls这个组件,这个组件在three.js的源码中可以找到。

下载three.js源码

下载地址: https://gitee.com/mirrors/three.js

可以在标签中选择版本进行下载,截止发文时的最新版本是r162

源码下载

新建目录

目录结构如下图:
目录结构
可以直接把下载下来的源码文件中的对应文件夹直接搬过来,也可以只在对应目录放置需要的文件(本文中主要是用到了three.js或者three.module.js/OrbitControls.js这两个文件)。

引入js

这里需要说明一下,在r159版本以前是提供纯js版本的OrbitControls的代码的,位置在examples/js/controls文件夹中,但是后面新出的版本(r160+)删除了js目录,新增了jsm目录,存放的是以模块化的方式实现的组件代码,这个模块化代码直接在html中引用的话控制台会报错。

这里直接说解决方案:

  • 使用旧版本的OrbitControls.js文件,可以直接在html中引入。

    <script src="static/r159/build/three.min.js"></script>
    <script src="static/r159/examples/js/OrbitControls.js"></script>
    <script>
    ....
    // 创建并初始化OrbitControls
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    </script>
    
  • 使用新版本的模块化方式引入,需要调整引入方式

    <script type="importmap">{"imports": {"three": "../static/latest/build/three.module.js","three/addons/": "../static/latest/examples/jsm/"}}</script><script type="module">import * as THREE from 'three';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';....// 创建并初始化OrbitControlsconst controls = new OrbitControls(camera, renderer.domElement);....
    </script>
    

PS: 注意以上两种方案在实例化OrbitControls对象时是有区别的。

OrbitControls的介绍和属性配置

THREE.OrbitControls是Three.js库中一个非常实用的相机控制器,它允许用户通过鼠标或触摸设备以直观的方式旋转、缩放和平移3D场景。这个控件主要是为了让用户能够交互式地查看3D模型或场景,模拟类似3D建模软件中的轨道摄像机操作。

以下是关键参数配置和示例:

用途代码示例
新建对象const controls = new OrbitControls(camera, renderer.domElement);
设置相机围绕的目标点(焦点)controls.target.set(x, y, z); // 默认为(0, 0, 0)
是否启用控件controls.enabled = true //默认为true
控制功能开关controls.enableRotate = true; // 是否允许旋转,默认为true
controls.enableZoom = true; // 是否允许缩放,默认为true
controls.enablePan = true; // 是否允许平移,默认为true
限制范围controls.minDistance = 1; // 相机离目标的最小距离,默认值通常取决于场景大小
controls.maxDistance = 1000; // 相机离目标的最大距离
controls.minZoom = 0.1; // 最小缩放比例(如果支持)
controls.maxZoom = 100; // 最大缩放比例(如果支持)
旋转速度与灵敏度controls.rotateSpeed = 1.0; // 旋转速度,默认值为1
controls.zoomSpeed = 1.2; // 缩放速度,默认值为1
controls.panSpeed = 0.8; // 平移速度,默认值为0.8
旋转限制controls.minAzimuthAngle = -Math.PI / 2; // 最小左旋角度
controls.maxAzimuthAngle = Math.PI / 2; // 最大右旋角度
controls.minPolarAngle = 0; // 最小抬头角度(防止相机倒置)
controls.maxPolarAngle = Math.PI / 2; // 最大低头角度(比如只允许鸟瞰视角)

OrbitControls本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围。

controls.addEventListener('change', function () {// 浏览器控制台查看相机位置变化console.log('camera.position',camera.position);
});

补充知识

1. importMap的使用场景

import map 是一种Web平台的原生模块加载映射机制,它允许开发者在浏览器中自定义模块导入路径与实际加载地址之间的映射关系。通过 <script type="importmap"> 标签在HTML文档中定义一个 import map,可以解决以下使用场景:

  1. 模块路径重定向
    • 当项目依赖了多个库,而这些库可能因为版本更新、CDN地址变化或者内部模块结构调整等原因需要修改其导入路径时,import map 可以集中管理这些映射关系,无需更改代码中的 import 语句。
  2. 命名空间或包结构支持
    • 在Node.js环境和一些构建工具中,开发者习惯于使用类似 npm 的包管理和导入方式(如 import { someModule } from 'package-name')。import map 提供了一种在浏览器环境中模拟这种行为的方法,使得大型项目能够更好地组织和维护模块间的依赖关系。
  3. 多版本共存与按需加载
    • 同一项目中可能需要同时使用不同版本的库,import map 可以将不同的模块版本映射到不同的URL上,实现多个版本的同时加载与使用,避免版本冲突。
  4. 优化加载策略
    • 开发者可以通过 import map 将模块的源码映射到经过编译、压缩或缓存优化后的URL上,从而提升加载速度和用户体验。
  5. 本地开发与生产环境切换
    • 在开发阶段,可能需要从本地文件系统加载模块;而在部署上线后,则需要从CDN或其他远程服务器加载。import map 可以灵活配置这些差异化的加载路径。

以下为示例代码

<script type="importmap">
{"imports": {"module-a": "/path/to/module-a.js","module-b": "//cdn.example.com/module-b.js","package-name": "/local/path/to/package-name/index.js"}
}
</script>

然后在JavaScript模块中就可以按照映射关系来导入模块:

import { someFunction } from 'module-a';
import * as packageApi from 'package-name';

2. type=“module”的使用场景

type="module" 属性在HTML <script> 标签中使用,用于指示浏览器按照ECMAScript模块(ES6 Modules)的规范来加载和执行JavaScript代码。以下是 type="module" 使用的主要场景:

  1. 模块化开发
    • 当你的项目采用了ES6模块化机制,通过 importexport 语句导入和导出模块时,需要在引用这些模块的 <script> 标签中设置 type="module"
  2. 异步加载与依赖管理
    • ES6模块支持异步加载,浏览器会并行加载多个模块,然后根据模块间的依赖关系按序执行。
    • 这种方式可以避免传统的脚本阻塞页面渲染,提升页面加载性能。
  3. 代码组织与复用
    • 随着项目规模扩大,将代码拆分成多个模块进行管理和复用是非常必要的。type="module" 允许开发者定义独立的、可维护的模块,并确保每个模块有自己独立的作用域。
  4. 避免全局命名空间污染
    • 在模块内部定义的变量、函数等不会自动添加到全局作用域,这有助于减少不同模块之间的命名冲突问题。
  5. 现代前端框架配合
    • 现代前端框架如Vue.js、React.js等,在构建工具配置下通常默认采用模块化开发,即便在实际应用中不直接写 <script type="module">,但在构建后的产物或运行环境支持模块化的现代浏览器上,依然受益于模块化机制。
  6. 跨域限制
    • 注意,当在本地文件系统(file://)上直接打开带有 type="module" 的HTML文件时,由于浏览器安全策略,默认不允许跨域请求本地文件,因此可能无法正常加载模块。解决办法是使用像VSCode的Live Server插件或者部署到支持HTTP协议的本地服务器环境来预览和调试。

完整代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>3D Map with Three.js</title><style>body {margin: 0;}canvas {display: block;}</style>
</head><body><script type="importmap">{"imports": {"three": "../static/latest/build/three.module.js","three/addons/": "../static/latest/examples/jsm/"}}</script><script type="module">import * as THREE from 'three';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';// //场景const scene = new THREE.Scene();//透视投影相机const camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);//创建渲染器 设置抗锯齿属性为trueconst renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });renderer.setSize(window.innerWidth, window.innerHeight);//作为元素添加到html中document.body.appendChild(renderer.domElement);camera.position.z = 5;// resize 事件window.addEventListener("resize", () => {let width = window.innerWidth;let height = window.innerHeight;renderer.setSize(width, height);camera.aspect = width / height;/*** updateProjectionMatrix() 是 Three.js 中的一个方法,* 通常用于相机(Camera)对象。在Three.js中,当你更改了相机的投影参数(如透视相机的视场角、近裁剪面或远裁剪面等),* 或者更改了相机的位置、朝向等影响其投影矩阵的因素时,需要调用此方法来更新相机的内部投影矩阵。*/camera.updateProjectionMatrix();});// 定义旋转速度const clockwiseRotationSpeed = -0.005; // 顺时针旋转速度const counterclockwiseRotationSpeed = 0.005; // 逆时针旋转速度// 创建虚线圆圈的函数(这里简化为8段虚线)function createDashedCircle(radius, segments, dashSize, gapSize) {const vertices = [];const indices = [];for (let i = 0; i <= segments * 4; i++) { // 每个点分割为dashSize和gapSize两部分const angle = i / (segments * 4) * Math.PI * 2;const x = radius * Math.cos(angle);const y = radius * Math.sin(angle);if (i % 4 === 0) {vertices.push(x, y, 0); // dash起点vertices.push(x + dashSize, y, 0); // dash终点if (i > 0 && i !== segments * 12) { // 添加索引以形成线条indices.push(i - 4, i - 3, i - 2, i - 1);}} else if (i % 4 === 2) {vertices.push(x + dashSize + gapSize, y, 0); // gap终点同时也是下一个dash的起点}}const geometry = new THREE.BufferGeometry();geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));geometry.setIndex(indices);const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });return new THREE.LineSegments(geometry, material);}// 创建并添加两个虚线圆圈到场景const outerDashedCircle = createDashedCircle(3, 120, 0.01, 0.01);outerDashedCircle.position.z = -1;scene.add(outerDashedCircle);const innerDashedCircle = createDashedCircle(2.5, 120, 0.01, 0.01);innerDashedCircle.position.z = -2;scene.add(innerDashedCircle);// 创建并初始化OrbitControlsconst controls = new OrbitControls(camera, renderer.domElement);// 设置OrbitControls的一些参数,例如只允许沿着X轴旋转controls.enableDamping = true; // 使动画更平滑controls.dampingFactor = 0.05;controls.screenSpacePanning = false; // 禁止屏幕空间平移controls.minDistance = 2;controls.maxDistance = 10;//设置横向和纵向可旋转角度范围 可以通过尝试并调整controls.maxAzimuthAngle = (45*Math.PI)/100controls.minAzimuthAngle = (0*Math.PI)/100;controls.maxPolarAngle = (90*Math.PI)/100controls.minPolarAngle = (0*Math.PI)/100controls.update()// 环境光/*** AmbientLight 是 Three.js 中的一种光源类型,* 它模拟环境光的效果,即场景中的每个点都会受到相同强度和颜色的光照。* 在三维场景中添加 AmbientLight 可以提供全局的基础照明。* @type {AmbientLight}*/const ambientLight = new THREE.AmbientLight(0x404040, 0.5);scene.add(ambientLight);// const helper = new THREE.CameraHelper(camera)// scene.add(helper)// 每帧更新场景和控制function animate() {requestAnimationFrame(animate);controls.update(); // 更新OrbitControls的状态outerDashedCircle.rotation.z += clockwiseRotationSpeed;innerDashedCircle.rotation.z += counterclockwiseRotationSpeed;renderer.render(scene, camera);}animate();</script>
</body></html>

整体代码结构跟上篇文章介绍的差不多,中间多了OrbitControls的引入和配置,再就是虚线双环的实现。

最终效果

threejs实现的双圆环效果图

问题记录

  1. 控制台报错: Uncaught SyntaxError: Cannot use import statement outside a module

    这个问题主要是在script中引入了模块化的组件,但是没有配置参数type=“module”

    解决方案就是:

    <script type="module">...
    </script>
    
  2. 控制台报错:

Refused to execute script from 'http://127.0.0.1:5500/demo/static/latest/build/three.module.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
circle_r159_module.html:1  Refused to execute script from 'http://127.0.0.1:5500/demo/static/latest/examples/jsm/controls/OrbitControls.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
circle_r159_module.html:36  Uncaught ReferenceError: THREE is not definedat circle_r159_module.html:36:23

这个问题就是上面提到的模块化实现直接在html中引用导致的问题,解决方案如上文所述,推荐使用importMap解决。

<script type="importmap">{"imports": {"three": "../static/latest/build/three.module.js","three/addons/": "../static/latest/examples/jsm/"}}
</script>

同时也要注意在引用的时候script标签添加type=”module“属性,然后引用的时候使用import关键字即可。

  1. 控制台报错找不到js文件等问题

    从以下几个方向检查:

    • 检查路径是否正确,路径下是否存在目标文件
    • 检查路径是否包含中文字符
    • 检查路径是否包含.等特殊字符

总结

本文记录了在不依赖vue等模块化开发框架的基础上实现OrbitControls.js的引入过程,解决了模块化组件引入的问题,希望能帮助到需要的朋友。

针对以上问题有任何问题或者建议欢迎留言交流。

创作不易欢迎一键三连~~~

这篇关于【工作记录】Threejs学习笔记-引入OrbitControls的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/779191

相关文章

SpringBoot项目引入token设置方式

《SpringBoot项目引入token设置方式》本文详细介绍了JWT(JSONWebToken)的基本概念、结构、应用场景以及工作原理,通过动手实践,展示了如何在SpringBoot项目中实现JWT... 目录一. 先了解熟悉JWT(jsON Web Token)1. JSON Web Token是什么鬼

Servlet中配置和使用过滤器的步骤记录

《Servlet中配置和使用过滤器的步骤记录》:本文主要介绍在Servlet中配置和使用过滤器的方法,包括创建过滤器类、配置过滤器以及在Web应用中使用过滤器等步骤,文中通过代码介绍的非常详细,需... 目录创建过滤器类配置过滤器使用过滤器总结在Servlet中配置和使用过滤器主要包括创建过滤器类、配置过滤

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6

python与QT联合的详细步骤记录

《python与QT联合的详细步骤记录》:本文主要介绍python与QT联合的详细步骤,文章还展示了如何在Python中调用QT的.ui文件来实现GUI界面,并介绍了多窗口的应用,文中通过代码介绍... 目录一、文章简介二、安装pyqt5三、GUI页面设计四、python的使用python文件创建pytho

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss