原来是这样的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;令售后维修陷入…

Kubernetes Kafka 系列|MirrorMaker 2 同步数据

一、MirrorMaker 2介绍 MirrorMaker 2&#xff08;简称MM2&#xff09;是Apache Kafka的一个工具&#xff0c;主要用于跨Kafka集群的数据复制和同步。相比早期的MirrorMaker 1&#xff08;简称MM1&#xff09;&#xff0c;MirrorMaker 2在设计和功能上有了显著的提升&#xff…

【创建型模式】原型模式

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

CV 面试指南—深度学习知识点总结(1)

本期专栏文章: CV 面试指南—深度学习知识点总结(1)CV 面试指南—深度学习知识点总结(2)CV 面试指南—深度学习知识点总结(3)CV 面试指南—深度学习知识点总结(4)CV 面试指南—深度学习知识点总结(5)

吴恩达深度学习笔记:深度学习的 实践层面 (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)…

QT c++ 将浮点数数组转换成 QByteArray

//上一篇文章&#xff0c;描写了怎么将数据已字节数组的形式写到Sqlite 数据库&#xff0c;那么。 //本文描述2种方法将浮点数数组转换为字节数组QByteArray //在QT6.2.4 MSVC2019 调试通过 #include <QCoreApplication> #include <QByteArray> #include <Q…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK实现相机给外界IO信号输出(C#)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK实现相机给外界IO信号输出&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机BGAPISDK和相机IO信号输出的技术背景Baumer工业相机通过BGAPISDK实现相机IO信号的输出功能1.引用合适的类文件2.通过BGAPISDK在初始化时设置相应…

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…

【torch函数】torch.multinomial函数

torch.multinomial 是PyTorch中的一个函数&#xff0c;用于从多项分布中抽取样本。多项分布是一种描述多个可能结果的概率分布&#xff0c;例如抛硬币、掷骰子等。 torch.multinomial的用法如下&#xff1a; torch.multinomial(input, num_samples, replacementFalse, *, gene…

C++入门之类和对象(下)

C入门之类和对象(下) 文章目录 C入门之类和对象(下)一、初始化列表1.1 概念1.2 注意事项 11.3 注意事项 21.4 注意事项 3 二、explicit关键字2.1 为什么要有explicit关键字 三、static成员3.1 static修饰类的成员和成员函数 一、初始化列表 1.1 概念 先来看看构造函数 #incl…

MySQL 中 InnoDB 存储引擎使用的 B+树底层数据结构

简要介绍 InnoDB 和它为什么选择使用 B树 InnoDB 是 MySQL 中默认的存储引擎&#xff0c;广泛用于生产环境中&#xff0c;特别是在要求高可靠性和事务性的应用场景。这个存储引擎支持事务处理、行级锁定、外键约束等高级数据库功能&#xff0c;这使得它非常适合处理大量数据并…

力扣爆刷第122天之CodeTop100五连刷96-100

力扣爆刷第122天之CodeTop100五连刷96-100 文章目录 力扣爆刷第122天之CodeTop100五连刷96-100一、912. 排序数组二、24. 两两交换链表中的节点三、297. 二叉树的序列化与反序列化四、560. 和为 K 的子数组五、209. 长度最小的子数组 一、912. 排序数组 题目链接&#xff1a;h…

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

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

嵌入式学习57-ARM6(linux驱动启动程序)

知识零碎&#xff1a; arm2440 精简指令集架构 …