forked from panda3d/panda3d
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
executable file
·416 lines (346 loc) · 16.9 KB
/
main.py
File metadata and controls
executable file
·416 lines (346 loc) · 16.9 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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#!/usr/bin/env python
# Author: Josh Yelon
# Date: 7/11/2005
#
# See the associated manual page for an explanation.
#
from direct.showbase.ShowBase import ShowBase
from panda3d.core import FrameBufferProperties, WindowProperties
from panda3d.core import GraphicsPipe, GraphicsOutput
from panda3d.core import Filename, Texture, Shader
from panda3d.core import RenderState, CardMaker
from panda3d.core import PandaNode, TextNode, NodePath
from panda3d.core import RenderAttrib, AlphaTestAttrib, ColorBlendAttrib
from panda3d.core import CullFaceAttrib, DepthTestAttrib, DepthWriteAttrib
from panda3d.core import LPoint3, LVector3, BitMask32
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.interval.MetaInterval import Sequence
from direct.task.Task import Task
from direct.actor.Actor import Actor
import sys
import os
import random
# Function to put instructions on the screen.
def addInstructions(pos, msg):
return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
parent=base.a2dTopLeft, align=TextNode.ALeft,
pos=(0.08, -pos - 0.04), scale=.05)
# Function to put title on the screen.
def addTitle(text):
return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
parent=base.a2dBottomRight, align=TextNode.ARight,
fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
class FireflyDemo(ShowBase):
def __init__(self):
# Initialize the ShowBase class from which we inherit, which will
# create a window and set up everything we need for rendering into it.
ShowBase.__init__(self)
self.setBackgroundColor((0, 0, 0, 0))
# Preliminary capabilities check.
if not self.win.getGsg().getSupportsBasicShaders():
self.t = addTitle("Firefly Demo: Video driver reports that Cg "
"shaders are not supported.")
return
if not self.win.getGsg().getSupportsDepthTexture():
self.t = addTitle("Firefly Demo: Video driver reports that depth "
"textures are not supported.")
return
# This algorithm uses two offscreen buffers, one of which has
# an auxiliary bitplane, and the offscreen buffers share a single
# depth buffer. This is a heck of a complicated buffer setup.
self.modelbuffer = self.makeFBO("model buffer", 1)
self.lightbuffer = self.makeFBO("light buffer", 0)
# Creation of a high-powered buffer can fail, if the graphics card
# doesn't support the necessary OpenGL extensions.
if self.modelbuffer is None or self.lightbuffer is None:
self.t = addTitle("Firefly Demo: Video driver does not support "
"multiple render targets")
return
# Create four render textures: depth, normal, albedo, and final.
# attach them to the various bitplanes of the offscreen buffers.
self.texDepth = Texture()
self.texDepth.setFormat(Texture.FDepthStencil)
self.texAlbedo = Texture()
self.texNormal = Texture()
self.texFinal = Texture()
self.modelbuffer.addRenderTexture(self.texDepth,
GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepthStencil)
self.modelbuffer.addRenderTexture(self.texAlbedo,
GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
self.modelbuffer.addRenderTexture(self.texNormal,
GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0)
self.lightbuffer.addRenderTexture(self.texFinal,
GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
# Set the near and far clipping planes.
self.cam.node().getLens().setNear(50.0)
self.cam.node().getLens().setFar(500.0)
lens = self.cam.node().getLens()
# This algorithm uses three cameras: one to render the models into the
# model buffer, one to render the lights into the light buffer, and
# one to render "plain" stuff (non-deferred shaded) stuff into the
# light buffer. Each camera has a bitmask to identify it.
self.modelMask = 1
self.lightMask = 2
self.plainMask = 4
self.modelcam = self.makeCamera(self.modelbuffer,
lens=lens, scene=render, mask=self.modelMask)
self.lightcam = self.makeCamera(self.lightbuffer,
lens=lens, scene=render, mask=self.lightMask)
self.plaincam = self.makeCamera(self.lightbuffer,
lens=lens, scene=render, mask=self.plainMask)
# Panda's main camera is not used.
self.cam.node().setActive(0)
# Take explicit control over the order in which the three
# buffers are rendered.
self.modelbuffer.setSort(1)
self.lightbuffer.setSort(2)
self.win.setSort(3)
# Within the light buffer, control the order of the two cams.
self.lightcam.node().getDisplayRegion(0).setSort(1)
self.plaincam.node().getDisplayRegion(0).setSort(2)
# By default, panda usually clears the screen before every
# camera and before every window. Tell it not to do that.
# Then, tell it specifically when to clear and what to clear.
self.modelcam.node().getDisplayRegion(0).disableClears()
self.lightcam.node().getDisplayRegion(0).disableClears()
self.plaincam.node().getDisplayRegion(0).disableClears()
self.cam.node().getDisplayRegion(0).disableClears()
self.cam2d.node().getDisplayRegion(0).disableClears()
self.modelbuffer.disableClears()
self.win.disableClears()
self.modelbuffer.setClearColorActive(1)
self.modelbuffer.setClearDepthActive(1)
self.lightbuffer.setClearColorActive(1)
self.lightbuffer.setClearColor((0, 0, 0, 1))
# Miscellaneous stuff.
self.disableMouse()
self.camera.setPos(-9.112, -211.077, 46.951)
self.camera.setHpr(0, -7.5, 2.4)
random.seed()
# Calculate the projection parameters for the final shader.
# The math here is too complex to explain in an inline comment,
# I've put in a full explanation into the HTML intro.
proj = self.cam.node().getLens().getProjectionMat()
proj_x = 0.5 * proj.getCell(3, 2) / proj.getCell(0, 0)
proj_y = 0.5 * proj.getCell(3, 2)
proj_z = 0.5 * proj.getCell(3, 2) / proj.getCell(2, 1)
proj_w = -0.5 - 0.5 * proj.getCell(1, 2)
# Configure the render state of the model camera.
tempnode = NodePath(PandaNode("temp node"))
tempnode.setAttrib(
AlphaTestAttrib.make(RenderAttrib.MGreaterEqual, 0.5))
tempnode.setShader(loader.loadShader("model.sha"))
tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MLessEqual))
self.modelcam.node().setInitialState(tempnode.getState())
# Configure the render state of the light camera.
tempnode = NodePath(PandaNode("temp node"))
tempnode.setShader(loader.loadShader("light.sha"))
tempnode.setShaderInput("texnormal", self.texNormal)
tempnode.setShaderInput("texalbedo", self.texAlbedo)
tempnode.setShaderInput("texdepth", self.texDepth)
tempnode.setShaderInput("proj", (proj_x, proj_y, proj_z, proj_w))
tempnode.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd,
ColorBlendAttrib.OOne, ColorBlendAttrib.OOne))
tempnode.setAttrib(
CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise))
# The next line causes problems on Linux.
# tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MGreaterEqual))
tempnode.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOff))
self.lightcam.node().setInitialState(tempnode.getState())
# Configure the render state of the plain camera.
rs = RenderState.makeEmpty()
self.plaincam.node().setInitialState(rs)
# Clear any render attribs on the root node. This is necessary
# because by default, panda assigns some attribs to the root
# node. These default attribs will override the
# carefully-configured render attribs that we just attached
# to the cameras. The simplest solution is to just clear
# them all out.
render.setState(RenderState.makeEmpty())
# My artist created a model in which some of the polygons
# don't have textures. This confuses the shader I wrote.
# This little hack guarantees that everything has a texture.
white = loader.loadTexture("models/white.jpg")
render.setTexture(white, 0)
# Create two subroots, to help speed cull traversal.
self.lightroot = NodePath(PandaNode("lightroot"))
self.lightroot.reparentTo(render)
self.modelroot = NodePath(PandaNode("modelroot"))
self.modelroot.reparentTo(render)
self.lightroot.hide(BitMask32(self.modelMask))
self.modelroot.hide(BitMask32(self.lightMask))
self.modelroot.hide(BitMask32(self.plainMask))
# Load the model of a forest. Make it visible to the model camera.
# This is a big model, so we load it asynchronously while showing a
# load text. We do this by passing in a callback function.
self.loading = addTitle("Loading models...")
self.forest = NodePath(PandaNode("Forest Root"))
self.forest.reparentTo(render)
self.forest.hide(BitMask32(self.lightMask | self.plainMask))
loader.loadModel([
"models/background",
"models/foliage01",
"models/foliage02",
"models/foliage03",
"models/foliage04",
"models/foliage05",
"models/foliage06",
"models/foliage07",
"models/foliage08",
"models/foliage09"],
callback=self.finishLoading)
# Cause the final results to be rendered into the main window on a
# card.
self.card = self.lightbuffer.getTextureCard()
self.card.setTexture(self.texFinal)
self.card.reparentTo(render2d)
# Panda contains a built-in viewer that lets you view the results of
# your render-to-texture operations. This code configures the viewer.
self.bufferViewer.setPosition("llcorner")
self.bufferViewer.setCardSize(0, 0.40)
self.bufferViewer.setLayout("vline")
self.toggleCards()
self.toggleCards()
# Firefly parameters
self.fireflies = []
self.sequences = []
self.scaleseqs = []
self.glowspheres = []
self.fireflysize = 1.0
self.spheremodel = loader.loadModel("misc/sphere")
# Create the firefly model, a fuzzy dot
dotSize = 1.0
cm = CardMaker("firefly")
cm.setFrame(-dotSize, dotSize, -dotSize, dotSize)
self.firefly = NodePath(cm.generate())
self.firefly.setTexture(loader.loadTexture("models/firefly.png"))
self.firefly.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add,
ColorBlendAttrib.O_incoming_alpha, ColorBlendAttrib.O_one))
# these allow you to change parameters in realtime
self.accept("escape", sys.exit, [0])
self.accept("arrow_up", self.incFireflyCount, [1.1111111])
self.accept("arrow_down", self.decFireflyCount, [0.9000000])
self.accept("arrow_right", self.setFireflySize, [1.1111111])
self.accept("arrow_left", self.setFireflySize, [0.9000000])
self.accept("v", self.toggleCards)
self.accept("V", self.toggleCards)
def finishLoading(self, models):
# This function is used as callback to loader.loadModel, and called
# when all of the models have finished loading.
# Attach the models to the scene graph.
for model in models:
model.reparentTo(self.forest)
# Show the instructions.
self.loading.destroy()
self.title = addTitle("Panda3D: Tutorial - Fireflies using Deferred Shading")
self.inst1 = addInstructions(0.06, "ESC: Quit")
self.inst2 = addInstructions(0.12, "Up/Down: More / Fewer Fireflies (Count: unknown)")
self.inst3 = addInstructions(0.18, "Right/Left: Bigger / Smaller Fireflies (Radius: unknown)")
self.inst4 = addInstructions(0.24, "V: View the render-to-texture results")
self.setFireflySize(25.0)
while len(self.fireflies) < 5:
self.addFirefly()
self.updateReadout()
self.nextadd = 0
taskMgr.add(self.spawnTask, "spawner")
def makeFBO(self, name, auxrgba):
# This routine creates an offscreen buffer. All the complicated
# parameters are basically demanding capabilities from the offscreen
# buffer - we demand that it be able to render to texture on every
# bitplane, that it can support aux bitplanes, that it track
# the size of the host window, that it can render to texture
# cumulatively, and so forth.
winprops = WindowProperties()
props = FrameBufferProperties()
props.setRgbColor(True)
props.setRgbaBits(8, 8, 8, 8)
props.setDepthBits(1)
props.setAuxRgba(auxrgba)
return self.graphicsEngine.makeOutput(
self.pipe, "model buffer", -2,
props, winprops,
GraphicsPipe.BFSizeTrackHost | GraphicsPipe.BFCanBindEvery |
GraphicsPipe.BFRttCumulative | GraphicsPipe.BFRefuseWindow,
self.win.getGsg(), self.win)
def addFirefly(self):
pos1 = LPoint3(random.uniform(-50, 50), random.uniform(-100, 150), random.uniform(-10, 80))
dir = LVector3(random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1))
dir.normalize()
pos2 = pos1 + (dir * 20)
fly = self.lightroot.attachNewNode(PandaNode("fly"))
glow = fly.attachNewNode(PandaNode("glow"))
dot = fly.attachNewNode(PandaNode("dot"))
color_r = 1.0
color_g = random.uniform(0.8, 1.0)
color_b = min(color_g, random.uniform(0.5, 1.0))
fly.setColor(color_r, color_g, color_b, 1.0)
fly.setShaderInput("lightcolor", (color_r, color_g, color_b, 1.0))
int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2)
int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1)
si1 = fly.scaleInterval(random.uniform(0.8, 1.5),
LPoint3(0.2, 0.2, 0.2), LPoint3(0.2, 0.2, 0.2))
si2 = fly.scaleInterval(random.uniform(1.5, 0.8),
LPoint3(1.0, 1.0, 1.0), LPoint3(0.2, 0.2, 0.2))
si3 = fly.scaleInterval(random.uniform(1.0, 2.0),
LPoint3(0.2, 0.2, 0.2), LPoint3(1.0, 1.0, 1.0))
siseq = Sequence(si1, si2, si3)
siseq.loop()
siseq.setT(random.uniform(0, 1000))
seq = Sequence(int1, int2)
seq.loop()
self.spheremodel.instanceTo(glow)
self.firefly.instanceTo(dot)
glow.setScale(self.fireflysize * 1.1)
glow.hide(BitMask32(self.modelMask | self.plainMask))
dot.hide(BitMask32(self.modelMask | self.lightMask))
dot.setColor(color_r, color_g, color_b, 1.0)
self.fireflies.append(fly)
self.sequences.append(seq)
self.glowspheres.append(glow)
self.scaleseqs.append(siseq)
def updateReadout(self):
self.inst2.destroy()
self.inst2 = addInstructions(0.12,
"Up/Down: More / Fewer Fireflies (Currently: %d)" % len(self.fireflies))
self.inst3.destroy()
self.inst3 = addInstructions(0.18,
"Right/Left: Bigger / Smaller Fireflies (Radius: %d ft)" % self.fireflysize)
def toggleCards(self):
self.bufferViewer.toggleEnable()
# When the cards are not visible, I also disable the color clear.
# This color-clear is actually not necessary, the depth-clear is
# sufficient for the purposes of the algorithm.
if (self.bufferViewer.isEnabled()):
self.modelbuffer.setClearColorActive(True)
else:
self.modelbuffer.setClearColorActive(False)
def incFireflyCount(self, scale):
n = int((len(self.fireflies) * scale) + 1)
while (n > len(self.fireflies)):
self.addFirefly()
self.updateReadout()
def decFireflyCount(self, scale):
n = int(len(self.fireflies) * scale)
if (n < 1):
n = 1
while (len(self.fireflies) > n):
self.glowspheres.pop()
self.sequences.pop().finish()
self.scaleseqs.pop().finish()
self.fireflies.pop().removeNode()
self.updateReadout()
def setFireflySize(self, n):
n = n * self.fireflysize
self.fireflysize = n
for x in self.glowspheres:
x.setScale(self.fireflysize * 1.1)
self.updateReadout()
def spawnTask(self, task):
if task.time > self.nextadd:
self.nextadd = task.time + 1.0
if (len(self.fireflies) < 300):
self.incFireflyCount(1.03)
return Task.cont
demo = FireflyDemo()
demo.run()