three.js加载车模型

<template>
    <div style="position:relative;">
        <div ref="containerRef"
             :style="{width:container.width+'px',height:container.height+'px',background:container.color}"
             class="container">
            <div v-show="progress<100" style="position: absolute;top:50%;left:50%;width:400px;">
                <el-progress :percentage="progress" :stroke-width="15" striped/>
            </div>
            <el-button style="position:absolute;left:30px;top:100px" @click="down()">下载</el-button>
        </div>
    </div>
</template>
<script setup>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';  //引入扩展库
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; //引入GUI库
//背景
const container = reactive({
    width: 1920,
    height: 900,
    color: '#5cb85c'
});
//画布
const canvas = reactive({
    width: 1920,
    height: 900,
    color: 0x444444,
    alpha: 1//透明度
});
//相机 视野角度(FOV) 长宽比(aspect)近截面(near),远截面(far) lookAt指向
const cameraOpt = reactive({
    fov: 75,
    near: 0.1,
    far: 1000,
    position: {
        x: 200,
        y: 200,
        z: 200
    },
    lookAt: {
        x: 0,
        y: 0,
        z: 0
    }
});
//添加点光源
const pointOpt = reactive({
    color: 0xffffff,
    intensity: 1.0,
    decay: 0.0,
    position: {
        x: 100,
        y: 100,
        z: 100
    },
    isOpen: true
});
//添加环境光
const ambientOpt = reactive({
    color: 0xffffff,
    intensity: 1.0,
    isOpen: true
});
//添加平行光
const directionOpt = reactive({
    color: 0xffffff,
    intensity: 1.0,
    position: {
        x: 100,
        y: 100,
        z: 100
    },
    isOpen: true
});
//辅助工具--坐标轴
const axesOpt = reactive({
    size: 150,
    isOpen: true
});
//辅助工具--点光源
const pointHelper = reactive({
    sphereSize: 1,
    isOpen: true
});
//---动画设置----
const animateOpt = reactive({
    isOpen: false,
    start: () => {
    },
    stop: () => {
    }
});
// 初始化场景
const scene = new THREE.Scene();
//-------初始化相机 (透视摄像机)------
const camera = new THREE.PerspectiveCamera(cameraOpt.fov, canvas.width / canvas.height, cameraOpt.near, cameraOpt.far);
//调摄像机的位置
camera.position.set(cameraOpt.position.x, cameraOpt.position.y, cameraOpt.position.z);
//------初始化渲染器------
const renderer = new THREE.WebGLRenderer({
    antialias: true,//开启抗锯齿
    //想把canvas画布上内容下载到本地,需要设置为true
    preserveDrawingBuffer: true
});
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(canvas.color, canvas.alpha); //设置背景颜色
renderer.setSize(canvas.width, canvas.height);
//相机指向
camera.lookAt(cameraOpt.lookAt.x, cameraOpt.lookAt.y, cameraOpt.lookAt.z);
//添加相机轨道
const controls = new OrbitControls(camera, renderer.domElement);
// 挂载完毕 获取dom
const containerRef = ref();
//========光源============
//点光源
const pointLight = new THREE.PointLight(pointOpt.color, pointOpt.intensity);
pointLight.decay = pointOpt.decay;  //随着距离光源强度衰减
pointLight.position.set(pointOpt.position.x, pointOpt.position.y, pointOpt.position.z);  //光源位置
if (pointOpt.isOpen) (scene.add(pointLight));
//环境光:没有特定方向,整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(ambientOpt.color, ambientOpt.intensity);
if (ambientOpt.isOpen) (scene.add(ambient));
// 平行光
const directionalLight = new THREE.DirectionalLight(directionOpt.color, directionOpt.intensity);
// 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(directionOpt.position.x, directionOpt.position.y, directionOpt.position.z);
if (directionOpt.isOpen) (scene.add(directionalLight));
//----------辅助工具-----------
//设置坐标轴
const axesHelper = new THREE.AxesHelper(axesOpt.size);
if (axesOpt.isOpen) {
    scene.add(axesHelper);
}
// 模拟点光源
const pointLightHelper = new THREE.PointLightHelper(pointLight, pointHelper.sphereSize);
if (pointHelper.isOpen) {
    scene.add(pointLightHelper);
}
//设置gui控制
const gui = new GUI();
//背景
const containerFolder = gui.addFolder('背景');
containerFolder.add(container, 'width', 300, 1920).name('宽').step(100);
containerFolder.add(container, 'height', 300, 900).name('高').step(100);
containerFolder.addColor(container, 'color').name('背景色');
containerFolder.close();
//画布
const canvasFolder = gui.addFolder('画布');
//重置画布
const resetCanvas = () => {
    renderer.setSize(canvas.width, canvas.height);
    camera.aspect = canvas.width / canvas.height;
    camera.updateProjectionMatrix();
};
canvasFolder.add(canvas, 'width', 300, 1920).onChange((value) => {
    resetCanvas();
}).name('宽');
canvasFolder.add(canvas, 'height', 300, 900).onChange((value) => {
    resetCanvas();
}).name('高');
canvasFolder.addColor(canvas, 'color').onChange((value) => {
    renderer.setClearColor(new THREE.Color(value));
}).name('背景色');
canvasFolder.close();
//相机
const cameraFolder = gui.addFolder('相机');
cameraFolder.add(cameraOpt, 'fov', 0, 900).onChange((value) => {
    camera.fov = value;
    camera.updateProjectionMatrix();
}).name('视野角度');
cameraFolder.add(cameraOpt, 'near', 0, 900).onChange((value) => {
    camera.near = value;
    camera.updateProjectionMatrix();
}).name('近截面');
cameraFolder.add(cameraOpt, 'far', 0, 2000).onChange((value) => {
    camera.far = value;
    camera.updateProjectionMatrix();
}).name('远截面');
const cameraPosFolder = cameraFolder.addFolder('相机位置');
cameraPosFolder.add(cameraOpt.position, 'x', 0, 2000).onChange((value) => {
    camera.position.set(cameraOpt.position.x, cameraOpt.position.y, cameraOpt.position.z);
    camera.updateProjectionMatrix();
}).name('位置x轴');
cameraPosFolder.add(cameraOpt.position, 'y', 0, 2000).onChange((value) => {
    camera.position.set(cameraOpt.position.x, cameraOpt.position.y, cameraOpt.position.z);
    camera.updateProjectionMatrix();
}).name('位置y轴');
cameraPosFolder.add(cameraOpt.position, 'z', 0, 2000).onChange((value) => {
    camera.position.set(cameraOpt.position.x, cameraOpt.position.y, cameraOpt.position.z);
    camera.updateProjectionMatrix();
}).name('位置z轴');
cameraFolder.close();
const cameraLookFolder = cameraFolder.addFolder('相机指向');
cameraLookFolder.add(cameraOpt.lookAt, 'x', 0, 2000).onChange((value) => {
    camera.position.set(cameraOpt.lookAt.x, cameraOpt.lookAt.y, cameraOpt.lookAt.z);
    camera.updateProjectionMatrix();
}).name('指向x轴');
cameraLookFolder.add(cameraOpt.lookAt, 'y', 0, 2000).onChange((value) => {
    camera.position.set(cameraOpt.lookAt.x, cameraOpt.lookAt.y, cameraOpt.lookAt.z);
    camera.updateProjectionMatrix();
}).name('指向y轴');
cameraLookFolder.add(cameraOpt.lookAt, 'z', 0, 2000).onChange((value) => {
    camera.position.set(cameraOpt.lookAt.x, cameraOpt.lookAt.y, cameraOpt.lookAt.z);
    camera.updateProjectionMatrix();
}).name('指向z轴');
//灯光
const lightFolder = gui.addFolder('灯光');
lightFolder.close();
const pointFolder = lightFolder.addFolder('点光源');
pointFolder.add(pointOpt, 'isOpen').onChange((value) => {
    if (value) {
        scene.add(pointLight);
    } else {
        scene.remove(pointLight);
    }
}).name('开启');
pointFolder.addColor(pointOpt, 'color').onChange((value) => {
    pointLight.color.set(value);
}).name('颜色');
pointFolder.add(pointOpt, 'intensity', 0, 10).onChange((value) => {
    pointLight.intensity = value;
}).name('强度');
pointFolder.add(pointOpt, 'decay', 0, 10).onChange((value) => {
    pointLight.decay = value;
}).name('衰减');
pointFolder.add(pointOpt.position, 'x', 10, 1000).onChange((value) => {
    pointLight.position.set(value, pointOpt.position.y, pointOpt.position.z);
}).name('位置X轴');
pointFolder.add(pointOpt.position, 'y', 10, 1000).onChange((value) => {
    pointLight.position.set(pointOpt.position.x, value, pointOpt.position.z);
}).name('位置y轴');
pointFolder.add(pointOpt.position, 'y', 10, 1000).onChange((value) => {
    pointLight.position.set(pointOpt.position.x, pointOpt.position.y, value);
}).name('位置z轴');
const ambientFolder = lightFolder.addFolder('环境光');
ambientFolder.add(ambientOpt, 'isOpen').onChange((value) => {
    if (value) {
        scene.add(ambient);
    } else {
        scene.remove(ambient);
    }
}).name('开启');
ambientFolder.addColor(ambientOpt, 'color').onChange((value) => {
    ambient.color.set(value);
}).name('颜色');
ambientFolder.add(ambientOpt, 'intensity', 0, 10).onChange((value) => {
    ambient.intensity = value;
}).name('强度');
const directionFolder = lightFolder.addFolder('平行光');
directionFolder.add(directionOpt, 'isOpen').onChange((value) => {
    if (value) {
        scene.add(directionalLight);
    } else {
        scene.remove(directionalLight);
    }
}).name('开启');
directionFolder.addColor(directionOpt, 'color').onChange((value) => {
    directionalLight.color.set(value);
}).name('颜色');
directionFolder.add(directionOpt, 'intensity', 0, 10).onChange((value) => {
    directionalLight.intensity = value;
}).name('强度');
directionFolder.add(directionOpt.position, 'x', 10, 1000).onChange((value) => {
    directionalLight.position.set(value, directionOpt.position.y, directionOpt.position.z);
}).name('位置X轴');
directionFolder.add(directionOpt.position, 'y', 10, 1000).onChange((value) => {
    directionalLight.position.set(directionOpt.position.x, value, directionOpt.position.z);
}).name('位置y轴');
directionFolder.add(directionOpt.position, 'y', 10, 1000).onChange((value) => {
    directionalLight.position.set(directionOpt.position.x, directionOpt.position.y, value);
}).name('位置z轴');
//------辅助工具----
const helperFolder = gui.addFolder('辅助工具');
helperFolder.close();
const axesHelperFolder = helperFolder.addFolder('坐标轴');
axesHelperFolder.add(axesOpt, 'isOpen').onChange((value) => {
    if (value) {
        scene.add(axesHelper);
    } else {
        scene.remove(axesHelper);
    }
}).name('开启');
const pointHelperFolder = helperFolder.addFolder('点光源');
pointHelperFolder.add(pointHelper, 'isOpen').onChange((value) => {
    if (value) {
        scene.add(pointLightHelper);
    } else {
        scene.remove(pointLightHelper);
    }
}).name('开启');
//------动画-----
const animateFolder = gui.addFolder('动画');
animateFolder.close();
animateFolder.add(animateOpt, 'isOpen').onChange((value) => {
    if (value) {
        animateOpt.start();
    } else {
        animateOpt.stop();
    }
}).name('开启');
//加载汽车模型
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const progress = ref();//进度条值
// 创建GLTF加载器对象
const loader = new GLTFLoader();
// 加载环境贴图
const textureCube = new THREE.CubeTextureLoader()
.setPath('/img/')
.load(['hj1.jpg', 'hj2.jpg', 'hj3.jpg', 'hj4.jpg', 'hj5.jpg', 'hj6.jpg']);
//加载模型
loader.load('/img/moskvich_407.glb', function(gltf) {
    let carMesh = gltf.scene;
    carMesh.name = '第一辆车';
    carMesh.scale.set(0.3, 0.3, 0.3);
    carMesh.traverse((obj) => {
        // 环境贴图纹理对象textureCube作为.environment属性值,影响所有模型
        // scene.environment = textureCube;
        //车身2,含灯车身3
        if (obj.isMesh) {
            if (['Object_2', 'Object_3', 'Object_4'].includes(obj.name)) {
                //车身
                obj.material = new THREE.MeshPhysicalMaterial({
                    metalness: 1.0,//金属度属性
                    roughness: 0.4,//表面粗糙度
                    color: '#ff2626',
                    envMap: textureCube,//贴图
                    envMapIntensity: 3.0,//控制环境贴图对mesh表面影响程度  默认值1, 设置为0.0,相当于没有环境贴图
                    clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
                    clearcoatRoughness: 0.1//透明涂层表面的粗糙度
                });
                //------gui添加车辆-----
                const carFolder = gui.addFolder('车身');
                carFolder.add(obj.material, 'metalness', 0, 1).name('金属度');
                carFolder.add(obj.material, 'roughness', 0, 1).name('粗糙度');
                carFolder.add(obj.material, 'clearcoat', 0, 1).name('涂层的厚度');
                carFolder.add(obj.material, 'clearcoatRoughness', 0, 1).name('涂层表面的粗糙度');
                carFolder.add(obj.material, 'envMapIntensity', 0, 10).name('环境贴图影响');
            }
            if (['Object_6'].includes(obj.name)) {
                //玻璃
                obj.material = new THREE.MeshPhysicalMaterial({
                    color: obj.material.color, // 材质颜色
                    metalness: 0.0,//玻璃非金属
                    roughness: 0.0,//玻璃表面光滑
                    envMap: textureCube,//环境贴图
                    envMapIntensity: 1.0, //环境贴图对Mesh表面影响程度
                    transmission: 1.0, //玻璃材质透光率,transmission替代opacity
                    ior: 1.5//折射率
                });
                //------gui添加玻璃-----
                const blFolder = gui.addFolder('玻璃');
                blFolder.add(obj.material, 'metalness', 0, 1).name('金属度');
                blFolder.add(obj.material, 'roughness', 0, 1).name('粗糙度');
                blFolder.add(obj.material, 'transmission', 0, 1).name('玻璃材质透光率');
                blFolder.add(obj.material, 'ior', 0, 3).name('折射率');
                blFolder.add(obj.material, 'envMapIntensity', 0, 10).name('环境贴图影响');
            }
        }
    });
    //克隆另一辆车
    let blueCar = carMesh.clone();
    blueCar.name = '蓝色车';
    blueCar.position.set(30, 30, -300);
    blueCar.traverse((obj) => {
        if (obj.isMesh) {
            if (['Object_2', 'Object_3', 'Object_4'].includes(obj.name)) {
                //使材质互不影响
                obj.material = obj.material.clone();
                obj.material.color.set('#00e7fa');
            }
        }
    });
    scene.add(carMesh, blueCar);
    getMesh();
}, function(xhr) {
    //加载进度条
    const percent = xhr.loaded / xhr.total;
    // Math.floor:小数加载进度取整
    progress.value = Math.floor(percent * 100); //进度百分比
});
//下载canves
const down = () => {
    // 创建一个超链接元素,用来下载保存数据的文件
    const link = document.createElement('a');
    // 通过超链接herf属性,设置要保存到文件中的数据
    const canvas = renderer.domElement; //获取canvas对象
    link.href = canvas.toDataURL('image/png');
    link.download = 'threejs.png'; //下载文件名
    link.click(); //js代码触发超链接元素a的鼠标点击事件,开始下载文件到本地
};
//添加描边
const textBg = new THREE.BoxGeometry(300, 100, 20);
const textBgMater = new THREE.MeshBasicMaterial({
    color: '#3096fc'
});
const textMesh = new THREE.Mesh(textBg, textBgMater);
textMesh.position.set(-300, 100, 0);
scene.add(textMesh);
animateFolder.add(animateOpt, 'isOpen').onChange((value) => {
    if (value) {
        animateOpt.start();
    } else {
        animateOpt.stop();
    }
}).name('开启');
//后处理
// 引入后处理扩展库EffectComposer.js
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
// 引入渲染器通道RenderPass
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
// 引入OutlinePass描边通道
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
// 引入GlitchPass闪屏通道
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
// 创建后处理对象EffectComposer,WebGL渲染器作为参数
const composer = new EffectComposer(renderer);
// 1. 创建一个渲染器通道,场景和相机作为参数
const renderPass = new RenderPass(scene, camera);
// 设置renderPass通道
composer.addPass(renderPass);
// 3. 设置glitchPass通道
const glitchPass = new GlitchPass();
// composer.addPass(glitchPass);
//-------------------------------创建高亮发光描边样式
// OutlinePass第一个参数v2的尺寸和canvas画布保持一致
const v2 = new THREE.Vector2(window.innerWidth, window.innerHeight);
const outlinePass = new OutlinePass(v2, scene, camera);
//模型描边颜色,默认白色
outlinePass.visibleEdgeColor.set(0xffff00);
//高亮发光描边厚度
outlinePass.edgeThickness = 4;
//高亮描边发光强度
outlinePass.edgeStrength = 6;
//模型闪烁频率控制,默认0不闪烁
outlinePass.pulsePeriod = 2;
// 一个模型对象
outlinePass.selectedObjects = [textMesh];
// 设置OutlinePass通道
composer.addPass(outlinePass);
//-----------点击事件
const getMesh = () => {
    renderer.domElement.addEventListener('click', function(event) {
        // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
        const px = event.offsetX;
        const py = event.offsetY;
        //屏幕坐标px、py转WebGL标准设备坐标x、y
        //width、height表示canvas画布宽高度
        const x = (px / canvas.width) * 2 - 1;
        const y = -(py / canvas.height) * 2 + 1;
        //创建一个射线投射器`Raycaster`
        const raycaster = new THREE.Raycaster();
        //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
        // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
        raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
        //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
        // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
        const intersects = raycaster.intersectObjects(scene.children);
        console.log('射线器返回的对象', intersects);
        // intersects.length大于0说明,说明选中了模型
        if (intersects.length > 0) {
            // 选中模型的第一个模型,设置为红色
            intersects[0].object.material.color.set(0xff0000);
        }
    });
};
//----添加渲染,动画循环-----
const animate = () => {
    requestAnimationFrame(animate);
    //添加立方体运动
    if (animateOpt.isOpen) {
        animateOpt.start();
    }
    //通过通道渲染
    composer.render();
    // renderer.render(scene, camera);
    // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
    controls.addEventListener('change', function() {});//监听鼠标、键盘事件
};
onMounted(() => {
    //渲染器挂载dom上
    containerRef.value.appendChild(renderer.domElement);
    animate();
});
</script>
<style>
* {
    margin: 0;
    padding: 0;
}
.container {
    /*width: 1000px;*/
    /*height: 800px;*/
    /*background-color: red;*/
    margin: auto;
    overflow: hidden;
}
</style>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值