关于three.js:Threejs-进阶之旅滚动控制模型动画和相机动画-🦢

0次阅读

共计 6978 个字符,预计需要花费 18 分钟才能阅读完成。

申明:本文波及图文和模型素材仅用于集体学习、钻研和观赏,请勿二次批改、非法流传、转载、出版、商用、及进行其余获利行为。

摘要

专栏上篇文章《Three.js 进阶之旅:页面平滑滚动 - 王国之泪》解说并实现了如何应用 R3F 进行页面图片平滑滚动,本文内容在上节的根底上,学习如何应用滚动管制 ScrollControls 来管制模型的的动画播放和相机动画,通过滚动鼠标滚轮或者高低挪动触摸板,来管制模型的动画播放进度或者相机的方位视角 ,从而呈现出惊艳的视觉效果。这种乏味的成果大家在平时浏览一些网页的时候应该常常见到,如一些 3D 产品 介绍页向下滑动鼠标滚轮时产品同时旋转并依据产品的不同视角加载不同文案、或者 3D 数字地球 依据滚轮的挪动间隔转到某个国家或地区、还有一些 个人简历 页面或时间轴页面也常常用到这种成果。通过本文的浏览和案例页面的实现,你将学习到的常识包含:R3F 生态中的 ScrollControlsHtmluseScrolluseGLTFuseAnimations 等组件和办法的根本用法、在 R3F 中加载模型并播放模型骨骼动画、通过滚动管制模型动画播放过程和相机参数、页面元素的一些 CSS 动画及页面整体丝滑滚动动画实现等。

成果

本文案例的实现成果如下图所示,页面主体元素由一个三维模型 🐸🦢、及底部的 5HTML 页面形成,页面初始加载时模型是静止的,当咱们应用鼠标或触控板或间接拖动页面滚动条时 🖱,相机镜头📷 从侧面近处平滑过渡到模型侧面远处,模型开始自动播放自带骨骼动画,模型动作依据页面滚动间隔和滚动时速率的大小而不同。当咱们点击页面顶部菜单时,页面会平滑滚动到对应地位,模型也会播放动画。

当页面逆向 👆 滚动时,相机和模型动画也会逆向变动和播放。

文章应用 GIF 可能会造成丢帧或卡顿,能够亲自关上预览链接试试,大屏拜访成果更佳。

  • 👁‍🗨 在线预览地址:https://dragonir.github.io/dancingDuck/

本专栏系列代码托管在 Github 仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新

🔗 代码仓库地址:git@github.com:dragonir/threejs-odessey.git

原理

如果用原生 JavaScript 实现滚动动画成果,就须要监听滚动事件和计算滚动间隔。本文还是和上篇文章《hree.js 进阶之旅:页面平滑滚动 - 王国之泪》一样,间接应用封装好的组件 ScrollControlsScroll 来实现,它们的具体用法和原理可返回上篇文章查看。本文中最终实现的页面须要加载模型并播放它自带的骨骼动画,因而用到了以下几个 @react-three/drei 中的组件和 hooks

Html

容许咱们将 HTML 内容绑定到场景中的任意对象,它将主动投影到对象上。

<Html
as='div' // 包裹元素,默认为 'div'
wrapperClass // 包裹元素的类名,默认为 undefined
prepend // 画布前面的元素,默认为 false
center // 增加 -50%/-50% css 变换,默认为 false
fullscreen // 与左上角对齐并填满屏幕,默认为 false
distanceFactor={10} // 如果设置该值,子元素将按与相机的间隔进行缩放,默认为 undefined
zIndexRange={[100, 0]} // Z 阶范畴,默认为 [16777271, 0]
portal={domnodeRef} // 对指标容器的援用,默认为 undefined
transform // 若设置 true,将进行 3d 矩阵转换,默认为 false
sprite // 渲染为 sprite,仅在转换模式下失效,默认为 false
occlude // 遮挡模式,默认为 false,当设置为 blending 时将开启真正混合遮挡
castShadow // 产生暗影
receiveShadow // 接管暗影
// 像 Mesh 一样设置材质
material={<meshPhysicalMaterial side={DoubleSide} opacity={0.1} />}
// 笼罩默认定位性能
calculatePosition={(el: Object3D, camera: Camera, size: { width: number; height: number}) => number[]}
occlude={[ref]} // 能够为真或 Ref<Object3D>,当为 true 时遮挡整个场景,默认为 undefined
onOcclude={(visible) => null} // 可见性批改时的回调,默认为 undefined
{...groupProps} // 反对所有 THREE.Group 属性
{...divProps} // 反对所有 HTML DIV 元素属性
>
<h1>hello</h1>
<p>world</p>
</Html>

Html 能够通过配置 occlude 属性暗藏在几何体前面。当 Html 组件暗藏时,它将在最外部的 div 上设置 opacity 属性,如果须要增加动画成果或者管制过渡成果,能够应用 onOcclude 自定义办法。

<Html
occlude
onOcclude={set}
style={{
transition: 'all 0.5s',
opacity: hidden ? 0 : 1,
transform: `scale(${hidden ? 0.5 : 1})`
}}
/>

useGLTF

它是一个应用 useLoaderGLTFLoader 的不便钩子函数,它默认应用 draco 来加载已压缩的模型文件。

useGLTF(url)
useGLTF(url, '/draco-gltf')
useGLTF.preload(url)

useAnimations

AnimationMixer 的形象钩子办法,能够像上面这样获取到模型自带的动画信息。

const {nodes, materials, animations} = useGLTF(url)
const {ref, mixer, names, actions, clips} = useAnimations(animations)
useEffect(() => {actions?.jump.play()
})

THREE.MathUtils.damp

应用 dt 以相似弹簧的形式从 xy 平滑地插入一个数字,以放弃与帧速率无关的静止。

.damp (x : Float, y: Float, lambda: Float, dt: Float): Float
  • x:以后点。
  • y:指标点。
  • lambda:较高的 lambda 值能够使静止更加忽然,较低的值能够使静止更加平缓。
  • dt:以秒为单位的增量工夫。

实现

〇 资源引入

在文件顶部,咱们按上述原理中所述,引入必须的组件和办法。

import * as THREE from 'three';
import {Suspense, useEffect} from 'react';
import {Canvas, useFrame} from '@react-three/fiber';
import {ScrollControls, Html, useScroll, useGLTF, useAnimations} from '@react-three/drei';

① 场景初始化

场景初始化非常简单,只需像上面这样增加 R3F<Canvas /> 组件,并初始化相机地位即可。

export default function Experience() {
return (
<>
<Canvas camera={{position: [0, 0, 0] }}></Canvas>
</>
);
}

② 加载模型

咱们先定义一个 ShubaDuck 类用来示意模型元素,而后应用 useGLTF 加载模型 🐸🦢 并应用模型文件的 scene 进行渲染。而后在 Canvas 中增加模型元素,并通过 scaleposition 等属性调整模型在页面上的显示大小和地位。在页面渲染前,咱们也能够应用 useGLTF.preload 对模型进行预加载,以进步页面应用体验。

function ShubaDuck({...props}) {const { scene, animations} = useGLTF('./models/duck.glb')
return <primitive object={scene} {...props} />
}
useGLTF.preload('./models/duck.glb');
<Canvas camera={{position: [0, 0, 0] }}>
<ShubaDuck scale={8} position={[0, -7, 0]} />
</Canvas>

以下为原始模型文件,下载完模型后能够咱们能够在 Blender 中查看模型构造和动画信息,删除批改不须要的元素,最初压缩导出。

页面中实现模型加载并渲染。

🔗 模型文件起源:https://sketchfab.com/3d-models/shuba-duck-54a6276ce06c4cc88fd497c8f1b8eb66

③ 播放模型骨骼动画

咱们从模型中拿到内置的骨骼动画 animations,而后应用 useAnimations 获取到所有的动作,能够依据动作的名称进行动画播放比方本例中是 LironShuba。能够在 useEffect 中对动作应用 play() 办法进行播放,本例中的 reset()fadeIn() 办法都是可选的,它们的作用别离是开始前重置动作和模型入场动画类型。

function ShubaDuck({...props}) {const { scene, animations} = useGLTF('./models/duck.glb')
const {actions} = useAnimations(animations, scene)
// 播放模型动画
useEffect(() => void (actions['LironShuba'].reset().fadeIn(0.5).play()), [actions])
// ...
}

④ 滚动管制模型动画

当初咱们来增加 通过鼠标滚轮滚动来管制模型动画播放进度 的性能,即当咱们向下滚动页面时,模型动画正向播放,否则逆向播放,滚动速度越快,模型动画播放速度也越快。咱们先在 useEffect 中将模型动画初始状态设置为 pause 暂停,而后在 useFrame 页面重绘动画钩子函数中拿到动画动作和滚动百分比 scroll.offset,设置动作播放工夫 action.time,使其从初始值平滑过渡到目标值,目标值的确定能够通过整个动画播放周期时间以及页面滚动间隔的长度去计算,第三个参数 lambda 也能够依据本人的页面进行调整。

function ShubaDuck({...props}) {
// ...
useEffect(() => void (actions['LironShuba'].play().paused = true), [actions])
useFrame((state, delta) => {const action = actions['LironShuba']
const offset = scroll.offset
action.time = THREE.MathUtils.damp(action.time, (action.getClip().duration / 2) * offset, 100, delta)
state.camera.lookAt(0, 0, 0)
})
// ...
}

在页面中,咱们须要应用 <ScrollControls /> 组将将模型组件 <ShubaDuck /> 包裹起来。

<Canvas camera={{position: [0, 0, 0] }}>
<Suspense fallback={null}>
<ScrollControls pages={4}>
<ShubaDuck scale={10} position={[0, -10, 0]} />
</ScrollControls>
</Suspense>
</Canvas>

此时应用滚动管制模型动画性能就全副实现了,页面初始加载时模型是静止的,当咱们滚动页面时,模型依据滚动速度和滚动间隔就会自动播放对应的动画了。

💡 scroll.offset 是一个处于 [0, 1] 之间的数,示意滚动的百分比,当页面未滚动时值为 0,齐全滚动到止境时值为 1.

⑤ 滚动管制相机

useFrame 钩子函数中,咱们同样能够在页面滚动时动静批改相机 📷 的地位,这样在视觉上也能造成比方模型旋转、放大放大、显示暗藏等动画成果。本案例中应用了如下的设置,当页面从上往下滚动时,相机从模型侧面变换到模型侧面更远的中央,在视觉上造成模型转身并变小的成果

useFrame((state, delta) => {
// ...
state.camera.position.set(Math.sin(-offset) * 50, 1, Math.cos((offset * Math.PI) / 5) * 15)
})

⑥ 页面装璜

模型局部曾经全副实现了,此时咱们能够应用原理中介绍的 Html 组件,将其增加到页面中,并间接用 HTMLCSS 增加一些难看的页面,与模型主题响应。像上面这样,本文中增加了 5 个页面,每个页面都增加了不同的款式。有工夫的话,咱们也能够应用 gsap 等动画库,给 HTML 元素也增加一些滚动时的动画成果

<Canvas camera={{position: [0, 0, 0] }}>
<Suspense fallback={null}>
<ScrollControls pages={4}>
<Html wrapperClass='articles' occlude>
<article className='page page1'></article>
<article className='page page2'></article>
<article className='page page3'></article>
<article className='page page4'></article>
<article className='page page5'></article>
</Html>
<ShubaDuck scale={10} position={[0, -10, 0]} />
</ScrollControls>
</Suspense>
</Canvas>

第一页

第一页有较多的页面元素,其中底部红色文字应用了一种 woff2 格局的开源卡通字体,旋转的鹅 🦢 是通过如下 CSS 动画实现的。

@keyframes rotateY
from
transform: perspective(400px) rotateY(0deg)
to
transform: perspective(400px) rotateY(360deg)

第二页

第二页是 3 个色块 🟩 通过旋转后造成的图案,给它们增加了明暗变动的动画成果。

其余页

剩下的页面应用了一些简略的文案或图片元素,等有工夫再优化小吧 😂。其实还有很多细节款式和性能,如鼠标的款式是一个 🟡、向下滚动的提醒语、顶部半透明的导航菜单等,具体实现可查看源码。

⑦ 点击菜单栏页面滑动动画

最初,咱们来给页面顶部的菜单增加一下点击操作 🖱。点击页面顶部导航栏菜单滑动到对应页面性能实现应用了 element.scrollIntoView 办法,能够像上面这样实现并绑定到菜单的点击事件中,此时点击菜单页面滚动时,模型动画也会同时播放。

const handleMenuClick = (className) => {const page = document.querySelector(className);
page.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'});
}
<span className='menu' onClick={handleMenuClick.bind(this, '.page1')}></span>

🔗 源码地址:https://github.com/dragonir/threejs-odessey

总结

本文中次要蕴含的知识点包含:

  • R3F 生态中的 ScrollControlsHtmluseScrolluseGLTFuseAnimations 等组件和办法的根本用法。
  • 学会在 R3F 中加载模型并播放模型骨骼动画。
  • 通过滚动管制模型动画播放过程和相机参数。
  • 在滚动页面中将模型和 HTML 元素联合起来。
  • 页面元素的一些 CSS 动画及页面整体丝滑滚动动画实现等。

想理解其余前端常识或其余未在本文中详细描述的 Web 3D 开发技术相干常识,可浏览我往期的文章。如果有疑难能够在评论中 留言 ,如果感觉文章对你有帮忙,不要忘了 一键三连哦 👍

附录

  • [1]. 🌴 Three.js 打造缤纷夏日 3D 梦中情岛
  • [2]. 🔥 Three.js 实现炫酷的赛博朋克格调 3D 数字地球大屏
  • [3]. 🐼 Three.js 实现 2022 冬奥主题 3D 趣味页面,含冰墩墩
  • [4]. 🦊 Three.js 实现 3D 凋谢世界小游戏:阿狸的多元宇宙
  • [5]. 🏡 Three.js 进阶之旅:全景漫游 - 高阶版在线看房
  • ...
  • 【Three.js 进阶之旅】系列专栏拜访 👈
  • 更多往期【3D】专栏拜访 👈
  • 更多往期【前端】专栏拜访 👈

参考

  • [1]. threejs.org
  • [2]. drei.pmnd.rs

正文完
 0