mrwonko
I've been thinking about how rend2 handles dynamic lights. How exactly do you create those? I'm specifically considering how impostors and real geometry might need to be lit differently depending on the distance.
A technical discussion regarding the implementation of Level of Detail (LOD) systems within the BSP format, focusing on how dynamic lights and PVS culling interact with impostor geometry. The thread explores the challenges of player visibility and shadow calculations when swapping between high-detail and low-detail leaf nodes.
[2023-11-08 22:17] <mrwonko> rend2 has dynamic lights, right? How do you create those? [2023-11-08 22:17] <.mjt> same as vanilla [2023-11-08 22:17] <.mjt> .efx [2023-11-08 22:17] <.mjt> SP has a specific dlight entity [2023-11-08 22:18] <.mjt> but MP hasn't afaik [2023-11-08 22:18] <mrwonko> I'm thinking about how the impostor and the real geometry may need to be lit differently [2023-11-08 22:18] <.mjt> also all the func_ entities have the light key [2023-11-08 22:18] <.mjt> binding a dlight at their origin [2023-11-08 22:19] <.mjt> impostor should still receive light based on surface type and shader / vertex, lightmap / dynamic only if it's a dynamic md3/glm [2023-11-08 22:19] <mrwonko> how is decided whether a dlight is rendered? [2023-11-08 22:19] <.mjt> since rgbGen lightingDiffuse / alphaGen lighting specular ONLY receive from one hardcoded lightsource on BSP geo [2023-11-08 22:19] <.mjt> not sure rend2 handles this differently [2023-11-08 22:19] <.mjt> probably nearest? since vanilla can only handle 16 at once and rend2 32 or so? [2023-11-08 22:20] <mrwonko> is there any PVS-based entity culling that affects it? [2023-11-08 22:20] <.mjt> SomaZ might know precisely. Could also be some hierarchy of importance... not sure [2023-11-08 22:20] <.mjt> sure [2023-11-08 22:20] <.mjt> should be [2023-11-08 22:20] <.mjt> if entity is not rendered / no dlight gonna be cast [2023-11-08 22:20] <mrwonko> so if we hide LOD0 using PVS, all dlights inside could be hidden with it (edited) [2023-11-08 22:20] <.mjt> no [2023-11-08 22:20] <.mjt> dlights stay the same [2023-11-08 22:20] <mrwonko> since the other LODs will be in unreachable leafs [2023-11-08 22:20] <.mjt> just the surfaces affected would be swapped [2023-11-08 22:21] <.mjt> yes [2023-11-08 22:21] <mrwonko> so you're saying dlights are not PVS-culled [2023-11-08 22:21] <.mjt> the lod stuff won't introduce different VIS - just from what kind of spots it gets shown [2023-11-08 22:22] <.mjt> the dynamic entities are PVS culled based on the stuff the PVS decides to render - separate of drawsurfaces as dlights are not filtered into the VIS (edited) [2023-11-08 22:22] <.mjt> they are dynamic stuff [2023-11-08 22:22] <.mjt> not stuff that is in the .bsp - it's just a position and if that position is in a portal / area that the PVS decides to render, it also renders the dlights [2023-11-08 22:23] <.somaz> first comes, first serves [2023-11-08 22:35] <outcastcantina> O o asked that laid down a sec and damn u guys nerded out haha [2023-11-08 22:39] <mrwonko> tried to sum up the results of the impostor brainstorming: https://github.com/mrwonko/netradiant-custom-playground/wiki/Q3Map2:-impostor-BSP-leafs / realized we'll need per-LOD light calculations, we don't want the impostors casting shadows on each other or LOD0. One more reason we need to define what area is being replaced by an impostor... [2023-11-08 22:40] <.somaz> the leaf must be reachable! [2023-11-08 22:41] <.somaz> else its not considered by the renderer [2023-11-08 22:41] <mrwonko> it will be reachable through PVS, but not through BSP [2023-11-08 22:42] <mrwonko> or does the renderer optimise that away somehow? [2023-11-08 22:42] <.somaz> that doesnt matter if the renderer wont reach the leaf. It actually traverses the bsp tree [2023-11-08 22:42] <.somaz> [attachment: https://cdn.discordapp.com/attachments/1103343199113711766/1171942885097230397/20231108_234136.jpg?ex=6a025b5d&is=6a0109dd&hm=a5412edf74aad9b0895c5778392bae1e84d86c880565905a386758a8d2941225&] [2023-11-08 22:42] <mrwonko> what? [2023-11-08 22:42] <mrwonko> I mean obviously the renderer traverses the bsp tree, it needs to find out the pvs [2023-11-08 22:42] <.somaz> thats how you could add a lod to the tree [2023-11-08 22:43] <.somaz> yea, and it traverses it again when adding the leaf surfaces. [2023-11-08 22:43] <mrwonko> how? why? what? [2023-11-08 22:45] <.mjt> only selfshadowing for LODs [2023-11-08 22:45] <.somaz> heres the start of adding world surfaces to the renderer list: / https://github.com/JACoders/OpenJK/blob/master/codemp/rd-vanilla/tr_world.cpp#L1694 [2023-11-08 22:45] <.mjt> similar to how _cs _rs negative values can be used to have intersecting geometry not cast shadows on each other [2023-11-08 22:46] <.somaz> might be easier to understand than my cryptic explainations [2023-11-08 22:47] <mrwonko> well, and shadowing by the rest of the world [2023-11-08 22:48] <.mjt> look into the quake 3 entity definitions from NRC [2023-11-08 22:48] <.mjt> about _cs and _rs [2023-11-08 22:48] <mrwonko> I think first you do a global LOD0 lightmap pass, then you do one more for each impostor [2023-11-08 22:48] <mrwonko> I know _cs and _rs [2023-11-08 22:48] <mrwonko> I just don't want to do that by hand [2023-11-08 22:48] <mrwonko> on impostors [2023-11-08 22:49] <mrwonko> remind me, are those bitfields? [2023-11-08 22:49] <.mjt> yeah, it must inherit _cs and _rs from original LOD0 but act different [2023-11-08 22:49] <outcastcantina> Is this really gonna be possible loding and all this? [2023-11-08 22:49] <.mjt> because _cs and _rs on LOD0 might still be attuned to other scenarios the other LODS also must respect [2023-11-08 22:49] <outcastcantina> I mean damn fps would sore with this sort of tech [2023-11-08 22:50] <mrwonko> it cannot inherit from LOD0 because there may be many func_groups with different values in there [2023-11-08 22:50] <.mjt> It MUST inherit with regards to what it receives from [2023-11-08 22:50] <.mjt> not the _cs part [2023-11-08 22:50] <.mjt> since only LOD0 _cs es other stuff [2023-11-08 22:50] <.mjt> only about selfshadowing [2023-11-08 22:50] <mrwonko> I think you'll have to manually set the property on the _impostor entity [2023-11-08 22:50] <mrwonko> because it cannot be derived reliably automatically (edited) [2023-11-08 22:51] <.mjt> it would basically always be _cs -1000 _rs -1000 but also _rs from LOD0 [2023-11-08 22:51] <.mjt> and 1000 would be unique to that specific entity/LOD setup [2023-11-08 22:51] <.mjt> I do think it can [2023-11-08 22:52] <mrwonko> if we define what is part of LOD0 by enclosing it in a brush, there can be many different _rs values inside? [2023-11-08 22:53] <mrwonko> so how do you match which surface gets what? [2023-11-08 22:54] <outcastcantina> So how would this work for us mappers to set up in simple terms func group a section we’ve designed a lod variant of and make it some sort of func group or ent that calls to that when culling hits a point? [2023-11-08 22:55] <.mjt> Still need to have some sort of brushgroup entity setup for all LODs, including LOD0 [2023-11-08 22:55] <.mjt> so no need to encapsulate it necessarily [2023-11-08 22:55] <.mjt> the encapsulate / portal shader approach is only for specific VIS hackery [2023-11-08 22:55] <mrwonko> I thought encapsulation removes this requirement? That's why I proposed it [2023-11-08 22:55] <.mjt> not for the LOD part [2023-11-08 22:56] <mrwonko> no different encapsulation [2023-11-08 22:56] <mrwonko> I'm talking about encapsulating LOD0 to mark it [2023-11-08 22:56] <.mjt> LODs would be radius / distance based I though... ofc relying on decent _blocksize [2023-11-08 22:56] <mrwonko> not about encapsulating areas that see the impostor [2023-11-08 22:57] <.mjt> how do you intend to construct different LODs? If you want to use the approach on misc_model, no need for encapsulation [2023-11-08 22:57] <.mjt> if you want to construct it from brushes and patches, what'S easier than binding an origin to a brushgroup entity [2023-11-08 22:57] <.mjt> and naming / linking it through keys that get stripped on compile [2023-11-08 22:57] <.mjt> to setup what is LOD0, 1, 2 etc [2023-11-08 22:57] <mrwonko> LOD1 is an _impostor that includes the LOD0-volume-brushes and an origin brush and a radius property. LOD2 is an impostor targeted to LOD1 (or maybe the targetting needs to be the other way around, I have to think about it) (edited) [2023-11-08 22:58] <.mjt> and placing them on top of each other. [2023-11-08 22:58] <outcastcantina> Just sort of reading through this will all be rend2 stuff right? Future update? [2023-11-08 22:58] <.mjt> vanilla [2023-11-08 22:58] <.mjt> AND rend2 [2023-11-08 22:58] <outcastcantina> Ah eternal and r2 [2023-11-08 22:59] <mrwonko> and mb2 and original jamp and quake 3, in theory [2023-11-08 23:00] <outcastcantina> Hmm dope… could really help us eventually move things beyond way beyond what we’re doing even now [2023-11-08 23:00] <outcastcantina> Chop chop XD [2023-11-08 23:01] <mrwonko> so if a player in an X-Wing flies into an impostor leaf, do they disappear for LOD1 observers? [2023-11-08 23:02] <mrwonko> or just runs in there, really [2023-11-08 23:02] <outcastcantina> Wouldn’t They just become whatever lod of the ship and player is? (edited) [2023-11-08 23:02] <outcastcantina> Caps [2023-11-08 23:02] <outcastcantina> Or I’d think it should be done that way [2023-11-08 23:03] <mrwonko> "become wherever"? [2023-11-08 23:03] <outcastcantina> Damn phone [2023-11-08 23:03] <mrwonko> the ship and player's LOD are irrelevant, those are realtime, we're talking about precomputed vis [2023-11-08 23:04] <mrwonko> I need to look at how player pvs culling works [2023-11-08 23:04] <outcastcantina> Well that’ll be ducky as about ninety percent have no lod forms [2023-11-08 23:04] <outcastcantina> Not like base chars [2023-11-08 23:05] <mrwonko> ducky? [2023-11-08 23:06] <outcastcantina> I stand by it.. [2023-11-08 23:06] <outcastcantina> [2023-11-08 23:06] <mrwonko> I have no idea what a lack of playermodel LODs has to do with level optimization [2023-11-08 23:07] <outcastcantina> Meh I’m ignorant to wether the new system would mess with players but that answers that I guess haha [2023-11-08 23:07] <.mjt> LOD in this case is only referring to the new tech, has nothing to do with the system that decides LOD for dynamic models [2023-11-08 23:08] <.mjt> Fly into a leaf? A leaf is what holds drawsurfaces... Not collision [2023-11-08 23:09] <mrwonko> yes, enter it (edited) [2023-11-08 23:09] <mrwonko> and leave all leafs not part of the LOD area [2023-11-08 23:09] <mrwonko> if only players in the PVS are networked, this would cause them to vanish, no? [2023-11-08 23:09] <.mjt> Enter it how, as in their PVS decides to draw LOD independent of an outside observer [2023-11-08 23:10] <.mjt> No [2023-11-08 23:10] <mrwonko> moving their position so that it is contained within the leaf's volume [2023-11-08 23:10] <.mjt> You have a misconception blocking your sight [2023-11-08 23:11] <mrwonko> something keeps simple shader wallhacks from working across PVS boundaries [2023-11-08 23:11] <mrwonko> the same presumably applies when we fuck with the PVS in this fashion [2023-11-08 23:11] <.mjt> Just runs in there [2023-11-08 23:12] <.mjt> Sort nearest and depthwrite disable? [2023-11-08 23:12] <mrwonko> yeah [2023-11-08 23:12] <mrwonko> something like that [2023-11-08 23:12] <mrwonko> it's been over a decade since I experimented with that (offline) [2023-11-08 23:13] <.mjt> Because surfaceparm forcesight also had some extra stuff going on and entities with explicit forcesight key that support it can be set to broadcast (edited) [2023-11-08 23:13] <mrwonko> I don't remember precisely [2023-11-08 23:13] <ensiform> Was forcesight a "server" side surface as well? I thought only cgame checked for it [2023-11-08 23:14] <.mjt> I still think you imagine opening a unique volume just for LODs... That wouldn't be the approach I think [2023-11-08 23:14] <mrwonko> you need LOD0 to disappear [2023-11-08 23:14] <mrwonko> so all of its leafs are removed from the PVS [2023-11-08 23:14] <.mjt> Only cgame I think... Unless entity key on top setting it to broadcast [2023-11-08 23:14] <mrwonko> this affects non-broadcast entities inside, no? [2023-11-08 23:14] <.mjt> No, those would respect PVS [2023-11-08 23:15] <mrwonko> and PVS says "don't show this leaf, it's LOD0 and that is too far away" [2023-11-08 23:15] <.mjt> That's what I meant by misconception [2023-11-08 23:16] <.mjt> You wouldn't open a specific leaf just for the LOD but extra leafs to all nodes that holds visibility to it [2023-11-08 23:16] <mrwonko> I'm only talking about LOD0 right now [2023-11-08 23:16] <.mjt> You can use a bounding volume for compile time tracing optimization [2023-11-08 23:17] <.mjt> You mean entire level sections or some models like a statue? (edited) [2023-11-08 23:18] <mrwonko> ignore any grouping and optimization, it is irrelevant to the issue at hand [2023-11-08 23:18] <mrwonko> look at one leaf [2023-11-08 23:18] <mrwonko> it has a "real" state, LOD0, reachable through the BSP [2023-11-08 23:19] <mrwonko> it has an LOD1 variant, which is an extra leaf, which cannot occupy the same location (in the BSP traversal), so it's effectively unreachable, regardless of how exactly it is encoded (edited) [2023-11-08 23:19] <mrwonko> a player inside of that area will thus always be assigned to the LOD0 leaf [2023-11-08 23:19] <mrwonko> so if I'm far enough away that my PVS tells me to show the LOD1 leaf instead of the LOD0 leaf, the player disappears [2023-11-08 23:20] <.mjt> Now get it [2023-11-08 23:21] <.mjt> Mutual exclusivity... So it might only be feasible for non enterable areas [2023-11-08 23:21] <mrwonko> Which is why if you look through the window of the jedi temple into the impostor interior, you won't see any players [2023-11-08 23:21] <.mjt> Good for SP, less so for MP xD [2023-11-08 23:21] <.mjt> Hmmm [2023-11-08 23:21] <mrwonko> but since you can't see them enter (the door is elsewhere), you don't really notice, right? [2023-11-08 23:21] <.mjt> Custom server can be made to always PVS based on lod0 [2023-11-08 23:22] <mrwonko> with a custom .bsp? (edited) [2023-11-08 23:22] <.mjt> Not sure clientside culls players and serverside only entity interaction [2023-11-08 23:22] <.mjt> That would be easier than code changes but will it work? [2023-11-08 23:22] <.mjt> It should [2023-11-08 23:23] <mrwonko> for the Jedi Temple window-based LODs the "LOD shows no players" restriction should not really be an issue, but for distance-based LODs it may be a problem [2023-11-08 23:23] <mrwonko> I just have trouble imagining the necessary code changes [2023-11-08 23:23] <mrwonko> would you attempt to detect LOD leafs based on them being unreachable? [2023-11-08 23:24] <mrwonko> I don't see how it would work, without additional metadata about LOD relations [2023-11-08 23:25] <mrwonko> and then there's this [2023-11-08 23:29] <.mjt> Force broadcast on all players using icarus xD [2023-11-08 23:30] <.mjt> No, consciously restrict myself to such scenarios when making the map [2023-11-08 23:31] <.mjt> And yes, then only by encapsulating say a statue or sth with a new leaf [2023-11-08 23:31] <mrwonko> What scenarios? [2023-11-08 23:31] <.mjt> An area that can switch on PVS but not contain players [2023-11-08 23:31] <.mjt> Because too small or sealed off [2023-11-08 23:32] <.mjt> I always thought the BSP stored the VIS areas which then stored the leafs [2023-11-08 23:32] <.mjt> So an area could have multiple leafs but it's different [2023-11-08 23:33] <.mjt> And area decides with entities to draw and not the leaf [2023-11-08 23:33] <.mjt> Taking about misconceptions xD [2023-11-08 23:33] <.mjt> We should go back to the basics first 🤣 [2023-11-08 23:34] <mrwonko> VIS area as in the ones separated by area portals? [2023-11-08 23:36] <mrwonko> Is your current area derived the leaf you're in? (edited) [2023-11-08 23:37] <mrwonko> Anyway, it's past my bedtime, I'll try to put the phone down [2023-11-08 23:50] <mrwonko> I still don't get what the problem with having a single leaf for an entire impostor instead of matching LOD0 leafs 1:1 is. It would keep the number of leafs low, the lower detail doesn't require as many of them [2023-11-08 23:58] <mrwonko> Using a func_group (or other brush/model entity) to mark the LOD0 to be replaced means we might have to partition a leaf based on which surfaces come from the entity. This would require a similar virtual leaf hack as we'll need to store impostors. [2023-11-09 00:03] <mrwonko> But you avoid overlaps, whereas a volume based approach may have surfaces in the LOD0 of multiple impostors, which raises the question is whether it would be hidden if any or all connected impostors are visible
I've been thinking about how rend2 handles dynamic lights. How exactly do you create those? I'm specifically considering how impostors and real geometry might need to be lit differently depending on the distance.
It's mostly the same as vanilla. You use .efx files, and SP has a specific dlight entity, though MP doesn't as far as I know. Also, all func_ entities have the light key which binds a dlight at their origin.
Regarding impostors: they should still receive light based on surface type and shader (vertex/lightmap). Dynamic lighting usually only applies if it's a dynamic MD3 or GLM. Since rgbGen lightingDiffuse and alphaGen lightingSpecular only receive from one hardcoded lightsource on BSP geometry in vanilla, I'm not sure if rend2 handles this differently. It probably picks the nearest light, since vanilla handles 16 at once and rend2 can do around 32.
How is it decided whether a dlight is rendered? Is there any PVS-based entity culling that affects it? I'm worried that if we hide LOD0 using PVS, all dlights inside could be hidden with it since the other LODs will be in unreachable leafs.
Dlights stay the same; just the surfaces affected would be swapped. The LOD stuff won't introduce different VIS—just different spots from which it gets shown. Dynamic entities are PVS culled based on what the PVS decides to render, separate from drawsurfaces. Dlights aren't filtered into the VIS; they are dynamic. If a position is in a portal or area that the PVS decides to render, it renders the dlights at that position.
I've tried to sum up the results of the impostor brainstorming here: https://github.com/mrwonko/netradiant-custom-playground/wiki/Q3Map2:-impostor-BSP-leafs
I realized we'll need per-LOD light calculations. We don't want the impostors casting shadows on each other or on LOD0. This is another reason we need to define exactly what area is being replaced by an impostor.
The leaf must be reachable! Else it's not considered by the renderer. It doesn't matter if you try to force it; if the renderer won't reach the leaf, it won't work because it actually traverses the BSP tree. It traverses the tree once to find the PVS and again when adding leaf surfaces.

You can see the start of adding world surfaces to the renderer list here: https://github.com/JACoders/OpenJK/blob/master/codemp/rd-vanilla/tr_world.cpp#L1694. That might be easier to understand than my cryptic explanations.
For the shadowing issue, you'd only want self-shadowing for LODs. It's similar to how _cs (cast shadows) and _rs (receive shadows) negative values are used to prevent intersecting geometry from casting shadows on each other.
The LOD would basically need to inherit _cs and _rs from the original LOD0 but act differently. It must inherit what it receives from, but the _cs part is different since only LOD0 should cast shadows on the rest of the world. You'd likely use a unique value (like -1000) for that specific entity/LOD setup.
Is this really going to be possible? If we can get LODing working like this, FPS would soar. How would mappers set this up? Do we just func_group a section we've designed a LOD variant for and have an entity call it when culling hits a certain point?
The theory is that LOD1 is an _impostor entity that includes the LOD0 volume brushes, an origin brush, and a radius property.
One major concern: if a player in an X-Wing flies into an impostor leaf, do they disappear for LOD1 observers? If I'm far enough away that my PVS tells me to show the LOD1 leaf instead of the LOD0 leaf, and the player is assigned to the LOD0 leaf (which is now culled), the player might vanish. This would be fine for the Jedi Temple windows where you can't enter the area, but for distance-based LODs in open space, it's a problem.
That sounds like a mutual exclusivity issue. It might only be feasible for non-enterable areas in MP. You could potentially make a custom server always calculate PVS based on LOD0 to ensure entities are networked, but that's a significant change.
I think we should go back to basics. I always thought the BSP stored VIS areas which then stored the leafs. An area decides which entities to draw, not the leaf. We need to be careful about how we're partitioning these volumes.
I still don't see the problem with having a single leaf for an entire impostor instead of matching LOD0 leafs 1:1. It would keep the leaf count low.
If we use a func_group to mark the LOD0 to be replaced, we might have to partition a leaf based on which surfaces come from that entity. This requires a virtual leaf hack similar to what we'll need for storing the impostors themselves. The volume-based approach is tricky because surfaces could exist in the LOD0 of multiple impostors, which raises the question of whether it should be hidden if only one or all connected impostors are visible.