class SkyAOCaptureStage(RenderStage): """ This stage captures the sky ao by rendering the scene from above """ required_inputs = [] required_pipes = [] @property def produced_pipes(self): return {"SkyAOHeight": self.target_convert.color_tex} @property def produced_inputs(self): return {"SkyAOCapturePosition": self.pta_position} def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.pta_position = PTALVecBase3f.empty_array(1) self.resolution = 512 self.capture_height = 100.0 self.max_radius = 100.0 def create(self): self.camera = Camera("SkyAOCaptureCam") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(self.max_radius, self.max_radius) self.cam_lens.set_near_far(0, self.capture_height) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.cam_node.look_at(0, 0, -1) self.cam_node.set_r(0) self.target = self.create_target("SkyAOCapture") self.target.size = self.resolution self.target.add_depth_attachment(bits=16) self.target.prepare_render(self.cam_node) self.target_convert = self.create_target("ConvertDepth") self.target_convert.size = self.resolution self.target_convert.add_color_attachment(bits=(16, 0, 0, 0)) self.target_convert.prepare_buffer() self.target_convert.set_shader_inputs( DepthTex=self.target.depth_tex, position=self.pta_position) # Register camera self._pipeline.tag_mgr.register_camera("shadow", self.camera) def update(self): snap_size = self.max_radius / self.resolution cam_pos = Globals.base.camera.get_pos(Globals.base.render) self.cam_node.set_pos( cam_pos.x - cam_pos.x % snap_size, cam_pos.y - cam_pos.y % snap_size, self.capture_height / 2.0) self.pta_position[0] = self.cam_node.get_pos() def reload_shaders(self): self.target_convert.shader = self.load_plugin_shader("convert_depth.frag.glsl")
class PbCameraNode(NodePath): """Pybullet-compatible camera node wrapper """ Z2Y = Mat4(1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1) def __init__(self, render: NodePath): self._camera = Camera('pb_camera') self._lens = MatrixLens() self._camera.set_lens(self._lens) super().__init__(self._camera) self.reparent_to(render) def set_active(self, active: bool): self._camera.set_active(active) def is_active(self): return self._camera.is_active() def update(self, pb_camera): self.set_mat(self.Z2Y * Mat4(*pb_camera.pose_mat)) mat = np.asarray(pb_camera.proj_mat).reshape(4, 4) m22, m32 = -mat[2, 2], -mat[3, 2] zfar = (2.0 * m32) / (2.0 * m22 - 2.0) znear = ((m22 - 1.0) * zfar) / (m22 + 1.0) self._lens.set_near_far(znear, zfar) self._lens.set_user_mat(self.Z2Y * Mat4(*pb_camera.proj_mat))
class SkyAOCaptureStage(RenderStage): """ This stage captures the sky ao by rendering the scene from above """ required_inputs = [] required_pipes = [] @property def produced_pipes(self): return {"SkyAOHeight": self.target_convert.color_tex} @property def produced_inputs(self): return {"SkyAOCapturePosition": self.pta_position} def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.pta_position = PTALVecBase3f.empty_array(1) self.resolution = 512 self.capture_height = 100.0 self.max_radius = 100.0 def create(self): self.camera = Camera("SkyAOCaptureCam") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(self.max_radius, self.max_radius) self.cam_lens.set_near_far(0, self.capture_height) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.cam_node.look_at(0, 0, -1) self.cam_node.set_r(0) self.target = self.create_target("SkyAOCapture") self.target.size = self.resolution self.target.add_depth_attachment(bits=16) self.target.prepare_render(self.cam_node) self.target_convert = self.create_target("ConvertDepth") self.target_convert.size = self.resolution self.target_convert.add_color_attachment(bits=(16, 0, 0, 0)) self.target_convert.prepare_buffer() self.target_convert.set_shader_inputs(DepthTex=self.target.depth_tex, position=self.pta_position) # Register camera self._pipeline.tag_mgr.register_camera("shadow", self.camera) def update(self): snap_size = self.max_radius / self.resolution cam_pos = Globals.base.camera.get_pos(Globals.base.render) self.cam_node.set_pos(cam_pos.x - cam_pos.x % snap_size, cam_pos.y - cam_pos.y % snap_size, self.capture_height / 2.0) self.pta_position[0] = self.cam_node.get_pos() def reload_shaders(self): self.target_convert.shader = self.load_plugin_shader( "convert_depth.frag.glsl")
def create_camera(self, name, projection_mat): """ Create a camera with the given projection matrix. """ cam_node = Camera(name) lens = MatrixLens() lens.set_user_mat(projection_mat) cam_node.set_lens(lens) return cam_node
def _make_fullscreen_cam(self): """ Creates an orthographic camera for the buffer """ buffer_cam = Camera("BufferCamera") lens = OrthographicLens() lens.set_film_size(2, 2) lens.set_film_offset(0, 0) lens.set_near_far(-100, 100) buffer_cam.set_lens(lens) buffer_cam.set_cull_bounds(OmniBoundingVolume()) self._camera = self._node.attach_new_node(buffer_cam) self._region.set_camera(self._camera)
class ForwardStage(RenderStage): """ Forward shading stage, which first renders all forward objects, and then merges them with the scene """ required_inputs = [ "DefaultEnvmap", "PrefilteredBRDF", "PrefilteredCoatBRDF", "EnvProbes" ] required_pipes = [ "SceneDepth", "ShadedScene", "PerCellProbes", "CellIndices" ] @property def produced_pipes(self): return {"ShadedScene": self.target_merge.color_tex} def create(self): self.forward_cam = Camera("ForwardShadingCam") self.forward_cam.set_lens(Globals.base.camLens) self.forward_cam_np = Globals.base.camera.attach_new_node( self.forward_cam) self.target = self.create_target("ForwardShading") self.target.add_color_attachment(bits=16, alpha=True) self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.forward_cam_np) self.target.set_clear_color(0, 0, 0, 0) self._pipeline.tag_mgr.register_camera("forward", self.forward_cam) self.target_merge = self.create_target("MergeWithDeferred") self.target_merge.add_color_attachment(bits=16) self.target_merge.prepare_buffer() self.target_merge.set_shader_input("ForwardDepth", self.target.depth_tex) self.target_merge.set_shader_input("ForwardColor", self.target.color_tex) def set_shader_input(self, *args): Globals.base.render.set_shader_input(*args) RenderStage.set_shader_input(self, *args) def reload_shaders(self): self.target_merge.shader = self.load_plugin_shader( "merge_with_deferred.frag.glsl")
class ForwardStage(RenderStage): """ Forward shading stage, which first renders all forward objects, and then merges them with the scene """ required_inputs = ["DefaultEnvmap", "PrefilteredBRDF", "PrefilteredCoatBRDF"] required_pipes = ["SceneDepth", "ShadedScene", "CellIndices"] @property def produced_pipes(self): return {"ShadedScene": self.target_merge.color_tex} def create(self): self.forward_cam = Camera("ForwardShadingCam") self.forward_cam.set_lens(Globals.base.camLens) self.forward_cam_np = Globals.base.camera.attach_new_node(self.forward_cam) self.target = self.create_target("ForwardShading") self.target.add_color_attachment(bits=16, alpha=True) self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.forward_cam_np) self.target.set_clear_color(0, 0, 0, 0) self._pipeline.tag_mgr.register_camera("forward", self.forward_cam) self.target_merge = self.create_target("MergeWithDeferred") self.target_merge.add_color_attachment(bits=16) self.target_merge.prepare_buffer() self.target_merge.set_shader_inputs( ForwardDepth=self.target.depth_tex, ForwardColor=self.target.color_tex) def set_shader_input(self, *args): Globals.base.render.set_shader_input(*args) RenderStage.set_shader_input(self, *args) def set_shader_inputs(self, **kwargs): Globals.base.render.set_shader_inputs(**kwargs) RenderStage.set_shader_inputs(self, **kwargs) def reload_shaders(self): self.target_merge.shader = self.load_plugin_shader("merge_with_deferred.frag.glsl")
class ForwardInjectionStage(RenderStage): """ Forward shading stage, which renders all forward objects and injects them into the clustered grid. Also produces a depth map which is used later on in the shading stage. """ required_inputs = [] required_pipes = [] @property def produced_pipes(self): return {} def create(self): self.forward_cam = Camera("ForwardShadingCam") self.forward_cam.set_lens(Globals.base.camLens) self.forward_cam_np = Globals.base.camera.attach_new_node( self.forward_cam) self.target = self.create_target("ForwardShading") self.target.add_color_attachment(bits=16, alpha=True) self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.forward_cam_np) self.target.set_clear_color(0, 0, 0, 0) self._pipeline.tag_mgr.register_camera("forward", self.forward_cam) self.target_merge = self.create_target("MergeWithDeferred") self.target_merge.add_color_attachment(bits=16) self.target_merge.prepare_buffer() self.target_merge.set_shader_input("ForwardDepth", self.target.depth_tex) self.target_merge.set_shader_input("ForwardColor", self.target.color_tex) def set_shader_input(self, *args): Globals.base.render.set_shader_input(*args) RenderStage.set_shader_input(self, *args) def reload_shaders(self): self.target_merge.shader = self.load_plugin_shader( "merge_with_deferred.frag.glsl")
def init(self): for i in range(self._max_updates): camera = Camera("ShadowCam-" + str(i)) camera.set_lens(MatrixLens()) camera.set_active(False) camera.set_scene(self._scene_parent) self._tag_state_mgr.register_shadow_camera(camera) self._camera_nps.append(self._scene_parent.attach_new_node(camera)) self._cameras.append(camera) region = self._atlas_graphics_output.make_display_region() region.set_sort(1000) region.set_clear_depth_active(True) region.set_clear_depth(1.0) region.set_clear_color_active(False) region.set_camera(self._camera_nps[i]) region.set_active(False) self._display_regions.append(region) self._atlas = ShadowAtlas(self._atlas_size)
def init(self): for i in range(self._max_updates): camera = Camera("ShadowCam-" + str(i)) camera.set_lens(MatrixLens()) camera.set_active(False) camera.set_scene(self._scene_parent) self._tag_state_mgr.register_camera("shadow", camera) self._camera_nps.append(self._scene_parent.attach_new_node(camera)) self._cameras.append(camera) region = self._atlas_graphics_output.make_display_region() region.set_sort(1000) region.set_clear_depth_active(True) region.set_clear_depth(1.0) region.set_clear_color_active(False) region.set_camera(self._camera_nps[i]) region.set_active(False) self._display_regions.append(region) self._atlas = ShadowAtlas(self._atlas_size)
class ForwardInjectionStage(RenderStage): """ Forward shading stage, which renders all forward objects and injects them into the clustered grid. Also produces a depth map which is used later on in the shading stage. """ required_inputs = [] required_pipes = [] @property def produced_pipes(self): return {} def create(self): self.forward_cam = Camera("ForwardShadingCam") self.forward_cam.set_lens(Globals.base.camLens) self.forward_cam_np = Globals.base.camera.attach_new_node(self.forward_cam) self.target = self.create_target("ForwardShading") self.target.add_color_attachment(bits=16, alpha=True) self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.forward_cam_np) self.target.set_clear_color(0, 0, 0, 0) self._pipeline.tag_mgr.register_camera("forward", self.forward_cam) self.target_merge = self.create_target("MergeWithDeferred") self.target_merge.add_color_attachment(bits=16) self.target_merge.prepare_buffer() self.target_merge.set_shader_input("ForwardDepth", self.target.depth_tex) self.target_merge.set_shader_input("ForwardColor", self.target.color_tex) def set_shader_input(self, *args): Globals.base.render.set_shader_input(*args) RenderStage.set_shader_input(self, *args) def reload_shaders(self): self.target_merge.shader = self.load_plugin_shader("merge_with_deferred.frag.glsl")
def __take_hd_screenshot(self): mx, my, mz = self.__map.data.shape # find the optimal zoom fit max_width = max(mx, my) max_height = max(mx, mz) tex = Texture() width = 4096 height = 4096 ss_buf = self._services.graphics.window.win.make_texture_buffer('hd_screenshot_buff', width, height, tex, True) cam = Camera('hd_cam') cam.set_lens(self._services.graphics.window.camLens.make_copy()) cam.get_lens().set_aspect_ratio(width / height) np_cam = NodePath(cam) np_cam.reparent_to(self.__world) x, y, z = self.__world.get_pos() if mz > 1: # 3D map np_cam.set_pos(x - mx * 2.1, y - max_height * 3.6, z) np_cam.set_h(-30) else: # 2D map np_cam.set_pos(x, y, z + max_width * 2 + 1) np_cam.set_p(-90) ss_cam = self._services.graphics.window.make_camera(ss_buf, useCamera=np_cam) ss_scene = self._services.graphics.window.render ss_cam.node().set_scene(ss_scene) self._services.graphics.window.graphicsEngine.render_frame() tex = ss_buf.get_texture() ss_buf.set_active(False) self._services.resources.screenshots_dir.append(lambda fn: tex.write(fn)) self._services.graphics.window.graphicsEngine.remove_window(ss_buf)
class PSSMSceneShadowStage(RenderStage): """ This stage creates the shadow map which covers the whole important part of the scene. This is required because the shadow cascades only cover the view frustum, but many plugins (VXGI, EnvMaps) require a shadow map. """ required_inputs = [] required_pipes = [] def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.resolution = 2048 self.sun_vector = Vec3(0, 0, 1) self.sun_distance = 10.0 self.pta_mvp = PTAMat4.empty_array(1) self.focus = None # Store last focus entirely for the purpose of being able to see # it in the debugger self.last_focus = None @property def produced_inputs(self): return {"PSSMSceneSunShadowMVP": self.pta_mvp} @property def produced_pipes(self): return {"PSSMSceneSunShadowMapPCF": (self.target.depth_tex, self.make_pcf_state())} def make_pcf_state(self): state = SamplerState() state.set_minfilter(SamplerState.FT_shadow) state.set_magfilter(SamplerState.FT_shadow) return state def request_focus(self, focus_point, focus_size): self.focus = (focus_point, focus_size) self.last_focus = self.focus @property def mvp(self): return Globals.base.render.get_transform(self.cam_node).get_mat() * \ self.cam_lens.get_projection_mat() def update(self): if self._pipeline.task_scheduler.is_scheduled("pssm_scene_shadows"): if self.focus is None: # When no focus is set, there is no point in rendering the shadow map self.target.active = False else: focus_point, focus_size = self.focus self.cam_lens.set_near_far(0.0, 2 * (focus_size + self.sun_distance)) self.cam_lens.set_film_size(2 * focus_size, 2 * focus_size) self.cam_node.set_pos( focus_point + self.sun_vector * (self.sun_distance + focus_size)) self.cam_node.look_at(focus_point) snap_shadow_map(self.mvp, self.cam_node, self.resolution) self.target.active = True self.pta_mvp[0] = self.mvp self.focus = None else: self.target.active = False def create(self): self.camera = Camera("PSSMSceneSunShadowCam") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(400, 400) self.cam_lens.set_near_far(100.0, 1800.0) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.target = self.create_target("ShadowMap") self.target.size = self.resolution self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.cam_node) # Register shadow camera self._pipeline.tag_mgr.register_camera("shadow", self.camera) def set_shader_input(self, *args): Globals.render.set_shader_input(*args) def set_shader_inputs(self, **kwargs): Globals.render.set_shader_inputs(**kwargs)
class CloudStage(RenderStage): """ This stage handles the volumetric cloud rendering """ required_pipes = ["ShadedScene", "GBuffer", "ScatteringIBLDiffuse"] def __init__(self, pipeline): RenderStage.__init__(self, "CloudStage", pipeline) self._voxel_res_xy = 256 self._voxel_res_z = 16 def get_produced_pipes(self): return {"ShadedScene": self._target_apply_clouds["color"]} def get_produced_defines(self): return {"CLOUD_RES_XY": self._voxel_res_xy, "CLOUD_RES_Z": self._voxel_res_z} def create(self): # Construct the voxel texture self._cloud_voxels = Image.create_3d( "CloudVoxels", self._voxel_res_xy, self._voxel_res_xy, self._voxel_res_z, Texture.T_unsigned_byte, Texture.F_rgba8, ) self._cloud_voxels.get_texture().set_wrap_u(SamplerState.WM_repeat) self._cloud_voxels.get_texture().set_wrap_v(SamplerState.WM_repeat) self._cloud_voxels.get_texture().set_wrap_w(SamplerState.WM_border_color) self._cloud_voxels.get_texture().set_border_color(Vec4(0, 0, 0, 0)) # self._cloud_voxels.get_texture().set_border_color(Vec4(1, 0, 0, 1)) # Construct the target which populates the voxel texture self._grid_target = self._create_target("Clouds:CreateGrid") self._grid_target.set_size(self._voxel_res_xy, self._voxel_res_xy) self._grid_target.prepare_offscreen_buffer() self._grid_target.set_shader_input("CloudVoxels", self._cloud_voxels.get_texture()) # Construct the target which shades the voxels self._shade_target = self._create_target("Clouds:ShadeVoxels") self._shade_target.set_size(self._voxel_res_xy, self._voxel_res_xy) self._shade_target.prepare_offscreen_buffer() self._shade_target.set_shader_input("CloudVoxels", self._cloud_voxels.get_texture()) self._shade_target.set_shader_input("CloudVoxelsDest", self._cloud_voxels.get_texture()) self._particle_target = self._create_target("Clouds:RenderClouds") self._particle_target.set_half_resolution() self._particle_target.add_color_texture(bits=16) self._particle_target.prepare_offscreen_buffer() self._particle_target.set_shader_input("CloudVoxels", self._cloud_voxels.get_texture()) # self._make_particle_scene() self._target_apply_clouds = self._create_target("Clouds:ApplyClouds") self._target_apply_clouds.add_color_texture(bits=16) self._target_apply_clouds.prepare_offscreen_buffer() self._target_apply_clouds.set_shader_input("CloudsTex", self._particle_target["color"]) def _make_particle_scene(self): # Create a new scene root self._particle_scene = Globals.base.render.attach_new_node("CloudParticles") self._particle_scene.hide(self._pipeline.tag_mgr.get_gbuffer_mask()) self._particle_scene.hide(self._pipeline.tag_mgr.get_shadow_mask()) self._particle_scene.hide(self._pipeline.tag_mgr.get_voxelize_mask()) cm = CardMaker("") cm.set_frame(-1.0, 1.0, -1.0, 1.0) cm.set_has_normals(False) cm.set_has_uvs(False) card_node = cm.generate() card_node.set_bounds(OmniBoundingVolume()) card_node.set_final(True) self._particle_np = self._particle_scene.attach_new_node(card_node) self._particle_np.set_shader_input("CloudVoxels", self._cloud_voxels.get_texture()) self._particle_np.set_instance_count(self._voxel_res_xy * self._voxel_res_xy * self._voxel_res_z) self._particle_np.set_transparency(TransparencyAttrib.M_multisample, 1000000) self._particle_scene.set_transparency(TransparencyAttrib.M_multisample, 1000000) self._particle_cam = Camera("CloudParticleCam") self._particle_cam.set_lens(Globals.base.camLens) self._particle_cam_np = self._particle_scene.attach_new_node(self._particle_cam) cloud_particle_mask = BitMask32.bit(16) self._particle_cam.set_camera_mask(cloud_particle_mask) render.hide(cloud_particle_mask) self._particle_scene.show_through(cloud_particle_mask) self._particle_target = self._create_target("Clouds:RenderParticles") self._particle_target.add_color_texture(bits=16) self._particle_target.set_source(self._particle_cam_np, Globals.base.win) self._particle_target.set_enable_transparency(True) self._particle_target.prepare_scene_render() self._particle_target.set_clear_color(True, color=Vec4(0, 0, 0, 0)) def update(self): pass # self._particle_cam_np.set_transform( # Globals.base.camera.get_transform(Globals.base.render)) def set_shaders(self): self._grid_target.set_shader(self._load_plugin_shader("GenerateClouds.frag.glsl")) self._target_apply_clouds.set_shader(self._load_plugin_shader("ApplyClouds.frag.glsl")) self._shade_target.set_shader(self._load_plugin_shader("ShadeClouds.frag.glsl")) self._particle_target.set_shader(self._load_plugin_shader("RenderClouds.frag.glsl"))
class VoxelizationStage(RenderStage): """ This stage voxelizes the whole scene """ required_inputs = ["DefaultEnvmap", "AllLightsData", "maxLightIndex"] required_pipes = [] # The different states of voxelization S_disabled = 0 S_voxelize_x = 1 S_voxelize_y = 2 S_voxelize_z = 3 S_gen_mipmaps = 4 def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.voxel_resolution = 256 self.voxel_world_size = -1 self.state = self.S_disabled self.create_ptas() def set_grid_position(self, pos): self.pta_next_grid_pos[0] = pos def create_ptas(self): self.pta_next_grid_pos = PTALVecBase3.empty_array(1) self.pta_grid_pos = PTALVecBase3.empty_array(1) @property def produced_inputs(self): return {"voxelGridPosition": self.pta_grid_pos} @property def produced_pipes(self): return {"SceneVoxels": self.voxel_grid} def create(self): # Create the voxel grid used to generate the voxels self.voxel_temp_grid = Image.create_3d("VoxelsTemp", self.voxel_resolution, self.voxel_resolution, self.voxel_resolution, "RGBA8") self.voxel_temp_grid.set_clear_color(Vec4(0)) self.voxel_temp_nrm_grid = Image.create_3d("VoxelsTemp", self.voxel_resolution, self.voxel_resolution, self.voxel_resolution, "R11G11B10") self.voxel_temp_nrm_grid.set_clear_color(Vec4(0)) # Create the voxel grid which is a copy of the temporary grid, but stable self.voxel_grid = Image.create_3d("Voxels", self.voxel_resolution, self.voxel_resolution, self.voxel_resolution, "RGBA8") self.voxel_grid.set_clear_color(Vec4(0)) self.voxel_grid.set_minfilter(SamplerState.FT_linear_mipmap_linear) # Create the camera for voxelization self.voxel_cam = Camera("VoxelizeCam") self.voxel_cam.set_camera_mask( self._pipeline.tag_mgr.get_voxelize_mask()) self.voxel_cam_lens = OrthographicLens() self.voxel_cam_lens.set_film_size(-2.0 * self.voxel_world_size, 2.0 * self.voxel_world_size) self.voxel_cam_lens.set_near_far(0.0, 2.0 * self.voxel_world_size) self.voxel_cam.set_lens(self.voxel_cam_lens) self.voxel_cam_np = Globals.base.render.attach_new_node(self.voxel_cam) self._pipeline.tag_mgr.register_camera("voxelize", self.voxel_cam) # Create the voxelization target self.voxel_target = self.create_target("VoxelizeScene") self.voxel_target.size = self.voxel_resolution self.voxel_target.prepare_render(self.voxel_cam_np) # Create the target which copies the voxel grid self.copy_target = self.create_target("CopyVoxels") self.copy_target.size = self.voxel_resolution self.copy_target.prepare_buffer() # TODO! Does not work with the new render target yet - maybe add option # to post process region for instances? self.copy_target.instance_count = self.voxel_resolution self.copy_target.set_shader_input("SourceTex", self.voxel_temp_grid) self.copy_target.set_shader_input("DestTex", self.voxel_grid) # Create the target which generates the mipmaps self.mip_targets = [] mip_size, mip = self.voxel_resolution, 0 while mip_size > 1: mip_size, mip = mip_size // 2, mip + 1 mip_target = self.create_target("GenMipmaps:" + str(mip)) mip_target.size = mip_size mip_target.prepare_buffer() mip_target.instance_count = mip_size mip_target.set_shader_input("SourceTex", self.voxel_grid) mip_target.set_shader_input("sourceMip", mip - 1) mip_target.set_shader_input("DestTex", self.voxel_grid, False, True, -1, mip, 0) self.mip_targets.append(mip_target) # Create the initial state used for rendering voxels initial_state = NodePath("VXGIInitialState") initial_state.set_attrib( CullFaceAttrib.make(CullFaceAttrib.M_cull_none), 100000) initial_state.set_attrib(DepthTestAttrib.make(DepthTestAttrib.M_none), 100000) initial_state.set_attrib(ColorWriteAttrib.make(ColorWriteAttrib.C_off), 100000) self.voxel_cam.set_initial_state(initial_state.get_state()) Globals.base.render.set_shader_input("voxelGridPosition", self.pta_next_grid_pos) Globals.base.render.set_shader_input("VoxelGridDest", self.voxel_temp_grid) def update(self): self.voxel_cam_np.show() self.voxel_target.active = True self.copy_target.active = False for target in self.mip_targets: target.active = False # Voxelization disable if self.state == self.S_disabled: self.voxel_cam_np.hide() self.voxel_target.active = False # Voxelization from X-Axis elif self.state == self.S_voxelize_x: # Clear voxel grid self.voxel_temp_grid.clear_image() self.voxel_cam_np.set_pos(self.pta_next_grid_pos[0] + Vec3(self.voxel_world_size, 0, 0)) self.voxel_cam_np.look_at(self.pta_next_grid_pos[0]) # Voxelization from Y-Axis elif self.state == self.S_voxelize_y: self.voxel_cam_np.set_pos(self.pta_next_grid_pos[0] + Vec3(0, self.voxel_world_size, 0)) self.voxel_cam_np.look_at(self.pta_next_grid_pos[0]) # Voxelization from Z-Axis elif self.state == self.S_voxelize_z: self.voxel_cam_np.set_pos(self.pta_next_grid_pos[0] + Vec3(0, 0, self.voxel_world_size)) self.voxel_cam_np.look_at(self.pta_next_grid_pos[0]) # Generate mipmaps elif self.state == self.S_gen_mipmaps: self.voxel_target.active = False self.copy_target.active = True self.voxel_cam_np.hide() for target in self.mip_targets: target.active = True # As soon as we generate the mipmaps, we need to update the grid position # as well self.pta_grid_pos[0] = self.pta_next_grid_pos[0] def reload_shaders(self): self.copy_target.shader = self.load_plugin_shader( "/$$rp/shader/default_post_process_instanced.vert.glsl", "copy_voxels.frag.glsl") mip_shader = self.load_plugin_shader( "/$$rp/shader/default_post_process_instanced.vert.glsl", "generate_mipmaps.frag.glsl") for target in self.mip_targets: target.shader = mip_shader def set_shader_input(self, *args): Globals.render.set_shader_input(*args)
class PSSMSceneShadowStage(RenderStage): """ This stage creates the shadow map which covers the whole important part of the scene. This is required because the shadow cascades only cover the view frustum, but many plugins (VXGI, EnvMaps) require a shadow map. """ required_inputs = [] required_pipes = [] def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.resolution = 2048 self.sun_vector = Vec3(0, 0, 1) self.pta_mvp = PTAMat4.empty_array(1) @property def produced_inputs(self): return {"PSSMSceneSunShadowMVP": self.pta_mvp} @property def produced_pipes(self): return {"PSSMSceneSunShadowMapPCF": (self.target.depth_tex, self.make_pcf_state())} def make_pcf_state(self): state = SamplerState() state.set_minfilter(SamplerState.FT_shadow) state.set_magfilter(SamplerState.FT_shadow) return state @property def mvp(self): return Globals.base.render.get_transform(self.cam_node).get_mat() * \ self.cam_lens.get_projection_mat() def update(self): if self._pipeline.task_scheduler.is_scheduled("pssm_scene_shadows"): cam_pos = Globals.base.cam.get_pos(Globals.base.render) self.cam_node.set_pos(cam_pos + self.sun_vector * 900) self.cam_node.look_at(cam_pos) snap_shadow_map(self.mvp, self.cam_node, self.resolution) self.target.active = True self.pta_mvp[0] = self.mvp else: self.target.active = False def create(self): self.camera = Camera("PSSMSceneSunShadowCam") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(400, 400) self.cam_lens.set_near_far(100.0, 1800.0) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.target = self.create_target("ShadowMap") self.target.size = self.resolution self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.cam_node) # Register shadow camera self._pipeline.tag_mgr.register_shadow_camera(self.camera) def set_shader_input(self, *args): Globals.render.set_shader_input(*args)
def create_camera(self, name, projection_mat): cam_node = Camera(name) lens = MatrixLens() lens.set_user_mat(projection_mat) cam_node.set_lens(lens) return cam_node
class VXGISunShadowStage(RenderStage): """ This stage creates the shadow map which covers the whole voxel grid, to provide sun shadows for the GI """ required_inputs = [] def __init__(self, pipeline): RenderStage.__init__(self, "VXGISunShadowStage", pipeline) self._resolution = 2048 self._sun_vector = Vec3(0, 0, 1) self._pta_mvp = PTAMat4.empty_array(1) def get_produced_inputs(self): return {"VXGISunShadowMVP": self._pta_mvp} def get_produced_pipes(self): return {"VXGISunShadowMap": (self._target['depth'], self.make_pcf_state()) } def make_pcf_state(self): state = SamplerState() state.set_minfilter(SamplerState.FT_shadow) state.set_magfilter(SamplerState.FT_shadow) return state def set_resolution(self, res): self._resolution = res def set_sun_vector(self, direction): self._sun_vector = direction distance = 400.0 cam_pos = Globals.base.cam.get_pos(Globals.base.render) self._cam_node.set_pos(cam_pos + self._sun_vector * distance) self._cam_node.look_at(cam_pos) # Compute MVP transform = Globals.base.render.get_transform(self._cam_node).get_mat() self._pta_mvp[0] = transform * self._cam_lens.get_projection_mat() def create(self): self._camera = Camera("VXGISunShadowCam") self._cam_lens = OrthographicLens() self._cam_lens.set_film_size(400, 400) self._cam_lens.set_near_far(0.0, 800.0) self._camera.set_lens(self._cam_lens) self._cam_node = Globals.base.render.attach_new_node(self._camera) self._target = self.make_target("PSSMDistShadowMap") self._target.set_source(self._cam_node, Globals.base.win) self._target.size = self._resolution self._target.add_depth_texture(bits=32) self._target.create_overlay_quad = False self._target.color_write = False self._target.prepare_scene_render() # Register shadow camera self._pipeline.tag_mgr.register_shadow_camera(self._camera) def set_shader_input(self, *args): Globals.render.set_shader_input(*args)
class VoxelizationStage(RenderStage): """ This stage voxelizes the whole scene """ required_inputs = ["DefaultEnvmap", "AllLightsData", "maxLightIndex"] required_pipes = [] # The different states of voxelization S_disabled = 0 S_voxelize_x = 1 S_voxelize_y = 2 S_voxelize_z = 3 S_gen_mipmaps = 4 def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.voxel_resolution = 256 self.voxel_world_size = -1 self.state = self.S_disabled self.create_ptas() def set_grid_position(self, pos): self.pta_next_grid_pos[0] = pos def create_ptas(self): self.pta_next_grid_pos = PTALVecBase3.empty_array(1) self.pta_grid_pos = PTALVecBase3.empty_array(1) @property def produced_inputs(self): return {"voxelGridPosition": self.pta_grid_pos} @property def produced_pipes(self): return {"SceneVoxels": self.voxel_grid} def create(self): # Create the voxel grid used to generate the voxels self.voxel_temp_grid = Image.create_3d( "VoxelsTemp", self.voxel_resolution, self.voxel_resolution, self.voxel_resolution, "RGBA8") self.voxel_temp_grid.set_clear_color(Vec4(0)) self.voxel_temp_nrm_grid = Image.create_3d( "VoxelsTemp", self.voxel_resolution, self.voxel_resolution, self.voxel_resolution, "R11G11B10") self.voxel_temp_nrm_grid.set_clear_color(Vec4(0)) # Create the voxel grid which is a copy of the temporary grid, but stable self.voxel_grid = Image.create_3d( "Voxels", self.voxel_resolution, self.voxel_resolution, self.voxel_resolution, "RGBA8") self.voxel_grid.set_clear_color(Vec4(0)) self.voxel_grid.set_minfilter(SamplerState.FT_linear_mipmap_linear) # Create the camera for voxelization self.voxel_cam = Camera("VoxelizeCam") self.voxel_cam.set_camera_mask(self._pipeline.tag_mgr.get_voxelize_mask()) self.voxel_cam_lens = OrthographicLens() self.voxel_cam_lens.set_film_size( -2.0 * self.voxel_world_size, 2.0 * self.voxel_world_size) self.voxel_cam_lens.set_near_far(0.0, 2.0 * self.voxel_world_size) self.voxel_cam.set_lens(self.voxel_cam_lens) self.voxel_cam_np = Globals.base.render.attach_new_node(self.voxel_cam) self._pipeline.tag_mgr.register_camera("voxelize", self.voxel_cam) # Create the voxelization target self.voxel_target = self.create_target("VoxelizeScene") self.voxel_target.size = self.voxel_resolution self.voxel_target.prepare_render(self.voxel_cam_np) # Create the target which copies the voxel grid self.copy_target = self.create_target("CopyVoxels") self.copy_target.size = self.voxel_resolution self.copy_target.prepare_buffer() # TODO! Does not work with the new render target yet - maybe add option # to post process region for instances? self.copy_target.instance_count = self.voxel_resolution self.copy_target.set_shader_input("SourceTex", self.voxel_temp_grid) self.copy_target.set_shader_input("DestTex", self.voxel_grid) # Create the target which generates the mipmaps self.mip_targets = [] mip_size, mip = self.voxel_resolution, 0 while mip_size > 1: mip_size, mip = mip_size // 2, mip + 1 mip_target = self.create_target("GenMipmaps:" + str(mip)) mip_target.size = mip_size mip_target.prepare_buffer() mip_target.instance_count = mip_size mip_target.set_shader_input("SourceTex", self.voxel_grid) mip_target.set_shader_input("sourceMip", mip - 1) mip_target.set_shader_input("DestTex", self.voxel_grid, False, True, -1, mip, 0) self.mip_targets.append(mip_target) # Create the initial state used for rendering voxels initial_state = NodePath("VXGIInitialState") initial_state.set_attrib(CullFaceAttrib.make(CullFaceAttrib.M_cull_none), 100000) initial_state.set_attrib(DepthTestAttrib.make(DepthTestAttrib.M_none), 100000) initial_state.set_attrib(ColorWriteAttrib.make(ColorWriteAttrib.C_off), 100000) self.voxel_cam.set_initial_state(initial_state.get_state()) Globals.base.render.set_shader_input("voxelGridPosition", self.pta_next_grid_pos) Globals.base.render.set_shader_input("VoxelGridDest", self.voxel_temp_grid) def update(self): self.voxel_cam_np.show() self.voxel_target.active = True self.copy_target.active = False for target in self.mip_targets: target.active = False # Voxelization disable if self.state == self.S_disabled: self.voxel_cam_np.hide() self.voxel_target.active = False # Voxelization from X-Axis elif self.state == self.S_voxelize_x: # Clear voxel grid self.voxel_temp_grid.clear_image() self.voxel_cam_np.set_pos( self.pta_next_grid_pos[0] + Vec3(self.voxel_world_size, 0, 0)) self.voxel_cam_np.look_at(self.pta_next_grid_pos[0]) # Voxelization from Y-Axis elif self.state == self.S_voxelize_y: self.voxel_cam_np.set_pos( self.pta_next_grid_pos[0] + Vec3(0, self.voxel_world_size, 0)) self.voxel_cam_np.look_at(self.pta_next_grid_pos[0]) # Voxelization from Z-Axis elif self.state == self.S_voxelize_z: self.voxel_cam_np.set_pos( self.pta_next_grid_pos[0] + Vec3(0, 0, self.voxel_world_size)) self.voxel_cam_np.look_at(self.pta_next_grid_pos[0]) # Generate mipmaps elif self.state == self.S_gen_mipmaps: self.voxel_target.active = False self.copy_target.active = True self.voxel_cam_np.hide() for target in self.mip_targets: target.active = True # As soon as we generate the mipmaps, we need to update the grid position # as well self.pta_grid_pos[0] = self.pta_next_grid_pos[0] def reload_shaders(self): self.copy_target.shader = self.load_plugin_shader( "/$$rp/shader/default_post_process_instanced.vert.glsl", "copy_voxels.frag.glsl") mip_shader = self.load_plugin_shader( "/$$rp/shader/default_post_process_instanced.vert.glsl", "generate_mipmaps.frag.glsl") for target in self.mip_targets: target.shader = mip_shader def set_shader_input(self, *args): Globals.render.set_shader_input(*args)
class PSSMSceneShadowStage(RenderStage): """ This stage creates the shadow map which covers the whole important part of the scene. This is required because the shadow cascades only cover the view frustum, but many plugins (VXGI, EnvMaps) require a shadow map. """ required_inputs = [] required_pipes = [] def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.resolution = 2048 self._sun_vector = Vec3(0, 0, 1) self.pta_mvp = PTAMat4.empty_array(1) @property def produced_inputs(self): return {"PSSMSceneSunShadowMVP": self.pta_mvp} @property def produced_pipes(self): return {"PSSMSceneSunShadowMapPCF": (self.target.depth_tex, self.make_pcf_state())} def make_pcf_state(self): state = SamplerState() state.set_minfilter(SamplerState.FT_shadow) state.set_magfilter(SamplerState.FT_shadow) return state @property def sun_vector(self): return self._sun_vector @sun_vector.setter def sun_vector(self, direction): self._sun_vector = direction distance = 400.0 cam_pos = Globals.base.cam.get_pos(Globals.base.render) self.cam_node.set_pos(cam_pos + self._sun_vector * distance) self.cam_node.look_at(cam_pos) # This snaps the source to its texel grids, so that there is no flickering # visible when the source moves. This works by projecting the # Point (0,0,0) to light space, compute the texcoord differences and # offset the light world space position by that. mvp = Mat4(self.mvp) base_point = mvp.xform(Point4(0, 0, 0, 1)) * 0.5 + 0.5 texel_size = 1.0 / float(self.resolution) offset_x = base_point.x % texel_size offset_y = base_point.y % texel_size mvp.invert_in_place() new_base = mvp.xform(Point4( (base_point.x - offset_x) * 2.0 - 1.0, (base_point.y - offset_y) * 2.0 - 1.0, (base_point.z) * 2.0 - 1.0, 1)) self.cam_node.set_pos(self.cam_node.get_pos() - Vec3(new_base.x, new_base.y, new_base.z)) self.pta_mvp[0] = self.mvp @property def mvp(self): return Globals.base.render.get_transform(self.cam_node).get_mat() * \ self.cam_lens.get_projection_mat() def create(self): self.camera = Camera("PSSMSceneSunShadowCam") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(200, 200) self.cam_lens.set_near_far(100.0, 800.0) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.target = self.create_target("ShadowMap") self.target.size = self.resolution self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.cam_node) # Register shadow camera self._pipeline.tag_mgr.register_shadow_camera(self.camera) def set_shader_input(self, *args): Globals.render.set_shader_input(*args)
class VoxelizationStage(RenderStage): """ This stage voxelizes the whole scene """ required_inputs = [] required_pipes = [] # The different states of voxelization S_disabled = 0 S_voxelize_x = 1 S_voxelize_y = 2 S_voxelize_z = 3 S_gen_mipmaps = 4 def __init__(self, pipeline): RenderStage.__init__(self, "VoxelizationStage", pipeline) self._voxel_res = 256 self._voxel_ws = 50.0 self._next_grid_position = Vec3(0) self._state = self.S_disabled self._create_ptas() def set_state(self, state): self._state = state def set_grid_position(self, pos): self._next_grid_position = pos def _create_ptas(self): self._pta_grid_pos = PTALVecBase3.empty_array(1) self._pta_grid_size = PTAFloat.empty_array(1) self._pta_grid_res = PTAInt.empty_array(1) self._pta_grid_size[0] = self._voxel_ws self._pta_grid_res[0] = self._voxel_res def get_produced_inputs(self): return {"VoxelGridPosition": self._pta_grid_pos} def get_produced_pipes(self): return {"SceneVoxels": self._voxel_grid} def get_produced_defines(self): return {"VOXEL_GRID_RES": self._voxel_res, "VOXEL_GRID_WS_SIZE": self._voxel_ws} def create(self): # Create the voxel grid used to store the voxels self._voxel_grid = Image.create_3d( "Voxels", self._voxel_res, self._voxel_res, self._voxel_res, Texture.T_float, Texture.F_r11_g11_b10 ) self._voxel_grid.set_clear_color(Vec4(0)) # Create the camera for voxelization self._voxel_cam = Camera("VoxelizeCam") self._voxel_cam.set_camera_mask(self._pipeline.tag_mgr.get_voxelize_mask()) self._voxel_cam_lens = OrthographicLens() self._voxel_cam_lens.set_film_size(-self._voxel_ws, self._voxel_ws) self._voxel_cam_lens.set_near_far(0.0, 2.0 * self._voxel_ws) self._voxel_cam.set_lens(self._voxel_cam_lens) self._voxel_cam_np = Globals.base.render.attach_new_node(self._voxel_cam) self._pipeline.tag_mgr.register_voxelize_camera(self._voxel_cam) # Create the voxelization target self._voxel_target = self._create_target("VoxelizeScene") self._voxel_target.set_source(source_cam=self._voxel_cam_np, source_win=Globals.base.win) self._voxel_target.set_size(self._voxel_res, self._voxel_res) self._voxel_target.set_create_overlay_quad(False) self._voxel_target.prepare_scene_render() # Create the initial state used for rendering voxels initial_state = NodePath("VXInitialState") initial_state.set_attrib(CullFaceAttrib.make(CullFaceAttrib.M_cull_none), 100000) initial_state.set_attrib(DepthTestAttrib.make(DepthTestAttrib.M_none), 100000) initial_state.set_attrib(ColorWriteAttrib.make(ColorWriteAttrib.C_off), 100000) self._voxel_cam.set_initial_state(initial_state.get_state()) Globals.base.render.set_shader_input("voxelGridPosition", self._pta_grid_pos) Globals.base.render.set_shader_input("voxelGridRes", self._pta_grid_res) Globals.base.render.set_shader_input("voxelGridSize", self._pta_grid_size) Globals.base.render.set_shader_input("VoxelGridDest", self._voxel_grid.texture) def update(self): self._voxel_cam_np.show() self._voxel_target.set_active(True) # Voxelization disable if self._state == self.S_disabled: self._voxel_cam_np.hide() self._voxel_target.set_active(False) # Voxelization from X-Axis elif self._state == self.S_voxelize_x: self._voxel_cam_np.set_pos(self._next_grid_position + Vec3(self._voxel_ws, 0, 0)) self._voxel_cam_np.look_at(self._next_grid_position) # Voxelization from Y-Axis elif self._state == self.S_voxelize_y: self._voxel_cam_np.set_pos(self._next_grid_position + Vec3(0, self._voxel_ws, 0)) self._voxel_cam_np.look_at(self._next_grid_position) # Voxelization from Z-Axis elif self._state == self.S_voxelize_z: self._voxel_cam_np.set_pos(self._next_grid_position + Vec3(0, 0, self._voxel_ws)) self._voxel_cam_np.look_at(self._next_grid_position) # Generate mipmaps elif self._state == self.S_gen_mipmaps: self._voxel_target.set_active(False) self._voxel_cam_np.hide() # As soon as we generate the mipmaps, we need to update the grid position # as well self._pta_grid_pos[0] = self._next_grid_position def set_shaders(self): pass
class PSSMDistShadowStage(RenderStage): """ This stage generates a depth map using Variance Shadow Maps for very distant objects. """ required_inputs = [] def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.resolution = 2048 self.clip_size = 500 self.sun_distance = 8000 self.sun_vector = Vec3(0, 0, 1) self.pta_mvp = PTAMat4.empty_array(1) @property def produced_inputs(self): return {"PSSMDistSunShadowMapMVP": self.pta_mvp} @property def produced_pipes(self): return {"PSSMDistSunShadowMap": self.target_blur_h.color_tex} @property def mvp(self): return Globals.base.render.get_transform(self.cam_node).get_mat() * \ self.cam_lens.get_projection_mat() def update(self): self.target.active = False self.target_convert.active = False self.target_blur_v.active = False self.target_blur_h.active = False # Query scheduled tasks if self._pipeline.task_scheduler.is_scheduled("pssm_distant_shadows"): self.target.active = True # Reposition camera before we capture the scene cam_pos = Globals.base.cam.get_pos(Globals.base.render) self.cam_node.set_pos(cam_pos + self.sun_vector * self.sun_distance) self.cam_node.look_at(cam_pos) self.cam_lens.set_film_size(self.clip_size, self.clip_size) snap_shadow_map(self.mvp, self.cam_node, self.resolution) if self._pipeline.task_scheduler.is_scheduled("pssm_convert_distant_to_esm"): self.target_convert.active = True if self._pipeline.task_scheduler.is_scheduled("pssm_blur_distant_vert"): self.target_blur_v.active = True if self._pipeline.task_scheduler.is_scheduled("pssm_blur_distant_horiz"): self.target_blur_h.active = True # Only update the MVP as soon as the shadow map is available self.pta_mvp[0] = self.mvp def create(self): self.camera = Camera("PSSMDistShadowsESM") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(12000, 12000) self.cam_lens.set_near_far(10.0, self.sun_distance * 2) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.target = self.create_target("ShadowMap") self.target.size = self.resolution self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.cam_node) self.target_convert = self.create_target("ConvertToESM") self.target_convert.size = self.resolution self.target_convert.add_color_attachment(bits=(32, 0, 0, 0)) self.target_convert.prepare_buffer() self.target_convert.set_shader_input("SourceTex", self.target.depth_tex) self.target_blur_v = self.create_target("BlurVert") self.target_blur_v.size = self.resolution self.target_blur_v.add_color_attachment(bits=(32, 0, 0, 0)) self.target_blur_v.prepare_buffer() self.target_blur_v.set_shader_input("SourceTex", self.target_convert.color_tex) self.target_blur_v.set_shader_input("direction", LVecBase2i(1, 0)) self.target_blur_h = self.create_target("BlurHoriz") self.target_blur_h.size = self.resolution self.target_blur_h.add_color_attachment(bits=(32, 0, 0, 0)) self.target_blur_h.prepare_buffer() self.target_blur_h.set_shader_input("SourceTex", self.target_blur_v.color_tex) self.target_blur_h.set_shader_input("direction", LVecBase2i(0, 1)) # Register shadow camera self._pipeline.tag_mgr.register_camera("shadow", self.camera) def reload_shaders(self): self.target_convert.shader = self.load_plugin_shader("convert_to_esm.frag.glsl") self.target_blur_v.shader = self.load_plugin_shader("blur_esm.frag.glsl") self.target_blur_h.shader = self.load_plugin_shader("blur_esm.frag.glsl") def set_shader_input(self, *args): Globals.render.set_shader_input(*args)
class PSSMSceneShadowStage(RenderStage): """ This stage creates the shadow map which covers the whole important part of the scene. This is required because the shadow cascades only cover the view frustum, but many plugins (VXGI, EnvMaps) require a shadow map. """ required_inputs = [] required_pipes = [] def __init__(self, pipeline): RenderStage.__init__(self, pipeline) self.resolution = 2048 self.sun_vector = Vec3(0, 0, 1) self.sun_distance = 10.0 self.pta_mvp = PTAMat4.empty_array(1) self.focus = None # Store last focus entirely for the purpose of being able to see # it in the debugger self.last_focus = None @property def produced_inputs(self): return {"PSSMSceneSunShadowMVP": self.pta_mvp} @property def produced_pipes(self): return { "PSSMSceneSunShadowMapPCF": (self.target.depth_tex, self.make_pcf_state()) } def make_pcf_state(self): state = SamplerState() state.set_minfilter(SamplerState.FT_shadow) state.set_magfilter(SamplerState.FT_shadow) return state def request_focus(self, focus_point, focus_size): self.focus = (focus_point, focus_size) self.last_focus = self.focus @property def mvp(self): return Globals.base.render.get_transform(self.cam_node).get_mat() * \ self.cam_lens.get_projection_mat() def update(self): if self._pipeline.task_scheduler.is_scheduled("pssm_scene_shadows"): if self.focus is None: # When no focus is set, there is no point in rendering the shadow map self.target.active = False else: focus_point, focus_size = self.focus self.cam_lens.set_near_far( 0.0, 2 * (focus_size + self.sun_distance)) self.cam_lens.set_film_size(2 * focus_size, 2 * focus_size) self.cam_node.set_pos(focus_point + self.sun_vector * (self.sun_distance + focus_size)) self.cam_node.look_at(focus_point) snap_shadow_map(self.mvp, self.cam_node, self.resolution) self.target.active = True self.pta_mvp[0] = self.mvp self.focus = None else: self.target.active = False def create(self): self.camera = Camera("PSSMSceneSunShadowCam") self.cam_lens = OrthographicLens() self.cam_lens.set_film_size(400, 400) self.cam_lens.set_near_far(100.0, 1800.0) self.camera.set_lens(self.cam_lens) self.cam_node = Globals.base.render.attach_new_node(self.camera) self.target = self.create_target("ShadowMap") self.target.size = self.resolution self.target.add_depth_attachment(bits=32) self.target.prepare_render(self.cam_node) # Register shadow camera self._pipeline.tag_mgr.register_camera("shadow", self.camera) def set_shader_input(self, *args): Globals.render.set_shader_input(*args)