1,介绍
该示例使用的是 r95版本Three.js库。使用Threejs粒子效果实现下雨,下雪,阴天,晴天,火焰。
效果图如下:
2,主要说明
阴天,晴天实现方式,主要是替换场景的背景图片实现。
下雨、下雪、火焰主要用粒子实现,创建粒子有两种方式:
1,使用THREE.Points创建一个粒子集合,使用THREE.PointsMaterial来样式化粒子。
2,使用Three.Sprite()构造函数手工创建粒子。我们传入的唯一参数是材质,它只能是THREE.SpriteMaterial或THREE.SpriteCanvasMaterial。
Sprite适用创建少量的粒子,如果想创建大量粒子应该使用Points。
我这里使用Points实现下雨和下雪,主要代码如下:
function createPointRainy() { var img = rainy_sw == 1 ? "raindrop-4.png" : rainy_sw == 2 ? "snowflake3.png" : ""; var name = rainy_sw == 1 ? "particles_rainy" : rainy_sw == 2 ? "particles_snowy" : ""; var texture = new THREE.TextureLoader().load("assets/textures/particles/" + img); var geom = new THREE.Geometry(); var material = new THREE.PointsMaterial({ size: 1.5, transparent: true, // 是否设置透明度 opacity: 1, // 透明 map: texture, // 粒子材质 blending: THREE.AdditiveBlending, sizeAttenuation: true, // 是否相同尺寸 color: 0xffffff }); var range = 120; for (var i = 0; i < 1500; i++) { var particle = new THREE.Vector3( Math.random() * range - range / 2, Math.random() * range * 1.5, 1 + (i / 10 - 80) ) if (rainy_sw == 2) { // 定义雨滴以多快的速度落下,纵向运动速度的范围是0.1~0.3 particle.velocityY = (0.1 + Math.random() / 5) - 0.1; // 定义粒子(雨滴)如何水平移动,横向运动速度的范围是-0.16~+0.16 particle.velocityX = ((Math.random() - 0.5) / 3) - 0.05; } else { particle.velocityY = 0.15 + Math.random() / 5; particle.velocityX = (Math.random() - 0.5) / 3; } geom.vertices.push(particle); } cloud = new THREE.Points(geom, material); cloud.sortParticles = true; cloud.name = name; scene.add(cloud); }
使用Sprite实现火焰效果,主要代码如下:
function initFlame() { var texture = new THREE.TextureLoader().load("assets/textures/particles/flamex.png"); //sprite材质 var material = new THREE.SpriteMaterial({ //以canvas作为纹理 map: texture, //混合度 加法混合 blending: THREE.AdditiveBlending }); //循环1000 添加粒子 for (var i = 0; i < 2000; i++) { var particle = new THREE.Sprite(material); initParticle(particle, i); krq.add(particle); krq.name = "particles_flame"; } scene.add(krq); }
3,源码
<!DOCTYPE html> <html> <head> <title>Threejs实现下雨,下雪,阴天,晴天,火焰</title> <script type="text/javascript" src="libs/three.js"></script> <script type="text/javascript" src="libs/OrbitControls.js"></script> <script type="text/javascript" src="libs/OBJLoader.js"></script> <script type="text/javascript" src="libs/other/Tween.min.js"></script> <script type="text/javascript" src="libs/dat.gui.js"></script> <style> body { margin: 0; overflow: hidden; } </style> </head> <body> <div id="dom"></div> <script type="text/javascript"> var camera; var renderer; var cloud; var rainy_sw = 3; // 1雨2雪3晴4阴 var flame_sw = true; //初始化一个空容器,装载粒子 var krq = new THREE.Object3D(); var textureLoader = new THREE.TextureLoader(); function init() { // 创建一个场景,它将包含我们所有的元素,如物体,相机和灯光。 var scene = new THREE.Scene(); var urls = [ 'assets/textures/cubemap/flowers/posx.jpg', 'assets/textures/cubemap/flowers/negx.jpg', 'assets/textures/cubemap/flowers/posy.jpg', 'assets/textures/cubemap/flowers/negy.jpg', 'assets/textures/cubemap/flowers/posz.jpg', 'assets/textures/cubemap/flowers/negz.jpg' ]; var urls1 = [ 'assets/textures/cubemap/flowers/posx1.jpg', 'assets/textures/cubemap/flowers/negx1.jpg', 'assets/textures/cubemap/flowers/posy1.jpg', 'assets/textures/cubemap/flowers/negy1.jpg', 'assets/textures/cubemap/flowers/posz1.jpg', 'assets/textures/cubemap/flowers/negz1.jpg' ]; var cubeLoader = new THREE.CubeTextureLoader(); scene.background = cubeLoader.load(urls); // 创建一个摄像机,它定义了我们正在看的地方 camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // 将摄像机对准场景的中心 camera.position.x = 10; camera.position.y = 50; camera.position.z = 90; camera.lookAt(scene.position); var orbit = new THREE.OrbitControls(camera); // 创建一个渲染器并设置大小,WebGLRenderer将会使用电脑显卡来渲染场景 renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true, }); renderer.setClearColor(new THREE.Color(0x121A39)); renderer.setSize(window.innerWidth, window.innerHeight); var alight = new THREE.AmbientLight("#ffffff", 1); alight.name = "aLight"; scene.add(alight); // 在屏幕上显示坐标轴 var axes = new THREE.AxesHelper(100); // scene.add(axes); // 将平面添加到场景中 createPlaneGeometryBasicMaterial(); // 将呈现器的输出添加到HTML元素 document.getElementById("dom").appendChild(renderer.domElement); // 使用GUI调试库 var controls = new function() { this.rainy = function() { scene.remove(scene.getObjectByName("particles_snowy")); if (rainy_sw != 1) { rainy_sw = 1; scene.background = cubeLoader.load(urls1); scene.getObjectByName("aLight").intensity = 0.6; createPointRainy(); } } this.snowy = function() { scene.remove(scene.getObjectByName("particles_rainy")); if (rainy_sw != 2) { rainy_sw = 2; scene.background = cubeLoader.load(urls1); scene.getObjectByName("aLight").intensity = 2; createPointRainy(); } } this.sunny = function() { if (rainy_sw != 3) { scene.remove(scene.getObjectByName("particles_rainy")); scene.remove(scene.getObjectByName("particles_snowy")); scene.background = cubeLoader.load(urls); scene.getObjectByName("aLight").intensity = 1.2; rainy_sw = 3; } } this.cloudy = function() { if (rainy_sw != 4) { scene.remove(scene.getObjectByName("particles_rainy")); scene.remove(scene.getObjectByName("particles_snowy")); scene.background = cubeLoader.load(urls1); scene.getObjectByName("aLight").intensity = 1; rainy_sw = 4; } } this.flame = function() { if (flame_sw) { initFlame(); flame_sw = !flame_sw; } } } var gui = new dat.GUI(); gui.add(controls, 'rainy'); // 雨 gui.add(controls, 'snowy'); // 雪 gui.add(controls, 'sunny'); // 晴 gui.add(controls, 'cloudy'); // 阴 gui.add(controls, 'flame'); // 火焰 // 启动动画 renderScene(); function createPointRainy() { var img = rainy_sw == 1 ? "raindrop-4.png" : rainy_sw == 2 ? "snowflake3.png" : ""; var name = rainy_sw == 1 ? "particles_rainy" : rainy_sw == 2 ? "particles_snowy" : ""; var texture = new THREE.TextureLoader().load("assets/textures/particles/" + img); var geom = new THREE.Geometry(); var material = new THREE.PointsMaterial({ size: 1.5, transparent: true, // 是否设置透明度 opacity: 1, // 透明 map: texture, // 粒子材质 blending: THREE.AdditiveBlending, sizeAttenuation: true, // 是否相同尺寸 color: 0xffffff }); var range = 120; for (var i = 0; i < 1500; i++) { var particle = new THREE.Vector3( Math.random() * range - range / 2, Math.random() * range * 1.5, 1 + (i / 10 - 80) ) if (rainy_sw == 2) { // 定义雨滴以多快的速度落下,纵向运动速度的范围是0.1~0.3 particle.velocityY = (0.1 + Math.random() / 5) - 0.1; // 定义粒子(雨滴)如何水平移动,横向运动速度的范围是-0.16~+0.16 particle.velocityX = ((Math.random() - 0.5) / 3) - 0.05; } else { particle.velocityY = 0.15 + Math.random() / 5; particle.velocityX = (Math.random() - 0.5) / 3; } geom.vertices.push(particle); } cloud = new THREE.Points(geom, material); cloud.sortParticles = true; cloud.name = name; scene.add(cloud); } function initFlame() { var texture = new THREE.TextureLoader().load("assets/textures/particles/flamex.png"); //sprite材质 var material = new THREE.SpriteMaterial({ //以canvas作为纹理 map: texture, //混合度 加法混合 blending: THREE.AdditiveBlending }); //循环1000 添加粒子 for (var i = 0; i < 2000; i++) { var particle = new THREE.Sprite(material); initParticle(particle, i); krq.add(particle); krq.name = "particles_flame"; } scene.add(krq); } /** * 粒子 延迟发散 * @param particle * @param delay */ function initParticle(particle, delay) { particle.position.set(5, Math.random() + 5, 0); particle.scale.x = particle.scale.y = Math.random() * 3; //下面是一系列的动画 var xx = Math.random() * 10 - 5; var yy = Math.cos((Math.PI / 100) * xx) * 20; //位移 new TWEEN.Tween(particle.position) .delay(delay) .to({ x: xx, y: yy, z: Math.random() * 10 - 5 }, 2000) .onComplete(function() { initParticle(particle, delay); }) .start(); // 大小 new TWEEN.Tween(particle.scale) .delay(delay) .to({ x: 0.01, y: 0.01 }, 1000) .start(); } /** * 创建地面并添加材质 * wrapS属性定义的是纹理沿x轴方向的行为,而warpT属性定义的是纹理沿y轴方向的行为。 * Three.js为这些属性提供了如下两个选项: * ·THREE.RepeatWrapping允许纹理重复自己。 * ·THREE.ClampToEdgeWrapping是属性的默认值。 * 属性值为THREE.ClampToEdgeWrapping时,那么纹理的整体不会重复,只会重复纹理边缘的像素来填满剩下的空间。 */ function createPlaneGeometryBasicMaterial() { var cubeMaterial = new THREE.MeshStandardMaterial({ map: textureLoader.load("assets/textures/stone/cd.jpg"), side: THREE.DoubleSide, }); cubeMaterial.map.wrapS = THREE.RepeatWrapping; cubeMaterial.map.wrapT = THREE.RepeatWrapping; cubeMaterial.map.repeat.set(3, 3) // 创建地平面并设置大小 var planeGeometry = new THREE.PlaneGeometry(100, 100); var plane = new THREE.Mesh(planeGeometry, cubeMaterial); // 设置平面位置并旋转 plane.rotation.x = -0.5 * Math.PI; plane.position.x = 0; plane.position.z = -5; plane.position.y = -5; scene.add(plane); } function renderScene() { orbit.update(); // 拖动 TWEEN.update(); if (cloud) { var vertices = cloud.geometry.vertices; vertices.forEach(function(v) { v.y = v.y - (v.velocityY); v.x = v.x - (v.velocityX); if (v.y <= 0) v.y = 60; if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1; }); cloud.geometry.verticesNeedUpdate = true; } // 使用requestAnimationFrame函数进行渲染 requestAnimationFrame(renderScene); renderer.render(scene, camera); } // 渲染的场景 renderer.render(scene, camera); } window.onload = init; // 随着窗体的变化修改场景 function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } // 监听窗体调整大小事件 window.addEventListener('resize', onResize, false); </script> </body> </html>