/** * @brief Constructs a new shadow atlas. * @details This constructs a new shadow atlas with the given size and tile size. * * The size determines the total size of the atlas in pixels. It should be a * power-of-two to favour the GPU. * * The tile_size determines the smallest unit of tiles the atlas can store. * If, for example, a tile_size of 32 is used, then every entry stored must * have a resolution of 32 or greater, and the resolution must be a multiple * of 32. This is to optimize the search in the atlas, so the atlas does not * have to check every pixel, and instead can just check whole tiles. * * If you want to disable the use of tiles, set the tile_size to 1, which * will make the shadow atlas use pixels instead of tiles. * * @param size Atlas-size in pixels * @param tile_size tile-size in pixels, or 1 to use no tiles. */ ShadowAtlas::ShadowAtlas(size_t size, size_t tile_size) { nassertv(size > 1 && tile_size >= 1); nassertv(tile_size < size && size % tile_size == 0); _size = size; _tile_size = tile_size; _num_used_tiles = 0; init_tiles(); }
/** * @brief Frees a given region * @details This frees a given region, marking it as free so that other shadow * maps can use the space again. The region should be the same as returned * by ShadowAtlas::find_and_reserve_region. * * If an invalid region is passed, an assertion is triggered. If assertions * are compiled out, undefined behaviour will occur. * * @param region Region to free */ void ShadowAtlas::free_region(const LVecBase4i& region) { // Out of bounds check, can't hurt nassertv(region.get_x() >= 0 && region.get_y() >= 0); nassertv(region.get_x() + region.get_z() <= _num_tiles && region.get_y() + region.get_w() <= _num_tiles); _num_used_tiles -= region.get_z() * region.get_w(); for (size_t x = 0; x < region.get_z(); ++x) { for (size_t y = 0; y < region.get_w(); ++y) { // Could do an assert here, that the tile should have been used (=true) before set_tile(region.get_x() + x, region.get_y() + y, false); } } }
/** * @brief Constructs a new TagStateManager * @details This constructs a new TagStateManager. The #main_cam_node should * refer to the main scene camera, and will most likely be base.cam. * It is necessary to pass the camera because the C++ code does not have * access to the showbase. * * @param main_cam_node The main scene camera */ TagStateManager::TagStateManager(NodePath main_cam_node) { nassertv(!main_cam_node.is_empty()); nassertv(DCAST(Camera, main_cam_node.node()) != NULL); _main_cam_node = main_cam_node; // Set default camera mask DCAST(Camera, _main_cam_node.node())->set_camera_mask(BitMask32::bit(1)); // Init containers _containers["shadow"] = StateContainer("Shadows", 2, false); _containers["voxelize"] = StateContainer("Voxelize", 3, false); _containers["envmap"] = StateContainer("Envmap", 4, true); _containers["forward"] = StateContainer("Forward", 5, true); }
/** * @brief Writes the light to a GPUCommand * @details This writes all of the lights data to the given GPUCommand handle. * Subclasses should first call this method, and then append their own * data. This makes sure that for unpacking a light, no information about * the type of the light is required. * * @param cmd The GPUCommand to write to */ void RPLight::write_to_command(GPUCommand &cmd) { cmd.push_int(_light_type); cmd.push_int(_ies_profile); if (_casts_shadows) { // If we casts shadows, write the index of the first source, we expect // them to be consecutive nassertv(_shadow_sources.size() >= 0); nassertv(_shadow_sources[0]->has_slot()); cmd.push_int(_shadow_sources[0]->get_slot()); } else { // If we cast no shadows, just push a negative number cmd.push_int(-1); } cmd.push_vec3(_position); // Get the lights color by multiplying color with lumens, I hope thats // physically correct. cmd.push_vec3(_color * _lumens); }
/** * @brief Initializes the ShadowManager. * @details This initializes the ShadowManager. All properties should have * been set before calling this, otherwise assertions will get triggered. * * This setups everything required for rendering shadows, including the * shadow atlas and the various shadow cameras. After calling this method, * no properties can be changed anymore. */ void ShadowManager::init() { nassertv(!_scene_parent.is_empty()); // Scene parent not set, call set_scene_parent before init! nassertv(_tag_state_mgr != NULL); // TagStateManager not set, call set_tag_state_mgr before init! nassertv(_atlas_graphics_output != NULL); // AtlasGraphicsOutput not set, call set_atlas_graphics_output before init! _cameras.resize(_max_updates); _display_regions.resize(_max_updates); _camera_nps.reserve(_max_updates); // Create the cameras and regions for(size_t i = 0; i < _max_updates; ++i) { // Create the camera PT(Camera) camera = new Camera("ShadowCam-" + to_string((long long)i)); camera->set_lens(new MatrixLens()); camera->set_active(false); camera->set_scene(_scene_parent); _tag_state_mgr->register_camera("shadow", camera); _camera_nps.push_back(_scene_parent.attach_new_node(camera)); _cameras[i] = camera; // Create the display region PT(DisplayRegion) region = _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(_camera_nps[i]); region->set_active(false); _display_regions[i] = region; } // Create the atlas _atlas = new ShadowAtlas(_atlas_size); // Reserve enough space for the updates _queued_updates.reserve(_max_updates); }
/** * @brief Updates the ShadowManager * @details This updates the ShadowManager, processing all shadow sources which * need to get updated. * * This first collects all sources which require an update, sorts them by priority, * and then processes the first <max_updates> ShadowSources. * * This may not get called before ShadowManager::init, or an assertion will be * thrown. */ void ShadowManager::update() { nassertv(_atlas != NULL); // ShadowManager::init not called yet nassertv(_queued_updates.size() <= _max_updates); // Internal error, should not happen // Disable all cameras and regions which will not be used for (size_t i = _queued_updates.size(); i < _max_updates; ++i) { _cameras[i]->set_active(false); _display_regions[i]->set_active(false); } // Iterate over all queued updates for (size_t i = 0; i < _queued_updates.size(); ++i) { const ShadowSource* source = _queued_updates[i]; // Enable the camera and display region, so they perform a render _cameras[i]->set_active(true); _display_regions[i]->set_active(true); // Set the view projection matrix DCAST(MatrixLens, _cameras[i]->get_lens())->set_user_mat(source->get_mvp()); // Optional: Show the camera frustum for debugging // _cameras[i]->show_frustum(); // Set the correct dimensions on the display region const LVecBase4f& uv = source->get_uv_region(); _display_regions[i]->set_dimensions( uv.get_x(), // left uv.get_x() + uv.get_z(), // right uv.get_y(), // bottom uv.get_y() + uv.get_w() // top ); } // Clear the update list _queued_updates.clear(); _queued_updates.reserve(_max_updates); }
/** * @brief Internal method to reserve a region in the atlas. * @details This reserves a given region in the shadow atlas. The region should * be in tile space.This is called by the ShadowAtlas::find_and_reserve_region. * It sets all flags in that region to true, indicating that those are used. * When an invalid region is passed, an assertion is triggered. If assertions * are optimized out, undefined behaviour occurs. * * @param x x- start positition of the region * @param y y- start position of the region * @param w width of the region * @param h height of the region */ void ShadowAtlas::reserve_region(size_t x, size_t y, size_t w, size_t h) { // Check if we are out of bounds, this should be disabled for performance // reasons at some point. nassertv(x >= 0 && y >= 0 && x + w <= _num_tiles && y + h <= _num_tiles); _num_used_tiles += w * h; // Iterate over every tile in the region and mark it as used for (size_t cx = 0; cx < w; ++cx) { for (size_t cy = 0; cy < h; ++cy) { set_tile(cx + x, cy + y, true); } } }
/** * @brief Light destructor * @details This destructs the light, cleaning up all resourced used. The light * should be detached at this point, because while the Light is attached, * the InternalLightManager holds a reference to prevent it from being * destructed. */ RPLight::~RPLight() { nassertv(!has_slot()); // Light still attached - should never happen clear_shadow_sources(); }