大家好,欢迎来到IT知识分享网。
粒子特效可以实现非常多的效果,如星空、烟雾、雨、灰尘、火等。粒子特效的优势是即使使用了成百上千的例子,也能保证比较高的帧率。
缺点是每个粒子都由一个始终面向相机的平面(两个三角形)组成。
点材质也是three.js最简单的类之一,相对于基类Material,它多做的事情只是传递了size,即点的尺寸这个值。
着色器1
<script id="vertex-shader" type="x-shader/x-vertex"> // // GLSL textureless classic 2D noise "cnoise", // with an RSL-style periodic variant "pnoise". // Author: Stefan Gustavson () // Version: 2011-08-22 // // Many thanks to Ian McEwan of Ashima Arts for the // ideas for permutation and gradient selection. // // Copyright (c) 2011 Stefan Gustavson. All rights reserved. // Distributed under the MIT license. See LICENSE file. // https://github.com/ashima/webgl-noise // vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } vec4 taylorInvSqrt(vec4 r) { return 1.159 - 0.314 * r; } vec2 fade(vec2 t) { return t*t*t*(t*(t*6.0-15.0)+10.0); } // Classic Perlin noise float cnoise(vec2 P) { vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0); vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0); Pi = mod289(Pi); // To avoid truncation effects in permutation vec4 ix = Pi.xzxz; vec4 iy = Pi.yyww; vec4 fx = Pf.xzxz; vec4 fy = Pf.yyww; vec4 i = permute(permute(ix) + iy); vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ; vec4 gy = abs(gx) - 0.5 ; vec4 tx = floor(gx + 0.5); gx = gx - tx; vec2 g00 = vec2(gx.x,gy.x); vec2 g10 = vec2(gx.y,gy.y); vec2 g01 = vec2(gx.z,gy.z); vec2 g11 = vec2(gx.w,gy.w); vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11))); g00 *= norm.x; g01 *= norm.y; g10 *= norm.z; g11 *= norm.w; float n00 = dot(g00, vec2(fx.x, fy.x)); float n10 = dot(g10, vec2(fx.y, fy.y)); float n01 = dot(g01, vec2(fx.z, fy.z)); float n11 = dot(g11, vec2(fx.w, fy.w)); vec2 fade_xy = fade(Pf.xy); vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x); float n_xy = mix(n_x.x, n_x.y, fade_xy.y); return 2.3 * n_xy; } float map(float value, float oldMin, float oldMax, float newMin, float newMax) { return newMin + (newMax - newMin) * (value - oldMin) / (oldMax - oldMin); } varying vec3 vUv; varying float vTime; varying float vZ; uniform float time; void main() { vUv = position; vTime = time; vec3 newPos = position; vec2 peak = vec2(1.0 - abs(.5 - uv.x), 1.0 - abs(.5 - uv.y)); vec2 noise = vec2( map(cnoise(vec2(0.3 * time + uv.x * 5., uv.y * 5.)), 0., 1., -2., (peak.x * peak.y * 30.)), map(cnoise(vec2(-0.3 * time + uv.x * 5., uv.y * 5.)), 0., 1., -2., 25.) ); //newPos.x += noise.x * 10.; newPos.z += noise.x * .06 * noise.y; vZ = newPos.z; vec4 mvPosition = modelViewMatrix * vec4( newPos, 1.0 ); gl_PointSize = 5.0; gl_Position = projectionMatrix * mvPosition; } </script> <script id="fragment-shader" type="x-shader/x-fragment"> varying vec3 vUv; varying float vTime; varying float vZ; uniform sampler2D texture; float map(float value, float oldMin, float oldMax, float newMin, float newMax) { return newMin + (newMax - newMin) * (value - oldMin) / (oldMax - oldMin); } void main() { vec3 colorA = vec3(.6, 0.17, 0.17); vec3 colorB = vec3(0.17, 0.8, .7); //vec3 color = mix(colorA, colorB, vUv.x * vUv.y); float alpha = map(vZ / 2., -1. / 2., 30. / 2., 0.17, 1.); vec3 color = vec3(.5, .5, .6); gl_FragColor = vec4( color, alpha); //gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord ); } </script>
着色器2
<!-- built files will be auto injected --> <!--粒子背景相关脚本--> <script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 position; attribute float scale; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; void main() { vec4 mvPosition = modelViewMatrix * position; gl_PointSize = scale*1.0 * ( 200.0 / - mvPosition.z ); gl_Position = projectionMatrix * mvPosition; } </script> <script id="fragmentShader" type="x-shader/x-fragment"> void main() { if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.49 ) discard; //gl_FragColor = vec4(0.0,1.0,1.0,1.0); // 根据片元的x坐标,来设置片元的像素值 if(gl_FragCoord.x < 120.0){ // canvas画布上[0,20)之间片元像素值设置 //gl_FragColor = vec4(1.0,0.0,0.0,1.0); // 片元沿着x方向渐变 gl_FragColor = vec4(gl_FragCoord.x/1000.0*0.1,1.0,1.0,0.1); }else if (gl_FragCoord.x <= 1800.0) { // canvas画布上(20,1900]之间片元像素值设置 //gl_FragColor = vec4(0.0,1.0,0.0,1.0); // 片元沿着y方向渐变 gl_FragColor = vec4(0.0,gl_FragCoord.y/3000.0*1.0,1.0,0.1); }else { // canvas画布上(1900,1920]之间片元像素值设置 //gl_FragColor = vec4(0.0,0.0,1.0,1.0); // 片元沿着z方向渐变 gl_FragColor = vec4(0.0,1.0,gl_FragCoord.z/1000.0*1.0,0.1); } } </script>
依赖importmap
<script type="importmap"> { "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.162.0/+esm", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.162.0/examples/jsm/", "lil-gui": "https://threejsfundamentals.org/3rdparty/dat.gui.module.js", "@tweenjs/tween.js": "https://cdn.jsdelivr.net/npm/@tweenjs/tween.js@23.1.1/dist/tween.esm.js", "canvas-confetti": "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/+esm" } } </script>
模块module代码
<script type="module"> import * as THREE from 'three'; import * as TWEEN from '@tweenjs/tween.js'; import confetti from 'canvas-confetti'; import { GUI } from 'lil-gui'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; function drawWaveParticle(){ let div = document.getElementById('webgl'); let canvasWebgl = document.createElement('canvas'); canvasWebgl.width = parseInt(window.innerWidth); canvasWebgl.height = parseInt(window.innerHeight); canvasWebgl.style.position = 'absolute'; canvasWebgl.style.zIndex = -1; div.appendChild(canvasWebgl); let gl = canvasWebgl.getContext('webgl'); let vertexShaderSource = document.getElementById('vertexShader').innerText; let fragShaderSource = document.getElementById('fragmentShader').innerText; let program = initShader(gl, vertexShaderSource, fragShaderSource); let aposLocation = gl.getAttribLocation(program, 'position'); let scale = gl.getAttribLocation(program, 'scale'); let modelViewMatrixLoc = gl.getUniformLocation(program, 'modelViewMatrix'); let projectionMatrixLoc = gl.getUniformLocation(program, 'projectionMatrix'); let SEPARATION = 100,AMOUNTX = 50,AMOUNTY = 50; let numParticles = AMOUNTX * AMOUNTY; let positions = new Float32Array(numParticles * 3); let scales = new Float32Array(numParticles); let i = 0, j = 0; for (let ix = 0; ix < AMOUNTX; ix++) { for (let iy = 0; iy < AMOUNTY; iy++) { positions[i] = ix * SEPARATION - ((AMOUNTX * SEPARATION) / 2); // x positions[i + 1] = 0; // y positions[i + 2] = iy * SEPARATION - ((AMOUNTY * SEPARATION) / 2); // z scales[j] = 1; i += 3; j++; } } let colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW); gl.vertexAttribPointer(scale, 1, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(scale); let buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(aposLocation); gl.enable(gl.DEPTH_TEST); let width = window.innerWidth; let height = window.innerHeight; let camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000); camera.position.set(200, 300, 200); camera.position.set(944, 206, -262); camera.lookAt(new THREE.Vector3(0, 0, 0)); camera.updateProjectionMatrix() camera.updateMatrixWorld(true) let mat4 = new THREE.Matrix4(); mat4.copy(camera.projectionMatrix) let mxArr = new Float32Array(mat4.elements); gl.uniformMatrix4fv(projectionMatrixLoc, false, mxArr); let mat4y = new THREE.Matrix4(); mat4y.copy(camera.matrixWorldInverse); //console.log(camera.matrixWorldInverse); let myArr = new Float32Array(mat4y.elements); gl.uniformMatrix4fv(modelViewMatrixLoc, false, myArr); let count = 0; let mouseX = 0,mouseY = 0; let windowHalfX = window.innerWidth / 2; let windowHalfY = window.innerHeight / 2; function draw() { camera.position.x += (mouseX - camera.position.x) * 0.001; camera.updateMatrixWorld(true) mat4y.copy(camera.matrixWorldInverse); let myArr = new Float32Array(mat4y.elements); gl.uniformMatrix4fv(modelViewMatrixLoc, false, myArr); let i = 0,j = 0; for (let ix = 0; ix < AMOUNTX; ix++) { for (let iy = 0; iy < AMOUNTY; iy++) { positions[i + 1] = (Math.sin((ix + count) * 0.3) * 50) + (Math.sin((iy + count) * 0.5) * 50); scales[j] = (Math.sin((ix + count) * 0.3) + 1.3) * 8 + (Math.sin((iy + count) * 0.5) + 1.3) * 8; i += 3; j++; } } count += 0.1; gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, scales, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); requestAnimationFrame(draw); gl.drawArrays(gl.POINTS, 0, 2500); } draw(); function initShader(gl, vertexShaderSource, fragmentShaderSource) { let vertexShader = gl.createShader(gl.VERTEX_SHADER); let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(vertexShader); gl.compileShader(fragmentShader); let program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); return program; } document.addEventListener('mousemove', onDocumentMouseMove, false); document.addEventListener('touchstart', onDocumentTouchStart, false); document.addEventListener('touchmove', onDocumentTouchMove, false); function onDocumentMouseMove(event) { mouseX = event.clientX - windowHalfX; mouseY = event.clientY - windowHalfY; } function onDocumentTouchStart(event) { if (event.touches.length === 1) { event.preventDefault(); mouseX = event.touches[0].pageX - windowHalfX; mouseY = event.touches[0].pageY - windowHalfY; } } function onDocumentTouchMove(event) { if (event.touches.length === 1) { event.preventDefault(); mouseX = event.touches[0].pageX - windowHalfX; mouseY = event.touches[0].pageY - windowHalfY; } } } class SceneViewer { constructor(options) { this.$el = options.el; this.time = 0; this.bindAll(); this.init(); } bindAll() { this.render = this.render.bind(this); this.resize = this.resize.bind(this); } init() { this.textureLoader = new THREE.TextureLoader(); this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000); this.camera.lookAt(new THREE.Vector3(0, 0, 0)); let boxSize = {width: 1,height: 1, thickness: 1}; const fov = 45; const angle = fov / 2; // 夹角 const rad = THREE.MathUtils.degToRad(angle); // 转为弧度值 const distanceZ = boxSize.width / 2 / Math.tan(rad) * 10; / * 调整相机的 X 轴位置,让视野能同时看到盒子顶部和侧面 * 调整相机的 Z 轴位置,使盒子完整显示到场景 */ this.camera.position.set(0, 1, distanceZ); this.scene = new THREE.Scene(); this.renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setSize(window.innerWidth, window.innerHeight); let geometry = new THREE.BoxGeometry(boxSize.width, boxSize.height, boxSize.tickness); let material = new THREE.MeshNormalMaterial(); this.mesh = new THREE.Mesh(geometry, material); this.scene.add(this.mesh); /* 相机轨道控制器 */ new OrbitControls(this.camera, this.renderer.domElement); const axesHelper = new THREE.AxesHelper(10); // 辅助坐标轴 const gridHelper = new THREE.GridHelper(10, 10); // 辅助网格线 this.scene.add(axesHelper, gridHelper); const ambientLight = new THREE.AmbientLight('#fff', 1); // 环境光 const directLight = new THREE.DirectionalLight('#fff', 3); // 平行光 this.scene.add(ambientLight, directLight); const cubeMaterial = new THREE.MeshLambertMaterial({ 'color': 'gray', }) const cubeGeometry = new THREE.CylinderGeometry(0.5, 1, 1) const cube = new THREE.Mesh(cubeGeometry, cubeMaterial) this.scene.add(cube) this.$el.appendChild(this.renderer.domElement); this.createParticles(); this.createLights(); this.bindEvents(); this.resize(); this.render(); } createLights(){ let directionX = 10, directionY = 10, directionZ = 10; const hemisphere = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6); // move the light right, up, and towards us hemisphere.position.set(10, 10, 10); const ambient = new THREE.AmbientLight(0xffffff, 1); // 环境光 const spot = new THREE.SpotLight(0xfdf4d5); spot.position.set(5, directionY * 4, 0); spot.angle = Math.PI / 2; spot.power = 2000; // eslint-disable-next-line @typescript-eslint/no-unused-vars const spotLightHelper = new THREE.SpotLightHelper(spot, 0x00f); const direct = new THREE.DirectionalLight(0xffffff, 3); // 平行光 direct.position.set(-directionX / 3, directionY * 4, directionZ * 1.5); direct.castShadow = true; direct.shadow.camera.left = -directionX; direct.shadow.camera.right = directionX; direct.shadow.camera.top = directionZ; direct.shadow.camera.bottom = -directionZ; // eslint-disable-next-line @typescript-eslint/no-unused-vars const directLightHelper = new THREE.DirectionalLightHelper(direct, 1, 0xf00); this.scene.add(hemisphere) this.scene.add(ambient) this.scene.add(spot) this.scene.add(direct) } createParticles() { //const plane = new THREE.PlaneBufferGeometry(500, 250, 250, 125); const plane = new THREE.PlaneGeometry(500, 250, 250, 125); const textureLoader = new THREE.TextureLoader(); textureLoader.crossOrigin = ''; const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 1.0 }, texture: { value: textureLoader.load("https://s3-us-west-2.amazonaws.com/s.cdpn.io//spark1.png") }, resolution: { value: new THREE.Vector2() } }, vertexShader: document.getElementById('vertex-shader').textContent, fragmentShader: document.getElementById('fragment-shader').textContent, blending: THREE.AdditiveBlending, depthTest: false, transparent: true }); //console.log(material.uniforms.texture); //const material = new THREE.PointsMaterial( { size: 1 } ); this.particles = new THREE.Points(plane, material); this.particles.rotation.x = this.degToRad(-90); this.scene.add(this.particles); } bindEvents() { // window.addEventListener('mousemove', this.mousemove); window.addEventListener('resize', this.resize); } resize() { const w = window.innerWidth; const h = window.innerHeight; this.renderer.setSize(w, h); this.camera.aspect = w / h; this.camera.updateProjectionMatrix(); } moveParticles() { this.particles.material.uniforms.time.value = this.time; this.particles.material.needsUpdate = true; } // Animations render() { requestAnimationFrame(this.render); this.time += .01; this.mesh.rotation.x += 0.01; this.mesh.rotation.y += 0.02; this.moveParticles(); this.renderer.render(this.scene, this.camera); } // Utils degToRad(angle) { return angle * Math.PI / 180; } } initViewer = ()=>{ drawWaveParticle(); //let container = document.getElementById(domId); let container = document.querySelector('#webgl'); let element = {el: container}; new SceneViewer(element); } </script>
挂载到DOM渲染
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#000000" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="force-rendering" content="webkit">
<meta name="google-site-verification" content="FTeR0c8arOPKh8c5DYh_9uu98_zJbaWw53J-Sch9MTg">
<meta data-rh="true" name="keywords" content="three.js实现粒子动画">
<meta data-rh="true" name="description" content="three.js实现粒子动画">
<meta data-rh="true" property="og:title" content="three.js实现粒子动画">
<link rel="icon" href="./favicon.ico">
<title>three.js实现粒子动画</title>
<style>
body {
padding: 0;
margin: 0;
font: normal 14px/1.42857 Tahoma;
background: radial-gradient(#a1b2c3, #123456);
}
.container {
position: relative;
}
.mask{
width: 100vw;
height: 100vh;
position: absolute;
left: 0;
background: radial-gradient(#123456, #c1d1e1);
z-index: -1;
}
#webgl{
position: absolute;
left: 0;
z-index: 1;
}
</style>
</head>
<body onload="initViewer()">
<div class="container">
<!-- 遮罩 -->
<div class="mask"></div>
<div id="webgl"></div>
</div>
<script>
let initViewer = null
</script>
</body>
</html>
粒子场景效果
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/140177.html