Exemple #1
0
class LightManager(DebugObject):

    """ This class is internally used by the RenderingPipeline to handle
    Lights and their Shadows. It stores a list of lights, and updates the
    required ShadowSources per frame. There are two main update methods:

    updateLights processes each light and does a basic frustum check.
    If the light is in the frustum, its ID is passed to the light precompute
    container (set with setLightingCuller). Also, each shadowSource of
    the light is checked, and if it reports to be invalid, it's queued to
    the list of queued shadow updates.

    updateShadows processes the queued shadow updates and setups everything
    to render the shadow depth textures to the shadow atlas.

    Lights can be added with addLight. Notice you cannot change the shadow
    resolution or wether the light casts shadows after you called addLight.
    This is because it might already have a position in the atlas, and so
    the atlas would have to delete it's map, which is not supported (yet).
    This shouldn't be an issue, as you usually always know before if a
    light will cast shadows or not.

    """

    def __init__(self, pipeline):
        """ Creates a new LightManager. It expects a RenderPipeline as parameter. """
        DebugObject.__init__(self, "LightManager")

        self._initArrays()

        self.pipeline = pipeline
        self.settings = pipeline.getSettings()

        # Create arrays to store lights & shadow sources
        self.lights = []
        self.shadowSources = []
        self.queuedShadowUpdates = []
        self.allLightsArray = ShaderStructArray(Light, self.maxTotalLights)
        self.updateCallbacks = []

        self.cullBounds = None
        self.shadowScene = Globals.render

        # Create atlas
        self.shadowAtlas = ShadowAtlas()
        self.shadowAtlas.setSize(self.settings.shadowAtlasSize)
        self.shadowAtlas.create()

        self.maxShadowMaps = 24
        self.maxShadowUpdatesPerFrame = self.settings.maxShadowUpdatesPerFrame
        self.numShadowUpdatesPTA = PTAInt.emptyArray(1)

        self.updateShadowsArray = ShaderStructArray(
            ShadowSource, self.maxShadowUpdatesPerFrame)
        self.allShadowsArray = ShaderStructArray(
            ShadowSource, self.maxShadowMaps)


        # Create shadow compute buffer
        self._createShadowComputationBuffer()

        # Create the initial shadow state
        self.shadowComputeCamera.setTagStateKey("ShadowPassShader")
        self._createTagStates()
        self.shadowScene.setTag("ShadowPassShader", "Default")

        # Create debug overlay
        self._createDebugTexts()

        # Disable buffer on start
        self.shadowComputeTarget.setActive(False)

        # Bind arrays
        self.updateShadowsArray.bindTo(self.shadowScene, "updateSources")
        self.updateShadowsArray.bindTo(
            self.shadowComputeTarget, "updateSources")

        # Set initial inputs
        for target in [self.shadowComputeTarget, self.shadowScene]:
            target.setShaderInput("numUpdates", self.numShadowUpdatesPTA)

        self.lightingComputator = None
        self.lightCuller = None
        self.skip = 0
        self.skipRate = 0


    def _createTagStates(self):
        # Create shadow caster shader
        self.shadowCasterShader = BetterShader.load(
            "Shader/DefaultShadowCaster/vertex.glsl",
            "Shader/DefaultShadowCaster/fragment.glsl",
            "Shader/DefaultShadowCaster/geometry.glsl")

        initialState = NodePath("ShadowCasterState")
        initialState.setShader(self.shadowCasterShader, 30)
        # initialState.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullNone))
        initialState.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff))
        self.shadowComputeCamera.setTagState(
            "Default", initialState.getState())

    def _createShadowComputationBuffer(self):
        """ This creates the internal shadow buffer which also is the
        shadow atlas. Shadow maps are rendered to this using Viewports
        (thank you rdb for adding this!). It also setups the base camera
        which renders the shadow objects, although a custom mvp is passed
        to the shaders, so the camera is mainly a dummy """

        # Create camera showing the whole scene
        self.shadowComputeCamera = Camera("ShadowComputeCamera")
        self.shadowComputeCameraNode = self.shadowScene.attachNewNode(
            self.shadowComputeCamera)
        self.shadowComputeCamera.getLens().setFov(30, 30)
        self.shadowComputeCamera.getLens().setNearFar(1.0, 2.0)

        # Disable culling
        self.shadowComputeCamera.setBounds(OmniBoundingVolume())
        self.shadowComputeCamera.setCullBounds(OmniBoundingVolume())
        self.shadowComputeCamera.setFinal(True)
        self.shadowComputeCameraNode.setPos(0, 0, 1500)
        self.shadowComputeCameraNode.lookAt(0, 0, 0)

        self.shadowComputeTarget = RenderTarget("ShadowAtlas")
        self.shadowComputeTarget.setSize(self.shadowAtlas.getSize())
        self.shadowComputeTarget.addDepthTexture()
        self.shadowComputeTarget.setDepthBits(32)
        
        self.shadowComputeTarget.setSource(
            self.shadowComputeCameraNode, Globals.base.win)

        self.shadowComputeTarget.prepareSceneRender()

        # This took me a long time to figure out. If not removing the quad
        # children, the color and aux buffers will be overridden each frame.
        # Quite annoying!
        self.shadowComputeTarget.getQuad().node().removeAllChildren()
        self.shadowComputeTarget.getInternalRegion().setSort(-200)

        self.shadowComputeTarget.getInternalRegion().setNumRegions(
            self.maxShadowUpdatesPerFrame + 1)
        self.shadowComputeTarget.getInternalRegion().setDimensions(0,
             (0, 0, 0, 0))

        self.shadowComputeTarget.getInternalRegion().disableClears()
        self.shadowComputeTarget.getInternalBuffer().disableClears()
        self.shadowComputeTarget.getInternalBuffer().setSort(-300)

        # We can't clear the depth per viewport.
        # But we need to clear it in any way, as we still want
        # z-testing in the buffers. So well, we create a
        # display region *below* (smaller sort value) each viewport
        # which has a depth-clear assigned. This is hacky, I know.
        self.depthClearer = []

        for i in range(self.maxShadowUpdatesPerFrame):
            buff = self.shadowComputeTarget.getInternalBuffer()
            dr = buff.makeDisplayRegion()
            dr.setSort(-250)
            for k in xrange(16):
                dr.setClearActive(k, True)
                dr.setClearValue(k, Vec4(0.5,0.5,0.5,1))

            dr.setClearDepthActive(True)
            dr.setClearDepth(1.0)
            dr.setDimensions(0,0,0,0)
            dr.setActive(False)
            self.depthClearer.append(dr)

        # When using hardware pcf, set the correct filter types
        
        if self.settings.useHardwarePCF:
            self.pcfSampleState = SamplerState()
            self.pcfSampleState.setMinfilter(SamplerState.FTShadow)
            self.pcfSampleState.setMagfilter(SamplerState.FTShadow)
            self.pcfSampleState.setWrapU(SamplerState.WMClamp)
            self.pcfSampleState.setWrapV(SamplerState.WMClamp)


        dTex = self.getAtlasTex()
        dTex.setWrapU(Texture.WMClamp)
        dTex.setWrapV(Texture.WMClamp)



    def getAllLights(self):
        """ Returns all attached lights """
        return self.lights

    def getPCFSampleState(self):
        """ Returns the pcf sample state used to sample the shadow map """
        return self.pcfSampleState

    def processCallbacks(self):
        """ Processes all updates from the previous frame """
        for update in self.updateCallbacks:
            update.onUpdated()
        self.updateCallbacks = []

    def _createDebugTexts(self):
        """ Creates a debug overlay if specified in the pipeline settings """
        self.lightsVisibleDebugText = None
        self.lightsUpdatedDebugText = None

        if self.settings.displayDebugStats:

            try:
                from Code.GUI.FastText import FastText
                self.lightsVisibleDebugText = FastText(pos=Vec2(
                    Globals.base.getAspectRatio() - 0.1, 0.84), rightAligned=True, color=Vec3(1, 1, 0), size=0.036)
                self.lightsUpdatedDebugText = FastText(pos=Vec2(
                    Globals.base.getAspectRatio() - 0.1, 0.8), rightAligned=True, color=Vec3(1, 1, 0), size=0.036)

            except Exception, msg:
                self.debug(
                    "Overlay is disabled because FastText wasn't loaded")
class LightManager(DebugObject):

    """ This class is internally used by the RenderingPipeline to handle
    Lights and their Shadows. It stores a list of lights, and updates the
    required ShadowSources per frame. There are two main update methods:

    updateLights processes each light and does a basic frustum check.
    If the light is in the frustum, its ID is passed to the light precompute
    container (set with setLightingCuller). Also, each shadowSource of
    the light is checked, and if it reports to be invalid, it's queued to
    the list of queued shadow updates.

    updateShadows processes the queued shadow updates and setups everything
    to render the shadow depth textures to the shadow atlas.

    Lights can be added with addLight. Notice you cannot change the shadow
    resolution or even wether the light casts shadows after you called addLight.
    This is because it might already have a position in the atlas, and so
    the atlas would have to delete it's map, which is not supported (yet).
    This shouldn't be an issue, as you usually always know before if a
    light will cast shadows or not.

    """

    def __init__(self, pipeline):
        """ Creates a new LightManager. It expects a RenderPipeline as parameter. """
        DebugObject.__init__(self, "LightManager")

        self._initArrays()

        self.pipeline = pipeline
        self.settings = pipeline.getSettings()

        # Create arrays to store lights & shadow sources
        self.lights = []
        self.shadowSources = []
        self.queuedShadowUpdates = []
        self.allLightsArray = ShaderStructArray(Light, self.maxTotalLights)
        self.updateCallbacks = []

        self.cullBounds = None
        self.shadowScene = Globals.render

        # Create atlas
        self.shadowAtlas = ShadowAtlas()
        self.shadowAtlas.setSize(self.settings.shadowAtlasSize)
        self.shadowAtlas.create()

        self.maxShadowMaps = 24
        self.maxShadowUpdatesPerFrame = self.settings.maxShadowUpdatesPerFrame
        self.numShadowUpdatesPTA = PTAInt.emptyArray(1)

        self.updateShadowsArray = ShaderStructArray(
            ShadowSource, self.maxShadowUpdatesPerFrame)
        self.allShadowsArray = ShaderStructArray(
            ShadowSource, self.maxShadowMaps)

        # Create shadow compute buffer
        self._createShadowComputationBuffer()

        # Create the initial shadow state
        self.shadowComputeCamera.setTagStateKey("ShadowPassShader")
        # self.shadowComputeCamera.setInitialState(RenderState.make(
            # ColorWriteAttrib.make(ColorWriteAttrib.C_off),
            # ColorWriteAttrib.make(ColorWriteAttrib.C_rgb),
            # DepthWriteAttrib.make(DepthWriteAttrib.M_on),
            # CullFaceAttrib.make(CullFaceAttrib.MCullNone),
            # 100))

        self._createTagStates()

        self.shadowScene.setTag("ShadowPassShader", "Default")

        # Create debug overlay
        self._createDebugTexts()

        # Disable buffer on start
        self.shadowComputeTarget.setActive(False)

        # Bind arrays
        self.updateShadowsArray.bindTo(self.shadowScene, "updateSources")
        self.updateShadowsArray.bindTo(
            self.shadowComputeTarget, "updateSources")

        # Set initial inputs
        for target in [self.shadowComputeTarget, self.shadowScene]:
            target.setShaderInput("numUpdates", self.numShadowUpdatesPTA)

        self.lightingComputator = None
        self.lightCuller = None
        self.skip = 0
        self.skipRate = 0

    def _createTagStates(self):
        # Create shadow caster shader
        self.shadowCasterShader = BetterShader.load(
            "Shader/DefaultShadowCaster/vertex.glsl",
            "Shader/DefaultShadowCaster/fragment.glsl",
            "Shader/DefaultShadowCaster/geometry.glsl")

        initialState = NodePath("ShadowCasterState")
        initialState.setShader(self.shadowCasterShader, 30)
        self.shadowComputeCamera.setTagState(
            "Default", initialState.getState())

    def _createShadowComputationBuffer(self):
        """ This creates the internal shadow buffer which also is the
        shadow atlas. Shadow maps are rendered to this using Viewports
        (thank you rdb for adding this!). It also setups the base camera
        which renders the shadow objects, although a custom mvp is passed
        to the shaders, so the camera is mainly a dummy """

        # Create camera showing the whole scene
        self.shadowComputeCamera = Camera("ShadowComputeCamera")
        self.shadowComputeCameraNode = self.shadowScene.attachNewNode(
            self.shadowComputeCamera)
        self.shadowComputeCamera.getLens().setFov(90, 90)
        self.shadowComputeCamera.getLens().setNearFar(10.0, 100000.0)

        # Disable culling
        self.shadowComputeCamera.setBounds(OmniBoundingVolume())
        self.shadowComputeCameraNode.setPos(0, 0, 150)
        self.shadowComputeCameraNode.lookAt(0, 0, 0)

        self.shadowComputeTarget = RenderTarget("ShadowAtlas")
        self.shadowComputeTarget.setSize(self.shadowAtlas.getSize())
        self.shadowComputeTarget.addDepthTexture()
        self.shadowComputeTarget.setDepthBits(32)
        
        if self.settings.enableGlobalIllumination:
            self.shadowComputeTarget.addColorTexture()
            self.shadowComputeTarget.setColorBits(16)
        
        self.shadowComputeTarget.setSource(
            self.shadowComputeCameraNode, Globals.base.win)

        self.shadowComputeTarget.prepareSceneRender()

        # This took me a long time to figure out. If not removing the quad
        # children, the color and aux buffers will be overridden each frame.
        # Quite annoying!
        self.shadowComputeTarget.getQuad().node().removeAllChildren()
        self.shadowComputeTarget.getInternalRegion().setSort(-200)

        self.shadowComputeTarget.getInternalRegion().setNumRegions(
            self.maxShadowUpdatesPerFrame + 1)
        self.shadowComputeTarget.getInternalRegion().setDimensions(0,
             (0, 0, 0, 0))

        self.shadowComputeTarget.getInternalBuffer().setSort(-300)

        # We can't clear the depth per viewport.
        # But we need to clear it in any way, as we still want
        # z-testing in the buffers. So well, we create a
        # display region *below* (smaller sort value) each viewport
        # which has a depth-clear assigned. This is hacky, I know.
        self.depthClearer = []

        for i in range(self.maxShadowUpdatesPerFrame):
            buff = self.shadowComputeTarget.getInternalBuffer()
            dr = buff.makeDisplayRegion()
            dr.setSort(-250)
            for k in xrange(16):
                dr.setClearActive(k, True)
                dr.setClearValue(k, Vec4(0.5,0.5,0.5,1))

            dr.setClearDepthActive(True)
            dr.setClearDepth(1.0)
            dr.setDimensions(0,0,0,0)
            dr.setActive(False)
            self.depthClearer.append(dr)

        # When using hardware pcf, set the correct filter types
        dTex = self.shadowComputeTarget.getDepthTexture()

        if self.settings.useHardwarePCF:
            dTex.setMinfilter(Texture.FTShadow)
            dTex.setMagfilter(Texture.FTShadow)

        dTex.setWrapU(Texture.WMClamp)
        dTex.setWrapV(Texture.WMClamp)

    def getAllLights(self):
        """ Returns all attached lights """
        return self.lights

    def _createDebugTexts(self):
        """ Creates a debug overlay if specified in the pipeline settings """
        self.lightsVisibleDebugText = None
        self.lightsUpdatedDebugText = None

        if self.settings.displayDebugStats:

            try:
                from Code.GUI.FastText import FastText
                self.lightsVisibleDebugText = FastText(pos=Vec2(
                    Globals.base.getAspectRatio() - 0.1, 0.84), rightAligned=True, color=Vec3(1, 1, 0), size=0.036)
                self.lightsUpdatedDebugText = FastText(pos=Vec2(
                    Globals.base.getAspectRatio() - 0.1, 0.8), rightAligned=True, color=Vec3(1, 1, 0), size=0.036)

            except Exception, msg:
                self.debug(
                    "Overlay is disabled because FastText wasn't loaded")
Exemple #3
0
class GlobalIllumination(DebugObject):
    """ This class handles the global illumination processing. It is still
    experimental, and thus not commented. """

    updateEnabled = False

    def __init__(self, pipeline):
        DebugObject.__init__(self, "GlobalIllumnination")
        self.pipeline = pipeline

        self.targetCamera = Globals.base.cam
        self.targetSpace = Globals.base.render

        self.voxelBaseResolution = 512 * 4
        self.voxelGridSizeWS = Vec3(50, 50, 20)
        self.voxelGridResolution = LVecBase3i(512, 512, 128)
        self.targetLight = None
        self.helperLight = None
        self.ptaGridPos = PTALVecBase3f.emptyArray(1)
        self.gridPos = Vec3(0)

    @classmethod
    def setUpdateEnabled(self, enabled):
        self.updateEnabled = enabled

    def setTargetLight(self, light):
        """ Sets the sun light which is the main source of GI """

        if light._getLightType() != LightType.Directional:
            self.error("setTargetLight expects a directional light!")
            return

        self.targetLight = light
        self._createHelperLight()

    def _prepareVoxelScene(self):
        """ Creates the internal buffer to voxelize the scene on the fly """
        self.voxelizeScene = Globals.render
        self.voxelizeCamera = Camera("VoxelizeScene")
        self.voxelizeCameraNode = self.voxelizeScene.attachNewNode(
            self.voxelizeCamera)
        self.voxelizeLens = OrthographicLens()
        self.voxelizeLens.setFilmSize(self.voxelGridSizeWS.x * 2,
                                      self.voxelGridSizeWS.y * 2)
        self.voxelizeLens.setNearFar(0.0, self.voxelGridSizeWS.x * 2)
        self.voxelizeCamera.setLens(self.voxelizeLens)
        self.voxelizeCamera.setTagStateKey("VoxelizePassShader")

        self.targetSpace.setTag("VoxelizePassShader", "Default")

        self.voxelizeCameraNode.setPos(0, 0, 0)
        self.voxelizeCameraNode.lookAt(0, 0, 0)

        self.voxelizeTarget = RenderTarget("DynamicVoxelization")
        self.voxelizeTarget.setSize(self.voxelBaseResolution)
        # self.voxelizeTarget.addDepthTexture()
        # self.voxelizeTarget.addColorTexture()
        # self.voxelizeTarget.setColorBits(16)
        self.voxelizeTarget.setSource(self.voxelizeCameraNode,
                                      Globals.base.win)
        self.voxelizeTarget.prepareSceneRender()

        self.voxelizeTarget.getQuad().node().removeAllChildren()
        self.voxelizeTarget.getInternalRegion().setSort(-400)
        self.voxelizeTarget.getInternalBuffer().setSort(-399)

        # for tex in [self.voxelizeTarget.getColorTexture()]:
        #     tex.setWrapU(Texture.WMClamp)
        #     tex.setWrapV(Texture.WMClamp)
        #     tex.setMinfilter(Texture.FTNearest)
        #     tex.setMagfilter(Texture.FTNearest)

        voxelSize = Vec3(
            self.voxelGridSizeWS.x * 2.0 / self.voxelGridResolution.x,
            self.voxelGridSizeWS.y * 2.0 / self.voxelGridResolution.y,
            self.voxelGridSizeWS.z * 2.0 / self.voxelGridResolution.z)

        self.targetSpace.setShaderInput("dv_gridSize",
                                        self.voxelGridSizeWS * 2)
        self.targetSpace.setShaderInput("dv_voxelSize", voxelSize)
        self.targetSpace.setShaderInput("dv_gridResolution",
                                        self.voxelGridResolution)

    def _createVoxelizeState(self):
        """ Creates the tag state and loades the voxelizer shader """
        self.voxelizeShader = BetterShader.load("Shader/GI/Voxelize.vertex",
                                                "Shader/GI/Voxelize.fragment"
                                                # "Shader/GI/Voxelize.geometry"
                                                )

        initialState = NodePath("VoxelizerState")
        initialState.setShader(self.voxelizeShader, 50)
        initialState.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullNone))
        initialState.setDepthWrite(False)
        initialState.setDepthTest(False)
        initialState.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MNone))

        initialState.setShaderInput("dv_dest_tex", self.voxelGenTex)

        self.voxelizeCamera.setTagState("Default", initialState.getState())

    def _createHelperLight(self):
        """ Creates the helper light. We can't use the main directional light
        because it uses PSSM, so we need an extra shadow map """
        self.helperLight = GIHelperLight()
        self.helperLight.setPos(Vec3(50, 50, 100))
        self.helperLight.setShadowMapResolution(512)
        self.helperLight.setFilmSize(
            math.sqrt((self.voxelGridSizeWS.x**2) * 2) * 2)
        self.helperLight.setCastsShadows(True)
        self.pipeline.addLight(self.helperLight)

        self.targetSpace.setShaderInput(
            "dv_uv_size",
            float(self.helperLight.shadowResolution) /
            self.pipeline.settings.shadowAtlasSize)
        self.targetSpace.setShaderInput(
            "dv_atlas",
            self.pipeline.getLightManager().getAtlasTex())

        self._updateGridPos()

    def setup(self):
        """ Setups everything for the GI to work """

        # if self.pipeline.settings.useHardwarePCF:
        #     self.fatal(
        #         "Global Illumination does not work in combination with PCF!")
        #     return

        self._prepareVoxelScene()

        # Create 3D Texture to store the voxel generation grid
        self.voxelGenTex = Texture("VoxelsTemp")
        self.voxelGenTex.setup3dTexture(self.voxelGridResolution.x,
                                        self.voxelGridResolution.y,
                                        self.voxelGridResolution.z,
                                        Texture.TInt, Texture.FR32i)
        self.voxelGenTex.setMinfilter(Texture.FTLinearMipmapLinear)
        self.voxelGenTex.setMagfilter(Texture.FTLinear)

        # Create 3D Texture which is a copy of the voxel generation grid but
        # stable, as the generation grid is updated part by part
        self.voxelStableTex = Texture("VoxelsStable")
        self.voxelStableTex.setup3dTexture(self.voxelGridResolution.x,
                                           self.voxelGridResolution.y,
                                           self.voxelGridResolution.z,
                                           Texture.TFloat, Texture.FRgba8)
        self.voxelStableTex.setMinfilter(Texture.FTLinearMipmapLinear)
        self.voxelStableTex.setMagfilter(Texture.FTLinear)

        for prepare in [self.voxelGenTex, self.voxelStableTex]:
            prepare.setMagfilter(Texture.FTLinear)
            prepare.setMinfilter(Texture.FTLinearMipmapLinear)
            prepare.setWrapU(Texture.WMBorderColor)
            prepare.setWrapV(Texture.WMBorderColor)
            prepare.setWrapW(Texture.WMBorderColor)
            prepare.setBorderColor(Vec4(0, 0, 0, 0))

        self.voxelGenTex.setMinfilter(Texture.FTNearest)
        self.voxelGenTex.setMagfilter(Texture.FTNearest)
        self.voxelGenTex.setWrapU(Texture.WMClamp)
        self.voxelGenTex.setWrapV(Texture.WMClamp)
        self.voxelGenTex.setWrapW(Texture.WMClamp)

        # self.voxelStableTex.generateRamMipmapImages()

        self._createVoxelizeState()

        self.clearTextureNode = NodePath("ClearTexture")
        self.copyTextureNode = NodePath("CopyTexture")
        self.generateMipmapsNode = NodePath("GenerateMipmaps")
        self.convertGridNode = NodePath("ConvertGrid")

        self.reloadShader()

    def _generateMipmaps(self, tex):
        """ Generates all mipmaps for a 3D texture, using a gaussian function """

        pstats_GenerateMipmaps.start()
        currentMipmap = 0
        computeSize = LVecBase3i(self.voxelGridResolution)
        self.generateMipmapsNode.setShaderInput("source", tex)
        self.generateMipmapsNode.setShaderInput("pixelSize",
                                                1.0 / computeSize.x)

        while computeSize.z > 1:
            computeSize /= 2
            self.generateMipmapsNode.setShaderInput("sourceMipmap",
                                                    LVecBase3i(currentMipmap))
            self.generateMipmapsNode.setShaderInput("currentMipmapSize",
                                                    LVecBase3i(computeSize))
            self.generateMipmapsNode.setShaderInput("dest", tex, False, True,
                                                    -1, currentMipmap + 1)
            self._executeShader(self.generateMipmapsNode,
                                (computeSize.x + 7) / 8,
                                (computeSize.y + 7) / 8,
                                (computeSize.z + 7) / 8)
            currentMipmap += 1

        pstats_GenerateMipmaps.stop()

    def _createCleanShader(self):
        shader = BetterShader.loadCompute("Shader/GI/ClearTexture.compute")
        self.clearTextureNode.setShader(shader)

    def _createConvertShader(self):
        shader = BetterShader.loadCompute("Shader/GI/ConvertGrid.compute")
        self.convertGridNode.setShader(shader)

    def _createGenerateMipmapsShader(self):
        shader = BetterShader.loadCompute("Shader/GI/GenerateMipmaps.compute")
        self.generateMipmapsNode.setShader(shader)

    def reloadShader(self):
        self._createCleanShader()
        self._createGenerateMipmapsShader()
        self._createConvertShader()
        self._createVoxelizeState()
        self.frameIndex = 0

    def _clear3DTexture(self, tex, clearVal=None):
        """ Clears a 3D Texture to <clearVal> """
        if clearVal is None:
            clearVal = Vec4(0)

        self.clearTextureNode.setShaderInput("target", tex, False, True, -1, 0)
        self.clearTextureNode.setShaderInput("clearValue", clearVal)

        self._executeShader(self.clearTextureNode, (tex.getXSize() + 7) / 8,
                            (tex.getYSize() + 7) / 8, (tex.getZSize() + 7) / 8)

    def _updateGridPos(self):

        snap = 32.0
        stepSizeX = float(self.voxelGridSizeWS.x * 2.0) / float(
            self.voxelGridResolution.x) * snap
        stepSizeY = float(self.voxelGridSizeWS.y * 2.0) / float(
            self.voxelGridResolution.y) * snap
        stepSizeZ = float(self.voxelGridSizeWS.z * 2.0) / float(
            self.voxelGridResolution.z) * snap

        self.gridPos = self.targetCamera.getPos(self.targetSpace)
        self.gridPos.x -= self.gridPos.x % stepSizeX
        self.gridPos.y -= self.gridPos.y % stepSizeY
        self.gridPos.z -= self.gridPos.z % stepSizeZ

    def process(self):
        if self.targetLight is None:
            self.fatal("The GI cannot work without a target light! Set one "
                       "with setTargetLight() first!")

        if not self.updateEnabled:
            self.voxelizeTarget.setActive(False)
            return

        direction = self.targetLight.getDirection()

        # time.sleep(0.4)

        if self.frameIndex == 0:
            # Find out cam pos

            self.targetSpace.setShaderInput(
                "dv_uv_start", self.helperLight.shadowSources[0].getAtlasPos())

            self.voxelizeTarget.setActive(True)
            # self.voxelizeTarget.setActive(False)

            self.voxelizeLens.setFilmSize(self.voxelGridSizeWS.y * 2,
                                          self.voxelGridSizeWS.z * 2)
            self.voxelizeLens.setNearFar(0.0, self.voxelGridSizeWS.x * 2)

            self.targetSpace.setShaderInput(
                "dv_mvp", Mat4(self.helperLight.shadowSources[0].mvp))
            self.targetSpace.setShaderInput(
                "dv_gridStart", self.gridPos - self.voxelGridSizeWS)
            self.targetSpace.setShaderInput(
                "dv_gridEnd", self.gridPos + self.voxelGridSizeWS)
            self.targetSpace.setShaderInput("dv_lightdir", direction)

            # Clear textures
            self._clear3DTexture(self.voxelGenTex, Vec4(0, 0, 0, 0))

            # Voxelize from x axis
            self.voxelizeCameraNode.setPos(self.gridPos -
                                           Vec3(self.voxelGridSizeWS.x, 0, 0))
            self.voxelizeCameraNode.lookAt(self.gridPos)
            self.targetSpace.setShaderInput("dv_direction", LVecBase3i(0))

        elif self.frameIndex == 1:
            # Voxelize from y axis

            # self.voxelizeTarget.setActive(False)

            self.voxelizeLens.setFilmSize(self.voxelGridSizeWS.x * 2,
                                          self.voxelGridSizeWS.z * 2)
            self.voxelizeLens.setNearFar(0.0, self.voxelGridSizeWS.y * 2)

            self.voxelizeCameraNode.setPos(self.gridPos -
                                           Vec3(0, self.voxelGridSizeWS.y, 0))
            self.voxelizeCameraNode.lookAt(self.gridPos)
            self.targetSpace.setShaderInput("dv_direction", LVecBase3i(1))

        elif self.frameIndex == 2:

            # self.voxelizeTarget.setActive(False)
            # Voxelize from z axis
            self.voxelizeLens.setFilmSize(self.voxelGridSizeWS.x * 2,
                                          self.voxelGridSizeWS.y * 2)
            self.voxelizeLens.setNearFar(0.0, self.voxelGridSizeWS.z * 2)

            self.voxelizeCameraNode.setPos(self.gridPos +
                                           Vec3(0, 0, self.voxelGridSizeWS.z))
            self.voxelizeCameraNode.lookAt(self.gridPos)
            self.targetSpace.setShaderInput("dv_direction", LVecBase3i(2))

        elif self.frameIndex == 3:

            self.voxelizeTarget.setActive(False)

            # Copy the cache to the actual texture
            self.convertGridNode.setShaderInput("src", self.voxelGenTex)
            self.convertGridNode.setShaderInput("dest", self.voxelStableTex)
            self._executeShader(self.convertGridNode,
                                (self.voxelGridResolution.x + 7) / 8,
                                (self.voxelGridResolution.y + 7) / 8,
                                (self.voxelGridResolution.z + 7) / 8)

            # Generate the mipmaps
            self._generateMipmaps(self.voxelStableTex)

            self.helperLight.setPos(self.gridPos)
            self.helperLight.setDirection(direction)

            # We are done now, update the inputs
            self.ptaGridPos[0] = Vec3(self.gridPos)
            self._updateGridPos()

        self.frameIndex += 1
        self.frameIndex = self.frameIndex % 5

    def bindTo(self, node, prefix):
        """ Binds all required shader inputs to a target to compute / display
        the global illumination """

        normFactor = Vec3(
            1.0,
            float(self.voxelGridResolution.y) /
            float(self.voxelGridResolution.x) * self.voxelGridSizeWS.y /
            self.voxelGridSizeWS.x,
            float(self.voxelGridResolution.z) /
            float(self.voxelGridResolution.x) * self.voxelGridSizeWS.z /
            self.voxelGridSizeWS.x)
        node.setShaderInput(prefix + ".gridPos", self.ptaGridPos)
        node.setShaderInput(prefix + ".gridHalfSize", self.voxelGridSizeWS)
        node.setShaderInput(prefix + ".gridResolution",
                            self.voxelGridResolution)
        node.setShaderInput(prefix + ".voxels", self.voxelStableTex)
        node.setShaderInput(prefix + ".voxelNormFactor", normFactor)
        node.setShaderInput(prefix + ".geometry", self.voxelStableTex)

    def _executeShader(self, node, threadsX, threadsY, threadsZ=1):
        """ Executes a compute shader, fetching the shader attribute from a NodePath """
        sattr = node.getAttrib(ShaderAttrib)
        Globals.base.graphicsEngine.dispatchCompute(
            (threadsX, threadsY, threadsZ), sattr, Globals.base.win.get_gsg())