当前位置:   article > 正文

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

更新 buffergeometry中的position

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

目录

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

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、实现步骤

 六、关键代码

七、源码


一、简单介绍

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

本节介绍, three.js (webgl) 中,使用 BufferGeometry ,添加位置和移动方向数据,结合 shader,简单实现类似 particle 的粒子运动效果,这里主要的逻辑在js脚本,而非 shader 中,所以这里算是 cpu 中,使用BufferGeometry 实现简单粒子效果,如果有不足之处,欢迎指出,或者你有更好的方法,欢迎留言。

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

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

二、实现原理

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

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

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

4、在 Animation 中结合 Clock 的 time 来控制 BufferGeometry  每个点的移动和 alpha 透明度,从而实现粒子效果的持续生成运动

  1. // 位置更新
  2. const positionArray = this.geometry.attributes.position.array;
  3. const translateArray = this.geometry.attributes.windDirection.array;
  4. const pointTimeArray = this.geometry.attributes.pointTime.array;
  5. const ss = 0.03
  6. for (var i = 0,j=0; i < positionArray.length,j<pointTimeArray.length; i+=3,j++) {
  7. var useTime = pointTimeArray[j] > 0.1 ? time2 : time;
  8. // console.log("useTime ", useTime)
  9. positionArray[i] =
  10. this.positions[i]+ translateArray[i] * useTime * ss
  11. positionArray[i+1] = this.positions[i+1]+ translateArray[i+1] * useTime * ss
  12. positionArray[i+2] = this.positions[i+2]+ translateArray[i+2] * useTime * ss
  13. }
  14. this.geometry.attributes.position.needsUpdate = true;
  15. // 更新颜色 Alpha
  16. const colorArray = this.geometry.attributes.color.array;
  17. const colorLenght = colorArray.length;
  18. const halfcolorLenght = colorLenght/2
  19. for (var i = 0; i < colorLenght; i+=4) {
  20. if(i < halfcolorLenght){
  21. if(time < this.maxRecordTime/2){
  22. colorArray[i+3] = time/this.maxRecordTime *2
  23. }else if(time > this.maxRecordTime * 2 / 3){
  24. colorArray[i+3] = (this.maxRecordTime - time)/this.maxRecordTime * 3
  25. }
  26. }else{
  27. if(time2 < this.maxRecordTime/2){
  28. colorArray[i+3] = time2/this.maxRecordTime * 3
  29. }else if(time2 > this.maxRecordTime * 2 / 3){
  30. colorArray[i+3] = (this.maxRecordTime - time2)/this.maxRecordTime * 3
  31. }
  32. }
  33. }
  34. this.geometry.attributes.color.needsUpdate = true;
  35. // 旋转更新
  36. const rotAngArray = this.geometry.attributes.rotAngle.array;
  37. const rotAngLenght = rotAngArray.length;
  38. for (var i = 0; i < rotAngArray.length; i++) {
  39. rotAngArray[i] += (Math.random()) * 0.04
  40. }
  41. this.geometry.attributes.rotAngle.needsUpdate = true;

三、注意事项

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、构建 位置、移动向量、粒子大小、颜色、旋转等数据,并传入粒子图片,创建 BufferGeometryCpuParticleEffect

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

六、关键代码

1、TestBufferGeometryCpuParticleEffect.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>TestBufferGeometryCpuParticleEffect</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <link type="text/css" rel="stylesheet" href="main.css">
  8. </head>
  9. <body>
  10. <!-- Import maps polyfill -->
  11. <!-- Remove this when import maps will be widely supported -->
  12. <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. <script type="module">
  21. // 引入 three 基础库
  22. import * as THREE from 'three';
  23. import Stats from '../../jsm/libs/stats.module.js';
  24. import { OrbitControls } from './../../jsm/controls/OrbitControls.js';
  25. import { BufferGeometryCpuParticleEffect } from './BufferGeometryParticle/BufferGeometryCpuParticleEffect.js'
  26. let camera, renderer, scene,controls;
  27. let object;
  28. let stats;
  29. let mBufferGeometryCpuParticleEffect;
  30. const params = {
  31. enableFpsRender: false,
  32. enableRightNowRender: false
  33. };
  34. init();
  35. animate();
  36. function init() {
  37. // 渲染器
  38. renderer = new THREE.WebGLRenderer();
  39. renderer.setPixelRatio( window.devicePixelRatio );
  40. renderer.setSize( window.innerWidth, window.innerHeight );
  41. renderer.setClearColor('#cccccc');
  42. document.body.appendChild( renderer.domElement );
  43. // camera
  44. camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
  45. camera.position.z = 8;
  46. // 场景
  47. scene = new THREE.Scene();
  48. // 添加环境光
  49. scene.add( new THREE.AmbientLight( 0x222222 ) );
  50. // 添加方向光
  51. const light = new THREE.DirectionalLight( 0xffffff );
  52. light.position.set( 1, 1, 1 );
  53. scene.add( light );
  54. // 窗口尺寸变化监听
  55. window.addEventListener( 'resize', onWindowResize );
  56. stats = new Stats();
  57. document.body.appendChild( stats.dom );
  58. controls = new OrbitControls( camera, renderer.domElement );
  59. createParticle(scene)
  60. }
  61. function createParticle(scene){
  62. const pointCount = 30
  63. const positions = [] // 点的位置
  64. const colors = [] // 点的颜色
  65. const moveDirs = [] // 点的运动方向
  66. const sizes = []; // 点的大小
  67. const rotAngles = []; // 点的方向
  68. for (var i = 0; i < pointCount; i++) {
  69. positions[3 * i] = Math.random() *4-2
  70. positions[3 * i +1] = Math.random() *4-2
  71. positions[3 * i+2] = Math.random() *4-2
  72. colors[4 * i] = Math.random()*4-2
  73. colors[4 * i +1] = Math.random()*4-2
  74. colors[4 * i+2] = Math.random()
  75. colors[4 * i+3] = 0.0
  76. moveDirs[3 * i] = Math.random() *2 -1
  77. moveDirs[3 * i +1] = Math.random()*2 -1
  78. moveDirs[3 * i+2] = Math.random()*2 -1
  79. sizes[i] = Math.random() * 1.0
  80. rotAngles[i] = Math.random() * Math.PI
  81. }
  82. mBufferGeometryCpuParticleEffect = new BufferGeometryCpuParticleEffect(positions,moveDirs,colors,sizes,rotAngles,'textures/left.png')
  83. mBufferGeometryCpuParticleEffect.addWindGeometry(scene)
  84. }
  85. function onWindowResize() {
  86. // camera 更新 aspect
  87. camera.aspect = window.innerWidth / window.innerHeight;
  88. camera.updateProjectionMatrix();
  89. // 渲染器更新大小
  90. renderer.setSize( window.innerWidth, window.innerHeight );
  91. }
  92. function animate() {
  93. requestAnimationFrame( animate );
  94. renderer.render( scene, camera );
  95. mBufferGeometryCpuParticleEffect.instancingGeometryUpdate()
  96. stats.update();
  97. controls.update()
  98. }
  99. </script>
  100. </body>
  101. </html>

2、BufferGeometryCpuParticleEffect

  1. import {
  2. BufferGeometry,
  3. InstancedBufferAttribute,
  4. Mesh,
  5. CircleGeometry,
  6. ShaderMaterial,
  7. NormalBlending,
  8. DoubleSide,
  9. Clock,
  10. Points,
  11. Float32BufferAttribute,TextureLoader,Color,DynamicDrawUsage,Object3D
  12. } from 'three'
  13. export class BufferGeometryCpuParticleEffect {
  14. /**
  15. * @param {Object} positions
  16. * @param {Object} moveDirs
  17. * @param {Object} colors
  18. * @param {Object} pngUrl
  19. */
  20. constructor(positions,moveDirs,colors,sizes,rotAngles,pngUrl){
  21. this.oriPositions = positions
  22. this.oriMoveDirs = moveDirs
  23. this.oriColors = colors
  24. this.oriSizes = sizes
  25. this.oriRotAngles = rotAngles
  26. this.pngUrl = pngUrl
  27. this.mInstancedBufferMesh = null
  28. this.shaderMaterial = null
  29. this.clock = null
  30. // 是否被对比占用(比较的时候用来缓存)
  31. this.mIsCompUsed = false
  32. this.recordTimeScale = 6.0// 6.0 //时间记录间隔
  33. this.maxRecordTime = 10.0 * Math.PI// 4.0 * Math.PI // 最大时间间隔
  34. this.halfMaxTime = this.maxRecordTime / 2.0
  35. this.isMeshVisible = false
  36. this.uniforms = null
  37. this.geometry = null
  38. }
  39. /**
  40. * 风速效果
  41. * @param scene
  42. * @returns {null}
  43. */
  44. addWindGeometry(
  45. scene
  46. ){
  47. this.uniforms = {
  48. pointTexture: { value: new TextureLoader().load( this.pngUrl ) },
  49. time:{value: 0.0},
  50. timeTwo:{value: 0.0},
  51. maxTime: { value: this.maxRecordTime },
  52. };
  53. const pointCount = this.oriPositions.length / 3
  54. this.positions = [] // 点的位置
  55. const colors = [] // 点的颜色
  56. const translateArray = [] // 点的运动方向
  57. const pointTime = [] // 点的波数
  58. const sizes = []; // 点的大小
  59. const rotAngles = []; // 点的方向
  60. const createPointTimeParticel = (n)=>{
  61. for (var i = 0; i < pointCount; i++) {
  62. this.positions.push(
  63. this.oriPositions[3*i],
  64. this.oriPositions[3*i+1],
  65. this.oriPositions[3*i+2],
  66. )
  67. colors.push(
  68. this.oriColors[4*i],
  69. this.oriColors[4*i+1],
  70. this.oriColors[4*i+2],
  71. this.oriColors[4*i+3],
  72. )
  73. translateArray.push(
  74. this.oriMoveDirs[3*i],
  75. this.oriMoveDirs[3*i+1],
  76. this.oriMoveDirs[3*i+2],
  77. )
  78. sizes.push(
  79. this.oriSizes[i]
  80. )
  81. rotAngles.push(
  82. this.oriRotAngles[i]
  83. )
  84. pointTime.push(n)
  85. }
  86. }
  87. createPointTimeParticel(0.0)
  88. createPointTimeParticel(1.0)
  89. const shaderMaterial = new ShaderMaterial( {
  90. uniforms: this.uniforms,
  91. vertexShader: `
  92. attribute float size;
  93. attribute vec4 windDirection;
  94. attribute float pointTime;
  95. attribute float rotAngle;
  96. varying vec4 vColor;
  97. varying float vRotAngle;
  98. void main() {
  99. vColor = color;
  100. vRotAngle = rotAngle;
  101. vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
  102. gl_PointSize = size * ( 300.0 / - mvPosition.z );
  103. gl_Position = projectionMatrix * mvPosition;
  104. }
  105. `,
  106. fragmentShader: `
  107. uniform sampler2D pointTexture;
  108. varying vec4 vColor;
  109. varying float vRotAngle;
  110. void main() {
  111. gl_FragColor = vec4( vColor);
  112. // gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord );
  113. // uv 旋转
  114. vec2 uv = gl_PointCoord.xy - vec2(0.5, 0.5);
  115. float angle = vRotAngle;
  116. uv = vec2(uv.x *cos(angle) - uv.y * sin(angle),uv.x *sin(angle) + uv.y*cos(angle));
  117. uv += vec2(0.5, 0.5);
  118. gl_FragColor = gl_FragColor * texture2D( pointTexture, uv );
  119. }
  120. `,
  121. blending: NormalBlending,
  122. depthTest: false,
  123. transparent: true,
  124. vertexColors: true,
  125. } );
  126. this.geometry = new BufferGeometry();
  127. this.geometry.setAttribute( 'position', new Float32BufferAttribute(
  128. this.positions, 3 ).setUsage( DynamicDrawUsage ) );
  129. this.geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 4 ).setUsage( DynamicDrawUsage ) );
  130. this.geometry.setAttribute( 'size', new Float32BufferAttribute( sizes, 1 ).setUsage( DynamicDrawUsage ) );
  131. this.geometry.setAttribute(
  132. 'windDirection',
  133. new InstancedBufferAttribute(new Float32Array(translateArray), 3)
  134. )
  135. this.geometry.setAttribute(
  136. 'pointTime',
  137. new InstancedBufferAttribute(new Float32Array(pointTime), 1)
  138. )
  139. this.geometry.setAttribute(
  140. 'rotAngle',
  141. new Float32BufferAttribute(new Float32Array(rotAngles), 1).setUsage( DynamicDrawUsage )
  142. )
  143. var particleSystem = new Points( this.geometry, shaderMaterial );
  144. this.mInstancedBufferMesh = particleSystem
  145. scene.add( particleSystem );
  146. if (this.clock == null) {
  147. this.clock = new Clock()
  148. }
  149. this.timeRecord = this.clock.getElapsedTime() * this.recordTimeScale //第一波风速时间记录
  150. this.timeRecord2 = 0.0 //第二波风速时间记录
  151. this.shaderMaterial = shaderMaterial
  152. return this.mInstancedBufferMesh
  153. }
  154. instancingGeometryUpdate(){
  155. if(this.clock === null){
  156. return
  157. }
  158. if (this.mInstancedBufferMesh != null && this.shaderMaterial != null) {
  159. if(this.mInstancedBufferMesh.visible === false ){
  160. return
  161. }
  162. if( this.isMeshVisible === false){
  163. this.timeRecord = this.clock.getElapsedTime() * this.recordTimeScale //第一波风速时间记录
  164. this.timeRecord2 = 0.0 //第二波风速时间记录
  165. this.startTime2 = false
  166. this.isMeshVisible = true
  167. }
  168. const current = this.clock.getElapsedTime() * this.recordTimeScale
  169. const time = current - this.timeRecord
  170. if (!this.startTime2 && time > this.halfMaxTime) {
  171. this.startTime2 = true
  172. this.timeRecord2 = this.timeRecord + this.halfMaxTime
  173. }
  174. const time2 = current - this.timeRecord2
  175. if (time > this.maxRecordTime) {
  176. this.timeRecord = current
  177. }
  178. if (time2 > this.maxRecordTime) {
  179. this.timeRecord2 = this.timeRecord + this.halfMaxTime
  180. }
  181. this.shaderMaterial.uniforms['time'].value = time
  182. if (this.startTime2) {
  183. this.shaderMaterial.uniforms['timeTwo'].value = time2
  184. }
  185. this.updatePostionAndColorAlpha(time,time2)
  186. }
  187. }
  188. /**
  189. * 更新对应波数的粒子
  190. * @param {Object} time
  191. * @param {Object} time2
  192. */
  193. updatePostionAndColorAlpha(time,time2){
  194. // 位置更新
  195. const positionArray = this.geometry.attributes.position.array;
  196. const translateArray = this.geometry.attributes.windDirection.array;
  197. const pointTimeArray = this.geometry.attributes.pointTime.array;
  198. const ss = 0.03
  199. for (var i = 0,j=0; i < positionArray.length,j<pointTimeArray.length; i+=3,j++) {
  200. var useTime = pointTimeArray[j] > 0.1 ? time2 : time;
  201. // console.log("useTime ", useTime)
  202. positionArray[i] =
  203. this.positions[i]+ translateArray[i] * useTime * ss
  204. positionArray[i+1] = this.positions[i+1]+ translateArray[i+1] * useTime * ss
  205. positionArray[i+2] = this.positions[i+2]+ translateArray[i+2] * useTime * ss
  206. }
  207. this.geometry.attributes.position.needsUpdate = true;
  208. // 更新颜色
  209. const colorArray = this.geometry.attributes.color.array;
  210. const colorLenght = colorArray.length;
  211. const halfcolorLenght = colorLenght/2
  212. for (var i = 0; i < colorLenght; i+=4) {
  213. if(i < halfcolorLenght){
  214. if(time < this.maxRecordTime/2){
  215. colorArray[i+3] = time/this.maxRecordTime *2
  216. }else if(time > this.maxRecordTime * 2 / 3){
  217. colorArray[i+3] = (this.maxRecordTime - time)/this.maxRecordTime * 3
  218. }
  219. }else{
  220. if(time2 < this.maxRecordTime/2){
  221. colorArray[i+3] = time2/this.maxRecordTime * 3
  222. }else if(time2 > this.maxRecordTime * 2 / 3){
  223. colorArray[i+3] = (this.maxRecordTime - time2)/this.maxRecordTime * 3
  224. }
  225. }
  226. }
  227. this.geometry.attributes.color.needsUpdate = true;
  228. // 旋转更新
  229. const rotAngArray = this.geometry.attributes.rotAngle.array;
  230. const rotAngLenght = rotAngArray.length;
  231. for (var i = 0; i < rotAngArray.length; i++) {
  232. rotAngArray[i] += (Math.random()) * 0.04
  233. }
  234. this.geometry.attributes.rotAngle.needsUpdate = true;
  235. }
  236. }

七、源码

根据需要可下载工程代码,地址:Three之three.js使用BufferGeometry(CPU)根据简单粒子particle运动效果代码工程

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/58936
推荐阅读
相关标签
  

闽ICP备14008679号