<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>
threejs3D看车
于 2024-11-25 10:46:41 首次发布