Three 之 three.js (webgl)使用BufferGeometry (GPU) 根据位置和移动向量简单实现持续生成运动的简单粒子particle运动效果

Three 之 three.js (webgl)使用BufferGeometry (GPU) 根据位置和移动向量简单实现持续生成运动的简单粒子particle运动效果

目录

Three 之 three.js (webgl)使用BufferGeometry (GPU) 根据位置和移动向量简单实现持续生成运动的简单粒子particle运动效果

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、实现步骤

六、关键代码

七、源码


一、简单介绍

Three js 开发的一些知识整理,方便后期遇到类似的问题,能够及时查阅使用。

本节介绍, three.js (webgl) 中,使用 BufferGeometry ,添加位置和移动方向数据,结合 shader,简单实现类似 particle 的粒子运动效果,这里把之前BufferGeometry 实现简单粒子效果 Three 之 three.js (webgl)使用BufferGeometry (CPU) 根据位置和移动向量简单实现持续生成运动的简单粒子particle运动效果 在 js (cpu)中实现的移动显隐等逻辑,改在 glsl shader (gpu)中实现,减轻了 cpu 的负担,性能上得以改善,重新实现 BufferGeometry 简单粒子效果,如果有不足之处,欢迎指出,或者你有更好的方法,欢迎留言。

BufferGeometry 官网相关介绍:three.js docs

BufferGeometry 官网使用案例:three.js examples

GLSL 中文手册:https://github.com/wshxbqq/GLSL-Card

二、实现原理

1、把位置和移动向量等数据,首先会重新把数据整理成两波数据,以便于模拟粒子一波波出现的效果

2、然后把数据传入给 BufferGeometry 对应的 setAttribute 属性中

3、并结合 ShaderMaterial 控制显示粒子大小旋转等数据显示

4、在 Animation 只需传入时间time,其他的移动,颜色变化、旋转等逻辑都在 glsl shader 中 实现,处理 BufferGeometry  每个点的移动和 alpha 透明度,从而实现粒子效果的持续生成运动

             // 移动位移数据处理
            float useTime = pointTime > 0.1 ? timeTwo : time;
            vec3 startPos = position + useTime * windDirection * 0.02;
          	vec4 mvPosition = modelViewMatrix * vec4( startPos, 1.0 );

            // 大小处理
          	gl_PointSize = size * ( 300.0 / - mvPosition.z );


             // 颜色 alpha 处理
                vec4 tColor = vColor;
               float useTime = vPointTime > 0.1 ? timeTwo : time;
               if(useTime < maxTime/2.0){
                 tColor.a = useTime/maxTime *2.0;
               }else if(useTime > maxTime * 2.0 / 3.0){
                 tColor.a = (maxTime - useTime)/maxTime * 3.0;
               }else{
                 tColor.a = 1.0;
               }


            // uv 旋转
             vec2 uv = gl_PointCoord.xy - vec2(0.5, 0.5);
             float angle = vRotAngle + useTime * 0.1;
              uv = vec2(uv.x *cos(angle) - uv.y * sin(angle),uv.x *sin(angle) + uv.y*cos(angle));
              uv += vec2(0.5, 0.5);
              gl_FragColor = gl_FragColor * texture2D( pointTexture, uv );

三、注意事项

1、这里只进行了两波数据来模拟粒子效果的不断生成效果,可能根据需要自行拓展三波及以上的不断生成效果,达到使用吧

2、这里传入不同 png 图片可以简单实现不同的粒子效果,比如绿叶、红花、雪花、气泡等

3、其中,粒子的大小和颜色、旋转等也能自行控制

四、效果预览

五、实现步骤

1、为了方便学习,这里是基于 Github 代码,进行开发的,大家可以下载官网代码,很多值得学习的案例

GitHub - mrdoob/three.js: JavaScript 3D Library.

gitcode:mirrors / mrdoob / three.js · GitCode

2、创建 BufferGeometry 模拟粒子效果的脚本,来管理粒子效果的生成 

3、在上面的基础上,添加一个 html ,用来实现案例效果,引入相关包 

4、创建基础场景

5、构建 位置、移动向量、粒子大小、颜色、旋转等数据,并传入粒子图片,创建 BufferGeometryGpuParticleEffect 

6、完成其他的基础代码编写,运行脚本,效果如下

六、关键代码

1、TestBufferGeometryGpuParticleEffect.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>20TestBufferGeometryGpuParticleEffect</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
	</head>
	<body>

		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>

		<script type="importmap">
			{
				"imports": {
					"three": "../../../build/three.module.js"
				}
			}
		</script>

		<script type="module">
			// 引入 three 基础库
			import * as THREE from 'three';
			import Stats from '../../jsm/libs/stats.module.js';
			import { OrbitControls } from './../../jsm/controls/OrbitControls.js';
			
			import { BufferGeometryGpuParticleEffect } from './BufferGeometryParticle/BufferGeometryGpuParticleEffect.js'

			let camera, renderer, scene,controls;
			let object;
			let stats;
			let mBufferGeometryGpuParticleEffect;
			
			const params = {
				enableFpsRender: false,
				enableRightNowRender: false
			
			};

			init();
			animate();

			function init() {

				// 渲染器
				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setClearColor('#cccccc');
				document.body.appendChild( renderer.domElement );

				// camera
				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.z = 8;

				// 场景
				scene = new THREE.Scene();

			

				// 添加环境光
				scene.add( new THREE.AmbientLight( 0x222222 ) );

				// 添加方向光
				const light = new THREE.DirectionalLight( 0xffffff );
				light.position.set( 1, 1, 1 );
				scene.add( light );

				// 窗口尺寸变化监听
				window.addEventListener( 'resize', onWindowResize );
				
				
				
				stats = new Stats();
				document.body.appendChild( stats.dom );

				controls = new OrbitControls( camera, renderer.domElement );
	
				createParticle(scene)
			}
			
			function createParticle(scene){
				const pointCount = 30
				const positions = []	// 点的位置
				const colors = []	// 点的颜色
				const moveDirs = []	// 点的运动方向
				const sizes = [];	// 点的大小
				const rotAngles = [];	// 点的方向
				
				for (var i = 0; i < pointCount; i++) {
					positions[3 * i] = Math.random() *4-2
					positions[3 * i +1] = Math.random() *4-2
					positions[3 * i+2] = Math.random() *4-2
				
					colors[4 * i] = Math.random()*4-2
					colors[4 * i +1] = Math.random()*4-2
					colors[4 * i+2] = Math.random()
					colors[4 * i+3] = 0.0
				
					moveDirs[3 * i] = Math.random() *2 -1
					moveDirs[3 * i +1] = Math.random()*2 -1
					moveDirs[3 * i+2] = Math.random()*2 -1
					
					sizes[i] = Math.random() * 1.0
					
					rotAngles[i] = Math.random() * Math.PI
				}
				
				mBufferGeometryGpuParticleEffect = new BufferGeometryGpuParticleEffect(positions,moveDirs,colors,sizes,rotAngles,'textures/left.png')
				mBufferGeometryGpuParticleEffect.addWindGeometry(scene)
			}

			function onWindowResize() {

				// camera 更新 aspect
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

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

			}
			

			function animate() {

				requestAnimationFrame( animate );

				renderer.render( scene, camera );
				mBufferGeometryGpuParticleEffect.instancingGeometryUpdate()
				stats.update();
				controls.update()
			}

		</script>
	</body>
</html>

2、BufferGeometryGpuParticleEffect

import {
  BufferGeometry,
  Mesh,
  CircleGeometry,
  ShaderMaterial,
  NormalBlending,
  DoubleSide,
  Clock,
  Points,
  Float32BufferAttribute,TextureLoader,Color,DynamicDrawUsage
} from 'three'


export class BufferGeometryGpuParticleEffect {
	/**
	 * @param {Object} positions
	 * @param {Object} moveDirs
	 * @param {Object} colors
	 * @param {Object} pngUrl
	 */
	constructor(positions,moveDirs,colors,sizes,rotAngles,pngUrl){

		this.oriPositions = positions
		this.oriMoveDirs = moveDirs
		this.oriColors = colors
		this.oriSizes = sizes
		this.oriRotAngles = rotAngles
		this.pngUrl = pngUrl

		this.mInstancedBufferMesh = null
		this.shaderMaterial = null
		this.clock = null

		// 是否被对比占用(比较的时候用来缓存)
		this.mIsCompUsed = false

		this.recordTimeScale = 6.0// 6.0 //时间记录间隔
		this.maxRecordTime = 10.0 * Math.PI// 4.0 * Math.PI // 最大时间间隔
		this.halfMaxTime = this.maxRecordTime / 2.0

		this.isMeshVisible = false

		this.uniforms = null
		this.geometry = null
  }
  /**
   * 风速效果
   * @param scene
   * @returns {null}
   */
  addWindGeometry(
    scene
  ){

    this.uniforms = {

    	pointTexture: { value: new TextureLoader().load( this.pngUrl ) },
      time:{value: 0.0},
      timeTwo:{value: 0.0},
      maxTime: { value: this.maxRecordTime },
    };

	const pointCount = this.oriPositions.length / 3
    this.positions = []	// 点的位置
    const colors = []	// 点的颜色
    const translateArray = []	// 点的运动方向
    const pointTime = []	// 点的波数
    const sizes = [];	// 点的大小
    const rotAngles = [];	// 点的方向

	// 数据重新整理,根据需要处理即可
	const createPointTimeParticel = (n)=>{
		for (var i = 0; i < pointCount; i++) {
			this.positions.push(
				this.oriPositions[3*i],
				this.oriPositions[3*i+1],
				this.oriPositions[3*i+2],
			)

			colors.push(
				this.oriColors[4*i],
				this.oriColors[4*i+1],
				this.oriColors[4*i+2],
				this.oriColors[4*i+3],
			)

			translateArray.push(
				this.oriMoveDirs[3*i],
				this.oriMoveDirs[3*i+1],
				this.oriMoveDirs[3*i+2],
			)

			sizes.push(
				this.oriSizes[i]
			)

			rotAngles.push(
				this.oriRotAngles[i]
			)

			pointTime.push(n)
		}

	}

	createPointTimeParticel(0.0)
	createPointTimeParticel(1.0)

    const shaderMaterial = new ShaderMaterial( {
    
    			uniforms: this.uniforms,
    			vertexShader: `
          uniform float time;
          uniform float timeTwo;
    
    
          attribute float size;
          attribute vec3 windDirection;
          attribute float pointTime;
          attribute float rotAngle;
    
          varying vec4 vColor;
          varying float vRotAngle;
          varying float vPointTime;
    
          void main() {
    
          	vColor = color;
            vRotAngle = rotAngle;
            vPointTime = pointTime;
    
            // 移动位移数据处理
            float useTime = pointTime > 0.1 ? timeTwo : time;
            vec3 startPos = position + useTime * windDirection * 0.02;
          	vec4 mvPosition = modelViewMatrix * vec4( startPos, 1.0 );
    
            // 大小处理
          	gl_PointSize = size * ( 300.0 / - mvPosition.z );
    
          	gl_Position = projectionMatrix * mvPosition;
    
          }
          `,
    			fragmentShader: `
            uniform sampler2D pointTexture;
            uniform float time; //当前时间
            uniform float timeTwo;
            uniform float maxTime; //最大时间
    
            varying vec4 vColor;
            varying float vRotAngle;
            varying float vPointTime;
    
            void main() {
    
                // 颜色 alpha 处理
                vec4 tColor = vColor;
               float useTime = vPointTime > 0.1 ? timeTwo : time;
               if(useTime < maxTime/2.0){
                 tColor.a = useTime/maxTime *2.0;
               }else if(useTime > maxTime * 2.0 / 3.0){
                 tColor.a = (maxTime - useTime)/maxTime * 3.0;
               }else{
				   tColor.a = 1.0;
			   }
    
    
            	gl_FragColor = vec4( tColor);
              // gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord );
    
            // uv 旋转
             vec2 uv = gl_PointCoord.xy - vec2(0.5, 0.5);
             float angle = vRotAngle + useTime * 0.1;
              uv = vec2(uv.x *cos(angle) - uv.y * sin(angle),uv.x *sin(angle) + uv.y*cos(angle));
              uv += vec2(0.5, 0.5);
              gl_FragColor = gl_FragColor * texture2D( pointTexture, uv );
    
            }
          `,
    
    			blending: NormalBlending,
    			depthTest: false,
    			transparent: true,
    			vertexColors: true,
    
    		} );

		this.geometry = new BufferGeometry();

		this.geometry.setAttribute( 'position', new Float32BufferAttribute(
	    this.positions, 3 ).setUsage( DynamicDrawUsage ) );
			this.geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 4 ).setUsage( DynamicDrawUsage ) );
			this.geometry.setAttribute( 'size', new Float32BufferAttribute( sizes, 1 ).setUsage( DynamicDrawUsage ) );
	    this.geometry.setAttribute(
	      'windDirection',
	      new Float32BufferAttribute(translateArray, 3)
	    )
	    this.geometry.setAttribute(
	      'pointTime',
	      new Float32BufferAttribute(pointTime, 1)
	    )
	
	    this.geometry.setAttribute(
	      'rotAngle',
	      new Float32BufferAttribute(rotAngles, 1).setUsage( DynamicDrawUsage )
	    )
	
	var particleSystem = new Points( this.geometry, shaderMaterial );
	this.mInstancedBufferMesh = particleSystem
	scene.add( particleSystem );
	if (this.clock == null) {
	  this.clock = new Clock()
	}
	this.timeRecord = this.clock.getElapsedTime() * this.recordTimeScale //第一波风速时间记录
	this.timeRecord2 = 0.0 //第二波风速时间记录
	
	this.shaderMaterial = shaderMaterial
	return this.mInstancedBufferMesh
  }

  instancingGeometryUpdate(){
      if(this.clock === null){
        return
      }
      
      if (this.mInstancedBufferMesh != null && this.shaderMaterial != null) {
        const current = this.clock.getElapsedTime() * this.recordTimeScale
        const time = current - this.timeRecord
      
        if (!this.startTime2 && time > this.halfMaxTime) {
          this.startTime2 = true
          this.timeRecord2 = this.timeRecord + this.halfMaxTime
        }
        const time2 = current - this.timeRecord2
        if (time > this.maxRecordTime) {
          this.timeRecord = current

        }
        if (time2 > this.maxRecordTime) {
          this.timeRecord2 = this.timeRecord + this.halfMaxTime

        }
      
        this.shaderMaterial.uniforms['time'].value = time
      
        if (this.startTime2) {
          this.shaderMaterial.uniforms['timeTwo'].value = time2
        }
	}
  }

}

七、源码

根据需要可下载工程代码,地址:审核中。。。

猜你喜欢

转载自blog.csdn.net/u014361280/article/details/126971715