原来是这样的Three.js,我悟了

最近在b站上面看到up主:gamemcu的3D作品,着实让人感到非常震撼,作品中的SU7模型,利用的是Blender进行建模,利用了webGL的技术进行开发。由此启发了我对3D极大的乐趣。因此,凭借一点点🤏的前端知识水平,打算从Three.js开始入门3D。学习成果如下:

初识Three

gamemcu大佬的作品

su7xiaomi su7 made by gamemcuicon-default.png?t=N7T8https://gamemcu.com/su7/

官网学习

利用了一天的时间,到官方网站(Three.js – JavaScript 3D Library)看了documentation、examples和editor。到b站中看了视频,学习了基础的知识。首页也有推荐一些教学视频,比如resource中提到的Three.js Journery。也是up主:gamemcu推荐去看的,在b站中可以找到免费的教学视频

Three.js Journey(新版-双语字幕)_哔哩哔哩_bilibili

源码学习

Three.js是开源的,可以把源码下载来看,官网里的所有examples在源码里面都有demo。

docs:是官网的文档,网络卡的时候,可以用本地的看。

examples:放的是所有的demo,可以学习到官方的编码方式

editor:是模型编辑器,可视化编辑模型,提高在编写代码时的效率。

初识Blender

大概了解部分Three.js知识后,还需要了解一下建模的软件-Blender,这款软件功能强大,而且是开源的。如果需要给模型添加一些动画效果,那可能就要修改模型的原点和模型分组情况。就需要到Blender对模型进行建模处理了。如何更加深入的话,可以直接在Blender中,添加好动画,然后直接Three.js导入Animation。

开发作品

经过这几天的基础学习和准备,现在可以着手开发了。

搭建场景

首先是搭建基本场景

function init() {// 初始化场景const initScene = () =>{scene = new THREE.Scene();scene.background = new THREE.Color( 0xa0a0a0 );scene.fog = new THREE.Fog( 0xa0a0a0, 4, 20 );}// 初始化相机const initCamera = () =>{camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 100 );camera.position.set( 10, 20, 10 );}// 初始化半球光const hemiLight = () =>{const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 3 );hemiLight.position.set( 0, 20, 0 );scene.add( hemiLight );}// 初始化平行光const initLight = () =>{dLight = new THREE.DirectionalLight( 0xffffff, 3 );dLight.position.set( 0, 20, 10 );dLight.castShadow = true;dLight.shadow.camera.top = 2;dLight.shadow.camera.bottom = - 2;dLight.shadow.camera.left = - 2;dLight.shadow.camera.right = 2;scene.add( dLight );}// 初始化地面const initGround = () =>{const ground = new THREE.Mesh( new THREE.PlaneGeometry( 40, 40 ), newTHREE.MeshPhongMaterial( { color: 0xbbbbbb, depthWrite: false } ) );ground.rotation.x = - Math.PI / 2;ground.receiveShadow = true;scene.add( ground );}// 初始化地面网格const initGrid = () =>{const grid = new THREE.GridHelper( 40, 20, 0x000000, 0x000000 );grid.material.opacity = 0.2;grid.material.transparent = true;scene.add( grid );}// 初始化场景辅助标线const initAxesHelper = () =>{let axesHelper = new THREE.AxesHelper( 3 );scene.add( axesHelper );}
}

导入模型

这里提前导入了两个模型,同时利用回调,添加了加载模型过程的progress。


// 模型1
function loadStaticScene(){return new Promise(resolve => {let loader = new GLTFLoader();const url = "../models/gltf/WM161_zhankai_1k.glb"loader.load(url, (gltf) => {gltf.scene.position.set(0,27,0)gltf.scene.scale.set(15, 15, 15);resolve(gltf);},(event) =>{const {loaded,total} = eventhandleLoadProgress({url,loaded,total})});})
}// 模型2
function loadStaticScene_v11(){return new Promise(resolve => {let loader = new GLTFLoader();const url = "../models/gltf/wm161_v11_zhedie_1k.glb"loader.load(url, (gltf) => {gltf.scene.position.set(0,-27,0)gltf.scene.scale.set(15, 15, 15);resolve(gltf);},(event) =>{const {loaded,total} = eventhandleLoadProgress({url,loaded,total})});})
}// 用于加载
function handleLoadProgress({url, loaded, total}){const percentage = ((loaded / total) * 100).toFixed(2);if (/.*\.(blob|glb)$/i.test(url)) {updateLoadingProgress(`加载场景模型:${percentage}%`);}}function updateLoadingProgress(loading_text){progress && (progress.textContent = loading_text);
}

加载模型

可用看到模型加载的时候,出现了加载进度。

loadScenes(); // 先加载模型async function loadScenes(){gltf = await loadStaticScene(); // 加载模型1gltf_v11 = await loadStaticScene_v11(); // 加载模型2showLoading(); // loading过程handleIntoScene(); // 进入场景
}

模型被加载到场景 

工作台

为了更换操作模型,在页面添加一个工作台。同时加上标题,丰富画面。

这是一个普通HTML编写的工作台 ,加了mac-docker动画效果

 <div class="dock_con display-none"><div class="dock"><div class="dock-item"><i class="icon icon_fly" data-name="起飞"></i></div><div class="dock-item"><i class="icon icon_color" data-name="颜色贴图"></i></div><div class="dock-item"><i class="icon icon_check" data-name="模型切换"></i></div><div class="dock-item"><i class="icon icon_setup" data-name="设置"></i></div></div></div>

切换模型

点击切换的时候,利用TWEEN进行动画式的切换效果 

function handleDockClick() {// 点击切换const handleCheck = () =>{isCheck = !isCheck;const gltf_vector = isCheck ? new THREE.Vector3(0,0,0): new THREE.Vector3(0,27,0)const gltf_v11_vector = isCheck ? new THREE.Vector3(0,0,0): new THREE.Vector3(0,-27,0)new TWEEN.Tween( gltf.scene.position).to({ x: gltf_vector.x, y: gltf_vector.y, z: gltf_vector.z }, 500).easing(TWEEN.Easing.Quadratic.Out).start();new TWEEN.Tween( gltf_v11.scene.position).to({ x: gltf_v11_vector.x, y: gltf_v11_vector.y, z: gltf_v11_vector.z }, 500).easing(TWEEN.Easing.Quadratic.Out).start();}const handleColor = () =>{}const handleFly = () =>{}// 点击事件的事件委托delegate(dock, 'click', 'i', function(e){const DOCK_MAP = [{target: ()=> e.target.classList.contains('icon_check'),handler: handleCheck},{target: ()=> e.target.classList.contains('icon_color'),handler: handleColor},{target: ()=> e.target.classList.contains('icon_fly'),handler: handleFly}]const event = DOCK_MAP.find(item => item.target());if (event) {event.handler();}})}

操作模型

这里给模型添加一个起飞的动画效果。

async function loadScenes(){gltf = await loadStaticScene();gltf_v11 = await loadStaticScene_v11();setRotateAnimation(gltf) // 缓存飞起动画showLoading();handleIntoScene();}// 在加载模型的时候,先给模型加入动画进行缓存
function setRotateAnimation(gltf){gltf.scene.traverse((e)=>{const polySurface_number = e.name.replace(/polySurface/g,'')const rotatePoly = ['58','81','90','102']if (rotatePoly.includes(polySurface_number)){const rotation = e.rotationconst tween  = new TWEEN.Tween( e.rotation).to({x:rotation.x,y:Math.PI * 2,z:rotation.z}, 50).easing(TWEEN.Easing.Linear.None).repeat(Infinity)flyPoly.push(tween)}})}function handleDockClick() {const handleCheck = () =>{}const handleColor = () =>{}// 起飞const handleFly = () =>{isFly =  !isFlyflyPoly.forEach(item=>{if (isFly){item && item.start()}else {item && item.stop()}})}// 点击事件的事件委托delegate(dock, 'click', 'i', function(e){const DOCK_MAP = [{target: ()=> e.target.classList.contains('icon_check'),handler: handleCheck},{target: ()=> e.target.classList.contains('icon_color'),handler: handleColor},{target: ()=> e.target.classList.contains('icon_fly'),handler: handleFly}]const event = DOCK_MAP.find(item => item.target());if (event) {event.handler();}})}

颜色板块

这里给页面添加一个色板,用于改变模型的颜色,当然也可以利用该方式去对模型进行其他操作

 如何知道替换颜色的模型,是哪个部位呢?可以用在上文提到过的editor编辑器,模型导入进去后,点击某个部位,就可以看到他的名称是什么了。

function handleDockClick() {const handleCheck = () =>{}// 替换颜色。const handleColor = () =>{isAppendColor = !isAppendColor;if (isAppendColor){color_map.classList.add('show-color-map');delegate(color_map, 'click', 'div', function(e){setColorMap(e.target.style.backgroundColor)})}else {color_map.classList.remove('show-color-map');}const setColorMap = (color) =>{gltf.scene.traverse((e)=>{const polySurface_number = e.name.replace(/polySurface/g,'')const windBlade = ['21','101','201'] // 给名为这几个的模型,替换颜色if (windBlade.includes(polySurface_number)){e.material.color.set(color)}})}}  const handleFly = () =>{}// 点击事件的事件委托delegate(dock, 'click', 'i', function(e){const DOCK_MAP = [{target: ()=> e.target.classList.contains('icon_check'),handler: handleCheck},{target: ()=> e.target.classList.contains('icon_color'),handler: handleColor},{target: ()=> e.target.classList.contains('icon_fly'),handler: handleFly}]const event = DOCK_MAP.find(item => item.target());if (event) {event.handler();}})}

射线处理

射线处理,就是当鼠标移动到射线投射的位置上时候,可以显示出对应的模型信息和知道当前鼠标位置是否在某个模型上面,一般用于碰撞检测。我这里只用来显示一些大概的内容。

function hoverPoint(){if (mouse.x === 0 && mouse.y ===0) return;raycaster.setFromCamera(mouse, camera); // 投射到鼠标上面let intersects = raycaster.intersectObjects(scene.children); // 作用到场景if(INTERSECTED) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );if (intersects.length && intersects[0].object.name){INTERSECTED = intersects[0].object; // 用一个变量来缓存被投射到的模型信息// 这里改变的是变量中保存的模型信息,就不会改变到原本模型的信息,也有利用做其他操作INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();INTERSECTED.material.emissive.setHex( 0x8F8BFF );showTip(INTERSECTED?.material.name)}else {if (INTERSECTED) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );INTERSECTED = null;tip.style.display = 'none';}}// 监听鼠标移动事件,这里需要对鼠标做一个处理
canvas.addEventListener('mousemove', function(event) {client = {x: event.clientX,y: event.clientY}clearMouse();mouse.x = (event.clientX / window.innerWidth) * 2 - 1; // -1 ~ 1mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // -1 ~ 1});           

动画处理

剩下的就是一些常用的CSS动画、CSS过渡动画和JS动画的细节处理了。当然这里还有一个加载完毕进场的阻尼效果。

    function updateCamera() {const vector = new THREE.Vector3()targetPosition.addScaledVector(vector, 0.25);const {x,y,z} = targetPositioncamera.position.x += (targetPosition.x - camera.position.x) * dampingFactor;camera.position.y += (targetPosition.y - camera.position.y) * dampingFactor;camera.position.z += (targetPosition.z - camera.position.z) * dampingFactor;const cameraX = camera.position.x.toFixed(1);const cameraY = camera.position.y.toFixed(1);const cameraZ = camera.position.z.toFixed(1);if (Number(cameraX) <= x && Number(cameraY) <= y && Number(cameraZ) <= z){isLoading = falsetitle.classList.remove("display-none");dock_con.classList.remove("display-none");}}function animate(){requestAnimationFrame( animate );updateJump();controls.update();TWEEN.update();isLoading && updateCamera();!isLoading && hoverPoint();renderer.render( scene, camera );}

操作杆处理

给摄像机添加一个前后左右的类似操作杆的效果,然后移动的时候,视口中间出现一些跳动的文字对应操作的按键。

 let mouse = new THREE.Vector2();let velocity = new THREE.Vector3();let up_vector = new THREE.Vector3(0, 1, 0);let temp_vector = new THREE.Vector3();let targetPosition = new THREE.Vector3(1.8, 1.9, 3.4);// 跳动的文字
const throttledScrollHandler = throttle(function() {const id = document.querySelector('.text-up')new TextUp( {id:id,text: keyCode.toUpperCase()} ) // 在节流的控制下生成文字
}, 500);// 操作杆
document.addEventListener('keydown', function(event) {keyCode = event.key === ' '? 'space' : event.keycamera.position.addScaledVector(velocity, 0.5);const angle = controls.getAzimuthalAngle();const speed = 0.25mouse = new THREE.Vector2(0,0)switch (event.key) {case 'w':temp_vector.set(0, 0, -1).applyAxisAngle(up_vector, angle);camera.position.addScaledVector(temp_vector, speed);throttledScrollHandler()break;case 's':temp_vector.set(0, 0, 1).applyAxisAngle(up_vector, angle);camera.position.addScaledVector(temp_vector, speed);throttledScrollHandler()break;case 'a':temp_vector.set(-1, 0, 0).applyAxisAngle(up_vector, angle);camera.position.addScaledVector(temp_vector, speed);throttledScrollHandler()break;case 'd':temp_vector.set(1, 0, 0).applyAxisAngle(up_vector, angle);camera.position.addScaledVector(temp_vector, speed);throttledScrollHandler()break;case ' ':jump();throttledScrollHandler()break;}});function updateJump(){if (isJumping) {camera.position.y += jumpVelocity;jumpVelocity -= 0.05;if (camera.position.y <= 1.5) {camera.position.y = 1.5;isJumping = false;}}}function jump() {if (!isJumping) {jumpVelocity = 0.3 * maxJumpHeightconsole.log(jumpVelocity);isJumping = true;}}

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

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

相关文章

RK3588 Android13 TvSetting 中增加 Usb 模式 Host/OTG 切换

前言 电视产品,客户要求在设置中设备偏好设置子菜单下增加一个USB模式切换菜单,一开始准备直接开整。但发现在开发者选项里就已经包含了一个USB模式 菜单了,只是没有 OTG HOST 这两选项,那就把这个菜单挪出来再增加一下就完事了,开整。 客户提供对比机图 效果图 framew…

【计算机毕业设计】学习平台功能介绍——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

学习部分排序,插入排序,冒泡排序以及希尔排序

1.插入排序 <1>.首先我们举个例子 我们要把6进行前面的插入&#xff0c;那我们要进行比较&#xff0c;首先确定一个end的指针&#xff0c;然后他指向的数字就是我们需要比较的&#xff0c;如果end指向的数比我们end1 的大的话&#xff0c;那我们就往前挪一个&#xff0c…

有效Dk值提取方法的仿真分析

目录 1. TDR技术提取Dk值的方法 2. 传输线双端口Delta-L技术提取Dk值的方法 3. 传输线单端口Delta-L技术提取Dk值的方法 4. 总结 参考文献 1. TDR技术提取Dk值的方法 测试有效Dk值的一些传统而有效的方法[1][2]&#xff0c;是采用TDR阻抗测试仪测试专门设计的传输线的传播延…

我的AI数字人分身上线了!

说起AI数字人&#xff0c;大家一定不会陌生。随着全民AI时代的到来&#xff0c;许多机关单位、企业和个人&#xff0c;都纷纷制作了自己的数字人形象。 前些天&#xff0c;小灰的老东家刘强东也开始用数字人直播带货&#xff0c;瞬间引爆了全网。 这一切背后的本质是什么呢&…

抽真空规范操作

抽真空规范操作 抽真空操作中&#xff0c;一个被忽视的现象是&#xff1a;许多维修人员热衷于解决空调故障&#xff0c;却对施工过程中的规范操作敷衍了事。殊不知&#xff0c;正是这些看似微不足道的细节疏忽&#xff0c;往往诱发空调各类疑难故障&#xff0c;令售后维修陷入…

【创建型模式】原型模式

一、原型模式概述 原型&#xff08;Prototype&#xff09;模式的定义&#xff1a;用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里&#xff0c;原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效&#xf…

吴恩达深度学习笔记:深度学习的 实践层面 (Practical aspects of Deep Learning)1.4-1.5

目录 第一门课&#xff1a;第二门课 改善深层神经网络&#xff1a;超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第一周&#xff1a;深度学习的 实践层面 (Practical aspects of Deep Learning)…

Vue接收接口返回的mp3格式数据并支持在页面播放音频

一、背景简介 在实际工作中需要开发一个转音频工具&#xff0c;并且能够在平台页面点击播放按钮播放音频 二、相关知识介绍 2.1 JS内置对象Blob Blob对象通常用于处理大量的二进制数据&#xff0c;可以读取/写入/操作文件、音视频等二进制数据流。Blob表示了一段不可变的二…

【PCL】教程alignment_prerejective.cpp 刚性物体的鲁棒位姿估计

The viewer window provides interactive commands; for help, press h or H from within the window. > Loading V:\learn\PCL\pcl\examples\test\chef.pcd [PCLVisualizer::setUseVbos] Has no effect when OpenGL version is 鈮?2 [done, 327.147 ms : 5092 points] Ava…

AI大模型量化格式介绍(GPTQ,GGML,GGUF,FP16/INT8/INT4)

在 HuggingFace 上下载模型时&#xff0c;经常会看到模型的名称会带有fp16、GPTQ&#xff0c;GGML等字样&#xff0c;对不熟悉模型量化的同学来说&#xff0c;这些字样可能会让人摸不着头脑&#xff0c;我开始也是一头雾水&#xff0c;后来通过查阅资料&#xff0c;总算有了一些…

DFS之剪枝(上交考研题目--正方形数组的数目)

题目 给定一个非负整数数组 A A A&#xff0c;如果该数组每对相邻元素之和是一个完全平方数&#xff0c;则称这一数组为正方形数组。 返回 A A A 的正方形排列的数目。 两个排列 A 1 A1 A1 和 A 2 A2 A2 不同的充要条件是存在某个索引 i i i&#xff0c;使得 A 1 [ i …

测试一下 Meta Llama3-70b-Instruct-q8

测试一下 Meta Llama3-70b-Instruct-q8 0. 引言1. 测试 Meta Llama3-70b-Instruct-q8 0. 引言 今天&#xff0c;Meta 正式介绍Meta Llama 3&#xff0c;Meta 开源大型语言模型的下一代产品。 这次发布包括具有80亿&#xff08;8B&#xff09;和700亿&#xff08;70B&#xff0…

指纹浏览器如何高效帮助TikTok账号矩阵搭建?

TikTok的账号矩阵&#xff0c;可能听起来还比较陌生&#xff0c;但随着TikTok业务已经成为吃手可热的跨境业务&#xff0c;TikTok多账号矩阵已成为流行策略。但它有什么优点呢&#xff1f;操作多个帐户会导致被禁止吗&#xff1f;如何有效地建立账户矩阵开展业务&#xff1f;这…

CANfestival 主机进入预操作态(preOperational)自动发送复位节点指令。

核心是iam_a_slave ,这个是字典生产的时候自动生成的。

【Flutter】多语言方案一:flutter_localizations 与 GetX 配合版

系列文章目录 多语言方案&#xff1a;flutter_localizations 与 GetX 配合版&#xff0c;好处&#xff1a;命令行生成多语言字符串的引用常量类&#xff0c;缺点&#xff1a;切换语言以后&#xff0c;主界面需要手动触发setState&#xff0c;重绘将最新的Locale数据设置给GetM…

使用LangChain和Llama-Index实现多重检索RAG

大家好&#xff0c;在信息检索的世界里&#xff0c;查询扩展技术正引领着一场效率革命。本文将介绍这一技术的核心多查询检索&#xff0c;以及其是如何在LangChain和Llama-Index中得到应用的。 1.查询扩展 查询扩展是一种信息检索技术&#xff0c;通过在原始查询的基础上增加…

基于Springboot的简历系统

基于SpringbootVue的简历系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 简历模板 招聘会 求职论坛 系统公告 后台登录 后台首页 用户管理 简历模板 模板…

uniapp中scroll-view初始化的时候 无法横向滚动到某个为止

项目需求 实现日历&#xff08;13天&#xff09;默认高亮第六天 并定位到第六 左边右边各六天&#xff08;可以滑动&#xff09; 直接上代码 <template><scroll-view class"scroll-X":show-scrollbar"true" :scroll-x"scrollable":…

OpenHarmony网络组件-Mars

项目简介 Mars 是一个跨平台的网络组件&#xff0c;包括主要用于网络请求中的长连接&#xff0c;短连接&#xff0c;是基于 socket 层的解决方案&#xff0c;在网络调优方面有更好的可控性&#xff0c;暂不支持HTTP协议。 Mars 极大的方便了开发者的开发效率。 效果演示 编译…