diff --git a/direct/src/showbase/ShadowDemo.py b/direct/src/showbase/ShadowDemo.py new file mode 100755 index 0000000000..0f82e56e90 --- /dev/null +++ b/direct/src/showbase/ShadowDemo.py @@ -0,0 +1,142 @@ + +"""Create a cheesy shadow effect by rendering the view of an +object (e.g. the local avatar) from a special camera as seen from +above (as if from the sun), using a solid gray foreground and a +solid white background, and then multitexturing that view onto the +world. + +This is meant primarily as a demonstration of multipass and +multitexture rendering techniques. It's not a particularly great +way to do shadows. +""" + +from pandac.PandaModules import * +from direct.task import Task + +sc = None + +class ShadowCaster: + texXSize = 256 + texYSize = 256 + + def __init__(self, lightPath, objectPath): + self.lightPath = lightPath + self.objectPath = objectPath + self.groundPath = None + + # Create an offscreen buffer to render the view of the avatar + # into a texture. + self.buffer = base.win.makeTextureBuffer( + 'shadowBuffer', self.texXSize, self.texYSize) + + # The background of this buffer--and the border of the + # texture--is pure white. + clearColor = VBase4(1, 1, 1, 1) + self.buffer.setClearColor(clearColor) + self.tex = self.buffer.getTexture() + self.tex.setBorderColor(clearColor) + self.tex.setWrapu(Texture.WMBorderColor) + self.tex.setWrapv(Texture.WMBorderColor) + + # Set up a display region on this buffer, and create a camera. + layer = self.buffer.getChannel(0).makeLayer() + dr = layer.makeDisplayRegion() + self.camera = Camera('shadowCamera') + self.cameraPath = self.lightPath.attachNewNode(self.camera) + self.camera.setScene(self.objectPath) + dr.setCamera(self.cameraPath) + + # Use a temporary NodePath to define the initial state for the + # camera. The initial state will render everything in a + # flat-shaded gray, as if it were a shadow. + initial = NodePath('initial') + initial.setColor(0.5, 0.5, 0.5, 1, 1) + initial.setTextureOff(2) + self.camera.setInitialState(initial.getState()) + + # Use an orthographic lens for this camera instead of the + # usual perspective lens. An orthographic lens is better to + # simulate sunlight, which is (almost) orthographic. We set + # the film size large enough to render a typical avatar (but + # not so large that we lose detail in the texture). + self.lens = OrthographicLens() + self.lens.setFilmSize(4, 6) + self.camera.setLens(self.lens) + + # Finally, we'll need a unique TextureStage to apply this + # shadow texture to the world. + self.stage = TextureStage('shadow') + + # Make sure the shadowing object doesn't get its own shadow + # applied to it. + self.objectPath.setTextureOff(self.stage) + + def setGround(self, groundPath): + """ Specifies the part of the world that is to be considered + the ground: this is the part onto which the rendered texture + will be applied. """ + + if self.groundPath: + self.groundPath.clearProjectTexture(self.stage) + + self.groundPath = groundPath + self.groundPath.projectTexture(self.stage, self.tex, self.cameraPath) + + def clear(self): + """ Undoes the effect of the ShadowCaster. """ + if self.groundPath: + self.groundPath.clearProjectTexture(self.stage) + self.groundPath = None + + if self.lightPath: + self.lightPath.detachNode() + self.lightPath = None + + if self.cameraPath: + self.cameraPath.detachNode() + self.cameraPath = None + self.camera = None + self.lens = None + + if self.buffer: + base.graphicsEngine.removeWindow(self.buffer) + self.tex = None + self.buffer = None + +def avatarShadow(): + # Turn off the existing drop shadow. + base.localAvatar.dropShadow.hide() + + # Set up a new node to hold the "light": this is an abitrary point + # somewhere above the avatar, looking down, as if from the sun. + objectPath = base.localAvatar + shadowCamera = objectPath.attachNewNode('shadowCamera') + lightPath = shadowCamera.attachNewNode('lightPath') + + # We can change this position at will to change the angle of the + # sun. + lightPath.setPos(5, 0, 7) + + # We need a task to keep the shadowCamera rotated in the same + # direction relative to render (otherwise, the shadow seems to + # rotate when you rotate your avatar, which is strange). We can't + # just use a compass effect, since that doesn't work on cameras. + def shadowCameraRotate(task, shadowCamera = shadowCamera): + shadowCamera.setHpr(render, 0, 0, 0) + lightPath.lookAt(shadowCamera, 0, 0, 3) + return Task.cont + + taskMgr.remove('shadowCamera') + taskMgr.add(shadowCameraRotate, 'shadowCamera') + + global sc + if sc != None: + sc.clear() + + sc = ShadowCaster(lightPath, objectPath) + + # Naively, just apply the shadow to everything in the world. It + # would probably be better to use a little restraint. + sc.setGround(render) + + return sc