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>

效果

相关推荐
joan_85几秒前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
程序猿进阶几秒前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618443 分钟前
shell 编程(二)
开发语言·bash·shell
charlie11451419117 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满18 分钟前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
还是大剑师兰特23 分钟前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
ELI_He99924 分钟前
PHP中替换某个包或某个类
开发语言·php
m0_7482361131 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
倔强的石头10639 分钟前
【C++指南】类和对象(九):内部类
开发语言·c++
Watermelo61744 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript