three.js学习(1)

文章详细介绍了如何使用THREE.js库在React应用中创建一个可交互的3D场景,包括设置相机、光照、网格、材质,以及使用GUI进行用户界面控制和动画效果的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<template>
    <div ref="containerRef"
         :style="{width:container.width+'px',height:container.height+'px',background:container.color}"
         class="container"></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: 1000,
    height: 800,
    color: '#5cb85c'
});
//画布
const canvas = reactive({
    width: 800,
    height: 500,
    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 box = reactive({
    type: 'boxGeometry',
    width: 50,
    height: 50,
    depth: 50,
    x: 1,
    y: 1,
    z: 1,
    radius: 50,
    radiusTop: 10,
    radiusBottom: 50
});
//材质
const materialOpt = reactive({
    type: 'lambertMaterial',
    color: 0x00ff00,
    transparent: false,
    opacity: 0.5,
    shininess: 100, //高光部分的亮度,默认30
    specular: 0x444444, //高光部分的颜色
    side: THREE.DoubleSide //两面可见  //THREE.FrontSide, //默认只有正面可见
});
//添加点光源
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 meshOpt=reactive({
    x:0,
    y:0,
    z:0
})
//辅助工具--坐标轴
const axesOpt=reactive({
    size:150,
    isOpen:true
})
//辅助工具--点光源
const pointHelper=reactive({
    sphereSize:1,
    isOpen:true
})
//---动画设置----
const animateOpt=reactive({
    isOpen:false,
    start:()=>{
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.01;
    },
    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//开启抗锯齿
});
// 获取你屏幕对应的设备像素比.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 getGeometry = (type) => {
    switch (type) {
        //立方体
        case 'boxGeometry':
            return new THREE.BoxGeometry(box.width, box.height, box.depth);
            break;
        //球形
        case 'sphereGeometry':
            return new THREE.SphereGeometry(box.radius);
            break;
        //圆柱体
        case 'cylinderGeometry':
            return new THREE.CylinderGeometry(box.radiusTop, box.radiusBottom, box.height);
            break;
        //矩形平面
        case 'planeGeometry':
            return new THREE.PlaneGeometry(box.width, box.height);
            break;
        //圆平面
        case 'circleGeometry':
            return new THREE.CircleGeometry(box.radius);
            break;
    }
};
let geometry = getGeometry('boxGeometry');
//----材质类型获取------
const getMaterial = (type) => {
    switch (type) {
        case 'lambertMaterial':
            return new THREE.MeshLambertMaterial({
                color: materialOpt.color,
                transparent: materialOpt.transparent,  //开启透明
                opacity: materialOpt.opacity
            });
            break;
        case 'phongMaterial':
            new THREE.MeshPhongMaterial({
                color: materialOpt.color,
                shininess: materialOpt.shininess, //高光部分的亮度,默认30
                specular: materialOpt.specular //高光部分的颜色
            });
            break;
    }
};
let material = getMaterial('lambertMaterial');
//----------添加网格--(geometry, material放在其中)
let mesh = new THREE.Mesh(geometry, material);
//设置网格--模型位置
mesh.position.set(meshOpt.x, meshOpt.y, meshOpt.z);
scene.add(mesh); //场景中添加
//========光源============
//点光源
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);}


//----添加渲染,动画循环-----
const animate = () => {
    requestAnimationFrame(animate);
    //添加立方体运动
    if(animateOpt.isOpen){
        animateOpt.start()
    }
    renderer.render(scene, camera);
    // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
    controls.addEventListener('change', function() {});//监听鼠标、键盘事件
};
//设置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 boxFolder = gui.addFolder('物体');
boxFolder.add(box, 'type', {
    立方体: 'boxGeometry',
    球形: 'sphereGeometry',
    圆柱体: 'cylinderGeometry',
    矩形平面: 'planeGeometry',
    圆形平面: 'circleGeometry'
}).onChange((value) => {
    scene.remove(mesh);
    geometry = getGeometry(value);
    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh); //网格模型添加到场景中
}).name('类型');
boxFolder.add(box, 'x', 0.1, 10).onChange((value) => {
    mesh.scale.x = value;
}).name('x缩放');
boxFolder.add(box, 'y', 0.1, 10).onChange((value) => {
    mesh.scale.y = value;
}).name('y缩放');
boxFolder.add(box, 'z', 0.1, 10).onChange((value) => {
    mesh.scale.z = value;
}).name('y缩放');
boxFolder.close()
//材质
const materFolder = gui.addFolder('材质');
materFolder.add(materialOpt, 'type', { 漫反射: 'lambertMaterial', 镜面反射: 'phongMaterial' }).onChange((value) => {
    scene.remove(mesh);
    material = getMaterial(value);
    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh); //网格模型添加到场景中
}).name('类型');
materFolder.addColor(materialOpt, 'color').onChange((value) => {
    mesh.material.color.set(value)
}).name('颜色');
materFolder.add(materialOpt, 'transparent').onChange((value) => {
    material.transparent=value
    material.needsUpdate = true; // 确保材质更新
}).name('开启透视');
materFolder.add(materialOpt, 'opacity',0.1,1).onChange((value) => {
    material.opacity=value
    material.needsUpdate = true; // 确保材质更新
}).name('透明度').step(0.1);
materFolder.close()
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('开启');
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、付费专栏及课程。

余额充值