当前位置: 首页 > news >正文

Threejs案例实践笔记

目录

  • 1. 圣诞贺卡
    • 1.1. 项目搭建
    • 1.2. 水面与天空
    • 1.3. 场景光源
    • 1.4. 相机与文字
    • 1.5. 星与心
    • 1.6. 代码奉上
      • 1.6.1. 最终效果
      • 1.6.2. 我的代码
      • 1.6.3. 老陈打码
  • 2. 汽车展示
    • 2.1. 项目搭建
    • 2.2. 模型与灯光
    • 2.3. 车身材质
    • 2.4. 材质切换
    • 2.5. 源码奉上
      • 2.5.1.我的代码
      • 2.5.2.老陈打码

1. 圣诞贺卡

1.1. 项目搭建

  • 安装库

    1. 安装threejs

      npm install three 或 yarn add three 或 pnpm install three
      
    2. 安装gsap

      npm install gsap 或 yarn add gsap 或 pnpm install gsap
      
  • 项目初始化

    准备:

    1. 准备场景模型scene.glb,并放置相应目录

    2. 准备解压器文件夹draco,并放置相应目录

    3. 初始代码

      • 导入依赖,定义变量关联元素

        import { ref, onMounted } from 'vue'
        import * as THREE from "three"
        import gsap from 'gsap' // 导入动画gsap
        import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"  // 轨道控制器
        import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 导入Draco加载器
        import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 导入gltf加载器
        const demo = ref(null)
        
      • 静态页面配置

        <template><div ref="demo" />
        </template><style scoped>
        * {margin: 0;padding: 0;
        }canvas {width: 100%;height: 100%;position: fixed;left: 0;top: 0;
        }
        </style>
        
      • threejs初始化配置

        // 1. --- 创建场景 ---
        const scene = new THREE.Scene()// 2. --- 创建相机 ---
        const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
        )
        camera.position.set(-3.23, 2.98, 4) // 相机位置
        camera.updateProjectionMatrix()// 3. --- 创建渲染器 ---
        const renderer = new THREE.WebGLRenderer({antialias: true, // 开启抗锯齿
        })
        renderer.setSize(window.innerWidth, window.innerHeight)
        demo.value.appendChild(renderer.domElement)// 4. --- 轨道控制器 ---
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true // 设置阻尼
        controls.dampingFactor = 0.05// 5. --- 模型加载 ---
        const dracoLoader = new DRACOLoader() // 实例化draco加载器
        dracoLoader.setDecoderPath("./draco/") // 设置draco解压器路径
        const gltfLoader = new GLTFLoader() // 实例化gltf加载器
        gltfLoader.setDRACOLoader(dracoLoader) // gltf加载器关联draco解压器
        gltfLoader.load("../public/model/scene.glb",(gltf) => scene.add(gltf.scene)
        )// 6. --- 添加光源 ---
        const light = new THREE.DirectionalLight(0xffffff, 1)
        light.position.set(0, 50, 0)
        scene.add(light)// 7. --- 渲染 ---
        const render = () => {requestAnimationFrame(render) // 请求动画帧renderer.render(scene, camera)controls.update()
        }
        render()// 窗口大小调整
        window.addEventListener("resize", () => {renderer.setSize(window.innerWidth, window.innerHeight)camera.aspect = window.innerWidth / window.innerHeightcamera.updateProjectionMatrix()
        })onMounted(() => {demo.value.appendChild(renderer.domElement)
        })
        

        效果图:

        在这里插入图片描述

1.2. 水面与天空

  • 准备环境纹理贴图sky.hdr,并放置相应目录

  • 加载环境纹理

    import { RGBELoader } from 'three/examples/jsm/Addons.js' // 导入加载器
    ...
    // 加载环境纹理
    const rgbeLoader = new RGBELoader()
    rgbeLoader.load('/texture/hdr/sky.hdr',(texture) => {texture.mapping = THREE.EquirectangularReflectionMapping  // 设置纹理映射scene.background = texturescene.environment = texture},
    )
    // 设置色调映射
    renderer.toneMapping = THREE.ACESFilmicToneMapping
    controls.enableDamping = true
    

    效果图:

    在这里插入图片描述

  • 设置水面效果

    import { Water } from 'three/examples/jsm/objects/Water2.js' // 导入自带水面库
    ...
    // 设置水面效果
    const waterGeometry = new THREE.CircleGeometry(300, 32)
    const water = new Water(waterGeometry, {textureWidth: 1024,textureHeight: 1024,color: 0xeeeeff,flowDirection: new THREE.Vector2(1, 1), //  waves directionscale: 100, //  wavesize
    })
    water.rotation.x = -Math.PI / 2
    water.position.y = -0.4
    scene.add(water)
    

    效果图:

    在这里插入图片描述

1.3. 场景光源

  • 给小木屋添加点光源

    注意开启阴影要对渲染器、物体、光源分别进行设置

    // 设置色调映射
    ...
    renderer.shadowMap.enabled = true;  // 1.渲染器开启阴影
    renderer.physicallyCorrectLights = true;
    ...
    gltfLoader.load("/model/scene.glb",(gltf) => {const model = gltf.scenemodel.traverse((child) => {...// 2.物体允许接收后投射阴影if (child.isMesh) {child.castShadow = true;child.receiveShadow = true;}})...})// 点光源
    const pointLight = new THREE.PointLight(0xffffff, 10)
    pointLight.position.set(0.1, 2.4, 0)
    pointLight.castShadow = true // 3.点光源开启阴影
    scene.add(pointLight)
    

    效果图:

    在这里插入图片描述

  • 循环创建发光小球并绑定点光源,组成点光源组

    // 创建点光源
    const pointLight = new THREE.PointLight(0xffffff, 50)
    pointLight.position.set(0.1, 2.4, 0)
    pointLight.castShadow = true// 创建点光源组
    const pointLightGroup = new THREE.Group()
    pointLightGroup.position.set(-8, 2.5, -1.5)
    const radius = 3
    const pointLightList = []
    for (let i = 0; i < 3; i++) {// 创建球体用于绑定光源 const sphereGeometry = new THREE.SphereGeometry(0.2, 32, 32)const sphereMaterial = new THREE.MeshStandardMaterial({color: 0xffffff,emissive: 0xffffff,emissiveIntensity: 10 // 亮度})const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)pointLightList.push(sphere);sphere.position.set(radius * Math.cos(i * 2 * Math.PI / 3),Math.cos(i * 2 * Math.PI / 3),radius * Math.sin(i * 2 * Math.PI / 3),)const pointLight = new THREE.PointLight(0xffffff, 50)sphere.add(pointLight) // 绑定点光源pointLightGroup.add(sphere)
    }
    scene.add(pointLightGroup)
    

    这里来解释这段代码:

    • 代码:radius * Math.cos(i * 2 * Math.PI / 3)

      圆为2π( 即2 * Math.PI),我们有三个小球,那么每个小球就应该相隔 (2 / 3)π, 那么每个小球的位置应该是i * (2 /3)π,即分别为0,(2 / 3)π ,(4 /3)* π

    • pointLightList是干啥用的?:

      这里只是往pointLightList添加小球,小球绑定了点光源,那么后面的gsap动画就直接操作小球就可以了(这里threejs应该在底层做了优化, 不然 pointLightList.push(sphere),应该写在最后一行的)

      在这里插入图片描述

  • 使点光源组运动

    // 使用补间函数,从0到2π,使小球旋转运动
    const options = {angle: 0,
    };
    gsap.to(options, {angle: Math.PI * 2,duration: 10,repeat: -1,ease: "linear", // 这里要用双引号,不然要报错onUpdate: () => {pointLightGroup.rotation.y = options.angle;// 让球上下摆动pointLightList.forEach((item, index) => {item.position.set(radius * Math.cos(index * 2 * Math.PI / 3),Math.cos(index * 2 * Math.PI / 3 + options.angle * 5),radius * Math.sin(index * 2 * Math.PI / 3))});},
    })
    

    效果图:

    光源组运动

1.4. 相机与文字

  • 监听鼠标滚轮事件

    const index = ref(0)
    window.addEventListener('wheel', (e) => {if (e.deltaY > 0) {index.value++if (index.value >= scenes.length) {index.value = 0}}scenes[index.value].callback()
    },false
    )
    
  • 利用数组存储数据即交互逻辑

    const scenes = [{text: '《认真的白学》',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(-3.23, 3, 4.06),new THREE.Vector3(-8, 2, 0))console.log(111);}}, {text: '学下得那么深',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(7, 0, 23),new THREE.Vector3(0, 0, 0))console.log(222);}}, {text: '学得那么认真',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(10, 3, 0),new THREE.Vector3(5, 2, 0))console.log(333);}}, {text: '倒映出我躺在学中的伤痕',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(7, 0, 23),new THREE.Vector3(0, 0, 0))console.log(444);}}, {text: '完',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(-20, 1.3, 6.6),new THREE.Vector3(5, 2, 0))console.log(555);}}
    ]
    
  • 微调一下结构样式,添加相应文字

    <template><div ref="demo"><h1 style="padding: 100px 50px; font-size: 50px; color: #fff" class="text">{{ scenes[index].text }}</h1></div></template><style scoped>
    * {margin: 0;padding: 0;
    }canvas {width: 100%;height: 100%;position: fixed;left: 0;top: 0;z-index: 1;
    }.text {position: fixed;z-index: 2;
    }
    </style>
    
  • 使用补间动画移动相机

    let timeLine1 = gsap.timeline()
    let timeLine2 = gsap.timeline()// 相机的移动(position:移动的目标位置, target:看向的位置)
    const translateCamera = (position, target) => {timeLine1.to(camera.position, {x: position.x,y: position.y,z: position.z,duration: 1,ease: "power2.inOut",})timeLine2.to(controls.target, {x: target.x,y: target.y,z: target.z,duration: 1,ease: "power2.inOut",})
    }
    

1.5. 星与心

  • 创建星实例并随即分布到天上

    // 实例化满天星
    const starsInstance = new THREE.InstancedMesh(new THREE.SphereGeometry(0.1, 32, 32),sphereMaterial,100
    )// 随机分布到天上
    const starsArr = []
    const endArr = []
    for (let i = 0; i < 100; i++) {const x = (Math.random() - 0.5) * 100const y = (Math.random() - 0.5) * 100const z = (Math.random() - 0.5) * 100starsArr.push(new THREE.Vector3(x, y, z))const matrix = new THREE.Matrix4()matrix.setPosition(x, y, z)starsInstance.setMatrixAt(i, matrix)
    }
    scene.add(starsInstance)
    
  • 创建爱心路径并获取点

    // 创建爱心路径
    const heartPath = new THREE.Shape()
    heartPath.moveTo(25, 25)
    heartPath.bezierCurveTo(25, 25, 20, 0, 0, 0)
    heartPath.bezierCurveTo(-30, 0, -30, 35, -30, 35)
    heartPath.bezierCurveTo(-30, 55, -10, 77, 25, 95)
    heartPath.bezierCurveTo(60, 77, 80, 55, 80, 35)
    heartPath.bezierCurveTo(80, 35, 80, 0, 50, 0)
    heartPath.bezierCurveTo(35, 0, 25, 25, 25, 25)// 根据爱心路径获取点
    const center = new THREE.Vector3(0, 2, 10)
    for (let i = 0; i < 100; i++) {const point = heartPath.getPoint(i / 100)endArr.push(new THREE.Vector3(point.x * 0.1 + center.x,point.y * 0.1 + center.y,center.z))
    }
    
  • 创建满天星和爱心的切换动画

    // 创建爱心动画
    const makeHeart = () => {const params = {time: 0}gsap.to(params, {time: 1,duration: 1,onUpdate: () => {for (let i = 0; i < 100; i++) {const x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.timeconst y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.timeconst z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.timeconst matrix = new THREE.Matrix4()matrix.setPosition(x, y, z)starsInstance.setMatrixAt(i, matrix)}starsInstance.instanceMatrix.needsUpdate = true}})
    }// 爱心复原
    const restoreHeart = () => {const params = {time: 0,}gsap.to(params, {time: 1,duration: 1,onUpdate: () => {for (let i = 0; i < 100; i++) {const x = endArr[i].x + (starsArr[i].x - endArr[i].x) * params.timeconst y = endArr[i].y + (starsArr[i].y - endArr[i].y) * params.timeconst z = endArr[i].z + (starsArr[i].z - endArr[i].z) * params.timeconst matrix = new THREE.Matrix4()matrix.setPosition(x, y, z)starsInstance.setMatrixAt(i, matrix)}starsInstance.instanceMatrix.needsUpdate = true}})
    }
    

1.6. 代码奉上

  • 代码差异
    1. 资源路径可能不同,按照实际路径修改几款
    2. 部分静态页面实现不同,我的文字使用固定定位和层级处理,切换相机没有动画
    3. 我的水面代码好像有问题,没有波光粼粼的感觉

1.6.1. 最终效果

1.6.2. 我的代码

<script setup>
import { ref, onMounted } from 'vue'
import * as THREE from "three"
import gsap from 'gsap' // 导入动画gsap
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"  // 轨道控制器
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 导入Draco加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 导入gltf加载器
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'
import { Water } from 'three/examples/jsm/objects/Water2.js'const demo = ref(null)
// 1. --- 创建场景 ---
const scene = new THREE.Scene()// 2. --- 创建相机 ---
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
)
camera.position.set(-3.23, 2.98, 4) // 相机位置
camera.updateProjectionMatrix()// 3. --- 创建渲染器 ---
const renderer = new THREE.WebGLRenderer({antialias: true, // 开启抗锯齿
})
renderer.setSize(window.innerWidth, window.innerHeight)// 设置色调映射
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.5;
renderer.shadowMap.enabled = true;  // 渲染器开启阴影
renderer.physicallyCorrectLights = true;// 4. --- 轨道控制器 ---
const controls = new OrbitControls(camera, renderer.domElement)
// controls.enableDamping = true // 设置阻尼// 5. --- 模型加载 ---
const dracoLoader = new DRACOLoader() // 实例化draco加载器
dracoLoader.setDecoderPath("./draco/") // 设置draco解压器路径
const gltfLoader = new GLTFLoader() // 实例化gltf加载器
gltfLoader.setDRACOLoader(dracoLoader) // gltf加载器关联draco解压器
gltfLoader.load("/model/scene.glb",(gltf) => {const model = gltf.scenemodel.traverse((child) => {if (child.name === "Plane") {child.visible = false}// 物体允许接收后投射阴影if (child.isMesh) {child.castShadow = true;child.receiveShadow = true;}})scene.add(model)}
)// 加载环境纹理
const rgbeLoader = new RGBELoader()
rgbeLoader.load('/texture/hdr/sky.hdr',(texture) => {texture.mapping = THREE.EquirectangularReflectionMapping  // 设置纹理映射scene.background = texturescene.environment = texture},
)
// 设置水面效果
const waterGeometry = new THREE.CircleGeometry(300, 32)
const water = new Water(waterGeometry, {textureWidth: 1024,textureHeight: 1024,color: 0xeeeeff,flowDirection: new THREE.Vector2(1, 1), //  waves directionscale: 100, //  wavesize
})
water.rotation.x = -Math.PI / 2
water.position.y = -0.4
scene.add(water)// 6. --- 添加光源 ---
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(0, 50, 0)
scene.add(light)// 创建点光源
const pointLight = new THREE.PointLight(0xffffff, 50)
pointLight.position.set(0.1, 2.4, 0)
pointLight.castShadow = true// 创建点光源组
const pointLightGroup = new THREE.Group()
pointLightGroup.position.set(-8, 2.5, -1.5)
const radius = 3
const pointLightList = []// 创建球体用于绑定光源 
const sphereGeometry = new THREE.SphereGeometry(0.2, 32, 32)
const sphereMaterial = new THREE.MeshStandardMaterial({color: 0xffffff,emissive: 0xffffff,emissiveIntensity: 10 // 亮度
})
for (let i = 0; i < 3; i++) {const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)pointLightList.push(sphere);sphere.position.set(radius * Math.cos(i * 2 * Math.PI / 3),Math.cos(i * 2 * Math.PI / 3),radius * Math.sin(i * 2 * Math.PI / 3),)const pointLight = new THREE.PointLight(0xffffff, 50)sphere.add(pointLight) // 绑定点光源pointLightGroup.add(sphere)
}
scene.add(pointLightGroup)// 使用补间函数,从0到2π,使小球旋转运动
const options = {angle: 0,
};
gsap.to(options, {angle: Math.PI * 2,duration: 10,repeat: -1,ease: "linear", // 这里要用双引号,不然要报错onUpdate: () => {pointLightGroup.rotation.y = options.angle;// 让球上下摆动pointLightList.forEach((item, index) => {item.position.set(radius * Math.cos(index * 2 * Math.PI / 3),Math.cos(index * 2 * Math.PI / 3 + options.angle * 5),radius * Math.sin(index * 2 * Math.PI / 3))});},
})// 7. --- 渲染 ---
const render = () => {requestAnimationFrame(render) // 请求动画帧renderer.render(scene, camera)controls.update()
}
render()// 数组存储数据即交互逻辑
const scenes = [{text: '《认真的白学》',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(-3.23, 3, 4.06),new THREE.Vector3(-8, 2, 0))}}, {text: '学下得那么深',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(7, 0, 23),new THREE.Vector3(0, 0, 0))}}, {text: '学得那么认真',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(10, 3, 0),new THREE.Vector3(5, 2, 0))}}, {text: '倒映出我躺在学中的伤痕',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(7, 0, 23),new THREE.Vector3(0, 0, 0))// 爱心makeHeart()}}, {text: '完',callback: () => {// 切换对应位置translateCamera(new THREE.Vector3(-20, 1.3, 6.6),new THREE.Vector3(5, 2, 0))}}
]const index = ref(0)
// 监听鼠标滚轮事件
window.addEventListener('wheel', (e) => {if (e.deltaY > 0) {index.value++if (index.value >= scenes.length) {index.value = 0restoreHeart() // 复原}}scenes[index.value].callback()
},false
)// 使用补间动画移动相机
let timeLine1 = gsap.timeline()
let timeLine2 = gsap.timeline()// 相机的移动(position:移动的目标位置, target:看向的位置)
const translateCamera = (position, target) => {timeLine1.to(camera.position, {x: position.x,y: position.y,z: position.z,duration: 1,ease: "power2.inOut",})timeLine2.to(controls.target, {x: target.x,y: target.y,z: target.z,duration: 1,ease: "power2.inOut",})
}
console.log(scenes);// 实例化满天星
const starsInstance = new THREE.InstancedMesh(new THREE.SphereGeometry(0.1, 32, 32),sphereMaterial,100
)// 随机分布到天上
const starsArr = []
const endArr = []
for (let i = 0; i < 100; i++) {const x = (Math.random() - 0.5) * 100const y = (Math.random() - 0.5) * 100const z = (Math.random() - 0.5) * 100starsArr.push(new THREE.Vector3(x, y, z))const matrix = new THREE.Matrix4()matrix.setPosition(x, y, z)starsInstance.setMatrixAt(i, matrix)
}
scene.add(starsInstance)// 创建爱心路径
const heartPath = new THREE.Shape()
heartPath.moveTo(25, 25)
heartPath.bezierCurveTo(25, 25, 20, 0, 0, 0)
heartPath.bezierCurveTo(-30, 0, -30, 35, -30, 35)
heartPath.bezierCurveTo(-30, 55, -10, 77, 25, 95)
heartPath.bezierCurveTo(60, 77, 80, 55, 80, 35)
heartPath.bezierCurveTo(80, 35, 80, 0, 50, 0)
heartPath.bezierCurveTo(35, 0, 25, 25, 25, 25)// 根据爱心路径获取点
const center = new THREE.Vector3(0, 2, 10)
for (let i = 0; i < 100; i++) {const point = heartPath.getPoint(i / 100)endArr.push(new THREE.Vector3(point.x * 0.1 + center.x,point.y * 0.1 + center.y,center.z))
}// 创建爱心动画
const makeHeart = () => {const params = {time: 0}gsap.to(params, {time: 1,duration: 1,onUpdate: () => {for (let i = 0; i < 100; i++) {const x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.timeconst y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.timeconst z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.timeconst matrix = new THREE.Matrix4()matrix.setPosition(x, y, z)starsInstance.setMatrixAt(i, matrix)}starsInstance.instanceMatrix.needsUpdate = true}})
}// 爱心复原
const restoreHeart = () => {const params = {time: 0,};gsap.to(params,{time: 1,duration: 1,onUpdate: () => {for (let i = 0; i < 100; i++) {const x = starsArr[i].x + (starsArr[i].x - endArr[i].x) * params.timeconst y = starsArr[i].y + (starsArr[i].y - endArr[i].y) * params.timeconst z = starsArr[i].z + (starsArr[i].z - endArr[i].z) * params.timeconst matrix = new THREE.Matrix4()matrix.setPosition(x, y, z)starsInstance.setMatrixAt(i, matrix)}starsInstance.instanceMatrix.needsUpdate = true}})
}onMounted(() => {demo.value.appendChild(renderer.domElement)
})</script><template><div ref="demo"><h1 style="padding: 100px 50px; font-size: 50px; color: #fff" class="text">{{ scenes[index].text }}</h1></div></template><style scoped>
* {margin: 0;padding: 0;
}canvas {width: 100%;height: 100%;position: fixed;left: 0;top: 0;z-index: 1;
}.text {position: fixed;z-index: 2;
}
</style>

1.6.3. 老陈打码

<template><div class="scenes" style="position: fixed;left: 0;top: 0;z-index: 10;pointer-events: none;transition: all 1s;" :style="{transform: `translate3d(0, ${-index * 100}vh, 0)`,}"><div v-for="item in scenes" style="width: 100vw; height: 100vh"><h1 style="padding: 100px 50px; font-size: 50px; color: #fff">{{ item.text }}</h1></div></div>
</template><script setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { Water } from "three/examples/jsm/objects/Water2";
import gsap from "gsap";
import { ref } from "vue";// 初始化场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(75,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.physicallyCorrectLights = true;
// 设置水面效果// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(-8, 2, 0);
controls.enableDamping = true;// 初始化loader
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);// 加载环境纹理
let rgbeLoader = new RGBELoader();
rgbeLoader.load("./textures/sky.hdr", (texture) => {texture.mapping = THREE.EquirectangularReflectionMapping;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.CircleGeometry(300, 32);
const water = new Water(waterGeometry, {textureWidth: 1024,textureHeight: 1024,color: 0xeeeeff,flowDirection: new THREE.Vector2(1, 1),scale: 100,
});
water.rotation.x = -Math.PI / 2;
water.position.y = -0.4;
scene.add(water);// 添加平行光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 50, 0);
scene.add(light);// 添加点光源
const pointLight = new THREE.PointLight(0xffffff, 50);
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.2, 32, 32);const sphereMaterial = new THREE.MeshStandardMaterial({color: 0xffffff,emissive: 0xffffff,emissiveIntensity: 10,});const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);pointLightArr.push(sphere);sphere.position.set(radius * Math.cos((i * 2 * Math.PI) / 3),Math.cos((i * 2 * Math.PI) / 3),radius * Math.sin((i * 2 * Math.PI) / 3));let pointLight = new THREE.PointLight(0xffffff, 50);sphere.add(pointLight);pointLightGroup.add(sphere);
}
scene.add(pointLightGroup);// 使用补间函数,从0到2π,使灯泡旋转
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 * 2 * Math.PI) / 3),Math.cos((index * 2 * Math.PI) / 3 + options.angle * 5),radius * Math.sin((index * 2 * Math.PI) / 3));});},
});
function render() {requestAnimationFrame(render);renderer.render(scene, camera);controls.update();
}
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: 1,ease: "power2.inOut",});timeline2.to(controls.target, {x: target.x,y: target.y,z: target.z,duration: 1,ease: "power2.inOut",});
}let scenes = [{text: "圣诞快乐",callback: () => {// 执行函数切换位置translateCamera(new THREE.Vector3(-3.23, 3, 4.06),new THREE.Vector3(-8, 2, 0));},},{text: "感谢在这么大的世界里遇见了你",callback: () => {// 执行函数切translateCamera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0));},},{text: "愿与你探寻世界的每一个角落",callback: () => {// 执行函数切translateCamera(new THREE.Vector3(10, 3, 0), new THREE.Vector3(5, 2, 0));},},{text: "愿将天上的星星送给你",callback: () => {// 执行函数切translateCamera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0));makeHeart();},},{text: "愿疫情结束,大家健康快乐!",callback: () => {// 执行函数切translateCamera(new THREE.Vector3(-20, 1.3, 6.6),new THREE.Vector3(5, 2, 0));},},
];let index = ref(0);
let isAnimate = false;
// 监听鼠标滚轮事件
window.addEventListener("wheel",(e) => {if (isAnimate) return;isAnimate = true;if (e.deltaY > 0) {index.value++;if (index.value > scenes.length - 1) {index.value = 0;restoreHeart();}}scenes[index.value].callback();setTimeout(() => {isAnimate = false;}, 1000);},false
);// 实例化创建漫天星星
let starsInstance = new THREE.InstancedMesh(new THREE.SphereGeometry(0.1, 32, 32),new THREE.MeshStandardMaterial({color: 0xffffff,emissive: 0xffffff,emissiveIntensity: 10,}),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);// 根据爱心路径获取点
let center = new THREE.Vector3(0, 2, 10);
for (let i = 0; i < 100; i++) {let point = heartShape.getPoint(i / 100);endArr.push(new THREE.Vector3(point.x * 0.1 + center.x,point.y * 0.1 + center.y,center.z));
}// 创建爱心动画
function makeHeart() {let params = {time: 0,};gsap.to(params, {time: 1,duration: 1,onUpdate: () => {for (let i = 0; i < 100; i++) {let x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.time;let y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.time;let z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.time;let matrix = new THREE.Matrix4();matrix.setPosition(x, y, z);starsInstance.setMatrixAt(i, matrix);}starsInstance.instanceMatrix.needsUpdate = true;},});
}function restoreHeart() {let params = {time: 0,};gsap.to(params, {time: 1,duration: 1,onUpdate: () => {for (let i = 0; i < 100; i++) {let x = endArr[i].x + (starsArr[i].x - endArr[i].x) * params.time;let y = endArr[i].y + (starsArr[i].y - endArr[i].y) * params.time;let z = endArr[i].z + (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><style>
* {margin: 0;padding: 0;
}canvas {width: 100vw;height: 100vh;position: fixed;left: 0;top: 0;
}
</style>

2. 汽车展示

2.1. 项目搭建

  • 基础代码

    <script setup>
    import { ref, onMounted } from 'vue'
    import * as THREE from "three"
    import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"  // 轨道控制器const demo = ref(null)
    // 1. --- 创建场景 ---
    const scene = new THREE.Scene()// 2. --- 创建相机 ---
    const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
    )
    camera.position.set(0, 2, 6) // 相机位置
    camera.updateProjectionMatrix() // 更新投影矩阵// 3. --- 创建渲染器 ---
    const renderer = new THREE.WebGLRenderer({antialias: true, // 开启抗锯齿
    })
    renderer.setSize(window.innerWidth, window.innerHeight)
    // 渲染函数
    const render = () => {renderer.render(scene, camera)controls && controls.update()requestAnimationFrame(render) // 渲染下一帧
    }
    // 挂载到dom中
    onMounted(() => {demo.value.appendChild(renderer.domElement) // 将渲染器添加到场景中renderer.setClearColor("#000")scene.background = new THREE.Color("#ccc")scene.environment = new THREE.Color("#ccc")const grid = new THREE.GridHelper(10, 10)// 添加网格地面 grid.material.transparent = truegrid.material.opacity = 0.2scene.add(grid)render() // 调用渲染函数
    })// 4. --- 添加控制器 ---
    const controls = new OrbitControls(camera, renderer.domElement)
    controls.update()// 5. ---窗口大小调整 ---
    window.addEventListener("resize", () => {renderer.setSize(window.innerWidth, window.innerHeight)camera.aspect = window.innerWidth / window.innerHeightcamera.updateProjectionMatrix()
    })</script><template><div ref="demo"></div>
    </template><style scoped>
    * {margin: 0;padding: 0;
    }canvas {width: 100%;height: 100%;position: fixed;left: 0;top: 0;z-index: 1;
    }
    </style>
    
  • 模型准备

    1. 准备draco文件夹,将其复制到public文件夹下
    2. 准备汽车模型car.glb,放到相应文件夹下

    效果图:

    在这里插入图片描述

2.2. 模型与灯光

  • 模型加载

    import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 导入Draco加载器
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 导入gltf加载器
    ...
    // 模型载入
    const loader = new GLTFLoader()
    const dracoLoader = new DRACOLoader()
    dracoLoader.setDecoderPath("/draco/") // 设置draco解码器路径
    loader.setDRACOLoader(dracoLoader) // 绑定draco解码器
    loader.load("/model/car.glb", (gltf) => {scene.add(gltf.scene)
    })
    

    效果图:

    在这里插入图片描述

    因为模型为物理材质,需要光才能看见模型颜色

  • 添加灯光

    // 添加灯光
    const light1 = new THREE.DirectionalLight(0xffffff, 1)  
    light1.position.set(0, 0, 10)
    const light2 = new THREE.DirectionalLight(0xffffff, 1)  
    light2.position.set(0, 0, -10)
    const light3 = new THREE.DirectionalLight(0xffffff, 1)  
    light3.position.set(10, 0, 0)
    const light4 = new THREE.DirectionalLight(0xffffff, 1)  
    light4.position.set(-10, 0, 0)
    const light5 = new THREE.DirectionalLight(0xffffff, 1)  
    light5.position.set(0, 10, 0)
    const light6 = new THREE.DirectionalLight(0xffffff, 1)  
    light6.position.set(5, 10, 0)
    const light7 = new THREE.DirectionalLight(0xffffff, 1) 
    light7.position.set(0, 10, 5)
    const light8 = new THREE.DirectionalLight(0xffffff, 1)  
    light8.position.set(0, 10,-5)
    const light9 = new THREE.DirectionalLight(0xffffff, 1)  
    light9.position.set(-5, 10, 0)
    scene.add(light1, light2, light3, light4, light5, light6, light7, light8, light9)
    

    效果图:

    在这里插入图片描述

2.3. 车身材质

  • 模型组成

    打印gltf.scene,发现汽车是由66个模型组成,接下来我们对这些物体进行设置,来完成对模型的操作

    loader.load("/model/car.glb", (gltf) => {scene.add(gltf.scene)console.log(gltf);
    })
    

    在这里插入图片描述

  • 获取模型名字(仅作为参考,最好在3D建模软件导出时就取好名字)

    loader.load("/model/car.glb", (gltf) => {const car = gltf.scenecar.traverse((child) => {if (child.isMesh) {console.log(child.name);}})scene.add(car)
    })
    

    在这里插入图片描述

  • 创建变量存储材质

    // - 创建材质 -
    // 车身材质
    const bodyMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0
    })
    // 前部车身材质
    const frontMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0
    })// 车的轮毂
    const wheelMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.1,
    })// 车玻璃
    const glassMaterial = new THREE.MeshPhysicalMaterial({color: 0xffffff,metalness: 0,roughness: 0,transmission: 1,transparent: true
    })// 引擎盖
    const hoodMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0
    })
    
  • 对车的各个部位模型进行赋值

    loader.load("/model/car.glb", (gltf) => {const car = gltf.scenecar.traverse((child) => {if (child.isMesh) {// 判断是否是轮毂if (child.name.includes("轮毂")) {child.material = wheelMaterialwheels.push(child)}// 判断是否是车身主体else if (child.name.includes("Mesh002")) {carBody = childcarBody.material = bodyMaterial}// 判断是否是前部车身else if (child.name.includes("前脸")) {frontCar = childfrontCar.material = frontMaterial}// 是否是引擎盖else if (child.name.includes("引擎盖_1")) {hoodCar = childhoodCar.material = hoodMaterial}// 是否是挡风玻璃else if (child.name.includes("挡风玻璃")) {glassCar = childglassCar.material = glassMaterial}}})scene.add(car)
    })
    

    效果图:

    在这里插入图片描述

2.4. 材质切换

  • 静态页面改变(我用了scss,如果要像我这样使用,请安装相应依赖)

    <template><div ref="demo"><div class="menu"><div class="title"><h1>汽车展示与选配</h1></div><!-- 车身颜色 --><h2>请选择车身颜色</h2><div class="select"><div class="option" v-for="(item, index) in colors" :key="index" @click="selectColor(index)"><div class="option-color" :style="{ background: item }" /></div></div><!-- 贴膜材质 --><h2>请选择贴膜材质</h2><div class="select"><div class="option" v-for="(item, index) in materials" :key="index" @click="selectMaterial(index)"><button class="option-material" :style="{ background: item }">{{ item.name }}</button></div></div></div></div>
    </template><style scoped lang="scss">
    * {margin: 0;padding: 0;
    }canvas {width: 100%;height: 100%;position: fixed;left: 0;top: 0;z-index: 1;
    }.menu {position: fixed;top: 20px;right: 20px;z-index: 2;.select {display: inline-block;display: flex;.option {display: flex;.option-color {width: 30px;height: 30px;border: 1px solid #ccc;margin: 10px;display: inline-block;cursor: pointer;border-radius: 10px;}}}
    }
    </style>
    
  • 颜色切换

    // 设置颜色数组
    const colors = ["red", "yellow", "blue", "green","orange", "pink", "purple", "white", "black", "gray"]
    ...// 颜色选择函数
    const selectColor = (index) => {bodyMaterial.color.set(colors[index])frontMaterial.color.set(colors[index])glassMaterial.color.set(colors[index])hoodMaterial.color.set(colors[index])wheelMaterial.color.set(colors[index])
    }
    
  • 贴膜材质切换

    // 设置贴膜数组
    const materials = [{ name: '磨砂', value: 1 },{ name: '冰晶', value: 0 }
    ]
    ...
    // 材质选择函数
    const selectMaterial = (index) => {bodyMaterial.clearcoatRoughness = materials[index].valuefrontMaterial.clearcoatRoughness = materials[index].valuehoodMaterial.clearcoatRoughness = materials[index].valuewheelMaterial.clearcoatRoughness = materials[index].value
    }
    

    效果图:

    • 黄色冰晶

      在这里插入图片描述

    • 绿色磨砂

      在这里插入图片描述

2.5. 源码奉上

2.5.1.我的代码

<script setup>
import { ref, onMounted } from 'vue'
import * as THREE from "three"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"  // 轨道控制器
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 导入Draco加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 导入gltf加载器const demo = ref(null)
// 1. --- 创建场景 ---
const scene = new THREE.Scene()// - 模型 -
const wheels = [] // 轮子
let carBody,  // 主体车身frontCar, // 前部车身hoodCar,  // 引擎盖glassCar // 挡风玻璃// - 创建材质 -
// 车身材质
const bodyMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0
})// 设置颜色数组
const colors = ["red", "yellow", "blue", "green","orange", "pink", "purple", "white", "black", "gray"]// 设置贴膜数组
const materials = [{ name: '磨砂', value: 1 },{ name: '冰晶', value: 0 }
]// 前部车身材质
const frontMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0
})// 车的轮毂
const wheelMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.1,
})// 车玻璃
const glassMaterial = new THREE.MeshPhysicalMaterial({color: 0xffffff,metalness: 0,roughness: 0,transmission: 1,transparent: true
})// 引擎盖
const hoodMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0
})// 2. --- 创建相机 ---
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
)
camera.position.set(0, 2, 6) // 相机位置
camera.updateProjectionMatrix() // 更新投影矩阵// 3. --- 创建渲染器 ---
const renderer = new THREE.WebGLRenderer({antialias: true, // 开启抗锯齿
})
renderer.setSize(window.innerWidth, window.innerHeight)
// 渲染函数
const render = () => {renderer.render(scene, camera)controls && controls.update()requestAnimationFrame(render) // 渲染下一帧
}
// 挂载到dom中
onMounted(() => {demo.value.appendChild(renderer.domElement) // 将渲染器添加到场景中renderer.setClearColor("#000")scene.background = new THREE.Color("#ccc")scene.environment = new THREE.Color("#ccc")const grid = new THREE.GridHelper(10, 10)// 添加网格地面 grid.material.transparent = truegrid.material.opacity = 0.2scene.add(grid)render() // 调用渲染函数
})// 4. --- 添加控制器 ---
const controls = new OrbitControls(camera, renderer.domElement)
controls.update()// 模型载入
const loader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/") // 设置draco解码器路径
loader.setDRACOLoader(dracoLoader) // 绑定draco解码器
loader.load("/model/car.glb", (gltf) => {const car = gltf.scenecar.traverse((child) => {if (child.isMesh) {// 判断是否是轮毂if (child.name.includes("轮毂")) {child.material = wheelMaterialwheels.push(child)}// 判断是否是车身主体else if (child.name.includes("Mesh002")) {carBody = childcarBody.material = bodyMaterial}// 判断是否是前部车身else if (child.name.includes("前脸")) {frontCar = childfrontCar.material = frontMaterial}// 是否是引擎盖else if (child.name.includes("引擎盖_1")) {hoodCar = childhoodCar.material = hoodMaterial}// 是否是挡风玻璃else if (child.name.includes("挡风玻璃")) {glassCar = childglassCar.material = glassMaterial}}})scene.add(car)
})// 添加灯光
const light1 = new THREE.DirectionalLight(0xffffff, 1)
light1.position.set(0, 0, 10)
const light2 = new THREE.DirectionalLight(0xffffff, 1)
light2.position.set(0, 0, -10)
const light3 = new THREE.DirectionalLight(0xffffff, 1)
light3.position.set(10, 0, 0)
const light4 = new THREE.DirectionalLight(0xffffff, 1)
light4.position.set(-10, 0, 0)
const light5 = new THREE.DirectionalLight(0xffffff, 1)
light5.position.set(0, 10, 0)
const light6 = new THREE.DirectionalLight(0xffffff, 1)
light6.position.set(5, 10, 0)
const light7 = new THREE.DirectionalLight(0xffffff, 1)
light7.position.set(0, 10, 5)
const light8 = new THREE.DirectionalLight(0xffffff, 1)
light8.position.set(0, 10, -5)
const light9 = new THREE.DirectionalLight(0xffffff, 1)
light9.position.set(-5, 10, 0)
scene.add(light1, light2, light3, light4, light5, light6, light7, light8, light9)// 5. ---窗口大小调整 
window.addEventListener("resize", () => {renderer.setSize(window.innerWidth, window.innerHeight)camera.aspect = window.innerWidth / window.innerHeightcamera.updateProjectionMatrix()
})// 颜色选择函数
const selectColor = (index) => {bodyMaterial.color.set(colors[index])frontMaterial.color.set(colors[index])glassMaterial.color.set(colors[index])hoodMaterial.color.set(colors[index])wheelMaterial.color.set(colors[index])
}// 材质选择函数
const selectMaterial = (index) => {bodyMaterial.clearcoatRoughness = materials[index].valuefrontMaterial.clearcoatRoughness = materials[index].valuehoodMaterial.clearcoatRoughness = materials[index].valuewheelMaterial.clearcoatRoughness = materials[index].value
}
</script><template><div ref="demo"><div class="menu"><div class="title"><h1>汽车展示与选配</h1></div><!-- 车身颜色 --><h2>请选择车身颜色</h2><div class="select"><div class="option" v-for="(item, index) in colors" :key="index" @click="selectColor(index)"><div class="option-color" :style="{ background: item }" /></div></div><!-- 贴膜材质 --><h2>请选择贴膜材质</h2><div class="select"><div class="option" v-for="(item, index) in materials" :key="index" @click="selectMaterial(index)"><button class="option-material" :style="{ background: item }">{{ item.name }}</button></div></div></div></div>
</template><style scoped lang="scss">
* {margin: 0;padding: 0;
}canvas {width: 100%;height: 100%;position: fixed;left: 0;top: 0;z-index: 1;
}.menu {position: fixed;top: 20px;right: 20px;z-index: 2;.title {}.select {display: inline-block;display: flex;.option {display: flex;.option-color {width: 30px;height: 30px;border: 1px solid #ccc;margin: 10px;display: inline-block;cursor: pointer;border-radius: 10px;}}}
}
</style>

2.5.2.老陈打码

<template><div class="home"><div class="canvas-container" ref="canvasDom"></div><div class="home-content"><div class="home-content-title"><h1>汽车展示与选配</h1></div><h2>选择车身颜色</h2><div class="select"><divclass="select-item"v-for="(item, index) in colors":key="index"@click="selectColor(index)"><divclass="select-item-color":style="{ backgroundColor: item }"></div></div></div><h2>选择贴膜材质</h2><div class="select"><divclass="select-item"v-for="(item, index) in materials":key="index"@click="selectMaterial(index)"><div class="select-item-text">{{ item.name }}</div></div></div></div></div>
</template><script setup>
import * as THREE from "three";
import { onMounted, reactive, ref } from "vue";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
let controls;
let canvasDom = ref(null);
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
);
camera.position.set(0, 2, 6);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({// 抗锯齿antialias: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);const render = () => {renderer.render(scene, camera);controls && controls.update();requestAnimationFrame(render);
};let wheels = [];
let carBody, frontCar, hoodCar, glassCar;
// 创建材质
const bodyMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0,
});const frontMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0,
});
const hoodMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.5,clearcoat: 1,clearcoatRoughness: 0,
});
const wheelsMaterial = new THREE.MeshPhysicalMaterial({color: 0xff0000,metalness: 1,roughness: 0.1,
});
const glassMaterial = new THREE.MeshPhysicalMaterial({color: 0xffffff,metalness: 0,roughness: 0,transmission: 1,transparent: true,
});let colors = ["red", "blue", "green", "gray", "orange", "purple"];let selectColor = (index) => {bodyMaterial.color.set(colors[index]);frontMaterial.color.set(colors[index]);hoodMaterial.color.set(colors[index]);wheelsMaterial.color.set(colors[index]);// glassMaterial.color.set(colors[index]);
};let materials = [{ name: "磨砂", value: 1 },{ name: "冰晶", value: 0 },
];
let selectMaterial = (index) => {bodyMaterial.clearcoatRoughness = materials[index].value;frontMaterial.clearcoatRoughness = materials[index].value;hoodMaterial.clearcoatRoughness = materials[index].value;
};
onMounted(() => {// 把渲染器插入到dom中// console.log(canvasDom.value);canvasDom.value.appendChild(renderer.domElement);// 初始化渲染器,渲染背景renderer.setClearColor("#000");scene.background = new THREE.Color("#ccc");scene.environment = new THREE.Color("#ccc");render();// 添加网格地面const gridHelper = new THREE.GridHelper(10, 10);gridHelper.material.opacity = 0.2;gridHelper.material.transparent = true;scene.add(gridHelper);// 添加控制器controls = new OrbitControls(camera, renderer.domElement);controls.update();// 添加gltf汽车模型const loader = new GLTFLoader();const dracoLoader = new DRACOLoader();dracoLoader.setDecoderPath("./draco/gltf/");loader.setDRACOLoader(dracoLoader);loader.load("./model/bmw01.glb", (gltf) => {const bmw = gltf.scene;// console.log(gltf);bmw.traverse((child) => {if (child.isMesh) {console.log(child.name);}// 判断是否是轮毂if (child.isMesh && child.name.includes("轮毂")) {child.material = wheelsMaterial;wheels.push(child);}// 判断是否是车身if (child.isMesh && child.name.includes("Mesh002")) {carBody = child;carBody.material = bodyMaterial;}// 判断是否是前脸if (child.isMesh && child.name.includes("前脸")) {child.material = frontMaterial;frontCar = child;}// 判断是否是引擎盖if (child.isMesh && child.name.includes("引擎盖_1")) {child.material = hoodMaterial;hoodCar = child;}// 判断是否是挡风玻璃if (child.isMesh && child.name.includes("挡风玻璃")) {child.material = glassMaterial;glassCar = child;}});scene.add(bmw);});// 添加灯光const light1 = new THREE.DirectionalLight(0xffffff, 1);light1.position.set(0, 0, 10);scene.add(light1);const light2 = new THREE.DirectionalLight(0xffffff, 1);light2.position.set(0, 0, -10);scene.add(light2);const light3 = new THREE.DirectionalLight(0xffffff, 1);light3.position.set(10, 0, 0);scene.add(light3);const light4 = new THREE.DirectionalLight(0xffffff, 1);light4.position.set(-10, 0, 0);scene.add(light4);const light5 = new THREE.DirectionalLight(0xffffff, 1);light5.position.set(0, 10, 0);scene.add(light5);const light6 = new THREE.DirectionalLight(0xffffff, 0.3);light6.position.set(5, 10, 0);scene.add(light6);const light7 = new THREE.DirectionalLight(0xffffff, 0.3);light7.position.set(0, 10, 5);scene.add(light7);const light8 = new THREE.DirectionalLight(0xffffff, 0.3);light8.position.set(0, 10, -5);scene.add(light8);const light9 = new THREE.DirectionalLight(0xffffff, 0.3);light9.position.set(-5, 10, 0);scene.add(light9);
});
</script><style>
* {margin: 0;padding: 0;
}.home-content {position: fixed;top: 0;right: 20px;
}.select-item-color {width: 50px;height: 50px;border: 1px solid #ccc;margin: 10px;display: inline-block;cursor: pointer;border-radius: 10px;
}
.select {display: flex;
}
</style>

文章转载自:

http://OOdL5USW.pfjbn.cn
http://FccuI3YP.pfjbn.cn
http://cIusFE0d.pfjbn.cn
http://p2Yr0qKV.pfjbn.cn
http://RBpUVDsA.pfjbn.cn
http://MClCz4xv.pfjbn.cn
http://fK7FyQIl.pfjbn.cn
http://FH5wZYFX.pfjbn.cn
http://TFrmKD85.pfjbn.cn
http://1LvDw2ZG.pfjbn.cn
http://iKiqR5gC.pfjbn.cn
http://la8Nglba.pfjbn.cn
http://717FvR1Q.pfjbn.cn
http://5v7TFIT5.pfjbn.cn
http://a4Fp3I5T.pfjbn.cn
http://Kxs0U7Ez.pfjbn.cn
http://QKYDN9hw.pfjbn.cn
http://BuBlOvrd.pfjbn.cn
http://1RXPNYXK.pfjbn.cn
http://fIPLnPve.pfjbn.cn
http://PygjRQbn.pfjbn.cn
http://SPWMZLRl.pfjbn.cn
http://dzaZKYkO.pfjbn.cn
http://qFolSOwD.pfjbn.cn
http://htz6k074.pfjbn.cn
http://KWlEwrSO.pfjbn.cn
http://J2OKnw1z.pfjbn.cn
http://N9NHhXK0.pfjbn.cn
http://WAnfQgbX.pfjbn.cn
http://q0CV8o8v.pfjbn.cn
http://www.dtcms.com/a/379615.html

相关文章:

  • React18学习笔记(一) 如何创建一个React项目,JSX的基础应用,案例---视频网站评论区
  • 【Threejs】学习笔记
  • 图像显示技术与色彩转换:从基础原理到实际应用
  • C 语言实现 I.MX6ULL 点灯(续上一篇)、SDK、deep及bsp工程管理
  • 飞桨paddlepaddle旧版本2.4.2安装
  • 2.5 DNS(Domain Name System)
  • CK: 03靶场渗透
  • User类CRUD实现
  • AFSim2.9.0学习笔记 —— 4.2、ArkSIM文件结构介绍及项目结构整理
  • JavaScript WebAPI 指南
  • 计算机毕业设计 基于Hadoop的南昌房价数据分析系统的设计与实现 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
  • 电路学习(六)三极管
  • 照度传感器考虑笔记
  • 在springboot中使用okhttp3
  • Android开发之Android官方模拟器启动失败问题跟踪排查
  • 第4节-排序和限制-FETCH
  • 2025.9.11总结
  • 三大范式是什么?
  • 传统文化与现代科技的完美融合:文昌帝君灵签系统开发实践
  • 避坑指南:从原理出发解决常见问题
  • 什么是特征冗余度?
  • 数据结构----栈的顺序存储(顺序栈)
  • Java 线上问题排查:从基础到高级的全面指南
  • 浅谈Nacos配置中心
  • 美国CISA发布通用漏洞披露 (CVE) 计划愿景
  • 软考中级习题与解答——第五章_面向对象方法(1)
  • 硬件驱动——I.MX6ULL裸机启动(2)
  • Linux 进程深度解析(6):资源隔离的底层实现 (Namespace、Cgroups 与容器化)
  • 【AI大模型面试宝典60题】1-5
  • AUTOSAR Adaptive Platform 日志与追踪 (Log and Trace) 规范深度解析