当前位置: 首页 > news >正文

【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】基本编程概念及海岛模型展示逻辑

采用three封装模式完成的海岛动画&#xff08;点击这里查看&#xff09; 直接上代码吧 <template><div class"scene"><video id"videoContainer" style"position:absolute;top:0px;left:0px;z-index:100;visibility: hidden"&g…...

python小技巧:创建单链表及删除元素

目前只有单链表&#xff08;无法查找上一个元素&#xff09;&#xff0c;后面再更新循环链表和双链表。 class SingleLinkedList:def createList(self, raw_list):if len(raw_list) 0:head ListNode()else:head ListNode(raw_list[0])cur headfor i in range(1, len(raw_l…...

ADuM1250 ADuM1251 模块 I2C IIC总线2500V电磁隔离 接口保护

功能说明&#xff1a; 1&#xff0c;2500V电磁隔离&#xff0c;2通道双向I2C&#xff1b; 2&#xff0c;支持电压在3到5.5V&#xff0c;最大时钟频率可达1000KHz&#xff1b; 3&#xff0c;将该隔离模块接入总线&#xff0c;可以保护主MCU引脚&#xff0c;降低I2C总线上的干…...

C# 把多个dll合成一个dll

Nuget 下载ILMerge两个工程 dog为测试工程 TestIlmerge为准备合并的类库 如下图所示&#xff0c; 由于我们引用下面4个库 正常生成后&#xff0c;会有TestIlmerge.dll和下面的这4个dll 只生成TestIlmerge.dll 打开工程文件 在最下方加入以下两段 <Target Name"ILMerge…...

scipy.sparse.coo_matrix.sum()关于axis的用法

以下面的矩阵为例 [1,2,0] [0,3,0] [0,0,0]示例代码 from scipy.sparse import coo_matrix# 创建一个稀疏矩阵 data [1, 2, 3] row [0, 0, 1] col [0, 1, 1] sparse_matrix coo_matrix((data, (row, col)), shape(3,3))# 计算稀疏矩阵中每行非零元素的总和 sum_of_column…...

C++类与对象(下)

文章目录 1.非类型模板2.模板特化2.1.类模板特化2.1.1.全特化2.1.2.偏特化 2.2.函数模板特化 3.函数模板声明定义分离 之前我们学习的模板能达到泛型的原因是&#xff1a;使用了“泛型的类型”&#xff0c;但是如果经过后面的“造轮子”&#xff08;后面会尝试实现一下 STL的一…...

SpringBoot——》引入Redis

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...

C# newtonsoft序列化将long类型转化为字符串

/// <summary> /// 转化为json的时候long类型转为string /// </summary> public class LongJsonConverter: JsonConverter {public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){try{return r…...

黑马点评-02使用Redis代替session,Redis + token机制实现

Redis代替session session共享问题 每个Tomcat中都有一份属于自己的session,所以多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时可能会导致数据丢失 用户第一次访问1号tomcat并把自己的信息存放session域中, 如果第二次访问到了2号tomcat就无法获取到在1号…...

arm 点灯实验代码以及现象

.text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R0,0x50000A28 LDR R1,[R0] ORR R1,R1,#(0x1<<4) 第4位置1 STR R1,[R0] 1.设置GPIOF寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R…...

选择适合普通公司的项目管理软件

不管是打工人还是学生党都适合使用Zoho Projects项目管理软件。利用项目概览功能&#xff0c;将整体项目尽收眼底&#xff0c;作为项目管理者&#xff0c;项目日程、进度都可见&#xff0c;Zoho Projects项目管理APP助推项目每一环节的进展&#xff0c;更便于管理者设计项目的下…...

E (1081) : DS堆栈--逆序输出(STL栈使用)

Description C中已经自带堆栈对象stack&#xff0c;无需编写堆栈操作的具体实现代码。 本题目主要帮助大家熟悉stack对象的使用&#xff0c;然后实现字符串的逆序输出 输入一个字符串&#xff0c;按字符按输入顺序压入堆栈&#xff0c;然后根据堆栈后进先出的特点&#xff0…...

访问者模式 行为型设计模式之九

1.定义 在不改变数据结构的前提下&#xff0c;增加作用于一组对象元素的新功能。 2.动机 访问者模式适用于数据结构相对稳定的系统它把数据结构和作用于数据结构之上的操作之间的耦合解脱开&#xff0c;使得操作集合可以相对自由的演化。访问者模式的目的是要把处理从数据结构…...

JVM垃圾回收之JVM GC算法探究

JVM垃圾回收之JVM GC算法探究 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;是自动管理内存的重要机制&#xff0c;它负责回收程序中不再使用的对象所占用的内存。GC算法是垃圾回收的核心&#xf…...

Django 前端模板显示换行符、日期格式

linebreaksbr 显示换行符 <td>{{ data.sku_list|default:"无"|linebreaksbr }}</td> date:"Y年m月d日 H:i" 设置日期格式 <td>{{ data.submit_time|date:"Y年m月d日 H:i" }}</td> 其他语法 forloop 获取循环的索引 …...

Aurora中的策略模式和模板模式

Aurora中的策略模式和模板模式 在aurora中为了方便以后的扩展使用了策略模式和模板模式实现图片上传和搜索功能&#xff0c;能够在配置类中设置使用Oss或者minio上传图片&#xff0c;es或者mysql文章搜索。后续有新的上传方式或者搜索方式只需要编写对应的实现类即可&#xff…...

Ubuntu 22.04 安装系统 手动分区 针对只有一块硬盘 lvm 单独分出/home

自动安装的信息 参考自动安装时产生的分区信息 rootyeqiang-MS-7B23:~# fdisk /dev/sdb -l Disk /dev/sdb&#xff1a;894.25 GiB&#xff0c;960197124096 字节&#xff0c;1875385008 个扇区 Disk model: INTEL SSDSC2KB96 单元&#xff1a;扇区 / 1 * 512 512 字节 扇区大…...

Android系统定制之监听USB键盘来判断是否弹出软键盘

一.项目背景 在设备上弹出软键盘,会将一大部分UI遮挡起来,造成很多图标无法看到和点击,使用起来不方便,因此通过插入usb键盘输入代替软键盘,但是点击输入框默认会弹出软键盘,因此想要插入USB键盘时,默认关闭软键盘,拔出键盘时再弹出,方便用户使用 二.设计思路 2.1…...

LeakyReLU激活函数

nn.LeakyReLU 是PyTorch中的Leaky Rectified Linear Unit&#xff08;ReLU&#xff09;激活函数的实现。Leaky ReLU是一种修正线性单元&#xff0c;它在非负数部分保持线性&#xff0c;而在负数部分引入一个小的斜率&#xff08;通常是一个小的正数&#xff09;&#xff0c;以防…...

Qt单一应用实例判断

原本项目中使用QSharedMemory的方法来判断当前是否已存在运行的实例&#xff0c;但在MacOS上&#xff0c;当程序异常崩溃后&#xff0c;QSharedMemory没有被正常销毁&#xff0c;导致应用程序无法再次被打开。 对此&#xff0c;Qt assistant中有相关说明&#xff1a; 摘抄 qt-s…...

企业AI工程化之路:如何实现高效、低成本、高质量的落地?

MLOps工程实践 概述面临挑战目的内容简介读者对象专家推荐目录 写在末尾&#xff1a; 主页传送门&#xff1a;&#x1f4c0; 传送 概述 作为计算机科学的一个重要领域&#xff0c;机器学习也是目前人工智能领域非常活跃的分支之一。机器学习通过分析海量数据、总结规律&#x…...

最短路径专题8 交通枢纽 (Floyd求最短路 )

题目&#xff1a; 样例&#xff1a; 输入 4 5 2 0 1 1 0 2 5 0 3 3 1 2 2 2 3 4 0 2 输出 0 7 思路&#xff1a; 由题意&#xff0c;绘制了该城市的地图之后&#xff0c;由给出的 k 个编号作为起点&#xff0c;求该点到各个点之间的最短距离之和最小的点是哪个&#xff0c;并…...

文件扫描模块

文章目录 前言文件扫描模块设计初级扫描方案一实现单线程扫描整合扫描步骤 设计初级扫描方案二周期性扫描 总结 前言 我们这个模块考虑的是数据库里面的内容从哪里获取。 获取完成后&#xff0c;这时候,我们就需要把目录里面文件/子文件都获取出来,并存入数据库。 文件扫描模…...

MySQL之主从复制

概述&#xff1a; 将主库的数据 变更同步到从库&#xff0c;从而保证主库和从库数据一致。 它的作用是 数据备份&#xff0c;失败迁移&#xff0c;读写分离&#xff0c;降低单库读写压力 原理&#xff1a; 主服务器上面的任何修改都会保存在二进制日志&#xff08; Bin-log日志…...

[leetcode 单调栈] 901. 股票价格跨度 M

设计一个算法收集某些股票的每日报价&#xff0c;并返回该股票当日价格的 跨度 。 当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数&#xff08;从今天开始往回数&#xff0c;包括今天&#xff09;。 例如&#xff0c;如果未来 7 天股票的价格是 [100…...

Java线程池:并发编程的利器

Java线程池&#xff1a;并发编程的利器 在多任务、高并发的时代&#xff0c;Java并发编程显得尤为重要。其中&#xff0c;Java线程池是一种高效的管理线程的工具&#xff0c;能够提高应用程序的性能和响应速度。本文将深入探讨Java线程池的工作原理、应用场景以及简单示例&…...

ARM硬件断点

hw_breakpoint 是由处理器提供专门断点寄存器来保存一个地址&#xff0c;是需要处理器支持的。处理器在执行过程中会不断去匹配&#xff0c;当匹配上后则会产生中断。 内核自带了硬件断点的样例linux-3.16\samples\hw_breakpoint\data_breakpoint.c static void sample_hbp_h…...

Java使用WebSocket(基础)

准备一个html页面 <!DOCTYPE HTML> <html> <head><meta charset"UTF-8"><title>WebSocket Demo</title> </head> <body><input id"text" type"text" /><button onclick"send()&…...

图像处理与计算机视觉--第五章-图像分割-自适应阈值分割

文章目录 1.自适应阈值分割介绍2.自适应阈值函数参数解析3.高斯概率函数介绍4.自适应阈值分割核心代码5.自适应阈值分割效果展示6.参考文章及致谢 1.自适应阈值分割介绍 在图片处理过程中&#xff0c;针对铺前进行二值化等操作的时候&#xff0c;我们希望能够将图片相应区域内所…...

记一次问题排查

1785年&#xff0c;卡文迪许在实验中发现&#xff0c;把不含水蒸气、二氧化碳的空气除去氧气和氮气后&#xff0c;仍有很少量的残余气体存在。这种现象在当时并没有引起化学家的重视。 一百多年后&#xff0c;英国物理学家瑞利测定氮气的密度时&#xff0c;发现从空气里分离出来…...

阿里服务器可以做多少个网站/网站推广投放

DataTable da CommonBLL.GetList("*", "sys_dict", "IfState1 and DictTypeId14");string strField "CACCNUM as 账号账号,Loannumber as 借据号,BILLDATE as 借款时间,CAName as 借款人姓名,CPOSITION as 质押商品房位置,LoanAmount as …...

芜湖 网站建设/学生网页设计模板

前言 有很多时候&#xff0c;我们希望可以在C类里面对那些比较耗时的函数使用多线程技术&#xff0c;但是熟悉C对象语法的人应该知道&#xff0c;C类的成员函数的函数指针不能直接做为参数传到pthread_create,主要因为是C成员函数指针带有类命名空间&#xff0c;同时成员函数末…...

广东门户网站建设/淘宝推广平台

author&#xff1a;skate time&#xff1a;2012/05/24 数据结构设计&#xff08;表&#xff0c;索引的创建&#xff09;的说明 表的设计 1.数据类型的选择 1) 数字类型&#xff1a;分为整数数据类型和浮点数据类型&#xff0c;它们之间的区别是取值范围不同&#xff0c;存储…...

宁波seo博客/优化的定义

目录 1.安装NVIDIA驱动 1.1下载驱动 1.2卸载原来的 1.3禁用nouveau驱动 1.4禁用X-Window服务 1.5命令行安装 1.6测试 2.安装cuda 2.1下载cuda 2.2安装 2.3环境变量配置 2.4测试 3.安装cudnn 3.1下载cudnn 3.2安装 3.3下载runtime library,developer library,cod…...

以后做网站发展前途/宁波seo高级方法

来自&#xff1a;http://www.cnblogs.com/zhwl/archive/2012/06/06/2538494.html 对于iphone开发人员来说&#xff0c;内存管理是极为重要的技巧&#xff0c;哪怕程序的功能再强大&#xff0c;设计再漂亮&#xff0c;如果内存控制不好&#xff0c;也难逃程序莫名退出的噩运&…...

wordpress只显示文章标题摘要/百度云引擎搜索

我们知道SQL Server和Oracle其实很多原理都类似.特别是一些常用的SQL语句都是按照标准来.所以它们也可以有一定的互操作性的.这里讲一下,怎么配置让SQL Server连接一个Oracle.然后你在SQL Server中也能查看Oracle中表的内容. 我先说下我使用的环境: 操作系统: win7 64 ,SQL …...