From 0a4f14b6c9788e0de9618f6bf13e2f08cc7e903e Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Wed, 9 Oct 2019 11:33:19 +0100 Subject: [PATCH] Preliminary lighting support --- README.md | 18 +- ldebug.cpp | 2 +- ldebug.h | 10 +- ldob.cpp | 104 +++++++++- ldob.h | 49 ++++- lportal.cpp | 86 ++++++-- lportal.h | 16 +- lroom.cpp | 158 ++++++++++----- lroom.h | 15 +- lroom_converter.cpp | 472 ++++++++++++++++++++++++++++++++++++++++++-- lroom_converter.h | 12 +- lroom_manager.cpp | 284 ++++++++++++++++++++++++-- lroom_manager.h | 17 ++ 13 files changed, 1108 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index e98ba71..9444e69 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,19 @@ Video of initial testing:\ https://www.youtube.com/watch?v=xF_3Fe2HRdk \ https://www.youtube.com/watch?v=NmlWkkhGoJA -_Feel free to leave suggestions / feature requests on the issue tracker, especially regarding ease of use._\ -_If you are interested in integrating LPortal into your game, feel free to get in touch, e.g. in the issue tracker, and I will endeavour to help._ +_Feel free to leave suggestions / feature requests on the issue tracker, especially regarding ease of use._ ## Current status -The basic system is working, I am now currently working on shadow casters. In a simple system you would pre-bake your lighting with e.g. lightmaps, however with realtime lights, shadow casters outside the view of the camera can cast shadows onto the objects in view. Rather than rendering everything as a possible shadow caster, it makes sense to use the same room / portal system to cull out as many shadow casters as possible that could not contribute to the final image. +The basic system is mostly working. I am now testing / polishing the interface and adding debugging. I will leave PVS and closable portals until after the first release. -Note that I've had to move from showing and hiding objects, to using Godot object and camera layers to differentiate between which objects to render from the camera and which for shadows, at least for now. This is a little hacky, and might interfere with your own code if you are using godot layers - layer 1 (default layer) is unset for all objects, and 19 and 20 are currently used by the system to determine whether objects are shadow casters or visible (or both), respectively. +I am now working on shadows. Shadows can be cast by objects that are not 'in view', thus with the naive culling system, in some circumstances shadows will pop into and out of view. -I've also uncovered a bug in Godot core shadow casting: -https://github.com/godotengine/godot/pull/32475 +There are three possibilities here: +1) Draw everything in the shadow pass (use Godot default octree). This gets rid of artefacts but would be slow and negate a lot of the benefits of visibility culling. +2) Draw everything roughly around the visible objects. The current version does this now, with the main pass only rendering objects in view, but the shadow pass draws everything within the rooms in view. This is better than naive version, but still can suffer 'popping' of shadows at doorways. +3) More intelligent system to try and work out which shadow casters are relevant to the view. -which I'm finding the best fix for to help with the showing / hiding of shadow casters. There is a workaround but I'm keen to get it fixed in core. +Note that I've had to move from showing and hiding objects, to using Godot object and camera layers to differentiate between which objects to render from the camera and which for shadows, at least for now. This is a little hacky, and might interfere with your own code if you are using godot layers - layer 1 (default layer) is unset for all objects, and 19 and 20 are currently used by the system to toggle visibility internally. ## Roadmap * Auto conversion of named room spatials and portal mesh instances to LRoom and LPortal DONE @@ -36,10 +37,11 @@ which I'm finding the best fix for to help with the showing / hiding of shadow c * Optional convex hull bound for rooms DONE * Add debug graphical view of portal planes DONE * Add debug graphical view of room bounds DONE -* Dealing with shadows from objects outside of view ONGOING +* Dealing with shadows from objects outside of view * Bug fixing / testing ONGOING * Closable portals * PVS (primary and secondary) +* Investigate multiple passes (shadows, lights) ## Instructions See [INSTRUCTIONS.md](INSTRUCTIONS.md) and [TUTORIAL.md](TUTORIAL.md) diff --git a/ldebug.cpp b/ldebug.cpp index 61b9f78..be2cd68 100644 --- a/ldebug.cpp +++ b/ldebug.cpp @@ -6,7 +6,7 @@ namespace Lawn int LDebug::m_iLoggingLevel = 0; // 2 int LDebug::m_iWarningLevel = 0; int LDebug::m_iTabDepth = 0; -bool LDebug::m_bRunning = false; +bool LDebug::m_bRunning = true; void LDebug::print(String sz) diff --git a/ldebug.h b/ldebug.h index 393f6a4..8f5cd09 100644 --- a/ldebug.h +++ b/ldebug.h @@ -1,11 +1,11 @@ #pragma once -//#define LPRINT_RUN(a, b) ; +#define LPRINT_RUN(a, b) ; -#define LPRINT_RUN(a, b) {String sz;\ -for (int n=0; n= Lawn::LDebug::m_iLoggingLevel)\ diff --git a/ldob.cpp b/ldob.cpp index f6fd7ae..f3ce3bb 100644 --- a/ldob.cpp +++ b/ldob.cpp @@ -24,8 +24,67 @@ #include "ldob.h" #include "scene/3d/mesh_instance.h" +#include "scene/3d/light.h" +void LHidable::Hidable_Create(Node * pNode) +{ +// m_pNode = 0; m_pParent = 0; m_bShow = true; + m_pNode = pNode; + m_pParent = m_pNode->get_parent(); + m_bShow = true; +} + + +void LHidable::Show(bool bShow) +{ + // noop + if (bShow == m_bShow) + return; + + // new state + m_bShow = bShow; + + assert (m_pParent); + + if (bShow) + { + // add to tree + m_pParent->add_child(m_pNode); + } + else + { + // remove from tree + m_pParent->remove_child(m_pNode); + } + +} + +///////////////////////////////////////////////////////////////////// + +void LLight::SetDefaults() +{ + m_GodotID = 0; + m_eType = LT_DIRECTIONAL; + m_fSpread = 0.0f; // for spotlight + m_fMaxDist = 100.0f; + m_RoomID = -1; + + m_FirstCaster = 0; + m_NumCasters = 0; +} + + +Light * LLight::GetGodotLight() +{ + Object * pObj = ObjectDB::get_instance(m_GodotID); + Light * p = Object::cast_to(pObj); + return p; +} + + +///////////////////////////////////////////////////////////////////// + Spatial * LSob::GetSpatial() const { Object * pObj = ObjectDB::get_instance(m_ID); @@ -59,18 +118,53 @@ VisualInstance * LSob::GetVI() const return pVI; } + + +/* void LSob::Show(bool bShow) { + // noop + if (bShow == m_bShow) + return; + + // new state + m_bShow = bShow; + Spatial * pS = GetSpatial(); if (!pS) return; - if (bShow) - pS->show(); - else - pS->hide(); -} + assert (m_pParent); + if (bShow) + { + // add to tree + m_pParent->add_child(m_pNode); + } + else + { + // remove from tree + m_pParent->remove_child(m_pNode); + } + + // noop +// if (pS->is_visible() == bShow) +// return; + +// if (bShow) +// pS->show(); +// else +// pS->hide(); + +// GeometryInstance * pGI = Object::cast_to(pS); +// if (pGI) +// { +// // godot visible bug workaround +// pGI->set_extra_cull_margin(0.0f); +// } + +} +*/ Spatial * LDob::GetSpatial() const diff --git a/ldob.h b/ldob.h index f60af90..fd767c5 100644 --- a/ldob.h +++ b/ldob.h @@ -28,19 +28,33 @@ #include "scene/3d/spatial.h" class VisualInstance; +class Light; + +class LHidable +{ +public: + void Hidable_Create(Node * pNode); + void Show(bool bShow); + + // new .. can be separated from the scene tree to cull + Node * m_pNode; + Node * m_pParent; + + // separate flag so we don't have to touch the godot lookup + bool m_bShow; +}; // static object -class LSob +class LSob : public LHidable { public: Spatial * GetSpatial() const; VisualInstance * GetVI() const; - void Show(bool bShow); + //void Show(bool bShow); bool IsShadowCaster() const; ObjectID m_ID; // godot object AABB m_aabb; // world space - //bool m_bSOBVisible; }; // dynamic object @@ -55,3 +69,32 @@ public: bool m_bVisible; float m_fRadius; }; + + +class LLight : public LHidable +{ +public: + enum eLightType + { + LT_DIRECTIONAL, + LT_SPOTLIGHT, + LT_OMNI, + }; + void SetDefaults(); + Light * GetGodotLight(); + bool IsGlobal() const {return m_RoomID == -1;} + + Vector3 m_ptDir; + Vector3 m_ptPos; + ObjectID m_GodotID; + eLightType m_eType; + float m_fSpread; // for spotlight + float m_fMaxDist; // shadow distance not light distance + + // source room + int m_RoomID; // or -1 for global lights + + // shadow casters + int m_FirstCaster; + int m_NumCasters; +}; diff --git a/lportal.cpp b/lportal.cpp index d393a97..0ed25da 100644 --- a/lportal.cpp +++ b/lportal.cpp @@ -25,6 +25,7 @@ #include "lroom_manager.h" + bool LPortal::NameStartsWith(Node * pNode, String szSearch) { int sl = szSearch.length(); @@ -71,38 +72,91 @@ String LPortal::FindNameAfter(Node * pNode, String szStart) ////////////////////////////////////////////////////////// +void LPortal::Debug_CheckPlaneValidity(const Plane &p) const +{ + assert (p.distance_to(m_ptCentre) < 0.0f); +} + + // preprocess -void LPortal::AddLightPlanes(const LLight &light, LVector &planes) const +void LPortal::AddLightPlanes(LRoomManager &manager, const LLight &light, LVector &planes, bool bReverse) const { const Vector &pts = m_ptsWorld; - // assuming ortho light int nPoints = pts.size(); ERR_FAIL_COND(nPoints < 3); - const int max_points = 32; - Vector3 pushed_pts[max_points]; - - if (nPoints > max_points) - nPoints = max_points; - - // transform pushed points - for (int n=0; n max_points) + nPoints = max_points; + + // transform pushed points + Vector3 ptPush = light.m_ptDir * 2.0; + + for (int n=0; n &planes) const; - void AddLightPlanes(const LLight &light, LVector &planes) const; + + // reverse direction if we are going back through portals TOWARDS the light rather than away from it + // (the planes will need reversing because the portal winding will be opposite) + void AddLightPlanes(LRoomManager &manager, const LLight &light, LVector &planes, bool bReverse) const; // normal determined by winding order Vector m_ptsWorld; @@ -87,6 +84,9 @@ public: // useful funcs static bool NameStartsWith(Node * pNode, String szSearch); static String FindNameAfter(Node * pNode, String szStart); + +private: + void Debug_CheckPlaneValidity(const Plane &p) const; }; diff --git a/lroom.cpp b/lroom.cpp index cbb2860..c1ab50a 100644 --- a/lroom.cpp +++ b/lroom.cpp @@ -132,29 +132,48 @@ LRoom * LRoom::DOB_Update(LRoomManager &manager, Spatial * pDOB) // objects can still be rendered outside immediate view for casting shadows. // All objects in view (that are set to cast shadows) should cast shadows, so the actual // shown objects are a superset of the softshown. -void LRoom::SoftShow(VisualInstance * pVI, bool bShow) +void LRoom::SoftShow(VisualInstance * pVI, uint32_t show_flags) { + // hijack this layer number uint32_t mask = pVI->get_layer_mask(); uint32_t orig_mask = mask; - const int SOFT_SHOW_MASK = 1 << SOFT_SHOW_BIT; - const int SOFT_HIDE_MASK = 1 << SOFT_HIDE_BIT; + // debug, to check shadow casters are correct for different light types +//#define DEBUG_SHOW_CASTERS_ONLY +#ifdef DEBUG_SHOW_CASTERS_ONLY + bShow = true; if (bShow) { - // set - mask |= SOFT_SHOW_MASK; - // clear - mask &= ~(1 | SOFT_HIDE_MASK); + } +#else + if (show_flags & LAYER_MASK_CAMERA) + mask |= LAYER_MASK_CAMERA; // set else - { - // set - mask |= SOFT_HIDE_MASK; - // clear - mask &= ~(1 | SOFT_SHOW_MASK); - } + mask &= ~LAYER_MASK_CAMERA; // clear + + if (show_flags & LAYER_MASK_LIGHT) + mask |= LAYER_MASK_LIGHT; + else + mask &= ~LAYER_MASK_LIGHT; + +// if (bShow) +// { +// // set +// mask |= SOFT_SHOW_MASK; +// // clear +// mask &= ~(1 | SOFT_HIDE_MASK); +// } +// else +// { +// // set +// mask |= SOFT_HIDE_MASK; +// // clear +// mask &= ~(1 | SOFT_SHOW_MASK); +// } +#endif // noop? don't touch the visual server if no change to mask @@ -163,51 +182,76 @@ void LRoom::SoftShow(VisualInstance * pVI, bool bShow) pVI->set_layer_mask(mask); -// pVI->set_layer_mask_bit(0, false); -// pVI->set_layer_mask_bit(SOFT_HIDE_BIT, bShow == false); -// pVI->set_layer_mask_bit(SOFT_SHOW_BIT, bShow); + // test godot bug +// GeometryInstance * pGI = Object::cast_to(pVI); +// if (pGI) +// { +// // godot visible bug workaround +// pGI->set_extra_cull_margin(0.0f); +// } + + + // test the visual server - NOT A BOTTLENECK. set_layer_mask is cheap } // naive version, adds all the non visible objects in visible rooms as shadow casters void LRoom::AddShadowCasters(LRoomManager &manager) { - LPRINT(2, "ADDSHADOWCASTERS room " + get_name() + ", " + itos(m_iNumShadowCasters_SOB) + " shadow casters"); + LPRINT_RUN(2, "ADDSHADOWCASTERS room " + get_name() + ", " + itos(m_iNumShadowCasters_SOB) + " shadow casters"); + + // add all the active lights in this room + for (int n=0; nget_name()); + manager.m_BF_caster_SOBs.SetBit(sobID, true); + manager.m_CasterList_SOBs.push_back(sobID); + } + else + { + //LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); + } + + } + } + } + // new!! use precalced list of shadow casters - int last = m_iFirstShadowCaster_SOB + m_iNumShadowCasters_SOB; - for (int n=m_iFirstShadowCaster_SOB; nget_name()); - manager.m_BF_caster_SOBs.SetBit(sobID, true); - manager.m_CasterList_SOBs.push_back(sobID); - } - else - { - LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); - } - } +// // only add to the caster list if not in it already +// if (!manager.m_BF_caster_SOBs.GetBit(sobID)) +// { +// LPRINT(2, "\t" + itos(sobID) + ", " + manager.m_SOBs[sobID].GetSpatial()->get_name()); +// manager.m_BF_caster_SOBs.SetBit(sobID, true); +// manager.m_CasterList_SOBs.push_back(sobID); +// } +// else +// { +// //LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); +// } +// } - -/* - int last_sob = m_iFirstSOB + m_iNumSOBs; - for (int n=m_iFirstSOB; nget_name()); @@ -317,7 +369,7 @@ void LRoom::Room_MakeVisible(bool bVisible) //} // show godot room and all linked dobs and all sobs -void LRoom::Debug_ShowAll() +void LRoom::Debug_ShowAll(bool bActive) { Room_MakeVisible(true); @@ -328,6 +380,12 @@ void LRoom::Debug_ShowAll() // Spatial * pS = sob.GetSpatial(); // if (pS) // pS->show(); + +// VisualInstance * pVI = sob.GetVI(); +// if (pVI) +// { +// SoftShow(pVI, LRoom::LAYER_MASK_CAMERA | LRoom::LAYER_MASK_LIGHT); +// } // } } diff --git a/lroom.h b/lroom.h index 0e7e6ff..bb64b42 100644 --- a/lroom.h +++ b/lroom.h @@ -54,8 +54,12 @@ class LRoom private: public: - static const int SOFT_SHOW_BIT = 18; - static const int SOFT_HIDE_BIT = 19; + // using flags we can determine which the object is visible to - the camera, or the lights (i.e. shadow caster), or both + static const int LAYER_LIGHT_BIT = 18; + static const int LAYER_CAMERA_BIT = 19; + + static const int LAYER_MASK_LIGHT = 1 << LAYER_LIGHT_BIT; + static const int LAYER_MASK_CAMERA = 1 << LAYER_CAMERA_BIT; // static objects are stored in the manager in a contiguous list int m_iFirstSOB; @@ -64,6 +68,9 @@ public: // dynamic objects LVector m_DOBs; + // local lights affecting this room + LVector m_LocalLights; + // portals are stored in the manager in a contiguous list int m_iFirstPortal; int m_iNumPortals; @@ -105,7 +112,7 @@ public: // void Hide_All(); // show godot room and all linked dobs and all sobs - void Debug_ShowAll(); + void Debug_ShowAll(bool bActive); // hide all the objects not hit on this frame .. instead of calling godot hide without need // (it might be expensive) @@ -130,7 +137,7 @@ public: // instead of directly showing and hiding objects we now set their layer, // and the camera will hide them with a cull mask. This is so that // objects can still be rendered outside immediate view for casting shadows. - static void SoftShow(VisualInstance * pVI, bool bShow); + static void SoftShow(VisualInstance * pVI, uint32_t show_flags); private: // whether lportal thinks this room is currently visible diff --git a/lroom_converter.cpp b/lroom_converter.cpp index 612a7d4..716380c 100644 --- a/lroom_converter.cpp +++ b/lroom_converter.cpp @@ -25,6 +25,7 @@ #include "scene/3d/mesh_instance.h" #include "core/math/quick_hull.h" #include "ldebug.h" +#include "scene/3d/light.h" // save typing, I am lazy #define LMAN m_pManager @@ -39,17 +40,17 @@ void LRoomConverter::Convert(LRoomManager &manager) LPRINT(5, "running convert"); LMAN = &manager; + + // force clear all arrays + manager.ReleaseResources(true); + int count = CountRooms(); - LMAN->m_SOBs.clear(); - LMAN->m_ShadowCasters_SOB.clear(); int num_global_lights = LMAN->m_Lights.size(); // make sure bitfield is right size for number of rooms LMAN->m_BF_visible_rooms.Create(count); - - LMAN->m_Rooms.clear(true); LMAN->m_Rooms.resize(count); m_TempRooms.clear(true); @@ -68,7 +69,11 @@ void LRoomConverter::Convert(LRoomManager &manager) LMAN->m_BF_master_SOBs.Create(num_sobs); LMAN->m_BF_master_SOBs_prev.Create(num_sobs); + LMAN->m_BF_ActiveLights.Create(LMAN->m_Lights.size()); + LMAN->m_BF_ActiveLights_prev.Create(LMAN->m_Lights.size()); + // must be done after the bitfields + Convert_Lights(); Convert_ShadowCasters(); // hide all in preparation for first frame @@ -78,7 +83,7 @@ void LRoomConverter::Convert(LRoomManager &manager) m_TempRooms.clear(true); // clear out the local room lights, leave only global lights - LMAN->m_Lights.resize(num_global_lights); + //LMAN->m_Lights.resize(num_global_lights); Lawn::LDebug::m_bRunning = true; } @@ -118,6 +123,25 @@ int LRoomConverter::FindRoom_ByName(String szName) const return -1; } +void LRoomConverter::Convert_Room_SetDefaultCullMask_Recursive(Node * pParent) +{ + int nChildren = pParent->get_child_count(); + for (int n=0; nget_child(n); + + // default cull mask should always be visible to camera and lights + VisualInstance * pVI = Object::cast_to(pChild); + if (pVI) + { +// LRoom::SoftShow(pVI, LRoom::LAYER_MASK_CAMERA | LRoom::LAYER_MASK_LIGHT); + } + + Convert_Room_SetDefaultCullMask_Recursive(pChild); + } +} + + void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &lroom, LAABB &bb_room) { int nChildren = pParent->get_child_count(); @@ -135,10 +159,18 @@ void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &l if (Node_IsBound(pChild)) continue; + // lights + if (Node_IsLight(pChild)) + { + LRoom_DetectedLight(lroom, pChild); + continue; + } VisualInstance * pVI = Object::cast_to(pChild); if (pVI) { + + LPRINT(2, "\t\tFound VI : " + pVI->get_name()); @@ -150,9 +182,13 @@ void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &l LSob sob; sob.m_ID = pVI->get_instance_id(); sob.m_aabb = bb; + sob.Hidable_Create(pChild); //lroom.m_SOBs.push_back(sob); LRoom_PushBackSOB(lroom, sob); + + // take away layer 0 from the sob, so it can be culled effectively + pVI->set_layer_mask(0); } else { @@ -193,6 +229,9 @@ bool LRoomConverter::Convert_Room(Spatial * pNode, int lroomID) LAABB bb_room; bb_room.SetToMaxOpposite(); + // set default cull masks + Convert_Room_SetDefaultCullMask_Recursive(pNode); + // recursively find statics Convert_Room_FindObjects_Recursive(pNode, lroom, bb_room); @@ -307,17 +346,272 @@ void LRoomConverter::Convert_HideAll() LSob &sob = LMAN->m_SOBs[n]; sob.Show(false); } + + // hide all lights that are non global + for (int n=0; nm_Lights.size(); n++) + { + LLight &light = LMAN->m_Lights[n]; + if (!light.IsGlobal()) + light.Show(false); + } + } +void LRoomConverter::Convert_Lights() +{ + // trace local lights out from rooms and add to each room the light affects + for (int n=0; nm_Lights.size(); n++) + { + LLight &l = LMAN->m_Lights[n]; + if (l.IsGlobal()) + continue; // ignore globals .. affect all rooms + + Light_Trace(n); + } +} + +void LRoomConverter::Light_Trace(int iLightID) +{ + // blank this each time as it is used to create the list of casters + LMAN->m_BF_caster_SOBs.Blank(); + + // get the light + LLight &l = LMAN->m_Lights[iLightID]; + + LPRINT(5,"\nLight_Trace " + itos (iLightID) + " direction " + l.m_ptDir); + + // reset the planes pool for each render out from the source room + LMAN->m_Pool.Reset(); + + + // the first set of planes are blank + unsigned int pool_member = LMAN->m_Pool.Request(); + assert (pool_member != -1); + + LVector &planes = LMAN->m_Pool.Get(pool_member); + planes.clear(); + + Lawn::LDebug::m_iTabDepth = 0; + + Light_TraceRecursive(0, LMAN->m_Rooms[l.m_RoomID], l, iLightID, planes); +} + + +void LRoomConverter::Light_TraceRecursive(int depth, LRoom &lroom, LLight &light, int iLightID, const LVector &planes) +{ + // prevent too much depth + if (depth > 8) + { + LPRINT_RUN(2, "\t\t\tLight_TraceRecursive DEPTH LIMIT REACHED"); + return; + } + + Lawn::LDebug::m_iTabDepth = depth; + LPRINT_RUN(2, "ROOM " + lroom.get_name() + " affected by local light"); + + + // add to the local lights affecting this room + // already in list? + bool bAlreadyInList = false; + for (int n=0; nm_SOBs[n]; + + //LPRINT_RUN(2, "sob " + itos(n) + " " + sob.GetSpatial()->get_name()); + // already determined to be visible through another portal +// if (LMAN->m_BF_caster_SOBs.GetBit(n)) +// { +// //LPRINT_RUN(2, "\talready visible"); +// continue; +// } + + bool bShow = true; + + + // estimate the radius .. for now + const AABB &bb = sob.m_aabb; + +// print("\t\t\tculling object " + pObj->get_name()); + + for (int p=0; p 0.0f) + { + bShow = false; + break; + } + } + + if (bShow) + { + Light_AddCaster_SOB(light, n); + } + + } // for through sobs + + + + // look through every portal out + for (int n=0; nm_Portals[portalID]; + + LPRINT_RUN(2, "\tPORTAL " + itos (n) + " (" + itos(portalID) + ") " + port.get_name() + " normal " + port.m_Plane.normal); + + float dot = port.m_Plane.normal.dot(light.m_ptDir); + + if (dot <= 0.0f) + { + LPRINT_RUN(2, "\t\tCULLED (wrong direction)"); + continue; + } + + // is it culled by the planes? + LPortal::eClipResult overall_res = LPortal::eClipResult::CLIP_INSIDE; + + // cull portal with planes + for (int l=0; lPortal_GetLinkedRoom(port); + + + // recurse into that portal + unsigned int uiPoolMem = LMAN->m_Pool.Request(); + if (uiPoolMem != -1) + { + // get a vector of planes from the pool + LVector &new_planes = LMAN->m_Pool.Get(uiPoolMem); + + // copy the existing planes + new_planes.copy_from(planes); + + // add the planes for the portal + port.AddLightPlanes(*LMAN, light, new_planes, false); + + Light_TraceRecursive(depth + 1, linked_room, light, iLightID, new_planes); + // for debugging need to reset tab depth + Lawn::LDebug::m_iTabDepth = depth; + + // we no longer need these planes + LMAN->m_Pool.Free(uiPoolMem); + } + else + { + // planes pool is empty! + // This will happen if the view goes through shedloads of portals + // The solution is either to increase the plane pool size, or build levels + // with views through multiple portals. Looking through multiple portals is likely to be + // slow anyway because of the number of planes to test. + WARN_PRINT_ONCE("LRoom_FindShadowCasters_Recursive : Planes pool is empty"); + } + + + } + +} + + void LRoomConverter::Convert_ShadowCasters() { - LPRINT(5,"Convert_ShadowCasters ... numlights " + itos (LMAN->m_Lights.size())); + int nLights = LMAN->m_Lights.size(); + LPRINT(5,"\nConvert_ShadowCasters ... numlights " + itos (nLights)); - for (int n=0; nm_Rooms.size(); n++) + + for (int l=0; lm_Rooms[n]; - LRoom_FindShadowCasters(lroom); + const LLight &light = LMAN->m_Lights[l]; + String sz = "Light " + itos (l); + if (light.IsGlobal()) + sz += " GLOBAL"; + else + sz += " LOCAL from room " + itos(light.m_RoomID); + + LPRINT(5, sz + " direction " + light.m_ptDir); + + for (int n=0; nm_Rooms.size(); n++) + { + LRoom &lroom = LMAN->m_Rooms[n]; + + // global lights affect every room + bool bAffectsRoom = true; + + // if the light is local, does it affect this room? + if (!light.IsGlobal()) + { + // a local light .. does it affect this room? + bAffectsRoom = false; + for (int i=0; im_Lights.size(); n++) +// { +// // if the light is not a global light, we are only interested if it emits from this room +// const LLight &l = LMAN->m_Lights[n]; + +// bool bAffectsRoom = true; +// if (l.m_RoomID != -1) +// { +// // a local light .. does it affect this room? +// bAffectsRoom = false; +// for (int i=0; im_Lights.size(); n++) - { - LRoom_FindShadowCasters_FromLight(lroom, LMAN->m_Lights[n]); - } + // we will reuse the rendering bitflags for shadow casters for this ... to check for double entries (fnaa fnaa) + if (LMAN->m_BF_caster_SOBs.GetBit(sobID)) + return; - return; + LPRINT_RUN(2, "\t\t\tLightCaster " + itos(sobID)); + + LMAN->m_BF_caster_SOBs.SetBit(sobID, true); + + + // first? + if (!light.m_NumCasters) + light.m_FirstCaster = LMAN->m_LightCasters_SOB.size(); + + LMAN->m_LightCasters_SOB.push_back(sobID); + light.m_NumCasters++; } + void LRoomConverter::LRoom_AddShadowCaster_SOB(LRoom &lroom, int sobID) { // we will reuse the rendering bitflags for shadow casters for this ... to check for double entries (fnaa fnaa) @@ -461,7 +796,7 @@ void LRoomConverter::LRoom_FindShadowCasters_FromLight(LRoom &lroom, const LLigh planes.clear(); Lawn::LDebug::m_iTabDepth = 0; - LRoom_FindShadowCasters_Recursive(lroom, 0, lroom, light, planes); + LRoom_FindShadowCasters_Recursive(lroom, 1, lroom, light, planes); } @@ -507,7 +842,10 @@ void LRoomConverter::LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int if (r_min > 0.0f) +// if (r_max < 0.0f) { + //LPRINT_RUN(2, "\tR_MIN is " + String(Variant(r_min)) + " R_MAX is " + String(Variant(r_max))+ ", for plane " + itos(p)); + bShow = false; break; } @@ -518,6 +856,10 @@ void LRoomConverter::LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int LPRINT_RUN(2, "\tcaster " + itos(n) + ", " + sob.GetSpatial()->get_name()); LRoom_AddShadowCaster_SOB(source_lroom, n); } + else + { + //LPRINT_RUN(2, "\tculled " + itos(n) + ", " + sob.GetSpatial()->get_name()); + } } // look through every portal out @@ -530,8 +872,21 @@ void LRoomConverter::LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int LPRINT_RUN(2, "\tPORTAL " + itos (n) + " (" + itos(portalID) + ") " + port.get_name() + " normal " + port.m_Plane.normal); // cull with light direction - float dot = port.m_Plane.normal.dot(light.m_ptDir); - if (dot <= 0.0f) + float dot; + if (light.m_eType == LLight::LT_DIRECTIONAL) + { + dot = port.m_Plane.normal.dot(light.m_ptDir); + } + else + { + // cull with light direction to portal + Vector3 ptLightToPort = port.m_ptCentre - light.m_ptPos; + dot = port.m_Plane.normal.dot(ptLightToPort); + } + + +// float dot = port.m_Plane.normal.dot(light.m_ptDir); + if (dot >= 0.0f) { LPRINT_RUN(2, "\t\tCULLED (wrong direction)"); continue; @@ -583,7 +938,7 @@ void LRoomConverter::LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int new_planes.copy_from(planes); // add the planes for the portal - port.AddLightPlanes(light, new_planes); + port.AddLightPlanes(*LMAN, light, new_planes, true); LRoom_FindShadowCasters_Recursive(source_lroom, depth + 1, linked_room, light, new_planes); // for debugging need to reset tab depth @@ -696,6 +1051,71 @@ void LRoomConverter::LRoom_MakePortalFinalList(LRoom &lroom, LTempRoom &troom) } } + +void LRoomConverter::LRoom_DetectedLight(LRoom &lroom, Node * pNode) +{ + Light * pLight = Object::cast_to(pNode); + assert (pLight); + + LMAN->LightCreate(pLight, lroom.m_RoomID); + /* + // create new light + LLight l; + l.SetDefaults(); + l.m_GodotID = pLight->get_instance_id(); + // direction + Transform tr = pLight->get_global_transform(); + l.m_ptPos = tr.origin; + l.m_ptDir = -tr.basis.get_axis(2); // or possibly get_axis .. z is what we want + l.m_fMaxDist = pLight->get_param(Light::PARAM_SHADOW_MAX_DISTANCE); + + // source room ID + l.m_RoomID = lroom.m_RoomID; + + bool bOK = false; + + // what kind of light? + SpotLight * pSL = Object::cast_to(pNode); + if (pSL) + { + LPRINT(2, "\tSPOTLIGHT detected " + pNode->get_name()); + l.m_eType = LLight::LT_SPOTLIGHT; + l.m_fSpread = pSL->get_param(Light::PARAM_SPOT_ANGLE); + + bOK = true; + } + + OmniLight * pOL = Object::cast_to(pNode); + if (pOL) + { + LPRINT(2, "\tOMNILIGHT detected " + pNode->get_name()); + l.m_eType = LLight::LT_OMNI; + bOK = true; + } + + DirectionalLight * pDL = Object::cast_to(pNode); + if (pDL) + { + LPRINT(2, "\tDIRECTIONALLIGHT detected " + pNode->get_name()); + l.m_eType = LLight::LT_DIRECTIONAL; + bOK = true; + } + + // don't add if not recognised + if (!bOK) + { + LPRINT(2, "\tLIGHT type unrecognised " + pNode->get_name()); + return; + } + + + // turn the local light off to start with + pLight->hide(); + + LMAN->m_Lights.push_back(l); + */ +} + // found a portal mesh! create a matching LPortal void LRoomConverter::LRoom_DetectedPortalMesh(LRoom &lroom, LTempRoom &troom, MeshInstance * pMeshInstance, String szLinkRoom) { @@ -792,6 +1212,16 @@ void LRoomConverter::TRoom_MakeOppositePortal(const LPortal &port, int iRoomOrig /////////////////////////////////////////////////// // helper +bool LRoomConverter::Node_IsLight(Node * pNode) const +{ + Light * pLight = Object::cast_to(pNode); + if (!pLight) + return false; + + return true; +} + + bool LRoomConverter::Node_IsRoom(Node * pNode) const { Spatial * pSpat = Object::cast_to(pNode); diff --git a/lroom_converter.h b/lroom_converter.h index ff4fdf3..977d0ce 100644 --- a/lroom_converter.h +++ b/lroom_converter.h @@ -89,11 +89,13 @@ private: void Convert_Rooms(); bool Convert_Room(Spatial * pNode, int lroomID); void Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &lroom, LAABB &bb_room); + void Convert_Room_SetDefaultCullMask_Recursive(Node * pParent); void Convert_Portals(); void Convert_Bounds(); bool Convert_Bound(LRoom &lroom, MeshInstance * pMI); void Convert_ShadowCasters(); + void Convert_Lights(); void Convert_HideAll(); @@ -104,12 +106,19 @@ private: LPortal * LRoom_RequestNewPortal(LRoom &lroom); void LRoom_PushBackSOB(LRoom &lroom, const LSob &sob); + // lights + void LRoom_DetectedLight(LRoom &lroom, Node * pNode); + void Light_Trace(int iLightID); + void Light_TraceRecursive(int depth, LRoom &lroom, LLight &light, int iLightID, const LVector &planes); + void Light_AddCaster_SOB(LLight &light, int sobID); + // shadows - void LRoom_FindShadowCasters(LRoom &lroom); +// void LRoom_FindShadowCasters(LRoom &lroom, int lightID, const LLight &light); void LRoom_FindShadowCasters_FromLight(LRoom &lroom, const LLight &light); void LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int depth, LRoom &lroom, const LLight &light, const LVector &planes); void LRoom_AddShadowCaster_SOB(LRoom &lroom, int sobID); + void TRoom_MakeOppositePortal(const LPortal &port, int iRoomOrig); @@ -118,6 +127,7 @@ private: bool Node_IsPortal(Node * pNode) const; bool Node_IsBound(Node * pNode) const; bool Node_IsIgnore(Node * pNode) const; + bool Node_IsLight(Node * pNode) const; int FindRoom_ByName(String szName) const; diff --git a/lroom_manager.cpp b/lroom_manager.cpp index 12b92d1..d0d6e74 100644 --- a/lroom_manager.cpp +++ b/lroom_manager.cpp @@ -43,6 +43,7 @@ LRoomManager::LRoomManager() m_bDebugPlanes = false; m_bDebugBounds = false; + m_bDebugLights = false; } int LRoomManager::FindClosestRoom(const Vector3 &pt) const @@ -220,9 +221,20 @@ void LRoomManager::CreateDebug() m_mat_Debug_Bounds = Ref(memnew(SpatialMaterial)); //m_mat_Debug_Bounds->set_flag(SpatialMaterial::FLAG_UNSHADED, true); m_mat_Debug_Bounds->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + m_mat_Debug_Bounds->set_cull_mode(SpatialMaterial::CULL_DISABLED); m_mat_Debug_Bounds->set_albedo(Color(0, 0, 1, 0.4)); b->set_material_override(m_mat_Debug_Bounds); b->hide(); + + { + ImmediateGeometry * b = memnew(ImmediateGeometry); + b->set_name("debug_lights"); + add_child(b); + m_ID_DebugLights = b->get_instance_id(); + b->set_material_override(m_mat_Debug_Bounds); + //b->hide(); + } + } @@ -231,7 +243,12 @@ ObjectID LRoomManager::DobRegister_FindVIRecursive(Node * pNode) const // is the node a VI? VisualInstance * pVI = Object::cast_to(pNode); if (pVI) + { + // take away layer 0 from the dob, so it can be culled effectively + pVI->set_layer_mask(0); + return pVI->get_instance_id(); + } // try the children for (int n=0; nget_child_count(); n++) @@ -453,6 +470,83 @@ bool LRoomManager::dob_unregister(Node * pDOB) } +// common stuff for global and local light creation +bool LRoomManager::LightCreate(Light * pLight, int roomID) +{ + // set culling flag for light + // 1 is for lighting objects outside the room system + pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); + + // create new light + LLight l; + l.SetDefaults(); + l.Hidable_Create(pLight); + l.m_GodotID = pLight->get_instance_id(); + + // direction + Transform tr = pLight->get_global_transform(); + l.m_ptPos = tr.origin; + l.m_ptDir = -tr.basis.get_axis(2); // or possibly get_axis .. z is what we want + l.m_RoomID = roomID; + + l.m_fMaxDist = pLight->get_param(Light::PARAM_SHADOW_MAX_DISTANCE); + + + //l.m_eType = LLight::LT_DIRECTIONAL; + + //m_Lights.push_back(l); + + + bool bOK = false; + + // what kind of light? + SpotLight * pSL = Object::cast_to(pLight); + if (pSL) + { + LPRINT(2, "\tSPOTLIGHT detected " + pLight->get_name()); + l.m_eType = LLight::LT_SPOTLIGHT; + l.m_fSpread = pSL->get_param(Light::PARAM_SPOT_ANGLE); + + bOK = true; + } + + OmniLight * pOL = Object::cast_to(pLight); + if (pOL) + { + LPRINT(2, "\tOMNILIGHT detected " + pLight->get_name()); + l.m_eType = LLight::LT_OMNI; + bOK = true; + } + + DirectionalLight * pDL = Object::cast_to(pLight); + if (pDL) + { + LPRINT(2, "\tDIRECTIONALLIGHT detected " + pLight->get_name()); + l.m_eType = LLight::LT_DIRECTIONAL; + bOK = true; + } + + // don't add if not recognised + if (!bOK) + { + LPRINT(2, "\tLIGHT type unrecognised " + pLight->get_name()); + return false; + } + + + // turn the local light off to start with + if (!l.IsGlobal()) + { +// l.Show(false); + //pLight->hide(); + } + + m_Lights.push_back(l); + + return true; +} + + bool LRoomManager::light_register(Node * pLightNode) { if (!pLightNode) @@ -470,18 +564,27 @@ bool LRoomManager::light_register(Node * pLightNode) return false; } - // create new light - LLight l; - l.m_GodotID = pLight->get_instance_id(); + return LightCreate(pLight, -1); - // direction - Transform tr = pLight->get_global_transform(); - l.m_ptPos = tr.origin; - l.m_ptDir = tr.basis.get_axis(2); // or possibly get_axis .. z is what we want +// // set culling flag for light +// // 1 is for lighting objects outside the room system +// pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); - m_Lights.push_back(l); +// // create new light +// LLight l; +// l.SetDefaults(); +// l.m_GodotID = pLight->get_instance_id(); - return true; +// // direction +// Transform tr = pLight->get_global_transform(); +// l.m_ptPos = tr.origin; +// l.m_ptDir = -tr.basis.get_axis(2); // or possibly get_axis .. z is what we want +// l.m_RoomID = -1; +// l.m_eType = LLight::LT_DIRECTIONAL; + +// m_Lights.push_back(l); + +// return true; } @@ -523,6 +626,21 @@ Node * LRoomManager::rooms_get_room(int room_id) return pRoom->GetGodotRoom(); } +void LRoomManager::rooms_set_debug_lights(bool bActive) +{ + m_bDebugLights = bActive; + Object * pObj = ObjectDB::get_instance(m_ID_DebugLights); + ImmediateGeometry * im = Object::cast_to(pObj); + if (!im) + return; + + if (bActive) + im->show(); + else + im->hide(); +} + + void LRoomManager::rooms_set_debug_bounds(bool bActive) { m_bDebugBounds = bActive; @@ -574,10 +692,33 @@ void LRoomManager::rooms_set_active(bool bActive) for (int n=0; nshow(); + else + pS->hide(); + + VisualInstance * pVI = sob.GetVI(); + if (pVI) + { + uint32_t mask = 0; + if (!bActive) + { + mask = LRoom::LAYER_MASK_CAMERA | LRoom::LAYER_MASK_LIGHT; + } + LRoom::SoftShow(pVI, mask); + } + + } + } void LRoomManager::rooms_set_logging(int level) @@ -610,7 +751,8 @@ void LRoomManager::rooms_set_camera(Node * pCam) m_ID_camera = pCam->get_instance_id(); // new .. select the cull layer - pCamera->set_cull_mask_bit(LRoom::SOFT_HIDE_BIT, false); + // 1 is for showing objects outside the room system + pCamera->set_cull_mask(1 | LRoom::LAYER_MASK_CAMERA); // use this temporarily to force debug // rooms_log_frame(); @@ -626,7 +768,31 @@ void LRoomManager::rooms_convert() // free memory for current set of rooms, prepare for converting a new game level void LRoomManager::rooms_release() { - m_Lights.clear(); + ReleaseResources(false); +} + +void LRoomManager::ReleaseResources(bool bPrepareConvert) +{ + m_ShadowCasters_SOB.clear(); + m_LightCasters_SOB.clear(); + m_Rooms.clear(true); + m_Portals.clear(true); + m_SOBs.clear(); + + if (!bPrepareConvert) + m_Lights.clear(); + + m_ActiveLights.clear(); + m_ActiveLights_prev.clear(); + + m_VisibleRoomList_A.clear(); + m_VisibleRoomList_B.clear(); + + m_MasterList_SOBs.clear(); + m_MasterList_SOBs_prev.clear(); + + m_VisibleList_SOBs.clear(); + m_CasterList_SOBs.clear(); } @@ -645,6 +811,7 @@ void LRoomManager::FrameUpdate_Prepare() // keep previous m_BF_master_SOBs_prev.CopyFrom(m_BF_master_SOBs); + m_BF_master_SOBs.Blank(); // note this can be done more efficiently with swapping pointer m_MasterList_SOBs_prev.copy_from(m_MasterList_SOBs); @@ -655,7 +822,12 @@ void LRoomManager::FrameUpdate_Prepare() m_BF_caster_SOBs.Blank(); m_BF_visible_SOBs.Blank(); - m_BF_master_SOBs.Blank(); + + // lights + m_BF_ActiveLights_prev.CopyFrom(m_BF_ActiveLights); + m_ActiveLights_prev.copy_from(m_ActiveLights); + m_ActiveLights.clear(); + m_BF_ActiveLights.Blank(); // as we hit visible rooms we will mark them in a bitset, so we can hide any rooms // that are showing that haven't been hit this frame @@ -833,6 +1005,7 @@ void LRoomManager::FrameUpdate_AddShadowCasters() m_Rooms[r].AddShadowCasters(*this); } + LPRINT(2, "TOTAL shadow casters " + itos(m_CasterList_SOBs.size())); } void LRoomManager::FrameUpdate_FinalizeVisibility_SoftShow() @@ -851,7 +1024,56 @@ void LRoomManager::FrameUpdate_FinalizeVisibility_SoftShow() { //SoftShow(pVI, sob.m_bSOBVisible); bool bVisible = m_BF_visible_SOBs.GetBit(ID) != 0; - LRoom::SoftShow(pVI, bVisible); + bool bCaster = m_BF_caster_SOBs.GetBit(ID) != 0; + + uint32_t flags = 0; + if (bVisible) flags |= LRoom::LAYER_MASK_CAMERA; + if (bCaster) flags |= LRoom::LAYER_MASK_LIGHT; + + LRoom::SoftShow(pVI, flags); + } + } + + // lights + for (int n=0; nshow(); + + // FIX GODOT BUG - light cull mask is not preserved when hiding and showing + // set culling flag for light + // 1 is for lighting objects outside the room system + //pLight->set_shadow(false); + //pLight->set_shadow(true); + //pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); + Vector3 ptBugFix = pLight->get_translation(); + pLight->set_translation(ptBugFix); + } + } + } + for (int n=0; nhide(); +// } } } @@ -886,7 +1108,17 @@ void LRoomManager::FrameUpdate_FinalizeVisibility_WithinRooms() { int ID = m_MasterList_SOBs[n]; LSob &sob = m_SOBs[ID]; + + // show / hide is relatively expensive because of propagating messages between nodes ... + // should be minimized sob.Show(true); + + // see how expensive show is +// for (int t=0; t<10000; t++) +// { +// sob.Show(false); +// sob.Show(true); +// } } } @@ -894,6 +1126,27 @@ void LRoomManager::FrameUpdate_FinalizeVisibility_WithinRooms() void LRoomManager::FrameUpdate_DrawDebug(const LCamera &cam, const LRoom &lroom) { + // light portal planes + if (m_bDebugLights) + { + Object * pObj = ObjectDB::get_instance(m_ID_DebugLights); + ImmediateGeometry * im = Object::cast_to(pObj); + if (!im) + return; + + im->clear(); + + im->begin(Mesh::PRIMITIVE_TRIANGLES, NULL); + + int nVerts = m_DebugPortalLightPlanes.size(); + + for (int n=0; nadd_vertex(m_DebugPortalLightPlanes[n]); + } + im->end(); + } + if (m_bDebugPlanes) { Vector3 ptCam = cam.m_ptPos; @@ -949,6 +1202,8 @@ void LRoomManager::FrameUpdate_DrawDebug(const LCamera &cam, const LRoom &lroom) im->end(); } + + } void LRoomManager::_notification(int p_what) { @@ -989,6 +1244,7 @@ void LRoomManager::_bind_methods() ClassDB::bind_method(D_METHOD("rooms_set_active"), &LRoomManager::rooms_set_active); ClassDB::bind_method(D_METHOD("rooms_set_debug_planes"), &LRoomManager::rooms_set_debug_planes); ClassDB::bind_method(D_METHOD("rooms_set_debug_bounds"), &LRoomManager::rooms_set_debug_bounds); + ClassDB::bind_method(D_METHOD("rooms_set_debug_lights"), &LRoomManager::rooms_set_debug_lights); // functions to add dynamic objects to the culling system diff --git a/lroom_manager.h b/lroom_manager.h index d71a6f5..3c1c9dc 100644 --- a/lroom_manager.h +++ b/lroom_manager.h @@ -90,11 +90,21 @@ private: // static objects LVector m_SOBs; + + // lights LVector m_Lights; + // active lights + LVector m_ActiveLights; + LVector m_ActiveLights_prev; + Lawn::LBitField_Dynamic m_BF_ActiveLights; + Lawn::LBitField_Dynamic m_BF_ActiveLights_prev; // master list of shadow casters for each room LVector m_ShadowCasters_SOB; + // master list of casters for each light + LVector m_LightCasters_SOB; + protected: static void _bind_methods(); void _notification(int p_what); @@ -127,8 +137,10 @@ private: bool DobRegister(Spatial * pDOB, float radius, int iRoom); ObjectID DobRegister_FindVIRecursive(Node * pNode) const; bool DobTeleport(Spatial * pDOB, int iNewRoomID); + bool LightCreate(Light * pLight, int roomID); void CreateDebug(); void DobChangeVisibility(Spatial * pDOB, const LRoom * pOld, const LRoom * pNew); + void ReleaseResources(bool bPrepareConvert); // helper funcs @@ -147,12 +159,16 @@ public: // whether debug planes is switched on bool m_bDebugPlanes; bool m_bDebugBounds; + bool m_bDebugLights; // the planes are shown as a list of lines from the camera to the portal verts LVector m_DebugPlanes; + LVector m_DebugPortalLightPlanes; + private: ObjectID m_ID_DebugPlanes; ObjectID m_ID_DebugBounds; + ObjectID m_ID_DebugLights; Ref m_mat_Debug_Planes; Ref m_mat_Debug_Bounds; @@ -179,6 +195,7 @@ public: void rooms_set_active(bool bActive); void rooms_set_debug_planes(bool bActive); void rooms_set_debug_bounds(bool bActive); + void rooms_set_debug_lights(bool bActive); // 0 to 6 .. defaults to 4 which is (2) in our priorities (i.e. 6 - level) void rooms_set_logging(int level);