class VoxelizePass(RenderPass): """ This pass manages voxelizing the scene from multiple directions to generate a 3D voxel grid. It handles the camera setup and provides a simple interface. """ def __init__(self): RenderPass.__init__(self) def getID(self): return "VoxelizePass" def getRequiredInputs(self): return { } def setVoxelGridResolution(self, voxelGridResolution): """ Sets the voxel grid resolution, this is the amount of voxels in every direction, so the voxel grid will have voxelGridResolution**3 total voxels. """ self.voxelGridResolution = voxelGridResolution def setVoxelGridSize(self, voxelGridSize): """ Sets the size of the voxel grid in world space units. This is the size going from the mid of the voxel grid, so the effective voxel grid will have twice the size specified in voxelGridSize """ self.voxelGridSize = voxelGridSize def setPhotonScaleFactor(self, factor): """ Sets the density of the photon grid. A number of 1 means that for every bright voxel 1 photon will be spawned. A number of 4 for example means that for ever bright voxel 4x4x4 = 64 Photons will be spawned. """ self.photonScaleFactor = factor def setActive(self, active): """ Enables and disables this pass """ self.target.setActive(active) def initVoxelStorage(self): """ Creates t he 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) # Set appropriate filter types self.voxelGenTex.setMinfilter(SamplerState.FTNearest) self.voxelGenTex.setMagfilter(Texture.FTNearest) self.voxelGenTex.setWrapU(Texture.WMClamp) self.voxelGenTex.setWrapV(Texture.WMClamp) self.voxelGenTex.setWrapW(Texture.WMClamp) self.voxelGenTex.setClearColor(Vec4(0)) # Register texture MemoryMonitor.addTexture("Voxel Temp Texture", self.voxelGenTex) def getVoxelTex(self): """ Returns a handle to the generated voxel texture """ return self.voxelGenTex def clearGrid(self): """ Clears the voxel grid """ self.voxelGenTex.clearImage() def create(self): # Create voxelize camera self.voxelizeCamera = Camera("VoxelizeScene") self.voxelizeCamera.setCameraMask(BitMask32.bit(4)) self.voxelizeCameraNode = Globals.render.attachNewNode(self.voxelizeCamera) self.voxelizeLens = OrthographicLens() self.voxelizeLens.setFilmSize(self.voxelGridSize.x*2, self.voxelGridSize.y*2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize.x*2) self.voxelizeCamera.setLens(self.voxelizeLens) self.voxelizeCamera.setTagStateKey("VoxelizePassShader") Globals.render.setTag("VoxelizePassShader", "Default") # Create voxelize tareet self.target = RenderTarget("VoxelizePass") self.target.setSize(self.voxelGridResolution.x * self.photonScaleFactor) # self.target.setColorWrite(False) self.target.addColorTexture() self.target.setCreateOverlayQuad(False) self.target.setSource(self.voxelizeCameraNode, Globals.base.win) self.target.prepareSceneRender() self.target.setActive(False) self.target.getInternalRegion().setSort(-400) self.target.getInternalBuffer().setSort(-399) def voxelizeSceneFromDirection(self, gridPos, direction="x"): """ Voxelizes the scene from a given direction. This method handles setting the camera position aswell as the near and far plane. If the pass was disabled, it also enables this pass. """ assert(direction in "x y z".split()) self.setActive(True) if direction == "x": self.voxelizeLens.setFilmSize(self.voxelGridSize.y*2, self.voxelGridSize.z*2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize.x*2) self.voxelizeCameraNode.setPos(gridPos - Vec3(self.voxelGridSize.x, 0, 0)) self.voxelizeCameraNode.lookAt(gridPos) elif direction == "y": self.voxelizeLens.setFilmSize(self.voxelGridSize.x*2, self.voxelGridSize.z*2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize.y*2) self.voxelizeCameraNode.setPos(gridPos - Vec3(0, self.voxelGridSize.y, 0)) self.voxelizeCameraNode.lookAt(gridPos) elif direction == "z": self.voxelizeLens.setFilmSize(self.voxelGridSize.x*2, self.voxelGridSize.y*2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize.z*2) self.voxelizeCameraNode.setPos(gridPos + Vec3(0, 0, self.voxelGridSize.z)) self.voxelizeCameraNode.lookAt(gridPos) def setShaders(self): """ Creates the tag state and loades the voxelizer shader """ voxelizeShader = Shader.load(Shader.SLGLSL, "Shader/GI/Voxelize.vertex", "Shader/GI/Voxelize.fragment") # Create tag state initialState = NodePath("VoxelizerState") initialState.setShader(voxelizeShader, 500) initialState.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullNone)) initialState.setDepthWrite(False) initialState.setDepthTest(False) initialState.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MNone)) initialState.setShaderInput("giVoxelGenerationTex", self.voxelGenTex) # Apply tag state self.voxelizeCamera.setTagState("Default", initialState.getState()) return [voxelizeShader] def getOutputs(self): return { }
class DeferredScenePass(RenderPass): """ This is the main scene pass which generates the G-Buffer used for deferred rendering """ def __init__(self, pipeline): RenderPass.__init__(self) self.pipeline = pipeline def getID(self): return "DeferredScenePass" def getRequiredInputs(self): return { "readyState": "SceneReadyState" } def create(self): earlyZ = self.pipeline.settings.enableEarlyZ if earlyZ: self.prepassCam = Camera(Globals.base.camNode) self.prepassCam.setTagStateKey("EarlyZShader") self.prepassCam.setName("EarlyZCamera") self.prepassCamNode = Globals.base.camera.attachNewNode(self.prepassCam) Globals.render.setTag("EarlyZShader", "Default") else: self.prepassCam = None # modify default camera initial state initial = Globals.base.camNode.getInitialState() initialNode = NodePath("IntiialState") initialNode.setState(initial) if earlyZ: initialNode.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOff)) initialNode.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MEqual)) pass else: initialNode.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MLessEqual)) # initialNode.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff), 10000) # initialNode.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullClockwise), 10000) Globals.base.camNode.setInitialState(initialNode.getState()) self.target = RenderTarget("DeferredScenePass") self.target.addColorAndDepth() self.target.addAuxTextures(3) self.target.setAuxBits(16) self.target.setDepthBits(32) self.target.setCreateOverlayQuad(False) if earlyZ: self.target.prepareSceneRender(earlyZ=True, earlyZCam=self.prepassCamNode) else: self.target.prepareSceneRender() self.target.setClearColor(True, color=Vec4(1, 1, 0, 1)) if earlyZ: self.target.setClearDepth(False) else: self.target.setClearDepth(True) def registerEarlyZTagState(self, name, state): """ Registers a new tag state """ if not self.prepassCam: return # state.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullClockwise), 10000) state.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff), 10000) state.setAttrib(AlphaTestAttrib.make(AlphaTestAttrib.MNone, 1.0), 10000) state.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOn), 10000) state.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MLess), 10000) state.setAttrib(TransparencyAttrib.make(TransparencyAttrib.MNone), 10000) self.prepassCam.setTagState(name, state.getState()) def setShaders(self): self.registerEarlyZTagState("Default", NodePath("DefaultEarlyZPass")) return [] def getOutputs(self): return { "DeferredScenePass.wsNormal": lambda: self.target.getColorTexture(), "DeferredScenePass.velocity": lambda: self.target.getAuxTexture(0), "DeferredScenePass.depth": lambda: self.target.getDepthTexture(), "DeferredScenePass.data0": lambda: self.target.getColorTexture(), "DeferredScenePass.data1": lambda: self.target.getAuxTexture(0), "DeferredScenePass.data2": lambda: self.target.getAuxTexture(1), "DeferredScenePass.data3": lambda: self.target.getAuxTexture(2), } def setShaderInput(self, name, value): pass
class DeferredScenePass(RenderPass): """ This is the main scene pass which generates the G-Buffer used for deferred rendering """ def __init__(self, pipeline): RenderPass.__init__(self) self.pipeline = pipeline def getID(self): return "DeferredScenePass" def getRequiredInputs(self): return {"readyState": "SceneReadyState"} def create(self): earlyZ = self.pipeline.settings.enableEarlyZ if earlyZ: self.prepassCam = Camera(Globals.base.camNode) self.prepassCam.setTagStateKey("EarlyZShader") self.prepassCam.setName("EarlyZCamera") self.prepassCamNode = Globals.base.camera.attachNewNode( self.prepassCam) Globals.render.setTag("EarlyZShader", "Default") else: self.prepassCam = None # modify default camera initial state initial = Globals.base.camNode.getInitialState() initialNode = NodePath("IntiialState") initialNode.setState(initial) if earlyZ: initialNode.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOff)) initialNode.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MEqual)) pass else: initialNode.setAttrib( DepthTestAttrib.make(DepthTestAttrib.MLessEqual)) # initialNode.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff), 10000) # initialNode.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullClockwise), 10000) Globals.base.camNode.setInitialState(initialNode.getState()) self.target = RenderTarget("DeferredScenePass") self.target.addColorAndDepth() self.target.addAuxTextures(3) self.target.setAuxBits(16) self.target.setDepthBits(32) self.target.setCreateOverlayQuad(False) if earlyZ: self.target.prepareSceneRender(earlyZ=True, earlyZCam=self.prepassCamNode) else: self.target.prepareSceneRender() self.target.setClearColor(True, color=Vec4(1, 1, 0, 1)) if earlyZ: self.target.setClearDepth(False) else: self.target.setClearDepth(True) def registerEarlyZTagState(self, name, state): """ Registers a new tag state """ if not self.prepassCam: return # state.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullClockwise), 10000) state.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff), 10000) state.setAttrib(AlphaTestAttrib.make(AlphaTestAttrib.MNone, 1.0), 10000) state.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOn), 10000) state.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MLess), 10000) state.setAttrib(TransparencyAttrib.make(TransparencyAttrib.MNone), 10000) self.prepassCam.setTagState(name, state.getState()) def setShaders(self): self.registerEarlyZTagState("Default", NodePath("DefaultEarlyZPass")) return [] def getOutputs(self): return { "DeferredScenePass.wsNormal": lambda: self.target.getColorTexture(), "DeferredScenePass.velocity": lambda: self.target.getAuxTexture(0), "DeferredScenePass.depth": lambda: self.target.getDepthTexture(), "DeferredScenePass.data0": lambda: self.target.getColorTexture(), "DeferredScenePass.data1": lambda: self.target.getAuxTexture(0), "DeferredScenePass.data2": lambda: self.target.getAuxTexture(1), "DeferredScenePass.data3": lambda: self.target.getAuxTexture(2), } def setShaderInput(self, name, value): pass
class VoxelizePass(RenderPass): """ This pass manages voxelizing the scene from multiple directions to generate a 3D voxel grid. It handles the camera setup and provides a simple interface. """ def __init__(self, pipeline): RenderPass.__init__(self) self.pipeline = pipeline def getID(self): return "VoxelizePass" def getRequiredInputs(self): return { # Lighting "renderedLightsBuffer": "Variables.renderedLightsBuffer", "lights": "Variables.allLights", "shadowAtlasPCF": "ShadowScenePass.atlasPCF", "shadowAtlas": "ShadowScenePass.atlas", "shadowSources": "Variables.allShadowSources", "directionToFace": "Variables.directionToFaceLookup", "cameraPosition": "Variables.cameraPosition", "mainCam": "Variables.mainCam", "mainRender": "Variables.mainRender", } def setVoxelGridResolution(self, voxelGridResolution): """ Sets the voxel grid resolution, this is the amount of voxels in every direction, so the voxel grid will have voxelGridResolution**3 total voxels. """ self.voxelGridResolution = voxelGridResolution def setVoxelGridSize(self, voxelGridSize): """ Sets the size of the voxel grid in world space units. This is the size going from the mid of the voxel grid, so the effective voxel grid will have twice the size specified in voxelGridSize """ self.voxelGridSize = voxelGridSize def setGridResolutionMultiplier(self, factor): """ Sets the density of the voxel grid. """ self.gridResolutionMultiplier = factor def setActive(self, active): """ Enables and disables this pass """ if hasattr(self, "target"): self.target.setActive(active) def getVoxelTex(self): """ Returns a handle to the generated voxel texture """ return self.voxelGenTex def clearGrid(self): """ Clears the voxel grid """ self.voxelGenTex.clearImage() def create(self): # Create voxelize camera self.voxelizeCamera = Camera("VoxelizeCamera") self.voxelizeCamera.setCameraMask(BitMask32.bit(4)) self.voxelizeCameraNode = Globals.render.attachNewNode(self.voxelizeCamera) self.voxelizeLens = OrthographicLens() self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCamera.setLens(self.voxelizeLens) self.voxelizeCamera.setTagStateKey("VoxelizePassShader") Globals.render.setTag("VoxelizePassShader", "Default") # Create voxelize tareet self.target = RenderTarget("VoxelizePass") self.target.setSize(self.voxelGridResolution * self.gridResolutionMultiplier) if self.pipeline.settings.useDebugAttachments: self.target.addColorTexture() else: self.target.setColorWrite(False) self.target.setCreateOverlayQuad(False) self.target.setSource(self.voxelizeCameraNode, Globals.base.win) self.target.prepareSceneRender() self.target.setActive(False) # self.target.getInternalRegion().setSort(-400) # self.target.getInternalBuffer().setSort(-399) def voxelizeSceneFromDirection(self, gridPos, direction="x"): """ Voxelizes the scene from a given direction. This method handles setting the camera position aswell as the near and far plane. If the pass was disabled, it also enables this pass. """ assert direction in ["x", "y", "z"] self.setActive(True) if direction == "x": self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCameraNode.setPos(gridPos - Vec3(self.voxelGridSize, 0, 0)) self.voxelizeCameraNode.lookAt(gridPos) elif direction == "y": self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCameraNode.setPos(gridPos - Vec3(0, self.voxelGridSize, 0)) self.voxelizeCameraNode.lookAt(gridPos) elif direction == "z": self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCameraNode.setPos(gridPos + Vec3(0, 0, self.voxelGridSize)) self.voxelizeCameraNode.lookAt(gridPos) def setShaders(self): """ Creates the tag state and loades the voxelizer shader """ self.registerTagState("Default", NodePath("DefaultVoxelizeState")) return [] def registerTagState(self, name, state): """ Registers a new tag state """ state.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullNone)) state.setDepthWrite(False) state.setDepthTest(False) state.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MNone)) state.setShaderInput("voxelizeCam", self.voxelizeCameraNode) self.voxelizeCamera.setTagState(name, state.getState()) def setShaderInput(self, *args): Globals.base.render.setShaderInput(*args) def getOutputs(self): return {}
class LightManager(DebugObject): 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 # Tries to create debug text to show how many lights are currently visible # / rendered def _createDebugTexts(self): try: from FastText import FastText self.lightsVisibleDebugText = FastText(pos=Vec2( base.getAspectRatio() - 0.1, 0.84), rightAligned=True, color=Vec3(1, 0, 0), size=0.036) self.lightsUpdatedDebugText = FastText(pos=Vec2( base.getAspectRatio() - 0.1, 0.8), rightAligned=True, color=Vec3(1, 0, 0), size=0.036) except Exception, msg: self.debug("Could not load fast text:", msg) self.debug("Overlay is disabled because FastText wasn't loaded") self.lightsVisibleDebugText = None self.lightsUpdatedDebugText = None
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())
class VoxelizePass(RenderPass): """ This pass manages voxelizing the scene from multiple directions to generate a 3D voxel grid. It handles the camera setup and provides a simple interface. """ def __init__(self, pipeline): RenderPass.__init__(self) self.pipeline = pipeline def getID(self): return "VoxelizePass" def getRequiredInputs(self): return { # Lighting "renderedLightsBuffer": "Variables.renderedLightsBuffer", "lights": "Variables.allLights", "shadowAtlasPCF": "ShadowScenePass.atlasPCF", "shadowAtlas": "ShadowScenePass.atlas", "shadowSources": "Variables.allShadowSources", "directionToFace": "Variables.directionToFaceLookup", "cameraPosition": "Variables.cameraPosition", "mainCam": "Variables.mainCam", "mainRender": "Variables.mainRender", } def setVoxelGridResolution(self, voxelGridResolution): """ Sets the voxel grid resolution, this is the amount of voxels in every direction, so the voxel grid will have voxelGridResolution**3 total voxels. """ self.voxelGridResolution = voxelGridResolution def setVoxelGridSize(self, voxelGridSize): """ Sets the size of the voxel grid in world space units. This is the size going from the mid of the voxel grid, so the effective voxel grid will have twice the size specified in voxelGridSize """ self.voxelGridSize = voxelGridSize def setGridResolutionMultiplier(self, factor): """ Sets the density of the voxel grid. """ self.gridResolutionMultiplier = factor def setActive(self, active): """ Enables and disables this pass """ if hasattr(self, "target"): self.target.setActive(active) def getVoxelTex(self): """ Returns a handle to the generated voxel texture """ return self.voxelGenTex def clearGrid(self): """ Clears the voxel grid """ self.voxelGenTex.clearImage() def create(self): # Create voxelize camera self.voxelizeCamera = Camera("VoxelizeCamera") self.voxelizeCamera.setCameraMask(BitMask32.bit(4)) self.voxelizeCameraNode = Globals.render.attachNewNode( self.voxelizeCamera) self.voxelizeLens = OrthographicLens() self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCamera.setLens(self.voxelizeLens) self.voxelizeCamera.setTagStateKey("VoxelizePassShader") Globals.render.setTag("VoxelizePassShader", "Default") # Create voxelize tareet self.target = RenderTarget("VoxelizePass") self.target.setSize(self.voxelGridResolution * self.gridResolutionMultiplier) if self.pipeline.settings.useDebugAttachments: self.target.addColorTexture() else: self.target.setColorWrite(False) self.target.setCreateOverlayQuad(False) self.target.setSource(self.voxelizeCameraNode, Globals.base.win) self.target.prepareSceneRender() self.target.setActive(False) # self.target.getInternalRegion().setSort(-400) # self.target.getInternalBuffer().setSort(-399) def voxelizeSceneFromDirection(self, gridPos, direction="x"): """ Voxelizes the scene from a given direction. This method handles setting the camera position aswell as the near and far plane. If the pass was disabled, it also enables this pass. """ assert (direction in ["x", "y", "z"]) self.setActive(True) if direction == "x": self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCameraNode.setPos(gridPos - Vec3(self.voxelGridSize, 0, 0)) self.voxelizeCameraNode.lookAt(gridPos) elif direction == "y": self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCameraNode.setPos(gridPos - Vec3(0, self.voxelGridSize, 0)) self.voxelizeCameraNode.lookAt(gridPos) elif direction == "z": self.voxelizeLens.setFilmSize(self.voxelGridSize * 2, self.voxelGridSize * 2) self.voxelizeLens.setNearFar(0.0, self.voxelGridSize * 2) self.voxelizeCameraNode.setPos(gridPos + Vec3(0, 0, self.voxelGridSize)) self.voxelizeCameraNode.lookAt(gridPos) def setShaders(self): """ Creates the tag state and loades the voxelizer shader """ self.registerTagState("Default", NodePath("DefaultVoxelizeState")) return [] def registerTagState(self, name, state): """ Registers a new tag state """ state.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullNone)) state.setDepthWrite(False) state.setDepthTest(False) state.setAttrib(DepthTestAttrib.make(DepthTestAttrib.MNone)) state.setShaderInput("voxelizeCam", self.voxelizeCameraNode) self.voxelizeCamera.setTagState(name, state.getState()) def setShaderInput(self, *args): Globals.base.render.setShaderInput(*args) def getOutputs(self): return {}