Skip to content
刘恩义的技术博客
返回

ThreeJS 3D Web 开发实战

Edit page

ThreeJS 是一个强大的 JavaScript 库,用于在浏览器中创建和展示 3D 图形。它基于 WebGL 技术,提供了简单易用的 API,使得 3D Web 开发变得更加容易。本文将详细介绍 ThreeJS 3D Web 开发的核心概念、基本用法和高级技巧,包括场景搭建、模型加载、材质应用、光照设置、动画实现等,帮助你快速掌握 ThreeJS 开发技能。

1. ThreeJS 简介

1.1 什么是 ThreeJS?

ThreeJS 是一个用于在浏览器中创建和展示 3D 图形的 JavaScript 库。它封装了 WebGL 的复杂性,提供了简单易用的 API,使得开发者可以更专注于 3D 场景的创建和交互逻辑的实现。

1.2 ThreeJS 的核心概念

1.3 ThreeJS 的应用场景

2. 环境搭建

2.1 安装 ThreeJS

使用 npm

npm install three

使用 CDN

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

2.2 基本项目结构

├── index.html
├── package.json
├── src/
│   ├── main.js
│   ├── components/
│   ├── scenes/
│   └── utils/
└── public/
    └── models/

2.3 基本示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ThreeJS Basic Example</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // 创建场景
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xf0f0f0);
        
        // 创建相机
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 5;
        
        // 创建渲染器
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        
        // 创建几何体
        const geometry = new THREE.BoxGeometry();
        
        // 创建材质
        const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
        
        // 创建网格
        const cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
        
        // 动画循环
        function animate() {
            requestAnimationFrame(animate);
            
            // 旋转立方体
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
            
            // 渲染场景
            renderer.render(scene, camera);
        }
        
        // 响应窗口大小变化
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        
        // 启动动画
        animate();
    </script>
</body>
</html>

3. 核心组件

3.1 场景(Scene)

创建场景

const scene = new THREE.Scene();

// 设置场景背景
scene.background = new THREE.Color(0xf0f0f0);

// 添加雾化效果
scene.fog = new THREE.Fog(0xf0f0f0, 1, 10);

// 添加环境贴图
const loader = new THREE.CubeTextureLoader();
loader.load([
    'textures/posx.jpg',
    'textures/negx.jpg',
    'textures/posy.jpg',
    'textures/negy.jpg',
    'textures/posz.jpg',
    'textures/negz.jpg'
], (texture) => {
    scene.environment = texture;
});

3.2 相机(Camera)

透视相机

// 创建透视相机
const camera = new THREE.PerspectiveCamera(
    75, // 视野角度
    window.innerWidth / window.innerHeight, // 宽高比
    0.1, // 近裁剪面
    1000 // 远裁剪面
);

// 设置相机位置
camera.position.set(0, 0, 5);

// 看向场景中心
camera.lookAt(0, 0, 0);

正交相机

// 创建正交相机
const camera = new THREE.OrthographicCamera(
    window.innerWidth / -2, // 左
    window.innerWidth / 2, // 右
    window.innerHeight / 2, // 上
    window.innerHeight / -2, // 下
    0.1, // 近裁剪面
    1000 // 远裁剪面
);

// 设置相机位置
camera.position.set(0, 0, 5);

// 看向场景中心
camera.lookAt(0, 0, 0);

3.3 渲染器(Renderer)

创建渲染器

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
    antialias: true, // 启用抗锯齿
    alpha: true // 启用透明背景
});

// 设置渲染器大小
renderer.setSize(window.innerWidth, window.innerHeight);

// 设置像素比
renderer.setPixelRatio(window.devicePixelRatio);

// 启用阴影
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

// 添加到 DOM
document.body.appendChild(renderer.domElement);

3.4 几何体(Geometry)

内置几何体

// 立方体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);

// 球体
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);

// 圆柱体
const cylinderGeometry = new THREE.CylinderGeometry(1, 1, 2, 32);

// 平面
const planeGeometry = new THREE.PlaneGeometry(2, 2);

//  torus
const torusGeometry = new THREE.TorusGeometry(1, 0.3, 16, 100);

自定义几何体

// 创建自定义几何体
const geometry = new THREE.BufferGeometry();

// 顶点数据
const vertices = new Float32Array([
    -1.0, -1.0,  1.0,
     1.0, -1.0,  1.0,
     1.0,  1.0,  1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0, -1.0,  1.0
]);

// 颜色数据
const colors = new Float32Array([
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0,
    0.0, 0.0, 1.0,
    0.0, 0.0, 1.0,
    1.0, 0.0, 0.0,
    0.0, 1.0, 0.0
]);

// 设置顶点属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

3.5 材质(Material)

基础材质

// 基础材质(不响应光照)
const basicMaterial = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: false,
    transparent: false,
    opacity: 1
});

//  Lambert 材质(响应漫反射光照)
const lambertMaterial = new THREE.MeshLambertMaterial({
    color: 0x00ff00,
    emissive: 0x000000,
    transparent: false,
    opacity: 1
});

//  Phong 材质(响应漫反射和高光)
const phongMaterial = new THREE.MeshPhongMaterial({
    color: 0x00ff00,
    emissive: 0x000000,
    specular: 0xffffff,
    shininess: 30,
    transparent: false,
    opacity: 1
});

//  Standard 材质(基于物理的渲染)
const standardMaterial = new THREE.MeshStandardMaterial({
    color: 0x00ff00,
    emissive: 0x000000,
    metalness: 0,
    roughness: 0.5,
    transparent: false,
    opacity: 1
});

纹理材质

// 加载纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('textures/earth.jpg');

// 创建纹理材质
const textureMaterial = new THREE.MeshStandardMaterial({
    map: texture,
    metalness: 0,
    roughness: 1
});

3.6 光源(Light)

环境光

// 创建环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

平行光

// 创建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
scene.add(directionalLight);

点光源

// 创建点光源
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(0, 10, 0);
pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 2048;
pointLight.shadow.mapSize.height = 2048;
pointLight.shadow.camera.near = 0.5;
pointLight.shadow.camera.far = 50;
scene.add(pointLight);

聚光灯

// 创建聚光灯
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(0, 10, 0);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.camera.near = 0.5;
spotLight.shadow.camera.far = 50;
spotLight.shadow.camera.fov = 30;
scene.add(spotLight);

3.7 网格(Mesh)

创建网格

// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);

// 创建材质
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });

// 创建网格
const mesh = new THREE.Mesh(geometry, material);

// 设置位置
mesh.position.set(0, 0, 0);

// 设置旋转
mesh.rotation.set(0, 0, 0);

// 设置缩放
mesh.scale.set(1, 1, 1);

// 启用阴影
mesh.castShadow = true;
mesh.receiveShadow = true;

// 添加到场景
scene.add(mesh);

4. 模型加载

4.1 OBJ 模型加载

依赖

npm install three-obj-loader

使用示例

import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';

// 创建加载器
const loader = new OBJLoader();

// 加载模型
loader.load('models/model.obj', (object) => {
    // 设置材质
    object.traverse((child) => {
        if (child.isMesh) {
            child.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
        }
    });
    
    // 设置位置和缩放
    object.position.set(0, 0, 0);
    object.scale.set(1, 1, 1);
    
    // 添加到场景
    scene.add(object);
}, (xhr) => {
    // 加载进度
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
    // 加载错误
    console.error('An error happened', error);
});

4.2 GLTF 模型加载

使用示例

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

// 创建加载器
const loader = new GLTFLoader();

// 加载模型
loader.load('models/model.gltf', (gltf) => {
    // 获取模型
    const model = gltf.scene;
    
    // 设置位置和缩放
    model.position.set(0, 0, 0);
    model.scale.set(1, 1, 1);
    
    // 添加到场景
    scene.add(model);
    
    // 播放动画
    if (gltf.animations && gltf.animations.length > 0) {
        const mixer = new THREE.AnimationMixer(model);
        const action = mixer.clipAction(gltf.animations[0]);
        action.play();
        
        // 在动画循环中更新
        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();
            mixer.update(delta);
            renderer.render(scene, camera);
        }
        animate();
    }
}, (xhr) => {
    // 加载进度
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
    // 加载错误
    console.error('An error happened', error);
});

4.3 FBX 模型加载

使用示例

import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';

// 创建加载器
const loader = new FBXLoader();

// 加载模型
loader.load('models/model.fbx', (object) => {
    // 设置材质
    object.traverse((child) => {
        if (child.isMesh) {
            child.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
        }
    });
    
    // 设置位置和缩放
    object.position.set(0, 0, 0);
    object.scale.set(0.01, 0.01, 0.01); // FBX 模型通常很大
    
    // 添加到场景
    scene.add(object);
    
    // 播放动画
    const mixer = new THREE.AnimationMixer(object);
    const action = mixer.clipAction(object.animations[0]);
    action.play();
    
    // 在动画循环中更新
    function animate() {
        requestAnimationFrame(animate);
        const delta = clock.getDelta();
        mixer.update(delta);
        renderer.render(scene, camera);
    }
    animate();
}, (xhr) => {
    // 加载进度
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
    // 加载错误
    console.error('An error happened', error);
});

5. 动画实现

5.1 基础动画

使用 requestAnimationFrame

// 创建时钟
const clock = new THREE.Clock();

// 动画循环
function animate() {
    requestAnimationFrame(animate);
    
    // 获取时间增量
    const delta = clock.getDelta();
    
    // 更新动画
    mesh.rotation.x += 0.01;
    mesh.rotation.y += 0.01;
    
    // 渲染场景
    renderer.render(scene, camera);
}

// 启动动画
animate();

5.2 关键帧动画

使用 AnimationMixer

// 创建动画混合器
const mixer = new THREE.AnimationMixer(mesh);

// 创建关键帧轨道
const positionTrack = new THREE.KeyframeTrack(
    '.position',
    [0, 1, 2], // 时间
    [0, 0, 0, 1, 0, 0, 0, 0, 0] // 位置数据
);

const rotationTrack = new THREE.KeyframeTrack(
    '.rotation',
    [0, 1, 2], // 时间
    [0, 0, 0, 0, Math.PI * 2, 0, 0, 0, 0] // 旋转数据
);

// 创建动画剪辑
const clip = new THREE.AnimationClip('Action', 2, [positionTrack, rotationTrack]);

// 创建动画动作
const action = mixer.clipAction(clip);

// 播放动画
action.play();

// 在动画循环中更新
function animate() {
    requestAnimationFrame(animate);
    const delta = clock.getDelta();
    mixer.update(delta);
    renderer.render(scene, camera);
}

animate();

5.3 路径动画

使用 CatmullRomCurve3

// 创建曲线
const curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-10, 0, 0),
    new THREE.Vector3(-5, 5, 0),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(5, -5, 0),
    new THREE.Vector3(10, 0, 0)
]);

// 创建曲线的可视化
const points = curve.getPoints(50);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
const curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);

// 动画循环
let time = 0;
function animate() {
    requestAnimationFrame(animate);
    
    // 更新时间
    time += 0.001;
    if (time > 1) time = 0;
    
    // 获取曲线上的点
    const point = curve.getPoint(time);
    mesh.position.copy(point);
    
    // 使网格看向曲线的下一个点
    const nextPoint = curve.getPoint((time + 0.01) % 1);
    mesh.lookAt(nextPoint);
    
    // 渲染场景
    renderer.render(scene, camera);
}

animate();

6. 交互实现

6.1 鼠标交互

使用 Raycaster

// 创建射线投射器
const raycaster = new THREE.Raycaster();

// 创建鼠标向量
const mouse = new THREE.Vector2();

// 鼠标移动事件
window.addEventListener('mousemove', (event) => {
    // 计算鼠标在归一化设备坐标中的位置
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});

// 鼠标点击事件
window.addEventListener('click', () => {
    // 更新射线
    raycaster.setFromCamera(mouse, camera);
    
    // 计算与射线相交的对象
    const intersects = raycaster.intersectObjects(scene.children, true);
    
    if (intersects.length > 0) {
        // 获取第一个相交对象
        const object = intersects[0].object;
        
        // 处理点击事件
        console.log('Clicked on:', object.name);
        
        // 例如,改变颜色
        if (object.isMesh) {
            object.material.color.set(Math.random() * 0xffffff);
        }
    }
});

6.2 触摸交互

使用 TouchEvent

// 触摸开始事件
window.addEventListener('touchstart', (event) => {
    // 获取第一个触摸点
    const touch = event.touches[0];
    
    // 计算触摸点在归一化设备坐标中的位置
    mouse.x = (touch.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1;
    
    // 更新射线
    raycaster.setFromCamera(mouse, camera);
    
    // 计算与射线相交的对象
    const intersects = raycaster.intersectObjects(scene.children, true);
    
    if (intersects.length > 0) {
        // 处理触摸事件
        const object = intersects[0].object;
        console.log('Touched on:', object.name);
    }
});

// 触摸移动事件
window.addEventListener('touchmove', (event) => {
    // 获取第一个触摸点
    const touch = event.touches[0];
    
    // 计算触摸点在归一化设备坐标中的位置
    mouse.x = (touch.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1;
});

6.3 控制器

使用 OrbitControls

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);

// 设置控制器属性
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 100;
controls.maxPolarAngle = Math.PI / 2;

// 在动画循环中更新控制器
function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
}

animate();

使用 FirstPersonControls

import { FirstPersonControls } from 'three/examples/jsm/controls/FirstPersonControls.js';

// 创建控制器
const controls = new FirstPersonControls(camera, renderer.domElement);

// 设置控制器属性
controls.movementSpeed = 10;
controls.lookSpeed = 0.05;
controls.lookVertical = true;

// 在动画循环中更新控制器
function animate() {
    requestAnimationFrame(animate);
    const delta = clock.getDelta();
    controls.update(delta);
    renderer.render(scene, camera);
}

animate();

7. 性能优化

7.1 几何体优化

// 使用 BufferGeometry
const geometry = new THREE.BufferGeometry();

// 合并几何体
const geometries = [geometry1, geometry2, geometry3];
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);

// 使用实例化渲染
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const count = 1000;
const mesh = new THREE.InstancedMesh(geometry, material, count);

// 设置实例矩阵
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
    matrix.makeTranslation(
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 10
    );
    mesh.setMatrixAt(i, matrix);
}

scene.add(mesh);

7.2 材质优化

7.3 渲染优化

7.4 内存管理

// 释放几何体
geometry.dispose();

// 释放材质
material.dispose();

// 释放纹理
texture.dispose();

// 清理事件监听器
window.removeEventListener('resize', onResize);

8. 实战案例

8.1 3D 地球仪

功能需求

实现

// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000033);

// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 2;

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 加载纹理
const textureLoader = new THREE.TextureLoader();
const earthTexture = textureLoader.load('textures/earth.jpg');
const normalTexture = textureLoader.load('textures/earth-normal.jpg');

// 创建地球几何体
const geometry = new THREE.SphereGeometry(1, 64, 64);

// 创建地球材质
const material = new THREE.MeshStandardMaterial({
    map: earthTexture,
    normalMap: normalTexture,
    metalness: 0,
    roughness: 1
});

// 创建地球网格
const earth = new THREE.Mesh(geometry, material);
scene.add(earth);

// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 3, 5);
scene.add(directionalLight);

// 添加控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;

// 响应窗口大小变化
window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});

// 动画循环
function animate() {
    requestAnimationFrame(animate);
    
    // 地球自转
    earth.rotation.y += 0.001;
    
    // 更新控制器
    controls.update();
    
    // 渲染场景
    renderer.render(scene, camera);
}

// 启动动画
animate();

8.2 3D 产品展示

功能需求

实现

// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);

// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);

const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.set(-5, -5, -5);
scene.add(pointLight);

// 加载模型
const loader = new GLTFLoader();
let model;

loader.load('models/product.gltf', (gltf) => {
    model = gltf.scene;
    model.position.set(0, 0, 0);
    model.scale.set(1, 1, 1);
    scene.add(model);
}, (xhr) => {
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
    console.error('An error happened', error);
});

// 添加控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;

// 响应窗口大小变化
window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});

// 鼠标交互
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

window.addEventListener('click', (event) => {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true);
    
    if (intersects.length > 0) {
        const object = intersects[0].object;
        console.log('Clicked on:', object.name);
        
        // 改变材质颜色
        if (object.isMesh) {
            object.material.color.set(Math.random() * 0xffffff);
        }
    }
});

// 动画循环
function animate() {
    requestAnimationFrame(animate);
    
    // 更新模型旋转
    if (model) {
        model.rotation.y += 0.005;
    }
    
    // 更新控制器
    controls.update();
    
    // 渲染场景
    renderer.render(scene, camera);
}

// 启动动画
animate();

8.3 3D 游戏场景

功能需求

实现

// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);

// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
scene.add(directionalLight);

// 创建地面
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.5;
ground.receiveShadow = true;
scene.add(ground);

// 创建墙壁
const wallGeometry = new THREE.BoxGeometry(10, 2, 0.1);
const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });

const wall1 = new THREE.Mesh(wallGeometry, wallMaterial);
wall1.position.set(0, 0.5, 5);
scene.add(wall1);

const wall2 = new THREE.Mesh(wallGeometry, wallMaterial);
wall2.position.set(0, 0.5, -5);
scene.add(wall2);

const wall3 = new THREE.Mesh(wallGeometry, wallMaterial);
wall3.rotation.y = Math.PI / 2;
wall3.position.set(5, 0.5, 0);
scene.add(wall3);

const wall4 = new THREE.Mesh(wallGeometry, wallMaterial);
wall4.rotation.y = Math.PI / 2;
wall4.position.set(-5, 0.5, 0);
scene.add(wall4);

// 添加控制器
const controls = new FirstPersonControls(camera, renderer.domElement);
controls.movementSpeed = 5;
controls.lookSpeed = 0.05;
controls.lookVertical = true;

// 响应窗口大小变化
window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});

// 动画循环
const clock = new THREE.Clock();
function animate() {
    requestAnimationFrame(animate);
    const delta = clock.getDelta();
    controls.update(delta);
    
    // 碰撞检测
    if (camera.position.y < 0) {
        camera.position.y = 0;
    }
    
    if (camera.position.x > 4.5) {
        camera.position.x = 4.5;
    }
    if (camera.position.x < -4.5) {
        camera.position.x = -4.5;
    }
    
    if (camera.position.z > 4.5) {
        camera.position.z = 4.5;
    }
    if (camera.position.z < -4.5) {
        camera.position.z = -4.5;
    }
    
    // 渲染场景
    renderer.render(scene, camera);
}

// 启动动画
animate();

9. 最佳实践总结

9.1 代码组织

9.2 性能优化

9.3 交互设计

9.4 部署

10. 总结

ThreeJS 是一个强大的 JavaScript 库,用于在浏览器中创建和展示 3D 图形。通过本文的学习,你应该已经掌握了 ThreeJS 的核心概念、基本用法和高级技巧,包括场景搭建、模型加载、材质应用、光照设置、动画实现等。

ThreeJS 的应用场景非常广泛,从产品展示到游戏开发,从数据可视化到虚拟实境,都可以使用 ThreeJS 来实现。随着 WebGL 技术的不断发展,ThreeJS 也在不断更新和改进,为开发者提供了更强大、更易用的 API。

要成为一名优秀的 ThreeJS 开发者,你需要不断学习和实践,掌握 3D 图形学的基本原理,了解 WebGL 的工作机制,熟悉 ThreeJS 的 API 和最佳实践。通过不断地尝试和创新,你可以创建出令人惊艳的 3D Web 应用。

希望本文能够帮助你快速入门 ThreeJS 开发,开启你的 3D Web 开发之旅!


Edit page

Previous Post
Fabric.js Canvas 操作实战
Next Post
Spring Boot 安全最佳实践