Three.js湖边小屋,包含gltf渲染、天空和水纹、光照阴影、运动的点光源、相机位置和文字切屏、粒子效果等

前期准备

使用vue3+vite+three.js+gsap 开发

bash 复制代码
npm install three gsap

代码

javascript 复制代码
<script setup>
// 导入three.js
import * as THREE from 'three';
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 加载模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
// 解压缩库
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'
// 引入官方水效果
import { Water } from 'three/examples/jsm/objects/Water2.js';
// 导入补间动画库
import gsap from 'gsap';
import { ref, onMounted } from 'vue';
// 创建场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
    45, // 视角
    window.innerWidth / window.innerHeight, // 宽高比
    0.1, // 近平面
    1000 // 远平面
);
camera.position.set(-3.23,2.98,4.06);
camera.updateProjectionMatrix();
// 初始化渲染器
const renderer = new THREE.WebGLRenderer(
  //设置抗锯齿
  { antialias: true } 
);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 设置色调映射
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 0.5
renderer.shadowMap.enabled = true
// 调节亮度
renderer.toneMappingExposure = 1

// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 初始化loader
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
// 初始化gltfLoader
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
// 加载环境纹理
let rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/sky.hdr',(texture)=>{
  texture.mapping = THREE.EquiretangularReflectiionMapping;
  scene.background = texture
  scene.environment = texture
})
gltfLoader.load("./model/scene.glb",(gltf)=>{
  const model = gltf.scene;
  model.traverse((child)=>{
    if(child.name = "Plane"){
     child.visible = false
    }
    if(child.isMesh){
      child.castShadow = true
      child.receiveShadow = true
    }
  })
  scene.add(model)
})

// 添加水效果
const waterGeometry = new THREE.CircleGemoetry(300,32)
const water = new Water(
  waterGeometry,
  {
    // 数值越大效果越明显
    textureWidth: 2048,
    textureHeight: 2048,
    color:0xeeeeff,
    flowDirection: new THREE.Vector2(1,1),
    scale:2,
  }
)
// 设置水的旋转,否则导致房子浸泡在水中
water.rotation.x = -Math.PI / 2
// 设置水的位置
water.position.y = -1
scene.add(water)

// 加载模型
gltfLoader.load(
  'models/scene.gltf',
  (gltf) => {
    scene.add(gltf.scene);
    // 设置模型位置
    gltf.scene.position.set(0,0,0);
    // 设置模型大小
    gltf.scene.scale.set(0.01,0.01,0.01);
    // 设置模型旋转
    gltf.scene.rotation.set(0,0,0);
  },
  (xhr) => {
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
  },
  (error) => {
    console.log('error!', error);
  }
);

// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 50, 0);
scene.add(light);
// 添加点光源
const pointLight = new THREE.PointLight(0xffffff, 100);
pointLight.position.set(0.1,2.4, 0);
pointLight.castShadow = true;
scene.add(pointLight);
// 创建点光源组
const pointLightGroup = new THREE.Group();
pointLightGroup.position.set(-8,2.5,-1.5);
// 球绕圆的半径
let radius = 3;
let pointLightArr = [];
for(let i = 0; i < 3; i++){
  // 创建点光源
  const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
  const sphereMaterial = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0xffffff,
    emissiveIntensity: 10,
    roughness: 0.5,
    metalness: 0.5
  })
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  sphere.position.set(radius * Math.cos(i * 120 * Math.PI / 180), Math.cos((i * 120 * Math.PI) / 180), radius * Math.sin(i * 120 * Math.PI / 180));

  // 添加到点光源组
  pointLightGroup.add(sphere);

  const pointLight = new THREE.PointLight(0xffffff, 1);
  sphere.add(pointLight);
    // 将点光源添加到点光源组
  pointLightGroup.add(sphere);
}
scene.add(pointLightGroup);
function render(){
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  controls.update();
}

// 使用补间动画,从0到2pi,使灯泡旋转
let options = {
  angle:0
}
gsap.to(options,{
  angle:Math.PI * 2,
  duration:10,
  repeat:-1,
  ease:"linear",
  onUpdate:()=>{
    pointLightGroup.rotation.y = options.angle
    pointLightArr.forEach((item,index)=>{
      item.position.set(radius * Math.cos((index * 120) * Math.PI / 180), Math.cos(((index * 120) * Math.PI) / 180), radius * Math.sin((index * 120) * Math.PI / 180));
    })
  }
})
render();

// 使用补间动画,移动相机
let timeLine1 = gsap.timeline();
let timeline2 = gsap.timeline();

// 定义相机移动函数
function translateCamera(position,target){
  timeLine1.to(camera.position,{
    x:position.x,
    y:position.y,
    z:position.z,
    duration:2,
    ease:"power2.inOut"
  })

  timeline2.to(camera.position,{
    x:target.x,
    y:target.y,
    z:target.z,
    duration:2,
    ease:"power2.inOut"
  })
}

let words = [
  {
    text:"Attendre et espérer !",
    callback:()=>{
      new THREE.Vector3(-3.23,2.98,4.06),
      new THREE.Vector3(-8, 2, 0)
    },
  },
  {
    text:"Attendre et espérer !!",
    callback:()=>{
      new THREE.Vector3(7,0,23),
      new THREE.Vector3(0,0,0)
    },
  },
  {
    text:"Attendre et espérer !!!",
    callback:()=>{
      new THREE.Vector3(10,3,0),
      new THREE.Vector3(5,2,0)
    },
  },
  {
    text:"Attendre et espérer !!!!",
    callback:()=>{
      new THREE.Vector3(7,0,23),
      new THREE.Vector3(0,0,0)
    },
  },
  {
    text:"Attendre et espérer !!!!!",
    callback:()=>{
      new THREE.Vector3(-20,1.3,6.6),
      new THREE.Vector3(5,2,0)
      // 调用画爱心函数
      makeHeart()
    },
  },
]

let index = ref(0)
let isAnimate = false
// 监听鼠标滚轮事件
window.addEventListener('wheel',(e)=>{
  // 操作节流
  if(isAnimate){
    return
  }
  if(e.deltaY > 0){
    index.value++
    if(index.value > words.length - 1){
      index.value = 0
    }
  }
  words[index.value].callback()
  setTimeout(() => {
    isAnimate = false
  }, 1000);
})

// 实例化星星
let starsInstance = new THREE.InstanceMesh(
  new THREE.SphereGeometry(0.1,32,32),
  new THREE.MeshStandardMaterial({
    color:0xffffff,
    emissive:0xffffff,
    emissiveIntensity:10,
    roughness:0.5,
    metalness:0.5
  }),
  100
);

// 随机分布星星
let starsArr = []
let endArr = []
for(let i = 0; i < 100; i++){
  let x = Math.random() * 100 - 50
  let y = Math.random() * 100 - 50
  let z = Math.random() * 100 - 50
  starsArr.push(new THREE.Vector3(x,y,z))
  // 矩阵修改位移
  let matrix = new THREE.Matrix4()
  matrix.setPosition(x,y,z)
  starsInstance.setMatrixAt(i,matrix)
}
scene.add(starsInstance)

// 使用贝塞尔曲线,实现星星移动
let heartShape = new THREE.Shape()
heartShape.moveTo(25,25)
heartShape.bezierCurveTo(25,25,20,0,0,0)
heartShape.bezierCurveTo(-30,0,-30,35,-30,35)
heartShape.bezierCurveTo(-30,55,-10,77,25,95)
heartShape.bezierCurveTo(60,77,80,55,80,35)
heartShape.bezierCurveTo(80,35,80,0,50,0)
heartShape.bezierCurveTo(35,0,25,25,25,25)

//根据路径获取点
for(let i = 0; i < 100; i++){
  let point = heartShape.getPointAt(i / 100)
  endArr.push(new THREE.Vector3(point.x,point.y,point.z))
}

// 创建动画
function makeHeart(){
  let params = {
    time:0
  }

  gsap.to(params,{
    time:1,
    duration:10,
    ease:"linear",
    onUpdate:()=>{
      for(let i = 0; i < 100; i++){
        let x = THREE.MathUtils.lerp(starsArr[i].x,endArr[i].x,params.time)
        let y = THREE.MathUtils.lerp(starsArr[i].y,endArr[i].y,params.time)
        let z = THREE.MathUtils.lerp(starsArr[i].z,endArr[i].z,params.time)
        let matrix = new THREE.Matrix4()
        matrix.setPosition(x,y,z)
        starsInstance.setMatrixAt(i,matrix)
      }
      starsInstance.instanceMatrix.needsUpdate = true
    }
  })
}
</script>
<template>
<div class="words" style="position:fixed;left: 0;top:0;z-index:10;pointer-events: none;transition: all 1s;" :style="{
    transform:`translate(${index.value * -100}vw,0)`
  }"> 
  <div v-for="item in words" style="width: 100vw;height: 100vh;">
    <h1 style="padding: 100px 50px;font-size:50px;color:#fff">{{item.text}}</h1>
  </div>
</div>
</template>
<style scoped>
*{
  margin:0;
  padding:0;
}
canvas{
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}
</style>

效果

相关推荐
小阮的学习笔记10 分钟前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜10 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=11 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
杨荧13 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰19 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
王俊山IT32 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。34 分钟前
c++多线程
java·开发语言
小政爱学习!36 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。41 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui