three.js demo

1、一个简单的 demo

在这里插入图片描述

import * as THREE from 'three'
import { PerspectiveCamera, AxesHelper, Scene, WebGLRenderer, TextureLoader, BoxGeometry, MeshBasicMaterial  } from "three"
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { Tween, Easing } from '@tweenjs/tween.js'
import bgc from './public/bgc.jpg'
 
let scene, camera, renderer, controls, mesh, tween;

// 初始化场景
function initScene() {
  scene = new Scene()
}

// 初始化相机
function initCamera() {
  // PerspectiveCamera 参数:相机角度、显示区的宽高比例,最近距离,最晚距离(最近最远距离,就是要显示 0.1 - 1000 这中间的事物)
  camera = new PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000)
  camera.position.z = 8
}

// 初始化渲染器
function initRenderer() {
  renderer = new WebGLRenderer({
    antialias: true
  })
  // 设置渲染画布大小
  renderer.setSize(window.innerWidth, window.innerHeight)
  // 将渲染画布元素插入 body 中
  document.body.appendChild(renderer.domElement)
}

// 初始化坐标系
function initAxesHelper() {
  const axesHelper = new AxesHelper(5)
  scene.add(axesHelper)
}

// 初始化轨道控制器(可以使页面场景进行360度旋转)
function initOrbitControls() {
  controls = new OrbitControls(camera, renderer.domElement)
}

// 初始化一个物体
function initMesh() {
  // 形状
  const geometry = new BoxGeometry(1,1,1)
  // 纹理
  const texture = new TextureLoader().load(bgc)
  // 材质
  const material = new MeshBasicMaterial({
    // color: 'red',
    map: texture
  })
  mesh = new THREE.Mesh(geometry, material)
  scene.add(mesh)
}

function init() {
  initScene()
  initCamera()
  initRenderer()
  initAxesHelper()
  initOrbitControls()
  initMesh()

  const coords = {x: 0} // Start at (0, 0)

	tween = new Tween(coords, false) // Create a new tween that modifies 'coords'.
		.to({x: 3}, 3000) // Move to (300, 200) in 1 second.
		.easing(Easing.Quadratic.InOut) // Use an easing function to make the animation smooth.
		.onUpdate((that) => {
      mesh.position.x = that.x
    })
		.start() // Start the tween immediately.
}

init()

// 动画循环
function render(time) {
  // 将场景和摄像机传入渲染器中,将摄影家视椎堆中的三维场景渲染成一个二维图片显示在画布上
  renderer.render(scene, camera)
  // requestAnimationFrame 浏览器每次刷新以后的回调
  requestAnimationFrame(render)

  tween.update(time)
}
render()

window.addEventListener('resize', () => {
  // 重新设置摄像机照射显示区的宽高比例
  camera.aspect = window.innerWidth / window.innerHeight
  // 更新摄像机
  camera.updateProjectionMatrix()
  // 重新设置渲染画布
  renderer.setSize(window.innerWidth, window.innerHeight)
})

2、3D汽车展厅

在这里插入图片描述

import { 
  PerspectiveCamera, 
  GridHelper, 
  Scene, 
  WebGLRenderer, 
  AmbientLight, 
  PlaneGeometry, 
  MeshPhysicalMaterial, 
  DoubleSide,
  Mesh,
  SpotLight,
  CylinderGeometry,
  Vector2,
  Raycaster
} from "three"
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import GUI from 'lil-gui';
import { Tween, Easing } from '@tweenjs/tween.js'
import Lamborghini from "./public/20221017143009_parent_directory_货车04.gltf"
// import Lamborghini from "./public/20221017150616_parent_directory_轿车22.gltf"
// import Lamborghini from "./public/JXL.glb"
// import Lamborghini from "./public/20230104165309_parent_directory_轿车27.gltf"
 
let scene, camera, renderer, controls, grid, tween, doorStatus = 'close';
let doors = []
// 车身材质
let bodyMaterial = new MeshPhysicalMaterial({
  color: "#d4e4fd",
  metalness: 1,
  roughness: 0.5,
  clearcoat: 1,
  clearcoatRoughness: 0.03
})
// 玻璃材质
let glassMaterial = new MeshPhysicalMaterial({
  color: "#4b4949",
  metalness: 0.25,
  roughness: 0,
  transmission: 1 //  透光性,transmission 属性可以使用在 玻璃 表面,可以变得更真实
})

// 初始化场景
function initScene() {
  scene = new Scene()
}

// 初始化相机
function initCamera() {
  // PerspectiveCamera 参数:相机角度、显示区的宽高比例,最近距离,最晚距离(最近最远距离,就是要显示 0.1 - 1000 这中间的事物)
  camera = new PerspectiveCamera(40, window.innerWidth/window.innerHeight, 0.1, 1000)
  // camera.position.z = 8
  // 设置相机 x,y,z 坐标
  camera.position.set(4.25, 1.4, -4.5)
}

// 初始化渲染器
function initRenderer() {
  renderer = new WebGLRenderer({
    antialias: true
  })
  // 设置渲染画布大小
  renderer.setSize(window.innerWidth, window.innerHeight)
  // 渲染器支持阴影
  renderer.shadowMap.enabled = true
  // 将渲染画布元素插入 body 中
  document.body.appendChild(renderer.domElement)
}

// 初始化轨道控制器(可以使页面场景进行360度旋转)
function initOrbitControls() {
  controls = new OrbitControls(camera, renderer.domElement)
  // 鼠标拖拽动画平滑(或者说动画惯性),需配合 controls.update() 使用
  controls.enableDamping = true

  // //相机位置与观察目标点最小值
  // controls.minDistance = 9;
  // //相机位置与观察目标点最大值
  // controls.maxDistance = 1;

  // 上下旋转范围
  controls.minPolarAngle = 0;
  controls.maxPolarAngle = 80 / 360 * 2 * Math.PI;
}

// 初始化网格
function initGridHelper() {
  // GridHelper 参数:网格宽度,等分数,中心线颜色,网格线颜色
  grid = new GridHelper(20, 40, 'red', 0xffffff)
  grid.material.opacity = 0.5
  grid.material.transparent = true
  scene.add(grid)
}

// 初始化一个模型
function loadCarModel() {
  new GLTFLoader().load(Lamborghini, (gltf) => {
    const carModel = gltf.scene
    console.log(carModel)
    // carModel.rotation.y = 1.6
    carModel.rotation.y = Math.PI
    // gltf.scene.scale.set(0.8, 0.8, 0.8)

    carModel.traverse(obj => {
      if(obj.name === 'che001_1' || obj.name === 'che001_4' || obj.name === 'che001_2' || obj.name === 'che001_6' || obj.name === 'che001_7') {
        // 设置车身
        obj.material = bodyMaterial

      } 
      if (obj.name === 'che001_10' || obj.name === 'che001_11' || obj.name === 'che001_12' || obj.name === 'che001_13') {
        // 设置玻璃
        obj.material = glassMaterial
      } 
      if(obj.name === 'che001_2') {
        doors.push(obj)
      }
      // 车模型产生阴影
      obj.castShadow = true
    })

    scene.add(carModel)
  })
}

// 初始化环境光
function initAmbientLight() {
  // AmbientLight 参数:灯光颜色, 灯光强度
  const ambientLight = new AmbientLight('#fff', 1)
  scene.add(ambientLight)
}

// 初始化地板
function initFloor() {
  // 实例化一个地板,参数:长,宽
  const floorGeometry = new PlaneGeometry(20, 20)
  // 地板材质
  const material = new MeshPhysicalMaterial({
    // 双面绘制
    side: DoubleSide,
    color: 0x808080,
    // 金属度:0 非金属,1 金属
    metalness: 0,
    // 粗糙度,数值越小越光滑
    roughness: 0.1,
  })
  const mesh = new Mesh(floorGeometry, material)
  // 旋转 180 度
  mesh.rotation.x = Math.PI / 2
  // 地板接收阴影
  mesh.receiveShadow = true
  scene.add(mesh)
}

// 初始化聚光灯
function initSpotLight() {
  // SpotLight 参数:光线颜色,光线强度
  const spotLight = new SpotLight(0xffffff, 800);
  // 散射角度,跟水平线的夹角
  spotLight.angle = Math.PI / 8
  // 横向:聚光锥的半影衰减百分比
  spotLight.penumbra = 0.2
  // 纵向:沿着光照距离的衰减量
  spotLight.decay = 2
  // 灯的距离
  spotLight.distance = 90
  spotLight.shadow.radius = 10
  // 阴影映射宽度,阴影映射高度
  spotLight.shadow.mapSize.set(4096, 4096)
  // 聚光灯位置调整
  spotLight.position.set(-5, 10, 1)
  // 光照射的方向
  spotLight.target.position.set(0,0,0)
  // 是否有投影阴影
  spotLight.castShadow = true
  scene.add(spotLight)
}

// 初始化圆柱体 - 模拟展厅
function initCylinder() {
  // CylinderGeometry 参数:上面的半径,下面的半径,xxx,xxx
  const geometry = new CylinderGeometry(12,12,20,32)
  const material = new MeshPhysicalMaterial({
    color: 0x6c6c6c,
    side: DoubleSide
  })
  const cylinder = new Mesh(geometry, material)
  scene.add(cylinder)
}

// 初始化gui
function initGui() {
  const gui = new GUI();
  let obj = {
    bodyColor: '#d4e4fd',
    glassColor: '#4b4949',
    doorOpen,
    doorClose,
    carIn,
    carOut
  }
  
  gui.addColor( obj, 'bodyColor').name('车身颜色').onChange(value => {
    // console.log('bodyColor')
    bodyMaterial.color.set(value)
  });
  gui.addColor( obj, 'glassColor').name('车窗颜色').onChange(value => {
    glassMaterial.color.set(value)
  });
  gui.add(obj, 'doorOpen').name('打开车门')
  gui.add(obj, 'doorClose').name('关闭车门')
  gui.add(obj, 'carIn').name('车内视角')
  gui.add(obj, 'carOut').name('车外视角')
}
// 移出轮胎
function doorOpen() {
  for(let index=0; index < doors.length; index ++) {
    setAnimationDoor({y: 0}, { y: Math.PI / 3 }, doors[index])
    // setAnimationDoor({y: 0}, { y: 1 }, doors[index])
  }
  doorStatus = 'open'

}
// 移出轮胎
function doorClose() {
  for(let index=0; index < doors.length; index ++) {
    setAnimationDoor({ y: Math.PI / 3 }, {y: 0}, doors[index])
    // setAnimationDoor({ y: 1 }, {y: 0}, doors[index])
  }
  doorStatus = 'close'
}
function setAnimationDoor(start, end, mesh) {
  tween = new Tween(start) // Create a new tween that modifies 'coords'.
		.to(end, 1000) // Move to (300, 200) in 1 second.
		.easing(Easing.Quadratic.InOut) // Use an easing function to make the animation smooth.
		.onUpdate((that) => {
      mesh.rotation.y = that.y
      // mesh.position.y = that.y
    })
		.start() // Start the tween immediately.
}
// 车内视角
function carIn() {
  setAnimationCamera({cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0}, {cx: -0.27, ct: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3})
}
// 车外视角
function carOut() {
  setAnimationCamera({cx: -0.27, ct: 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})
}
function setAnimationCamera(start, end) {
  // 车内外视角其实就是通过挪动摄像机和控制器,将视角转入
  tween = new Tween(start) // Create a new tween that modifies 'coords'.
		.to(end, 3000) // Move to (300, 200) in 1 second.
		.easing(Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
		.onUpdate((that) => {
      camera.position.set(that.cx, that.cy, that.cz)
      controls.target.set(that.ox, that.oy, that.oz)
    })
		.start() // Start the tween immediately.
}

function init() {
  initScene()
  initCamera()
  initRenderer()
  initOrbitControls()
  // initGridHelper()
  loadCarModel()
  initAmbientLight()
  initFloor()
  initSpotLight()
  initCylinder()
  initGui()
}

init()

// 动画循环
function render(time) {
  // 将场景和摄像机传入渲染器中,将摄影家视椎堆中的三维场景渲染成一个二维图片显示在画布上
  renderer.render(scene, camera)
  // requestAnimationFrame 浏览器每次刷新以后的回调
  requestAnimationFrame(render)

  controls.update()
  tween && tween.update(time)

  // console.log('render')
}
render()

window.addEventListener('resize', () => {
  // 重新设置摄像机照射显示区的宽高比例
  camera.aspect = window.innerWidth / window.innerHeight
  // 更新摄像机
  camera.updateProjectionMatrix()
  // 重新设置渲染画布
  renderer.setSize(window.innerWidth, window.innerHeight)
})

window.addEventListener('click', onPointClick)
// 处理模型上的点击事件
function onPointClick(event) {
  let pointer = {}
  // 根据页面上鼠标点击的位置,计算世界坐标
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1
  pointer.y = (event.clientY / window.innerHeight) * 2 - 1

  let vector = new Vector2(pointer.x, pointer.y)
  // 光线投射类,用于鼠标交互,可以捕捉到穿过了什么物体
  let raycaster = new Raycaster()
  raycaster.setFromCamera(vector, camera)
  let intersects = raycaster.intersectObjects(scene.children)

  intersects.forEach(item => {
    console.log(item.object)
    if(item.object.name === 'che001_2') {
      if(doorStatus === 'close') {
        doorOpen()
      } else {
        doorClose()
      }
      console.log(intersects)
    }
  })

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值