• 基于粒子(Points)模拟雨雪天气效果


    目录

    功能描述

    最终效果

    准备工作

    实现原理

    参数配置

    核心代码


    功能描述

            在基于THREE.js的场景中,实现雨雪天气效果,可设置下雨、雪的范围、位置、量级以及下落速度。

    最终效果

       

    准备工作

            材质贴图,包含雨点、雪花、环境球贴图,其中环境球贴图可有可无,如不用环境球,直接忽略即可。

    实现原理

            雨和雪的原理是一样的,仅仅是材质贴图和下落速度不一样。

            在某一个确定的空间范围内,随机生成N个三维坐标点。这个范围可以是球体,也可以是六面体,本例使用的是六面体。另外,N越大,雨雪的密度就越大,通过调整N,可以修改雨雪等级。

    1. /**
    2. * 指定尺寸六面体内,随机生成三维向量
    3. * @param width
    4. * @param height
    5. * @param depth
    6. * @returns {THREE.Vector3}
    7. */
    8. export function randomVectorInBox (width, height, depth) {
    9. const x = random(-width / 2, width / 2)
    10. const y = random(-height / 2, height / 2)
    11. const z = random(-depth / 2, depth / 2)
    12. return new THREE.Vector3(x, y, z)
    13. }
    14. // 向量坐标集合
    15. const vectors = []
    16. // 循环生成向量
    17. for (let i = 0; i < N; i++) {
    18. const v = randomVectorInBox(4000, 4000, 4000)
    19. vectors.push(v.x, v.y, v.z)
    20. }

            创建一个BufferGeometry,将这N个坐标点以Float32BufferAttribute的形式,放入BufferGeometry的position属性中。

          然后创建一个PointsMaterial材质,贴图就是雨或雪的贴图图片,这里的图片一定是png格式,否则你的雪花或者雨点会有棱有角非常难看。在设置下材质的透明度。

            最后通过上述创建的几何体和材质对象,创建Points对象,添加到场景,这样我们就到了一个静止状态的雨雪效果。

    1. const geometry = new THREE.BufferGeometry()
    2. // 设置position
    3. geometry.setAttribute('position', new THREE.Float32BufferAttribute(vectors, 3))
    4. // 加载材质贴图
    5. const texture = new THREE.TextureLoader().load(img)
    6. // 创建材质
    7. const materials = new THREE.PointsMaterial({
    8. size: size,
    9. map: texture,
    10. blending: THREE.AdditiveBlending,
    11. depthTest: true,
    12. transparent: true,
    13. opacity: 0.5
    14. })
    15. // 创建Points粒子对象
    16. const particle = new THREE.Points(geometry, materials)

             但此时得到效果是静止的,我们需要让粒子下落来实现最终的效果,原理就是在requestAnimationFrame中,不断修改粒子的y轴坐标,实现下落,当然如果不想垂直下落,也可以同时修改其他坐标轴,来模拟风吹的效果,这里为了简单示意,就只修改y轴了。每次y轴的变化量V,决定了下落的速度。为了方便操作,我将上面生成的vectors集合,挂在了points对象上。下面是在requestAnimationFrame中每一帧刷新时执行的代码。

    1. // 循环粒子坐标点
    2. for (let i = 1; i < points.vectors.length; i += 3) {
    3. // 修改y轴坐标,每次变化V,注意是减法,否则就上升了
    4. points.vectors[i] = points.vectors[i] - V
    5. // 此处判断是为了让粒子下落到最低点后,重复从最高点继续下落,实现循环下落
    6. if (points.vectors[i] < -height / 2) {
    7. const v = randomVectorInBox(width, height, depth)
    8. particle.vectors[i] = height / 2
    9. particle.vectors[i - 1] = v.x
    10. particle.vectors[i + 1] = v.z
    11. }
    12. }
    13. // 更新几何体position
    14. points.geometry.setAttribute('position', new THREE.Float32BufferAttribute(points.vectors, 3))

    参数配置

     修改V可以调整下落速度

     修改六面体尺寸,可修改粒子范围

    修改points的position,可以调整粒子整体在场景中的位置

     修改N可以调整雨雪的量级

    核心代码

            demo是在vue上跑的,尽量把与本例无关的vue代码全部清除了。

    1. // 创建场景封装对象(场景、光源等于本例无关的内容)
    2. const ts = new TS('container')
    3. // UI控制参数对象
    4. this.controls = {
    5. // 开启下雨
    6. rainVisible: false,
    7. // 雨滴四度
    8. rainSpeed: 10,
    9. // 雨量等级(密度)
    10. rainGrade: 10000,
    11. // 开启下雪
    12. snowVisible: false,
    13. // 雪花速度
    14. snowSpeed: 5,
    15. // 雪量等级(密度)
    16. snowGrade: 10000,
    17. // 覆盖范围-X
    18. width: 4000,
    19. // 覆盖范围-Y
    20. height: 4000,
    21. // 覆盖范围-Z
    22. depth: 4000,
    23. // 雨坐标
    24. rx: 0,
    25. ry: 0,
    26. rz: 0,
    27. // 雪坐标
    28. sx: 0,
    29. sy: 0,
    30. sz: 0
    31. }
    32. // 雨雪对象
    33. this.rain = null
    34. this.snow = null
    35. // GUI 方便调试
    36. const gui = new GUI()
    37. // 覆盖范围
    38. gui.add(this.controls, 'width', 1000, 10000).step(10).name('宽').onChange(v => {
    39. this.controls.width = v
    40. // 刷新几何体
    41. updateGeometry.call(this, 'rain')
    42. updateGeometry.call(this, 'snow')
    43. })
    44. gui.add(this.controls, 'width', 1000, 10000).step(10).name('高').onChange(v => {
    45. this.controls.height = v
    46. // 刷新几何体
    47. updateGeometry.call(this, 'rain')
    48. updateGeometry.call(this, 'snow')
    49. })
    50. gui.add(this.controls, 'depth', 1000, 10000).step(10).name('长').onChange(v => {
    51. this.controls.depth = v
    52. // 刷新几何体
    53. updateGeometry.call(this, 'rain')
    54. updateGeometry.call(this, 'snow')
    55. })
    56. // 雨相关UI控制
    57. const rainGroup = gui.addFolder('雨')
    58. rainGroup.add(this.controls, 'rainVisible', false).name('开启').onChange(v => {
    59. this.controls.rainVisible = v
    60. // 创建粒子对象,否则删除并销毁
    61. if (v) {
    62. this.rain = generateParticle.call(this, 'img/rain.png', 'snow', 10)
    63. ts.scene.add(this.rain)
    64. } else {
    65. this.rain.geometry.dispose()
    66. this.rain.material.dispose()
    67. this.rain.removeFromParent()
    68. this.rain = null
    69. }
    70. })
    71. rainGroup.add(this.controls, 'rainSpeed', 5, 15).step(1).name('速度').onChange(v => {
    72. this.controls.rainSpeed = v
    73. })
    74. rainGroup.add(this.controls, 'rainGrade', 100, 200000).step(100).name('量级').onChange(v => {
    75. this.controls.rainGrade = v
    76. if (!this.rain) return
    77. updateGeometry.call(this, 'rain')
    78. })
    79. rainGroup.add(this.controls, 'rx').step(1).name('X坐标').onChange(v => {
    80. this.controls.rx = v
    81. if (this.rain) this.rain.position.x = v
    82. })
    83. rainGroup.add(this.controls, 'ry').step(1).name('Y坐标').onChange(v => {
    84. this.controls.ry = v
    85. if (this.rain) this.rain.position.y = v
    86. })
    87. rainGroup.add(this.controls, 'rz').step(1).name('Z坐标').onChange(v => {
    88. this.controls.rz = v
    89. if (this.rain) this.rain.position.z = v
    90. })
    91. // 雪相关UI控制
    92. const snowGroup = gui.addFolder('雪')
    93. snowGroup.add(this.controls, 'snowVisible', false).name('开启').onChange(v => {
    94. this.controls.snowVisible = v
    95. if (v) {
    96. this.snow = generateParticle.call(this, 'img/snow.png', 'snow')
    97. ts.scene.add(this.snow)
    98. } else {
    99. this.snow.geometry.dispose()
    100. this.snow.material.dispose()
    101. this.snow.removeFromParent()
    102. this.snow = null
    103. }
    104. })
    105. snowGroup.add(this.controls, 'snowSpeed', 1, 10).step(1).name('速度').onChange(v => {
    106. this.controls.snowSpeed = v
    107. })
    108. snowGroup.add(this.controls, 'snowGrade', 100, 200000).step(100).name('量级').onChange(v => {
    109. this.controls.snowGrade = v
    110. if (!this.snow) return
    111. updateGeometry.call(this, 'snow')
    112. })
    113. snowGroup.add(this.controls, 'sx').step(1).name('X坐标').onChange(v => {
    114. this.controls.sx = v
    115. if (this.snow) this.snow.position.x = v
    116. })
    117. snowGroup.add(this.controls, 'sy').step(1).name('Y坐标').onChange(v => {
    118. this.controls.sy = v
    119. if (this.snow) this.snow.position.y = v
    120. })
    121. snowGroup.add(this.controls, 'sz').step(1).name('Z坐标').onChange(v => {
    122. this.controls.sz = v
    123. if (this.snow) this.snow.position.z = v
    124. })
    125. // 此处相当于requestAnimationFrame,只不过内部封装了一下
    126. ts.updates.push(() => {
    127. update.call(this, 'rain')
    128. update.call(this, 'snow')
    129. })
    130. /**
    131. * 更新几何体
    132. * @param type
    133. */
    134. function updateGeometry (type) {
    135. // 获取粒子对象
    136. const particle = this[type]
    137. if (!particle) return
    138. // 重新计算粒子坐标
    139. const vectors = computeVectors.call(this, type)
    140. // 更新属性
    141. particle.vectors = vectors
    142. particle.geometry.setAttribute('position', new THREE.Float32BufferAttribute(particle.vectors, 3))
    143. }
    144. /**
    145. * 更新粒子移动
    146. * @param particle
    147. * @param speed
    148. */
    149. function update (type) {
    150. // 获取粒子对象
    151. const particle = this[type]
    152. if (!particle || !particle.vectors) return
    153. // 获取速度
    154. const speed = this.controls[type + 'Speed']
    155. // 下落动画
    156. for (let i = 1; i < particle.vectors.length; i += 3) {
    157. particle.vectors[i] = particle.vectors[i] - speed
    158. if (particle.vectors[i] < -this.controls.height / 2) {
    159. const v = randomVectorInBox(this.controls.width, this.controls.height, this.controls.depth)
    160. particle.vectors[i] = this.controls.height / 2
    161. particle.vectors[i - 1] = v.x
    162. particle.vectors[i + 1] = v.z
    163. }
    164. }
    165. particle.geometry.setAttribute('position', new THREE.Float32BufferAttribute(particle.vectors, 3))
    166. }
    167. /**
    168. * 计算粒子坐标
    169. * @param type
    170. * @returns {*[]}
    171. */
    172. function computeVectors (type) {
    173. const vectors = []
    174. for (let i = 0; i < this.controls[type + 'Grade']; i++) {
    175. // 从六面体范围内获取随机坐标
    176. const v = randomVectorInBox(this.controls.width, this.controls.height, this.controls.depth)
    177. vectors.push(v.x, v.y, v.z)
    178. }
    179. return vectors
    180. }
    181. /**
    182. * 雨雪粒子
    183. * @param img
    184. * @param size
    185. * @returns {any}
    186. */
    187. function generateParticle (img, type, size = 15) {
    188. const geometry = new THREE.BufferGeometry()
    189. // 获取粒子坐标点集合
    190. const vectors = computeVectors.call(this, type)
    191. // 设置到几何体
    192. geometry.setAttribute('position', new THREE.Float32BufferAttribute(vectors, 3))
    193. // 加载贴图
    194. const texture = new THREE.TextureLoader().load(img)
    195. // 创建材质对象
    196. const materials = new THREE.PointsMaterial({
    197. size: size,
    198. map: texture,
    199. blending: THREE.AdditiveBlending,
    200. // 是否永远置顶
    201. depthTest: true,
    202. transparent: true,
    203. opacity: 0.5
    204. })
    205. const particle = new THREE.Points(geometry, materials)
    206. particle.vectors = vectors
    207. return particle
    208. }
    209. /**
    210. * 指定尺寸六面体内,随机生成三维向量
    211. * @param width
    212. * @param height
    213. * @param depth
    214. * @returns {THREE.Vector3}
    215. */
    216. export function randomVectorInBox (width, height, depth) {
    217. const x = random(-width / 2, width / 2)
    218. const y = random(-height / 2, height / 2)
    219. const z = random(-depth / 2, depth / 2)
    220. return new THREE.Vector3(x, y, z)
    221. }

  • 相关阅读:
    DBeaver 与 Navicat:数据库工具对决
    valgrind笔记
    第07章 InnoDB数据存储结构
    Yarp 与 Nginx性能大比拼不出所料它胜利了!
    java面试题总结
    GreenPlum扩容节点
    Docker学习总结
    智能制造工厂中MES系统的应用发展
    Django笔记四十之运行Django环境的python脚本
    【Python】获取或修改 Windows 系统中文件的创建时间、修改时间和访问时间(os | win32file)
  • 原文地址:https://blog.csdn.net/delongcpp/article/details/125993365