Three.js3D模型与粒子系统

368 阅读10分钟

目录


基本模型加载

导入库:

首先,你需要在HTML文件中引入Three.js库,如果是CDN,可以这样添加:

   <script src="https://cdn.jsdelivr.net/npm/three@0.145.0/build/three.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/three@0.145.0/examples/js/loaders/GLTFLoader.js"></script>

GLTFLoader用于加载.gltf.glb格式的模型。

初始化场景、相机和渲染器:

在JavaScript中设置Three.js的基本元素:

   const scene = new THREE.Scene();
   const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
   const renderer = new THREE.WebGLRenderer();
   renderer.setSize(window.innerWidth, window.innerHeight);
   document.body.appendChild(renderer.domElement);

加载模型: 使用GLTFLoader加载模型文件:

   const loader = new THREE.GLTFLoader();
   loader.load('path_to_model.gltf', function(gltf) {
       const model = gltf.scene;
       // 将模型添加到场景中
       scene.add(model);

       // 如果需要处理模型的骨骼,可以这样:
       const skeletonHelper = new THREE.SkeletonHelper(model);
       skeletonHelper.visible = false; // 默认隐藏骨骼,可根据需求调整
       scene.add(skeletonHelper);
   }, undefined, function(error) {
       console.error(error);
   });

load方法接受模型文件的URL,加载成功后会调用回调函数,其中gltf对象包含了模型的所有数据。

设置动画(如果有的话):

如果模型包含动画,你可以从gltf.animations中获取并应用到模型上:

   const mixer = new THREE.AnimationMixer(model);
   gltf.animations.forEach((clip) => {
       mixer.clipAction(clip).play();
   });

更新和渲染:

在每一帧中,你需要更新混合器和渲染场景:

   function animate() {
       requestAnimationFrame(animate);
       mixer.update(delta); // delta 是时间差,通常由THREE.Clock提供
       renderer.render(scene, camera);
   }
   animate();

事件监听:

根据需要,你还可以添加鼠标或触摸事件来交互控制模型。

请注意,不同格式的模型(如.fbx.obj等)可能需要不同的加载器,例如FBXLoaderOBJLoader。确保正确引入对应的加载器,并使用相应的加载方法。此外,模型的路径应根据实际部署情况调整,确保浏览器能够正确访问到模型文件。

3D模型操作

设置光照:

为了使模型看起来更真实,通常需要添加光源:

   const ambientLight = new THREE.AmbientLight(0x404040); // soft white light
   scene.add(ambientLight);

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

缩放模型:

可以通过修改模型的scale属性来缩放模型:

   model.scale.set(0.5, 0.5, 0.5); // 缩小到原来的50%

移动模型: 调整模型的位置:

   model.position.set(0, -1, -2); // 移动到XYZ坐标为(0, -1, -2)

旋转模型: 设置模型的旋转角度:

   model.rotation.set(0, Math.PI / 2, 0); // 使模型绕Y轴旋转90度

交互控制:

如果你想让用户可以通过鼠标或触摸来旋转、平移或缩放模型,可以使用OrbitControls插件:

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

   const controls = new OrbitControls(camera, renderer.domElement);
   controls.update();

自定义材质和颜色:

对于某些3D模型,你可能希望改变其材质或颜色。这需要深入理解Three.js的材质系统。例如,如果你的模型使用了MeshStandardMaterial,你可以这样做:

   const materials = model.material;
   if (Array.isArray(materials)) {
       materials.forEach(mat => mat.color.setHex(0xff0000)); // 设置所有材质为红色
   } else {
       materials.color.setHex(0xff0000); // 单一材质设置为红色
   }

以上就是Three.js加载和处理3D模型的一些常见操作。请根据项目需求进行选择和调整。

功能优化

加载纹理:

有些模型可能需要纹理贴图,你可以使用TextureLoader加载纹理并应用到材质上:

   const textureLoader = new THREE.TextureLoader();

   textureLoader.load('path_to_texture.png', function(texture) {
       const material = new THREE.MeshStandardMaterial({ map: texture });
       // 将材质应用到模型上,这可能需要根据模型结构来实现
       model.traverse(node => {
           if (node.isMesh) {
               node.material = material;
           }
       });
   });

阴影: 如果需要,可以启用阴影以增加场景的真实感:

   renderer.shadowMap.enabled = true;
   renderer.shadowMap.type = THREE.PCFSoftShadowMap;

   // 确保光源和接收阴影的对象都开启阴影支持
   directionalLight.castShadow = true;
   model.receiveShadow = true;

性能优化:

  • LOD(Level of Detail):对于大模型,可以使用LOD来根据相机距离加载不同细节级别的模型。
  • 批处理渲染:将多个相似的几何体组合成一个批次,减少渲染次数。
  • 剔除:使用THREE.BackSide或THREE.FrontSide来剔除不需要渲染的面。
  • 缓存和复用:避免重复加载和创建相同的资源。

动画帧率控制:

使用requestAnimationFrame的替代方案,如THREE.Clock,以控制动画的帧率:

    const clock = new THREE.Clock();
    
    function animate() {
        const delta = clock.getDelta(); // 获取时间差
        mixer.update(delta);
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
    }
    animate();

响应式设计:

当窗口大小变化时,调整相机和渲染器的尺寸:

javascript
    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }, false);

错误处理和日志记录:

为了调试和优化,确保捕获和记录加载或渲染过程中可能出现的错误。

粒子系统基础

基础的粒子系统

使用THREE.ParticleSystemTHREE.ParticleBasicMaterial实现:

// 导入Three.js库
import * as THREE from 'three';

// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建粒子几何体
const particleCount = 1000;
const particles = new THREE.Geometry();
for (let i = 0; i < particleCount; i++) {
    const particle = new THREE.Vector3(
        Math.random() * 200 - 100,
        Math.random() * 200 - 100,
        Math.random() * 200 - 100
    );
    particles.vertices.push(particle);
}

// 创建粒子材质
const particleMaterial = new THREE.PointsMaterial({
    color: 0xFFFFFF,
    size: 10,
    map: new THREE.TextureLoader().load('path_to_particle_texture.png'), // 如果有纹理
    blending: THREE.AdditiveBlending,
    transparent: true
});

// 创建粒子系统
const particleSystem = new THREE.Points(particles, particleMaterial);
scene.add(particleSystem);

// 更新粒子位置(简单示例,实际可能需要动画循环)
function updateParticles() {
    for (let i = 0; i < particles.vertices.length; i++) {
        const vertex = particles.vertices[i];
        vertex.y -= 0.1; // 假设粒子向下移动
        if (vertex.y < -100) {
            vertex.y = 100; // 重置粒子位置
        }
    }
    particles.verticesNeedUpdate = true; // 通知粒子系统更新
}

// 渲染场景
function animate() {
    requestAnimationFrame(animate);
    updateParticles();
    renderer.render(scene, camera);
}
animate();

上面创建了一个简单的粒子系统,每个粒子随机分布在3D空间中,然后以一定的速度向下移动。当粒子到达屏幕底部时,它们会被重置到顶部。注意,粒子的运动和行为在这个示例中是非常基础的,实际的粒子系统可能需要更复杂的动画逻辑。

自定义着色器创建粒子爆炸效果

首先,定义两个着色器:

particleVertexShaderparticleFragmentShader,它们定义粒子的行为和外观:

// particleVertexShader.glsl
uniform float time;
attribute vec3 velocity;

void main() {
    vec3 newPosition = position + velocity * time;
    vec4 mvPosition = modelViewMatrix * vec4(newPosition, 1.0);
    gl_PointSize = 10.0;
    gl_Position = projectionMatrix * mvPosition;
}
// particleFragmentShader.glsl
uniform vec3 color;

void main() {
    gl_FragColor = vec4(color, 1.0);
}

然后在JavaScript中创建自定义材质并加载着色器:

// 加载着色器
const particleVertexShader = document.getElementById('particleVertexShader').textContent;
const particleFragmentShader = document.getElementById('particleFragmentShader').textContent;

// 创建粒子材质
const particleMaterial = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0 },
        color: { value: new THREE.Color(0xffffff) },
    },
    vertexShader: particleVertexShader,
    fragmentShader: particleFragmentShader,
    blending: THREE.AdditiveBlending,
    transparent: true,
    depthWrite: false,
});

接下来,创建粒子系统:

// 创建粒子
const particleCount = 1000;
const positions = new Float32Array(particleCount * 3);
const velocities = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);

// 初始化粒子位置、速度和颜色
// ... 这部分取决于你的粒子行为

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

const particleSystem = new THREE.Points(geometry, particleMaterial);
scene.add(particleSystem);

最后,更新粒子系统:

function animate() {
    requestAnimationFrame(animate);
    const time = performance.now() / 1000; // 获取当前时间
    particleMaterial.uniforms.time.value = time; // 更新时间 uniform

    // 更新粒子位置,速度,颜色等
    // ... 这部分取决于你的粒子行为

    renderer.render(scene, camera);
}
animate();

three-emitter库

three-emitter库提供了Emitter类,简化了创建粒子发射器的过程。它允许你定义粒子的生命周期、速度、颜色变化等。以下是一个使用three-emitter库创建粒子发射器的例子:

首先,确保安装了three-emitter库:

npm install three three-emitter

然后在代码中导入并使用它:

import * as THREE from 'three';
import { Emitter, Particle } from 'three-emitter';

// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建粒子材质
const particleMaterial = new THREE.PointsMaterial({
    color: 0xffffff,
    size: 10,
    blending: THREE.AdditiveBlending,
    transparent: true
});

// 创建粒子发射器
const emitter = new Emitter({
    rate: new Emitter.Constant(1), // 每秒发射1个粒子
    life: new Emitter.Range(2, 4), // 粒子生命周期2-4秒
    position: new Emitter.Box(new THREE.Vector3(-50, 0, -50), new THREE.Vector3(50, 100, 50)), // 发射范围
    velocity: new Emitter.Range(new THREE.Vector3(-10, 20, -10), new THREE.Vector3(10, 20, 10)), // 初始速度范围
    acceleration: new Emitter.Constant(new THREE.Vector3(0, -10, 0)), // 重力
    color: new Emitter.Range(new THREE.Color(0xff0000), new THREE.Color(0x00ff00)), // 颜色范围
    size: new Emitter.Range(1, 5), // 粒子大小范围
});

// 创建粒子几何体并添加发射器
const particleGeo = new THREE.Geometry();
emitter.create(particleGeo, particleMaterial);

// 添加到场景
const particleSystem = new THREE.Points(particleGeo, particleMaterial);
scene.add(particleSystem);

// 更新和渲染
function animate() {
    requestAnimationFrame(animate);
    emitter.update(0.01); // 更新粒子状态
    renderer.render(scene, camera);
}
animate();

在这个例子中,我们创建了一个粒子发射器,粒子从一个立方体区域内的随机位置发射,初始速度、颜色和大小都是随机的。粒子受到一个向下的加速度,模拟重力效果。粒子的生命周期也在2到4秒之间随机,颜色从红色渐变到绿色。每秒发射一个粒子,但你可以根据需要调整rate参数。

常见粒子系统特效

火花效果

火花通常使用白色或暖色调的粒子,具有向上或向外扩散的运动。

   const sparkMaterial = new THREE.PointsMaterial({
       color: 0xffaa00,
       size: 1,
       map: new THREE.TextureLoader().load('spark_texture.png'),
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   const sparkPositions = new Float32Array(sparkCount * 3);
   // ... 初始化火花位置和速度

   const sparkGeometry = new THREE.BufferGeometry();
   sparkGeometry.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));

   const sparkPoints = new THREE.Points(sparkGeometry, sparkMaterial);
   scene.add(sparkPoints);

   // 更新火花位置
   function updateSparks() {
       // ... 根据速度和时间更新火花位置
   }

烟雾效果

烟雾通常是灰白色的,可以使用THREE.Points和烟雾纹理。

   const smokeMaterial = new THREE.PointsMaterial({
       color: 0xaaaaaa,
       size: 2,
       map: new THREE.TextureLoader().load('smoke_texture.png'),
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   const smokePositions = new Float32Array(smokeCount * 3);
   // ... 初始化烟雾位置和速度

   const smokeGeometry = new THREE.BufferGeometry();
   smokeGeometry.setAttribute('position', new THREE.BufferAttribute(smokePositions, 3));

   const smokePoints = new THREE.Points(smokeGeometry, smokeMaterial);
   scene.add(smokePoints);

   // 更新烟雾位置
   function updateSmoke() {
       // ... 根据速度和时间更新烟雾位置
   }

雨雪效果

雨滴和雪花可以使用类似的方法,但可能需要不同的纹理和动画逻辑。

   const snowMaterial = new THREE.PointsMaterial({
       color: 0xffffff,
       size: 0.5,
       map: new THREE.TextureLoader().load('snow_texture.png'),
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   const snowPositions = new Float32Array(snowCount * 3);
   // ... 初始化雪花位置和速度

   const snowGeometry = new THREE.BufferGeometry();
   snowGeometry.setAttribute('position', new THREE.BufferAttribute(snowPositions, 3));

   const snowPoints = new THREE.Points(snowGeometry, snowMaterial);
   scene.add(snowPoints);

   // 更新雪花位置
   function updateSnow() {
       // ... 根据速度和时间更新雪花位置
   }

火焰效果

火焰通常使用复杂的颜色变化和动画。可以使用THREE.Points配合自定义着色器来实现。

   // 定义火焰着色器
   // ... 类似上面的粒子着色器,但要包含火焰颜色变化的计算

   const flameMaterial = new THREE.ShaderMaterial({
       uniforms: {
           // ... 包括火焰颜色、时间等uniforms
       },
       vertexShader: flameVertexShader,
       fragmentShader: flameFragmentShader,
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   // 初始化火焰粒子
   // ... 创建火焰粒子的位置、颜色和速度

   const flamePoints = new THREE.Points(flameGeometry, flameMaterial);
   scene.add(flamePoints);

   // 更新火焰
   function updateFlame() {
       // ... 更新火焰颜色、位置等
   }

爆炸效果

爆炸通常涉及多个粒子层,从中心向外扩散,并逐渐消失。可以使用多个粒子系统和不同的颜色变化来模拟。

   // 创建多个粒子系统,每个系统代表爆炸的不同阶段
   const explosionMaterials = [/* ... */];
   const explosionGeometries = [/* ... */];

   for (let i = 0; i < explosionMaterials.length; i++) {
       const explosionPoints = new THREE.Points(explosionGeometries[i], explosionMaterials[i]);
       scene.add(explosionPoints);
   }

   // 更新爆炸粒子
   function updateExplosion() {
       // ... 更新每个粒子系统的位置、颜色、透明度等
   }

光晕效果

光晕可以模拟发光或模糊的效果,通常使用半透明粒子和自定义着色器。

   const haloMaterial = new THREE.ShaderMaterial({
       uniforms: {
           glowColor: { value: new THREE.Color(0x00ffff) },
           glowIntensity: { value: 1.0 },
           blur: { value: 1.0 },
           resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
       },
       vertexShader: haloVertexShader,
       fragmentShader: haloFragmentShader,
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   const haloPoints = new THREE.Points(haloGeometry, haloMaterial);
   scene.add(haloPoints);

   // 更新光晕
   function updateHalo() {
       // ... 更新光晕的颜色、强度、模糊程度等
   }

喷射流效效果

喷射流可以模拟水流、火焰或其他流动效果。

   const jetMaterial = new THREE.PointsMaterial({
       color: 0x00ff00,
       size: 0.5,
       map: new THREE.TextureLoader().load('jet_texture.png'),
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   const jetPositions = new Float32Array(jetCount * 3);
   // ... 初始化喷射流位置和速度

   const jetGeometry = new THREE.BufferGeometry();
   jetGeometry.setAttribute('position', new THREE.BufferAttribute(jetPositions, 3));

   const jetPoints = new THREE.Points(jetGeometry, jetMaterial);
   scene.add(jetPoints);

   // 更新喷射流
   function updateJet() {
       // ... 根据速度和时间更新喷射流位置
   }

旋转涡旋效果

涡旋可以模拟旋转的气流或漩涡。

   const vortexMaterial = new THREE.PointsMaterial({
       color: 0x990000,
       size: 1,
       map: new THREE.TextureLoader().load('vortex_texture.png'),
       blending: THREE.AdditiveBlending,
       transparent: true,
   });

   const vortexPositions = new Float32Array(vortexCount * 3);
   // ... 初始化涡旋位置和速度

   const vortexGeometry = new THREE.BufferGeometry();
   vortexGeometry.setAttribute('position', new THREE.BufferAttribute(vortexPositions, 3));

   const vortexPoints = new THREE.Points(vortexGeometry, vortexMaterial);
   scene.add(vortexPoints);

   // 更新涡旋
   function updateVortex() {
       // ... 更新涡旋的位置和旋转
   }