def renderQuadInto(self, xsize, ysize, colortex=None, cmode = GraphicsOutput.RTMBindOrCopy, auxtex = None): buffer = self.createBuffer("filter-stage", xsize, ysize, colortex, cmode, auxtex) if (buffer == None): return None cm = CardMaker("filter-stage-quad") cm.setFrameFullscreenQuad() quad = NodePath(cm.generate()) quad.setDepthTest(0) quad.setDepthWrite(0) quad.setColor(Vec4(1,0.5,0.5,1)) quadcamnode = Camera("filter-quad-cam") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) quadcamnode.setLens(lens) quadcam = quad.attachNewNode(quadcamnode) buffer.getDisplayRegion(0).setCamera(quadcam) buffer.getDisplayRegion(0).setActive(1) return quad, buffer
def _makeFullscreenCam(self): """ Create a orthographic camera for this buffer """ bufferCam = Camera("BufferCamera") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) bufferCam.setLens(lens) bufferCam.setCullBounds(OmniBoundingVolume()) return bufferCam
def renderQuadInto(self, mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None): """ Creates an offscreen buffer for an intermediate computation. Installs a quad into the buffer. Returns the fullscreen quad. The size of the buffer is initially equal to the size of the main window. The parameters 'mul', 'div', and 'align' can be used to adjust that size. """ texgroup = (depthtex, colortex, auxtex0, auxtex1) winx, winy = self.getScaledSize(mul, div, align) depthbits = bool(depthtex != None) buffer = self.createBuffer("filter-stage", winx, winy, texgroup, depthbits) if (buffer == None): return None cm = CardMaker("filter-stage-quad") cm.setFrameFullscreenQuad() quad = NodePath(cm.generate()) quad.setDepthTest(0) quad.setDepthWrite(0) quad.setColor(1, 0.5, 0.5, 1) quadcamnode = Camera("filter-quad-cam") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) quadcamnode.setLens(lens) quadcam = quad.attachNewNode(quadcamnode) dr = buffer.makeDisplayRegion((0, 1, 0, 1)) dr.disableClears() dr.setCamera(quadcam) dr.setActive(True) dr.setScissorEnabled(False) # This clear stage is important if the buffer is padded, so that # any pixels accidentally sampled in the padded region won't # be reading from unititialised memory. buffer.setClearColor((0, 0, 0, 1)) buffer.setClearColorActive(True) self.buffers.append(buffer) self.sizes.append((mul, div, align)) return quad
def makeCamera(self, win, sort=0, scene=None, displayRegion=(0, 1, 0, 1), stereo=None, aspectRatio=None, clearDepth=0, clearColor=None, lens=None, camName='cam', mask=None, useCamera=None): """ Makes a new 3-d camera associated with the indicated window, and creates a display region in the indicated subrectangle. If stereo is True, then a stereo camera is created, with a pair of DisplayRegions. If stereo is False, then a standard camera is created. If stereo is None or omitted, a stereo camera is created if the window says it can render in stereo. If useCamera is not None, it is a NodePath to be used as the camera to apply to the window, rather than creating a new camera. """ # self.camera is the parent node of all cameras: a node that # we can move around to move all cameras as a group. if self.camera == None: # We make it a ModelNode with the PTLocal flag, so that # a wayward flatten operations won't attempt to mangle the # camera. self.camera = self.render.attachNewNode(ModelNode('camera')) self.camera.node().setPreserveTransform(ModelNode.PTLocal) builtins.camera = self.camera self.mouse2cam.node().setNode(self.camera.node()) if useCamera: # Use the existing camera node. cam = useCamera camNode = useCamera.node() assert (isinstance(camNode, Camera)) lens = camNode.getLens() cam.reparentTo(self.camera) else: # Make a new Camera node. camNode = Camera(camName) if lens == None: lens = PerspectiveLens() if aspectRatio == None: aspectRatio = self.getAspectRatio(win) lens.setAspectRatio(aspectRatio) cam = self.camera.attachNewNode(camNode) if lens != None: camNode.setLens(lens) if scene != None: camNode.setScene(scene) if mask != None: if (isinstance(mask, int)): mask = BitMask32(mask) camNode.setCameraMask(mask) if self.cam == None: self.cam = cam self.camNode = camNode self.camLens = lens self.camList.append(cam) # Now, make a DisplayRegion for the camera. if stereo is not None: if stereo: dr = win.makeStereoDisplayRegion(*displayRegion) else: dr = win.makeMonoDisplayRegion(*displayRegion) else: dr = win.makeDisplayRegion(*displayRegion) dr.setSort(sort) dr.disableClears() # By default, we do not clear 3-d display regions (the entire # window will be cleared, which is normally sufficient). But # we will if clearDepth is specified. if clearDepth: dr.setClearDepthActive(1) if clearColor: dr.setClearColorActive(1) dr.setClearColor(clearColor) dr.setCamera(cam) return cam
class ShadowSource(DebugObject, ShaderStructElement): """ This class can be seen as a camera. It stores the necessary data to generate and store the shadow map for the assigned lens (like computing the MVP), and also stores information about the shadowmap, like position in the shadow atlas, or resolution. Each ShadowSource has a unique index, which is used by the lights to identify which sources belong to it. """ # Store a global index for assigning unique ids to the instances _GlobalShadowIndex = 999 @classmethod def getExposedAttributes(self): return { "resolution": "int", "atlasPos": "vec2", "mvp": "mat4", "nearPlane": "float", "farPlane": "float" } @classmethod def _generateUID(self): """ Generates an uid and returns that """ self._GlobalShadowIndex += 1 return self._GlobalShadowIndex def __init__(self): """ Creates a new ShadowSource. After the creation, a lens can be added with setupPerspectiveLens or setupOrtographicLens. """ self.index = self._generateUID() DebugObject.__init__(self, "ShadowSource-" + str(self.index)) ShaderStructElement.__init__(self) self.valid = False self.camera = Camera("ShadowSource-" + str(self.index)) self.camera.setActive(False) self.cameraNode = NodePath(self.camera) self.cameraNode.reparentTo(Globals.render.find("RPCameraDummys")) self.cameraNode.hide() self.resolution = 512 self.atlasPos = Vec2(0) self.doesHaveAtlasPos = False self.sourceIndex = 0 self.mvp = UnalignedLMatrix4f() self.sourceIndex = -1 self.nearPlane = 0.0 self.farPlane = 1000.0 self.converterYUR = None self.transforMat = TransformState.makeMat( Mat4.convertMat( Globals.base.win.getGsg().getInternalCoordinateSystem(), CSZupRight)) def cleanup(self): """ Cleans up the shadow source """ self.cameraNode.removeNode() def setFilmSize(self, size_x, size_y): """ Sets the film size of the source, this is equivalent to setFilmSize on a Lens. """ self.lens.setFilmSize(size_x, size_y) self.rebuildMatrixCache() def getLens(self): """ Returns the source lens """ return self.lens def getSourceIndex(self): """ Returns the assigned source index. The source index is the index of the ShadowSource in the ShadowSources array of the assigned Light. """ return self.sourceIndex def getUID(self): """ Returns the uid of the shadow source """ return self.index def setSourceIndex(self, index): """ Sets the source index of this source. This is called by the light, as only the light knows at which position this source is in the Sources array. """ self.sourceIndex = index def computeMVP(self): """ Computes the modelViewProjection matrix for the lens. Actually, this is the worldViewProjection matrix, but for convenience it is called mvp. """ self.rebuildMatrixCache() projMat = self.converterYUR # modelViewMat = self.transforMat.invertCompose( modelViewMat = Globals.render.getTransform(self.cameraNode).getMat() return UnalignedLMatrix4f(modelViewMat * projMat) def assignAtlasPos(self, x, y): """ Assigns this source a position in the shadow atlas. This is called by the shadow atlas. Coordinates are float from 0 .. 1 """ self.atlasPos = Vec2(x, y) self.doesHaveAtlasPos = True def update(self): """ Updates the shadow source. Currently only recomputes the mvp and triggers an array update """ self.mvp = self.computeMVP() self.onPropertyChanged() def getAtlasPos(self): """ Returns the assigned atlas pos, if present. Coordinates are float from 0 .. 1 """ return self.atlasPos def hasAtlasPos(self): """ Returns Whether this ShadowSource has already a position in the shadow atlas, or is currently unassigned """ return self.doesHaveAtlasPos def removeFromAtlas(self): """ Deletes the atlas coordinates, this gets called by the atlas after the Source got removed from the atlas """ self.doesHaveAtlasPos = False self.atlasPos = Vec2(0) def setResolution(self, resolution): """ Sets the resolution in pixels of this shadow source. Has to be a multiple of the tileSize specified in LightManager """ assert (resolution > 1 and resolution <= 8192) self.resolution = resolution def getResolution(self): """ Returns the resolution of the shadow source in pixels """ return self.resolution def setupPerspectiveLens(self, near=0.1, far=100.0, fov=(90, 90)): """ Setups a PerspectiveLens with a given near plane, far plane and FoV. The FoV is a tuple in the format (Horizontal FoV, Vertical FoV) """ self.lens = PerspectiveLens() self.lens.setNearFar(near, far) self.lens.setFov(fov[0], fov[1]) self.camera.setLens(self.lens) self.nearPlane = near self.farPlane = far self.rebuildMatrixCache() def setLens(self, lens): """ Setups the ShadowSource to use an external lens """ self.lens = lens self.camera.setLens(self.lens) self.nearPlane = lens.getNear() self.farPlane = lens.getFar() self.nearPlane = 0.5 self.farPlane = 50.0 self.rebuildMatrixCache() def setupOrtographicLens(self, near=0.1, far=100.0, filmSize=(512, 512)): """ Setups a OrtographicLens with a given near plane, far plane and film size. The film size is a tuple in the format (filmWidth, filmHeight) in world space. """ self.lens = OrthographicLens() self.lens.setNearFar(near, far) self.lens.setFilmSize(*filmSize) self.camera.setLens(self.lens) self.nearPlane = near self.farPlane = far self.rebuildMatrixCache() def rebuildMatrixCache(self): """ Internal method to precompute a part of the MVP to improve performance""" self.converterYUR = self.lens.getProjectionMat() def setPos(self, pos): """ Sets the position of the source in world space """ self.cameraNode.setPos(pos) def getPos(self): """ Returns the position of the source in world space """ return self.cameraNode.getPos() def setHpr(self, hpr): """ Sets the rotation of the source in world space """ self.cameraNode.setHpr(hpr) def lookAt(self, pos): """ Looks at a point (in world space) """ self.cameraNode.lookAt(pos.x, pos.y, pos.z) def invalidate(self): """ Invalidates this shadow source, means telling the LightManager that the shadow map for this light should be rebuilt. Otherwise it won't get refreshed. """ self.valid = False def setValid(self): """ The LightManager calls this after the shadow map got updated successfully """ self.valid = True def isValid(self): """ Returns wether the shadow map is still valid or should be refreshed """ return self.valid def __repr__(self): """ Returns a representative string of this instance """ return "ShadowSource[id=" + str(self.index) + "]" def __hash__(self): return self.index def onUpdated(self): """ Gets called when shadow source was updated """
class 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 SpotLight(Light, DebugObject): """ This light type simulates a SpotLight. It has a position and an orientation. """ def __init__(self): """ Creates a new spot light. """ Light.__init__(self) DebugObject.__init__(self, "SpotLight") self.typeName = "SpotLight" self.nearPlane = 0.5 self.radius = 30.0 self.spotSize = Vec2(30, 30) # Used to compute the MVP self.ghostCamera = Camera("PointLight") self.ghostCamera.setActive(False) self.ghostLens = PerspectiveLens() self.ghostLens.setFov(130) self.ghostCamera.setLens(self.ghostLens) self.ghostCameraNode = NodePath(self.ghostCamera) self.ghostCameraNode.reparentTo(Globals.render) self.ghostCameraNode.hide() def getLightType(self): """ Internal method to fetch the type of this light, used by Light """ return LightType.Spot def _updateLens(self): """ Internal method which gets called when the lens properties changed """ for source in self.shadowSources: source.rebuildMatrixCache() def cleanup(self): """ Internal method which gets called when the light got deleted """ self.ghostCameraNode.removeNode() Light.cleanup(self) def setFov(self, fov): """ Sets the field of view of the spotlight """ assert(fov > 1 and fov < 180) self.ghostLens.setFov(fov) self._updateLens() def setPos(self, pos): """ Sets the position of the spotlight """ self.ghostCameraNode.setPos(pos) Light.setPos(self, pos) def lookAt(self, pos): """ Makes the spotlight look at the given position """ self.ghostCameraNode.lookAt(pos) def _computeAdditionalData(self): """ Internal method to recompute the spotlight MVP """ self.ghostCameraNode.setPos(self.position) projMat = self.ghostLens.getProjectionMat() modelViewMat = Globals.render.getTransform(self.ghostCameraNode).getMat() self.mvp = modelViewMat * projMat def _computeLightBounds(self): """ Recomputes the bounds of this light. For a SpotLight, we for now use a simple BoundingSphere """ self.bounds = BoundingSphere(Point3(self.position), self.radius * 2.0) def setNearFar(self, near, far): """ Sets the near and far plane of the spotlight """ self.nearPlane = near self.radius = far self.ghostLens.setNearFar(near, far) self._updateLens() def _updateDebugNode(self): """ Internal method to generate new debug geometry. """ debugNode = NodePath("SpotLightDebugNode") # Create the inner image cm = CardMaker("SpotLightDebug") cm.setFrameFullscreenQuad() innerNode = NodePath(cm.generate()) innerNode.setTexture(Globals.loader.loadTexture("Data/GUI/Visualization/SpotLight.png")) innerNode.setBillboardPointEye() innerNode.reparentTo(debugNode) innerNode.setPos(self.position) innerNode.setColorScale(1,1,0,1) # Create the outer lines lineNode = debugNode.attachNewNode("lines") currentNodeTransform = render.getTransform(self.ghostCameraNode).getMat() currentCamTransform = self.ghostLens.getProjectionMat() currentRelativeCamPos = self.ghostCameraNode.getPos(render) currentCamBounds = self.ghostLens.makeBounds() currentCamBounds.xform(self.ghostCameraNode.getMat(render)) p = lambda index: currentCamBounds.getPoint(index) # Make a circle at the bottom frustumBottomCenter = (p(0) + p(1) + p(2) + p(3)) * 0.25 upVector = (p(0) + p(1)) / 2 - frustumBottomCenter rightVector = (p(1) + p(2)) / 2 - frustumBottomCenter points = [] for idx in xrange(64): rad = idx / 64.0 * math.pi * 2.0 pos = upVector * math.sin(rad) + rightVector * math.cos(rad) pos += frustumBottomCenter points.append(pos) frustumLine = self._createDebugLine(points, True) frustumLine.setColorScale(1,1,0,1) frustumLine.reparentTo(lineNode) # Create frustum lines which connect the origin to the bottom circle pointArrays = [ [self.position, frustumBottomCenter + upVector], [self.position, frustumBottomCenter - upVector], [self.position, frustumBottomCenter + rightVector], [self.position, frustumBottomCenter - rightVector], ] for pointArray in pointArrays: frustumLine = self._createDebugLine(pointArray, False) frustumLine.setColorScale(1,1,0,1) frustumLine.reparentTo(lineNode) # Create line which is in the direction of the spot light startPoint = (p(0) + p(1) + p(2) + p(3)) * 0.25 endPoint = (p(4) + p(5) + p(6) + p(7)) * 0.25 line = self._createDebugLine([startPoint, endPoint], False) line.setColorScale(1,1,1,1) line.reparentTo(lineNode) # Remove the old debug node self.debugNode.node().removeAllChildren() # Attach the new debug node debugNode.reparentTo(self.debugNode) # self.debugNode.flattenStrong() def _initShadowSources(self): """ Internal method to init the shadow sources """ source = ShadowSource() source.setResolution(self.shadowResolution) source.setLens(self.ghostLens) self._addShadowSource(source) def _updateShadowSources(self): """ Recomputes the position of the shadow source """ self.shadowSources[0].setPos(self.position) self.shadowSources[0].setHpr(self.ghostCameraNode.getHpr()) def __repr__(self): """ Generates a string representation of this instance """ return "SpotLight[]"
def make_camera(self, output, sort=0, dr_dims=(0, 1, 0, 1), aspect_ratio=None, clear_depth=False, clear_color=None, lens=None, cam_name="camera0", mask=None): """ Makes a new 3-d camera associated with the indicated window, and creates a display region in the indicated subrectangle. If stereo is True, then a stereo camera is created, with a pair of DisplayRegions. If stereo is False, then a standard camera is created. If stereo is None or omitted, a stereo camera is created if the window says it can render in stereo. If useCamera is not None, it is a NodePath to be used as the camera to apply to the window, rather than creating a new camera. Args: output (GraphicsOutput): Output object. Keyword Args: sort (int): Sort order. dr_dims (Iterable, 4): DisplayRegion dimensions. aspect_ratio (float): Aspect ratio. clear_depth (bool): Indicator to clear depth buffer. clear_color (bool): Indicator to clear color buffer. lens (Lens): Lens object. cam_name (str): Window name. mask (BitMask32): Bit mask that indicates which objects to render. Return: (NodePath): Camera nodepath. """ # self.cameras is the parent node of all cameras: a node that # we can move around to move all cameras as a group. if self.cameras is None: # We make it a ModelNode with the PTLocal flag, so that a # wayward flatten operations won't attempt to mangle the # camera. self.cameras = self.root.attachNewNode(ModelNode("cameras")) self.cameras.node().setPreserveTransform(ModelNode.PTLocal) # Make a new Camera node. cam_node = Camera(cam_name) if lens is None: lens = PerspectiveLens() if aspect_ratio is None: aspect_ratio = self.get_aspect_ratio(output) lens.setAspectRatio(aspect_ratio) lens.setNear(0.1) lens.setFar(1000.0) if lens is not None: cam_node.setLens(lens) camera = self.cameras.attachNewNode(cam_node) # Masks out part of scene from camera if mask is not None: if (isinstance(mask, int)): mask = BitMask32(mask) cam_node.setCameraMask(mask) # Make a display region dr = output.makeDisplayRegion(*dr_dims) # By default, we do not clear 3-d display regions (the entire # window will be cleared, which is normally sufficient). But # we will if clearDepth is specified. if clear_depth: dr.setClearDepthActive(1) if clear_color: dr.setClearColorActive(1) dr.setClearColor(clear_color) dr.setSort(sort) dr.setCamera(camera) dr.setActive(True) return camera
class ShadowSource(DebugObject): _GlobalShadowIndex = 1000 @classmethod def getExposedAttributes(self): return { "resolution": "int", "atlasPos": "vec2", "mvp": "mat4", "nearPlane": "float", "farPlane": "float" } def __init__(self): DebugObject.__init__(self, "ShadowSource") self.valid = False ShadowSource._GlobalShadowIndex += 1 self.index = ShadowSource._GlobalShadowIndex self.camera = Camera("ShadowSource-" + str(self.index)) self.cameraNode = NodePath(self.camera) self.cameraNode.reparentTo(render) # self.camera.showFrustum() self.resolution = 1024 self.atlasPos = Vec2(0) self.doesHaveAtlasPos = False self.sourceIndex = 0 self.mvp = Mat4() self.sourceIndex = -1 self.nearPlane = 0.0 self.farPlane = 1000.0 def getSourceIndex(self): return self.sourceIndex def getUid(self): return self.index def __repr__(self): return "ShadowSource[id=" + str(self.index) + "]" def setSourceIndex(self, index): # self.debug("Assigning index", index) self.sourceIndex = index def computeMVP(self): projMat = Mat4.convertMat( CSYupRight, self.lens.getCoordinateSystem()) * self.lens.getProjectionMat() transformMat = TransformState.makeMat( Mat4.convertMat(base.win.getGsg().getInternalCoordinateSystem(), CSZupRight)) modelViewMat = transformMat.invertCompose( render.getTransform(self.cameraNode)).getMat() self.mvp = UnalignedLMatrix4f(modelViewMat * projMat) def assignAtlasPos(self, x, y): # self.debug("Assigning atlas pos", x, "/", y) self.atlasPos = Vec2(x, y) self.doesHaveAtlasPos = True def update(self): self.computeMVP() def getAtlasPos(self): return self.atlasPos def hasAtlasPos(self): return self.doesHaveAtlasPos def removeFromAtlas(self): self.doesHaveAtlasPos = False self.atlasPos = Vec2(0) def setResolution(self, resolution): self.resolution = resolution def getResolution(self): return self.resolution def setupPerspectiveLens(self, near=0.1, far=100.0, fov=(90, 90)): self.lens = PerspectiveLens() self.lens.setNearFar(near, far) self.lens.setFov(fov[0], fov[1]) self.camera.setLens(self.lens) self.nearPlane = near self.farPlane = far def setPos(self, pos): self.cameraNode.setPos(pos) def setHpr(self, hpr): self.cameraNode.setHpr(hpr) def lookAt(self, pos): self.cameraNode.lookAt(pos.x, pos.y, pos.z) def invalidate(self): self.valid = False def setValid(self): self.valid = True def isValid(self): return self.valid
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None, clamping=None): """ Causes the scene to be rendered into the supplied textures instead of into the original window. Puts a fullscreen quad into the original window to show the render-to-texture results. Returns the quad. Normally, the caller would then apply a shader to the quad. To elaborate on how this all works: * An offscreen buffer is created. It is set up to mimic the original display region - it is the same size, uses the same clear colors, and contains a DisplayRegion that uses the original camera. * A fullscreen quad and an orthographic camera to render that quad are both created. The original camera is removed from the original window, and in its place, the orthographic quad-camera is installed. * The fullscreen quad is textured with the data from the offscreen buffer. A shader is applied that tints the results pink. * Automatic shader generation NOT enabled. If you have a filter that depends on a render target from the auto-shader, you either need to set an auto-shader attrib on the main camera or scene, or, you need to provide these outputs in your own shader. * All clears are disabled on the original display region. If the display region fills the whole window, then clears are disabled on the original window as well. It is assumed that rendering the full-screen quad eliminates the need to do clears. Hence, the original window which used to contain the actual scene, now contains a pink-tinted quad with a texture of the scene. It is assumed that the user will replace the shader on the quad with a more interesting filter. """ if textures: colortex = textures.get("color", None) depthtex = textures.get("depth", None) auxtex = textures.get("aux", None) auxtex0 = textures.get("aux0", auxtex) auxtex1 = textures.get("aux1", None) else: auxtex0 = auxtex auxtex1 = None if colortex is None: colortex = Texture("filter-base-color") colortex.setWrapU(Texture.WMClamp) colortex.setWrapV(Texture.WMClamp) texgroup = (depthtex, colortex, auxtex0, auxtex1) # Choose the size of the offscreen buffer. (winx, winy) = self.getScaledSize(1, 1, 1) if fbprops is not None: buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops) else: buffer = self.createBuffer("filter-base", winx, winy, texgroup) if buffer is None: return None cm = CardMaker("filter-base-quad") cm.setFrameFullscreenQuad() quad = NodePath(cm.generate()) quad.setDepthTest(0) quad.setDepthWrite(0) quad.setTexture(colortex) quad.setColor(1, 0.5, 0.5, 1) cs = NodePath("dummy") cs.setState(self.camstate) # Do we really need to turn on the Shader Generator? #cs.setShaderAuto() if auxbits: cs.setAttrib(AuxBitplaneAttrib.make(auxbits)) if clamping is False: # Disables clamping in the shader generator. cs.setAttrib(LightRampAttrib.make_identity()) self.camera.node().setInitialState(cs.getState()) quadcamnode = Camera("filter-quad-cam") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) quadcamnode.setLens(lens) quadcam = quad.attachNewNode(quadcamnode) self.region.setCamera(quadcam) self.setStackedClears(buffer, self.rclears, self.wclears) if auxtex0: buffer.setClearActive(GraphicsOutput.RTPAuxRgba0, 1) buffer.setClearValue(GraphicsOutput.RTPAuxRgba0, (0.5, 0.5, 1.0, 0.0)) if auxtex1: buffer.setClearActive(GraphicsOutput.RTPAuxRgba1, 1) self.region.disableClears() if self.isFullscreen(): self.win.disableClears() dr = buffer.makeDisplayRegion() dr.disableClears() dr.setCamera(self.camera) dr.setActive(1) self.buffers.append(buffer) self.sizes.append((1, 1, 1)) return quad
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 Viewport(QtWidgets.QWidget, DirectObject): ClearColor = LEGlobals.vec3GammaToLinear(Vec4(0.361, 0.361, 0.361, 1.0)) def __init__(self, vpType, window, doc): DirectObject.__init__(self) QtWidgets.QWidget.__init__(self, window) self.doc = doc self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setMouseTracking(True) self.qtWindow = None self.qtWidget = None self.window = window self.type = vpType self.spec = VIEWPORT_SPECS[self.type] self.lens = None self.camNode = None self.camera = None self.cam = None self.win = None self.displayRegion = None self.mouseWatcher = None self.mouseWatcherNp = None self.buttonThrower = None self.clickRay = None self.clickNode = None self.clickNp = None self.clickQueue = None self.tickTask = None self.zoom = 1.0 self.gizmo = None self.inputDevice = None self.mouseAndKeyboard = None self.lastRenderTime = 0.0 self.enabled = False self.needsUpdate = True # 2D stuff copied from ShowBase :( self.camera2d = None self.cam2d = None self.render2d = None self.aspect2d = None self.a2dBackground = None self.a2dTop = None self.a2dBottom = None self.a2dLeft = None self.a2dRight = None self.a2dTopCenter = None self.a2dTopCenterNs = None self.a2dBottomCenter = None self.a2dBottomCenterNs = None self.a2dRightCenter = None self.a2dRightCenterNs = None self.a2dTopLeft = None self.a2dTopLeftNs = None self.a2dTopRight = None self.a2dTopRightNs = None self.a2dBottomLeft = None self.a2dBottomLeftNs = None self.a2dBottomRight = None self.a2dBottomRightNs = None self.__oldAspectRatio = None self.gridRoot = self.doc.render.attachNewNode("gridRoot") self.gridRoot.setLightOff(1) #self.gridRoot.setBSPMaterial("phase_14/materials/unlit.mat") #self.gridRoot.setDepthWrite(False) self.gridRoot.setBin("background", 0) self.gridRoot.hide(~self.getViewportMask()) self.grid = None def updateView(self, now=False): if now: self.renderView() else: self.needsUpdate = True def getGizmoAxes(self): raise NotImplementedError def getMouseRay(self, collRay=False): ray = CollisionRay() ray.setFromLens(self.camNode, self.getMouse()) if collRay: return ray else: return Ray(ray.getOrigin(), ray.getDirection()) def hasMouse(self): return self.mouseWatcher.hasMouse() def getMouse(self): if self.mouseWatcher.hasMouse(): return self.mouseWatcher.getMouse() return Point2(0, 0) def is3D(self): return self.type == VIEWPORT_3D def is2D(self): return self.type != VIEWPORT_3D def makeGrid(self): raise NotImplementedError def getViewportMask(self): return BitMask32.bit(self.type) def getViewportFullMask(self): return self.getViewportMask() def makeLens(self): raise NotImplementedError def getGridAxes(self): raise NotImplementedError def expand(self, point): return point def initialize(self): self.lens = self.makeLens() self.camera = self.doc.render.attachNewNode( ModelNode("viewportCameraParent")) self.camNode = Camera("viewportCamera") self.camNode.setLens(self.lens) self.camNode.setCameraMask(self.getViewportMask()) self.cam = self.camera.attachNewNode(self.camNode) winprops = WindowProperties.getDefault() winprops.setParentWindow(int(self.winId())) winprops.setForeground(False) winprops.setUndecorated(True) gsg = self.doc.gsg output = base.graphicsEngine.makeOutput( base.pipe, "viewportOutput", 0, FrameBufferProperties.getDefault(), winprops, (GraphicsPipe.BFFbPropsOptional | GraphicsPipe.BFRequireWindow), gsg) self.qtWindow = QtGui.QWindow.fromWinId( output.getWindowHandle().getIntHandle()) self.qtWidget = QtWidgets.QWidget.createWindowContainer( self.qtWindow, self, QtCore.Qt.WindowDoesNotAcceptFocus | QtCore.Qt.WindowTransparentForInput | QtCore.Qt.WindowStaysOnBottomHint | QtCore.Qt.BypassWindowManagerHint | QtCore.Qt.SubWindow) #, #(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowDoesNotAcceptFocus #| QtCore.Qt.WindowTransparentForInput | QtCore.Qt.BypassWindowManagerHint #| QtCore.Qt.SubWindow | QtCore.Qt.WindowStaysOnBottomHint)) self.qtWidget.setFocusPolicy(QtCore.Qt.NoFocus) self.inputDevice = output.getInputDevice(0) assert output is not None, "Unable to create viewport output!" dr = output.makeDisplayRegion() dr.disableClears() dr.setCamera(self.cam) self.displayRegion = dr output.disableClears() output.setClearColor(Viewport.ClearColor) output.setClearColorActive(True) output.setClearDepthActive(True) output.setActive(True) self.win = output # keep track of the mouse in this viewport mak = MouseAndKeyboard(self.win, 0, "mouse") mouse = base.dataRoot.attachNewNode(mak) self.mouseAndKeyboard = mouse self.mouseWatcher = MouseWatcher() self.mouseWatcher.setDisplayRegion(self.displayRegion) mw = mouse.attachNewNode(self.mouseWatcher) self.mouseWatcherNp = mw # listen for keyboard and mouse events in this viewport bt = ButtonThrower("kbEvents") bt.setButtonDownEvent("btndown") bt.setButtonUpEvent("btnup") mods = ModifierButtons() mods.addButton(KeyboardButton.shift()) mods.addButton(KeyboardButton.control()) mods.addButton(KeyboardButton.alt()) mods.addButton(KeyboardButton.meta()) bt.setModifierButtons(mods) self.buttonThrower = mouse.attachNewNode(bt) # collision objects for clicking on objects from this viewport self.clickRay = CollisionRay() self.clickNode = CollisionNode("viewportClickRay") self.clickNode.addSolid(self.clickRay) self.clickNp = NodePath(self.clickNode) self.clickQueue = CollisionHandlerQueue() self.setupRender2d() self.setupCamera2d() self.gizmo = ViewportGizmo(self) self.doc.viewportMgr.addViewport(self) self.makeGrid() def cleanup(self): self.grid.cleanup() self.grid = None self.gridRoot.removeNode() self.gridRoot = None self.lens = None self.camNode = None self.cam.removeNode() self.cam = None self.camera.removeNode() self.camera = None self.spec = None self.doc = None self.type = None self.window = None self.zoom = None self.gizmo.cleanup() self.gizmo = None self.clickNp.removeNode() self.clickNp = None self.clickQueue.clearEntries() self.clickQueue = None self.clickNode = None self.clickRay = None self.buttonThrower.removeNode() self.buttonThrower = None self.inputDevice = None self.mouseWatcherNp.removeNode() self.mouseWatcherNp = None self.mouseWatcher = None self.mouseAndKeyboard.removeNode() self.mouseAndKeyboard = None self.win.removeAllDisplayRegions() self.displayRegion = None base.graphicsEngine.removeWindow(self.win) self.win = None self.camera2d.removeNode() self.camera2d = None self.cam2d = None self.render2d.removeNode() self.render2d = None self.a2dBackground = None self.a2dTop = None self.a2dBottom = None self.a2dLeft = None self.a2dRight = None self.aspect2d = None self.a2dTopCenter = None self.a2dTopCenterNs = None self.a2dBottomCenter = None self.a2dBottomCenterNs = None self.a2dLeftCenter = None self.a2dLeftCenterNs = None self.a2dRightCenter = None self.a2dRightCenterNs = None self.a2dTopLeft = None self.a2dTopLeftNs = None self.a2dTopRight = None self.a2dTopRightNs = None self.a2dBottomLeft = None self.a2dBottomLeftNs = None self.a2dBottomRight = None self.a2dBottomRightNs = None self.__oldAspectRatio = None self.qtWindow.deleteLater() self.qtWidget.deleteLater() self.qtWindow = None self.qtWidget = None self.deleteLater() def keyPressEvent(self, event): button = LEUtils.keyboardButtonFromQtKey(event.key()) if button: self.inputDevice.buttonDown(button) def keyReleaseEvent(self, event): button = LEUtils.keyboardButtonFromQtKey(event.key()) if button: self.inputDevice.buttonUp(button) def enterEvent(self, event): # Give ourselves focus. self.setFocus() QtWidgets.QWidget.enterEvent(self, event) def mouseMoveEvent(self, event): self.inputDevice.setPointerInWindow(event.pos().x(), event.pos().y()) QtWidgets.QWidget.mouseMoveEvent(self, event) def leaveEvent(self, event): self.clearFocus() self.inputDevice.setPointerOutOfWindow() self.inputDevice.focusLost() QtWidgets.QWidget.leaveEvent(self, event) def mousePressEvent(self, event): btn = event.button() if btn == QtCore.Qt.LeftButton: self.inputDevice.buttonDown(MouseButton.one()) elif btn == QtCore.Qt.MiddleButton: self.inputDevice.buttonDown(MouseButton.two()) elif btn == QtCore.Qt.RightButton: self.inputDevice.buttonDown(MouseButton.three()) QtWidgets.QWidget.mousePressEvent(self, event) def mouseReleaseEvent(self, event): btn = event.button() if btn == QtCore.Qt.LeftButton: self.inputDevice.buttonUp(MouseButton.one()) elif btn == QtCore.Qt.MiddleButton: self.inputDevice.buttonUp(MouseButton.two()) elif btn == QtCore.Qt.RightButton: self.inputDevice.buttonUp(MouseButton.three()) QtWidgets.QWidget.mouseReleaseEvent(self, event) def wheelEvent(self, event): ang = event.angleDelta().y() if ang > 0: self.inputDevice.buttonDown(MouseButton.wheelUp()) self.inputDevice.buttonUp(MouseButton.wheelUp()) else: self.inputDevice.buttonDown(MouseButton.wheelDown()) self.inputDevice.buttonUp(MouseButton.wheelDown()) QtWidgets.QWidget.wheelEvent(self, event) def getAspectRatio(self): return self.win.getXSize() / self.win.getYSize() def setupRender2d(self): ## This is the root of the 2-D scene graph. self.render2d = NodePath("viewport-render2d") # Set up some overrides to turn off certain properties which # we probably won't need for 2-d objects. # It's probably important to turn off the depth test, since # many 2-d objects will be drawn over each other without # regard to depth position. # We used to avoid clearing the depth buffer before drawing # render2d, but nowadays we clear it anyway, since we # occasionally want to put 3-d geometry under render2d, and # it's simplest (and seems to be easier on graphics drivers) # if the 2-d scene has been cleared first. self.render2d.setDepthTest(0) self.render2d.setDepthWrite(0) self.render2d.setMaterialOff(1) self.render2d.setTwoSided(1) self.aspect2d = self.render2d.attachNewNode("viewport-aspect2d") aspectRatio = self.getAspectRatio() self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0) self.a2dBackground = self.aspect2d.attachNewNode("a2dBackground") ## The Z position of the top border of the aspect2d screen. self.a2dTop = 1.0 ## The Z position of the bottom border of the aspect2d screen. self.a2dBottom = -1.0 ## The X position of the left border of the aspect2d screen. self.a2dLeft = -aspectRatio ## The X position of the right border of the aspect2d screen. self.a2dRight = aspectRatio self.a2dTopCenter = self.aspect2d.attachNewNode("a2dTopCenter") self.a2dTopCenterNs = self.aspect2d.attachNewNode("a2dTopCenterNS") self.a2dBottomCenter = self.aspect2d.attachNewNode("a2dBottomCenter") self.a2dBottomCenterNs = self.aspect2d.attachNewNode( "a2dBottomCenterNS") self.a2dLeftCenter = self.aspect2d.attachNewNode("a2dLeftCenter") self.a2dLeftCenterNs = self.aspect2d.attachNewNode("a2dLeftCenterNS") self.a2dRightCenter = self.aspect2d.attachNewNode("a2dRightCenter") self.a2dRightCenterNs = self.aspect2d.attachNewNode("a2dRightCenterNS") self.a2dTopLeft = self.aspect2d.attachNewNode("a2dTopLeft") self.a2dTopLeftNs = self.aspect2d.attachNewNode("a2dTopLeftNS") self.a2dTopRight = self.aspect2d.attachNewNode("a2dTopRight") self.a2dTopRightNs = self.aspect2d.attachNewNode("a2dTopRightNS") self.a2dBottomLeft = self.aspect2d.attachNewNode("a2dBottomLeft") self.a2dBottomLeftNs = self.aspect2d.attachNewNode("a2dBottomLeftNS") self.a2dBottomRight = self.aspect2d.attachNewNode("a2dBottomRight") self.a2dBottomRightNs = self.aspect2d.attachNewNode("a2dBottomRightNS") # Put the nodes in their places self.a2dTopCenter.setPos(0, 0, self.a2dTop) self.a2dTopCenterNs.setPos(0, 0, self.a2dTop) self.a2dBottomCenter.setPos(0, 0, self.a2dBottom) self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom) self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0) self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0) self.a2dRightCenter.setPos(self.a2dRight, 0, 0) self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0) self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop) self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop) self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop) self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop) self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom) self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom) self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom) self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom) def setupCamera2d(self, sort=10, displayRegion=(0, 1, 0, 1), coords=(-1, 1, -1, 1)): dr = self.win.makeMonoDisplayRegion(*displayRegion) dr.setSort(10) # Enable clearing of the depth buffer on this new display # region (see the comment in setupRender2d, above). dr.setClearDepthActive(1) # Make any texture reloads on the gui come up immediately. dr.setIncompleteRender(False) left, right, bottom, top = coords # Now make a new Camera node. cam2dNode = Camera('cam2d') lens = OrthographicLens() lens.setFilmSize(right - left, top - bottom) lens.setFilmOffset((right + left) * 0.5, (top + bottom) * 0.5) lens.setNearFar(-1000, 1000) cam2dNode.setLens(lens) # self.camera2d is the analog of self.camera, although it's # not as clear how useful it is. self.camera2d = self.render2d.attachNewNode('camera2d') camera2d = self.camera2d.attachNewNode(cam2dNode) dr.setCamera(camera2d) self.cam2d = camera2d return camera2d def mouse1Up(self): pass def mouse1Down(self): pass def mouse2Up(self): pass def mouse2Down(self): pass def mouse3Up(self): pass def mouse3Down(self): pass def mouseEnter(self): self.updateView() def mouseExit(self): pass def mouseMove(self): pass def wheelUp(self): pass def wheelDown(self): pass def shouldRender(self): if not self.enabled: return False now = globalClock.getRealTime() if self.lastRenderTime != 0: elapsed = now - self.lastRenderTime if elapsed <= 0: return False frameRate = 1 / elapsed if frameRate > 100.0: # Never render faster than 100Hz return False return self.needsUpdate def renderView(self): self.lastRenderTime = globalClock.getRealTime() self.needsUpdate = False #self.win.setActive(1) base.requestRender() def tick(self): if self.shouldRender(): self.renderView() else: pass #self.win.setActive(0) def getViewportName(self): return self.spec.name def getViewportCenterPixels(self): return LPoint2i(self.win.getXSize() // 2, self.win.getYSize() // 2) def centerCursor(self, cursor): center = self.getViewportCenterPixels() cursor.setPos( self.mapToGlobal(QtCore.QPoint(self.width() / 2, self.height() / 2))) self.inputDevice.setPointerInWindow(center.x, center.y) def viewportToWorld(self, viewport, vec=False): front = Point3() back = Point3() self.lens.extrude(viewport, front, back) world = (front + back) / 2 worldMat = self.cam.getMat(render) if vec: world = worldMat.xformVec(world) else: world = worldMat.xformPoint(world) return world def worldToViewport(self, world): # move into local camera space invMat = Mat4(self.cam.getMat(render)) invMat.invertInPlace() local = invMat.xformPoint(world) point = Point2() self.lens.project(local, point) return point def zeroUnusedCoordinate(self, vec): pass def click(self, mask, queue=None, traverser=None, root=None): if not self.mouseWatcher.hasMouse(): return None if not queue: queue = self.clickQueue self.clickRay.setFromLens(self.camNode, self.mouseWatcher.getMouse()) self.clickNode.setFromCollideMask(mask) self.clickNode.setIntoCollideMask(BitMask32.allOff()) self.clickNp.reparentTo(self.cam) queue.clearEntries() if not traverser: base.clickTraverse(self.clickNp, queue) else: if not root: root = self.doc.render traverser.addCollider(self.clickNp, queue) traverser.traverse(root) traverser.removeCollider(self.clickNp) queue.sortEntries() self.clickNp.reparentTo(NodePath()) return queue.getEntries() def fixRatio(self, size=None): if not self.lens: return if size is None: aspectRatio = self.win.getXSize() / self.win.getYSize() else: if size.y > 0: aspectRatio = size.x / size.y else: aspectRatio = 1.0 if self.is2D(): zoomFactor = (1.0 / self.zoom) * 100.0 self.lens.setFilmSize(zoomFactor * aspectRatio, zoomFactor) else: self.lens.setAspectRatio(aspectRatio) if aspectRatio != self.__oldAspectRatio: self.__oldAspectRatio = aspectRatio # Fix up some anything that depends on the aspectRatio if aspectRatio < 1: # If the window is TALL, lets expand the top and bottom self.aspect2d.setScale(1.0, aspectRatio, aspectRatio) self.a2dTop = 1.0 / aspectRatio self.a2dBottom = -1.0 / aspectRatio self.a2dLeft = -1 self.a2dRight = 1.0 else: # If the window is WIDE, lets expand the left and right self.aspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0) self.a2dTop = 1.0 self.a2dBottom = -1.0 self.a2dLeft = -aspectRatio self.a2dRight = aspectRatio # Reposition the aspect2d marker nodes self.a2dTopCenter.setPos(0, 0, self.a2dTop) self.a2dTopCenterNs.setPos(0, 0, self.a2dTop) self.a2dBottomCenter.setPos(0, 0, self.a2dBottom) self.a2dBottomCenterNs.setPos(0, 0, self.a2dBottom) self.a2dLeftCenter.setPos(self.a2dLeft, 0, 0) self.a2dLeftCenterNs.setPos(self.a2dLeft, 0, 0) self.a2dRightCenter.setPos(self.a2dRight, 0, 0) self.a2dRightCenterNs.setPos(self.a2dRight, 0, 0) self.a2dTopLeft.setPos(self.a2dLeft, 0, self.a2dTop) self.a2dTopLeftNs.setPos(self.a2dLeft, 0, self.a2dTop) self.a2dTopRight.setPos(self.a2dRight, 0, self.a2dTop) self.a2dTopRightNs.setPos(self.a2dRight, 0, self.a2dTop) self.a2dBottomLeft.setPos(self.a2dLeft, 0, self.a2dBottom) self.a2dBottomLeftNs.setPos(self.a2dLeft, 0, self.a2dBottom) self.a2dBottomRight.setPos(self.a2dRight, 0, self.a2dBottom) self.a2dBottomRightNs.setPos(self.a2dRight, 0, self.a2dBottom) def resizeEvent(self, event): if not self.win: return newsize = LVector2i(event.size().width(), event.size().height()) self.qtWidget.resize(newsize[0], newsize[1]) self.qtWidget.move(0, 0) #props = WindowProperties() #props.setSize(newsize) #props.setOrigin(0, 0) #self.win.requestProperties(props) self.fixRatio(newsize) self.onResize(newsize) self.updateView() def onResize(self, newsize): pass def draw(self): pass def enable(self): # Render to the viewport self.win.setActive(True) self.enabled = True def disable(self): # Don't render to the viewport self.win.setActive(False) self.enabled = False
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None, clamping=None): """ overload direct.filters.FilterManager.renderSceneInto :param depthtex: :param colortex: :param auxtex: :param auxbits: :param textures: :param fbprops: :param clamping: :return: """ if (textures): colortex = textures.get("color", None) depthtex = textures.get("depth", None) auxtex = textures.get("aux", None) auxtex0 = textures.get("aux0", auxtex) auxtex1 = textures.get("aux1", None) else: auxtex0 = auxtex auxtex1 = None if (colortex == None): colortex = Texture("filter-base-color") colortex.setWrapU(Texture.WMClamp) colortex.setWrapV(Texture.WMClamp) texgroup = (depthtex, colortex, auxtex0, auxtex1) # Choose the size of the offscreen buffer. (winx, winy) = self.getScaledSize(1, 1, 1) if fbprops is not None: buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops) else: buffer = self.createBuffer("filter-base", winx, winy, texgroup) if (buffer == None): return None cm = CardMaker("filter-base-quad") cm.setFrameFullscreenQuad() quad = NodePath(cm.generate()) quad.setDepthTest(0) quad.setDepthWrite(0) quad.setTexture(colortex) quad.setColor(1, 0.5, 0.5, 1) cs = NodePath("dummy") cs.setState(self.camstate) # Do we really need to turn on the Shader Generator? # cs.setShaderAuto() if (auxbits): cs.setAttrib(AuxBitplaneAttrib.make(auxbits)) if clamping is False: # Disables clamping in the shader generator. cs.setAttrib(LightRampAttrib.make_identity()) self.camera.node().setInitialState(cs.getState()) quadcamnode = Camera("filter-quad-cam") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) quadcamnode.setLens(lens) quadcam = quad.attachNewNode(quadcamnode) self.region.setCamera(quadcam) self.setStackedClears(buffer, self.rclears, self.wclears) if (auxtex0): buffer.setClearActive(GraphicsOutput.RTPAuxRgba0, 1) buffer.setClearValue(GraphicsOutput.RTPAuxRgba0, (0.5, 0.5, 1.0, 0.0)) if (auxtex1): buffer.setClearActive(GraphicsOutput.RTPAuxRgba1, 1) self.region.disableClears() if (self.isFullscreen()): self.win.disableClears() dr = buffer.makeDisplayRegion() dr.disableClears() dr.setCamera(self.camera) dr.setActive(1) self.buffers.append(buffer) self.sizes.append((1, 1, 1)) return quad
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None): """ Causes the scene to be rendered into the supplied textures instead of into the original window. Puts a fullscreen quad into the original window to show the render-to-texture results. Returns the quad. Normally, the caller would then apply a shader to the quad. To elaborate on how this all works: * An offscreen buffer is created. It is set up to mimic the original display region - it is the same size, uses the same clear colors, and contains a DisplayRegion that uses the original camera. * A fullscreen quad and an orthographic camera to render that quad are both created. The original camera is removed from the original window, and in its place, the orthographic quad-camera is installed. * The fullscreen quad is textured with the data from the offscreen buffer. A shader is applied that tints the results pink. * Automatic shader generation NOT enabled. If you have a filter that depends on a render target from the auto-shader, you either need to set an auto-shader attrib on the main camera or scene, or, you need to provide these outputs in your own shader. * All clears are disabled on the original display region. If the display region fills the whole window, then clears are disabled on the original window as well. It is assumed that rendering the full-screen quad eliminates the need to do clears. Hence, the original window which used to contain the actual scene, now contains a pink-tinted quad with a texture of the scene. It is assumed that the user will replace the shader on the quad with a more interesting filter. """ if (textures): colortex = textures.get("color", None) depthtex = textures.get("depth", None) auxtex = textures.get("aux", None) auxtex0 = textures.get("aux0", auxtex) auxtex1 = textures.get("aux1", None) else: auxtex0 = auxtex auxtex1 = None if (colortex == None): colortex = Texture("filter-base-color") colortex.setWrapU(Texture.WMClamp) colortex.setWrapV(Texture.WMClamp) texgroup = (depthtex, colortex, auxtex0, auxtex1) # Choose the size of the offscreen buffer. (winx, winy) = self.getScaledSize(1,1,1) buffer = self.createBuffer("filter-base", winx, winy, texgroup) if (buffer == None): return None cm = CardMaker("filter-base-quad") cm.setFrameFullscreenQuad() quad = NodePath(cm.generate()) quad.setDepthTest(0) quad.setDepthWrite(0) quad.setTexture(colortex) quad.setColor(1, 0.5, 0.5, 1) cs = NodePath("dummy") cs.setState(self.camstate) # Do we really need to turn on the Shader Generator? #cs.setShaderAuto() if (auxbits): cs.setAttrib(AuxBitplaneAttrib.make(auxbits)) self.camera.node().setInitialState(cs.getState()) quadcamnode = Camera("filter-quad-cam") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) quadcamnode.setLens(lens) quadcam = quad.attachNewNode(quadcamnode) self.region.setCamera(quadcam) self.setStackedClears(buffer, self.rclears, self.wclears) if (auxtex0): buffer.setClearActive(GraphicsOutput.RTPAuxRgba0, 1) buffer.setClearValue(GraphicsOutput.RTPAuxRgba0, (0.5, 0.5, 1.0, 0.0)) if (auxtex1): buffer.setClearActive(GraphicsOutput.RTPAuxRgba1, 1) self.region.disableClears() if (self.isFullscreen()): self.win.disableClears() dr = buffer.makeDisplayRegion() dr.disableClears() dr.setCamera(self.camera) dr.setActive(1) self.buffers.append(buffer) self.sizes.append((1, 1, 1)) return quad
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 {}
class ShadowSource(DebugObject, ShaderStructElement): """ This class can be seen as a camera. It stores the necessary data to generate and store the shadow map for the assigned lens (like computing the MVP), and also stores information about the shadowmap, like position in the shadow atlas, or resolution. Each ShadowSource has a unique index, which is used by the LightClass to identify which sources belong to it. TODO: Support OrtographicLens """ # Store a global index for assigning unique ids to the instances _GlobalShadowIndex = 1000 @classmethod def getExposedAttributes(self): return { "resolution": "int", "atlasPos": "vec2", "mvp": "mat4", "nearPlane": "float", "farPlane": "float" } @classmethod def _generateUID(self): """ Generates an uid and returns that """ self._GlobalShadowIndex += 1 return self._GlobalShadowIndex def __init__(self): """ Creates a new ShadowSource. After the creation, a lens can be added with setupPerspectiveLens. """ self.index = self._generateUID() DebugObject.__init__(self, "ShadowSource-" + str(self.index)) ShaderStructElement.__init__(self) self.valid = False self.camera = Camera("ShadowSource-" + str(self.index)) self.cameraNode = NodePath(self.camera) self.cameraNode.reparentTo(Globals.render) self.resolution = 1024 self.atlasPos = Vec2(0) self.doesHaveAtlasPos = False self.sourceIndex = 0 self.mvp = Mat4() self.sourceIndex = -1 self.nearPlane = 0.0 self.farPlane = 1000.0 self.converterYUR = None def getSourceIndex(self): """ Returns the assigned source index. The source index is the index of the ShadowSource in the ShadowSources array of the assigned Light. """ return self.sourceIndex def getUid(self): """ Returns the uid of the light """ return self.index def setSourceIndex(self, index): """ Sets the source index of this source. This is called by the light, as only the light knows at which position this source is in the Sources array. """ self.sourceIndex = index def computeMVP(self): """ Computes the modelViewProjection matrix for the lens. Actually, this is the worldViewProjection matrix, but for convenience it is called mvp. """ projMat = self.converterYUR transformMat = TransformState.makeMat( Mat4.convertMat(Globals.base.win.getGsg().getInternalCoordinateSystem(), CSZupRight)) modelViewMat = transformMat.invertCompose( Globals.render.getTransform(self.cameraNode)).getMat() self.mvp = UnalignedLMatrix4f(modelViewMat * projMat) def assignAtlasPos(self, x, y): """ Assigns this source a position in the shadow atlas. This is called by the shadow atlas. Coordinates are float from 0 .. 1 """ self.atlasPos = Vec2(x, y) self.doesHaveAtlasPos = True def update(self): """ Updates the shadow source. Currently only recomputes the mvp. """ self.computeMVP() def getAtlasPos(self): """ Returns the assigned atlas pos, if present. Coordinates are float from 0 .. 1 """ return self.atlasPos def hasAtlasPos(self): """ Returns wheter this ShadowSource has already a position in the shadow atlas """ return self.doesHaveAtlasPos def removeFromAtlas(self): """ Deletes the atlas coordinates, called by the atlas after the Source got removed from the atlas """ self.doesHaveAtlasPos = False self.atlasPos = Vec2(0) def setResolution(self, resolution): """ Sets the resolution in pixels of this shadow source. Has to be a multiple of the tileSize specified in LightManager """ assert(resolution > 1 and resolution <= 8192) self.resolution = resolution def getResolution(self): """ Returns the resolution of the shadow source in pixels """ return self.resolution def setupPerspectiveLens(self, near=0.1, far=100.0, fov=(90, 90)): """ Setups a PerspectiveLens with a given nearPlane, farPlane and FoV. The FoV is a tuple in the format (Horizontal FoV, Vertical FoV) """ # self.debug("setupPerspectiveLens(",near,",",far,",",fov,")") self.lens = PerspectiveLens() self.lens.setNearFar(near, far) self.lens.setFov(fov[0], fov[1]) self.camera.setLens(self.lens) self.nearPlane = near self.farPlane = far self.rebuildMatrixCache() def setupOrtographicLens(self, near=0.1, far=100.0, filmSize=(512, 512)): """ Setups a OrtographicLens with a given nearPlane, farPlane and filmSize. The filmSize is a tuple in the format (filmWidth, filmHeight) in world space. """ # self.debug("setupOrtographicLens(",near,",",far,",",filmSize,")") self.lens = OrtographicLens() self.lens.setNearFar(near, far) self.lens.setFilmSize(*filmSize) self.camera.setLens(self.lens) self.nearPlane = near self.farPlane = far self.rebuildMatrixCache() def rebuildMatrixCache(self): """ Computes values frequently used to compute the mvp """ self.converterYUR = Mat4.convertMat(CSYupRight, self.lens.getCoordinateSystem()) * self.lens.getProjectionMat() def setPos(self, pos): """ Sets the position in world space """ self.cameraNode.setPos(pos) def setHpr(self, hpr): """ Sets the rotation in world space """ self.cameraNode.setHpr(hpr) def lookAt(self, pos): """ Looks at a point (in world space) """ self.cameraNode.lookAt(pos.x, pos.y, pos.z) def invalidate(self): """ Invalidates this shadow source, means telling the LightManager that the shadow map for this light should be rebuilt. Otherwise it won't get refreshed """ self.valid = False def setValid(self): """ The LightManager calls this after the shadow map got updated successfully """ self.valid = True def isValid(self): """ Returns wether the shadow map is still valid or should be refreshed """ return self.valid def __repr__(self): """ Returns a representative string of this instance """ return "ShadowSource[id=" + str(self.index) + "]"
def __init__(self): # Player Model setup self.player = Actor("Player", {"Run":"Player-Run", "Sidestep":"Player-Sidestep", "Idle":"Player-Idle"}) self.player.setBlend(frameBlend = True) self.player.setPos(0, 0, 0) self.player.pose("Idle", 0) self.player.reparentTo(render) self.player.hide() self.footstep = base.audio3d.loadSfx('footstep.ogg') self.footstep.setLoop(True) base.audio3d.attachSoundToObject(self.footstep, self.player) # Create a brush to paint on the texture splat = PNMImage("../data/Splat.png") self.colorBrush = PNMBrush.makeImage(splat, 6, 6, 1) CamMask = BitMask32.bit(0) AvBufMask = BitMask32.bit(1) self.avbuf = None if base.win: self.avbufTex = Texture('avbuf') self.avbuf = base.win.makeTextureBuffer('avbuf', 256, 256, self.avbufTex, True) cam = Camera('avbuf') cam.setLens(base.camNode.getLens()) self.avbufCam = base.cam.attachNewNode(cam) dr = self.avbuf.makeDisplayRegion() dr.setCamera(self.avbufCam) self.avbuf.setActive(False) self.avbuf.setClearColor((1, 0, 0, 1)) cam.setCameraMask(AvBufMask) base.camNode.setCameraMask(CamMask) # avbuf renders everything it sees with the gradient texture. tex = loader.loadTexture('gradient.png') np = NodePath('np') np.setTexture(tex, 100) np.setColor((1, 1, 1, 1), 100) np.setColorScaleOff(100) np.setTransparency(TransparencyAttrib.MNone, 100) np.setLightOff(100) cam.setInitialState(np.getState()) #render.hide(AvBufMask) # Setup a texture stage to paint on the player self.paintTs = TextureStage('paintTs') self.paintTs.setMode(TextureStage.MDecal) self.paintTs.setSort(10) self.paintTs.setPriority(10) self.tex = Texture('paint_av_%s'%id(self)) # Setup a PNMImage that will hold the paintable texture of the player self.imageSizeX = 64 self.imageSizeY = 64 self.p = PNMImage(self.imageSizeX, self.imageSizeY, 4) self.p.fill(1) self.p.alphaFill(0) self.tex.load(self.p) self.tex.setWrapU(self.tex.WMClamp) self.tex.setWrapV(self.tex.WMClamp) # Apply the paintable texture to the avatar self.player.setTexture(self.paintTs, self.tex) # team self.playerTeam = "" # A lable that will display the players team self.lblTeam = DirectLabel( scale = 1, pos = (0, 0, 3), frameColor = (0, 0, 0, 0), text = "TEAM", text_align = TextNode.ACenter, text_fg = (0,0,0,1)) self.lblTeam.reparentTo(self.player) self.lblTeam.setBillboardPointEye() # basic player values self.maxHits = 3 self.currentHits = 0 self.isOut = False self.TorsorControl = self.player.controlJoint(None,"modelRoot","Torsor") # setup the collision detection # wall and object collision self.playerSphere = CollisionSphere(0, 0, 1, 1) self.playerCollision = self.player.attachNewNode(CollisionNode("playerCollision%d"%id(self))) self.playerCollision.node().addSolid(self.playerSphere) base.pusher.addCollider(self.playerCollision, self.player) base.cTrav.addCollider(self.playerCollision, base.pusher) # foot (walk) collision self.playerFootRay = self.player.attachNewNode(CollisionNode("playerFootCollision%d"%id(self))) self.playerFootRay.node().addSolid(CollisionRay(0, 0, 2, 0, 0, -1)) self.playerFootRay.node().setIntoCollideMask(0) self.lifter = CollisionHandlerFloor() self.lifter.addCollider(self.playerFootRay, self.player) base.cTrav.addCollider(self.playerFootRay, self.lifter) # Player weapon setup self.gunAttach = self.player.exposeJoint(None, "modelRoot", "WeaponSlot_R") self.color = LPoint3f(1, 1, 1) self.gun = Gun(id(self)) self.gun.reparentTo(self.gunAttach) self.gun.hide() self.gun.setColor(self.color) self.hud = None # Player controls setup self.keyMap = {"left":0, "right":0, "forward":0, "backward":0} # screen sizes self.winXhalf = base.win.getXSize() / 2 self.winYhalf = base.win.getYSize() / 2 self.mouseSpeedX = 0.1 self.mouseSpeedY = 0.1 # AI controllable variables self.AIP = 0.0 self.AIH = 0.0 self.movespeed = 5.0 self.userControlled = False self.accept("Bulet-hit-playerCollision%d" % id(self), self.hit) self.accept("window-event", self.recalcAspectRatio)
def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None): """ Creates an offscreen buffer for an intermediate computation. Installs a quad into the buffer. Returns the fullscreen quad. The size of the buffer is initially equal to the size of the main window. The parameters 'mul', 'div', and 'align' can be used to adjust that size. """ texgroup = (depthtex, colortex, auxtex0, auxtex1) winx, winy = self.getScaledSize(mul, div, align) depthbits = int(depthtex is not None) if fbprops is not None: buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops) else: buffer = self.createBuffer(name, winx, winy, texgroup, depthbits) if buffer is None: return None cm = CardMaker("filter-stage-quad") cm.setFrameFullscreenQuad() quad = NodePath(cm.generate()) quad.setDepthTest(0) quad.setDepthWrite(0) quad.setColor(1, 0.5, 0.5, 1) quadcamnode = Camera("filter-quad-cam") lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) quadcamnode.setLens(lens) quadcam = quad.attachNewNode(quadcamnode) dr = buffer.makeDisplayRegion((0, 1, 0, 1)) dr.disableClears() dr.setCamera(quadcam) dr.setActive(True) dr.setScissorEnabled(False) # This clear stage is important if the buffer is padded, so that # any pixels accidentally sampled in the padded region won't # be reading from unititialised memory. buffer.setClearColor((0, 0, 0, 1)) buffer.setClearColorActive(True) self.buffers.append(buffer) self.sizes.append((mul, div, align)) return quad
class CreditsReel(object): ''' Creates a Panda scene that's independent of the main render tree, so that the credits can be displayed in a smaller display region within the main screen. ''' def __init__(self, app): self.app = app self.do = DirectObject() self.running = False self.displayRegion = None self.root = NodePath('creditsRender') self.camera = Camera('creditsCam') self.cameraNP = NodePath(self.camera) # Set parameters to match those of render2d self.root.setDepthTest(0) self.root.setDepthWrite(0) self.root.setMaterialOff(1) self.root.setTwoSided(1) self.aspect2d = self.root # self.root.attachNewNode('creditsAspect') lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setFilmOffset(0, 0) lens.setNearFar(-1000, 1000) self.camera.setLens(lens) self.cameraNP.reparentTo(self.root) self.scrollTask = None self.lastTime = None self.creditsFileLoaded = False def loadCreditsFileIfNeeded(self): if self.creditsFileLoaded: return filePath = getPath(startupMenu, 'credits3d.txt') with codecs.open(filePath, 'rU', encoding='utf-8') as f: creditsLines = f.read().splitlines() y = 0 for line in creditsLines: if line.startswith('!!'): font = self.app.fonts.creditsH1 line = line[len('!!'):] elif line.startswith('!'): font = self.app.fonts.creditsH2 line = line[len('!'):] else: font = self.app.fonts.creditsFont lineHeight = font.getPandaLineHeight(self.app) node = font.makeOnscreenText( self.app, text=line or ' ', # Prevents .getNumRows() bug fg=self.app.theme.colours.mainMenuColour, align=TextNode.ACenter, pos=(0, y - lineHeight), parent=self.aspect2d, wordwrap=28, ) y -= lineHeight * (node.textNode.getNumRows() + 0.2) self.creditsFileLoaded = True def stop(self): if not self.running: return self.running = False self.do.ignoreAll() if self.scrollTask is not None: self.app.panda.taskMgr.remove(self.scrollTask) self.scrollTask = None if self.displayRegion is not None: self.displayRegion.setActive(False) def start(self): self.cameraNP.setPos((0, 0, 0)) if self.running: return self.running = True self.loadCreditsFileIfNeeded() if self.scrollTask is None: self.scrollTask = self.app.panda.taskMgr.add( self.updateCredits, 'creditsLoop') self.lastTime = self.scrollTask.time if self.displayRegion is None: self.displayRegion = self.app.panda.win.makeDisplayRegion() self.displayRegion.setSort(5) self.displayRegion.setClearDepthActive(1) self.displayRegion.setIncompleteRender(False) self.displayRegion.setCamera(self.cameraNP) self.displayRegion.setActive(True) self.do.accept('aspectRatioChanged', self.aspectRatioChanged) self.aspectRatioChanged() def aspectRatioChanged(self): # Scaling from -1 to +1 idealTop = 0.66 idealBottom = -0.74 aspectRatio = self.app.panda.getAspectRatio() top = idealTop * min(aspectRatio * 3. / 4, 1) bottom = idealBottom * min(aspectRatio * 3. / 4, 1) self.displayRegion.setDimensions(0, 1, 0.5 * (1 + bottom), 0.5 * (1 + top)) windowRatio = 2 * aspectRatio / (top - bottom) self.cameraNP.setScale(windowRatio * 3. / 4, 1.0, 1.0) def updateCredits(self, task): deltaT = task.time - self.lastTime self.lastTime = task.time z = self.cameraNP.getZ() z -= deltaT * CREDITS_SCROLL_SPEED self.cameraNP.setZ(z) return Task.cont