| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- #!/usr/bin/env python
- # This program shows a shader-based particle system. With this approach, you
- # can define an inertial particle system with a moving emitter whose position
- # can not be pre-determined.
- from array import array
- from itertools import chain
- from random import uniform
- from math import pi, sin, cos
- from panda3d.core import TextNode
- from panda3d.core import AmbientLight, DirectionalLight
- from panda3d.core import LVector3
- from panda3d.core import NodePath
- from panda3d.core import GeomPoints
- from panda3d.core import GeomEnums
- from panda3d.core import GeomVertexFormat
- from panda3d.core import GeomVertexData
- from panda3d.core import GeomNode
- from panda3d.core import Geom
- from panda3d.core import OmniBoundingVolume
- from panda3d.core import Texture
- from panda3d.core import TextureStage
- from panda3d.core import TexGenAttrib
- from panda3d.core import Shader
- from panda3d.core import ShaderAttrib
- from panda3d.core import loadPrcFileData
- from direct.showbase.ShowBase import ShowBase
- from direct.gui.OnscreenText import OnscreenText
- import sys
- HELP_TEXT = """
- left/right arrow: Rotate teapot
- ESC: Quit
- """
- # We need to use GLSL 1.50 for these, and some drivers (notably Mesa) require
- # us to explicitly ask for an OpenGL 3.2 context in that case.
- config = """
- gl-version 3 2
- """
- vert = """
- #version 150
- #extension GL_ARB_shader_image_load_store : require
- layout(rgba32f) uniform imageBuffer positions; // current positions
- layout(rgba32f) uniform imageBuffer start_vel; // emission velocities
- layout(rgba32f) uniform imageBuffer velocities; // current velocities
- layout(rgba32f) uniform imageBuffer emission_times; // emission times
- uniform mat4 p3d_ModelViewProjectionMatrix;
- uniform vec3 emitter_pos; // emitter's position
- uniform vec3 accel; // the acceleration of the particles
- uniform float osg_FrameTime; // time of the current frame (absolute)
- uniform float osg_DeltaFrameTime;// time since last frame
- uniform float start_time; // particle system's start time (absolute)
- uniform float part_duration; // single particle's duration
- out float from_emission; // time from specific particle's emission
- out vec4 color;
- void main() {
- float emission_time = imageLoad(emission_times, gl_VertexID).x;
- vec4 pos = imageLoad(positions, gl_VertexID);
- vec4 vel = imageLoad(velocities, gl_VertexID);
- float from_start = osg_FrameTime - start_time; // time from system's start
- from_emission = 0;
- color = vec4(1);
- if (from_start > emission_time) { // we've to show the particle
- from_emission = from_start - emission_time;
- if (from_emission <= osg_DeltaFrameTime + .01) {
- // it's particle's emission frame: let's set its position at the
- // emitter's position and set the initial velocity
- pos = vec4(emitter_pos, 1);
- vel = imageLoad(start_vel, gl_VertexID);
- }
- pos += vec4((vel * osg_DeltaFrameTime).xyz, 0);
- vel += vec4(accel, 0) * osg_DeltaFrameTime;
- } else color = vec4(0);
- // update the emission time (for particle recycling)
- if (from_start >= emission_time + part_duration) {
- imageStore(emission_times, gl_VertexID, vec4(from_start, 0, 0, 1));
- }
- gl_PointSize = 10;
- gl_Position = p3d_ModelViewProjectionMatrix * pos;
- imageStore(positions, gl_VertexID, pos);
- imageStore(velocities, gl_VertexID, vel);
- }
- """
- frag = """
- #version 150
- in float from_emission; // time elapsed from particle's emission
- in vec4 color;
- uniform float part_duration; // single particle's duration
- uniform sampler2D image; // particle's texture
- out vec4 p3d_FragData[1];
- void main() {
- vec4 col = texture(image, gl_PointCoord) * color;
- // fade the particle considering the time from its emission
- float alpha = clamp(1 - from_emission / part_duration, 0, 1);
- p3d_FragData[0] = vec4(col.rgb, col.a * alpha);
- }
- """
- class Particle:
- def __init__(
- self,
- emitter, # the node which is emitting
- texture, # particle's image
- rate=.001, # the emission rate
- gravity=-9.81, # z-component of the gravity force
- vel=1.0, # length of emission vector
- partDuration=1.0 # single particle's duration
- ):
- self.__emitter = emitter
- self.__texture = texture
- # let's compute the total number of particles
- self.__numPart = int(round(partDuration * 1 / rate))
- self.__rate = rate
- self.__gravity = gravity
- self.__vel = vel
- self.__partDuration = partDuration
- self.__nodepath = render.attachNewNode(self.__node())
- self.__nodepath.setTransparency(True) # particles have alpha
- self.__nodepath.setBin("fixed", 0) # render it at the end
- self.__setTextures()
- self.__setShader()
- self.__nodepath.setRenderModeThickness(10) # we want sprite particles
- self.__nodepath.setTexGen(TextureStage.getDefault(),
- TexGenAttrib.MPointSprite)
- self.__nodepath.setDepthWrite(False) # don't sort the particles
- self.__upd_tsk = taskMgr.add(self.__update, "update")
- def __node(self):
- # this function creates and returns particles' GeomNode
- points = GeomPoints(GeomEnums.UH_static)
- points.addNextVertices(self.__numPart)
- format_ = GeomVertexFormat.getEmpty()
- geom = Geom(GeomVertexData("abc", format_, GeomEnums.UH_static))
- geom.addPrimitive(points)
- geom.setBounds(OmniBoundingVolume()) # always render it
- node = GeomNode("node")
- node.addGeom(geom)
- return node
- def __setTextures(self):
- # initial positions are all zeros (each position is denoted by 4 values)
- # positions are stored in a texture
- positions = [(0, 0, 0, 1) for i in range(self.__numPart)]
- posLst = list(chain.from_iterable(positions))
- self.__texPos = self.__buffTex(posLst)
- # define emission times' texture
- emissionTimes = [(self.__rate * i, 0, 0, 0)
- for i in range(self.__numPart)]
- timesLst = list(chain.from_iterable(emissionTimes))
- self.__texTimes = self.__buffTex(timesLst)
- # define a list with emission velocities
- velocities = [self.__rndVel() for _ in range(self.__numPart)]
- velLst = list(chain.from_iterable(velocities))
- # we need two textures,
- # the first one contains the emission velocity (we need to keep it for
- # particle recycling)...
- self.__texStartVel = self.__buffTex(velLst)
- # ... and the second one contains the current velocities
- self.__texCurrVel = self.__buffTex(velLst)
- def __buffTex(self, values):
- # this function returns a buffer texture with the received values
- data = array("f", values)
- tex = Texture("tex")
- tex.setupBufferTexture(self.__numPart, Texture.T_float,
- Texture.F_rgba32, GeomEnums.UH_static)
- tex.setRamImage(data)
- return tex
- def __rndVel(self):
- # this method returns a random vector for emitting the particle
- theta = uniform(0, pi / 12)
- phi = uniform(0, 2 * pi)
- vec = LVector3(
- sin(theta) * cos(phi),
- sin(theta) * sin(phi),
- cos(theta))
- vec *= uniform(self.__vel * .8, self.__vel * 1.2)
- return [vec.x, vec.y, vec.z, 1]
- def __setShader(self):
- shader = Shader.make(Shader.SL_GLSL, vert, frag)
- # Apply the shader to the node, but set a special flag indicating that
- # the point size is controlled bythe shader.
- attrib = ShaderAttrib.make(shader)
- attrib = attrib.setFlag(ShaderAttrib.F_shader_point_size, True)
- self.__nodepath.setAttrib(attrib)
- self.__nodepath.setShaderInputs(
- positions=self.__texPos,
- emitter_pos=self.__emitter.getPos(render),
- start_vel=self.__texStartVel,
- velocities=self.__texCurrVel,
- accel=(0, 0, self.__gravity),
- start_time=base.clock.getFrameTime(),
- emission_times=self.__texTimes,
- part_duration=self.__partDuration,
- image=loader.loadTexture(self.__texture))
- def __update(self, task):
- pos = self.__emitter.getPos(render)
- self.__nodepath.setShaderInput("emitter_pos", pos)
- return task.again
- class ParticleDemo(ShowBase):
- def __init__(self):
- loadPrcFileData("config", config)
- ShowBase.__init__(self)
- # Standard title and instruction text
- self.title = OnscreenText(
- text="Panda3D: Tutorial - Shader-based Particles",
- parent=base.a2dBottomCenter,
- style=1, fg=(1, 1, 1, 1), pos=(0, 0.1), scale=.08)
- self.escapeEvent = OnscreenText(
- text=HELP_TEXT, parent=base.a2dTopLeft,
- style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.06),
- align=TextNode.ALeft, scale=.05)
- # More standard initialization
- self.accept('escape', sys.exit)
- self.accept('arrow_left', self.rotate, ['left'])
- self.accept('arrow_right', self.rotate, ['right'])
- base.disableMouse()
- base.camera.setPos(0, -20, 2)
- base.setBackgroundColor(0, 0, 0)
- self.teapot = loader.loadModel("teapot")
- self.teapot.setPos(0, 10, 0)
- self.teapot.reparentTo(render)
- self.setupLights()
- # we define a nodepath as particle's emitter
- self.emitter = NodePath("emitter")
- self.emitter.reparentTo(self.teapot)
- self.emitter.setPos(3.000, 0.000, 2.550)
- # let's create the particle system
- Particle(self.emitter, "smoke.png", gravity=.01, vel=1.2,
- partDuration=5.0)
- def rotate(self, direction):
- direction_factor = (1 if direction == "left" else -1)
- self.teapot.setH(self.teapot.getH() + 10 * direction_factor)
- # Set up lighting
- def setupLights(self):
- ambientLight = AmbientLight("ambientLight")
- ambientLight.setColor((.4, .4, .35, 1))
- directionalLight = DirectionalLight("directionalLight")
- directionalLight.setDirection(LVector3(0, 8, -2.5))
- directionalLight.setColor((0.9, 0.8, 0.9, 1))
- # Set lighting on teapot so steam doesn't get affected
- self.teapot.setLight(self.teapot.attachNewNode(directionalLight))
- self.teapot.setLight(self.teapot.attachNewNode(ambientLight))
- demo = ParticleDemo()
- demo.run()
|