def create(self): # Fetch the original texture size from the window size size = LVecBase2i(Globals.base.win.getXSize(), Globals.base.win.getYSize()) # Create the first downscale pass which reads the scene texture, does a # 2x2 inplace box filter, and then converts the result to luminance. # Using luminance allows faster downscaling, as we can use texelGather then self.downscalePass0 = RenderTarget("Downscale Initial") self.downscalePass0.addColorTexture() self.downscalePass0.setSize(size.x / 2, size.y / 2) self.downscalePass0.prepareOffscreenBuffer() # Store the current size of the pass workSizeX, workSizeY = int(size.x / 2), int(size.y / 2) self.downscalePasses = [] passIdx = 0 lastTex = self.downscalePass0.getColorTexture() # Scale the scene until there are only a few pixels left. Each pass does a # 4x4 inplace box filter, which is cheap because we can sample the luminance # only. while workSizeX * workSizeY > 128: workSizeX /= 4 workSizeY /= 4 passIdx += 1 scalePass = RenderTarget("Downscale Pass " + str(passIdx)) scalePass.setSize(workSizeX, workSizeY) scalePass.addColorTexture() scalePass.prepareOffscreenBuffer() scalePass.setShaderInput("luminanceTex", lastTex) lastTex = scalePass.getColorTexture() self.downscalePasses.append(scalePass) # Create the final pass which computes the average of all left pixels, # compares that with the last exposure and stores the difference. self.finalDownsamplePass = RenderTarget("Downscale Final") self.finalDownsamplePass.setSize(1, 1) # self.finalDownsamplePass.setColorBits(16) # self.finalDownsamplePass.addColorTexture() self.finalDownsamplePass.setColorWrite(False) self.finalDownsamplePass.prepareOffscreenBuffer() self.finalDownsamplePass.setShaderInput("luminanceTex", lastTex) self.finalDownsamplePass.setShaderInput("targetExposure", self.pipeline.settings.targetExposure) self.finalDownsamplePass.setShaderInput("adaptionSpeed", self.pipeline.settings.brightnessAdaptionSpeed) # Clear the storage in the beginning self.lastExposureStorage.setClearColor(Vec4(0)) self.lastExposureStorage.clearImage() # Set defines and other inputs self.finalDownsamplePass.setShaderInput("lastExposureTex", self.lastExposureStorage) self.pipeline.renderPassManager.registerDefine("USE_DYNAMIC_EXPOSURE", 1)
class LightCullingPass(RenderPass): """ This pass takes a list of all rendered lights and performs light culling per tile. The result is stored in a buffer which then can be used by the lighting pass to render the lights. The buffer maps 1 pixel per tile, so when using a tile size of 32 then there are 50x30 pixels if the window has a size of 1600*960. To cull the lights, the scene depth texture is analyzed and the minimum and maximum depth per tile is extracted. We could use compute shaders for this task, but they are horribly slow. """ def __init__(self): RenderPass.__init__(self) def getID(self): return "LightCullingPass" def setSize(self, sizeX, sizeY): """ Sets the amount of tiles. This is usally screenSize/tileSize """ self.size = LVecBase2i(sizeX, sizeY) def setPatchSize(self, patchSizeX, patchSizeY): """ Sets the tile size in pixels """ self.patchSize = LVecBase2i(patchSizeX, patchSizeY) def getRequiredInputs(self): return { "renderedLightsBuffer": "Variables.renderedLightsBuffer", "lights": "Variables.allLights", "depth": "DeferredScenePass.depth", "mainCam": "Variables.mainCam", "mainRender": "Variables.mainRender", "cameraPosition": "Variables.cameraPosition" } def create(self): self.target = RenderTarget("ComputeLightTileBounds") self.target.setSize(self.size.x, self.size.y) self.target.addColorTexture() self.target.prepareOffscreenBuffer() self.target.getColorTexture().setMagfilter(SamplerState.FTNearest) self.makePerTileStorage() self.target.setShaderInput("destinationBuffer", self.lightPerTileBuffer) def makePerTileStorage(self): """ Creates the buffer which stores which lights affect which tiles. The first 16 entries are counters which store how many lights of that type were rendered, and the following entries store the light indices """ self.tileStride = 0 self.tileStride += 16 # Counters for the light types self.tileStride += LightLimits.maxPerTileLights["PointLight"] self.tileStride += LightLimits.maxPerTileLights["PointLightShadow"] self.tileStride += LightLimits.maxPerTileLights["DirectionalLight"] self.tileStride += LightLimits.maxPerTileLights["DirectionalLightShadow"] self.tileStride += LightLimits.maxPerTileLights["SpotLight"] self.tileStride += LightLimits.maxPerTileLights["SpotLightShadow"] tileBufferSize = self.size.x * self.size.y * self.tileStride self.lightPerTileBuffer = Texture("LightsPerTileBuffer") self.lightPerTileBuffer.setupBufferTexture( tileBufferSize, Texture.TInt, Texture.FR32i, GeomEnums.UHDynamic) MemoryMonitor.addTexture("Light Per Tile Buffer", self.lightPerTileBuffer) def getDefines(self): return { "LIGHTING_PER_TILE_STRIDE": self.tileStride } def setShaders(self): shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/PrecomputeLights.fragment") self.target.setShader(shader) return [shader] def getOutputs(self): return { "LightCullingPass.lightsPerTile": lambda: self.lightPerTileBuffer }
loadPrcFileData("", "textures-power-2 none") import os import direct.directbase.DirectStart from Code.Globals import Globals Globals.load(base) from Code.RenderTarget import RenderTarget import shutil sz = 2048 target = RenderTarget() target.setSize(sz, sz) target.addColorTexture() target.setColorBits(16) target.prepareOffscreenBuffer() vertex_shader = """ #version 400 uniform mat4 p3d_ModelViewProjectionMatrix; in vec4 p3d_Vertex; out vec2 texcoord; void main() { gl_Position = vec4(p3d_Vertex.x, p3d_Vertex.z, 0, 1); texcoord = sign(p3d_Vertex.xz * 0.5 + 0.5);
import os import direct.directbase.DirectStart from Code.Globals import Globals Globals.load(base) from Code.RenderTarget import RenderTarget import shutil sz = 2048 target = RenderTarget() target.setSize(sz, sz) target.addColorTexture() target.setColorBits(16) target.prepareOffscreenBuffer() vertex_shader = """ #version 400 uniform mat4 p3d_ModelViewProjectionMatrix; in vec4 p3d_Vertex; out vec2 texcoord; void main() { gl_Position = vec4(p3d_Vertex.x, p3d_Vertex.z, 0, 1); texcoord = sign(p3d_Vertex.xz * 0.5 + 0.5);
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 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 * 4 ) 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".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 DynamicExposurePass(RenderPass): """ This pass handles the dynamic exposure feature, it downscales the Scene to get the average brightness and then outputs a new exposure which can be used by the lighting pass. """ def __init__(self, pipeline): RenderPass.__init__(self) self.pipeline = pipeline # Create the storage for the exposure. We cannot simply use the color output # as the RenderTargetMatcher would have problems with that (Circular Reference) self.lastExposureStorage = Texture("Last Exposure") self.lastExposureStorage.setup2dTexture(1, 1, Texture.TFloat, Texture.FR32) # Registers the texture so the lighting pass can use it self.pipeline.renderPassManager.registerStaticVariable( "dynamicExposureTex", self.lastExposureStorage) def getID(self): return "DynamicExposurePass" def getRequiredInputs(self): return { "colorTex": "LightingPass.resultTex", "dt": "Variables.frameDelta" } def create(self): # Fetch the original texture size from the window size size = LVecBase2i(Globals.base.win.getXSize(), Globals.base.win.getYSize()) # Create the first downscale pass which reads the scene texture, does a # 2x2 inplace box filter, and then converts the result to luminance. # Using luminance allows faster downscaling, as we can use texelGather then self.downscalePass0 = RenderTarget("Downscale Initial") self.downscalePass0.addColorTexture() self.downscalePass0.setSize(size.x / 2, size.y / 2) self.downscalePass0.prepareOffscreenBuffer() # Store the current size of the pass workSizeX, workSizeY = int(size.x / 2), int(size.y / 2) self.downscalePasses = [] passIdx = 0 lastTex = self.downscalePass0.getColorTexture() # Scale the scene until there are only a few pixels left. Each pass does a # 4x4 inplace box filter, which is cheap because we can sample the luminance # only. while workSizeX * workSizeY > 128: workSizeX /= 4 workSizeY /= 4 passIdx += 1 scalePass = RenderTarget("Downscale Pass " + str(passIdx)) scalePass.setSize(workSizeX, workSizeY) scalePass.addColorTexture() scalePass.prepareOffscreenBuffer() scalePass.setShaderInput("luminanceTex", lastTex) lastTex = scalePass.getColorTexture() self.downscalePasses.append(scalePass) # Create the final pass which computes the average of all left pixels, # compares that with the last exposure and stores the difference. self.finalDownsamplePass = RenderTarget("Downscale Final") self.finalDownsamplePass.setSize(1, 1) # self.finalDownsamplePass.setColorBits(16) # self.finalDownsamplePass.addColorTexture() self.finalDownsamplePass.setColorWrite(False) self.finalDownsamplePass.prepareOffscreenBuffer() self.finalDownsamplePass.setShaderInput("luminanceTex", lastTex) self.finalDownsamplePass.setShaderInput("targetExposure", self.pipeline.settings.targetExposure) self.finalDownsamplePass.setShaderInput("adaptionSpeed", self.pipeline.settings.brightnessAdaptionSpeed) # Clear the storage in the beginning self.lastExposureStorage.setClearColor(Vec4(0)) self.lastExposureStorage.clearImage() # Set defines and other inputs self.finalDownsamplePass.setShaderInput("lastExposureTex", self.lastExposureStorage) self.pipeline.renderPassManager.registerDefine("USE_DYNAMIC_EXPOSURE", 1) def setShaders(self): shaderFirstPass = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/DownsampleFirstPass.fragment") self.downscalePass0.setShader(shaderFirstPass) shaderDownsample = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/Downsample.fragment") for scalePass in self.downscalePasses: scalePass.setShader(shaderDownsample) shaderFinal = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/DownsampleFinalPass.fragment") self.finalDownsamplePass.setShader(shaderFinal) return [shaderFirstPass, shaderDownsample, shaderFinal] def setShaderInput(self, name, value, *args): self.downscalePass0.setShaderInput(name, value, *args) self.finalDownsamplePass.setShaderInput(name, value, *args) def getOutputs(self): return { }
shader = Shader.make(Shader.SLGLSL, vertexShader, fragmentShader) size = 1024 mipidx = 0 blurF = 0.5 while size > 1: size /= 2 blurF *= 1.5 target = RenderTarget("precompute cubemap") target.addColorTexture() target.setSize(size, size) target.prepareOffscreenBuffer() stex = target.getColorTexture() target.setShader(shader) target.setShaderInput("sourceMap", envmap) target.setShaderInput("mipIndex", mipidx) target.setShaderInput("blurFactor", blurF) for i in xrange(6): print "Generating face", i,"for mipmap",mipidx target.setShaderInput("directionIndex", i) base.graphicsEngine.renderFrame() base.graphicsEngine.extractTextureData(stex, base.win.getGsg())
resultColor = vec4(sum, 1); } """ shader = Shader.make(Shader.SLGLSL, vertexShader, fragmentShader) size = 1024 mipidx = 0 blurF = 0.5 while size > 1: size /= 2 blurF *= 1.5 target = RenderTarget("precompute cubemap") target.addColorTexture() target.setSize(size, size) target.prepareOffscreenBuffer() stex = target.getColorTexture() target.setShader(shader) target.setShaderInput("sourceMap", envmap) target.setShaderInput("mipIndex", mipidx) target.setShaderInput("blurFactor", blurF) for i in xrange(6): print "Generating face", i, "for mipmap", mipidx target.setShaderInput("directionIndex", i) base.graphicsEngine.renderFrame() base.graphicsEngine.extractTextureData(stex, base.win.getGsg())
class ShadowScenePass(RenderPass): """ This pass manages rendering the scene from the perspective of the shadow sources to generate the shadow maps. It also handles creating and managing the different regions of the shadow atlas, aswell as the initial state of all cameras assigned to the regions """ def __init__(self): RenderPass.__init__(self) self.maxRegions = 8 self.shadowScene = Globals.base.render def setMaxRegions(self, maxRegions): """ Sets the maximum amount of regions the atlas has. This is usually equal to the maximum number of shadow updates per frame """ self.maxRegions = maxRegions def getID(self): return "ShadowScenePass" def getRequiredInputs(self): return { "numUpdates": "Variables.numShadowUpdates", "updateSources": "Variables.shadowUpdateSources" } def setShaders(self): casterShader = Shader.load(Shader.SLGLSL, "Shader/DefaultShaders/ShadowCasting/vertex.glsl", "Shader/DefaultShaders/ShadowCasting/fragment.glsl") initialState = NodePath("ShadowCasterState") initialState.setShader(casterShader, 100) initialState.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff)) for camera in self.shadowCameras: camera.node().setTagState("Default", initialState.getState()) casterShaderTransparent = Shader.load(Shader.SLGLSL, "Shader/DefaultShaders/TransparentShadowCasting/vertex.glsl", "Shader/DefaultShaders/TransparentShadowCasting/fragment.glsl") initialState = NodePath("ShadowCasterStateTransparent") initialState.setShader(casterShaderTransparent, 100) initialState.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff)) for camera in self.shadowCameras: camera.node().setTagState("Transparent", initialState.getState()) return [casterShader, casterShaderTransparent] def setSize(self, size): """ Sets the shadow atlas size """ self.size = size def setActiveRegionCount(self, activeCount): """ Sets the number of active regions, disabling all other regions. If the count is less than 1, completely disables the pass """ if activeCount < 1: self.target.setActive(False) for region in self.renderRegions: region.setActive(False) else: self.target.setActive(True) for index, region in enumerate(self.renderRegions): if index < activeCount: region.setActive(True) pass else: region.setActive(False) def setRegionDimensions(self, index, l, r, b, t): """ Sets the dimensions of the n-th region to the given dimensions """ self.renderRegions[index].setDimensions(l, r, b, t) def getRegionCamera(self, index): """ Returns the camera of the n-th region """ return self.shadowCameras[index] def create(self): # Create the atlas target self.target = RenderTarget("ShadowAtlas") self.target.setSize(self.size) self.target.addDepthTexture() self.target.setDepthBits(32) self.target.setColorWrite(False) self.target.setCreateOverlayQuad(False) # self.target.setActive(False) self.target.setSource( NodePath(Camera("tmp")), Globals.base.win) self.target.prepareSceneRender() self.target.setClearDepth(False) # Set the appropriate filter modes dTex = self.target.getDepthTexture() dTex.setWrapU(SamplerState.WMClamp) dTex.setWrapV(SamplerState.WMClamp) # Remove the default postprocess quad # self.target.getQuad().node().removeAllChildren() # self.target.getInternalRegion().setSort(-200) self.target.getInternalRegion().disableClears() self.target.getInternalBuffer().disableClears() # self.target.getInternalBuffer().setSort(-300) # Create default initial state initialState = NodePath("InitialState") initialState.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.COff)) # Create a camera for each update self.shadowCameras = [] for i in xrange(self.maxRegions): shadowCam = Camera("ShadowComputeCamera") shadowCam.setTagStateKey("ShadowPassShader") shadowCam.setInitialState(initialState.getState()) shadowCam.setCameraMask(BitMask32.bit(3)) shadowCamNode = self.shadowScene.attachNewNode(shadowCam) self.shadowCameras.append(shadowCamNode) # Create regions self.renderRegions = [] buff = self.target.getInternalBuffer() for i in xrange(self.maxRegions): dr = buff.makeDisplayRegion() dr.setSort(1000) dr.setClearDepthActive(True) dr.setClearDepth(1.0) # dr.setClearColorActive(False) # dr.setClearColor(Vec4(1,1,1,1)) dr.setCamera(self.shadowCameras[i]) dr.setActive(False) self.renderRegions.append(dr) self.pcfSampleState = SamplerState() self.pcfSampleState.setMinfilter(SamplerState.FTShadow) self.pcfSampleState.setMagfilter(SamplerState.FTShadow) self.pcfSampleState.setWrapU(SamplerState.WMClamp) self.pcfSampleState.setWrapV(SamplerState.WMClamp) # Globals.render.setTag("ShadowPassShader", "Default") def setShaderInput(self, name, val, *args): self.shadowScene.setShaderInput(name, val, *args) def getOutputs(self): return { "ShadowScenePass.atlas": lambda: self.target.getDepthTexture(), "ShadowScenePass.atlasPCF": lambda: (self.target.getDepthTexture(), self.pcfSampleState), }