forked from panda3d/panda3d
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadvanced.py
More file actions
executable file
·278 lines (239 loc) · 10.4 KB
/
advanced.py
File metadata and controls
executable file
·278 lines (239 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/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=globalClock.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()