【jsthreeJS】入门three,并实现3D汽车展示厅,附带全码

首先放个最终效果图:

 

三维(3D)概念:

三维(3D)是一个描述物体在三个空间坐标轴上的位置和形态的概念。相比于二维(2D)只有长度和宽度的平面,三维增加了高度或深度这一维度

在三维空间中,我们使用三个独立的坐标轴来描述物体的位置。通常使用笛卡尔坐标系,即X、Y和Z轴。其中,X轴表示横向,Y轴表示纵向,Z轴表示纵深或垂直方向。通过在这些轴上的不同值组合,可以确定一个点或对象在三维空间中的位置

大家可以three编辑器中感受一下三维:three.js editor

ps:默认英文,可以切换中文语言

three前提概念

以舞台展示为例:

  • 场景 Sence 相当于一个舞台,在这里是布置场景物品和表演者表演的地方
  • 相机 Carma 相当于观众的眼睛去观看
  • 几何体 Geometry 相当于舞台的表演者
  • 灯光 light 相当于舞台灯光照射控制 
  • Controls 相当于这出舞台剧的总导演

创建场景与相机

<html><head><meta charset="utf-8"><title>My first three.js app</title>
</head><body><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script type="importmap">{"imports": {"three": "./three.module.js"}}</script><script type="module">import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'let scene,renderer,camera//创建场景const setScene = () => {scene = new Scene()renderer = new WebGLRenderer()//调用 setSize() 方法设置渲染器的大小为当前窗口的宽度和高度renderer.setSize(window.innerWidth, window.innerHeight)//将渲染器的 DOM 元素添加到页面的 <body> 元素中document.body.appendChild(renderer.domElement)}//相机的默认坐标const defaultMap = {x: 0,y: 10,z: 20,}//创建相机  const setCamera = () => {const { x, y, z } = defaultMap//创建一个 PerspectiveCamera 对象,并传入参数来设置透视相机的属性:视野角度为 45 度,宽高比为窗口的宽高比,近裁剪面为 1,远裁剪面为 1000camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)//用 position.set() 方法将相机的位置设置为之前从 defaultMap 中提取的坐标camera.position.set(x, y, z)}(function () {setScene()setCamera()})()</script>
</body></html>

PerspectiveCamera的详细说明:

new THREE.PerspectiveCamera构造函数用来创建透视投影相机,该构造函数总共有四个参数,分别是fov,aspect,near,far 。

fov表示摄像机视锥体垂直视野角度,最小值为0,最大值为180,默认值为50,实际项目中一般都定义45,因为45最接近人正常睁眼角度;aspect表示摄像机视锥体长宽比,默认长宽比为1,即表示看到的是正方形,实际项目中使用的是屏幕的宽高比;near表示摄像机视锥体近端面,这个值默认为0.1,实际项目中都会设置为1;far表示摄像机视锥体远端面,默认为2000,这个值可以是无限的,说的简单点就是我们视觉所能看到的最远距离。

引入模型

国外一个3d模型下载网站,里面有很多免费的模型下载  点击红框处下载

Log in to your Sketchfab account - Sketchfab

<html><head><meta charset="utf-8"><title>My first three.js app</title>
</head><body><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script type="importmap">{"imports": {"three": "./three.module.js"}}</script><script type="module">import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'let scene, renderer, camera, directionalLight, dhelperlet isLoading = truelet loadingWidth = 0//创建场景const setScene = () => {scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(window.innerWidth, window.innerHeight)document.body.appendChild(renderer.domElement)}//相机的默认坐标const defaultMap = {x: 0,y: 10,z: 20,}//创建相机  const setCamera = () => {const { x, y, z } = defaultMapcamera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)camera.position.set(x, y, z)}//通过Promise处理一下loadfile函数const loader = new GLTFLoader() //引入模型的loader实例const loadFile = (url) => {return new Promise(((resolve, reject) => {loader.load(url,(gltf) => {resolve(gltf)}, ({ loaded, total }) => {let load = Math.abs(loaded / total * 100)loadingWidth = loadif (load >= 100) {setTimeout(() => {isLoading = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}(async function () {const gltf = await loadFile('./assets/scene.gltf')setScene()setCamera()scene.add(gltf.scene)})()</script>
</body></html>

加载模型代码讲解:

loader.load 用来加载和解析 glTF 文件,接受四个参数:

  • 第一个参数 url 是要加载的 glTF 模型文件的路径。
  • 第二个参数是一个回调函数,当模型加载成功时会被调用
  • 第三个参数是一个回调函数,用于跟踪加载进度。回调函数的 { loaded, total } 参数表示已加载和总共需要加载的文件数量,通过计算百分比可以得到当前加载进度。
  • 第四个参数是一个回调函数,当加载出错时会被调用。

创建灯光

<html><head><meta charset="utf-8"><title>My first three.js app</title>
</head><body><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script type="importmap">{"imports": {"three": "./three.module.js"}}</script><script type="module">import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'let scene, renderer, camera, directionalLight, dhelperlet isLoading = truelet loadingWidth = 0//创建场景const setScene = () => {scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(window.innerWidth, window.innerHeight)document.body.appendChild(renderer.domElement)}//相机的默认坐标const defaultMap = {x: 0,y: 10,z: 20,}//创建相机  const setCamera = () => {const { x, y, z } = defaultMapcamera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)camera.position.set(x, y, z)}// 设置灯光const setLight = () => {// 创建一个颜色为白色(0xffffff),强度为 0.5 的平行光对象directionalLight = new DirectionalLight(0xffffff, 0.5)//设置平行光的位置,这里将其放置在三维坐标 (-4, 8, 4) 的位置directionalLight.position.set(-4, 8, 4)//创建一个平行光辅助对象,用于可视化平行光的方向和强度dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)//创建一个颜色为白色(0xffffff),半球颜色为白色(0xffffff),强度为 0.4 的半球光对象hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)hemisphereLight.position.set(0, 8, 0)//创建一个半球光辅助对象,用于可视化半球光的方向和强度hHelper = new HemisphereLightHelper(hemisphereLight, 5)//添加到场景scene.add(directionalLight)//添加到场景scene.add(hemisphereLight)}//使场景、照相机、模型不停调用const loop = () => {//requestAnimationFrame(loop) 是浏览器提供的方法,用于在下一次重绘页面之前调用回调函数 loop。这样可以创建一个循环,使场景、相机和模型不断被渲染更新requestAnimationFrame(loop)//使用渲染器 renderer 渲染场景 scene 中的模型,使用相机 camera 进行投影renderer.render(scene, camera)}//通过Promise处理一下loadfile函数const loader = new GLTFLoader() //引入模型的loader实例const loadFile = (url) => {return new Promise(((resolve, reject) => {// loader.load 用来加载和解析 glTF 文件loader.load(url,(gltf) => {resolve(gltf)}, ({ loaded, total }) => {let load = Math.abs(loaded / total * 100)loadingWidth = loadif (load >= 100) {setTimeout(() => {isLoading = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}(async function () {const gltf = await loadFile('./assets/scene.gltf')setScene()setCamera()setLight()scene.add(gltf.scene)loop()})()</script>
</body></html>

DirectionalLight 和 HemisphereLight 是 Three.js 中的两种灯光类型,分别表示平行光和半球光。它们用于模拟现实世界中的光照效果

此刻模型已经可以看见了,如何你只能看见黑黑的一片,无法看到模型,一般两个原因:

  • 模型是否加载成功
try {gltf = await loadFile('./assets/scene.gltf');console.log('Model loading completed:', gltf);
} catch (error) {console.error('Error loading model:', error);
}
  • 相机位置偏差,调整下相机(defaultMap)的位置

控制模型

这一步完成之后,模型就可以通过鼠标移动,旋转了

<html><head><meta charset="utf-8"><title>My first three.js app</title>
</head><body><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script type="importmap">{"imports": {"three": "./three.module.js"}}</script><script type="module">import { Scene, WebGLRenderer, PerspectiveCamera, DirectionalLight, HemisphereLight, DirectionalLightHelper, HemisphereLightHelper } from 'three'import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'import { OrbitControls } from './jsm/controls/OrbitControls.js'let scene, renderer, camera, directionalLight, hemisphereLight, dhelper, hHelper, controlslet isLoading = truelet loadingWidth = 0//创建场景const setScene = () => {scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(window.innerWidth, window.innerHeight)document.body.appendChild(renderer.domElement)}//相机的默认坐标const defaultMap = {x: 0,y: 10,z: 20,}//创建相机  const setCamera = () => {const { x, y, z } = defaultMapcamera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)camera.position.set(x, y, z)}// 设置灯光const setLight = () => {directionalLight = new DirectionalLight(0xffffff, 0.5)directionalLight.position.set(-4, 8, 4)dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)hemisphereLight.position.set(0, 8, 0)hHelper = new HemisphereLightHelper(hemisphereLight, 5)scene.add(directionalLight)scene.add(hemisphereLight)}//使场景、照相机、模型不停调用const loop = () => {requestAnimationFrame(loop)renderer.render(scene, camera)controls.update()}// 设置模型控制const setControls = () => {// 创建一个新的 OrbitControls 对象,并将它绑定到相机 camera 和渲染器的 DOM 元素 renderer.domElement 上controls = new OrbitControls(camera, renderer.domElement)// 设置相机的最大仰角(上下旋转角度),这里将其限制为 0.9 * π / 2controls.maxPolarAngle = 0.9 * Math.PI / 2//启用相机的缩放功能,允许用户通过鼠标滚轮或触摸手势进行缩放操作controls.enableZoom = true//监听控制器的变化事件,当用户操作控制器导致相机位置发生改变时,触发渲染函数 rendercontrols.addEventListener('change', render)}//在相机位置发生变化时,将新的相机位置保存到 defaultMap 对象中const render = () => {defaultMap.x = Number.parseInt(camera.position.x)defaultMap.y = Number.parseInt(camera.position.y)defaultMap.z = Number.parseInt(camera.position.z)}//通过Promise处理一下loadfile函数const loader = new GLTFLoader() //引入模型的loader实例const loadFile = (url) => {return new Promise(((resolve, reject) => {// loader.load 用来加载和解析 glTF 文件loader.load(url,(gltf) => {resolve(gltf)}, ({ loaded, total }) => {let load = Math.abs(loaded / total * 100)loadingWidth = loadif (load >= 100) {setTimeout(() => {isLoading = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}(async function () {setScene()setCamera()setLight()setControls()const gltf = await loadFile('./assets/scene.gltf')scene.add(gltf.scene)loop()})()</script>
</body></html>

ps:这段代码没问题,可正常运行,前两三个可能会有些引入缺失或者声明变量的缺失,大家参考这个补齐,我就不去查漏补缺了

改变车身颜色

scene 有一个traverse函数,它回调了所有模型的子模型信息,只要我们找到对应name属性,就可以更改颜色,和增加贴图等等

        //设置车身颜色const setCarColor = (index) => {//Color 是 Three.js 中的一个类,用于表示颜色。它的作用是创建和管理三维场景中物体的颜色const currentColor = new Color(colorAry[index])// 使用 Three.js 中的 traverse 方法遍历场景中的每个子对象scene.traverse(child => {if (child.isMesh) {console.log(child)if (child.name) {//将当前子对象的材质颜色设置为 currentColor,实现改变颜色的效果child.material.color.set(currentColor)}}})}

整个的完整代码:

<html><head><meta charset="utf-8"><title>My first three.js app</title><style>body {margin: 0;}.maskLoading {background: #000;position: fixed;display: flex;justify-content: center;align-items: center;top: 0;left: 0;bottom: 0;right: 0;z-index: 1111111;color: #fff;}.maskLoading .loading {width: 400px;height: 20px;border: 1px solid #fff;background: #000;overflow: hidden;border-radius: 10px;}.maskLoading .loading div {background: #fff;height: 20px;width: 0;transition-duration: 500ms;transition-timing-function: ease-in;}canvas {width: 100%;height: 100%;margin: auto;}.mask {color: #fff;position: absolute;bottom: 0;left: 0;width: 100%;}.flex {display: flex;flex-wrap: wrap;padding: 20px;}.flex div {width: 10px;height: 10px;margin: 5px;cursor: pointer;}</style>
</head><body><div class="boxs"><div class="maskLoading"><div class="loading"><div class="oneDiv"></div></div><div style="padding-left: 10px;" class="twoDiv"></div></div><div class="mask"><p class="realTimeDate"></p><button class="rotatingCar">转动车</button><button class="stop">停止</button><div class="flex" id="colorContainer"></div></div></div><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script type="importmap">{"imports": {"three": "./three.module.js"}}</script><script type="module">import {Color,DirectionalLight,DirectionalLightHelper,HemisphereLight,HemisphereLightHelper,PerspectiveCamera,Scene,WebGLRenderer} from 'three'import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'import { OrbitControls } from './jsm/controls/OrbitControls.js'//车身颜色数组const colorAry = ["rgb(216, 27, 67)", "rgb(142, 36, 170)", "rgb(81, 45, 168)", "rgb(48, 63, 159)", "rgb(30, 136, 229)", "rgb(0, 137, 123)","rgb(67, 160, 71)", "rgb(251, 192, 45)", "rgb(245, 124, 0)", "rgb(230, 74, 25)", "rgb(233, 30, 78)", "rgb(156, 39, 176)","rgb(0, 0, 0)"]let scene, camera, renderer, controls, floor, dhelper, hHelper, directionalLight, hemisphereLightlet gltflet isLoading = truelet loadingWidth = 0//相机的默认坐标const defaultMap = {x: 0,y: 10,z: 20,}//遮罩层const maskLayer = () => {if (isLoading) {$('.maskLoading').hide();} else {$('.maskLoading').show()}}maskLayer()// 进度const schedule = () => {let timer = setInterval(function () {$('oneDiv').css('width', `${loadingWidth}%`);$('twoDiv').text(`${loadingWidth}%`);if (loadingWidth == 100) {clearInterval(timer);}}, 10);}schedule()//实时更新x,y,zconst realTime = () => {let timer = setInterval(function () {$('realTimeDate').text(`x:${defaultMap.x} y:${defaultMap.y} z:${defaultMap.z}`);}, 10);}// 生成颜色旋转块$.each(colorAry, function (index, item) {$('<div>').appendTo('#colorContainer') // 在 #colorContainer 中创建一个 <div> 元素.css('background-color', item) // 设置背景颜色.click(function () {setCarColor(index); // 调用 setCarColor 函数并传递索引参数});});//创建场景const setScene = () => {scene = new Scene()renderer = new WebGLRenderer()renderer.setSize(window.innerWidth, window.innerHeight)document.body.appendChild(renderer.domElement)}//创建相机  const setCamera = () => {const { x, y, z } = defaultMapcamera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)camera.position.set(x, y, z)}//引入模型的loader实例const loader = new GLTFLoader()//通过Promise处理一下loadfile函数const loadFile = (url) => {return new Promise(((resolve, reject) => {loader.load(url,(gltf) => {resolve(gltf)}, ({ loaded, total }) => {let load = Math.abs(loaded / total * 100)loadingWidth = loadif (load >= 100) {setTimeout(() => {isLoading = false}, 1000)}console.log((loaded / total * 100) + '% loaded')},(err) => {reject(err)})}))}// 设置灯光const setLight = () => {directionalLight = new DirectionalLight(0xffffff, 0.8)directionalLight.position.set(-4, 8, 4)dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)hemisphereLight.position.set(0, 8, 0)hHelper = new HemisphereLightHelper(hemisphereLight, 5)scene.add(directionalLight)scene.add(hemisphereLight)}// 设置模型控制const setControls = () => {controls = new OrbitControls(camera, renderer.domElement)controls.maxPolarAngle = 0.9 * Math.PI / 2controls.enableZoom = truecontrols.addEventListener('change', render)}//返回坐标信息const render = () => {defaultMap.x = Number.parseInt(camera.position.x)defaultMap.y = Number.parseInt(camera.position.y)defaultMap.z = Number.parseInt(camera.position.z)}(async function () {setScene()setCamera()setLight()setControls()try {gltf = await loadFile('./assets/scene.gltf');console.log('Model loading completed:', gltf);} catch (error) {console.error('Error loading model:', error);}scene.add(gltf.scene)loop()})()//使场景、照相机、模型不停调用const loop = () => {requestAnimationFrame(loop)renderer.render(scene, camera)controls.update()}//是否自动转动$('.rotatingCar').click(function () {console.log("旋转")controls.autoRotate = true})//停止转动$('.stop').click(function () {console.log("停止")controls.autoRotate = false})//设置车身颜色const setCarColor = (index) => {const currentColor = new Color(colorAry[index])scene.traverse(child => {if (child.isMesh) {console.log(child)if (child.name) {child.material.color.set(currentColor)}}})}</script>
</body></html>

完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/81255.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ElasticSearch】一键安装ElasticSearch与Kibana以及解决遇到的问题

目录 一、安装ES 二、安装Kibana 三、遇到的问题 一、安装ES 按顺序复制即可 docker network create es-net # 创建网络 docker pull images:7.12.1 # 拉取镜像 mkdir -p /root/es/data # 创建数据卷 mkdir -p /root/es/plugins # 创建数据卷 chmod 777 /root/es/** # 设置权…

QT中资源文件resourcefile的使用,使用API完成页面布局

QT中资源文件resourcefile的使用 之前添加图标的方法使用资源文件的方法创建资源文件资源文件添加前缀资源文件添加资源使用资源文件中的资源 使用API完成布局使用QHBoxLayout完成水平布局使用QVBoxLayout完成垂直布局使用QGridLayout完成网格布局 在Qt中引入资源文件好处在于他…

Firefox(火狐),使用技巧汇总,问题处理

本文目的 说明火狐如何安装在C盘之外的盘&#xff0c;即定制安装路径。如何将同步功能切换到本地服务上。默认是国际服务器。安装在C盘之后如何解决&#xff0c;之前安装的扩展无法自动同步的问题。扩展或插件失效问题解决方案。顺带分享一下&#xff0c;火狐的一些比较好用的…

使用Python搭建服务器公网展示本地电脑文件

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 Python作为热度比较高的编程语言&#xff0c;其语法简单且语句清晰&#xff0c;而且python有…

操作系统练习:在Linux上创建进程,及查看进程状态

说明 进程在执行过程中可以创建多个新的进程。创建进程称为“父进程”&#xff0c;新的进程称为“子进程”。每个新的进程可以再创建其他进程&#xff0c;从而形成进程树。 每个进程都有一个唯一的进程标识符&#xff08;process identifier&#xff0c;pid&#xff09;。在L…

vue学习之hello world

依赖引入 <script src"https://unpkg.com/vue2.6.10/dist/vue.js"></script>Hello world 实现 <html><head><style></style></head><body><script src"https://unpkg.com/vue2.6.10/dist/vue.js">…

java八股文面试[数据结构]——Map有哪些子类

知识来源&#xff1a; 【23版面试突击】 用过哪些Map类&#xff0c;都有什么区别&#xff0c;HashMap是线程安全的吗&#xff1f;_哔哩哔哩_bilibili https://www.cnblogs.com/bubbleboom/p/12694013.html

c++学习之内存管理

目录 1.c/c内存分布 2.new与delete/malloc与free c内存管理方式&#xff1a; new/delete操作内置类型&#xff1a; new/delete操作自定义类型 operator new与operator delete函数 new和delete的实现原理 内置类型 自定义类型 malloc/free和new/delete的区别 1.c/c内存分…

解决Pandas KeyError: “None of [Index([...])] are in the [columns]“问题

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【MyBatis】:PageHelper分页插件与特殊字符处理

目录 一、PageHelper介绍 二、PageHelper使用 1. 导入pom依赖 2. Mybatis.cfg.xml 配置拦截器 3. 配置 Mapper.xml 4. 编写测试 三、特殊字符处理 1. 使用转义字符 2. 使用CDATA 区段 一、PageHelper介绍 PageHelper 是 Mybatis 的一个插件&#xff0c;这里就不扯了&a…

Blazor 依赖注入妙用:巧设回调

文章目录 前言依赖注入特性需求解决方案示意图 前言 依赖注入我之前写过一篇文章&#xff0c;没看过的可以看看这个。 C# Blazor 学习笔记(10):依赖注入 依赖注入特性 只能Razor组件中注入所有Razor组件在作用域注入的都是同一个依赖。作用域可以看看我之前的文章。 需求 …

【Vue框架】 router和route是什么关系

前言 之前没太注意&#xff0c;写着写着突然发现它们貌似不太一样&#xff0c;记录以下&#xff0c;回顾的看总结就好。 1、总结✨ route&#xff1a;当前激活路由的对象&#xff0c;用于访问和操作当前路由的信息 router&#xff1a;管理多个route的对象&#xff0c;整个应…