threejs3D看车

<template>
  <div class="container"></div>
</template>

<script setup>
import { onMounted } from 'vue'
import {PerspectiveCamera, Scene,
  WebGLRenderer, AxesHelper, RectAreaLight,
  AmbientLight, PlaneGeometry,
  MeshPhysicalMaterial, DoubleSide, Mesh,
  SpotLight, CylinderGeometry
} from 'three';
// 轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper'
// 调色板
import GUI from 'lil-gui';
import TWEEN from '@tweenjs/tween.js';
import Lamborghini from '../assets/3d/Lamborghini.glb'
// 定义基本对象
let scene, camera, renderer, controls, doors = [], carStatus, rectLight1, rectLight2, rectLight3
// 车身材质
let bodyMaterial = new MeshPhysicalMaterial({
  color: "#6e2121",
  // 金属度
  metalness: 1,
  // 粗糙度
  roughness: 0.5,
  // 喷漆
  clearcoat: 1.0,
  // 漆的粗糙度
  clearcoatRoughness: 0.03,
});
// 玻璃材质
let glassMaterial = new MeshPhysicalMaterial({
  color: "#793e3e",
  // 金属度
  metalness: 0.25,
  // 粗糙度
  roughness: 0,
  // 透光性.transmission属性可以让一些很薄的透明表面,玻璃,变得更真实一些。
  transmission: 1.0
});

// 初始化一个场景
const initScene = () => {
  scene = new Scene()
  // 添加个坐标系
  const axesHelper = new AxesHelper(10);
  scene.add(axesHelper);
}

// 初始化相机
const initCamera = () => {
  camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
  // 设置相机的位置
  // camera.position.set(4.25, 1.4, -4.5);
  // 坦克500的
  camera.position.set(10, 10, -15);
}

// 初始化渲染器
const initRender = () => {
  //渲染器 antialias抗锯齿
  renderer = new WebGLRenderer({
    antialias: true
  })
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.querySelector('.container').appendChild(renderer.domElement)
}


// 初始化轨道控制器
const initOrbitControls = ()=> {
  controls = new OrbitControls(camera, renderer.domElement);
  // 开启阻尼,需要update更新
  controls.enableDamping = true

}

// 加载模型
const loadCarModal = () => {
  new GLTFLoader().load(Lamborghini, function (gltf) {
    // 加载车模型
    const carModel = gltf.scene
    // 设置模型展示的初始位置
    carModel.rotation.y = Math.PI
    // 配置车身颜色,three官网可以获取相关参数
    carModel.traverse(obj => {
      if (obj.name === 'Object_103' || obj.name === 'Object_64' || obj.name === 'Object_77') {
        // 车身
        obj.material = bodyMaterial
      } else if (obj.name === 'Object_90') {
        // 玻璃
        obj.material = glassMaterial
      } else if (obj.name === 'Empty001_16' || obj.name === 'Empty002_20') {
        // 门
        doors.push(obj)
      } else {
      }
      obj.castShadow = true;
    })
    // 模型添加到场景中,这个时候车模型已经可以出现在场景中了
    scene.add(carModel)
  })
}

// 添加光照效果
const initAmbientLight = () => {
  const ambientLight = new AmbientLight(0xffffff, 0.5)
  // 添加到场景中
  scene.add(ambientLight)
}
// 添加聚光灯,使地板有个反光效果
const initSpotLight = () => {
  // 添加头顶聚光灯 白色,强度1
  const bigSpotLight = new SpotLight("#ffffff", 10);
  //散射角度,跟水平线的夹角
  bigSpotLight.angle = Math.PI / 4
  // 聚光锥的半影衰减百分比
  bigSpotLight.penumbra = 0.1
  // 纵向:沿着光照距离的衰减量。
  bigSpotLight.decay = 1
  // 光最远照射到哪里
  bigSpotLight.distance = 30
  // 光影圆角
  bigSpotLight.shadow.radius = 20
  // 阴影映射宽度,阴影映射高度
  bigSpotLight.shadow.mapSize.set(4096, 4096)
  // 从哪个方向开始
  bigSpotLight.position.set(0, 5, 0)
  // 光照射到哪个方向
  bigSpotLight.target.position.set(0, 0, 0)
  // 光产生阴影
  bigSpotLight.castShadow = true
  scene.add(bigSpotLight);
}
// 初始化场景地板
const initFloor = () => {
  // 建任何一个物体,1是形状、2是材质
  const floorGeometry = new PlaneGeometry(25, 25)
  const material = new MeshPhysicalMaterial({
    // 双面展示
    side: DoubleSide,
    // 地板颜色
    color: 0x8ffffff,
    // 金属度
    metalness: 0,
    // 粗糙度
    roughness: 0.1
  })
  // 物体创建以后,就要初始化成mesh
  const floorMesh = new Mesh(floorGeometry, material)
  floorMesh.rotation.x = Math.PI / 2
  floorMesh.receiveShadow = true;
  scene.add(floorMesh)
}

// 创建一个圆柱,模拟展厅
const initCylinder = () => {
  // 上下半径为10, 高度为20, 侧面底面20分段数
  const geometry = new CylinderGeometry(10, 10, 20, 20)
  // 材质颜色,上下两面
  const material = new MeshPhysicalMaterial({
    color: 0x6c6c6c,
    side: DoubleSide
  })

  const cylinder = new Mesh(geometry, material)
  scene.add(cylinder)
}

// 控制不让其旋转缩放过度
const initController = () => {
  controls = new OrbitControls(camera, renderer.domElement)

  controls.enableDamping = true
  // 设置最大最小能到哪
  controls.maxDistance = 9
  controls.minDistance = 1
  // 设置最大最小的角度
  controls.minPolarAngle = 0
  controls.maxPolarAngle = 80 / 360 * 2 * Math.PI
}

// 设置调色面板
const initGUI = () => {
  const gui = new GUI()
  const obj = {
    bodyColor: '#6e2121',
    glassColor: '#aaaaaa',
    carOpen,
    carClose,
    carIn,
    carOut
  }
  gui.addColor(obj, 'bodyColor').name('外观颜色').onChange(val => {
    bodyMaterial.color.set(val)
  })
  gui.addColor(obj, 'glassColor').name('玻璃颜色').onChange(val => {
    glassMaterial.color.set(val)
  })
  gui.add(obj, "carOpen").name('打开车门')
  gui.add(obj, "carClose").name('关门车门')
  gui.add(obj, "carIn").name('车内视角')
  gui.add(obj, "carOut").name('车外视角')
}

// 开门
const carOpen = () => {
  carStatus = 'open'
  for (let i = 0; i < doors.length; i++) {
    // 可以替换y,横开门
    setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[i])
  }
}

//关门
const carClose = () => {
  carStatus = 'close'
  for (let i = 0; i < doors.length; i++) {
    setAnimationDoor({ x: 1 }, { x: 0 }, doors[i])
  }
}

// 车内视角
const carIn = () => {
  // c是相机的偏移、o是物体的偏移
  setAnimationCamera({ cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 }, { cx: -0.27, cy: 0.83, cz: 0.60, ox: 0, oy: 0.5, oz: -3 });
}

// 车外视角
const carOut = () => {
  setAnimationCamera({ cx: -0.27, cy: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3 }, { cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 });
}

// 设置动画模式
const setAnimationDoor = (start, end, mesh) => {
  // TWEEN.Easing.Quadratic.Out动画模式
  const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
  console.log(tween)
  tween.onUpdate((that) => {
    mesh.rotation.x = that.x
  })
  tween.start()
}
// 设置动画模式
const setAnimationCamera = (start, end) => {
  const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
    //  camera.postition  和 controls.target 一起使用
    camera.position.set(that.cx, that.cy, that.cz)
    controls.target.set(that.ox, that.oy, that.oz)
  })
  tween.start()
}

// 创建三色光源
const initMutilColor = () => {
  rectLight1 = new RectAreaLight(0xff0000, 50, 1, 10);
  rectLight1.position.set(15, 10, 15);
  rectLight1.rotation.x = -Math.PI / 2
  rectLight1.rotation.z = -Math.PI / 4
  scene.add(rectLight1);

  rectLight2 = new RectAreaLight(0x00ff00, 50, 1, 10);
  rectLight2.position.set(13, 10, 13);
  rectLight2.rotation.x = -Math.PI / 2
  rectLight2.rotation.z = -Math.PI / 4
  scene.add(rectLight2);

  rectLight3 = new RectAreaLight(0x0000ff, 50, 1, 10);
  rectLight3.position.set(11, 10, 11);
  rectLight3.rotation.x = -Math.PI / 2
  rectLight3.rotation.z = -Math.PI / 4
  scene.add(rectLight3);

  scene.add(new RectAreaLightHelper(rectLight1));
  scene.add(new RectAreaLightHelper(rectLight2));
  scene.add(new RectAreaLightHelper(rectLight3));

  startColorAnim()
}

// 灯光动画
const startColorAnim = () => {
  const carTween = new TWEEN.Tween({ x: -5 }).to({ x: 25 }, 2000).easing(TWEEN.Easing.Quadratic.Out);
  carTween.onUpdate(function (that) {
    rectLight1.position.set(15 - that.x, 10, 15 - that.x)
    rectLight2.position.set(13 - that.x, 10, 13 - that.x)
    rectLight3.position.set(11 - that.x, 10, 11 - that.x)
  });
  carTween.onComplete(function () {
    rectLight1.position.set(-15, 10, 15);
    rectLight2.position.set(-13, 10, 13);
    rectLight3.position.set(-11, 10, 11);

    rectLight1.rotation.z = Math.PI / 4
    rectLight2.rotation.z = Math.PI / 4
    rectLight3.rotation.z = Math.PI / 4
  })
  carTween.repeat(10)


  const carTween2 = new TWEEN.Tween({ x: -5 }).to({ x: 25 }, 2000).easing(TWEEN.Easing.Quadratic.Out);
  carTween2.onUpdate(function (that) {
    rectLight1.position.set(-15 + that.x, 10, 15 - that.x)
    rectLight2.position.set(-13 + that.x, 10, 13 - that.x)
    rectLight3.position.set(-11 + that.x, 10, 11 - that.x)
  });
  carTween2.onComplete(function (that) {
    rectLight1.position.set(15, 10, 15);
    rectLight2.position.set(13, 10, 13);
    rectLight3.position.set(11, 10, 11);
    rectLight1.rotation.z = - Math.PI / 4
    rectLight2.rotation.z = - Math.PI / 4
    rectLight3.rotation.z = - Math.PI / 4
  })

  carTween.start();
}

// 全量初始化
const init = () => {
  initScene()
  initCamera()
  initRender()
  initOrbitControls()
  loadCarModal()
  initAmbientLight()
  initFloor()
  initSpotLight()
  initCylinder()
  initController()
  initGUI()
  initMutilColor()
}

// 进行渲染
const render = (time) => {
  renderer.render(scene, camera)
  requestAnimationFrame(render)
  // 使用动画的时候,要记得更新动画和控制器
  TWEEN.update(time)
  controls.update()
}
onMounted(() => {
  init()
  render()
  window.addEventListener('resize', function () {
    // 相机的宽高比重新设置
    camera.aspect = window.innerWidth / window.innerHeight
    // 更新相机的投影矩阵
    camera.updateProjectionMatrix()
    // 渲染器重新计算画布大小
    renderer.setSize(window.innerWidth, window.innerHeight)
  })
})
</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值