【threejs】基本编程概念及海岛模型展示逻辑

编程入门 行业动态 更新时间:2024-10-14 18:19:55

【threejs】基本编程概念及<a href=https://www.elefans.com/category/jswz/34/1675359.html style=海岛模型展示逻辑"/>

【threejs】基本编程概念及海岛模型展示逻辑

采用three封装模式完成的海岛动画(点击这里查看)

直接上代码吧

<template><div class="scene"><video id="videoContainer" style="position:absolute;top:0px;left:0px;z-index:100;visibility: hidden"></video><div v-if="loadingProcess !== 100" class='loading'><span class='progress'>{{loadingProcess}} %</span></div><div class="scene" id="viewer-container"></div><div class="point point-0"><div class="label label-0">1</div><div class="text">灯塔:矗立在海岸的岩石之上,白色的塔身以及红色的塔屋,在湛蓝色的天空和深蓝色大海的映衬下,显得如此醒目和美丽。</div></div><div class="point point-1"><div class="label label-1">2</div><div class="text">小船:梦中又见那宁静的大海,我前进了,驶向远方,我知道我是船,只属于远方。这一天,我用奋斗作为白帆,要和明天一起飘扬,呼喊。</div></div><div class="point point-2"><div class="label label-2">3</div><div class="text">沙滩:宇宙展开的一小角。不想说来这里是暗自疗伤,那过于矫情,只想对每一粒沙子,每一朵浪花问声你们好吗</div></div><div class="point point-3"><div class="label label-3">4</div><div class="text">飞鸟:在苍茫的大海上,狂风卷集着乌云。在乌云和大海之间,海燕像黑色的闪电,在高傲地飞翔。</div></div><div class="point point-4"><div class="label label-4">5</div><div class="text">礁石:寂寞又怎么样?礁石都不说话,但是水流过去之后,礁石留下。</div></div><div class="panel"><div class="main"><li class="tools-li" @click="resetScene"><p class="tools-name">场景重置</p></li><li class="tools-li" @click="inScene"><p class="tools-name">进入场景</p></li></div></div></div></template><script setup>
import { onBeforeUnmount, onMounted, nextTick, ref } from "vue"
import gsap from "gsap";
import modules from "./modules/index.js";
import Animations from './utils/animations';
import * as THREE from "three";
import { Water } from 'three/examples/jsm/objects/Water';
import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js'; // tween 动画效果渲染 效果同 gsap
import { Lensflare, LensflareElement } from 'three/examples/jsm/objects/Lensflare.js';
import vertexShader from './shaders/vertex.glsl?raw';
import fragmentShader from './shaders/fragment.glsl?raw';let loadingProcess = ref(0) // loading加载数据 0 25 50 75 100
let sceneReady = false // 场景加载完毕标志,程序进行label展示,镜头拉进等效果let viewer = null // 基础类,包含场景、相机、控制器等实例
let tiemen = null // 水面动画 函数
let allTiemen = null // 全局动画 函数const sizes = { // 存储全局宽度 高度width: window.innerWidth,height: window.innerHeight
}const lensflareTexture0 = 'images/lensflare0.png' // 太阳光贴图
const lensflareTexture1 = 'images/lensflare1.png' // 黑色描边贴图
const waterTexture = 'images/waternormals.jpg' // 水面基础图const resetScene = () => { // 重置场景函数// Animations.animateCamera 利用tweenjs 完成的镜头切换动画工具函数,分别传入相机,控制器,相机最终位置,指向控制器位置,动作时间Animations.animateCamera(viewer.camera, viewer.controls, { x: 0, y: 600, z: 1600 }, { x: 0, y: 0, z: 0 }, 4000, () => {sceneReady = true});
}const inScene = () => { // 进入场景函数// Animations.animateCamera 利用tweenjs 完成的镜头切换动画工具函数,分别传入相机,控制器,相机最终位置,指向控制器位置,动作时间Animations.animateCamera(viewer.camera, viewer.controls, { x: 0, y: 40, z: 140 }, { x: 0, y: 0, z: 0 }, 4000, () => {sceneReady = true});
}// 初始化three场景
const init = () => {viewer = new modules.Viewer('viewer-container') //初始化场景// 初始化模型上方的label存储空间// let labels = new modules.Labels(viewer)// 添加3种天空盒子的效果中的一种 白天 黑夜 黄昏viewer._initSkybox(0)// 调整相机位置(相机位置在初始化的时候设置过一次,这里对其进行调整)viewer.camera.position.set(0, 600, 1600)// 限制controls的上下角度范围 (OrbitControls的范围)viewer.controls.maxPolarAngle = Math.PI / 2.1;// 增加灯光(初始化viewer的时候,对灯光也做了初始,这里进行灯光调整)let { lights } = viewer// 环境光会均匀的照亮场景中的所有物体。 环境光不能用来投射阴影,因为它没有方向。let ambientLight = lights.addAmbientLight() ambientLight.setOption({color: 0xffffff, intensity: 0.8}) // 调用灯光内置方法,设置新的属性// 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。 太阳足够远,因此我们可以认为太阳的位置是无限远,所以我们认为从太阳发出的光线也都是平行的。lights.addDirectionalLight([-1, 1.75, 1], { // 增加直射灯光方法 color: 'rgb(255,234,229)',// intensity: 3, // intensity属性是用来设置聚光灯的强度,默认值是1,如果设置成0那什么也看不到,该值越大,点光源看起来越亮// castShadow: true, // castShadow属性是用来控制光源是否产生阴影,取值为true或false})// 从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。const pointLight = lights.addPointLight([0, 45, -2000], { // 增加直射灯光方法 color: 'rgb(253,153,253)'})// 模拟太阳光效果const textureLoader = new THREE.TextureLoader(); // 加载texture的一个类。 内部使用ImageLoader来加载文件。const textureFlare0 = textureLoader.load(lensflareTexture0); // 加载太阳光 贴图const textureFlare1 = textureLoader.load(lensflareTexture1); // 加载黑色贴图// 镜头光晕const lensflare = new Lensflare(); // 创建一个模拟追踪着灯光的镜头光晕。 Lensflare can only be used when setting the alpha context parameter of WebGLRenderer to true.lensflare.addElement(new LensflareElement( textureFlare0, 600, 0, pointLight.color));// LensflareElement( texture : Texture, size : Float, distance : Float, color : Color )// texture - 用于光晕的THREE.Texture(贴图)// size - (可选)光晕尺寸(单位为像素)// distance - (可选)和光源的距离值在0到1之间(值为0时在光源的位置)// color - (可选)光晕的(Color)颜色lensflare.addElement(new LensflareElement( textureFlare1, 60, .6));lensflare.addElement(new LensflareElement( textureFlare1, 70, .7));lensflare.addElement(new LensflareElement( textureFlare1, 120, .9));lensflare.addElement(new LensflareElement( textureFlare1, 70, 1));pointLight.add(lensflare);// 海const waterGeometry = new THREE.PlaneGeometry(10000, 10000); // 一个用于生成平面几何体的类。const water = new Water(waterGeometry, { // 官方模板textureWidth: 512,textureHeight: 512,waterNormals: new THREE.TextureLoader().load(waterTexture,  texture => {texture.wrapS = texture.wrapT = THREE.RepeatWrapping;}),sunDirection: new THREE.Vector3(),sunColor: 0xffffff,waterColor: 0x0072ff,distortionScale: 4,fog: viewer.scene.fog !== undefined});water.rotation.x = - Math.PI / 2;viewer.scene.add(water)tiemen = { // 水面移动动画绘制fun和contentfun: (water) => {water.material.uniforms[ 'time' ].value += 1.0 / 60.0; // 参考threejs example 进行设置}, // 水面移动方法汇总content: water}viewer.addAnimate(tiemen) // 设置水面波动 动画执行// 彩虹(目前未展示)const material = new THREE.ShaderMaterial({side: THREE.DoubleSide,transparent: true,uniforms: {},vertexShader: vertexShader,fragmentShader: fragmentShader});const geometry = new THREE.TorusGeometry(200, 10, 50, 100);const torus = new THREE.Mesh(geometry, material);torus.opacity = .1;torus.position.set(0, -50, -400);viewer.scene.add(torus);// 官方模板给定的太阳渲染方式// 天空 需配合太阳进行渲染// const sky = new Sky();// sky.scale.setScalar( 450000 );// viewer.scene.add( sky );// const skyUniforms = sky.material.uniforms;// skyUniforms['turbidity'].value = 20;// skyUniforms['rayleigh'].value = 2;// skyUniforms['mieCoefficient'].value = 0.005;// skyUniforms['mieDirectionalG'].value = 0.8;// // 太阳// const sun = new THREE.Vector3();// const pmremGenerator = new THREE.PMREMGenerator(viewer.renderer);// const phi = THREE.MathUtils.degToRad(88);// const theta = THREE.MathUtils.degToRad(180);// sun.setFromSphericalCoords( 1, phi, theta );// sky.material.uniforms['sunPosition'].value.copy( sun );// water.material.uniforms['sunDirection'].value.copy(sun).normalize();// viewer.scene.environment = pmremGenerator.fromScene(sky).texture;// LoadingManager是three.js中的加载管理器,用于监控和管理加载资源的过程。// 通过使用LoadingManager,我们可以在应用程序中方便地加载各种类型的数据,例如模型、纹理、声音等const manager = new THREE.LoadingManager();// 模型加载时,处理过程的函数manager.onProgress = async(url, loaded, total) => {const rate = Math.floor(loaded / total * 100) // 计算当前模型加载比例 0 25 50 75 100loadingProcess.value = rate // 设置模型加载比例if (rate === 100) { // 如果模型加载完,则进行镜头拉进// Animations.animateCamera 利用tweenjs 完成的镜头切换动画工具函数,分别传入相机,控制器,相机最终位置,指向控制器位置,动作时间Animations.animateCamera(viewer.camera, viewer.controls, { x: 0, y: 40, z: 140 }, { x: 0, y: 0, z: 0 }, 4000, () => {sceneReady = true});}};// 实例化ModelLoder,用于加载模型// 将模型加载时要用到 的回调函数 传入loader的创建过程中let modeloader = new modules.ModelLoder(viewer, manager) // 这样利用modeloader进行加载的模型都会计入模型加载时机中// 小岛加载 利用modeloader,就会触发manager.onProgress中的方法modeloader.loadModelToScene('models/island.glb', _model => {// modeloader.loadModelToScene('models/island.glb', _model => { 跟下面,原 GLTFLoader 产生的mesh和loadModelToScene产生的_model 其实是一回事// const loader = new GLTFLoader(manager);// loader.load(islandModel, mesh => {_model.openCastShadow() // 开启模型阴影 数组中移除阴影_model.object.traverse(child => {if (child.isMesh) {child.material.metalness = .4;child.material.roughness = .6;}})_model.object.position.set(0, -2, 0);_model.object.scale.set(33, 33, 33);})// 鸟加载modeloader.loadModelToScene('models/flamingo.glb', _model => {_model.openCastShadow() // 开启模型阴影 数组中移除阴影_model.startAnima(0, 1.2) // 开启模型自带的第1个动画,延时1.2秒执行一次 code/src/components/three/modules/DsModel/index.jsconst mesh = _model.object.children[0];mesh.scale.set(.35, .35, .35);mesh.position.set(-100, 80, -300);mesh.rotation.y = - 1;mesh.castShadow = true;_model.cloneModel([150, 80, -500]).startAnima(0, 1.8) // 开启模型自带的第1个动画,延时1.8秒执行一次 code/src/components/three/modules/DsModel/index.js// _model.startAnima(0, 1.2) 同下方的动画效果// const mixer = new THREE.AnimationMixer(mesh);// mixer.clipAction(gltf.animations[0]).setDuration(1.2).play(); // 开启模型自带的第1个动画,延时1.2秒执行一次// this.mixers.push(mixer);// _model.cloneModel([150, 80, -500]).startAnima() 同下方的动画效果// const mixer2 = new THREE.AnimationMixer(bird2);// mixer2.clipAction(gltf.animations[0]).setDuration(1.8).play(); // 开启模型自带的第1个动画,延时1.8秒执行一次// this.mixers.push(mixer2);})const raycaster = new THREE.Raycaster()// 小岛上各个景点的点位置,和dom元素const points = [{position: new THREE.Vector3(10, 46, 0),element: document.querySelector('.point-0')},{position: new THREE.Vector3(-10, 8, 24),element: document.querySelector('.point-1')},{position: new THREE.Vector3(30, 10, 70),element: document.querySelector('.point-2')},{position: new THREE.Vector3(-100, 50, -300),element: document.querySelector('.point-3')},{position: new THREE.Vector3(-120, 50, -100),element: document.querySelector('.point-4')}];// 给每一个景点增加click事件,点击后移动到对应位置document.querySelectorAll('.point').forEach(item => {item.addEventListener('click', event => {let className = event.target.classList[event.target.classList.length - 1];switch(className) {case 'label-0':Animations.animateCamera(viewer.camera, viewer.controls, { x: -15, y: 80, z: 60 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;case 'label-1':Animations.animateCamera(viewer.camera, viewer.controls, { x: -20, y: 10, z: 60 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;case 'label-2':Animations.animateCamera(viewer.camera, viewer.controls, { x: 30, y: 10, z: 100 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;default:Animations.animateCamera(viewer.camera, viewer.controls, { x: 0, y: 40, z: 140 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;}}, false);});const { camera, scene } = viewerallTiemen = {fun: (water) => {TWEEN && TWEEN.update(); // 镜头拉进等效果 Animations.animateCamera// 镜头上下浮动效果const timer = Date.now() * 0.0005;camera && (camera.position.y += Math.sin(timer) * .05);if (sceneReady) {// 遍历每个点for (const point of points) {// 获取2D屏幕位置const screenPosition = point.position.clone();screenPosition.project(camera);raycaster.setFromCamera(screenPosition, camera);const intersects = raycaster.intersectObjects(scene.children, true);if (intersects.length === 0) {// 未找到相交点,显示point.element.classList.add('visible');} else {// 找到相交点// 获取相交点的距离和点的距离const intersectionDistance = intersects[0].distance;const pointDistance = point.position.distanceTo(camera.position);// 相交点距离比点距离近,隐藏;相交点距离比点距离远,显示intersectionDistance < pointDistance ? point.element.classList.remove('visible') :  point.element.classList.add('visible');}const translateX = screenPosition.x * sizes.width * 0.5;const translateY = - screenPosition.y * sizes.height * 0.5;point.element.style.transform = `translateX(${translateX}px) translateY(${translateY}px)`;}}},content: water}viewer.addAnimate(allTiemen)
}onBeforeUnmount(()=>{window.removeEventListener('resize', () => {viewer._undateDom()})
})onMounted(()=>{init()// 监听页面大小变动,自适应页面, 第一次直接触发执行window.addEventListener('resize', () => {viewer._undateDom()})// 初次页面变动执行不成功,主动延迟执行一次nextTick(()=>{viewer._undateDom()})
})  
</script><style lang="scss">
//定义全局颜色
$color: #123ca8;
.scene {height: 100vh;width: 100%;overflow: hidden;.loading {position: fixed;height: 100%;width: 100%;z-index: 99;background: rgba(46, 66, 77, .8);filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, .25));backdrop-filter: blur(10px);display: flex;justify-content: space-around;align-items: center;.progress {font-size: 3.6rem;color: #FFFFFF;text-shadow: 0 1px 0 hsl(174,5%,80%),0 2px 0 hsl(174,5%,75%),0 3px 0 hsl(174,5%,70%),0 4px 0 hsl(174,5%,66%),0 5px 0 hsl(174,5%,64%),0 6px 0 hsl(174,5%,62%),0 7px 0 hsl(174,5%,61%),0 8px 0 hsl(174,5%,60%),0 0 5px rgba(0,0,0,.05),0 1px 3px rgba(0,0,0,.2),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.2),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.3);}}.point {position: fixed;top: 50%;left: 50%;z-index: 10;.label {position: absolute;top: -16px;left: -16px;width: 8px;height: 8px;border-radius: 50%;background: #00000077;border: 1px solid #ffffff77;color: #ffffff;font-family: Helvetica, Arial, sans-serif;text-align: center;line-height: 8px;font-weight: 100;font-size: 14px;cursor: help;transform: scale(0, 0);transition: transform 0.3s;}.text {position: absolute;top: 30px;left: -120px;width: 200px;padding: 20px;border-radius: 4px;background: rgba(0, 0, 0, .6);border: 1px solid #ffffff77;color: #ffffff;line-height: 1.3em;font-family: Helvetica, Arial, sans-serif;font-weight: 100;font-size: 14px;opacity: 0;transition: opacity 0.3s;pointer-events: none;text-align: justify;text-align-last: left;}&:hover .text{opacity: 1;}&.visible .label{transform: scale(1, 1);}}.label {padding: 20px;background: $color;color: aliceblue;border-radius: 5px;cursor: pointer;}.panel {margin: 0 auto;padding: 0;box-sizing: border-box;bottom: 10px;position: absolute;opacity: 0.8;width: 100%;left: 0;right: 0;display: flex;flex-direction: column;align-items: center;justify-content: center;.main {margin: 0;padding: 0;box-sizing: border-box;border-radius: 4px;opacity: 0.96;border: 1px solid #14171c;background: linear-gradient(0deg, #1e202a 0%, #0d1013 100%);box-shadow: 0px 2px 21px 0px rgba(33, 34, 39, 0.55);li {padding: 5px 10px;box-sizing: border-box;list-style: none;cursor: pointer;border: 1px solid #313642;border-radius: 2px;float: left;margin: 5px;position: relative;width: 70px;p {list-style: none;cursor: pointer;margin: 0;padding: 0;box-sizing: border-box;height: 20px;text-align: center;font-size: 12px;font-weight: 400;color: #fbfbfb;display: block;}}}}
}
</style>

这里只把主文件进行讲解,涉及到的插件和方法就不细讲了
源码放在这里了

一个theejs的场景无外乎场景scene、相机camera、光照light、渲染器render
当整个组件进入mounted的时候调用init函数,

  1. 对场景等配置进行初始化类的实例化时,会将场景、相机、光照、渲染器进行初始化,满足大多数three功能的需要。
viewer = new modules.Viewer('viewer-container') 
  1. 添加天空盒子、调整相机位置、控制器位置
// 添加3种天空盒子的效果中的一种 白天 黑夜 黄昏viewer._initSkybox(0)// 调整相机位置(相机位置在初始化的时候设置过一次,这里对其进行调整)viewer.camera.position.set(0, 600, 1600)// 限制controls的上下角度范围 (OrbitControls的范围)viewer.controls.maxPolarAngle = Math.PI / 2.1;
  1. 调整光照位置、增加光照和太阳光模拟效果
// 增加灯光(初始化viewer的时候,对灯光也做了初始,这里进行灯光调整)let { lights } = viewer// 环境光会均匀的照亮场景中的所有物体。 环境光不能用来投射阴影,因为它没有方向。let ambientLight = lights.addAmbientLight() ambientLight.setOption({color: 0xffffff, intensity: 0.8}) // 调用灯光内置方法,设置新的属性// 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。 太阳足够远,因此我们可以认为太阳的位置是无限远,所以我们认为从太阳发出的光线也都是平行的。lights.addDirectionalLight([-1, 1.75, 1], { // 增加直射灯光方法 color: 'rgb(255,234,229)',// intensity: 3, // intensity属性是用来设置聚光灯的强度,默认值是1,如果设置成0那什么也看不到,该值越大,点光源看起来越亮// castShadow: true, // castShadow属性是用来控制光源是否产生阴影,取值为true或false})// 从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。const pointLight = lights.addPointLight([0, 45, -2000], { // 增加直射灯光方法 color: 'rgb(253,153,253)'})// 模拟太阳光效果const textureLoader = new THREE.TextureLoader(); // 加载texture的一个类。 内部使用ImageLoader来加载文件。const textureFlare0 = textureLoader.load(lensflareTexture0); // 加载太阳光 贴图const textureFlare1 = textureLoader.load(lensflareTexture1); // 加载黑色贴图// 镜头光晕const lensflare = new Lensflare(); // 创建一个模拟追踪着灯光的镜头光晕。 Lensflare can only be used when setting the alpha context parameter of WebGLRenderer to true.lensflare.addElement(new LensflareElement( textureFlare0, 600, 0, pointLight.color));// LensflareElement( texture : Texture, size : Float, distance : Float, color : Color )// texture - 用于光晕的THREE.Texture(贴图)// size - (可选)光晕尺寸(单位为像素)// distance - (可选)和光源的距离值在0到1之间(值为0时在光源的位置)// color - (可选)光晕的(Color)颜色lensflare.addElement(new LensflareElement( textureFlare1, 60, .6));lensflare.addElement(new LensflareElement( textureFlare1, 70, .7));lensflare.addElement(new LensflareElement( textureFlare1, 120, .9));lensflare.addElement(new LensflareElement( textureFlare1, 70, 1));pointLight.add(lensflare);
  1. 添加大海效果,并增加大海波浪动画(参考官网demo)
 // 海const waterGeometry = new THREE.PlaneGeometry(10000, 10000); // 一个用于生成平面几何体的类。const water = new Water(waterGeometry, { // 官方模板textureWidth: 512,textureHeight: 512,waterNormals: new THREE.TextureLoader().load(waterTexture,  texture => {texture.wrapS = texture.wrapT = THREE.RepeatWrapping;}),sunDirection: new THREE.Vector3(),sunColor: 0xffffff,waterColor: 0x0072ff,distortionScale: 4,fog: viewer.scene.fog !== undefined});water.rotation.x = - Math.PI / 2;viewer.scene.add(water)tiemen = { // 水面移动动画绘制fun和contentfun: (water) => {water.material.uniforms[ 'time' ].value += 1.0 / 60.0; // 参考threejs example 进行设置}, // 水面移动方法汇总content: water}viewer.addAnimate(tiemen) // 设置水面波动 动画执行
  1. 模型加载loading过程函数构建(用于loading效果的展示)
 // LoadingManager是three.js中的加载管理器,用于监控和管理加载资源的过程。
// 通过使用LoadingManager,我们可以在应用程序中方便地加载各种类型的数据,例如模型、纹理、声音等
const manager = new THREE.LoadingManager();
// 模型加载时,处理过程的函数
manager.onProgress = async(url, loaded, total) => {const rate = Math.floor(loaded / total * 100) // 计算当前模型加载比例 0 25 50 75 100loadingProcess.value = rate // 设置模型加载比例if (rate === 100) { // 如果模型加载完,则进行镜头拉进// Animations.animateCamera 利用tweenjs 完成的镜头切换动画工具函数,分别传入相机,控制器,相机最终位置,指向控制器位置,动作时间Animations.animateCamera(viewer.camera, viewer.controls, { x: 0, y: 40, z: 140 }, { x: 0, y: 0, z: 0 }, 4000, () => {sceneReady = true});}
};
  1. 实例化Modeloader,用于加载模型,会触发步骤5中的loading过程函数
// 实例化ModelLoder,用于加载模型// 将模型加载时要用到 的回调函数 传入loader的创建过程中let modeloader = new modules.ModelLoder(viewer, manager) // 这样利用modeloader进行加载的模型都会计入模型加载时机中// 小岛加载 利用modeloader,就会触发manager.onProgress中的方法modeloader.loadModelToScene('models/island.glb', _model => {// modeloader.loadModelToScene('models/island.glb', _model => { 跟下面,原 GLTFLoader 产生的mesh和loadModelToScene产生的_model 其实是一回事// const loader = new GLTFLoader(manager);// loader.load(islandModel, mesh => {_model.openCastShadow() // 开启模型阴影 数组中移除阴影_model.object.traverse(child => {if (child.isMesh) {child.material.metalness = .4;child.material.roughness = .6;}})_model.object.position.set(0, -2, 0);_model.object.scale.set(33, 33, 33);})// 鸟加载modeloader.loadModelToScene('models/flamingo.glb', _model => {_model.openCastShadow() // 开启模型阴影 数组中移除阴影_model.startAnima(0, 1.2) // 开启模型自带的第1个动画,延时1.2秒执行一次 code/src/components/three/modules/DsModel/index.jsconst mesh = _model.object.children[0];mesh.scale.set(.35, .35, .35);mesh.position.set(-100, 80, -300);mesh.rotation.y = - 1;mesh.castShadow = true;_model.cloneModel([150, 80, -500]).startAnima(0, 1.8) // 开启模型自带的第1个动画,延时1.8秒执行一次 code/src/components/three/modules/DsModel/index.js// _model.startAnima(0, 1.2) 同下方的动画效果// const mixer = new THREE.AnimationMixer(mesh);// mixer.clipAction(gltf.animations[0]).setDuration(1.2).play(); // 开启模型自带的第1个动画,延时1.2秒执行一次// this.mixers.push(mixer);// _model.cloneModel([150, 80, -500]).startAnima() 同下方的动画效果// const mixer2 = new THREE.AnimationMixer(bird2);// mixer2.clipAction(gltf.animations[0]).setDuration(1.8).play(); // 开启模型自带的第1个动画,延时1.8秒执行一次// this.mixers.push(mixer2);})
  1. 添加场景中各个景点的点位置和点击事件
const raycaster = new THREE.Raycaster()// 小岛上各个景点的点位置,和dom元素const points = [{position: new THREE.Vector3(10, 46, 0),element: document.querySelector('.point-0')},{position: new THREE.Vector3(-10, 8, 24),element: document.querySelector('.point-1')},{position: new THREE.Vector3(30, 10, 70),element: document.querySelector('.point-2')},{position: new THREE.Vector3(-100, 50, -300),element: document.querySelector('.point-3')},{position: new THREE.Vector3(-120, 50, -100),element: document.querySelector('.point-4')}];// 给每一个景点增加click事件,点击后移动到对应位置document.querySelectorAll('.point').forEach(item => {item.addEventListener('click', event => {let className = event.target.classList[event.target.classList.length - 1];switch(className) {case 'label-0':Animations.animateCamera(viewer.camera, viewer.controls, { x: -15, y: 80, z: 60 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;case 'label-1':Animations.animateCamera(viewer.camera, viewer.controls, { x: -20, y: 10, z: 60 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;case 'label-2':Animations.animateCamera(viewer.camera, viewer.controls, { x: 30, y: 10, z: 100 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;default:Animations.animateCamera(viewer.camera, viewer.controls, { x: 0, y: 40, z: 140 }, { x: 0, y: 0, z: 0 }, 1600, () => {});break;}}, false);});
  1. 全局动画效果逻辑处理
const { camera, scene } = viewerallTiemen = {fun: (water) => {TWEEN && TWEEN.update(); // 镜头拉进等效果 Animations.animateCamera// 镜头上下浮动效果const timer = Date.now() * 0.0005;camera && (camera.position.y += Math.sin(timer) * .05);if (sceneReady) {// 遍历每个点for (const point of points) {// 获取2D屏幕位置const screenPosition = point.position.clone();screenPosition.project(camera);raycaster.setFromCamera(screenPosition, camera);const intersects = raycaster.intersectObjects(scene.children, true);if (intersects.length === 0) {// 未找到相交点,显示point.element.classList.add('visible');} else {// 找到相交点// 获取相交点的距离和点的距离const intersectionDistance = intersects[0].distance;const pointDistance = point.position.distanceTo(camera.position);// 相交点距离比点距离近,隐藏;相交点距离比点距离远,显示intersectionDistance < pointDistance ? point.element.classList.remove('visible') :  point.element.classList.add('visible');}const translateX = screenPosition.x * sizes.width * 0.5;const translateY = - screenPosition.y * sizes.height * 0.5;point.element.style.transform = `translateX(${translateX}px) translateY(${translateY}px)`;}}},content: water}viewer.addAnimate(allTiemen)

另外,太阳+天空的渲染方式还有一种就是官方demo给出的

  // 天空 需配合太阳进行渲染const sky = new Sky();sky.scale.setScalar( 450000 );viewer.scene.add( sky );const skyUniforms = sky.material.uniforms;skyUniforms['turbidity'].value = 20;skyUniforms['rayleigh'].value = 2;skyUniforms['mieCoefficient'].value = 0.005;skyUniforms['mieDirectionalG'].value = 0.8;// 太阳const sun = new THREE.Vector3();const pmremGenerator = new THREE.PMREMGenerator(viewer.renderer);const phi = THREE.MathUtils.degToRad(88);const theta = THREE.MathUtils.degToRad(180);sun.setFromSphericalCoords( 1, phi, theta );sky.material.uniforms['sunPosition'].value.copy( sun );water.material.uniforms['sunDirection'].value.copy(sun).normalize();viewer.scene.environment = pmremGenerator.fromScene(sky).texture;

更多详细代码请关注公众号索取(备注:公众号):

更多推荐

【threejs】基本编程概念及海岛模型展示逻辑

本文发布于:2024-02-27 15:11:49,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1707059.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:海岛   逻辑   模型   概念   threejs

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!