Ejemplo n.º 1
0
 def _makeLightBoundsComputationBuffer(self, w, h):
     """ Creates the buffer which precomputes the lights per tile """
     self.debug("Creating light precomputation buffer of size", w, "x", h)
     self.lightBoundsComputeBuff = RenderTarget("ComputeLightTileBounds")
     self.lightBoundsComputeBuff.setSize(w, h)
     self.lightBoundsComputeBuff.setColorWrite(False)
     self.lightBoundsComputeBuff.prepareOffscreenBuffer()
Ejemplo n.º 2
0
    def _makeLightingComputeBuffer(self):
        """ Creates the buffer which applies the lighting """
        self.lightingComputeContainer = RenderTarget("ComputeLighting")

        if self.settings.enableTemporalReprojection:
            self.lightingComputeContainer.setSize(self.size.x / 2, self.size.y)
        else:
            self.lightingComputeContainer.setSize(self.size.x, self.size.y)

        self.lightingComputeContainer.addColorTexture()
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(self.size.x,
                                                       self.size.y,
                                                       Texture.TFloat,
                                                       Texture.FRgba8)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(self.size.x, self.size.y,
                                               Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)
    def _createBlurBuffer(self):
        """ Creates the buffers for the dof """
        self.blurColorV = RenderTarget("blurColorVertical")
        self.blurColorV.addColorTexture()
        self.blurColorV.prepareOffscreenBuffer()

        self.blurColorH = RenderTarget("blurColorHorizontal")
        self.blurColorH.addColorTexture()
        self.blurColorH.prepareOffscreenBuffer()
Ejemplo n.º 4
0
    def _createBlurBuffer(self):
        """ Creates the buffers for the dof """
        self.blurColorV = RenderTarget("blurColorVertical")
        self.blurColorV.addColorTexture()
        self.blurColorV.prepareOffscreenBuffer()

        self.blurColorH = RenderTarget("blurColorHorizontal")
        self.blurColorH.addColorTexture()
        self.blurColorH.prepareOffscreenBuffer()
Ejemplo n.º 5
0
 def _createNormalPrecomputeBuffer(self):
     """ Creates a buffer which reconstructs the normals and position
     from view-space """
     self.normalPrecompute = RenderTarget("PrecomputeNormals")
     self.normalPrecompute.addColorTexture()
     self.normalPrecompute.addAuxTextures(1)
     self.normalPrecompute.setColorBits(16)
     self.normalPrecompute.setAuxBits(16)
     self.normalPrecompute.prepareOffscreenBuffer()
    def _createOcclusionBlurBuffer(self):
        """ Creates the buffers needed to blur the occlusion """
        self.blurOcclusionV = RenderTarget("blurOcclusionVertical")
        self.blurOcclusionV.addColorTexture()
        self.blurOcclusionV.prepareOffscreenBuffer()

        self.blurOcclusionH = RenderTarget("blurOcclusionHorizontal")
        self.blurOcclusionH.addColorTexture()
        self.blurOcclusionH.prepareOffscreenBuffer()
Ejemplo n.º 7
0
 def _createCombiner(self):
     """ Creates the target which combines the result from the
     lighting computation and last frame together
     (Temporal Reprojection) """
     self.combiner = RenderTarget("Combine-Temporal")
     self.combiner.addColorTexture()
     self.combiner.setColorBits(16)
     self.combiner.prepareOffscreenBuffer()
     self._setCombinerShader()
Ejemplo n.º 8
0
    def _createOcclusionBlurBuffer(self):
        """ Creates the buffers needed to blur the occlusion """
        self.blurOcclusionV = RenderTarget("blurOcclusionVertical")
        self.blurOcclusionV.addColorTexture()
        self.blurOcclusionV.prepareOffscreenBuffer()

        self.blurOcclusionH = RenderTarget("blurOcclusionHorizontal")
        self.blurOcclusionH.addColorTexture()
        self.blurOcclusionH.prepareOffscreenBuffer()
Ejemplo n.º 9
0
    def _creatGIPrecomputeBuffer(self):
        """ Creates the half-resolution buffer which computes gi and gi
        reflections. We use half-res for performance """

        self.giPrecomputeBuffer = RenderTarget("GICompute")
        self.giPrecomputeBuffer.setSize(self.size.x / 2, self.size.y / 2)
        self.giPrecomputeBuffer.addColorTexture()
        self.giPrecomputeBuffer.addAuxTextures(1)
        self.giPrecomputeBuffer.setColorBits(16)
        self.giPrecomputeBuffer.prepareOffscreenBuffer()
Ejemplo n.º 10
0
class AntialiasingTechniqueFXAA(AntialiasingTechnique):

    """ FXAA 3.11 by NVIDIA """

    def __init__(self):
        AntialiasingTechnique.__init__(self, "FXAA")

    def setup(self):
        """ only one buffer is required """
        self._buffer = RenderTarget("FXAA")
        self._buffer.addColorTexture()
        self._buffer.prepareOffscreenBuffer()
        self._buffer.setShaderInput("colorTex", self._colorTexture)

    def reloadShader(self):
        """ Reloads all assigned shaders """

        fxaaShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                       "Shader/FXAA/FXAA3.fragment")
        self._buffer.setShader(fxaaShader)

    def getResultTexture(self):
        """ Returns the result texture, see AntialiasingTechnique """
        return self._buffer.getColorTexture()

    def requiresJittering(self):
        """ Not required """
        return False
 def _makeDeferredTargets(self):
     self.debug("Creating deferred targets")
     self.deferredTarget = RenderTarget("DeferredTarget")
     self.deferredTarget.addRenderTexture(RenderTargetType.Color)
     self.deferredTarget.addRenderTexture(RenderTargetType.Depth)
     self.deferredTarget.addRenderTexture(RenderTargetType.Aux0)
     self.deferredTarget.addRenderTexture(RenderTargetType.Aux1)
     self.deferredTarget.setAuxBits(16)
     self.deferredTarget.setColorBits(16)
     self.deferredTarget.setDepthBits(32)
     # self.deferredTarget.setSize(400, 240) # check for overdraw
     self.deferredTarget.prepareSceneRender()
    def _makeLightBoundsComputationBuffer(self, w, h):
        self.debug("Creating light precomputation buffer of size", w, "x", h)
        self.lightBoundsComputeBuff = RenderTarget("ComputeLightTileBounds")
        self.lightBoundsComputeBuff.setSize(w, h)
        self.lightBoundsComputeBuff.addRenderTexture(RenderTargetType.Color)
        self.lightBoundsComputeBuff.setColorBits(16)
        self.lightBoundsComputeBuff.prepareOffscreenBuffer()

        self.lightBoundsComputeBuff.setShaderInput("mainCam", base.cam)
        self.lightBoundsComputeBuff.setShaderInput("mainRender", base.render)

        self._setPositionComputationShader()
Ejemplo n.º 13
0
    def _makeDeferredTargets(self):
        """ Creates the multi-render-target """
        self.debug("Creating deferred targets")
        self.deferredTarget = RenderTarget("DeferredTarget")
        self.deferredTarget.addColorAndDepth()

        if self.haveMRT:
            self.deferredTarget.addAuxTextures(3)
            self.deferredTarget.setAuxBits(16)
            self.deferredTarget.setColorBits(32)
            self.deferredTarget.setDepthBits(32)

        self.deferredTarget.prepareSceneRender()
    def _createFinalPass(self):
        self.debug("Creating final pass")
        self.finalPass = RenderTarget("FinalPass")
        self.finalPass.addRenderTexture(RenderTargetType.Color)
        self.finalPass.prepareOffscreenBuffer()

        colorTex = self.antialias.getResultTexture()
        # Set wrap for motion blur
        colorTex.setWrapU(Texture.WMMirror)
        colorTex.setWrapV(Texture.WMMirror)
        self.finalPass.setShaderInput("colorTex", colorTex)
        self.finalPass.setShaderInput("velocityTex",
                                      self.deferredTarget.getAuxTexture(1))
        self.finalPass.setShaderInput("depthTex",
                                      self.deferredTarget.getDepthTexture())
        self._setFinalPassShader()
 def _makeLightBoundsComputationBuffer(self, w, h):
     """ Creates the buffer which precomputes the lights per tile """
     self.debug("Creating light precomputation buffer of size", w, "x", h)
     self.lightBoundsComputeBuff = RenderTarget("ComputeLightTileBounds")
     self.lightBoundsComputeBuff.setSize(w, h)
     self.lightBoundsComputeBuff.setColorWrite(False)
     self.lightBoundsComputeBuff.prepareOffscreenBuffer()
Ejemplo n.º 16
0
    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 _createCombiner(self):
        self.combiner = RenderTarget("Combine-Temporal")
        self.combiner.setColorBits(8)
        self.combiner.addRenderTexture(RenderTargetType.Color)
        self.combiner.prepareOffscreenBuffer()
        self.combiner.setShaderInput(
            "currentComputation",
            self.lightingComputeContainer.getColorTexture())
        self.combiner.setShaderInput("lastFrame",
                                     self.lightingComputeCombinedTex)
        self.combiner.setShaderInput("positionBuffer",
                                     self.deferredTarget.getColorTexture())
        self.combiner.setShaderInput("velocityBuffer",
                                     self.deferredTarget.getAuxTexture(1))
        self.combiner.setShaderInput("lastPosition", self.lastPositionBuffer)

        self._setCombinerShader()
 def _createNormalPrecomputeBuffer(self):
     """ Creates a buffer which reconstructs the normals and position
     from view-space """
     self.normalPrecompute = RenderTarget("PrecomputeNormals")
     self.normalPrecompute.addColorTexture()
     self.normalPrecompute.addAuxTextures(1)
     self.normalPrecompute.setColorBits(16)
     self.normalPrecompute.setAuxBits(16)
     self.normalPrecompute.prepareOffscreenBuffer()
 def _createCombiner(self):
     """ Creates the target which combines the result from the
     lighting computation and last frame together
     (Temporal Reprojection) """
     self.combiner = RenderTarget("Combine-Temporal")
     self.combiner.addColorTexture()
     self.combiner.setColorBits(16)
     self.combiner.prepareOffscreenBuffer()
     self._setCombinerShader()
Ejemplo n.º 20
0
    def _makeDeferredTargets(self):
        """ Creates the multi-render-target """
        self.debug("Creating deferred targets")
        self.deferredTarget = RenderTarget("DeferredTarget")
        self.deferredTarget.addColorAndDepth()

        if self.haveMRT:
            self.deferredTarget.addAuxTextures(3)
            self.deferredTarget.setAuxBits(16)
            self.deferredTarget.setColorBits(32)
            self.deferredTarget.setDepthBits(32)

        self.deferredTarget.prepareSceneRender()
    def _makeLightingComputeBuffer(self):
        self.lightingComputeContainer = RenderTarget("ComputeLighting")
        self.lightingComputeContainer.setSize(
            base.win.getXSize() / self.temporalProjFactor, base.win.getYSize())
        self.lightingComputeContainer.addRenderTexture(RenderTargetType.Color)
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(base.win.getXSize(),
                                                       base.win.getYSize(),
                                                       Texture.TFloat,
                                                       Texture.FRgba16)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(base.win.getXSize(),
                                               base.win.getYSize(),
                                               Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)
    def _makeLightingComputeBuffer(self):
        """ Creates the buffer which applies the lighting """
        self.lightingComputeContainer = RenderTarget("ComputeLighting")
        self.lightingComputeContainer.setSize(
            self.showbase.win.getXSize() / 2,  self.showbase.win.getYSize())
        self.lightingComputeContainer.addColorTexture()
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(
            self.showbase.win.getXSize(), self.showbase.win.getYSize(),
            Texture.TFloat, Texture.FRgba8)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(
            self.showbase.win.getXSize(), self.showbase.win.getYSize(),
            Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)
Ejemplo n.º 23
0
    def _makeLightingComputeBuffer(self):
        """ Creates the buffer which applies the lighting """
        self.lightingComputeContainer = RenderTarget("ComputeLighting")

        if self.settings.enableTemporalReprojection:
            self.lightingComputeContainer.setSize(self.size.x / 2, self.size.y)
        else:
            self.lightingComputeContainer.setSize(self.size.x, self.size.y)

        self.lightingComputeContainer.addColorTexture()
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(
            self.size.x, self.size.y, Texture.TFloat, Texture.FRgba8)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(
            self.size.x, self.size.y, Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)
Ejemplo n.º 24
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 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")
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
 def _setupBlendBuffer(self):
     """ Internal method to create the blending buffer """
     self._blendBuffer = RenderTarget("SMAA-Blend")
     self._blendBuffer.addRenderTexture(RenderTargetType.Color)
     self._blendBuffer.prepareOffscreenBuffer()
class RenderingPipeline(DebugObject):

    """ This is the core class, driving all other classes. To use this
    pipeline, your code has to call *after* the initialization of ShowBase:

        renderPipeline = RenderingPipeline()
        renderPipeline.loadSettings("pipeline.ini")
        renderPipeline.create()

    The pipeline will setup all required buffers, tasks and shaders itself. To
    add lights, see the documentation of LightManager.

    How it works:

    You can see an example buffer view at http://i.imgur.com/mZK6TVj.png

    The pipeline first renders all normal objects (parented to render) into a
    buffer, using multiple render targets. These buffers store normals,
    position and material properties. Your shaders have to output these
    values, but there is a handy api, just look at
    Shaders/DefaultObjectShader.fragment.

    After that, the pipeline splits the screen into tiles, typically of the
    size 32x32. For each tile, it computes which lights affect which tile,
    called Tiled Deferred Shading. This is written to a buffer.

    The next step is applying the lighting. This is done at half window
    resolution only, using Temporal Reprojection. I don't aim to explain
    Temporal Reprojection here, but basically, I render only each second
    pixel each frame. This is simply for performance.

    The lighting pass iterates through the list of lights per tile, and
    applies both lighting and shadows to each pixel, using the material
    information from the previous rendered buffers.

    After the lighting pass, a combiner pass combines both the current
    frame and the last frame, this is required because of
    Temporal Reprojection.

    At this step, we already have a frame we could display. In the next passes,
    only anti-aliasing and post-processing effects like motion blur are added.

    In the meantime, the LightManager builds a list of ShadowSources which
    need an update. It creates a scene render and renders the scene from the
    view of the shadow sources to the global shadow atlas. There are a limited
    amount of shadow updates per frame available, and the updates are stored
    in a queue. So when displaying many shadow-lights, not each shadowmap is
    update each frame. The reason is, again, performance. When you need a
    custom shadow caster shader, e.g. for alpha blending, you should use the
    Shader/DefaultShaowCaster.* as prefab.

    """

    def __init__(self, showbase):
        """ Creates a new pipeline """
        DebugObject.__init__(self, "RenderingPipeline")
        self.showbase = showbase
        self.settings = None
        self.mountManager = MountManager()

    def getMountManager(self):
        """ Returns the mount manager. You can use this to set the
        write directory and base path """
        return self.mountManager

    def loadSettings(self, filename):
        """ Loads the pipeline settings from an ini file """
        self.settings = PipelineSettingsManager()
        self.settings.loadFromFile(filename)

    def getSettings(self):
        """ Returns the current pipeline settings """
        return self.settings

    def create(self):
        """ Creates this pipeline """

        self.debug("Setting up render pipeline")

        if self.settings is None:
            self.error("You have to call loadSettings first!")
            return

        # Mount everything first
        self.mountManager.mount()

        # Store globals, as cython can't handle them
        self.debug("Setting up globals")
        Globals.load(self.showbase)

        # Setting up shader loading
        BetterShader._DumpShaders = self.settings.dumpGeneratedShaders

        # We use PTA's for shader inputs, because that's faster than
        # using setShaderInput
        self.temporalProjXOffs = PTAInt.emptyArray(1)
        self.cameraPosition = PTAVecBase3f.emptyArray(1)
        self.motionBlurFactor = PTAFloat.emptyArray(1)
        self.lastMVP = PTALMatrix4f.emptyArray(1)
        self.currentMVP = PTALMatrix4f.emptyArray(1)

        # Create onscreen gui

        # For the temporal reprojection it is important that the window width
        # is a multiple of 2
        if self.showbase.win.getXSize() % 2 == 1:
            self.error(
                "The window has to have a width which is a multiple of 2 "
                "(Current: ", self.showbase.win.getXSize(), ")")
            self.error(
                "I'll correct that for you, but next time pass the correct "
                "window size!")

            wp = WindowProperties()
            wp.setSize(
                self.showbase.win.getXSize() + 1, self.showbase.win.getYSize())
            self.showbase.win.requestProperties(wp)
            self.showbase.graphicsEngine.openWindows()



        self.camera = self.showbase.cam
        self.size = self._getSize()
        self.cullBounds = None

        # Debug variables to disable specific features
        self.haveLightingPass = True

        # haveCombiner can only be true when haveLightingPass is enabled
        self.haveCombiner = True
        self.haveMRT = True

        # Not as good as I want it, so disabled. I'll work on it.
        self.blurEnabled = False

        self.debug("Window size is", self.size.x, "x", self.size.y)

        self.showbase.camLens.setNearFar(0.1, 50000)
        self.showbase.camLens.setFov(90)


        self.showbase.win.setClearColor(Vec4(1.0,0.0,1.0,1.0))

        # Create occlusion handler
        self._setupOcclusion()

        if self.settings.displayOnscreenDebugger:
            self.guiManager = PipelineGuiManager(self)
            self.guiManager.setup()


        # Generate auto-configuration for shaders
        self._generateShaderConfiguration()


        # Create light manager, which handles lighting + shadows
        if self.haveLightingPass:
            self.lightManager = LightManager(self)

        self.patchSize = LVecBase2i(
            self.settings.computePatchSizeX,
            self.settings.computePatchSizeY)

        # Create separate scene graphs. The deferred graph is render
        self.forwardScene = NodePath("Forward-Rendering")
        self.transparencyScene = NodePath("Transparency-Rendering")

        # We need no transparency (we store other information in the alpha
        # channel)
        self.showbase.render.setAttrib(
            TransparencyAttrib.make(TransparencyAttrib.MNone), 100)

        # Now create deferred render buffers
        self._makeDeferredTargets()

        # Create the target which constructs the view-space normals and
        # position from world-space position
        if self.occlusion.requiresViewSpacePosNrm():
            self._createNormalPrecomputeBuffer()

        # Setup the buffers for lighting
        self._createLightingPipeline()

        # Setup combiner for temporal reprojetion
        if self.haveCombiner:
            self._createCombiner()

        if self.occlusion.requiresBlurring():
            self._createOcclusionBlurBuffer()

        self._setupAntialiasing()

        if self.blurEnabled:
            self._createDofStorage()
            self._createBlurBuffer()

        self._setupFinalPass()
        self._setShaderInputs()

        # add update task
        self._attachUpdateTask()


        # display shadow atlas is defined
        # todo: move this to the gui manager
        # if self.settings.displayShadowAtlas and self.haveLightingPass:
        # self.atlasDisplayImage = OnscreenImage(
        #     image=self.lightManager.getAtlasTex(), pos=(
        #         self.showbase.getAspectRatio() - 0.55, 0, 0.2),
        #     scale=(0.5, 0, 0.5))

    def getForwardScene(self):
        """ Reparent objects to this scene to use forward rendering.
        Objects in this scene will directly get rendered, with no
        lighting etc. applied. """
        return self.forwardScene

    def getTransparentScene(self):
        """ Reparent objects to this scene to allow this objects to
        have transparency. Objects in this scene will get directly
        rendered and no lighting will get applied. """
        return self.transparencyScene

    def _createCombiner(self):
        """ Creates the target which combines the result from the
        lighting computation and last frame together
        (Temporal Reprojection) """
        self.combiner = RenderTarget("Combine-Temporal")
        self.combiner.addColorTexture()
        self.combiner.setColorBits(16)
        self.combiner.prepareOffscreenBuffer()
        self._setCombinerShader()

    def _setupAntialiasing(self):
        """ Creates the antialiasing technique """
        technique = self.settings.antialiasingTechnique
        self.debug("Creating antialiasing handler for", technique)

        if technique == "None":
            self.antialias = AntialiasingTechniqueNone()
        elif technique == "SMAA":
            self.antialias = AntialiasingTechniqueSMAA()
        else:
            self.error(
                "Unkown antialiasing technique", technique, "-> using None:")
            self.antialias = AntialiasingTechniqueNone()

        if self.occlusion.requiresBlurring():
            self.antialias.setColorTexture(
                self.blurOcclusionH.getColorTexture())
        else:
            if self.haveCombiner:
                self.antialias.setColorTexture(self.combiner.getColorTexture())
            else:
                self.antialias.setColorTexture(
                    self.deferredTarget.getColorTexture())

        self.antialias.setDepthTexture(self.deferredTarget.getDepthTexture())
        self.antialias.setup()

    def _setupOcclusion(self):
        """ Creates the occlusion technique """
        technique = self.settings.occlusionTechnique
        self.debug("Creating occlusion handle for", technique)

        if technique == "None":
            self.occlusion = AmbientOcclusionTechniqueNone()
        elif technique == "SAO":
            self.occlusion = AmbientOcclusionTechniqueSAO()
        else:
            self.error("Unkown occlusion technique:", technique)
            self.occlusion = AmbientOcclusionTechniqueNone()

    def _makeDeferredTargets(self):
        """ Creates the multi-render-target """
        self.debug("Creating deferred targets")
        self.deferredTarget = RenderTarget("DeferredTarget")
        self.deferredTarget.addColorAndDepth()

        if self.haveMRT:
            self.deferredTarget.addAuxTextures(3)
            self.deferredTarget.setAuxBits(16)
            self.deferredTarget.setColorBits(16)
            self.deferredTarget.setDepthBits(32)

        # GL_INVALID_OPERATION ?
        # self.deferredTarget.setMultisamples(1)

        self.deferredTarget.prepareSceneRender()

    def _setupFinalPass(self):
        """ Setups the final pass which applies motion blur and so on """
        # Set wrap for motion blur
        colorTex = self.antialias.getResultTexture()
        colorTex.setWrapU(Texture.WMClamp)
        colorTex.setWrapV(Texture.WMClamp)
        self._setFinalPassShader()

    def _makeLightPerTileStorage(self):
        """ Creates a texture to store the lights per tile into. Should
        get replaced with ssbos later """
        storageSizeX = self.precomputeSize.x * 8
        storageSizeY = self.precomputeSize.y * 8

        self.debug(
            "Creating per tile storage of size",
            storageSizeX, "x", storageSizeY)

        self.lightPerTileStorage = Texture("LightsPerTile")
        self.lightPerTileStorage.setup2dTexture(
            storageSizeX, storageSizeY, Texture.TUnsignedShort, Texture.FR32i)
        self.lightPerTileStorage.setMinfilter(Texture.FTNearest)
        self.lightPerTileStorage.setMagfilter(Texture.FTNearest)

    def _createLightingPipeline(self):
        """ Creates the lighting pipeline, including shadow handling """

        if not self.haveLightingPass:
            self.debug("Skipping lighting pipeline")
            return

        self.debug("Creating lighting pipeline ..")

        # size has to be a multiple of the compute unit size
        # but still has to cover the whole screen
        sizeX = int(math.ceil(float(self.size.x) / self.patchSize.x))
        sizeY = int(math.ceil(float(self.size.y) / self.patchSize.y))

        self.precomputeSize = LVecBase2i(sizeX, sizeY)

        self.debug("Batch size =", sizeX, "x", sizeY,
                   "Actual Buffer size=", int(sizeX * self.patchSize.x),
                   "x", int(sizeY * self.patchSize.y))

        self._makeLightPerTileStorage()

        # Create a buffer which computes which light affects which tile
        self._makeLightBoundsComputationBuffer(sizeX, sizeY)

        # Create a buffer which applies the lighting
        self._makeLightingComputeBuffer()

        # Register for light manager
        self.lightManager.setLightingComputator(self.lightingComputeContainer)
        self.lightManager.setLightingCuller(self.lightBoundsComputeBuff)

        self._loadFallbackCubemap()
        self._loadLookupCubemap()

    def _setShaderInputs(self):
        """ Sets most of the required shader inputs to the targets """

        # Shader inputs for the light-culling pass
        if self.haveLightingPass:
            self.lightBoundsComputeBuff.setShaderInput(
                "destination", self.lightPerTileStorage)
            self.lightBoundsComputeBuff.setShaderInput(
                "depth", self.deferredTarget.getDepthTexture())
            self.lightBoundsComputeBuff.setShaderInput(
                "mainCam", self.showbase.cam)
            self.lightBoundsComputeBuff.setShaderInput(
                "mainRender", self.showbase.render)

            # Shader inputs for the light-applying pass
            self.lightingComputeContainer.setShaderInput(
                "data0", self.deferredTarget.getColorTexture())
            self.lightingComputeContainer.setShaderInput(
                "data1", self.deferredTarget.getAuxTexture(0))
            self.lightingComputeContainer.setShaderInput(
                "data2", self.deferredTarget.getAuxTexture(1))
            self.lightingComputeContainer.setShaderInput(
                "data3", self.deferredTarget.getAuxTexture(2))
            self.lightingComputeContainer.setShaderInput(
                "depth", self.deferredTarget.getDepthTexture())

            if self.occlusion.requiresViewSpacePosNrm():
                self.lightingComputeContainer.setShaderInput(
                    "viewSpaceNormals",
                    self.normalPrecompute.getColorTexture())
                self.lightingComputeContainer.setShaderInput(
                    "viewSpacePosition",
                    self.normalPrecompute.getAuxTexture(0))

            self.lightingComputeContainer.setShaderInput(
                "shadowAtlas", self.lightManager.getAtlasTex())
            self.lightingComputeContainer.setShaderInput(
                "destination", self.lightingComputeCombinedTex)
            self.lightingComputeContainer.setShaderInput(
                "temporalProjXOffs", self.temporalProjXOffs)
            self.lightingComputeContainer.setShaderInput(
                "cameraPosition", self.cameraPosition)

            self.lightingComputeContainer.setShaderInput(
                "noiseTexture", self.showbase.loader.loadTexture("Data/Occlusion/noise4x4.png"))
            self.lightingComputeContainer.setShaderInput(
                "lightsPerTile", self.lightPerTileStorage)

        # Shader inputs for the occlusion blur passes
        if self.occlusion.requiresBlurring() and self.haveCombiner:
            self.blurOcclusionH.setShaderInput(
                "colorTex", self.blurOcclusionV.getColorTexture())
            self.blurOcclusionV.setShaderInput(
                "colorTex", self.combiner.getColorTexture())
            self.blurOcclusionH.setShaderInput(
                "normalTex", self.deferredTarget.getAuxTexture(0))
            self.blurOcclusionV.setShaderInput(
                "normalTex", self.deferredTarget.getAuxTexture(0))
            self.blurOcclusionH.setShaderInput(
                "normalsView", self.normalPrecompute.getAuxTexture(0))
            self.blurOcclusionV.setShaderInput(
                "normalsView", self.normalPrecompute.getAuxTexture(0))

        # Shader inputs for the blur passes
        if self.blurEnabled:
            self.blurColorH.setShaderInput(
                "dofStorage", self.dofStorage)
            self.blurColorV.setShaderInput(
                "dofStorage", self.dofStorage)
            self.blurColorH.setShaderInput("colorTex",
                                           self.antialias.getResultTexture())
            self.blurColorH.setShaderInput("depthTex",
                                           self.deferredTarget.getDepthTexture())
            self.blurColorV.setShaderInput("colorTex",
                                           self.blurColorH.getColorTexture())

        # Shader inputs for the temporal reprojection
        if self.haveCombiner:
            self.combiner.setShaderInput(
                "currentComputation",
                self.lightingComputeContainer.getColorTexture())
            self.combiner.setShaderInput(
                "lastFrame", self.lightingComputeCombinedTex)
            self.combiner.setShaderInput(
                "positionBuffer", self.deferredTarget.getColorTexture())
            self.combiner.setShaderInput(
                "velocityBuffer", self.deferredTarget.getAuxTexture(1))

            if self.blurEnabled:
                self.combiner.setShaderInput(
                    "dofStorage", self.dofStorage)

            self.combiner.setShaderInput(
                "depthTex", self.deferredTarget.getDepthTexture())
            self.combiner.setShaderInput(
                "lastPosition", self.lastPositionBuffer)
            self.combiner.setShaderInput(
                "temporalProjXOffs", self.temporalProjXOffs)
            self.combiner.setShaderInput("lastMVP", self.lastMVP)
            self.combiner.setShaderInput("cameraPosition", self.cameraPosition)
            self.combiner.setShaderInput("currentMVP", self.lastMVP)

        # Shader inputs for the final pass
        if self.blurEnabled:
            self.deferredTarget.setShaderInput(
                "colorTex", self.blurColorV.getColorTexture())
        else:
            self.deferredTarget.setShaderInput(
                "colorTex", self.antialias.getResultTexture())

        if self.occlusion.requiresBlurring():
            self.normalPrecompute.setShaderInput(
                "positionTex", self.deferredTarget.getColorTexture())
            self.normalPrecompute.setShaderInput(
                "mainCam", self.showbase.cam)
            self.normalPrecompute.setShaderInput(
                "mainRender", self.showbase.render)
            self.normalPrecompute.setShaderInput(
                "depthTex", self.deferredTarget.getDepthTexture())

        if self.haveMRT:
            self.deferredTarget.setShaderInput(
                "velocityTex", self.deferredTarget.getAuxTexture(1))

        self.deferredTarget.setShaderInput(
            "depthTex", self.deferredTarget.getDepthTexture())
        self.deferredTarget.setShaderInput(
            "motionBlurFactor", self.motionBlurFactor)

        if self.haveLightingPass:
            self.deferredTarget.setShaderInput(
                "lastFrame", self.lightingComputeCombinedTex)

        if self.haveCombiner:
            self.deferredTarget.setShaderInput(
                "newFrame", self.combiner.getColorTexture())
            self.deferredTarget.setShaderInput(
                "lastPosition", self.lastPositionBuffer)

        self.deferredTarget.setShaderInput(
            "currentPosition", self.deferredTarget.getColorTexture())

        # Set last / current mvp handles
        self.showbase.render.setShaderInput("lastMVP", self.lastMVP)

        # Finally, set shaders
        self.reloadShaders()

    def _loadFallbackCubemap(self):
        """ Loads the cubemap for image based lighting """
        cubemap = self.showbase.loader.loadCubeMap(
            "Data/Cubemaps/Default/#.png")
        cubemap.setMinfilter(Texture.FTLinearMipmapLinear)
        cubemap.setMagfilter(Texture.FTLinearMipmapLinear)
        cubemap.setFormat(Texture.F_srgb_alpha)
        self.lightingComputeContainer.setShaderInput(
            "fallbackCubemap", cubemap)

    def _loadLookupCubemap(self):
        self.debug("Loading lookup cubemap")
        cubemap = self.showbase.loader.loadCubeMap(
            "Data/Cubemaps/DirectionLookup/#.png")
        cubemap.setMinfilter(Texture.FTNearest)
        cubemap.setMagfilter(Texture.FTNearest)
        cubemap.setFormat(Texture.F_rgb8)
        self.lightingComputeContainer.setShaderInput(
            "directionToFace", cubemap)

    def _makeLightBoundsComputationBuffer(self, w, h):
        """ Creates the buffer which precomputes the lights per tile """
        self.debug("Creating light precomputation buffer of size", w, "x", h)
        self.lightBoundsComputeBuff = RenderTarget("ComputeLightTileBounds")
        self.lightBoundsComputeBuff.setSize(w, h)
        self.lightBoundsComputeBuff.setColorWrite(False)
        self.lightBoundsComputeBuff.prepareOffscreenBuffer()

    def _makeLightingComputeBuffer(self):
        """ Creates the buffer which applies the lighting """
        self.lightingComputeContainer = RenderTarget("ComputeLighting")
        self.lightingComputeContainer.setSize(
            self.showbase.win.getXSize() / 2,  self.showbase.win.getYSize())
        self.lightingComputeContainer.addColorTexture()
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(
            self.showbase.win.getXSize(), self.showbase.win.getYSize(),
            Texture.TFloat, Texture.FRgba8)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(
            self.showbase.win.getXSize(), self.showbase.win.getYSize(),
            Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)

    def _createOcclusionBlurBuffer(self):
        """ Creates the buffers needed to blur the occlusion """
        self.blurOcclusionV = RenderTarget("blurOcclusionVertical")
        self.blurOcclusionV.addColorTexture()
        self.blurOcclusionV.prepareOffscreenBuffer()

        self.blurOcclusionH = RenderTarget("blurOcclusionHorizontal")
        self.blurOcclusionH.addColorTexture()
        self.blurOcclusionH.prepareOffscreenBuffer()

        # Mipmaps for blur?
        # self.blurOcclusionV.getColorTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)
        # self.combiner.getColorTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)

    def _createBlurBuffer(self):
        """ Creates the buffers for the dof """
        self.blurColorV = RenderTarget("blurColorVertical")
        self.blurColorV.addColorTexture()
        self.blurColorV.prepareOffscreenBuffer()

        self.blurColorH = RenderTarget("blurColorHorizontal")
        self.blurColorH.addColorTexture()
        self.blurColorH.prepareOffscreenBuffer()

        # self.blurColorH.getColorTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)
        # self.antialias.getResultTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)

    def _createNormalPrecomputeBuffer(self):
        """ Creates a buffer which reconstructs the normals and position
        from view-space """
        self.normalPrecompute = RenderTarget("PrecomputeNormals")
        self.normalPrecompute.addColorTexture()
        self.normalPrecompute.addAuxTextures(1)
        self.normalPrecompute.setColorBits(16)
        self.normalPrecompute.setAuxBits(16)
        self.normalPrecompute.prepareOffscreenBuffer()

    def _createDofStorage(self):
        """ Creates the texture where the dof factor is stored in, so we
        don't recompute it each pass """
        self.dofStorage = Texture("DOFStorage")
        self.dofStorage.setup2dTexture(
            self.showbase.win.getXSize(), self.showbase.win.getYSize(),
            Texture.TFloat, Texture.FRg16)

    def _setOcclusionBlurShader(self):
        """ Sets the shaders which blur the occlusion """
        blurVShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/BlurOcclusionVertical.fragment")
        blurHShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/BlurOcclusionHorizontal.fragment")
        self.blurOcclusionV.setShader(blurVShader)
        self.blurOcclusionH.setShader(blurHShader)

    def _setBlurShader(self):
        """ Sets the shaders which blur the color """
        blurVShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/BlurVertical.fragment")
        blurHShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/BlurHorizontal.fragment")
        self.blurColorV.setShader(blurVShader)
        self.blurColorH.setShader(blurHShader)

    def _setLightingShader(self):
        """ Sets the shader which applies the light """
        lightShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/ApplyLighting.fragment")
        self.lightingComputeContainer.setShader(lightShader)

    def _setCombinerShader(self):
        """ Sets the shader which combines the lighting with the previous frame
        (temporal reprojection) """
        cShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/Combiner.fragment")
        self.combiner.setShader(cShader)

    def _setPositionComputationShader(self):
        """ Sets the shader which computes the lights per tile """
        pcShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/PrecomputeLights.fragment")
        self.lightBoundsComputeBuff.setShader(pcShader)

    def _setFinalPassShader(self):
        """ Sets the shader which computes the final frame,
        with motion blur and so on """
        fShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/Final.fragment")
        self.deferredTarget.setShader(fShader)

    def _getSize(self):
        """ Returns the window size. """
        return LVecBase2i(
            self.showbase.win.getXSize(),
            self.showbase.win.getYSize())

    def reloadShaders(self):
        """ Reloads all shaders """

        if self.haveLightingPass:
            self.lightManager.debugReloadShader()
            self._setPositionComputationShader()
            self._setLightingShader()

        if self.haveCombiner:
            self._setCombinerShader()

        self._setFinalPassShader()

        if self.occlusion.requiresBlurring():
            self._setOcclusionBlurShader()

        if self.blurEnabled:
            self._setBlurShader()

        if self.occlusion.requiresViewSpacePosNrm():
            self._setNormalExtractShader()

        self.antialias.reloadShader()

    def _setNormalExtractShader(self):
        """ Sets the shader which constructs the normals from position """
        npShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/ExtractNormals.fragment")
        self.normalPrecompute.setShader(npShader)

    def _attachUpdateTask(self):
        """ Attaches the update tasks to the showbase """

        self.showbase.addTask(
            self._update, "UpdateRenderingPipeline", sort=-10)

        if self.haveLightingPass:
            self.showbase.addTask(
                self._updateLights, "UpdateLights", sort=-9)
            self.showbase.addTask(
                self._updateShadows, "updateShadows", sort=-8)

        if self.settings.displayOnscreenDebugger:
            self.showbase.addTask(
                self._updateGUI, "UpdateGUI", sort=7)

    def _computeCameraBounds(self):
        """ Computes the current camera bounds, i.e. for light culling """
        cameraBounds = self.camera.node().getLens().makeBounds()
        cameraBounds.xform(self.camera.getMat(self.showbase.render))
        return cameraBounds

    def _updateLights(self, task=None):
        """ Task which updates/culls the lights """
        self.lightManager.updateLights()
        if task is not None:
            return task.cont

    def _updateShadows(self, task=None):
        """ Task which updates the shadow maps """
        self.lightManager.updateShadows()
        if task is not None:
            return task.cont

    def _updateGUI(self, task=None):
        """ Task which updates the onscreen gui debugger """
        self.guiManager.update()

        if task is not None:
            return task.cont

    def _update(self, task=None):
        """ Main update task """
        currentFPS = 1.0 / self.showbase.taskMgr.globalClock.getDt()

        self.temporalProjXOffs[0] = 1 - self.temporalProjXOffs[0]
        self.cameraPosition[0] = self.showbase.cam.getPos(self.showbase.render)
        self.motionBlurFactor[0] = min(1.5, currentFPS /
                                       60.0) * self.settings.motionBlurFactor

        self.cullBounds = self._computeCameraBounds()

        if self.haveLightingPass:
            self.lightManager.setCullBounds(self.cullBounds)

        self.lastMVP[0] = self.currentMVP[0]
        self.currentMVP[0] = self._computeMVP()

        if task is not None:
            return task.cont

    def _computeMVP(self):
        """ Computes the current mvp. Actually, this is the
        worldViewProjectionMatrix, but for convience it's called mvp. """
        camLens = self.showbase.camLens
        projMat = Mat4.convertMat(
            CSYupRight,
            camLens.getCoordinateSystem()) * camLens.getProjectionMat()
        transformMat = TransformState.makeMat(
            Mat4.convertMat(self.showbase.win.getGsg().getInternalCoordinateSystem(),
                            CSZupRight))
        modelViewMat = transformMat.invertCompose(
            self.showbase.render.getTransform(self.showbase.cam)).getMat()
        return UnalignedLMatrix4f(modelViewMat * projMat)

    def getLightManager(self):
        """ Returns a handle to the light manager """
        return self.lightManager

    def getDefaultObjectShader(self, tesselated=False):
        """ Returns the default shader for objects """

        if not tesselated:
            shader = BetterShader.load(
                "Shader/DefaultObjectShader/vertex.glsl",
                "Shader/DefaultObjectShader/fragment.glsl")
        else:
            self.warn(
                "Tesselation is only experimental! Remember "
                "to convert the geometry to patches first!")

            shader = BetterShader.load(
                "Shader/DefaultObjectShader/vertex.glsl",
                "Shader/DefaultObjectShader/fragment.glsl",
                "",
                "Shader/DefaultObjectShader/tesscontrol.glsl",
                "Shader/DefaultObjectShader/tesseval.glsl")

        return shader

    def addLight(self, light):
        """ Adds a light to the list of rendered lights """
        if self.haveLightingPass:
            self.lightManager.addLight(light)
        else:
            self.warn("Lighting is disabled, so addLight has no effect")

    def _generateShaderConfiguration(self):
        """ Genrates the global shader include which defines
        most values used in the shaders. """

        self.debug("(Re)Generating shader configuration")

        # Generate list of defines
        defines = []

        if self.settings.antialiasingTechnique == "SMAA":
            quality = self.settings.smaaQuality.upper()
            if quality in ["LOW", "MEDIUM", "HIGH", "ULTRA"]:
                defines.append(("SMAA_PRESET_" + quality, ""))
            else:
                self.error("Unrecognized SMAA quality:", quality)
                return

        defines.append(
            ("LIGHTING_COMPUTE_PATCH_SIZE_X", self.settings.computePatchSizeX))
        defines.append(
            ("LIGHTING_COMPUTE_PATCH_SIZE_Y", self.settings.computePatchSizeY))
        defines.append(
            ("LIGHTING_MIN_MAX_DEPTH_ACCURACY", self.settings.minMaxDepthAccuracy))

        if self.blurEnabled:
            defines.append(("USE_DOF", 1))

        if self.settings.useSimpleLighting:
            defines.append(("USE_SIMPLE_LIGHTING", 1))

        if self.settings.anyLightBoundCheck:
            defines.append(("LIGHTING_ANY_BOUND_CHECK", 1))

        if self.settings.accurateLightBoundCheck:
            defines.append(("LIGHTING_ACCURATE_BOUND_CHECK", 1))

        if self.settings.renderShadows:
            defines.append(("USE_SHADOWS", 1))

        defines.append(
            ("SHADOW_MAP_ATLAS_SIZE", self.settings.shadowAtlasSize))
        defines.append(
            ("SHADOW_MAX_UPDATES_PER_FRAME", self.settings.maxShadowUpdatesPerFrame))
        defines.append(
            ("SHAODOW_GEOMETRY_MAX_VERTICES", self.settings.maxShadowUpdatesPerFrame * 3))

        defines.append(("SHADOWS_NUM_SAMPLES", self.settings.numShadowSamples))

        if self.settings.useHardwarePCF:
            defines.append(("USE_HARDWARE_PCF", 1))

        defines.append(("WINDOW_WIDTH", self.showbase.win.getXSize()))
        defines.append(("WINDOW_HEIGHT", self.showbase.win.getYSize()))

        if self.settings.motionBlurEnabled:
            defines.append(("USE_MOTION_BLUR", 1))

        defines.append(
            ("MOTION_BLUR_SAMPLES", self.settings.motionBlurSamples))

        # Occlusion
        defines.append(
            ("OCCLUSION_TECHNIQUE_" + self.occlusion.getIncludeName(), 1))
        defines.append(
            ("OCCLUSION_RADIUS", self.settings.occlusionRadius))
        defines.append(
            ("OCCLUSION_STRENGTH", self.settings.occlusionStrength))
        defines.append(
            ("OCCLUSION_SAMPLES", self.settings.occlusionSampleCount))

        if self.settings.displayOnscreenDebugger:
            defines.append(("DEBUGGER_ACTIVE", 1))

            extraSettings = self.guiManager.getDefines()
            defines += extraSettings


        # Pass near far
        defines.append(("CAMERA_NEAR", Globals.base.camLens.getNear()))
        defines.append(("CAMERA_FAR", Globals.base.camLens.getFar()))

        # Generate
        output = "// Autogenerated by RenderingPipeline.py\n"
        output += "// Do not edit! Your changes will be lost.\n\n"

        for key, value in defines:
            output += "#define " + key + " " + str(value) + "\n"

        # Try to write the file
        # Todo: add error handling
        with open("PipelineTemp/ShaderAutoConfig.include", "w") as handle:
            handle.write(output)

    def onWindowResized(self):
        """ Call this whenever the window resized """
        raise NotImplementedError()

    def destroy(self):
        """ Call this when you want to shut down the pipeline """
        self.mountManager.unmount()
        self.error("Destroy is not implemented yet")

    def reload(self):
        """ This reloads the whole pipeline, same as destroy(); create() """
        self.debug("Reloading pipeline")
        self.destroy()
        self.create()

    def setActive(self, active):
        """ You can enable/disable the pipeline, for example
        when the user is in the menu, the 3d scene does not have
        to be rendered """
        raise NotImplementedError()
Ejemplo n.º 28
0
    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)
Ejemplo n.º 29
0
class AntialiasingTechniqueSMAA(AntialiasingTechnique):

    """ SMAA Method from http://www.iryoku.com/smaa/. We only use
    SMAA x1, as we already use temporal reprojection and using T2
    we would be 4 frames behind (too many!!) """

    def __init__(self):
        """ Creates this Technique """
        AntialiasingTechnique.__init__(self, "SMAA")
        self.currentIndex = PTAInt.emptyArray(1)
        self.currentIndex[0] = 0

    def setup(self):
        """ Setups the SMAA. The comments are original from the SMAA.glsl """
        #  1. The first step is to create two RGBA temporal render targets for holding
        #|edgesTex| and |blendTex|.
        self._setupEdgesBuffer()
        self._setupBlendBuffer()
        self._setupNeighborBuffer()
        self._setupResolveBuffer()

        #  2. Both temporal render targets |edgesTex| and |blendTex| must be cleared
        #     each frame. Do not forget to clear the alpha channel!

        self._edgesBuffer.setClearColor()
        self._blendBuffer.setClearColor()

        #  3. The next step is loading the two supporting precalculated textures,
        #     'areaTex' and 'searchTex'. You'll find them in the 'Textures' folder as
        #     C++ headers, and also as regular DDS files. They'll be needed for the
        #     'SMAABlendingWeightCalculation' pass.
        self.areaTex = Globals.loader.loadTexture(
            "Data/Antialiasing/SMAA_AreaTexGL.png")
        self.searchTex = Globals.loader.loadTexture(
            "Data/Antialiasing/SMAA_SearchTexGL.png")

        #  4. All samplers must be set to linear filtering and clamp.
        for sampler in [self.areaTex, self.searchTex, self._edgesBuffer.getColorTexture(), self._blendBuffer.getColorTexture()]:
            sampler.setMinfilter(Texture.FTLinear)
            sampler.setMagfilter(Texture.FTLinear)
            sampler.setWrapU(Texture.WMClamp)
            sampler.setWrapV(Texture.WMClamp)

        self._edgesBuffer.setShaderInput("colorTex", self._colorTexture)
        # self._edgesBuffer.setShaderInput("depthTex", self._depthTexture)

        self._blendBuffer.setShaderInput(
            "edgesTex", self._edgesBuffer.getColorTexture())
        self._blendBuffer.setShaderInput("areaTex", self.areaTex)
        self._blendBuffer.setShaderInput("searchTex", self.searchTex)
        self._blendBuffer.setShaderInput("currentIndex", self.currentIndex)

        for buff in self._neighborBuffers:
            buff.setShaderInput("colorTex", self._colorTexture)
            buff.setShaderInput("velocityTex", self._velocityTexture)
            buff.setShaderInput(
                "blendTex", self._blendBuffer.getColorTexture())

        self._resolveBuffer.setShaderInput(
            "velocityTex", self._velocityTexture)

        # Set initial shader
        self.reloadShader()

    def reloadShader(self):
        """ Reloads all used shaders """
        edgeShader = BetterShader.load(
            "Shader/SMAA/EdgeDetection.vertex", "Shader/SMAA/EdgeDetection.fragment")
        self._edgesBuffer.setShader(edgeShader)

        weightsShader = BetterShader.load(
            "Shader/SMAA/BlendingWeights.vertex", "Shader/SMAA/BlendingWeights.fragment")
        self._blendBuffer.setShader(weightsShader)

        neighborShader = BetterShader.load(
            "Shader/SMAA/Neighbors.vertex", "Shader/SMAA/Neighbors.fragment")

        for buff in self._neighborBuffers:
            buff.setShader(neighborShader)

        resolveShader = BetterShader.load(
            "Shader/SMAA/Resolve.vertex", "Shader/SMAA/Resolve.fragment")
        self._resolveBuffer.setShader(resolveShader)

    def getResultTexture(self):
        """ Returns the result texture, see AntialiasingTechnique """
        return self._resolveBuffer.getColorTexture()

    def _setupEdgesBuffer(self):
        """ Internal method to create the edges buffer """
        self._edgesBuffer = RenderTarget("SMAA-Edges")
        self._edgesBuffer.addRenderTexture(RenderTargetType.Color)
        self._edgesBuffer.prepareOffscreenBuffer()

    def _setupBlendBuffer(self):
        """ Internal method to create the blending buffer """
        self._blendBuffer = RenderTarget("SMAA-Blend")
        self._blendBuffer.addRenderTexture(RenderTargetType.Color)
        self._blendBuffer.prepareOffscreenBuffer()

    def _setupNeighborBuffer(self):
        """ Internal method to create the weighting buffer """

        self._neighborBuffers = []
        for i in xrange(2):
            self._neighborBuffers.append(RenderTarget("SMAA-Neighbors-" + str(i)))
            self._neighborBuffers[i].addRenderTexture(RenderTargetType.Color)
            self._neighborBuffers[i].prepareOffscreenBuffer()

    def _setupResolveBuffer(self):
        """ Creates the buffer which does the final resolve pass """
        self._resolveBuffer = RenderTarget("SMAA-Resolve")
        self._resolveBuffer.addColorTexture()
        self._resolveBuffer.prepareOffscreenBuffer()

    def requiresJittering(self):
        """ For SMAA T2 """
        return True

    def preRenderUpdate(self):
        """ Selects the correct buffers """

        self._neighborBuffers[self.currentIndex[0]].setActive(False)
        self._resolveBuffer.setShaderInput("lastTex",
                                           self._neighborBuffers[self.currentIndex[0]].getColorTexture())

        self.currentIndex[0] = 1 - self.currentIndex[0]
        self._neighborBuffers[self.currentIndex[0]].setActive(True)

        self._resolveBuffer.setShaderInput("currentTex",
                                           self._neighborBuffers[self.currentIndex[0]].getColorTexture())
Ejemplo n.º 30
0
    def setup(self):
        """ Setups everything for the GI to work """
        assert (self.distributionSteps % 2 == 0)

        self._createDebugTexts()

        self.pipeline.getRenderPassManager().registerDefine(
            "USE_GLOBAL_ILLUMINATION", 1)
        self.pipeline.getRenderPassManager().registerDefine(
            "GI_SLIDE_COUNT", self.slideCount)
        self.pipeline.getRenderPassManager().registerDefine(
            "GI_QUALITY_LEVEL", self.qualityLevelIndex)

        # make the grid resolution a constant
        self.pipeline.getRenderPassManager().registerDefine(
            "GI_GRID_RESOLUTION", self.voxelGridResolution)

        self.taskManager.addTask(3, self.stepVoxelize)
        self.taskManager.addTask(self.distributionSteps, self.stepDistribute)

        # Create the voxelize pass which is used to voxelize the scene from
        # several directions
        self.voxelizePass = VoxelizePass(self.pipeline)
        self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution)
        self.voxelizePass.setVoxelGridSize(self.voxelGridSize)
        self.voxelizePass.setGridResolutionMultiplier(1)
        self.pipeline.getRenderPassManager().registerPass(self.voxelizePass)

        self.generationTextures = []

        # Create the buffers used to create the voxel grid
        for color in "rgb":
            tex = Texture("VoxelGeneration-" + color)
            tex.setup3dTexture(self.voxelGridResolution,
                               self.voxelGridResolution,
                               self.voxelGridResolution, Texture.TInt,
                               Texture.FR32)
            tex.setClearColor(Vec4(0))
            self.generationTextures.append(tex)
            Globals.render.setShaderInput("voxelGenDest" + color.upper(), tex)

            MemoryMonitor.addTexture("VoxelGenerationTex-" + color.upper(),
                                     tex)

        self.bindTo(Globals.render, "giData")

        self.convertGridTarget = RenderTarget("ConvertGIGrid")
        self.convertGridTarget.setSize(
            self.voxelGridResolution * self.slideCount,
            self.voxelGridResolution * self.slideVertCount)

        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.addColorTexture()
        self.convertGridTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.getColorTexture().setMinfilter(
                Texture.FTNearest)
            self.convertGridTarget.getColorTexture().setMagfilter(
                Texture.FTNearest)

        self.clearGridTarget = RenderTarget("ClearGIGrid")
        self.clearGridTarget.setSize(
            self.voxelGridResolution * self.slideCount,
            self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.clearGridTarget.addColorTexture()
        self.clearGridTarget.prepareOffscreenBuffer()

        for idx, color in enumerate("rgb"):
            self.convertGridTarget.setShaderInput(
                "voxelGenSrc" + color.upper(), self.generationTextures[idx])
            self.clearGridTarget.setShaderInput("voxelGenTex" + color.upper(),
                                                self.generationTextures[idx])

        # Create the data textures
        self.dataTextures = []
        self.directions = ["PosX", "NegX", "PosY", "NegY", "PosZ", "NegZ"]

        for i, direction in enumerate(self.directions):
            tex = Texture("GIDataTex" + direction)
            tex.setup3dTexture(self.voxelGridResolution,
                               self.voxelGridResolution,
                               self.voxelGridResolution, Texture.TFloat,
                               Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelDataTex-" + direction, tex)
            self.dataTextures.append(tex)
            self.pipeline.getRenderPassManager().registerStaticVariable(
                "giVoxelData" + direction, tex)

        # Create ping / pong textures
        self.pingDataTextures = []
        self.pongDataTextures = []

        for i, direction in enumerate(self.directions):
            texPing = Texture("GIPingDataTex" + direction)
            texPing.setup3dTexture(self.voxelGridResolution,
                                   self.voxelGridResolution,
                                   self.voxelGridResolution, Texture.TFloat,
                                   Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPingDataTex-" + direction, texPing)
            self.pingDataTextures.append(texPing)

            texPong = Texture("GIPongDataTex" + direction)
            texPong.setup3dTexture(self.voxelGridResolution,
                                   self.voxelGridResolution,
                                   self.voxelGridResolution, Texture.TFloat,
                                   Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPongDataTex-" + direction, texPong)
            self.pongDataTextures.append(texPong)

            self.convertGridTarget.setShaderInput("voxelDataDest" + direction,
                                                  self.pingDataTextures[i])
            # self.clearGridTarget.setShaderInput("voxelDataDest" + str(i), self.pongDataTextures[i])

        # Set texture wrap modes
        for tex in self.pingDataTextures + self.pongDataTextures + self.dataTextures + self.generationTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)
            tex.setWrapU(Texture.WMBorderColor)
            tex.setWrapV(Texture.WMBorderColor)
            tex.setWrapW(Texture.WMBorderColor)
            tex.setAnisotropicDegree(0)
            tex.setBorderColor(Vec4(0))

        for tex in self.dataTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)

        self.distributeTarget = RenderTarget("DistributeVoxels")
        self.distributeTarget.setSize(
            self.voxelGridResolution * self.slideCount,
            self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.addColorTexture()
        self.distributeTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.getColorTexture().setMinfilter(
                Texture.FTNearest)
            self.distributeTarget.getColorTexture().setMagfilter(
                Texture.FTNearest)

        self.distributeTarget.setShaderInput("isLastStep", False)

        # Create solidness texture
        self.voxelSolidTex = Texture("GIDataSolidTex")
        self.voxelSolidTex.setup3dTexture(self.voxelGridResolution,
                                          self.voxelGridResolution,
                                          self.voxelGridResolution,
                                          Texture.TFloat, Texture.FR16)
        self.convertGridTarget.setShaderInput("voxelSolidDest",
                                              self.voxelSolidTex)
        self.distributeTarget.setShaderInput("voxelSolidTex",
                                             self.voxelSolidTex)
        MemoryMonitor.addTexture("VoxelSolidTex", self.voxelSolidTex)

        self.voxelSolidStableTex = Texture("GIDataSolidStableTex")
        self.voxelSolidStableTex.setup3dTexture(self.voxelGridResolution,
                                                self.voxelGridResolution,
                                                self.voxelGridResolution,
                                                Texture.TFloat, Texture.FR16)

        self.distributeTarget.setShaderInput("voxelSolidWriteTex",
                                             self.voxelSolidStableTex)
        self.pipeline.getRenderPassManager().registerStaticVariable(
            "giVoxelSolidTex", self.voxelSolidStableTex)

        # Create the final gi pass
        self.finalPass = GlobalIlluminationPass()
        self.pipeline.getRenderPassManager().registerPass(self.finalPass)
        self.pipeline.getRenderPassManager().registerDynamicVariable(
            "giData", self.bindTo)
        self.pipeline.getRenderPassManager().registerStaticVariable(
            "giReadyState", self.readyStateFlag)

        # Visualize voxels
        if False:
            self.voxelCube = loader.loadModel("Box")
            self.voxelCube.reparentTo(render)
            # self.voxelCube.setTwoSided(True)
            self.voxelCube.node().setFinal(True)
            self.voxelCube.node().setBounds(OmniBoundingVolume())
            self.voxelCube.setInstanceCount(self.voxelGridResolution**3)
            # self.voxelCube.hide()
            self.bindTo(self.voxelCube, "giData")

            for i in xrange(5):
                self.voxelCube.setShaderInput("giDataTex" + str(i),
                                              self.pingDataTextures[i])

        self.disableTargets()
Ejemplo n.º 31
0
    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("ShadowCompute")
        self.shadowComputeTarget.setSize(self.shadowAtlas.getSize())
        self.shadowComputeTarget.addDepthTexture()
        self.shadowComputeTarget.setDepthBits(32)
        self.shadowComputeTarget.setSource(
            self.shadowComputeCameraNode, Globals.base.win)
        self.shadowComputeTarget.prepareSceneRender()

        # We have to adjust the sort
        self.shadowComputeTarget.getInternalRegion().setSort(3)
        self.shadowComputeTarget.getRegion().setSort(3)

        self.shadowComputeTarget.getInternalRegion().setNumRegions(
            self.maxShadowUpdatesPerFrame + 1)

        # The first viewport always has to be fullscreen
        self.shadowComputeTarget.getInternalRegion().setDimensions(
            0, (0, 1, 0, 1))
        self.shadowComputeTarget.setClearDepth(False)

        # 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(2)
            dr.setClearDepthActive(True)
            dr.setClearDepth(1.0)
            dr.setClearColorActive(False)
            dr.setDimensions(0, 0, 0, 0)
            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)
Ejemplo n.º 32
0
    def setup(self):
        """ Setups everything for the GI to work """


        # Create the voxelize pass which is used to voxelize the scene from
        # several directions
        self.voxelizePass = VoxelizePass()
        self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution)
        self.voxelizePass.setVoxelGridSize(self.voxelGridSizeWS)
        self.voxelizePass.initVoxelStorage()
        self.pipeline.getRenderPassManager().registerPass(self.voxelizePass)

        # Create 3D Texture which is a copy of the voxel generation grid but
        # stable, as the generation grid is updated part by part and that would 
        # lead to flickering
        self.voxelStableTex = Texture("VoxelsStable")
        self.voxelStableTex.setup3dTexture(self.voxelGridResolution.x, self.voxelGridResolution.y, 
                                            self.voxelGridResolution.z, Texture.TFloat, Texture.FRgba8)

        # Set appropriate filter types:
        # The stable texture has mipmaps, which are generated during the process.
        # This is required for cone tracing.
        self.voxelStableTex.setMagfilter(SamplerState.FTLinear)
        self.voxelStableTex.setMinfilter(SamplerState.FTLinearMipmapLinear)
        self.voxelStableTex.setWrapU(SamplerState.WMBorderColor)
        self.voxelStableTex.setWrapV(SamplerState.WMBorderColor)
        self.voxelStableTex.setWrapW(SamplerState.WMBorderColor)
        self.voxelStableTex.setBorderColor(Vec4(0,0,0,0))

        MemoryMonitor.addTexture("Voxel Grid Texture", self.voxelStableTex)

        # Setups the render target to convert the voxel grid
        self.convertBuffer = RenderTarget("VoxelConvertBuffer")
        self.convertBuffer.setSize(self.voxelGridResolution.x, self.voxelGridResolution.y)
        self.convertBuffer.setColorWrite(False)
        # self.convertBuffer.addColorTexture()
        self.convertBuffer.prepareOffscreenBuffer()
        self.convertBuffer.setShaderInput("src", self.voxelizePass.getVoxelTex())
        self.convertBuffer.setShaderInput("dest", self.voxelStableTex)
        self.convertBuffer.setActive(False)

        # Store the frame index, we need that to decide which step we are currently
        # doing
        self.frameIndex = 0


        # Create the various render targets to generate the mipmaps of the stable voxel grid
        self.mipmapTargets = []
        computeSize = LVecBase3i(self.voxelGridResolution)
        currentMipmap = 0
        while computeSize.z > 1:
            computeSize /= 2
            target = RenderTarget("GIMiplevel" + str(currentMipmap))
            target.setSize(computeSize.x, computeSize.y)
            target.setColorWrite(False)
            # target.addColorTexture()
            target.prepareOffscreenBuffer()
            target.setActive(False)
            target.setShaderInput("sourceMipmap", currentMipmap)
            target.setShaderInput("source", self.voxelStableTex)
            target.setShaderInput("dest", self.voxelStableTex, False, True, -1, currentMipmap + 1)
            self.mipmapTargets.append(target)
            currentMipmap += 1


        # Create the final gi pass
        self.finalPass = GlobalIlluminationPass()
        self.pipeline.getRenderPassManager().registerPass(self.finalPass)
        self.pipeline.getRenderPassManager().registerDynamicVariable("giVoxelGridData", self.bindTo)
Ejemplo n.º 33
0
class GlobalIllumination(DebugObject):

    """ This class handles the global illumination processing. To process the
    global illumination, the scene is first rasterized from 3 directions, and 
    a 3D voxel grid is created. After that, the mipmaps of the voxel grid are
    generated. The final shader then performs voxel cone tracing to compute 
    an ambient, diffuse and specular term.

    The gi is split over several frames to reduce the computation cost. Currently
    there are 5 different steps, split over 4 frames:

    Frame 1: 
        - Rasterize the scene from the x-axis

    Frame 2:
        - Rasterize the scene from the y-axis

    Frame 3: 
        - Rasterize the scene from the z-axis

    Frame 4:
        - Copy the generated temporary voxel grid into a stable voxel grid
        - Generate the mipmaps for that stable voxel grid using a gaussian filter

    In the final pass the stable voxel grid is sampled. The voxel tracing selects
    the mipmap depending on the cone size. This enables small scale details as well
    as blurry reflections and low-frequency ao / diffuse. For performance reasons,
    the final pass is executed at half window resolution and then bilateral upscaled.
    """


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

        # Fetch the scene data
        self.targetCamera = Globals.base.cam
        self.targetSpace = Globals.base.render

        # Store grid size in world space units
        # This is the half voxel grid size
        self.voxelGridSizeWS = Vec3(60)

        # When you change this resolution, you have to change it in Shader/GI/ConvertGrid.fragment aswell
        self.voxelGridResolution = LVecBase3i(256)

        self.targetLight = None
        self.helperLight = None
        self.ptaGridPos = PTALVecBase3f.emptyArray(1)
        self.gridPos = Vec3(0)

        # Create ptas 

        self.ptaLightUVStart = PTALVecBase2f.emptyArray(1)
        self.ptaLightMVP = PTAMat4.emptyArray(1)
        self.ptaVoxelGridStart = PTALVecBase3f.emptyArray(1)
        self.ptaVoxelGridEnd = PTALVecBase3f.emptyArray(1)
        self.ptaLightDirection = PTALVecBase3f.emptyArray(1)

        self.targetSpace.setShaderInput("giLightUVStart", self.ptaLightUVStart)
        self.targetSpace.setShaderInput("giLightMVP", self.ptaLightMVP)
        self.targetSpace.setShaderInput("giVoxelGridStart", self.ptaVoxelGridStart)
        self.targetSpace.setShaderInput("giVoxelGridEnd", self.ptaVoxelGridEnd)
        self.targetSpace.setShaderInput("giLightDirection", self.ptaLightDirection)

    def setTargetLight(self, light):
        """ Sets the sun light which is the main source of GI. Only that light
        casts gi. """
        if light.getLightType() != LightType.Directional:
            self.error("setTargetLight expects a directional light!")
            return

        self.targetLight = light
        self._createHelperLight()

    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 that covers the
        whole voxel grid. """
        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("giLightUVSize", 
            float(self.helperLight.shadowResolution) / self.pipeline.settings.shadowAtlasSize)
        self._updateGridPos()

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


        # Create the voxelize pass which is used to voxelize the scene from
        # several directions
        self.voxelizePass = VoxelizePass()
        self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution)
        self.voxelizePass.setVoxelGridSize(self.voxelGridSizeWS)
        self.voxelizePass.initVoxelStorage()
        self.pipeline.getRenderPassManager().registerPass(self.voxelizePass)

        # Create 3D Texture which is a copy of the voxel generation grid but
        # stable, as the generation grid is updated part by part and that would 
        # lead to flickering
        self.voxelStableTex = Texture("VoxelsStable")
        self.voxelStableTex.setup3dTexture(self.voxelGridResolution.x, self.voxelGridResolution.y, 
                                            self.voxelGridResolution.z, Texture.TFloat, Texture.FRgba8)

        # Set appropriate filter types:
        # The stable texture has mipmaps, which are generated during the process.
        # This is required for cone tracing.
        self.voxelStableTex.setMagfilter(SamplerState.FTLinear)
        self.voxelStableTex.setMinfilter(SamplerState.FTLinearMipmapLinear)
        self.voxelStableTex.setWrapU(SamplerState.WMBorderColor)
        self.voxelStableTex.setWrapV(SamplerState.WMBorderColor)
        self.voxelStableTex.setWrapW(SamplerState.WMBorderColor)
        self.voxelStableTex.setBorderColor(Vec4(0,0,0,0))

        MemoryMonitor.addTexture("Voxel Grid Texture", self.voxelStableTex)

        # Setups the render target to convert the voxel grid
        self.convertBuffer = RenderTarget("VoxelConvertBuffer")
        self.convertBuffer.setSize(self.voxelGridResolution.x, self.voxelGridResolution.y)
        self.convertBuffer.setColorWrite(False)
        # self.convertBuffer.addColorTexture()
        self.convertBuffer.prepareOffscreenBuffer()
        self.convertBuffer.setShaderInput("src", self.voxelizePass.getVoxelTex())
        self.convertBuffer.setShaderInput("dest", self.voxelStableTex)
        self.convertBuffer.setActive(False)

        # Store the frame index, we need that to decide which step we are currently
        # doing
        self.frameIndex = 0


        # Create the various render targets to generate the mipmaps of the stable voxel grid
        self.mipmapTargets = []
        computeSize = LVecBase3i(self.voxelGridResolution)
        currentMipmap = 0
        while computeSize.z > 1:
            computeSize /= 2
            target = RenderTarget("GIMiplevel" + str(currentMipmap))
            target.setSize(computeSize.x, computeSize.y)
            target.setColorWrite(False)
            # target.addColorTexture()
            target.prepareOffscreenBuffer()
            target.setActive(False)
            target.setShaderInput("sourceMipmap", currentMipmap)
            target.setShaderInput("source", self.voxelStableTex)
            target.setShaderInput("dest", self.voxelStableTex, False, True, -1, currentMipmap + 1)
            self.mipmapTargets.append(target)
            currentMipmap += 1


        # Create the final gi pass
        self.finalPass = GlobalIlluminationPass()
        self.pipeline.getRenderPassManager().registerPass(self.finalPass)
        self.pipeline.getRenderPassManager().registerDynamicVariable("giVoxelGridData", self.bindTo)

    def _createConvertShader(self):
        """ Loads the shader for converting the voxel grid """
        shader = Shader.load(Shader.SLGLSL, 
            "Shader/DefaultPostProcess.vertex", "Shader/GI/ConvertGrid.fragment")
        self.convertBuffer.setShader(shader)

    def _createGenerateMipmapsShader(self):
        """ Loads the shader for generating the voxel grid mipmaps """
        computeSizeZ = self.voxelGridResolution.z
        for child in self.mipmapTargets:
            computeSizeZ /= 2
            shader = Shader.load(Shader.SLGLSL, 
                "Shader/DefaultPostProcess.vertex", 
                "Shader/GI/GenerateMipmaps/" + str(computeSizeZ) + ".fragment")
            child.setShader(shader)

    def reloadShader(self):
        """ Reloads all shaders and updates the voxelization camera state aswell """
        self._createGenerateMipmapsShader()
        self._createConvertShader()

    def _updateGridPos(self):
        """ Computes the new center of the voxel grid. The center pos is also
        snapped, to avoid flickering. """

        # It is important that the grid is snapped, otherwise it will flicker 
        # while the camera moves. When using a snap of 32, everything until
        # the log2(32) = 5th mipmap is stable. 
        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 update(self):
        """ Processes the gi, this method is called every frame """

        # With no light, there is no gi
        if self.targetLight is None:
            self.error("The GI cannot work without a directional target light! Set one "
                "with renderPipeline.setGILightSource(directionalLight) first!")
            return

        # Fetch current light direction
        direction = self.targetLight.getDirection()

        if self.frameIndex == 0:
            # Step 1: Voxelize scene from the x-Axis
            for child in self.mipmapTargets:
                child.setActive(False)

            self.convertBuffer.setActive(False)

            # Clear the old data in generation texture 
            self.voxelizePass.clearGrid()
            self.voxelizePass.voxelizeSceneFromDirection(self.gridPos, "x")

            # Set required inputs
            self.ptaLightUVStart[0] = self.helperLight.shadowSources[0].getAtlasPos()
            self.ptaLightMVP[0] = self.helperLight.shadowSources[0].mvp
            self.ptaVoxelGridStart[0] = self.gridPos - self.voxelGridSizeWS
            self.ptaVoxelGridEnd[0] = self.gridPos + self.voxelGridSizeWS
            self.ptaLightDirection[0] = direction

        elif self.frameIndex == 1:

            # Step 2: Voxelize scene from the y-Axis
            self.voxelizePass.voxelizeSceneFromDirection(self.gridPos, "y")

        elif self.frameIndex == 2:

            # Step 3: Voxelize the scene from the z-Axis 
            self.voxelizePass.voxelizeSceneFromDirection(self.gridPos, "z")
            
            # Update helper light, so that it is at the right position when Step 1
            # starts again 
            self.helperLight.setPos(self.gridPos)
            self.helperLight.setDirection(direction)

        elif self.frameIndex == 3:

            # Step 4: Extract voxel grid and generate mipmaps
            self.voxelizePass.setActive(False)
            self.convertBuffer.setActive(True)

            for child in self.mipmapTargets:
                child.setActive(True)

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

        # Increase frame index
        self.frameIndex += 1
        self.frameIndex = self.frameIndex % 4

    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)
Ejemplo n.º 34
0
class Antialiasing(DebugObject):
    def __init__(self):
        DebugObject.__init__(self, "Antialiasing")
        self._colorTexture = None
        self._depthTexture = None

    def setDepthTexture(self, tex):
        self._depthTexture = tex

    def setColorTexture(self, tex):
        self._colorTexture = tex

    def setup(self):
        #  1. The first step is to create two RGBA temporal render targets for holding
        #|edgesTex| and |blendTex|.
        self._setupEdgesBuffer()
        self._setupBlendBuffer()
        self._setupNeighborBuffer()

        self.reloadShader()

        #  2. Both temporal render targets |edgesTex| and |blendTex| must be cleared
        #     each frame. Do not forget to clear the alpha channel!

        self._edgesBuffer.setClearColor()
        self._blendBuffer.setClearColor()

        #  3. The next step is loading the two supporting precalculated textures,
        #     'areaTex' and 'searchTex'. You'll find them in the 'Textures' folder as
        #     C++ headers, and also as regular DDS files. They'll be needed for the
        #     'SMAABlendingWeightCalculation' pass.
        self.areaTex = loader.loadTexture("Data/Antialiasing/AreaTexGL.png")
        self.searchTex = loader.loadTexture(
            "Data/Antialiasing/SearchTexGL.png")

        #  4. All samplers must be set to linear filtering and clamp.
        for sampler in [
                self.areaTex, self.searchTex,
                self._edgesBuffer.getColorTexture(),
                self._blendBuffer.getColorTexture()
        ]:
            sampler.setMinfilter(Texture.FTLinear)
            sampler.setMagfilter(Texture.FTLinear)
            sampler.setWrapU(Texture.WMClamp)
            sampler.setWrapV(Texture.WMClamp)

        self._edgesBuffer.setShaderInput("colorTex", self._colorTexture)
        # self._edgesBuffer.setShaderInput("depthTex", self._depthTexture)

        self._blendBuffer.setShaderInput("edgesTex",
                                         self._edgesBuffer.getColorTexture())
        self._blendBuffer.setShaderInput("areaTex", self.areaTex)
        self._blendBuffer.setShaderInput("searchTex", self.searchTex)

        self._neighborBuffer.setShaderInput("colorTex", self._colorTexture)
        self._neighborBuffer.setShaderInput(
            "blendTex", self._blendBuffer.getColorTexture())

    def reloadShader(self):
        edgeShader = BetterShader.load("Shader/SMAA-EdgeDetection.vertex",
                                       "Shader/SMAA-EdgeDetection.fragment")
        self._edgesBuffer.setShader(edgeShader)

        weightsShader = BetterShader.load(
            "Shader/SMAA-BlendingWeights.vertex",
            "Shader/SMAA-BlendingWeights.fragment")
        self._blendBuffer.setShader(weightsShader)

        neighborShader = BetterShader.load("Shader/SMAA-Neighbors.vertex",
                                           "Shader/SMAA-Neighbors.fragment")
        self._neighborBuffer.setShader(neighborShader)

    def getFirstBuffer(self):
        return self._edgesBuffer

    def getResultTexture(self):
        return self._neighborBuffer.getColorTexture()

    def _setupEdgesBuffer(self):
        self._edgesBuffer = RenderTarget("SMAA-Edges")
        self._edgesBuffer.addRenderTexture(RenderTargetType.Color)
        # self._edgesBuffer.setColorBits(16)
        self._edgesBuffer.prepareOffscreenBuffer()

    def _setupBlendBuffer(self):
        self._blendBuffer = RenderTarget("SMAA-Blend")
        self._blendBuffer.addRenderTexture(RenderTargetType.Color)
        # self._blendBuffer.setColorBits(16)
        self._blendBuffer.prepareOffscreenBuffer()

    def _setupNeighborBuffer(self):
        self._neighborBuffer = RenderTarget("SMAA-Neighbors")
        self._neighborBuffer.addRenderTexture(RenderTargetType.Color)
        # self._blendBuffer.setColorBits(16)
        self._neighborBuffer.prepareOffscreenBuffer()
    def setup(self):
        """ Setups everything for the GI to work """
        assert(self.distributionSteps % 2 == 0)

        self._createDebugTexts()

        self.pipeline.getRenderPassManager().registerDefine("USE_GLOBAL_ILLUMINATION", 1)
        self.pipeline.getRenderPassManager().registerDefine("GI_SLIDE_COUNT", self.slideCount)
        self.pipeline.getRenderPassManager().registerDefine("GI_QUALITY_LEVEL", self.qualityLevelIndex)

        # make the grid resolution a constant
        self.pipeline.getRenderPassManager().registerDefine("GI_GRID_RESOLUTION", self.voxelGridResolution)

        self.taskManager.addTask(3, self.stepVoxelize)
        self.taskManager.addTask(self.distributionSteps, self.stepDistribute)

        # Create the voxelize pass which is used to voxelize the scene from
        # several directions
        self.voxelizePass = VoxelizePass(self.pipeline)
        self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution)
        self.voxelizePass.setVoxelGridSize(self.voxelGridSize)
        self.voxelizePass.setGridResolutionMultiplier(1)
        self.pipeline.getRenderPassManager().registerPass(self.voxelizePass)

        self.generationTextures = []

        # Create the buffers used to create the voxel grid
        for color in "rgb":
            tex = Texture("VoxelGeneration-" + color)
            tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TInt, Texture.FR32)
            tex.setClearColor(Vec4(0))
            self.generationTextures.append(tex)
            Globals.render.setShaderInput("voxelGenDest" + color.upper(), tex)
            
            MemoryMonitor.addTexture("VoxelGenerationTex-" + color.upper(), tex)

        self.bindTo(Globals.render, "giData")

        self.convertGridTarget = RenderTarget("ConvertGIGrid")
        self.convertGridTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount)

        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.addColorTexture()
        self.convertGridTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.getColorTexture().setMinfilter(Texture.FTNearest)
            self.convertGridTarget.getColorTexture().setMagfilter(Texture.FTNearest)

        self.clearGridTarget = RenderTarget("ClearGIGrid")
        self.clearGridTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.clearGridTarget.addColorTexture()
        self.clearGridTarget.prepareOffscreenBuffer()

        for idx, color in enumerate("rgb"):
            self.convertGridTarget.setShaderInput("voxelGenSrc" + color.upper(), self.generationTextures[idx])
            self.clearGridTarget.setShaderInput("voxelGenTex" + color.upper(), self.generationTextures[idx])


        # Create the data textures
        self.dataTextures = []
        self.directions = ["PosX", "NegX", "PosY", "NegY", "PosZ", "NegZ"]

        for i, direction in enumerate(self.directions):
            tex = Texture("GIDataTex" + direction)
            tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelDataTex-" + direction, tex)
            self.dataTextures.append(tex)
            self.pipeline.getRenderPassManager().registerStaticVariable("giVoxelData" + direction, tex)


        # Create ping / pong textures
        self.pingDataTextures = []
        self.pongDataTextures = []

        for i, direction in enumerate(self.directions):
            texPing = Texture("GIPingDataTex" + direction)
            texPing.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPingDataTex-" + direction, texPing)
            self.pingDataTextures.append(texPing)

            texPong = Texture("GIPongDataTex" + direction)
            texPong.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPongDataTex-" + direction, texPong)
            self.pongDataTextures.append(texPong)

            self.convertGridTarget.setShaderInput("voxelDataDest"+direction, self.pingDataTextures[i])
            # self.clearGridTarget.setShaderInput("voxelDataDest" + str(i), self.pongDataTextures[i])
        
        # Set texture wrap modes
        for tex in self.pingDataTextures + self.pongDataTextures + self.dataTextures + self.generationTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)
            tex.setWrapU(Texture.WMBorderColor)
            tex.setWrapV(Texture.WMBorderColor)
            tex.setWrapW(Texture.WMBorderColor)
            tex.setAnisotropicDegree(0)
            tex.setBorderColor(Vec4(0))

        for tex in self.dataTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)

        self.distributeTarget = RenderTarget("DistributeVoxels")
        self.distributeTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.addColorTexture()
        self.distributeTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.getColorTexture().setMinfilter(Texture.FTNearest)
            self.distributeTarget.getColorTexture().setMagfilter(Texture.FTNearest)

        self.distributeTarget.setShaderInput("isLastStep", False)

        # Create solidness texture
        self.voxelSolidTex = Texture("GIDataSolidTex")
        self.voxelSolidTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16)
        self.convertGridTarget.setShaderInput("voxelSolidDest", self.voxelSolidTex)
        self.distributeTarget.setShaderInput("voxelSolidTex", self.voxelSolidTex)
        MemoryMonitor.addTexture("VoxelSolidTex", self.voxelSolidTex)

        self.voxelSolidStableTex = Texture("GIDataSolidStableTex")
        self.voxelSolidStableTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16)

        self.distributeTarget.setShaderInput("voxelSolidWriteTex", self.voxelSolidStableTex)
        self.pipeline.getRenderPassManager().registerStaticVariable("giVoxelSolidTex", self.voxelSolidStableTex)





        # Create the final gi pass
        self.finalPass = GlobalIlluminationPass()
        self.pipeline.getRenderPassManager().registerPass(self.finalPass)
        self.pipeline.getRenderPassManager().registerDynamicVariable("giData", self.bindTo)
        self.pipeline.getRenderPassManager().registerStaticVariable("giReadyState", self.readyStateFlag)


        # Visualize voxels
        if False:
            self.voxelCube = loader.loadModel("Box")
            self.voxelCube.reparentTo(render)
            # self.voxelCube.setTwoSided(True)
            self.voxelCube.node().setFinal(True)
            self.voxelCube.node().setBounds(OmniBoundingVolume())
            self.voxelCube.setInstanceCount(self.voxelGridResolution**3)
            # self.voxelCube.hide()
            self.bindTo(self.voxelCube, "giData")
            
            for i in xrange(5):
                self.voxelCube.setShaderInput("giDataTex" + str(i), self.pingDataTextures[i])

        self.disableTargets()
Ejemplo n.º 36
0
class RenderingPipeline(DebugObject):
    """ This is the core class, driving all other classes. To use this
    pipeline, your code has to call this *after* the initialization of ShowBase:

        renderPipeline = RenderingPipeline()
        renderPipeline.loadSettings("pipeline.ini")
        renderPipeline.create()

    The pipeline will setup all required buffers, tasks and shaders itself. To
    add lights, see the documentation of LightManager.

    How it works:

    You can see an example buffer view at http://i.imgur.com/mZK6TVj.png

    The pipeline first renders all normal objects (parented to render) into a
    buffer, using multiple render targets. These buffers store normals,
    position and material properties. Your shaders have to output these
    values, but there is a handy api, just look at
    Shaders/DefaultObjectShader.fragment.

    After that, the pipeline splits the screen into tiles, typically of the
    size 32x32. For each tile, it computes which lights affect which tile,
    called Tiled Deferred Shading. This is written to a buffer.

    The next step is applying the lighting. This is done at half window
    resolution only, using Temporal Reprojection. I don't aim to explain
    Temporal Reprojection here, but basically, I render only each second
    pixel each frame. This is simply for performance.

    The lighting pass iterates through the list of lights per tile, and
    applies both lighting and shadows to each pixel, using the material
    information from the previous rendered buffers.

    After the lighting pass, a combiner pass combines both the current
    frame and the last frame, this is required because of
    Temporal Reprojection.

    At this step, we already have a frame we could display. In the next passes,
    only anti-aliasing and post-processing effects like motion blur are added.

    In the meantime, the LightManager builds a list of ShadowSources which
    need an update. It creates a scene render and renders the scene from the
    view of the shadow sources to the global shadow atlas. There are a limited
    amount of shadow updates per frame available, and the updates are stored
    in a queue. So when displaying many shadow-lights, not each shadowmap is
    update each frame. The reason is, again, performance. When you need a
    custom shadow caster shader, e.g. for alpha blending, you should use the
    Shader/DefaultShaowCaster.* as prefab.

    """
    def __init__(self, showbase):
        """ Creates a new pipeline """
        DebugObject.__init__(self, "RenderingPipeline")
        self.showbase = showbase
        self.settings = None
        self.mountManager = MountManager()

    def getMountManager(self):
        """ Returns the mount manager. You can use this to set the
        write directory and base path """
        return self.mountManager

    def loadSettings(self, filename):
        """ Loads the pipeline settings from an ini file """
        self.settings = PipelineSettingsManager()
        self.settings.loadFromFile(filename)

    def getSettings(self):
        """ Returns the current pipeline settings """
        return self.settings

    def create(self):
        """ Creates this pipeline """

        self.debug("Setting up render pipeline")

        if self.settings is None:
            self.error("You have to call loadSettings first!")
            return

        self.debug("Analyzing system ..")
        SystemAnalyzer.analyze()

        self.debug("Checking required Panda3D version ..")
        SystemAnalyzer.checkPandaVersionOutOfDate(01, 12, 2014)

        # Mount everything first
        self.mountManager.mount()

        # Store globals, as cython can't handle them
        self.debug("Setting up globals")
        Globals.load(self.showbase)
        Globals.font = loader.loadFont("Data/Font/SourceSansPro-Semibold.otf")
        Globals.font.setPixelsPerUnit(25)

        # Setting up shader loading
        BetterShader._DumpShaders = self.settings.dumpGeneratedShaders

        # We use PTA's for shader inputs, because that's faster than
        # using setShaderInput
        self.temporalProjXOffs = PTAInt.emptyArray(1)
        self.cameraPosition = PTAVecBase3f.emptyArray(1)
        self.motionBlurFactor = PTAFloat.emptyArray(1)
        self.lastMVP = PTALMatrix4f.emptyArray(1)
        self.currentMVP = PTALMatrix4f.emptyArray(1)
        self.currentShiftIndex = PTAInt.emptyArray(1)

        # Initialize variables
        self.camera = self.showbase.cam
        self.size = self._getSize()
        self.cullBounds = None

        # For the temporal reprojection it is important that the window width
        # is a multiple of 2
        if self.settings.enableTemporalReprojection and self.size.x % 2 == 1:
            self.error(
                "The window has to have a width which is a multiple of 2 "
                "(Current: ", self.showbase.win.getXSize(), ")")
            self.error(
                "I'll correct that for you, but next time pass the correct "
                "window size!")

            wp = WindowProperties()
            wp.setSize(self.showbase.win.getXSize() + 1,
                       self.showbase.win.getYSize())
            self.showbase.win.requestProperties(wp)
            self.showbase.graphicsEngine.openWindows()

            # Get new size
            self.size = self._getSize()

        # Debug variables to disable specific features
        self.haveLightingPass = True

        # haveCombiner can only be true when haveLightingPass is enabled
        self.haveCombiner = True
        self.haveMRT = True

        # Not as good as I want it, so disabled. I'll work on it.
        self.blurEnabled = False

        self.debug("Window size is", self.size.x, "x", self.size.y)

        self.showbase.camLens.setNearFar(0.1, 50000)
        self.showbase.camLens.setFov(90)

        self.showbase.win.setClearColor(Vec4(1.0, 0.0, 1.0, 1.0))

        # Create GI handler
        if self.settings.enableGlobalIllumination:
            self._setupGlobalIllumination()

        # Create occlusion handler
        self._setupOcclusion()

        if self.settings.displayOnscreenDebugger:
            self.guiManager = PipelineGuiManager(self)
            self.guiManager.setup()

        # Generate auto-configuration for shaders
        self._generateShaderConfiguration()

        # Create light manager, which handles lighting + shadows
        if self.haveLightingPass:
            self.lightManager = LightManager(self)

        self.patchSize = LVecBase2i(self.settings.computePatchSizeX,
                                    self.settings.computePatchSizeY)

        # Create separate scene graphs. The deferred graph is render
        self.forwardScene = NodePath("Forward-Rendering")
        self.transparencyScene = NodePath("Transparency-Rendering")
        self.transparencyScene.setBin("transparent", 30)

        # We need no transparency (we store other information in the alpha
        # channel)
        self.showbase.render.setAttrib(
            TransparencyAttrib.make(TransparencyAttrib.MNone), 100)

        # Now create deferred render buffers
        self._makeDeferredTargets()

        # Create the target which constructs the view-space normals and
        # position from world-space position
        if self.occlusion.requiresViewSpacePosNrm():
            self._createNormalPrecomputeBuffer()

        if self.settings.enableGlobalIllumination:
            self._creatGIPrecomputeBuffer()

        # Setup the buffers for lighting
        self._createLightingPipeline()

        # Setup combiner for temporal reprojetion
        if self.haveCombiner and self.settings.enableTemporalReprojection:
            self._createCombiner()

        if self.occlusion.requiresBlurring():
            self._createOcclusionBlurBuffer()

        self._setupAntialiasing()

        if self.blurEnabled:
            self._createDofStorage()
            self._createBlurBuffer()

        # Not sure why it has to be 0.25. But that leads to the best result
        aspect = float(self.size.y) / self.size.x
        self.onePixelShift = Vec2(0.125 / self.size.x, 0.125 / self.size.y /
                                  aspect) * self.settings.jitterAmount

        # Annoying that Vec2 has no multliply-operator for non-floats
        multiplyVec2 = lambda a, b: Vec2(a.x * b.x, a.y * b.y)

        if self.antialias.requiresJittering():
            self.pixelShifts = [
                multiplyVec2(self.onePixelShift, Vec2(-0.25, 0.25)),
                multiplyVec2(self.onePixelShift, Vec2(0.25, -0.25))
            ]
        else:
            self.pixelShifts = [Vec2(0), Vec2(0)]
        self.currentPixelShift = PTAVecBase2f.emptyArray(1)
        self.lastPixelShift = PTAVecBase2f.emptyArray(1)

        self._setupFinalPass()
        self._setShaderInputs()

        # Give the gui a hint when the pipeline is done loading
        if self.settings.displayOnscreenDebugger:
            self.guiManager.onPipelineLoaded()

        # add update task
        self._attachUpdateTask()

    def getForwardScene(self):
        """ Reparent objects to this scene to use forward rendering.
        Objects in this scene will directly get rendered, with no
        lighting etc. applied. """
        return self.forwardScene

    def getTransparentScene(self):
        """ Reparent objects to this scene to allow this objects to
        have transparency. Objects in this scene will get directly
        rendered and no lighting will get applied. """
        return self.transparencyScene

    def _createCombiner(self):
        """ Creates the target which combines the result from the
        lighting computation and last frame together
        (Temporal Reprojection) """
        self.combiner = RenderTarget("Combine-Temporal")
        self.combiner.addColorTexture()
        self.combiner.setColorBits(16)
        self.combiner.prepareOffscreenBuffer()
        self._setCombinerShader()

    def _setupGlobalIllumination(self):
        """ Creates the GI handler """
        self.globalIllum = GlobalIllumination(self)
        self.globalIllum.setup()

    def _setupAntialiasing(self):
        """ Creates the antialiasing technique """
        technique = self.settings.antialiasingTechnique
        self.debug("Creating antialiasing handler for", technique)

        if technique == "None":
            self.antialias = AntialiasingTechniqueNone()
        elif technique == "SMAA":
            self.antialias = AntialiasingTechniqueSMAA()
        elif technique == "FXAA":
            self.antialias = AntialiasingTechniqueFXAA()
        else:
            self.error("Unkown antialiasing technique", technique,
                       "-> using None:")
            self.antialias = AntialiasingTechniqueNone()

        if self.occlusion.requiresBlurring():
            self.antialias.setColorTexture(
                self.blurOcclusionH.getColorTexture())
        else:
            if self.haveCombiner and self.settings.enableTemporalReprojection:
                self.antialias.setColorTexture(self.combiner.getColorTexture())
            else:
                self.antialias.setColorTexture(
                    self.lightingComputeContainer.getColorTexture())

        self.antialias.setDepthTexture(self.deferredTarget.getDepthTexture())
        self.antialias.setVelocityTexture(self.deferredTarget.getAuxTexture(1))
        self.antialias.setup()

    def _setupOcclusion(self):
        """ Creates the occlusion technique """
        technique = self.settings.occlusionTechnique
        self.debug("Creating occlusion handle for", technique)

        if technique == "None":
            self.occlusion = AmbientOcclusionTechniqueNone()
        elif technique == "SAO":
            self.occlusion = AmbientOcclusionTechniqueSAO()
        else:
            self.error("Unkown occlusion technique:", technique)
            self.occlusion = AmbientOcclusionTechniqueNone()

    def _makeDeferredTargets(self):
        """ Creates the multi-render-target """
        self.debug("Creating deferred targets")
        self.deferredTarget = RenderTarget("DeferredTarget")
        self.deferredTarget.addColorAndDepth()

        if self.haveMRT:
            self.deferredTarget.addAuxTextures(3)
            self.deferredTarget.setAuxBits(16)
            self.deferredTarget.setColorBits(32)
            self.deferredTarget.setDepthBits(32)

        self.deferredTarget.prepareSceneRender()

    def _setupFinalPass(self):
        """ Setups the final pass which applies motion blur and so on """
        # Set wrap for motion blur
        colorTex = self.antialias.getResultTexture()
        colorTex.setWrapU(Texture.WMClamp)
        colorTex.setWrapV(Texture.WMClamp)
        self._setFinalPassShader()

    def _makeLightPerTileStorage(self):
        """ Creates a texture to store the lights per tile into. Should
        get replaced with ssbos later """
        storageSizeX = self.precomputeSize.x * 8
        storageSizeY = self.precomputeSize.y * 8

        self.debug("Creating per tile storage of size", storageSizeX, "x",
                   storageSizeY)

        self.lightPerTileStorage = Texture("LightsPerTile")
        self.lightPerTileStorage.setup2dTexture(storageSizeX, storageSizeY,
                                                Texture.TUnsignedShort,
                                                Texture.FR32i)
        self.lightPerTileStorage.setMinfilter(Texture.FTNearest)
        self.lightPerTileStorage.setMagfilter(Texture.FTNearest)

    def _creatGIPrecomputeBuffer(self):
        """ Creates the half-resolution buffer which computes gi and gi
        reflections. We use half-res for performance """

        self.giPrecomputeBuffer = RenderTarget("GICompute")
        self.giPrecomputeBuffer.setSize(self.size.x / 2, self.size.y / 2)
        self.giPrecomputeBuffer.addColorTexture()
        self.giPrecomputeBuffer.addAuxTextures(1)
        self.giPrecomputeBuffer.setColorBits(16)
        self.giPrecomputeBuffer.prepareOffscreenBuffer()

    def _createLightingPipeline(self):
        """ Creates the lighting pipeline, including shadow handling """

        if not self.haveLightingPass:
            self.debug("Skipping lighting pipeline")
            return

        self.debug("Creating lighting pipeline ..")

        # size has to be a multiple of the compute unit size
        # but still has to cover the whole screen
        sizeX = int(math.ceil(float(self.size.x) / self.patchSize.x))
        sizeY = int(math.ceil(float(self.size.y) / self.patchSize.y))

        self.precomputeSize = LVecBase2i(sizeX, sizeY)

        self.debug("Batch size =", sizeX, "x", sizeY, "Actual Buffer size=",
                   int(sizeX * self.patchSize.x), "x",
                   int(sizeY * self.patchSize.y))

        self._makeLightPerTileStorage()

        # Create a buffer which computes which light affects which tile
        self._makeLightBoundsComputationBuffer(sizeX, sizeY)

        # Create a buffer which applies the lighting
        self._makeLightingComputeBuffer()

        # Register for light manager
        self.lightManager.setLightingComputator(self.lightingComputeContainer)
        self.lightManager.setLightingCuller(self.lightBoundsComputeBuff)

        self._loadFallbackCubemap()
        self._loadLookupCubemap()

    def _setShaderInputs(self):
        """ Sets most of the required shader inputs to the targets """

        # Shader inputs for the light-culling pass
        if self.haveLightingPass:
            self.lightBoundsComputeBuff.setShaderInput(
                "destination", self.lightPerTileStorage)
            self.lightBoundsComputeBuff.setShaderInput(
                "depth", self.deferredTarget.getDepthTexture())
            self.lightBoundsComputeBuff.setShaderInput("mainCam",
                                                       self.showbase.cam)
            self.lightBoundsComputeBuff.setShaderInput("mainRender",
                                                       self.showbase.render)

            # Shader inputs for the light-applying pass
            self.lightingComputeContainer.setShaderInput(
                "data0", self.deferredTarget.getColorTexture())
            self.lightingComputeContainer.setShaderInput(
                "data1", self.deferredTarget.getAuxTexture(0))
            self.lightingComputeContainer.setShaderInput(
                "data2", self.deferredTarget.getAuxTexture(1))
            self.lightingComputeContainer.setShaderInput(
                "data3", self.deferredTarget.getAuxTexture(2))

            self.lightingComputeContainer.setShaderInput(
                "depth", self.deferredTarget.getDepthTexture())
            self.lightingComputeContainer.setShaderInput(
                "mainCam", self.showbase.cam)
            self.lightingComputeContainer.setShaderInput(
                "mainRender", self.showbase.render)

            if self.occlusion.requiresViewSpacePosNrm():
                self.lightingComputeContainer.setShaderInput(
                    "viewSpaceNormals",
                    self.normalPrecompute.getColorTexture())
                self.lightingComputeContainer.setShaderInput(
                    "viewSpacePosition",
                    self.normalPrecompute.getAuxTexture(0))

            self.lightingComputeContainer.setShaderInput(
                "shadowAtlas", self.lightManager.getAtlasTex())

            if self.settings.useHardwarePCF:
                self.lightingComputeContainer.setShaderInput(
                    "shadowAtlasPCF", self.lightManager.getAtlasTex(),
                    self.lightManager.getPCFSampleState())

            self.lightingComputeContainer.setShaderInput(
                "destination", self.lightingComputeCombinedTex)
            self.lightingComputeContainer.setShaderInput(
                "temporalProjXOffs", self.temporalProjXOffs)
            self.lightingComputeContainer.setShaderInput(
                "cameraPosition", self.cameraPosition)

            self.lightingComputeContainer.setShaderInput(
                "noiseTexture",
                self.showbase.loader.loadTexture(
                    "Data/Occlusion/noise4x4.png"))
            self.lightingComputeContainer.setShaderInput(
                "lightsPerTile", self.lightPerTileStorage)

            if self.settings.enableGlobalIllumination:
                self.lightingComputeContainer.setShaderInput(
                    "giDiffuseTex", self.giPrecomputeBuffer.getColorTexture())
                self.lightingComputeContainer.setShaderInput(
                    "giReflectionTex",
                    self.giPrecomputeBuffer.getAuxTexture(0))

        # Shader inputs for the occlusion blur passes
        if self.occlusion.requiresBlurring() and self.haveCombiner:
            self.blurOcclusionH.setShaderInput(
                "colorTex", self.blurOcclusionV.getColorTexture())

            if self.settings.enableTemporalReprojection:
                self.blurOcclusionV.setShaderInput(
                    "colorTex", self.combiner.getColorTexture())
            else:
                self.blurOcclusionV.setShaderInput(
                    "colorTex",
                    self.lightingComputeContainer.getColorTexture())

            self.blurOcclusionH.setShaderInput(
                "normalTex", self.deferredTarget.getAuxTexture(0))
            self.blurOcclusionV.setShaderInput(
                "normalTex", self.deferredTarget.getAuxTexture(0))
            self.blurOcclusionH.setShaderInput(
                "normalsView", self.normalPrecompute.getAuxTexture(0))
            self.blurOcclusionV.setShaderInput(
                "normalsView", self.normalPrecompute.getAuxTexture(0))

        # Shader inputs for the blur passes
        if self.blurEnabled:
            self.blurColorH.setShaderInput("dofStorage", self.dofStorage)
            self.blurColorV.setShaderInput("dofStorage", self.dofStorage)
            self.blurColorH.setShaderInput("colorTex",
                                           self.antialias.getResultTexture())
            self.blurColorH.setShaderInput(
                "depthTex", self.deferredTarget.getDepthTexture())
            self.blurColorV.setShaderInput("colorTex",
                                           self.blurColorH.getColorTexture())

        # Shader inputs for the temporal reprojection
        if self.haveCombiner and self.settings.enableTemporalReprojection:
            self.combiner.setShaderInput(
                "currentComputation",
                self.lightingComputeContainer.getColorTexture())
            self.combiner.setShaderInput("lastFrame",
                                         self.lightingComputeCombinedTex)
            self.combiner.setShaderInput("positionBuffer",
                                         self.deferredTarget.getColorTexture())
            self.combiner.setShaderInput("velocityBuffer",
                                         self.deferredTarget.getAuxTexture(1))
            self.combiner.setShaderInput("currentPixelShift",
                                         self.currentPixelShift)
            self.combiner.setShaderInput("lastPixelShift", self.lastPixelShift)

            if self.blurEnabled:
                self.combiner.setShaderInput("dofStorage", self.dofStorage)

            self.combiner.setShaderInput("depthTex",
                                         self.deferredTarget.getDepthTexture())
            self.combiner.setShaderInput("lastPosition",
                                         self.lastPositionBuffer)
            self.combiner.setShaderInput("temporalProjXOffs",
                                         self.temporalProjXOffs)
            self.combiner.setShaderInput("lastMVP", self.lastMVP)
            self.combiner.setShaderInput("cameraPosition", self.cameraPosition)
            self.combiner.setShaderInput("currentMVP", self.lastMVP)

        # Shader inputs for the final pass
        if self.blurEnabled:
            self.deferredTarget.setShaderInput(
                "colorTex", self.blurColorV.getColorTexture())
        else:
            self.deferredTarget.setShaderInput(
                "colorTex", self.antialias.getResultTexture())

        if self.occlusion.requiresBlurring():
            self.normalPrecompute.setShaderInput(
                "positionTex", self.deferredTarget.getColorTexture())
            self.normalPrecompute.setShaderInput("mainCam", self.showbase.cam)
            self.normalPrecompute.setShaderInput("mainRender",
                                                 self.showbase.render)
            self.normalPrecompute.setShaderInput(
                "depthTex", self.deferredTarget.getDepthTexture())

        if self.haveMRT:
            self.deferredTarget.setShaderInput(
                "velocityTex", self.deferredTarget.getAuxTexture(1))

        self.deferredTarget.setShaderInput(
            "depthTex", self.deferredTarget.getDepthTexture())
        self.deferredTarget.setShaderInput("motionBlurFactor",
                                           self.motionBlurFactor)

        if self.haveLightingPass:
            self.deferredTarget.setShaderInput("lastFrame",
                                               self.lightingComputeCombinedTex)

        if self.haveCombiner and self.settings.enableTemporalReprojection:
            self.deferredTarget.setShaderInput("newFrame",
                                               self.combiner.getColorTexture())
            self.deferredTarget.setShaderInput("lastPosition",
                                               self.lastPositionBuffer)

            self.deferredTarget.setShaderInput("debugTex",
                                               self.combiner.getColorTexture())
        else:
            self.deferredTarget.setShaderInput(
                "debugTex", self.antialias.getResultTexture())

        self.deferredTarget.setShaderInput(
            "currentPosition", self.deferredTarget.getColorTexture())

        # Set last / current mvp handles
        self.showbase.render.setShaderInput("lastMVP", self.lastMVP)

        # Set GI inputs
        if self.settings.enableGlobalIllumination:
            self.globalIllum.bindTo(self.giPrecomputeBuffer, "giData")

            self.giPrecomputeBuffer.setShaderInput(
                "data0", self.deferredTarget.getColorTexture())
            self.giPrecomputeBuffer.setShaderInput(
                "data1", self.deferredTarget.getAuxTexture(0))
            self.giPrecomputeBuffer.setShaderInput(
                "data2", self.deferredTarget.getAuxTexture(1))
            self.giPrecomputeBuffer.setShaderInput(
                "data3", self.deferredTarget.getAuxTexture(2))
            self.giPrecomputeBuffer.setShaderInput("cameraPosition",
                                                   self.cameraPosition)

        # Finally, set shaders
        self.reloadShaders()

    def _loadFallbackCubemap(self):
        """ Loads the cubemap for image based lighting """
        print self.settings.defaultReflectionCubemap
        cubemap = self.showbase.loader.loadCubeMap(
            self.settings.defaultReflectionCubemap)
        cubemap.setMinfilter(Texture.FTLinearMipmapLinear)
        cubemap.setMagfilter(Texture.FTLinearMipmapLinear)
        cubemap.setFormat(Texture.F_srgb)
        print math.log(cubemap.getXSize(), 2)
        self.lightingComputeContainer.setShaderInput("fallbackCubemap",
                                                     cubemap)
        self.lightingComputeContainer.setShaderInput(
            "fallbackCubemapMipmaps", math.log(cubemap.getXSize(), 2))

    def _loadLookupCubemap(self):
        self.debug("Loading lookup cubemap")
        cubemap = self.showbase.loader.loadCubeMap(
            "Data/Cubemaps/DirectionLookup/#.png")
        cubemap.setMinfilter(Texture.FTNearest)
        cubemap.setMagfilter(Texture.FTNearest)
        cubemap.setFormat(Texture.F_rgb8)
        self.lightingComputeContainer.setShaderInput("directionToFace",
                                                     cubemap)

    def _makeLightBoundsComputationBuffer(self, w, h):
        """ Creates the buffer which precomputes the lights per tile """
        self.debug("Creating light precomputation buffer of size", w, "x", h)
        self.lightBoundsComputeBuff = RenderTarget("ComputeLightTileBounds")
        self.lightBoundsComputeBuff.setSize(w, h)
        self.lightBoundsComputeBuff.setColorWrite(False)
        self.lightBoundsComputeBuff.prepareOffscreenBuffer()

    def _makeLightingComputeBuffer(self):
        """ Creates the buffer which applies the lighting """
        self.lightingComputeContainer = RenderTarget("ComputeLighting")

        if self.settings.enableTemporalReprojection:
            self.lightingComputeContainer.setSize(self.size.x / 2, self.size.y)
        else:
            self.lightingComputeContainer.setSize(self.size.x, self.size.y)

        self.lightingComputeContainer.addColorTexture()
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(self.size.x,
                                                       self.size.y,
                                                       Texture.TFloat,
                                                       Texture.FRgba8)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(self.size.x, self.size.y,
                                               Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)

    def _createOcclusionBlurBuffer(self):
        """ Creates the buffers needed to blur the occlusion """
        self.blurOcclusionV = RenderTarget("blurOcclusionVertical")
        self.blurOcclusionV.addColorTexture()
        self.blurOcclusionV.prepareOffscreenBuffer()

        self.blurOcclusionH = RenderTarget("blurOcclusionHorizontal")
        self.blurOcclusionH.addColorTexture()
        self.blurOcclusionH.prepareOffscreenBuffer()

        # Mipmaps for blur?
        # self.blurOcclusionV.getColorTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)
        # self.combiner.getColorTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)

    def _createBlurBuffer(self):
        """ Creates the buffers for the dof """
        self.blurColorV = RenderTarget("blurColorVertical")
        self.blurColorV.addColorTexture()
        self.blurColorV.prepareOffscreenBuffer()

        self.blurColorH = RenderTarget("blurColorHorizontal")
        self.blurColorH.addColorTexture()
        self.blurColorH.prepareOffscreenBuffer()

        # self.blurColorH.getColorTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)
        # self.antialias.getResultTexture().setMinfilter(
        #     Texture.FTLinearMipmapLinear)

    def _createNormalPrecomputeBuffer(self):
        """ Creates a buffer which reconstructs the normals and position
        from view-space """
        self.normalPrecompute = RenderTarget("PrecomputeNormals")
        self.normalPrecompute.addColorTexture()
        self.normalPrecompute.addAuxTextures(1)
        self.normalPrecompute.setColorBits(16)
        self.normalPrecompute.setAuxBits(16)
        self.normalPrecompute.prepareOffscreenBuffer()

    def _createDofStorage(self):
        """ Creates the texture where the dof factor is stored in, so we
        don't recompute it each pass """
        self.dofStorage = Texture("DOFStorage")
        self.dofStorage.setup2dTexture(self.size.x, self.size.y,
                                       Texture.TFloat, Texture.FRg16)

    def _setOcclusionBlurShader(self):
        """ Sets the shaders which blur the occlusion """
        blurVShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/BlurOcclusionVertical.fragment")
        blurHShader = BetterShader.load(
            "Shader/DefaultPostProcess.vertex",
            "Shader/BlurOcclusionHorizontal.fragment")
        self.blurOcclusionV.setShader(blurVShader)
        self.blurOcclusionH.setShader(blurHShader)

    def _setGIComputeShader(self):
        """ Sets the shader which computes the GI """
        giShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                     "Shader/ComputeGI.fragment")
        self.giPrecomputeBuffer.setShader(giShader)

    def _setBlurShader(self):
        """ Sets the shaders which blur the color """
        blurVShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                        "Shader/BlurVertical.fragment")
        blurHShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                        "Shader/BlurHorizontal.fragment")
        self.blurColorV.setShader(blurVShader)
        self.blurColorH.setShader(blurHShader)

    def _setLightingShader(self):
        """ Sets the shader which applies the light """
        lightShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                        "Shader/ApplyLighting.fragment")
        self.lightingComputeContainer.setShader(lightShader)

    def _setCombinerShader(self):
        """ Sets the shader which combines the lighting with the previous frame
        (temporal reprojection) """
        cShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                    "Shader/Combiner.fragment")
        self.combiner.setShader(cShader)

    def _setPositionComputationShader(self):
        """ Sets the shader which computes the lights per tile """
        pcShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                     "Shader/PrecomputeLights.fragment")
        self.lightBoundsComputeBuff.setShader(pcShader)

    def _setFinalPassShader(self):
        """ Sets the shader which computes the final frame,
        with motion blur and so on """
        fShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                    "Shader/Final.fragment")
        self.deferredTarget.setShader(fShader)

    def _getSize(self):
        """ Returns the window size. """
        return LVecBase2i(self.showbase.win.getXSize(),
                          self.showbase.win.getYSize())

    def reloadShaders(self):
        """ Reloads all shaders """

        if self.haveLightingPass:
            self.lightManager.debugReloadShader()
            self._setPositionComputationShader()
            self._setLightingShader()

        if self.haveCombiner and self.settings.enableTemporalReprojection:
            self._setCombinerShader()

        self._setFinalPassShader()

        if self.settings.enableGlobalIllumination:
            self._setGIComputeShader()

        if self.occlusion.requiresBlurring():
            self._setOcclusionBlurShader()

        if self.blurEnabled:
            self._setBlurShader()

        if self.occlusion.requiresViewSpacePosNrm():
            self._setNormalExtractShader()

        self.antialias.reloadShader()
        if self.settings.enableGlobalIllumination:
            self.globalIllum.reloadShader()

    def _setNormalExtractShader(self):
        """ Sets the shader which constructs the normals from position """
        npShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                     "Shader/ExtractNormals.fragment")
        self.normalPrecompute.setShader(npShader)

    def _attachUpdateTask(self):
        """ Attaches the update tasks to the showbase """

        self.showbase.addTask(self._preRenderCallback,
                              "RP_BeforeRender",
                              sort=-5000)

        self.showbase.addTask(self._update, "RP_Update", sort=-10)

        if self.haveLightingPass:
            self.showbase.addTask(self._updateLights,
                                  "RP_UpdateLights",
                                  sort=-9)
            self.showbase.addTask(self._updateShadows,
                                  "RP_UpdateShadows",
                                  sort=-8)

            self.showbase.addTask(self._processShadowCallbacks,
                                  "RP_ShadowCallbacks",
                                  sort=-5)

        if self.settings.displayOnscreenDebugger:
            self.showbase.addTask(self._updateGUI, "RP_UpdateGUI", sort=7)

        self.showbase.addTask(self._postRenderCallback,
                              "RP_AfterRender",
                              sort=5000)

    def _preRenderCallback(self, task=None):
        """ Called before rendering """

        if self.settings.enableGlobalIllumination:
            self.globalIllum.process()

        self.antialias.preRenderUpdate()

        if task is not None:
            return task.cont

    def _postRenderCallback(self, task=None):
        """ Called after rendering """

        self.antialias.postRenderUpdate()
        if task is not None:
            return task.cont

    def _computeCameraBounds(self):
        """ Computes the current camera bounds, i.e. for light culling """
        cameraBounds = self.camera.node().getLens().makeBounds()
        cameraBounds.xform(self.camera.getMat(self.showbase.render))
        return cameraBounds

    def _updateLights(self, task=None):
        """ Task which updates/culls the lights """
        self.lightManager.updateLights()

        if task is not None:
            return task.cont

    def _processShadowCallbacks(self, task=None):
        self.lightManager.processCallbacks()
        if task is not None:
            return task.cont

    def _updateShadows(self, task=None):
        """ Task which updates the shadow maps """
        self.lightManager.updateShadows()
        if task is not None:
            return task.cont

    def _updateGUI(self, task=None):
        """ Task which updates the onscreen gui debugger """
        self.guiManager.update()

        if task is not None:
            return task.cont

    def _update(self, task=None):
        """ Main update task """

        self.currentShiftIndex[0] = 1 - self.currentShiftIndex[0]

        currentFPS = 1.0 / self.showbase.taskMgr.globalClock.getDt()

        self.temporalProjXOffs[0] = 1 - self.temporalProjXOffs[0]
        self.cameraPosition[0] = self.showbase.cam.getPos(self.showbase.render)
        self.motionBlurFactor[0] = min(
            1.5, currentFPS / 60.0) * self.settings.motionBlurFactor

        self.cullBounds = self._computeCameraBounds()

        if self.haveLightingPass:
            self.lightManager.setCullBounds(self.cullBounds)

        self.lastMVP[0] = self.currentMVP[0]
        self.currentMVP[0] = self._computeMVP()

        shift = self.pixelShifts[self.currentShiftIndex[0]]
        self.lastPixelShift[0] = self.currentPixelShift[0]
        self.currentPixelShift[0] = shift
        Globals.base.camLens.setFilmOffset(shift.x, shift.y)

        if task is not None:
            return task.cont

    def _computeMVP(self):
        """ Computes the current mvp. Actually, this is the
        worldViewProjectionMatrix, but for convience it's called mvp. """
        camLens = self.showbase.camLens
        projMat = Mat4.convertMat(
            CSYupRight,
            camLens.getCoordinateSystem()) * camLens.getProjectionMat()
        transformMat = TransformState.makeMat(
            Mat4.convertMat(
                self.showbase.win.getGsg().getInternalCoordinateSystem(),
                CSZupRight))
        modelViewMat = transformMat.invertCompose(
            self.showbase.render.getTransform(self.showbase.cam)).getMat()
        return UnalignedLMatrix4f(modelViewMat * projMat)

    def getLightManager(self):
        """ Returns a handle to the light manager """
        return self.lightManager

    def getDefaultObjectShader(self, tesselated=False):
        """ Returns the default shader for objects """

        if not tesselated:
            shader = BetterShader.load(
                "Shader/DefaultObjectShader/vertex.glsl",
                "Shader/DefaultObjectShader/fragment.glsl")
        else:
            self.warn("Tesselation is only experimental! Remember "
                      "to convert the geometry to patches first!")

            shader = BetterShader.load(
                "Shader/DefaultObjectShader/vertex.glsl",
                "Shader/DefaultObjectShader/fragment.glsl", "",
                "Shader/DefaultObjectShader/tesscontrol.glsl",
                "Shader/DefaultObjectShader/tesseval.glsl")

        return shader

    def _getDeferredBuffer(self):
        """ Returns a handle to the internal deferred target """
        return self.deferredTarget.getInternalBuffer()

    def addLight(self, light):
        """ Adds a light to the list of rendered lights """
        if self.haveLightingPass:
            self.lightManager.addLight(light)
        else:
            self.warn("Lighting is disabled, so addLight has no effect")

    def setScattering(self, scatteringModel):
        """ Sets a scattering model to use. Only has an effect if enableScattering
        is enabled """
        self.debug("Loading scattering model ..")
        if not self.settings.enableScattering:
            self.error("You cannot set a scattering model as scattering is not"
                       " enabled in your pipeline.ini!")
            return

        self.lightingComputeContainer.setShaderInput(
            "transmittanceSampler", scatteringModel.getTransmittanceResult())
        self.lightingComputeContainer.setShaderInput(
            "inscatterSampler", scatteringModel.getInscatterTexture())
        scatteringModel.bindTo(self.lightingComputeContainer,
                               "scatteringOptions")

    def enableDefaultEarthScattering(self):
        """ Adds a standard scattering model, representing the atmosphere of
        the earth. This is a shortcut for creating a Scattering instance and
        precomputing it """
        earthScattering = Scattering()

        scale = 1000000000
        earthScattering.setSettings({
            "atmosphereOffset":
            Vec3(0, 0, -(6360.0 + 9.5) * scale),
            "atmosphereScale":
            Vec3(scale)
        })
        earthScattering.precompute()
        self.setScattering(earthScattering)

    def setGILightSource(self, light):
        """ Sets the light source for the global illumination. The GI uses this
        light to shade the voxels, so this light is the only light which "casts"
        global illumination. When GI is disabled, this has no effect """

        if self.settings.enableGlobalIllumination:
            self.globalIllum.setTargetLight(light)

    def _generateShaderConfiguration(self):
        """ Genrates the global shader include which defines
        most values used in the shaders. """

        self.debug("(Re)Generating shader configuration")

        # Generate list of defines
        defines = []

        if self.settings.antialiasingTechnique == "SMAA":
            quality = self.settings.smaaQuality.upper()
            if quality in ["LOW", "MEDIUM", "HIGH", "ULTRA"]:
                defines.append(("SMAA_PRESET_" + quality, ""))
            else:
                self.error("Unrecognized SMAA quality:", quality)
                return

        defines.append(
            ("LIGHTING_COMPUTE_PATCH_SIZE_X", self.settings.computePatchSizeX))
        defines.append(
            ("LIGHTING_COMPUTE_PATCH_SIZE_Y", self.settings.computePatchSizeY))
        defines.append(("LIGHTING_MIN_MAX_DEPTH_ACCURACY",
                        self.settings.minMaxDepthAccuracy))

        if self.blurEnabled:
            defines.append(("USE_DOF", 1))

        if self.settings.useSimpleLighting:
            defines.append(("USE_SIMPLE_LIGHTING", 1))

        if self.settings.anyLightBoundCheck:
            defines.append(("LIGHTING_ANY_BOUND_CHECK", 1))

        if self.settings.accurateLightBoundCheck:
            defines.append(("LIGHTING_ACCURATE_BOUND_CHECK", 1))

        if self.settings.renderShadows:
            defines.append(("USE_SHADOWS", 1))

        defines.append(
            ("AMBIENT_CUBEMAP_SAMPLES", self.settings.ambientCubemapSamples))

        defines.append(
            ("SHADOW_MAP_ATLAS_SIZE", self.settings.shadowAtlasSize))
        defines.append(("SHADOW_MAX_UPDATES_PER_FRAME",
                        self.settings.maxShadowUpdatesPerFrame))
        defines.append(("SHADOW_GEOMETRY_MAX_VERTICES",
                        self.settings.maxShadowUpdatesPerFrame * 3))

        defines.append(("SHADOW_NUM_PCF_SAMPLES", self.settings.numPCFSamples))
        defines.append(("SHADOW_NUM_PCSS_SEARCH_SAMPLES",
                        self.settings.numPCSSSearchSamples))
        defines.append(("SHADOW_NUM_PCSS_FILTER_SAMPLES",
                        self.settings.numPCSSFilterSamples))

        defines.append(("SHADOW_PSSM_BORDER_PERCENTAGE",
                        self.settings.shadowCascadeBorderPercentage))

        if self.settings.useHardwarePCF:
            defines.append(("USE_HARDWARE_PCF", 1))

        defines.append(("WINDOW_WIDTH", self.size.x))
        defines.append(("WINDOW_HEIGHT", self.size.y))

        if self.settings.motionBlurEnabled:
            defines.append(("USE_MOTION_BLUR", 1))

        defines.append(
            ("MOTION_BLUR_SAMPLES", self.settings.motionBlurSamples))

        # Occlusion
        defines.append(
            ("OCCLUSION_TECHNIQUE_" + self.occlusion.getIncludeName(), 1))
        defines.append(("OCCLUSION_RADIUS", self.settings.occlusionRadius))
        defines.append(("OCCLUSION_STRENGTH", self.settings.occlusionStrength))
        defines.append(
            ("OCCLUSION_SAMPLES", self.settings.occlusionSampleCount))

        if self.settings.displayOnscreenDebugger:
            defines.append(("DEBUGGER_ACTIVE", 1))

            extraSettings = self.guiManager.getDefines()
            defines += extraSettings

        if self.settings.enableTemporalReprojection:
            defines.append(("USE_TEMPORAL_REPROJECTION", 1))

        if self.settings.enableGlobalIllumination:
            defines.append(("USE_GLOBAL_ILLUMINATION", 1))

        if self.settings.enableScattering:
            defines.append(("USE_SCATTERING", 1))

        # Pass near far
        defines.append(("CAMERA_NEAR", Globals.base.camLens.getNear()))
        defines.append(("CAMERA_FAR", Globals.base.camLens.getFar()))

        # Generate
        output = "// Autogenerated by RenderingPipeline.py\n"
        output += "// Do not edit! Your changes will be lost.\n\n"

        for key, value in defines:
            output += "#define " + key + " " + str(value) + "\n"

        # Try to write the file

        try:
            with open("PipelineTemp/ShaderAutoConfig.include", "w") as handle:
                handle.write(output)
        except Exception, msg:
            self.fatal(
                "Error writing shader autoconfig. Maybe no write-access?")
            return
class GlobalIllumination(DebugObject):

    """ This class handles the global illumination processing. To process the
    global illumination, the scene is first rasterized from 3 directions, and 
    a 3D voxel grid is created. After that, the mipmaps of the voxel grid are
    generated. The final shader then performs voxel cone tracing to compute 
    an ambient, diffuse and specular term.

    The gi is split over several frames to reduce the computation cost. Currently
    there are 5 different steps, split over 4 frames:

    Frame 1: 
        - Rasterize the scene from the x-axis

    Frame 2:
        - Rasterize the scene from the y-axis

    Frame 3: 
        - Rasterize the scene from the z-axis

    Frame 4:
        - Copy the generated temporary voxel grid into a stable voxel grid
        - Generate the mipmaps for that stable voxel grid using a gaussian filter

    In the final pass the stable voxel grid is sampled. The voxel tracing selects
    the mipmap depending on the cone size. This enables small scale details as well
    as blurry reflections and low-frequency ao / diffuse. For performance reasons,
    the final pass is executed at half window resolution and then bilateral upscaled.
    """

    QualityLevels = ["Low", "Medium", "High", "Ultra"]

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

        self.qualityLevel = self.pipeline.settings.giQualityLevel

        if self.qualityLevel not in self.QualityLevels:
            self.fatal("Unsupported gi quality level:" + self.qualityLevel)

        self.qualityLevelIndex = self.QualityLevels.index(self.qualityLevel)

        # Grid size in world space units
        self.voxelGridSize = self.pipeline.settings.giVoxelGridSize
        
        # Grid resolution in pixels
        self.voxelGridResolution = [32, 64, 128, 192][self.qualityLevelIndex]

        # Has to be a multiple of 2
        self.distributionSteps = [16, 30, 60, 90][self.qualityLevelIndex]
        self.slideCount = int(self.voxelGridResolution / 8) 
        self.slideVertCount = self.voxelGridResolution / self.slideCount       

        self.bounds = BoundingBox()
        self.renderCount = 0

        # Create the task manager
        self.taskManager = DistributedTaskManager()

        self.gridPosLive = PTALVecBase3f.emptyArray(1)
        self.gridPosTemp = PTALVecBase3f.emptyArray(1)

        # Store ready state
        self.readyStateFlag = PTAFloat.emptyArray(1)
        self.readyStateFlag[0] = 0

        self.frameIndex = 0
        self.steps = []

    def _createDebugTexts(self):
        """ Creates a debug overlay to show GI status """
        self.debugText = None
        self.buildingText = None

        if self.pipeline.settings.displayDebugStats:
            self.debugText = FastText(pos=Vec2(
                Globals.base.getAspectRatio() - 0.1, 0.88), rightAligned=True, color=Vec3(1, 1, 0), size=0.03)
            self.buildingText = FastText(pos=Vec2(-0.3, 0), rightAligned=False, color=Vec3(1, 1, 0), size=0.03)
            self.buildingText.setText("PREPARING GI, PLEASE BE PATIENT ....")

    def stepVoxelize(self, idx):
        
        # If we are at the beginning of the frame, compute the new grid position
        if idx == 0:
            self.gridPosTemp[0] = self._computeGridPos()
            # Clear voxel grid at the beginning
            # for tex in self.generationTextures:
                # tex.clearImage()

            self.clearGridTarget.setActive(True)

            if self.debugText is not None:
                self.debugText.setText("GI Grid Center: " + ", ".join(str(round(i, 2)) for i in self.gridPosTemp[0]) + " / GI Frame " + str(self.renderCount) )
            
            self.renderCount += 1

            if self.renderCount == 3:
                self.readyStateFlag[0] = 1.0
                if self.buildingText:
                    self.buildingText.remove()
                    self.buildingText = None

        self.voxelizePass.voxelizeSceneFromDirection(self.gridPosTemp[0], "xyz"[idx])


    def stepDistribute(self, idx):
        
        if idx == 0:

            skyBegin = 142.0
            skyInGrid = (skyBegin - self.gridPosTemp[0].z) / (2.0 * self.voxelGridSize)
            skyInGrid = int(skyInGrid * self.voxelGridResolution)
            self.convertGridTarget.setShaderInput("skyStartZ", skyInGrid)
            self.convertGridTarget.setActive(True)           

        self.distributeTarget.setActive(True)

        swap = idx % 2 == 0
        sources = self.pingDataTextures if swap else self.pongDataTextures
        dests = self.pongDataTextures if swap else self.pingDataTextures

        if idx == self.distributionSteps - 1:
            self.publishGrid()
            dests = self.dataTextures

        for i, direction in enumerate(self.directions):
            self.distributeTarget.setShaderInput("src" + direction, sources[i])
            self.distributeTarget.setShaderInput("dst" + direction, dests[i])

        # Only do the last blur-step on high+ quality, leads to artifacts otherwise
        # due to the low grid resolution
        if self.qualityLevel in ["High", "Ultra"]:
            self.distributeTarget.setShaderInput("isLastStep", idx >= self.distributionSteps-1)
        self.distributeTarget.setShaderInput("writeSolidness", idx >= self.distributionSteps-1)

    def publishGrid(self):
        """ This function gets called when the grid is ready to be used, and updates
        the live grid data """
        self.gridPosLive[0] = self.gridPosTemp[0]
        self.bounds.setMinMax(self.gridPosLive[0]-Vec3(self.voxelGridSize), self.gridPosLive[0]+Vec3(self.voxelGridSize))

    def getBounds(self):
        """ Returns the bounds of the gi grid """
        return self.bounds

    def update(self):
        """ Processes the gi, this method is called every frame """

        # Disable all buffers here before starting the rendering
        self.disableTargets()
        # for target in self.mipmapTargets:
            # target.setActive(False)

        self.taskManager.process()

    def disableTargets(self):
        """ Disables all active targets """
        self.voxelizePass.setActive(False)
        self.convertGridTarget.setActive(False)
        self.clearGridTarget.setActive(False)
        self.distributeTarget.setActive(False)


    def setup(self):
        """ Setups everything for the GI to work """
        assert(self.distributionSteps % 2 == 0)

        self._createDebugTexts()

        self.pipeline.getRenderPassManager().registerDefine("USE_GLOBAL_ILLUMINATION", 1)
        self.pipeline.getRenderPassManager().registerDefine("GI_SLIDE_COUNT", self.slideCount)
        self.pipeline.getRenderPassManager().registerDefine("GI_QUALITY_LEVEL", self.qualityLevelIndex)

        # make the grid resolution a constant
        self.pipeline.getRenderPassManager().registerDefine("GI_GRID_RESOLUTION", self.voxelGridResolution)

        self.taskManager.addTask(3, self.stepVoxelize)
        self.taskManager.addTask(self.distributionSteps, self.stepDistribute)

        # Create the voxelize pass which is used to voxelize the scene from
        # several directions
        self.voxelizePass = VoxelizePass(self.pipeline)
        self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution)
        self.voxelizePass.setVoxelGridSize(self.voxelGridSize)
        self.voxelizePass.setGridResolutionMultiplier(1)
        self.pipeline.getRenderPassManager().registerPass(self.voxelizePass)

        self.generationTextures = []

        # Create the buffers used to create the voxel grid
        for color in "rgb":
            tex = Texture("VoxelGeneration-" + color)
            tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TInt, Texture.FR32)
            tex.setClearColor(Vec4(0))
            self.generationTextures.append(tex)
            Globals.render.setShaderInput("voxelGenDest" + color.upper(), tex)
            
            MemoryMonitor.addTexture("VoxelGenerationTex-" + color.upper(), tex)

        self.bindTo(Globals.render, "giData")

        self.convertGridTarget = RenderTarget("ConvertGIGrid")
        self.convertGridTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount)

        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.addColorTexture()
        self.convertGridTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.getColorTexture().setMinfilter(Texture.FTNearest)
            self.convertGridTarget.getColorTexture().setMagfilter(Texture.FTNearest)

        self.clearGridTarget = RenderTarget("ClearGIGrid")
        self.clearGridTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.clearGridTarget.addColorTexture()
        self.clearGridTarget.prepareOffscreenBuffer()

        for idx, color in enumerate("rgb"):
            self.convertGridTarget.setShaderInput("voxelGenSrc" + color.upper(), self.generationTextures[idx])
            self.clearGridTarget.setShaderInput("voxelGenTex" + color.upper(), self.generationTextures[idx])


        # Create the data textures
        self.dataTextures = []
        self.directions = ["PosX", "NegX", "PosY", "NegY", "PosZ", "NegZ"]

        for i, direction in enumerate(self.directions):
            tex = Texture("GIDataTex" + direction)
            tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelDataTex-" + direction, tex)
            self.dataTextures.append(tex)
            self.pipeline.getRenderPassManager().registerStaticVariable("giVoxelData" + direction, tex)


        # Create ping / pong textures
        self.pingDataTextures = []
        self.pongDataTextures = []

        for i, direction in enumerate(self.directions):
            texPing = Texture("GIPingDataTex" + direction)
            texPing.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPingDataTex-" + direction, texPing)
            self.pingDataTextures.append(texPing)

            texPong = Texture("GIPongDataTex" + direction)
            texPong.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPongDataTex-" + direction, texPong)
            self.pongDataTextures.append(texPong)

            self.convertGridTarget.setShaderInput("voxelDataDest"+direction, self.pingDataTextures[i])
            # self.clearGridTarget.setShaderInput("voxelDataDest" + str(i), self.pongDataTextures[i])
        
        # Set texture wrap modes
        for tex in self.pingDataTextures + self.pongDataTextures + self.dataTextures + self.generationTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)
            tex.setWrapU(Texture.WMBorderColor)
            tex.setWrapV(Texture.WMBorderColor)
            tex.setWrapW(Texture.WMBorderColor)
            tex.setAnisotropicDegree(0)
            tex.setBorderColor(Vec4(0))

        for tex in self.dataTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)

        self.distributeTarget = RenderTarget("DistributeVoxels")
        self.distributeTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.addColorTexture()
        self.distributeTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.getColorTexture().setMinfilter(Texture.FTNearest)
            self.distributeTarget.getColorTexture().setMagfilter(Texture.FTNearest)

        self.distributeTarget.setShaderInput("isLastStep", False)

        # Create solidness texture
        self.voxelSolidTex = Texture("GIDataSolidTex")
        self.voxelSolidTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16)
        self.convertGridTarget.setShaderInput("voxelSolidDest", self.voxelSolidTex)
        self.distributeTarget.setShaderInput("voxelSolidTex", self.voxelSolidTex)
        MemoryMonitor.addTexture("VoxelSolidTex", self.voxelSolidTex)

        self.voxelSolidStableTex = Texture("GIDataSolidStableTex")
        self.voxelSolidStableTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16)

        self.distributeTarget.setShaderInput("voxelSolidWriteTex", self.voxelSolidStableTex)
        self.pipeline.getRenderPassManager().registerStaticVariable("giVoxelSolidTex", self.voxelSolidStableTex)





        # Create the final gi pass
        self.finalPass = GlobalIlluminationPass()
        self.pipeline.getRenderPassManager().registerPass(self.finalPass)
        self.pipeline.getRenderPassManager().registerDynamicVariable("giData", self.bindTo)
        self.pipeline.getRenderPassManager().registerStaticVariable("giReadyState", self.readyStateFlag)


        # Visualize voxels
        if False:
            self.voxelCube = loader.loadModel("Box")
            self.voxelCube.reparentTo(render)
            # self.voxelCube.setTwoSided(True)
            self.voxelCube.node().setFinal(True)
            self.voxelCube.node().setBounds(OmniBoundingVolume())
            self.voxelCube.setInstanceCount(self.voxelGridResolution**3)
            # self.voxelCube.hide()
            self.bindTo(self.voxelCube, "giData")
            
            for i in xrange(5):
                self.voxelCube.setShaderInput("giDataTex" + str(i), self.pingDataTextures[i])

        self.disableTargets()

    def _createConvertShader(self):
        """ Loads the shader for converting the voxel grid """
        shader = Shader.load(Shader.SLGLSL, 
            "Shader/DefaultPostProcess.vertex", "Shader/GI/ConvertGrid.fragment")
        self.convertGridTarget.setShader(shader)

    def _createClearShader(self):
        """ Loads the shader for converting the voxel grid """
        shader = Shader.load(Shader.SLGLSL, 
            "Shader/DefaultPostProcess.vertex", "Shader/GI/ClearGrid.fragment")
        self.clearGridTarget.setShader(shader)

    def _createGenerateMipmapsShader(self):
        """ Loads the shader for generating the voxel grid mipmaps """
        computeSize = self.voxelGridResolution
        for child in self.mipmapTargets:
            computeSize /= 2
            shader = Shader.load(Shader.SLGLSL, 
                "Shader/DefaultPostProcess.vertex", 
                "Shader/GI/GenerateMipmaps/" + str(computeSize) + ".fragment")
            child.setShader(shader)

    def _createDistributionShader(self):
        """ Creates the photon distribution shader """
        shader = Shader.load(Shader.SLGLSL, 
            "Shader/DefaultPostProcess.vertex", "Shader/GI/Distribute.fragment")
        self.distributeTarget.setShader(shader)

    def _createBlurShader(self):
        """ Creates the photon distribution shader """
        shader = Shader.load(Shader.SLGLSL, 
            "Shader/DefaultPostProcess.vertex", "Shader/GI/BlurPhotonGrid.fragment")
        self.blurBuffer.setShader(shader)

    def reloadShader(self):
        """ Reloads all shaders and updates the voxelization camera state aswell """
        self.debug("Reloading shaders")
        self._createConvertShader()
        self._createClearShader()
        self._createDistributionShader()
        # self._createGenerateMipmapsShader()
        # self._createPhotonBoxShader()
        # self._createBlurShader()

        if hasattr(self, "voxelCube"):
            self.pipeline.setEffect(self.voxelCube, "Effects/DisplayVoxels.effect", {
                "normalMapping": False,
                "castShadows": False,
                "castGI": False
            })

    def _createPhotonBoxShader(self):
        """ Loads the shader to visualize the photons """
        shader = Shader.load(Shader.SLGLSL, 
            "Shader/DefaultShaders/Photon/vertex.glsl",
            "Shader/DefaultShaders/Photon/fragment.glsl")
        # self.photonBox.setShader(shader, 100)

    def _computeGridPos(self):
        """ Computes the new center of the voxel grid. The center pos is also
        snapped, to avoid flickering. """

        # It is important that the grid is snapped, otherwise it will flicker 
        # while the camera moves. When using a snap of 32, everything until
        # the log2(32) = 5th mipmap is stable. 
        snap = 1.0
        stepSizeX = float(self.voxelGridSize * 2.0) / float(self.voxelGridResolution) * snap
        stepSizeY = float(self.voxelGridSize * 2.0) / float(self.voxelGridResolution) * snap
        stepSizeZ = float(self.voxelGridSize * 2.0) / float(self.voxelGridResolution) * snap

        gridPos = Globals.base.camera.getPos(Globals.base.render)
        gridPos.x -= gridPos.x % stepSizeX
        gridPos.y -= gridPos.y % stepSizeY
        gridPos.z -= gridPos.z % stepSizeZ
        return gridPos

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

        node.setShaderInput(prefix + ".positionGeneration", self.gridPosTemp)
        node.setShaderInput(prefix + ".position", self.gridPosLive)
        node.setShaderInput(prefix + ".size", self.voxelGridSize)
        node.setShaderInput(prefix + ".resolution", self.voxelGridResolution)
Ejemplo n.º 38
0
 def _setupEdgesBuffer(self):
     self._edgesBuffer = RenderTarget("SMAA-Edges")
     self._edgesBuffer.addRenderTexture(RenderTargetType.Color)
     # self._edgesBuffer.setColorBits(16)
     self._edgesBuffer.prepareOffscreenBuffer()
Ejemplo n.º 39
0
class GlobalIllumination(DebugObject):
    """ This class handles the global illumination processing. To process the
    global illumination, the scene is first rasterized from 3 directions, and 
    a 3D voxel grid is created. After that, the mipmaps of the voxel grid are
    generated. The final shader then performs voxel cone tracing to compute 
    an ambient, diffuse and specular term.

    The gi is split over several frames to reduce the computation cost. Currently
    there are 5 different steps, split over 4 frames:

    Frame 1: 
        - Rasterize the scene from the x-axis

    Frame 2:
        - Rasterize the scene from the y-axis

    Frame 3: 
        - Rasterize the scene from the z-axis

    Frame 4:
        - Copy the generated temporary voxel grid into a stable voxel grid
        - Generate the mipmaps for that stable voxel grid using a gaussian filter

    In the final pass the stable voxel grid is sampled. The voxel tracing selects
    the mipmap depending on the cone size. This enables small scale details as well
    as blurry reflections and low-frequency ao / diffuse. For performance reasons,
    the final pass is executed at half window resolution and then bilateral upscaled.
    """

    QualityLevels = ["Low", "Medium", "High", "Ultra"]

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

        self.qualityLevel = self.pipeline.settings.giQualityLevel

        if self.qualityLevel not in self.QualityLevels:
            self.fatal("Unsupported gi quality level:" + self.qualityLevel)

        self.qualityLevelIndex = self.QualityLevels.index(self.qualityLevel)

        # Grid size in world space units
        self.voxelGridSize = self.pipeline.settings.giVoxelGridSize

        # Grid resolution in pixels
        self.voxelGridResolution = [32, 64, 128, 192][self.qualityLevelIndex]

        # Has to be a multiple of 2
        self.distributionSteps = [16, 30, 60, 90][self.qualityLevelIndex]
        self.slideCount = int(self.voxelGridResolution / 8)
        self.slideVertCount = self.voxelGridResolution / self.slideCount

        self.bounds = BoundingBox()
        self.renderCount = 0

        # Create the task manager
        self.taskManager = DistributedTaskManager()

        self.gridPosLive = PTALVecBase3f.emptyArray(1)
        self.gridPosTemp = PTALVecBase3f.emptyArray(1)

        # Store ready state
        self.readyStateFlag = PTAFloat.emptyArray(1)
        self.readyStateFlag[0] = 0

        self.frameIndex = 0
        self.steps = []

    def _createDebugTexts(self):
        """ Creates a debug overlay to show GI status """
        self.debugText = None
        self.buildingText = None

        if self.pipeline.settings.displayDebugStats:
            self.debugText = FastText(pos=Vec2(
                Globals.base.getAspectRatio() - 0.1, 0.88),
                                      rightAligned=True,
                                      color=Vec3(1, 1, 0),
                                      size=0.03)
            self.buildingText = FastText(pos=Vec2(-0.3, 0),
                                         rightAligned=False,
                                         color=Vec3(1, 1, 0),
                                         size=0.03)
            self.buildingText.setText("PREPARING GI, PLEASE BE PATIENT ....")

    def stepVoxelize(self, idx):

        # If we are at the beginning of the frame, compute the new grid position
        if idx == 0:
            self.gridPosTemp[0] = self._computeGridPos()
            # Clear voxel grid at the beginning
            # for tex in self.generationTextures:
            # tex.clearImage()

            self.clearGridTarget.setActive(True)

            if self.debugText is not None:
                self.debugText.setText("GI Grid Center: " + ", ".join(
                    str(round(i, 2)) for i in self.gridPosTemp[0]) +
                                       " / GI Frame " + str(self.renderCount))

            self.renderCount += 1

            if self.renderCount == 3:
                self.readyStateFlag[0] = 1.0
                if self.buildingText:
                    self.buildingText.remove()
                    self.buildingText = None

        self.voxelizePass.voxelizeSceneFromDirection(self.gridPosTemp[0],
                                                     "xyz"[idx])

    def stepDistribute(self, idx):

        if idx == 0:

            skyBegin = 142.0
            skyInGrid = (skyBegin -
                         self.gridPosTemp[0].z) / (2.0 * self.voxelGridSize)
            skyInGrid = int(skyInGrid * self.voxelGridResolution)
            self.convertGridTarget.setShaderInput("skyStartZ", skyInGrid)
            self.convertGridTarget.setActive(True)

        self.distributeTarget.setActive(True)

        swap = idx % 2 == 0
        sources = self.pingDataTextures if swap else self.pongDataTextures
        dests = self.pongDataTextures if swap else self.pingDataTextures

        if idx == self.distributionSteps - 1:
            self.publishGrid()
            dests = self.dataTextures

        for i, direction in enumerate(self.directions):
            self.distributeTarget.setShaderInput("src" + direction, sources[i])
            self.distributeTarget.setShaderInput("dst" + direction, dests[i])

        # Only do the last blur-step on high+ quality, leads to artifacts otherwise
        # due to the low grid resolution
        if self.qualityLevel in ["High", "Ultra"]:
            self.distributeTarget.setShaderInput(
                "isLastStep", idx >= self.distributionSteps - 1)
        self.distributeTarget.setShaderInput("writeSolidness",
                                             idx >= self.distributionSteps - 1)

    def publishGrid(self):
        """ This function gets called when the grid is ready to be used, and updates
        the live grid data """
        self.gridPosLive[0] = self.gridPosTemp[0]
        self.bounds.setMinMax(self.gridPosLive[0] - Vec3(self.voxelGridSize),
                              self.gridPosLive[0] + Vec3(self.voxelGridSize))

    def getBounds(self):
        """ Returns the bounds of the gi grid """
        return self.bounds

    def update(self):
        """ Processes the gi, this method is called every frame """

        # Disable all buffers here before starting the rendering
        self.disableTargets()
        # for target in self.mipmapTargets:
        # target.setActive(False)

        self.taskManager.process()

    def disableTargets(self):
        """ Disables all active targets """
        self.voxelizePass.setActive(False)
        self.convertGridTarget.setActive(False)
        self.clearGridTarget.setActive(False)
        self.distributeTarget.setActive(False)

    def setup(self):
        """ Setups everything for the GI to work """
        assert (self.distributionSteps % 2 == 0)

        self._createDebugTexts()

        self.pipeline.getRenderPassManager().registerDefine(
            "USE_GLOBAL_ILLUMINATION", 1)
        self.pipeline.getRenderPassManager().registerDefine(
            "GI_SLIDE_COUNT", self.slideCount)
        self.pipeline.getRenderPassManager().registerDefine(
            "GI_QUALITY_LEVEL", self.qualityLevelIndex)

        # make the grid resolution a constant
        self.pipeline.getRenderPassManager().registerDefine(
            "GI_GRID_RESOLUTION", self.voxelGridResolution)

        self.taskManager.addTask(3, self.stepVoxelize)
        self.taskManager.addTask(self.distributionSteps, self.stepDistribute)

        # Create the voxelize pass which is used to voxelize the scene from
        # several directions
        self.voxelizePass = VoxelizePass(self.pipeline)
        self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution)
        self.voxelizePass.setVoxelGridSize(self.voxelGridSize)
        self.voxelizePass.setGridResolutionMultiplier(1)
        self.pipeline.getRenderPassManager().registerPass(self.voxelizePass)

        self.generationTextures = []

        # Create the buffers used to create the voxel grid
        for color in "rgb":
            tex = Texture("VoxelGeneration-" + color)
            tex.setup3dTexture(self.voxelGridResolution,
                               self.voxelGridResolution,
                               self.voxelGridResolution, Texture.TInt,
                               Texture.FR32)
            tex.setClearColor(Vec4(0))
            self.generationTextures.append(tex)
            Globals.render.setShaderInput("voxelGenDest" + color.upper(), tex)

            MemoryMonitor.addTexture("VoxelGenerationTex-" + color.upper(),
                                     tex)

        self.bindTo(Globals.render, "giData")

        self.convertGridTarget = RenderTarget("ConvertGIGrid")
        self.convertGridTarget.setSize(
            self.voxelGridResolution * self.slideCount,
            self.voxelGridResolution * self.slideVertCount)

        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.addColorTexture()
        self.convertGridTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.convertGridTarget.getColorTexture().setMinfilter(
                Texture.FTNearest)
            self.convertGridTarget.getColorTexture().setMagfilter(
                Texture.FTNearest)

        self.clearGridTarget = RenderTarget("ClearGIGrid")
        self.clearGridTarget.setSize(
            self.voxelGridResolution * self.slideCount,
            self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.clearGridTarget.addColorTexture()
        self.clearGridTarget.prepareOffscreenBuffer()

        for idx, color in enumerate("rgb"):
            self.convertGridTarget.setShaderInput(
                "voxelGenSrc" + color.upper(), self.generationTextures[idx])
            self.clearGridTarget.setShaderInput("voxelGenTex" + color.upper(),
                                                self.generationTextures[idx])

        # Create the data textures
        self.dataTextures = []
        self.directions = ["PosX", "NegX", "PosY", "NegY", "PosZ", "NegZ"]

        for i, direction in enumerate(self.directions):
            tex = Texture("GIDataTex" + direction)
            tex.setup3dTexture(self.voxelGridResolution,
                               self.voxelGridResolution,
                               self.voxelGridResolution, Texture.TFloat,
                               Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelDataTex-" + direction, tex)
            self.dataTextures.append(tex)
            self.pipeline.getRenderPassManager().registerStaticVariable(
                "giVoxelData" + direction, tex)

        # Create ping / pong textures
        self.pingDataTextures = []
        self.pongDataTextures = []

        for i, direction in enumerate(self.directions):
            texPing = Texture("GIPingDataTex" + direction)
            texPing.setup3dTexture(self.voxelGridResolution,
                                   self.voxelGridResolution,
                                   self.voxelGridResolution, Texture.TFloat,
                                   Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPingDataTex-" + direction, texPing)
            self.pingDataTextures.append(texPing)

            texPong = Texture("GIPongDataTex" + direction)
            texPong.setup3dTexture(self.voxelGridResolution,
                                   self.voxelGridResolution,
                                   self.voxelGridResolution, Texture.TFloat,
                                   Texture.FR11G11B10)
            MemoryMonitor.addTexture("VoxelPongDataTex-" + direction, texPong)
            self.pongDataTextures.append(texPong)

            self.convertGridTarget.setShaderInput("voxelDataDest" + direction,
                                                  self.pingDataTextures[i])
            # self.clearGridTarget.setShaderInput("voxelDataDest" + str(i), self.pongDataTextures[i])

        # Set texture wrap modes
        for tex in self.pingDataTextures + self.pongDataTextures + self.dataTextures + self.generationTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)
            tex.setWrapU(Texture.WMBorderColor)
            tex.setWrapV(Texture.WMBorderColor)
            tex.setWrapW(Texture.WMBorderColor)
            tex.setAnisotropicDegree(0)
            tex.setBorderColor(Vec4(0))

        for tex in self.dataTextures:
            tex.setMinfilter(Texture.FTLinear)
            tex.setMagfilter(Texture.FTLinear)

        self.distributeTarget = RenderTarget("DistributeVoxels")
        self.distributeTarget.setSize(
            self.voxelGridResolution * self.slideCount,
            self.voxelGridResolution * self.slideVertCount)
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.addColorTexture()
        self.distributeTarget.prepareOffscreenBuffer()

        # Set a near-filter to the texture
        if self.pipeline.settings.useDebugAttachments:
            self.distributeTarget.getColorTexture().setMinfilter(
                Texture.FTNearest)
            self.distributeTarget.getColorTexture().setMagfilter(
                Texture.FTNearest)

        self.distributeTarget.setShaderInput("isLastStep", False)

        # Create solidness texture
        self.voxelSolidTex = Texture("GIDataSolidTex")
        self.voxelSolidTex.setup3dTexture(self.voxelGridResolution,
                                          self.voxelGridResolution,
                                          self.voxelGridResolution,
                                          Texture.TFloat, Texture.FR16)
        self.convertGridTarget.setShaderInput("voxelSolidDest",
                                              self.voxelSolidTex)
        self.distributeTarget.setShaderInput("voxelSolidTex",
                                             self.voxelSolidTex)
        MemoryMonitor.addTexture("VoxelSolidTex", self.voxelSolidTex)

        self.voxelSolidStableTex = Texture("GIDataSolidStableTex")
        self.voxelSolidStableTex.setup3dTexture(self.voxelGridResolution,
                                                self.voxelGridResolution,
                                                self.voxelGridResolution,
                                                Texture.TFloat, Texture.FR16)

        self.distributeTarget.setShaderInput("voxelSolidWriteTex",
                                             self.voxelSolidStableTex)
        self.pipeline.getRenderPassManager().registerStaticVariable(
            "giVoxelSolidTex", self.voxelSolidStableTex)

        # Create the final gi pass
        self.finalPass = GlobalIlluminationPass()
        self.pipeline.getRenderPassManager().registerPass(self.finalPass)
        self.pipeline.getRenderPassManager().registerDynamicVariable(
            "giData", self.bindTo)
        self.pipeline.getRenderPassManager().registerStaticVariable(
            "giReadyState", self.readyStateFlag)

        # Visualize voxels
        if False:
            self.voxelCube = loader.loadModel("Box")
            self.voxelCube.reparentTo(render)
            # self.voxelCube.setTwoSided(True)
            self.voxelCube.node().setFinal(True)
            self.voxelCube.node().setBounds(OmniBoundingVolume())
            self.voxelCube.setInstanceCount(self.voxelGridResolution**3)
            # self.voxelCube.hide()
            self.bindTo(self.voxelCube, "giData")

            for i in xrange(5):
                self.voxelCube.setShaderInput("giDataTex" + str(i),
                                              self.pingDataTextures[i])

        self.disableTargets()

    def _createConvertShader(self):
        """ Loads the shader for converting the voxel grid """
        shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex",
                             "Shader/GI/ConvertGrid.fragment")
        self.convertGridTarget.setShader(shader)

    def _createClearShader(self):
        """ Loads the shader for converting the voxel grid """
        shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex",
                             "Shader/GI/ClearGrid.fragment")
        self.clearGridTarget.setShader(shader)

    def _createGenerateMipmapsShader(self):
        """ Loads the shader for generating the voxel grid mipmaps """
        computeSize = self.voxelGridResolution
        for child in self.mipmapTargets:
            computeSize /= 2
            shader = Shader.load(
                Shader.SLGLSL, "Shader/DefaultPostProcess.vertex",
                "Shader/GI/GenerateMipmaps/" + str(computeSize) + ".fragment")
            child.setShader(shader)

    def _createDistributionShader(self):
        """ Creates the photon distribution shader """
        shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex",
                             "Shader/GI/Distribute.fragment")
        self.distributeTarget.setShader(shader)

    def _createBlurShader(self):
        """ Creates the photon distribution shader """
        shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex",
                             "Shader/GI/BlurPhotonGrid.fragment")
        self.blurBuffer.setShader(shader)

    def reloadShader(self):
        """ Reloads all shaders and updates the voxelization camera state aswell """
        self.debug("Reloading shaders")
        self._createConvertShader()
        self._createClearShader()
        self._createDistributionShader()
        # self._createGenerateMipmapsShader()
        # self._createPhotonBoxShader()
        # self._createBlurShader()

        if hasattr(self, "voxelCube"):
            self.pipeline.setEffect(self.voxelCube,
                                    "Effects/DisplayVoxels.effect", {
                                        "normalMapping": False,
                                        "castShadows": False,
                                        "castGI": False
                                    })

    def _createPhotonBoxShader(self):
        """ Loads the shader to visualize the photons """
        shader = Shader.load(Shader.SLGLSL,
                             "Shader/DefaultShaders/Photon/vertex.glsl",
                             "Shader/DefaultShaders/Photon/fragment.glsl")
        # self.photonBox.setShader(shader, 100)

    def _computeGridPos(self):
        """ Computes the new center of the voxel grid. The center pos is also
        snapped, to avoid flickering. """

        # It is important that the grid is snapped, otherwise it will flicker
        # while the camera moves. When using a snap of 32, everything until
        # the log2(32) = 5th mipmap is stable.
        snap = 1.0
        stepSizeX = float(self.voxelGridSize * 2.0) / float(
            self.voxelGridResolution) * snap
        stepSizeY = float(self.voxelGridSize * 2.0) / float(
            self.voxelGridResolution) * snap
        stepSizeZ = float(self.voxelGridSize * 2.0) / float(
            self.voxelGridResolution) * snap

        gridPos = Globals.base.camera.getPos(Globals.base.render)
        gridPos.x -= gridPos.x % stepSizeX
        gridPos.y -= gridPos.y % stepSizeY
        gridPos.z -= gridPos.z % stepSizeZ
        return gridPos

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

        node.setShaderInput(prefix + ".positionGeneration", self.gridPosTemp)
        node.setShaderInput(prefix + ".position", self.gridPosLive)
        node.setShaderInput(prefix + ".size", self.voxelGridSize)
        node.setShaderInput(prefix + ".resolution", self.voxelGridResolution)
Ejemplo n.º 40
0
    def _createRT(self, name, w, h, aux=False, shaderName="", layers=1):
        """ Internal shortcut to create a new render target """
        rt = RenderTarget("Scattering" + name)
        rt.setSize(w, h)
        rt.addColorTexture()
        rt.setColorBits(16)
        # rt.setEngine(self.engine)s

        if aux:
            rt.addAuxTextures(1)
            rt.setAuxBits(16)

        if layers > 1:
            rt.setLayers(layers)
        rt.prepareOffscreenBuffer()

        # self._engine.openWindows()

        sArgs = [
            "Shader/Scattering/DefaultVertex.vertex",
            "Shader/Scattering/" + shaderName + ".fragment"
        ]

        if layers > 1:
            sArgs.append("Shader/Scattering/DefaultGeometry.geometry")
        shader = BetterShader.load(*sArgs)
        rt.setShader(shader)

        self._setInputs(rt, "options")

        lc = lambda x: x[0].lower() + x[1:]

        for key, tex in self.textures.items():
            rt.setShaderInput(lc(key), tex)

        self.textures[lc(name) + "Color"] = rt.getColorTexture()

        if aux:
            self.textures[lc(name) + "Aux"] = rt.getAuxTexture(0)

        return rt
Ejemplo n.º 41
0
 def _setupEdgesBuffer(self):
     """ Internal method to create the edges buffer """
     self._edgesBuffer = RenderTarget("SMAA-Edges")
     self._edgesBuffer.addRenderTexture(RenderTargetType.Color)
     self._edgesBuffer.prepareOffscreenBuffer()
class RenderingPipeline(DebugObject):
    def __init__(self, showbase):
        DebugObject.__init__(self, "RenderingPipeline")
        self.showbase = showbase
        self.lightManager = LightManager()
        self.size = self._getSize()
        self.precomputeSize = Vec2(0)
        self.camera = base.cam
        self.cullBounds = None
        self.patchSize = Vec2(32, 32)

        self.temporalProjXOffs = 0
        self.temporalProjFactor = 2

        self.forwardScene = NodePath("Forward Rendering")
        self.lastMVP = None

        self._setup()

    def _setup(self):
        self.debug("Setting up render pipeline")
        # First, we need no transparency
        render.setAttrib(TransparencyAttrib.make(TransparencyAttrib.MNone),
                         100)

        # Now create deferred render buffers
        self._makeDeferredTargets()

        # Setup compute shader for lighting
        self._createLightingPipeline()

        # Setup combiner
        self._createCombiner()

        self.deferredTarget.setShader(
            BetterShader.load("Shader/DefaultPostProcess.vertex",
                              "Shader/TextureDisplay.fragment"))
        self._setupAntialiasing()

        self._createFinalPass()

        self.antialias.getFirstBuffer().setShaderInput(
            "lastFrame", self.lightingComputeCombinedTex)
        self.antialias.getFirstBuffer().setShaderInput("lastPosition",
                                                       self.lastPositionBuffer)
        self.antialias.getFirstBuffer().setShaderInput(
            "currentPosition", self.deferredTarget.getColorTexture())

        # self.deferredTarget.setShaderInput("sampler", self.lightingComputeCombinedTex)
        # self.deferredTarget.setShaderInput("sampler", self.antialias.getResultTexture())
        self.deferredTarget.setShaderInput("sampler",
                                           self.finalPass.getColorTexture())
        # self.deferredTarget.setShaderInput("sampler", self.combiner.getColorTexture())

        # self.deferredTarget.setShaderInput("sampler", self.lightingComputeCombinedTex)
        # self.deferredTarget.setShaderInput("sampler", self.antialias._neighborBuffer.getColorTexture())
        # self.deferredTarget.setShaderInput("sampler", self.antialias._blendBuffer.getColorTexture())
        # self.deferredTarget.setShaderInput("sampler", self.lightingComputeCombinedTex)

        # add update task
        self._attachUpdateTask()

        # compute first mvp
        self._computeMVP()
        self.lastLastMVP = self.lastMVP

        # DirectFrame(frameColor=(1, 1, 1, 0.2), frameSize=(-0.28, 0.28, -0.27, 0.4), pos=(base.getAspectRatio() - 0.35, 0.0, 0.49))
        self.atlasDisplayImage = OnscreenImage(
            image=self.lightManager.getAtlasTex(),
            pos=(base.getAspectRatio() - 0.35, 0, 0.5),
            scale=(0.25, 0, 0.25))
        self.lastPosImage = OnscreenImage(
            image=self.lightingComputeCombinedTex,
            pos=(base.getAspectRatio() - 0.35, 0, -0.05),
            scale=(0.25, 0, 0.25))
        # self.atlasDisplayImage =  OnscreenImage(image = self.lightManager.getAtlasTex(), pos = (0,0,0), scale=(0.8,1,0.8))
        # self.atlasDisplayImage =  OnscreenImage(image = self.lightPerTileStorage, pos = (base.getAspectRatio() - 0.35, 0, 0.5), scale=(0.25,0,0.25))

    def _createCombiner(self):
        self.combiner = RenderTarget("Combine-Temporal")
        self.combiner.setColorBits(8)
        self.combiner.addRenderTexture(RenderTargetType.Color)
        self.combiner.prepareOffscreenBuffer()
        self.combiner.setShaderInput(
            "currentComputation",
            self.lightingComputeContainer.getColorTexture())
        self.combiner.setShaderInput("lastFrame",
                                     self.lightingComputeCombinedTex)
        self.combiner.setShaderInput("positionBuffer",
                                     self.deferredTarget.getColorTexture())
        self.combiner.setShaderInput("velocityBuffer",
                                     self.deferredTarget.getAuxTexture(1))
        self.combiner.setShaderInput("lastPosition", self.lastPositionBuffer)

        self._setCombinerShader()

    def _setupAntialiasing(self):
        self.debug("Creating antialiasing handler ..")
        self.antialias = Antialiasing()

        # self.antialias.setColorTexture(self.lightingComputeContainer.getColorTexture())
        self.antialias.setColorTexture(self.combiner.getColorTexture())
        self.antialias.setDepthTexture(self.deferredTarget.getDepthTexture())
        self.antialias.setup()

    # Creates all the render targets
    def _makeDeferredTargets(self):
        self.debug("Creating deferred targets")
        self.deferredTarget = RenderTarget("DeferredTarget")
        self.deferredTarget.addRenderTexture(RenderTargetType.Color)
        self.deferredTarget.addRenderTexture(RenderTargetType.Depth)
        self.deferredTarget.addRenderTexture(RenderTargetType.Aux0)
        self.deferredTarget.addRenderTexture(RenderTargetType.Aux1)
        self.deferredTarget.setAuxBits(16)
        self.deferredTarget.setColorBits(16)
        self.deferredTarget.setDepthBits(32)
        # self.deferredTarget.setSize(400, 240) # check for overdraw
        self.deferredTarget.prepareSceneRender()

    def _createFinalPass(self):
        self.debug("Creating final pass")
        self.finalPass = RenderTarget("FinalPass")
        self.finalPass.addRenderTexture(RenderTargetType.Color)
        self.finalPass.prepareOffscreenBuffer()

        colorTex = self.antialias.getResultTexture()
        # Set wrap for motion blur
        colorTex.setWrapU(Texture.WMMirror)
        colorTex.setWrapV(Texture.WMMirror)
        self.finalPass.setShaderInput("colorTex", colorTex)
        self.finalPass.setShaderInput("velocityTex",
                                      self.deferredTarget.getAuxTexture(1))
        self.finalPass.setShaderInput("depthTex",
                                      self.deferredTarget.getDepthTexture())
        self._setFinalPassShader()

    # Creates the storage to store the list of visible lights per tile
    def _makeLightPerTileStorage(self):

        storageSizeX = int(self.precomputeSize.x * 8)
        storageSizeY = int(self.precomputeSize.y * 8)

        self.debug("Creating per tile storage of size", storageSizeX, "x",
                   storageSizeY)

        self.lightPerTileStorage = Texture("LightsPerTile")
        self.lightPerTileStorage.setup2dTexture(storageSizeX, storageSizeY,
                                                Texture.TUnsignedShort,
                                                Texture.FR32i)
        self.lightPerTileStorage.setMinfilter(Texture.FTNearest)
        self.lightPerTileStorage.setMagfilter(Texture.FTNearest)

    # Inits the lighting pipeline
    def _createLightingPipeline(self):
        self.debug("Creating lighting pipeline ..")

        # size has to be a multiple of the compute unit size
        # but still has to cover the whole screen
        sizeX = int(math.ceil(self.size.x / self.patchSize.x))
        sizeY = int(math.ceil(self.size.y / self.patchSize.y))

        self.precomputeSize = Vec2(sizeX, sizeY)

        self.debug("Batch size =", sizeX, "x", sizeY, "Actual Buffer size=",
                   int(sizeX * self.patchSize.x), "x",
                   int(sizeY * self.patchSize.y))

        self._makeLightPerTileStorage()

        # Create a buffer which computes which light affects which tile
        self._makeLightBoundsComputationBuffer(sizeX, sizeY)

        # Create a buffer which applies the lighting
        self._makeLightingComputeBuffer()

        # Register for light manager
        self.lightManager.setLightingComputator(self.lightingComputeContainer)
        self.lightManager.setLightingCuller(self.lightBoundsComputeBuff)

        self.lightingComputeContainer.setShaderInput("lightsPerTile",
                                                     self.lightPerTileStorage)

        self.lightingComputeContainer.setShaderInput("cameraPosition",
                                                     base.cam.getPos(render))

        # Ensure the images have the correct filter mode
        for bmode in [RenderTargetType.Color]:
            tex = self.lightBoundsComputeBuff.getTexture(bmode)
            tex.setMinfilter(Texture.FTNearest)
            tex.setMagfilter(Texture.FTNearest)

        self._loadFallbackCubemap()

        # Create storage for the bounds computation

        # Set inputs
        self.lightBoundsComputeBuff.setShaderInput("destination",
                                                   self.lightPerTileStorage)
        self.lightBoundsComputeBuff.setShaderInput(
            "depth", self.deferredTarget.getDepthTexture())

        self.lightingComputeContainer.setShaderInput(
            "data0", self.deferredTarget.getColorTexture())
        self.lightingComputeContainer.setShaderInput(
            "data1", self.deferredTarget.getAuxTexture(0))
        self.lightingComputeContainer.setShaderInput(
            "data2", self.deferredTarget.getAuxTexture(1))

        self.lightingComputeContainer.setShaderInput(
            "shadowAtlas", self.lightManager.getAtlasTex())
        self.lightingComputeContainer.setShaderInput(
            "destination", self.lightingComputeCombinedTex)
        # self.lightingComputeContainer.setShaderInput("sampleTex", loader.loadTexture("Data/Antialiasing/Unigine01.png"))

    def _loadFallbackCubemap(self):
        cubemap = loader.loadCubeMap("Cubemap/#.png")
        cubemap.setMinfilter(Texture.FTLinearMipmapLinear)
        cubemap.setMagfilter(Texture.FTLinearMipmapLinear)
        cubemap.setFormat(Texture.F_srgb_alpha)
        self.lightingComputeContainer.setShaderInput("fallbackCubemap",
                                                     cubemap)

    def _makeLightBoundsComputationBuffer(self, w, h):
        self.debug("Creating light precomputation buffer of size", w, "x", h)
        self.lightBoundsComputeBuff = RenderTarget("ComputeLightTileBounds")
        self.lightBoundsComputeBuff.setSize(w, h)
        self.lightBoundsComputeBuff.addRenderTexture(RenderTargetType.Color)
        self.lightBoundsComputeBuff.setColorBits(16)
        self.lightBoundsComputeBuff.prepareOffscreenBuffer()

        self.lightBoundsComputeBuff.setShaderInput("mainCam", base.cam)
        self.lightBoundsComputeBuff.setShaderInput("mainRender", base.render)

        self._setPositionComputationShader()

    def _makeLightingComputeBuffer(self):
        self.lightingComputeContainer = RenderTarget("ComputeLighting")
        self.lightingComputeContainer.setSize(
            base.win.getXSize() / self.temporalProjFactor, base.win.getYSize())
        self.lightingComputeContainer.addRenderTexture(RenderTargetType.Color)
        self.lightingComputeContainer.setColorBits(16)
        self.lightingComputeContainer.prepareOffscreenBuffer()

        self.lightingComputeCombinedTex = Texture("Lighting-Compute-Combined")
        self.lightingComputeCombinedTex.setup2dTexture(base.win.getXSize(),
                                                       base.win.getYSize(),
                                                       Texture.TFloat,
                                                       Texture.FRgba16)
        self.lightingComputeCombinedTex.setMinfilter(Texture.FTLinear)
        self.lightingComputeCombinedTex.setMagfilter(Texture.FTLinear)

        self.lastPositionBuffer = Texture("Last-Position-Buffer")
        self.lastPositionBuffer.setup2dTexture(base.win.getXSize(),
                                               base.win.getYSize(),
                                               Texture.TFloat, Texture.FRgba16)
        self.lastPositionBuffer.setMinfilter(Texture.FTNearest)
        self.lastPositionBuffer.setMagfilter(Texture.FTNearest)

    def _setLightingShader(self):

        lightShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                        "Shader/ApplyLighting.fragment")
        self.lightingComputeContainer.setShader(lightShader)

    def _setCombinerShader(self):
        cShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                    "Shader/Combiner.fragment")
        self.combiner.setShader(cShader)

    def _setPositionComputationShader(self):
        pcShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                     "Shader/PrecomputeLights.fragment")
        self.lightBoundsComputeBuff.setShader(pcShader)

    def _setFinalPassShader(self):
        fShader = BetterShader.load("Shader/DefaultPostProcess.vertex",
                                    "Shader/Final.fragment")
        self.finalPass.setShader(fShader)

    def _getSize(self):
        return Vec2(int(self.showbase.win.getXSize()),
                    int(self.showbase.win.getYSize()))

    def debugReloadShader(self):
        self.lightManager.debugReloadShader()
        self._setPositionComputationShader()
        self._setCombinerShader()
        self._setLightingShader()
        self._setFinalPassShader()
        self.antialias.reloadShader()

    def _attachUpdateTask(self):
        self.showbase.addTask(self._update,
                              "UpdateRenderingPipeline",
                              sort=-10000)

    def _computeCameraBounds(self):
        # compute camera bounds in render space
        cameraBounds = self.camera.node().getLens().makeBounds()
        cameraBounds.xform(self.camera.getMat(render))
        return cameraBounds

    def _update(self, task=None):

        self.temporalProjXOffs += 1
        self.temporalProjXOffs = self.temporalProjXOffs % self.temporalProjFactor

        self.cullBounds = self._computeCameraBounds()

        self.lightManager.setCullBounds(self.cullBounds)
        self.lightManager.update()

        self.lightingComputeContainer.setShaderInput("cameraPosition",
                                                     base.cam.getPos(render))
        self.lightingComputeContainer.setShaderInput(
            "temporalProjXOffs", LVecBase2i(self.temporalProjXOffs))
        self.combiner.setShaderInput("lastMVP", self.lastMVP)
        render.setShaderInput("lastMVP", self.lastMVP)
        self.combiner.setShaderInput("temporalProjXOffs",
                                     LVecBase2i(self.temporalProjXOffs))
        self._computeMVP()
        self.combiner.setShaderInput("currentMVP", self.lastMVP)

        self.combiner.setShaderInput("cameraPosition", base.cam.getPos(render))

        if task is not None:
            return task.cont

    def _computeMVP(self):
        projMat = Mat4.convertMat(CSYupRight, base.camLens.getCoordinateSystem(
        )) * base.camLens.getProjectionMat()
        transformMat = TransformState.makeMat(
            Mat4.convertMat(base.win.getGsg().getInternalCoordinateSystem(),
                            CSZupRight))
        modelViewMat = transformMat.invertCompose(render.getTransform(
            base.cam)).getMat()
        self.lastMVP = modelViewMat * projMat
        # print "Self.lastMVP is now from frame",globalClock.getFrameTime()

    def getLightManager(self):
        return self.lightManager

    def getDefaultObjectShader(self):
        shader = BetterShader.load("Shader/DefaultObjectShader.vertex",
                                   "Shader/DefaultObjectShader.fragment")
        return shader
Ejemplo n.º 43
0
 def setup(self):
     """ only one buffer is required """
     self._buffer = RenderTarget("FXAA")
     self._buffer.addColorTexture()
     self._buffer.prepareOffscreenBuffer()
     self._buffer.setShaderInput("colorTex", self._colorTexture)
Ejemplo n.º 44
0
 def _setupResolveBuffer(self):
     """ Creates the buffer which does the final resolve pass """
     self._resolveBuffer = RenderTarget("SMAA-Resolve")
     self._resolveBuffer.addColorTexture()
     self._resolveBuffer.prepareOffscreenBuffer()
Ejemplo n.º 45
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())
Ejemplo n.º 46
0
 def _setupNeighborBuffer(self):
     self._neighborBuffer = RenderTarget("SMAA-Neighbors")
     self._neighborBuffer.addRenderTexture(RenderTargetType.Color)
     # self._blendBuffer.setColorBits(16)
     self._neighborBuffer.prepareOffscreenBuffer()
Ejemplo n.º 47
0
    def __init__(self):
        DebugObject.__init__(self, "LightManager")

        self._initArrays()

        # create arrays to store lights & shadow sources
        self.lights = []
        self.shadowSources = []
        self.allLightsArray = ShaderStructArray(Light, 30)

        self.cullBounds = None
        self.shadowScene = render

        ## SHADOW ATLAS ##
        # todo: move to separate class

        # When you change this, change also SHADOW_MAP_ATLAS_SIZE in configuration.include,
        # and reduce the default shadow map resolution of point lights
        self.shadowAtlasSize = 512
        self.maxShadowMaps = 24

        # When you change it , change also SHAODOW_GEOMETRY_MAX_VERTICES and
        # SHADOW_MAX_UPDATES_PER_FRAME in configuration.include!
        self.maxShadowUpdatesPerFrame = 2
        self.tileSize = 128
        self.tileCount = self.shadowAtlasSize / self.tileSize
        self.tiles = []

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

        # self.shadowAtlasTex = Texture("ShadowAtlas")
        # self.shadowAtlasTex.setup2dTexture(
        #     self.shadowAtlasSize, self.shadowAtlasSize, Texture.TFloat, Texture.FRg16)
        # self.shadowAtlasTex.setMinfilter(Texture.FTLinear)
        # self.shadowAtlasTex.setMagfilter(Texture.FTLinear)

        self.debug("Init shadow atlas with tileSize =", self.tileSize,
                   ", tileCount =", self.tileCount)

        for i in xrange(self.tileCount):
            self.tiles.append([None for j in xrange(self.tileCount)])

        # create shadow compute buffer
        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)

        self.shadowComputeCameraNode.setPos(0, 0, 150)
        self.shadowComputeCameraNode.lookAt(0, 0, 0)

        self.shadowComputeTarget = RenderTarget("ShadowCompute")
        self.shadowComputeTarget.setSize(self.shadowAtlasSize,
                                         self.shadowAtlasSize)
        # self.shadowComputeTarget.setLayers(self.maxShadowUpdatesPerFrame)
        self.shadowComputeTarget.addRenderTexture(RenderTargetType.Depth)
        self.shadowComputeTarget.setDepthBits(32)
        self.shadowComputeTarget.setSource(self.shadowComputeCameraNode,
                                           base.win)
        self.shadowComputeTarget.prepareSceneRender()

        self.shadowComputeTarget.getInternalRegion().setSort(3)
        self.shadowComputeTarget.getRegion().setSort(3)

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

        self.depthClearer = []

        for i in xrange(self.maxShadowUpdatesPerFrame):
            buff = self.shadowComputeTarget.getInternalBuffer()
            dr = buff.makeDisplayRegion()
            dr.setSort(2)
            dr.setClearDepthActive(True)
            dr.setClearDepth(1.0)
            dr.setClearColorActive(False)
            dr.setDimensions(0, 0, 0, 0)
            self.depthClearer.append(dr)

        self.queuedShadowUpdates = []

        # Assign copy shader
        self._setCopyShader()
        # self.shadowComputeTarget.setShaderInput("atlas", self.shadowComputeTarget.getColorTexture())
        # self.shadowComputeTarget.setShaderInput(
        #     "renderResult", self.shadowComputeTarget.getDepthTexture())

        # self.shadowComputeTarget.setActive(False)

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

        self.shadowComputeCamera.setTagStateKey("ShadowPass")
        initialState = NodePath("ShadowCasterState")
        initialState.setShader(self.shadowCasterShader, 30)
        self.shadowComputeCamera.setInitialState(
            RenderState.make(ColorWriteAttrib.make(ColorWriteAttrib.C_off),
                             DepthWriteAttrib.make(DepthWriteAttrib.M_on),
                             100))

        self.shadowComputeCamera.setTagState("True", initialState.getState())
        self.shadowScene.setTag("ShadowPass", "True")

        self._createDebugTexts()

        self.updateShadowsArray.bindTo(self.shadowScene, "updateSources")
        self.updateShadowsArray.bindTo(self.shadowComputeTarget,
                                       "updateSources")

        self.numShadowUpdatesPTA = PTAInt.emptyArray(1)

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

        self.lightingComputator = None
        self.lightCuller = None
Ejemplo n.º 48
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")
Ejemplo n.º 49
0
    def _createRT(self, name, width, height, attachAuxTexture=False, shaderName="", layers=1):
        """ Internal shortcut to create a new render target. The name should be
        a unique identifier. When attachAuxTexture is True, an aux texture will
        be attached to the buffer, additionally to the color texture. 
        The shader name determines the shader to load for the target, see below.
        When layers is > 1, a layered render target will be created to render to
        a 3D texture."""

        # Setup the render target
        target = RenderTarget("Scattering" + name)
        target.setSize(width, height)
        target.addColorTexture()
        target.setColorBits(32)

        # Adds aux textures if specified
        if attachAuxTexture:
            target.addAuxTextures(1)
            target.setAuxBits(32)

        # Add render layers if specified
        if layers > 1:
            target.setLayers(layers)

        target.prepareOffscreenBuffer()

        # Load the appropriate shader
        sArgs = ["Shader/Scattering/DefaultVertex.vertex",
            "Shader/Scattering/" + shaderName + ".fragment"]

        # When using layered rendering, a geometry shader is required
        if layers > 1:
            sArgs.append("Shader/Scattering/DefaultGeometry.geometry")

        shader = Shader.load(Shader.SLGLSL, *sArgs)
        target.setShader(shader)

        # Make the scattering options available
        self._setInputs(target, "options")

        # Lowercase the first letter
        lowerCaseFirst = lambda x: x[0].lower() + x[1:]

        # Make all rendered textures so far available to the target
        for key, tex in self.textures.iteritems():
            target.setShaderInput(key, tex)

        # Register the created textures
        self.textures[lowerCaseFirst(name) + "Color"] = target.getColorTexture()
        if attachAuxTexture:
            self.textures[lowerCaseFirst(name) + "Aux"] = target.getAuxTexture(0)
 
        return target
Ejemplo n.º 50
0
 def _setupNeighborBuffer(self):
     """ Internal method to create the weighting buffer """
     self._neighborBuffer = RenderTarget("SMAA-Neighbors")
     self._neighborBuffer.addRenderTexture(RenderTargetType.Color)
     self._neighborBuffer.prepareOffscreenBuffer()