ShadowDemo.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. """Create a cheesy shadow effect by rendering the view of an
  2. object (e.g. the local avatar) from a special camera as seen from
  3. above (as if from the sun), using a solid gray foreground and a
  4. solid white background, and then multitexturing that view onto the
  5. world.
  6. This is meant primarily as a demonstration of multipass and
  7. multitexture rendering techniques. It's not a particularly great
  8. way to do shadows.
  9. """
  10. __all__ = ['ShadowCaster', 'avatarShadow', 'piratesAvatarShadow', 'arbitraryShadow']
  11. from panda3d.core import *
  12. from direct.task import Task
  13. sc = None
  14. class ShadowCaster:
  15. texXSize = 128
  16. texYSize = 128
  17. def __init__(self, lightPath, objectPath, filmX, filmY):
  18. self.lightPath = lightPath
  19. self.objectPath = objectPath
  20. self.groundPath = None
  21. # Create an offscreen buffer to render the view of the avatar
  22. # into a texture.
  23. self.buffer = base.win.makeTextureBuffer(
  24. 'shadowBuffer', self.texXSize, self.texYSize)
  25. # The background of this buffer--and the border of the
  26. # texture--is pure white.
  27. clearColor = VBase4(1, 1, 1, 1)
  28. self.buffer.setClearColor(clearColor)
  29. self.tex = self.buffer.getTexture()
  30. self.tex.setBorderColor(clearColor)
  31. self.tex.setWrapU(Texture.WMBorderColor)
  32. self.tex.setWrapV(Texture.WMBorderColor)
  33. # Set up a display region on this buffer, and create a camera.
  34. dr = self.buffer.makeDisplayRegion()
  35. self.camera = Camera('shadowCamera')
  36. self.cameraPath = self.lightPath.attachNewNode(self.camera)
  37. self.camera.setScene(self.objectPath)
  38. dr.setCamera(self.cameraPath)
  39. # Use a temporary NodePath to define the initial state for the
  40. # camera. The initial state will render everything in a
  41. # flat-shaded gray, as if it were a shadow.
  42. initial = NodePath('initial')
  43. initial.setColor(0.6, 0.6, 0.6, 1, 1)
  44. initial.setTextureOff(2)
  45. initial.setLightOff(2)
  46. self.camera.setInitialState(initial.getState())
  47. # Use an orthographic lens for this camera instead of the
  48. # usual perspective lens. An orthographic lens is better to
  49. # simulate sunlight, which is (almost) orthographic. We set
  50. # the film size large enough to render a typical avatar (but
  51. # not so large that we lose detail in the texture).
  52. self.lens = OrthographicLens()
  53. self.lens.setFilmSize(filmX, filmY)
  54. self.camera.setLens(self.lens)
  55. # Finally, we'll need a unique TextureStage to apply this
  56. # shadow texture to the world.
  57. self.stage = TextureStage('shadow')
  58. # Make sure the shadowing object doesn't get its own shadow
  59. # applied to it.
  60. self.objectPath.setTextureOff(self.stage)
  61. def setGround(self, groundPath):
  62. """ Specifies the part of the world that is to be considered
  63. the ground: this is the part onto which the rendered texture
  64. will be applied. """
  65. if self.groundPath:
  66. self.groundPath.clearProjectTexture(self.stage)
  67. self.groundPath = groundPath
  68. self.groundPath.projectTexture(self.stage, self.tex, self.cameraPath)
  69. def clear(self):
  70. """ Undoes the effect of the ShadowCaster. """
  71. if self.groundPath:
  72. self.groundPath.clearProjectTexture(self.stage)
  73. self.groundPath = None
  74. if self.lightPath:
  75. self.lightPath.detachNode()
  76. self.lightPath = None
  77. if self.cameraPath:
  78. self.cameraPath.detachNode()
  79. self.cameraPath = None
  80. self.camera = None
  81. self.lens = None
  82. if self.buffer:
  83. base.graphicsEngine.removeWindow(self.buffer)
  84. self.tex = None
  85. self.buffer = None
  86. def avatarShadow():
  87. # Turn off the existing drop shadow.
  88. # stash it so that when the game hides and shows it, it will still be gone
  89. base.localAvatar.dropShadow.stash()
  90. # Set up a new node to hold the "light": this is an abitrary point
  91. # somewhere above the avatar, looking down, as if from the sun.
  92. objectPath = base.localAvatar.getGeomNode()
  93. shadowCamera = objectPath.attachNewNode('shadowCamera')
  94. lightPath = shadowCamera.attachNewNode('lightPath')
  95. # We can change this position at will to change the angle of the
  96. # sun.
  97. lightPath.setPos(5, 0, 7)
  98. # We need a task to keep the shadowCamera rotated in the same
  99. # direction relative to render (otherwise, the shadow seems to
  100. # rotate when you rotate your avatar, which is strange). We can't
  101. # just use a compass effect, since that doesn't work on cameras.
  102. def shadowCameraRotate(task, shadowCamera = shadowCamera):
  103. shadowCamera.setHpr(render, 0, 0, 0)
  104. lightPath.lookAt(shadowCamera, 0, 0, 3)
  105. return Task.cont
  106. taskMgr.remove('shadowCamera')
  107. taskMgr.add(shadowCameraRotate, 'shadowCamera')
  108. global sc
  109. if sc != None:
  110. sc.clear()
  111. sc = ShadowCaster(lightPath, objectPath, 4, 6)
  112. # Naively, just apply the shadow to everything in the world. It
  113. # would probably be better to use a little restraint.
  114. sc.setGround(render)
  115. return sc
  116. def piratesAvatarShadow():
  117. a = avatarShadow()
  118. # Force the lod to be 0 at all times
  119. base.localAvatar.getGeomNode().getChild(0).node().forceSwitch(0)
  120. return a
  121. def arbitraryShadow(node):
  122. # Turn off the existing drop shadow, if any
  123. if hasattr(node, "dropShadow"):
  124. # stash it so that when the game hides and shows it, it will still be gone
  125. node.dropShadow.stash()
  126. # Set up a new node to hold the "light": this is an abitrary point
  127. # somewhere above the node, looking down, as if from the sun.
  128. objectPath = node
  129. shadowCamera = objectPath.attachNewNode('shadowCamera')
  130. lightPath = shadowCamera.attachNewNode('lightPath')
  131. # We can change this position at will to change the angle of the
  132. # sun.
  133. lightPath.setPos(50, 0, 50)
  134. # We need a task to keep the shadowCamera rotated in the same
  135. # direction relative to render (otherwise, the shadow seems to
  136. # rotate when you rotate your avatar, which is strange). We can't
  137. # just use a compass effect, since that doesn't work on cameras.
  138. def shadowCameraRotate(task, shadowCamera = shadowCamera):
  139. shadowCamera.setHpr(render, 0, 0, 0)
  140. lightPath.lookAt(shadowCamera, 0, 0, 3)
  141. return Task.cont
  142. taskMgr.remove('shadowCamera')
  143. taskMgr.add(shadowCameraRotate, 'shadowCamera')
  144. global sc
  145. if sc != None:
  146. sc.clear()
  147. sc = ShadowCaster(lightPath, objectPath, 100, 100)
  148. # Naively, just apply the shadow to everything in the world. It
  149. # would probably be better to use a little restraint.
  150. sc.setGround(render)
  151. return sc
  152. ##def testShadow():
  153. ## a = piratesAvatarShadow()
  154. ##
  155. ##from direct.showbase.ShadowDemo import *
  156. ##from direct.interval.IntervalGlobal import *
  157. ##b = loader.loadModel('/i/beta/PotC/Maya/Pirates/scenes/models/sets/buildings/spanish_buildings/TavernIntExt/tavern_ext/bar.egg')
  158. ##bs = arbitraryShadow(b)
  159. ##s = loader.loadModel('smiley')
  160. ##s.reparentTo(bs.lightPath)
  161. ##b.reparentTo((base.localAvatar))
  162. ##a = AmbientLight('cloudAmbientHi')
  163. ##a.setColor(Vec4(0.9, 0.9, 0.9, 1.000))
  164. ##aNP = s.attachNewNode(a.upcastToPandaNode())
  165. ##b.setLight(aNP)
  166. ##d = DirectionalLight("chernabogDirectionalLight")
  167. ##d.setDirection(Vec3(0, 1, 0))
  168. ##d.setColor(Vec4(1))
  169. ###d.setColor(Vec4(0.9, 0.7, 0.7, 1.000))
  170. ##dNP = s.attachNewNode(d.upcastToPandaNode())
  171. ##b.setLight(dNP)
  172. ##
  173. ##ival = Sequence(LerpPosInterval(bs.lightPath, 0.0, Vec3(-200, 0, 50)),
  174. ## LerpPosInterval(bs.lightPath, 10.0, Vec3(-200, 0, 200)),
  175. ## LerpPosInterval(bs.lightPath, 10.0, Vec3(200, 0, 200)),
  176. ## LerpPosInterval(bs.lightPath, 10.0, Vec3(200, 0, 50)),
  177. ##)
  178. ##ival.loop()