import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
// 导入动画库
import gsap from 'gsap'
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 0, 40)
scene.add(camera)
// const axesHelper = new THREE.AxesHelper(5)
// scene.add(axesHelper)
// hdr环境纹理
// const rgbeLoader = new RGBELoader()
// rgbeLoader.load('../public/assets/texture/038.hdr', texture => {
// texture.mapping = THREE.EquirectangularReflectionMapping
// scene.background = texture
// scene.environment = texture
// })
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight)
// -----------------------------------------------------------------------
const gltfLoader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('../public/draco/')
`设置【解码器的配置】,指定解码器的类型为'js',即:JavaScript解码器
Draco也支持WebAssembly(Wasm)解码器,
这里,我们选择JavaScript版本。`
dracoLoader.setDecoderConfig({ type: 'js' })
`【预加载解码器】
将Draco加载器,与,GLTF加载器,结合使用之前,需要预加载解码器,
因为,解码器可能需要一些时间从网络加载,并且我们希望,它在模型加载之前就已经准备好了。`
dracoLoader.preload()
gltfLoader.setDRACOLoader(dracoLoader)
// -----------------------------------------------------------------------
let stem = null // 茎
let stem_1 = null // 茎
let stem_2 = null // 茎
let petal = null // 花瓣
let petal_1 = null // 花瓣
let petal_2 = null // 花瓣
let params = {
value_0: 0,
value_1: 0
}
gltfLoader.load('../public/assets/model-19/f4.glb', gltf => {
// console.log('gltf=', gltf)
gltf.scene.rotation.x = Math.PI
// 遍历,gltf.scene中的所有对象
gltf.scene.traverse(item => {
`【水】`
if (item.material && item.material.name == 'Water') {
// console.log('item=', item)
// item.scale.set(0.5, 0.5, 0.5) // 缩放比例,设置为0.8(在三个维度上)
item.material = new THREE.MeshStandardMaterial({
color: 'red',
// side: THREE.DoubleSide,
transparent: true,
opacity: 0.7,
depthWrite: false,
depthTest: false
})
}
`【茎】`
if (item.material && item.material.name == 'Stem') {
stem = item
}
`【花瓣】- 开始`
if (item.material && item.material.name == 'Petal') {
petal = item
`【花苞】- 开始`
gltfLoader.load('../public/assets/model-19/f2.glb', gltf => {
// console.log('gltf=', gltf)
gltf.scene.traverse(item => {
if (item.material && item.material.name == 'Petal') {
petal_1 = item
console.log('petal_1=', petal_1)
// console.log('petal_1.geometry.attributes.position=', petal_1.geometry.attributes.position)
`1、检查,petal.geometry 是否有 morphAttributes.position数组,若没有,则初始化一个空数组。(morphAttributes:形态属性。)`
if (!petal.geometry.morphAttributes.position) {
petal.geometry.morphAttributes.position = []
}
`2、将,petal_1 的顶点位置,赋值给,petal 的morphAttributes.position数组的第一个元素
实际上,是将,petal_1 的顶点位置,作为一个形态目标,添加到 petal 上。`
petal.geometry.morphAttributes.position[0] = petal_1.geometry.attributes.position
`3、更新 petal 的形态目标,这一步是必需的,以确保Three.js能够正确地处理新添加的形态目标。`
petal.updateMorphTargets()
`4、将,第一个形态目标的权重设置为1,
意味着,petal 将完全采用这个形态目标(即:petal1 的形状)
【morphTargetInfluences】是一个数组,用于控制一个模型(如:Three.js中的Mesh)的多个形态目标的权重,
通过改变这些权重,可以平滑地过渡到一个模型的多个形态之间。`
petal.morphTargetInfluences[0] = 1
打印查看,petal,的`morphTargetInfluences`数组【这个数组,定义了,每个形态目标的权重】
console.log('petal.morphTargetInfluences=', petal.morphTargetInfluences)
console.log('petal.geometry.morphAttributes=', petal.geometry.morphAttributes)
gsap.to(params, {
value_0: 1,
duration: 10, 【持续时间】
delay: 0, 【延迟时间】
`onUpdate:在动画的每一帧被调用,意味着,随着动画的进行,这个函数会不断执行。`
onUpdate: function () {
petal.morphTargetInfluences[0] = params.value_0 // 更改,花瓣的权重
stem.morphTargetInfluences[0] = params.value_0 // 更改,茎的权重
}
})
}
if (item.material && item.material.name == 'Stem') {
stem_1 = item
if (!stem.geometry.morphAttributes.position) {
stem.geometry.morphAttributes.position = []
}
stem.geometry.morphAttributes.position[0] = stem_1.geometry.attributes.position
stem.updateMorphTargets()
stem.morphTargetInfluences[0] = 1
}
})
}) `【花苞】- 结束`
`【开花】- 开始`
gltfLoader.load('../public/assets/model-19/f1.glb', gltf => {
gltf.scene.traverse(item => {
if (item.material && item.material.name == 'Petal') {
petal_2 = item
petal.geometry.morphAttributes.position[1] = petal_2.geometry.attributes.position
petal.updateMorphTargets()
petal.morphTargetInfluences[1] = 0
gsap.to(params, {
value_1: 1,
duration: 10,
delay: 10,
// repeat: -1, // 无线循环
onUpdate: function () {
// console.log(petal.morphTargetInfluences)
// console.log(stem.morphTargetInfluences)
petal.morphTargetInfluences[0] = params.value_0
stem.morphTargetInfluences[0] = params.value_0
petal.morphTargetInfluences[1] = params.value_1
stem.morphTargetInfluences[1] = params.value_1
}
})
}
if (item.material && item.material.name == 'Stem') {
stem_2 = item
stem.geometry.morphAttributes.position[1] = stem_2.geometry.attributes.position
stem.updateMorphTargets()
stem.morphTargetInfluences[1] = 0
}
})
}) `【开花】- 结束`
} `【花瓣】- 结束`
})
scene.add(gltf.scene)
})
//#region --------------------- 看看用到的这几个gltf模型长什么样 ---------------------
gltfLoader.load('../public/assets/model-19/f4.glb', gltf => {
// console.log('gltf=', gltf)
gltf.scene.position.y = -20
gltf.scene.position.x = 10
gltf.scene.rotation.x = Math.PI
scene.add(gltf.scene)
})
gltfLoader.load('../public/assets/model-19/f2.glb', gltf => {
// console.log('gltf=', gltf)
gltf.scene.position.y = -20
gltf.scene.position.x = 20
gltf.scene.rotation.x = Math.PI
scene.add(gltf.scene)
})
gltfLoader.load('../public/assets/model-19/f1.glb', gltf => {
// console.log('gltf=', gltf)
gltf.scene.position.y = -20
gltf.scene.position.x = 30
gltf.scene.rotation.x = Math.PI
scene.add(gltf.scene)
})
//#endregion --------------------- 看看用到的这几个gltf模型长什么样 ---------------------
const renderer = new THREE.WebGLRenderer({
antialias: true,
`启用【对数深度缓冲区】有助于减少在深度测试中常见的【z-fighting问题】,特别是在处理远距离物体时`
logarithmicDepthBuffer: true
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.shadowMap.enabled = true // 阴影效果
renderer.physicallyCorrectLights = true // 开启物理光照效果
// renderer.setClearColor(0xcccccc, 1) // 设置背景颜色
/* 禁用自动清除,每次渲染调用之前,渲染器不会自动清除其颜色、深度和模板缓冲区
这在,想要在某些帧上,保留前一帧的内容时非常有用,
比如:实现某些类型的累积效果,或自定义渲染流程。*/
renderer.autoClear = false `和【renderer.autoClear = false】配合使用`
renderer.toneMapping = THREE.ACESFilmicToneMapping // 设置电影渲染模式
`渲染器输出的颜色编码方式为:sRGB方式`(sRGB编码:一种非线性的颜色空间,更接近于人眼对亮度的感知方式,更准确自然)
renderer.outputEncoding = THREE.sRGBEncoding
/* 启用,渲染器的对象排序功能,
渲染器,会根据对象到摄像机的距离,来排序场景中的对象,
并且,先渲染较远的对象,再渲染较近的对象。
有助于,减少由于深度测试引起的渲染问题,如z-fighting(深度冲突),
并且,可能提高渲染性能,因为,早期深度测试,可以更早地拒绝被遮挡的像素。
缺点: 需要注意的是,
对象排序,可能会增加渲染前的处理时间,可能不适用于所有场景。
特别是,当场景中有大量透明对象,或复杂的层叠关系时,简单的距离排序可能不足以解决问题,甚至可能引入新的问题。*/
renderer.sortObjects = true
// 启用对数深度缓冲区,有助于减少在深度测试中常见的z-fighting问题,特别是在处理远距离物体时。
renderer.logarithmicDepthBuffer = true
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
const clock = new THREE.Clock()
const render = ()=>{
let time = clock.getElapsedTime()
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(render)
}
render()
window.addEventListener('resize', () => {
// 重置相机的宽高比
camera.aspect = window.innerWidth / window.innerHeight
// 更新相机的投影矩阵
camera.updateProjectionMatrix()
// 重置渲染器的宽高比
renderer.setSize(window.innerWidth, window.innerHeight)
// 更新渲染器的像素比
renderer.setPixelRatio(window.devicePixelRatio)
})
3.js - 变形动画
于 2024-09-27 14:15:31 首次发布