示例#1
0
class ShadowSource(DebugObject, ShaderStructElement):
    """ This class can be seen as a camera. It stores the necessary data to 
    generate and store the shadow map for the assigned lens (like computing the MVP), 
    and also stores information about the shadowmap, like position in the 
    shadow atlas, or resolution. Each ShadowSource has a unique index, 
    which is used by the lights to identify which sources belong to it.
    """

    # Store a global index for assigning unique ids to the instances
    _GlobalShadowIndex = 999

    @classmethod
    def getExposedAttributes(self):
        return {
            "resolution": "int",
            "atlasPos": "vec2",
            "mvp": "mat4",
            "nearPlane": "float",
            "farPlane": "float"
        }

    @classmethod
    def _generateUID(self):
        """ Generates an uid and returns that """
        self._GlobalShadowIndex += 1
        return self._GlobalShadowIndex

    def __init__(self):
        """ Creates a new ShadowSource. After the creation, a lens can be added
        with setupPerspectiveLens or setupOrtographicLens. """
        self.index = self._generateUID()
        DebugObject.__init__(self, "ShadowSource-" + str(self.index))
        ShaderStructElement.__init__(self)

        self.valid = False
        self.camera = Camera("ShadowSource-" + str(self.index))
        self.camera.setActive(False)
        self.cameraNode = NodePath(self.camera)
        self.cameraNode.reparentTo(Globals.render.find("RPCameraDummys"))
        self.cameraNode.hide()
        self.resolution = 512
        self.atlasPos = Vec2(0)
        self.doesHaveAtlasPos = False
        self.sourceIndex = 0
        self.mvp = UnalignedLMatrix4f()
        self.sourceIndex = -1
        self.nearPlane = 0.0
        self.farPlane = 1000.0
        self.converterYUR = None
        self.transforMat = TransformState.makeMat(
            Mat4.convertMat(
                Globals.base.win.getGsg().getInternalCoordinateSystem(),
                CSZupRight))

    def cleanup(self):
        """ Cleans up the shadow source """
        self.cameraNode.removeNode()

    def setFilmSize(self, size_x, size_y):
        """ Sets the film size of the source, this is equivalent to setFilmSize
        on a Lens. """
        self.lens.setFilmSize(size_x, size_y)
        self.rebuildMatrixCache()

    def getLens(self):
        """ Returns the source lens """
        return self.lens

    def getSourceIndex(self):
        """ Returns the assigned source index. The source index is the index
        of the ShadowSource in the ShadowSources array of the assigned
        Light. """
        return self.sourceIndex

    def getUID(self):
        """ Returns the uid of the shadow source """
        return self.index

    def setSourceIndex(self, index):
        """ Sets the source index of this source. This is called by the light,
        as only the light knows at which position this source is in the
        Sources array. """
        self.sourceIndex = index

    def computeMVP(self):
        """ Computes the modelViewProjection matrix for the lens. Actually,
        this is the worldViewProjection matrix, but for convenience it is
        called mvp. """

        self.rebuildMatrixCache()
        projMat = self.converterYUR
        # modelViewMat = self.transforMat.invertCompose(
        modelViewMat = Globals.render.getTransform(self.cameraNode).getMat()
        return UnalignedLMatrix4f(modelViewMat * projMat)

    def assignAtlasPos(self, x, y):
        """ Assigns this source a position in the shadow atlas. This is called
        by the shadow atlas. Coordinates are float from 0 .. 1 """
        self.atlasPos = Vec2(x, y)
        self.doesHaveAtlasPos = True

    def update(self):
        """ Updates the shadow source. Currently only recomputes the mvp and
        triggers an array update """
        self.mvp = self.computeMVP()
        self.onPropertyChanged()

    def getAtlasPos(self):
        """ Returns the assigned atlas pos, if present. Coordinates are float
        from 0 .. 1 """
        return self.atlasPos

    def hasAtlasPos(self):
        """ Returns Whether this ShadowSource has already a position in the
        shadow atlas, or is currently unassigned """
        return self.doesHaveAtlasPos

    def removeFromAtlas(self):
        """ Deletes the atlas coordinates, this gets called by the atlas after the
        Source got removed from the atlas """
        self.doesHaveAtlasPos = False
        self.atlasPos = Vec2(0)

    def setResolution(self, resolution):
        """ Sets the resolution in pixels of this shadow source. Has to be
        a multiple of the tileSize specified in LightManager """
        assert (resolution > 1 and resolution <= 8192)
        self.resolution = resolution

    def getResolution(self):
        """ Returns the resolution of the shadow source in pixels """
        return self.resolution

    def setupPerspectiveLens(self, near=0.1, far=100.0, fov=(90, 90)):
        """ Setups a PerspectiveLens with a given near plane, far plane
        and FoV. The FoV is a tuple in the format (Horizontal FoV, Vertical FoV) """
        self.lens = PerspectiveLens()
        self.lens.setNearFar(near, far)
        self.lens.setFov(fov[0], fov[1])
        self.camera.setLens(self.lens)
        self.nearPlane = near
        self.farPlane = far
        self.rebuildMatrixCache()

    def setLens(self, lens):
        """ Setups the ShadowSource to use an external lens """
        self.lens = lens
        self.camera.setLens(self.lens)
        self.nearPlane = lens.getNear()
        self.farPlane = lens.getFar()
        self.nearPlane = 0.5
        self.farPlane = 50.0
        self.rebuildMatrixCache()

    def setupOrtographicLens(self, near=0.1, far=100.0, filmSize=(512, 512)):
        """ Setups a OrtographicLens with a given near plane, far plane
        and film size. The film size is a tuple in the format (filmWidth, filmHeight)
        in world space. """
        self.lens = OrthographicLens()
        self.lens.setNearFar(near, far)
        self.lens.setFilmSize(*filmSize)
        self.camera.setLens(self.lens)
        self.nearPlane = near
        self.farPlane = far
        self.rebuildMatrixCache()

    def rebuildMatrixCache(self):
        """ Internal method to precompute a part of the MVP to improve performance"""
        self.converterYUR = self.lens.getProjectionMat()

    def setPos(self, pos):
        """ Sets the position of the source in world space """
        self.cameraNode.setPos(pos)

    def getPos(self):
        """ Returns the position of the source in world space """
        return self.cameraNode.getPos()

    def setHpr(self, hpr):
        """ Sets the rotation of the source in world space """
        self.cameraNode.setHpr(hpr)

    def lookAt(self, pos):
        """ Looks at a point (in world space) """
        self.cameraNode.lookAt(pos.x, pos.y, pos.z)

    def invalidate(self):
        """ Invalidates this shadow source, means telling the LightManager
        that the shadow map for this light should be rebuilt. Otherwise it
        won't get refreshed. """
        self.valid = False

    def setValid(self):
        """ The LightManager calls this after the shadow map got updated
        successfully """
        self.valid = True

    def isValid(self):
        """ Returns wether the shadow map is still valid or should be refreshed """
        return self.valid

    def __repr__(self):
        """ Returns a representative string of this instance """
        return "ShadowSource[id=" + str(self.index) + "]"

    def __hash__(self):
        return self.index

    def onUpdated(self):
        """ Gets called when shadow source was updated """
class ShadowSource(DebugObject, ShaderStructElement):

    """ This class can be seen as a camera. It stores the necessary data to 
    generate and store the shadow map for the assigned lens (like computing the MVP), 
    and also stores information about the shadowmap, like position in the 
    shadow atlas, or resolution. Each ShadowSource has a unique index, 
    which is used by the lights to identify which sources belong to it.
    """

    # Store a global index for assigning unique ids to the instances
    _GlobalShadowIndex = 999

    @classmethod
    def getExposedAttributes(self):
        return {
            "resolution": "int",
            "atlasPos": "vec2",
            "mvp": "mat4",
            "nearPlane": "float",
            "farPlane": "float"
        }

    @classmethod
    def _generateUID(self):
        """ Generates an uid and returns that """
        self._GlobalShadowIndex += 1
        return self._GlobalShadowIndex

    def __init__(self):
        """ Creates a new ShadowSource. After the creation, a lens can be added
        with setupPerspectiveLens or setupOrtographicLens. """
        self.index = self._generateUID()
        DebugObject.__init__(self, "ShadowSource-" + str(self.index))
        ShaderStructElement.__init__(self)

        self.valid = False
        self.camera = Camera("ShadowSource-" + str(self.index))
        self.camera.setActive(False)
        self.cameraNode = NodePath(self.camera)
        self.cameraNode.reparentTo(Globals.render.find("RPCameraDummys"))
        self.cameraNode.hide()
        self.resolution = 512
        self.atlasPos = Vec2(0)
        self.doesHaveAtlasPos = False
        self.sourceIndex = 0
        self.mvp = UnalignedLMatrix4f()
        self.sourceIndex = -1
        self.nearPlane = 0.0
        self.farPlane = 1000.0
        self.converterYUR = None
        self.transforMat = TransformState.makeMat(
            Mat4.convertMat(Globals.base.win.getGsg().getInternalCoordinateSystem(),
                            CSZupRight))
            
    def cleanup(self):
        """ Cleans up the shadow source """
        self.cameraNode.removeNode()

    def setFilmSize(self, size_x, size_y):
        """ Sets the film size of the source, this is equivalent to setFilmSize
        on a Lens. """
        self.lens.setFilmSize(size_x, size_y)
        self.rebuildMatrixCache()

    def getLens(self):
        """ Returns the source lens """
        return self.lens

    def getSourceIndex(self):
        """ Returns the assigned source index. The source index is the index
        of the ShadowSource in the ShadowSources array of the assigned
        Light. """
        return self.sourceIndex

    def getUID(self):
        """ Returns the uid of the shadow source """
        return self.index

    def setSourceIndex(self, index):
        """ Sets the source index of this source. This is called by the light,
        as only the light knows at which position this source is in the
        Sources array. """
        self.sourceIndex = index

    def computeMVP(self):
        """ Computes the modelViewProjection matrix for the lens. Actually,
        this is the worldViewProjection matrix, but for convenience it is
        called mvp. """

        self.rebuildMatrixCache()
        projMat = self.converterYUR
        # modelViewMat = self.transforMat.invertCompose(
        modelViewMat = Globals.render.getTransform(self.cameraNode).getMat()
        return UnalignedLMatrix4f(modelViewMat * projMat)

    def assignAtlasPos(self, x, y):
        """ Assigns this source a position in the shadow atlas. This is called
        by the shadow atlas. Coordinates are float from 0 .. 1 """
        self.atlasPos = Vec2(x, y)
        self.doesHaveAtlasPos = True

    def update(self):
        """ Updates the shadow source. Currently only recomputes the mvp and
        triggers an array update """
        self.mvp = self.computeMVP()
        self.onPropertyChanged()

    def getAtlasPos(self):
        """ Returns the assigned atlas pos, if present. Coordinates are float
        from 0 .. 1 """
        return self.atlasPos

    def hasAtlasPos(self):
        """ Returns Whether this ShadowSource has already a position in the
        shadow atlas, or is currently unassigned """
        return self.doesHaveAtlasPos

    def removeFromAtlas(self):
        """ Deletes the atlas coordinates, this gets called by the atlas after the
        Source got removed from the atlas """
        self.doesHaveAtlasPos = False
        self.atlasPos = Vec2(0)

    def setResolution(self, resolution):
        """ Sets the resolution in pixels of this shadow source. Has to be
        a multiple of the tileSize specified in LightManager """
        assert(resolution > 1 and resolution <= 8192)
        self.resolution = resolution

    def getResolution(self):
        """ Returns the resolution of the shadow source in pixels """
        return self.resolution

    def setupPerspectiveLens(self, near=0.1, far=100.0, fov=(90, 90)):
        """ Setups a PerspectiveLens with a given near plane, far plane
        and FoV. The FoV is a tuple in the format (Horizontal FoV, Vertical FoV) """
        self.lens = PerspectiveLens()
        self.lens.setNearFar(near, far)
        self.lens.setFov(fov[0], fov[1])
        self.camera.setLens(self.lens)
        self.nearPlane = near
        self.farPlane = far
        self.rebuildMatrixCache()

    def setLens(self, lens):
        """ Setups the ShadowSource to use an external lens """
        self.lens = lens
        self.camera.setLens(self.lens)
        self.nearPlane = lens.getNear()
        self.farPlane = lens.getFar()
        self.nearPlane = 0.5
        self.farPlane = 50.0
        self.rebuildMatrixCache()

    def setupOrtographicLens(self, near=0.1, far=100.0, filmSize=(512, 512)):
        """ Setups a OrtographicLens with a given near plane, far plane
        and film size. The film size is a tuple in the format (filmWidth, filmHeight)
        in world space. """
        self.lens = OrthographicLens()
        self.lens.setNearFar(near, far)
        self.lens.setFilmSize(*filmSize)
        self.camera.setLens(self.lens)
        self.nearPlane = near
        self.farPlane = far
        self.rebuildMatrixCache()

    def rebuildMatrixCache(self):
        """ Internal method to precompute a part of the MVP to improve performance"""
        self.converterYUR = self.lens.getProjectionMat()

    def setPos(self, pos):
        """ Sets the position of the source in world space """
        self.cameraNode.setPos(pos)

    def getPos(self):
        """ Returns the position of the source in world space """
        return self.cameraNode.getPos()

    def setHpr(self, hpr):
        """ Sets the rotation of the source in world space """
        self.cameraNode.setHpr(hpr)

    def lookAt(self, pos):
        """ Looks at a point (in world space) """
        self.cameraNode.lookAt(pos.x, pos.y, pos.z)

    def invalidate(self):
        """ Invalidates this shadow source, means telling the LightManager
        that the shadow map for this light should be rebuilt. Otherwise it
        won't get refreshed. """
        self.valid = False

    def setValid(self):
        """ The LightManager calls this after the shadow map got updated
        successfully """
        self.valid = True

    def isValid(self):
        """ Returns wether the shadow map is still valid or should be refreshed """
        return self.valid

    def __repr__(self):
        """ Returns a representative string of this instance """
        return "ShadowSource[id=" + str(self.index) + "]"

    def __hash__(self):
        return self.index

    def onUpdated(self):
        """ Gets called when shadow source was updated """
示例#3
0
class SpotLight(Light, DebugObject):

    """ This light type simulates a SpotLight. It has a position
    and an orientation. """

    def __init__(self):
        """ Creates a new spot light. """
        Light.__init__(self)
        DebugObject.__init__(self, "SpotLight")
        self.typeName = "SpotLight"

        self.nearPlane = 0.5
        self.radius = 30.0
        self.spotSize = Vec2(30, 30)

        # Used to compute the MVP
        self.ghostCamera = Camera("PointLight")
        self.ghostCamera.setActive(False)
        self.ghostLens = PerspectiveLens()
        self.ghostLens.setFov(130)
        self.ghostCamera.setLens(self.ghostLens)
        self.ghostCameraNode = NodePath(self.ghostCamera)
        self.ghostCameraNode.reparentTo(Globals.render)
        self.ghostCameraNode.hide()

    def getLightType(self):
        """ Internal method to fetch the type of this light, used by Light """
        return LightType.Spot

    def _updateLens(self):
        """ Internal method which gets called when the lens properties changed """
        for source in self.shadowSources:
            source.rebuildMatrixCache()

    def cleanup(self):
        """ Internal method which gets called when the light got deleted """
        self.ghostCameraNode.removeNode()
        Light.cleanup(self)

    def setFov(self, fov):
        """ Sets the field of view of the spotlight """
        assert(fov > 1 and fov < 180)
        self.ghostLens.setFov(fov)
        self._updateLens()

    def setPos(self, pos):
        """ Sets the position of the spotlight """
        self.ghostCameraNode.setPos(pos)
        Light.setPos(self, pos)

    def lookAt(self, pos):
        """ Makes the spotlight look at the given position """
        self.ghostCameraNode.lookAt(pos)

    def _computeAdditionalData(self):
        """ Internal method to recompute the spotlight MVP """
        self.ghostCameraNode.setPos(self.position)

        projMat = self.ghostLens.getProjectionMat()

        modelViewMat = Globals.render.getTransform(self.ghostCameraNode).getMat()
        self.mvp = modelViewMat * projMat

    def _computeLightBounds(self):
        """ Recomputes the bounds of this light. For a SpotLight, we for now
        use a simple BoundingSphere """
        self.bounds = BoundingSphere(Point3(self.position), self.radius * 2.0)

    def setNearFar(self, near, far):
        """ Sets the near and far plane of the spotlight """
        self.nearPlane = near
        self.radius = far
        self.ghostLens.setNearFar(near, far)
        self._updateLens()

    def _updateDebugNode(self):
        """ Internal method to generate new debug geometry. """
        debugNode = NodePath("SpotLightDebugNode")

        # Create the inner image 
        cm = CardMaker("SpotLightDebug")
        cm.setFrameFullscreenQuad()
        innerNode = NodePath(cm.generate())
        innerNode.setTexture(Globals.loader.loadTexture("Data/GUI/Visualization/SpotLight.png"))
        innerNode.setBillboardPointEye()
        innerNode.reparentTo(debugNode)
        innerNode.setPos(self.position)
        innerNode.setColorScale(1,1,0,1)

        # Create the outer lines
        lineNode = debugNode.attachNewNode("lines")

        currentNodeTransform = render.getTransform(self.ghostCameraNode).getMat()
        currentCamTransform = self.ghostLens.getProjectionMat()
        currentRelativeCamPos = self.ghostCameraNode.getPos(render)
        currentCamBounds = self.ghostLens.makeBounds()
        currentCamBounds.xform(self.ghostCameraNode.getMat(render))

        p = lambda index: currentCamBounds.getPoint(index)

        # Make a circle at the bottom
        frustumBottomCenter = (p(0) + p(1) + p(2) + p(3)) * 0.25
        upVector = (p(0) + p(1)) / 2 - frustumBottomCenter
        rightVector = (p(1) + p(2)) / 2 - frustumBottomCenter
        points = []
        for idx in xrange(64):
            rad = idx / 64.0 * math.pi * 2.0
            pos = upVector * math.sin(rad) + rightVector * math.cos(rad)
            pos += frustumBottomCenter
            points.append(pos)
        frustumLine = self._createDebugLine(points, True)
        frustumLine.setColorScale(1,1,0,1)
        frustumLine.reparentTo(lineNode)


        # Create frustum lines which connect the origin to the bottom circle
        pointArrays = [
            [self.position, frustumBottomCenter + upVector],
            [self.position, frustumBottomCenter - upVector],
            [self.position, frustumBottomCenter + rightVector],
            [self.position, frustumBottomCenter - rightVector],
        ]

        for pointArray in pointArrays:
            frustumLine = self._createDebugLine(pointArray, False)
            frustumLine.setColorScale(1,1,0,1)
            frustumLine.reparentTo(lineNode)

        # Create line which is in the direction of the spot light
        startPoint = (p(0) + p(1) + p(2) + p(3)) * 0.25
        endPoint = (p(4) + p(5) + p(6) + p(7)) * 0.25
        line = self._createDebugLine([startPoint, endPoint], False)
        line.setColorScale(1,1,1,1)
        line.reparentTo(lineNode)

        # Remove the old debug node
        self.debugNode.node().removeAllChildren()

        # Attach the new debug node
        debugNode.reparentTo(self.debugNode)
        # self.debugNode.flattenStrong()

    def _initShadowSources(self):
        """ Internal method to init the shadow sources """
        source = ShadowSource()
        source.setResolution(self.shadowResolution)
        source.setLens(self.ghostLens)
        self._addShadowSource(source)

    def _updateShadowSources(self):
        """ Recomputes the position of the shadow source """
        self.shadowSources[0].setPos(self.position)
        self.shadowSources[0].setHpr(self.ghostCameraNode.getHpr())

    def __repr__(self):
        """ Generates a string representation of this instance """
        return "SpotLight[]"