Appearance
gpu 粒子
fps: 0
点击运行
<template>
<div>
<div>
<div class="flex space-between">
<div>fps: {{ fps }}</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<div v-if="isRunning" class="flex space-between">
<div>
粒子数量{{ numbers }}
</div>
<div class="flex">
<div class="pointer" @click="onMove">当前是否move中({{ moveEmitter ? '是' : '否' }})</div>
<div class="pointer" @click="onRotate">当前是否rotate中({{ rotateEmitter ? '是' : '否' }})</div>
<div class="pointer" @click="onUseGpu">当前是否启用GPU中({{ useGPUVersion ? '是' : '否' }})</div>
</div>
</div>
</div>
<canvas v-if="isRunning" id="particleGPU" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Engine,
Scene,
ArcRotateCamera,
Vector3,
HemisphericLight,
MeshBuilder,
Color3,
Color4,
Texture,
GPUParticleSystem,
ParticleSystem,
CustomParticleEmitter
} from 'babylonjs'
import {
GridMaterial
} from 'babylonjs-materials'
let sceneResources
const fps = ref(0)
const isRunning = ref(false)
const useGPUVersion = ref(true) // 是否启用gpu
const particleSystem = ref<any>(null)
const alpha = ref(0)
const moveEmitter = ref(false)
const rotateEmitter = ref(false)
const fountain = ref<any>(null)
const diam = 4 // box大小
const numbers = 50000 // 粒子数量
const onMove = () => {
moveEmitter.value = !moveEmitter.value
}
const onRotate = () => {
rotateEmitter.value = !rotateEmitter.value
}
const onUseGpu = async () => {
if (isRunning.value) {
destroy()
useGPUVersion.value = !useGPUVersion.value
setTimeout(async() => {
sceneResources = await initScene()
}, 300)
}
}
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = async () => {
const ele = document.getElementById("particleGPU") as any
ele.addEventListener('wheel', function(event) {
// 根据需要处理滚动
// 例如,可以修改相机的半径或角度
event.preventDefault() // 阻止默认滚动行为
})
const engine: any = new Engine(ele, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
})
const scene = new Scene(engine)
scene.useRightHandedSystem = true
const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
camera.upperBetaLimit = Math.PI / 2.2
camera.wheelPrecision = 30
camera.panningSensibility = 200
camera.attachControl(ele, true)
camera.setPosition(new Vector3(0, 20, 60))
const createLight = () => {
const light = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
return light
}
const createGround = () => {
const ground = MeshBuilder.CreateGround('ground', {
width: 100,
height: 100,
}, scene)
const groundMat = new GridMaterial('grid1', scene)
const color = new Color3(0, 245 / 255, 171 / 255)
groundMat.mainColor = color
groundMat.lineColor = color
groundMat.opacity = 0.7
groundMat.majorUnitFrequency = 10
groundMat.minorUnitVisibility = 0.8
groundMat.gridRatio = 25
ground.material = groundMat
}
const createBox = () => {
const fountain = MeshBuilder.CreateBox('box', {
height: diam,
width: diam
}, scene)
return fountain
}
const createParticle = (fountain) => {
if (particleSystem.value) {
particleSystem.value.dispose()
}
if (useGPUVersion.value && GPUParticleSystem.IsSupported) {
particleSystem.value = new GPUParticleSystem(
'gpu-particles',
{ capacity: numbers },
scene
)
particleSystem.value.activeParticleCount = numbers
console.log('GPUParticleSystem')
} else {
particleSystem.value = new ParticleSystem(
'particles',
numbers,
scene
)
console.log('ParticlesSystem')
}
const customEmitter = new CustomParticleEmitter()
// 计算粒子生成的xy位置
customEmitter.particlePositionGenerator = (_index, _particle, out) => {
const hu_du = 2 * Math.PI * Math.random()
const r = +((Math.random() * diam) / 2).toFixed(4)
const deg = (180 / Math.PI) * hu_du
const x = Math.cos(deg) * r
const y = Math.sin(deg) * r
out.x = x
out.y = y
out.z = 0
}
// 计算粒子到达xyz的位置
customEmitter.particleDestinationGenerator = (_index, _particle, out) => {
const hu_du = 2 * Math.PI * Math.random()
const r = +((Math.random() * diam) / 2).toFixed(4)
const deg = (180 / Math.PI) * hu_du
const x = Math.cos(deg) * r
const y = Math.sin(deg) * r
out.x = x
out.y = y
const z = Math.random() / (x * x + y * y)
out.z = z > 20 ? 20 : z
}
particleSystem.value.emitRate = numbers
particleSystem.value.particleEmitterType = customEmitter
particleSystem.value.particleTexture = new Texture(
'/images/grass.png',
scene
)
particleSystem.value.textureMask = new Color4(0.11, 0.8, 0.1, 1)
particleSystem.value.emitter = fountain
particleSystem.value.maxLifeTime = 1
particleSystem.value.minSize = 0.1
particleSystem.value.maxSize = 0.1
particleSystem.value.start()
}
const runAnimate = () => {
engine.runRenderLoop(function() {
if (scene && scene.activeCamera) {
scene.render()
fps.value = engine.getFps().toFixed(2)
}
})
}
fountain.value = createBox()
createParticle(fountain.value)
createLight()
createGround()
runAnimate()
scene.registerBeforeRender(function() {
if (moveEmitter.value) {
console.log('moveEmitter')
fountain.value.position.x = 5 * Math.cos(alpha.value)
fountain.value.position.z = 5 * Math.sin(alpha.value)
}
if (rotateEmitter.value) {
console.log('rotateEmitter')
fountain.value.rotation.x += 0.01
}
alpha.value = +((0.01+alpha.value).toFixed(2))
})
return {
scene,
engine,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.engine.stopRenderLoop()
sceneResources.engine.dispose()
sceneResources.scene.dispose()
sceneResources = null
}
fountain.value = null
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
<style scoped>
</style>
喷泉
fps: 0
点击运行
<template>
<div>
<div class="flex space-between">
<div>fps: {{ fps }}</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
</div>
<canvas v-if="isRunning" id="particleWaterSpray" class="stage"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import 'babylonjs-loaders'
import {
Engine,
Scene,
ArcRotateCamera,
Vector3,
Color3,
HemisphericLight,
MeshBuilder,
StandardMaterial,
CubeTexture,
Texture,
Mesh,
ImportMeshAsync,
ParticleSystem
} from 'babylonjs'
let sceneResources
const fps = ref(0)
const isRunning = ref(false)
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = async () => {
let switched = false
let particle: any = null
const ele = document.getElementById("particleWaterSpray") as any
ele.addEventListener('wheel', function(event) {
// 根据需要处理滚动
// 例如,可以修改相机的半径或角度
event.preventDefault() // 阻止默认滚动行为
})
const engine: any = new Engine(ele, true, {
preserveDrawingBuffer: true,
stencil: true,
disableWebGL2Support: false
})
const scene = new Scene(engine)
scene.useRightHandedSystem = true
const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
camera.upperBetaLimit = Math.PI / 2.2
camera.wheelPrecision = 30
camera.panningSensibility = 200
camera.attachControl(ele, true)
camera.setPosition(new Vector3(20, 20, 20))
const createLight = () => {
const light = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
return light
}
const createSkyBox = () => {
const skyBox = MeshBuilder.CreateBox(
'skyBox',
{ size: 150 },
scene
)
const skyBoxMaterial = new StandardMaterial(
'skyBox',
scene
)
skyBoxMaterial.backFaceCulling = false
skyBoxMaterial.reflectionTexture = new CubeTexture(
'/images/skybox',
scene
)
skyBoxMaterial.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE
skyBoxMaterial.diffuseColor = new Color3(0, 0, 0)
skyBoxMaterial.specularColor = new Color3(0, 0, 0)
skyBox.material = skyBoxMaterial
}
const createVillage = async () => {
await ImportMeshAsync('/scenes/valleyvillage.glb', scene)
}
const createParticleSystem = (lathe) => {
const particleSystem = new ParticleSystem('system', 5000, scene)
// 每个粒子的纹理
particleSystem.particleTexture = new Texture('/images/star.jpg')
// 粒子从何而来
// 从喷泉顶部喷出
particleSystem.emitter = new Vector3(lathe.position.x, 0.8, lathe.position.z)
// 从
particleSystem.minEmitBox = new Vector3(-0.01, 0, -0.01)
// 到...
particleSystem.maxEmitBox = new Vector3(0.01, 0, 0.01)
// 所有粒子的颜色
// particleSystem.color1 = new Color4(0.7, 0.8, 1.0, 1.0)
// particleSystem.color2 = new Color4(0.2, 0.5, 1.0, 1.0)
// 每个粒子的大小(随机...
particleSystem.minSize = 0.01
particleSystem.maxSize = 0.05
// 每个粒子的寿命(随机...
particleSystem.minLifeTime = 0.3
particleSystem.maxLifeTime = 1.5
// 排放率
particleSystem.emitRate = 1500
// 混合模式 : BLENDMODE_ONEONE, or BLENDMODE_STANDARD
particleSystem.blendMode = ParticleSystem.BLENDMODE_ONEONE
// 设置所有粒子的重力
particleSystem.gravity = new Vector3(0, -9.81, 0)
// 每个粒子发射后的方向
// 其中的粒子从某一点向四面八方发射
// 可以将 direction1 设置为指向一个方向的向量
// 而将 direction2 设置为指向另一个方向的向量
// 这样粒子就会在这两者之间随机发射,形成一个圆锥形的发射范围
particleSystem.direction1 = new Vector3(-1, 10, 1)
particleSystem.direction2 = new Vector3(1, 10, -1)
// 力量和速度
particleSystem.minEmitPower = 0.2
particleSystem.maxEmitPower = 0.6
particleSystem.updateSpeed = 0.01
return particleSystem
}
const createLathe = () => {
const latheProfile = [
new Vector3(0, 0, 0),
new Vector3(0.5, 0, 0),
new Vector3(0.5, 0.2, 0),
new Vector3(0.4, 0.2, 0),
new Vector3(0.4, 0.05, 0),
new Vector3(0.05, 0.1, 0),
new Vector3(0.05, 0.8, 0),
new Vector3(0.15, 0.9, 0)
]
// 创建一个旋转体网格
const lathe = MeshBuilder.CreateLathe('latheProfile', {
shape: latheProfile,
// 设置网格的侧面朝向
// Mesh.DOUBLESIDE 表示网格的两个面都是可见的
// 即无论摄像机从哪个方向看,网格的表面都是可见的
sideOrientation: Mesh.DOUBLESIDE
})
lathe.position.x = -3
lathe.position.z = 3
return lathe
}
const pointerDown = (mesh) => {
if (mesh.id === 'latheProfile') {
switched = !switched
if (switched) {
particle.start()
} else {
particle.stop()
}
console.log(particle)
}
}
const createPointerObservable = () => {
scene.onPointerObservable.add((pointerInfo: any) => {
switch (pointerInfo.type) {
case BABYLON.PointerEventTypes.POINTERDOWN:
if (pointerInfo.pickInfo.hit) {
pointerDown(pointerInfo.pickInfo.pickedMesh)
}
break
}
})
}
const runAnimate = () => {
engine.runRenderLoop(function() {
if (scene && scene.activeCamera) {
scene.render()
fps.value = engine.getFps().toFixed(2)
}
})
}
const lathe = createLathe()
particle = createParticleSystem(lathe)
createLight()
createSkyBox()
createVillage()
createPointerObservable()
runAnimate()
return {
scene,
engine,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.engine.stopRenderLoop()
sceneResources.engine.dispose()
sceneResources.scene.dispose()
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>