About Mesh LOD

Design / architecture / roadmap discussions related to future of Ogre3D (version 2.0 and above)
Post Reply
xrgo
OGRE Expert User
OGRE Expert User
Posts: 989
Joined: Sat Jul 06, 2013 10:59 pm
Location: Chile
x 60

About Mesh LOD

Post by xrgo » Sun Dec 06, 2015 4:23 am

Hello! I am starting to need LOD for my meshes and I am using MeshLodGenerator to automatically generate LODs, so far it seems like it generates good lods, but it seems like no matter what value I put in the first distance it behaves always the same, like if it has a very low distance, this is my code (working with V1 mesh then I importV1):

Code: Select all

        Ogre::LodConfig config(newMesh);
        config.strategy = Ogre::PixelCountLodStrategy::getSingletonPtr();
        config.createGeneratedLodLevel(100, 0.4);
        config.createGeneratedLodLevel(200, 0.7);
        Ogre::MeshLodGenerator::getSingleton().generateLodLevels(config);
it behaves like distances ~1 and ~200, not 100 and 200
and setting lod_bias in my compositor nodes seems like its doing nothing I tried very low values and lods always seems to pop at the same distance.

are LODs broken on Ogre 2.1?
and I tried to use createManualLodLevel and I got a crash when importV1... is manual lod broken too? if not I'll post more details =)

Another question, what strategies are for? don't I need just the distance and the reduced mesh? or is needed for generate the reduced mesh?

Thanks in advance!
0 x

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 3917
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 167
Contact:

Re: About Mesh LOD

Post by dark_sylinc » Sun Dec 06, 2015 7:32 pm

Has been in my TODO list in a long while. I thought it wasn't even working. Tried your snippet in one of the demos and OMG lod is working??? (wrong, but working).

I've located the bug, it's in LodStrategy::lodSet. If LOD 0 should start after 0 meters, and LOD 1 should start after 100 meters; then LOD 1 will be displayed after 0 meters instead of 100 (it's off by one). You can still see the LOD 0 popup when the distance is negative (i.e. when you're completely inside the AABB). That's why you're getting such weird behavior that not even playing with lod values or biases help you.
and I tried to use createManualLodLevel and I got a crash when importV1... is manual lod broken too? if not I'll post more details =)
Meehh... not too interested since manual LOD levels never worked right anyway; and LOD was known to need a thorough examination.
Another question, what strategies are for? don't I need just the distance and the reduced mesh? or is needed for generate the reduced mesh?
In 1.x you could have LOD strategies per mesh. In 2.0+; LOD strategies are global (i.e. same strategy for everybody).
You can change the global strategy via LodStrategyManager::getSingleton().setDefaultStrategy.

I don't remember why MeshLodGenerator wants to know the strategy. Could be a relic from 1.x (i.e. so that it could tell the mesh what strategy it should use... which no longer works in 2.0) or could be that it needs a bit of its functionality for properly setting internal values. If the latter, then always provide the strategy you intend to use (i.e. what you will be using via setDefaultStrategy).
MeshLodGenerator allows explicitly providing a different one instead of always grabbing the default one, for offline tools (e.g. user knows in advance what strategy will be used).
It's not exactly user friendly. The MeshLodGenerator's code is a cluster fuck; but its mesh LOD reduction algorithm is sooo damn good I don't want to rewrite it or change it (it's really hard to get it right, and the GSoC student nailed it. IMHO it produces better results than what most artists could do providing manual LOD meshes).
0 x

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 3917
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 167
Contact:

Re: About Mesh LOD

Post by dark_sylinc » Sun Dec 06, 2015 9:01 pm

OK it's fixed and tested now.

Manual LODs is not tested though.
Another question, what strategies are for? don't I need just the distance and the reduced mesh? or is needed for generate the reduced mesh?
I forgot to explain the strategies. There's not too much science to it.
The default strategy is distance based. So the distance to the center of the AABB minus the object's radius determines the value to compare against the LOD list.

The PixelCount family of strategies are very similar, except they also account for the camera's projection and FOV; and thus it's best to think of them as the area of the screen they occupy. The closer an object is to the camera, the bigger it is and will occupy more screen. The further away it is, the smaller it is.
It's very similar to a simple distance strategy, but accounting FOV (i.e. how bigger/smaller it becomes as you move it closer/away) and in a different unit of measure (IIRC it's in % of the screen aka from 0 to 1; which is a rough estimate and not an exact measure)
0 x

xrgo
OGRE Expert User
OGRE Expert User
Posts: 989
Joined: Sat Jul 06, 2013 10:59 pm
Location: Chile
x 60

Re: About Mesh LOD

Post by xrgo » Sun Dec 06, 2015 10:11 pm

thank you so much!!!!! for the fix and the explanation =) <3
0 x

xrgo
OGRE Expert User
OGRE Expert User
Posts: 989
Joined: Sat Jul 06, 2013 10:59 pm
Location: Chile
x 60

Re: About Mesh LOD

Post by xrgo » Thu Dec 24, 2015 4:38 am

Hello! what about this feature?
myCamera->setUseMinPixelSize(true);
then myItem->setRenderingMinPixelSize(100);

doesn't seems to be working...

Edit: its commented out from OgreMovableObject.cpp, haven't tested it but I am guessing its commented because its now incompatible with the new compositor system(?). I think I can write my own implementation using Item::setRenderingDistance :D
Last edited by xrgo on Thu Dec 24, 2015 9:07 pm, edited 2 times in total.
0 x

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 3917
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 167
Contact:

Re: About Mesh LOD

Post by dark_sylinc » Thu Dec 24, 2015 9:02 pm

It's not being accounted anymore. setRenderingDistance fulfills a very similar job.
0 x

xrgo
OGRE Expert User
OGRE Expert User
Posts: 989
Joined: Sat Jul 06, 2013 10:59 pm
Location: Chile
x 60

Re: About Mesh LOD

Post by xrgo » Thu Dec 24, 2015 9:08 pm

lol, yes figured it just now and edited my prev post while you were posting :P
Thanks!!!
0 x

Hrenli
Halfling
Posts: 62
Joined: Tue Jun 14, 2016 12:26 pm
x 8

Re: About Mesh LOD

Post by Hrenli » Sat May 06, 2017 3:30 pm

Ok, guys, sorry for a necroing the old post, but I've just started to play with LODs in 2.1 and... It doesn't seem to do ANYTHING to me.

Basically, I've created a simple scene into which I load some meshes very like V2Mesh tutorial shows (i.e. I switched back to v1 meshe format in the file I am loading). I just added that MeshLodGenerator snippet from the OP (almost 1:1, just switched the distances to 10 and 20 for easier testing) before I import that v1 mesh into v2 and add it to the scene node:

Code: Select all

	Ogre::v1::MeshPtr v1Mesh = Ogre::v1::MeshManager::getSingleton().load(
		"test.mesh", Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME,
		Ogre::v1::HardwareBuffer::HBU_STATIC, Ogre::v1::HardwareBuffer::HBU_STATIC);

	Ogre::LodConfig config(v1Mesh);
	config.strategy = Ogre::DistanceLodBoxStrategy::getSingletonPtr();
	config.createGeneratedLodLevel(10, 0.4);
	config.createGeneratedLodLevel(20, 0.7);
	Ogre::MeshLodGenerator::getSingletonPtr().generateLodLevels(config);

	//Create a v2 mesh to import to, with a different name (arbitrary).
	Ogre::MeshPtr v2Mesh = Ogre::MeshManager::getSingleton().createManual("test.mesh.imported", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Everything compiles and runs, but the results look EXACTLY as there are no any LODs. I've tried to play with different strategies (the same in config.strategy and later calling Ogre::LodStrategyManager::getSingleton().setDefaultStrategy() ) but still the same - original mesh is shown no matter how far I move the camera. That was strange, so I added that to check:

Code: Select all

class MyLodListener : public Ogre::LodListener {

	virtual bool prequeueEntityMaterialLodChanged(Ogre::EntityMaterialLodChangedEvent & evt) {
		logMessage("EntityMaterialLodChangedEvent");
		return false;
	}

	virtual bool prequeueEntityMeshLodChanged(Ogre::EntityMeshLodChangedEvent & 	evt) {
		logMessage("EntityMeshLodChangedEvent");
		return false;
	}

	virtual bool prequeueMovableObjectLodChanged(const Ogre::MovableObjectLodChangedEvent & 	evt) {
		logMessage("MovableObjectLodChangedEvent");
		return false;
	}

};
And then in the CreateScene01:

Code: Select all

	MyLodListener* lodListener = new MyLodListener();
	sceneManager->addLodListener(lodListener);
But neither of those prequeue functions are fired when I move the camera (even quite far). Also, just to try to see, I added exporting of the resulting v2 mesh after it being processed by the MeshLodGenerator. The file I get is 1:1 as if the very original mesh.xml converted to v2 mesh... Looks like there are just no LODs at all in my scene.

So, I am wondering - are the LODs working (I guess they should?) and if so, what am I doing wrong? :) How one is supposed to use the LODs? Must be something stupid I am missing, like a generic init or something...
0 x

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 3917
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 167
Contact:

Re: About Mesh LOD

Post by dark_sylinc » Sat May 06, 2017 6:27 pm

LOD listeners are deprecated so they are not being fired. Don't use them for debugging because that won't work.

For V1 meshes make sure:
  • v1::SubMesh::mLodFaceList is populated
  • v1::Mesh::mLodValues is populated
  • v1::Mesh::mMeshLodUsageList is populated
For V2 meshes:
  • Mesh::mNumLods > 1
  • Mesh::mLodValues is populated
  • mVao[0] & mVao[1] have both more than one entry (one per LOD each).
Overall check:
  • MovableObject::mLodMesh pointer is set and populated.
  • LodStrategyManager::getSingleton().getDefaultStrategy is the one you want.
  • The PASS_SCENE / render_scene definition must update the LODs (or the LODs should already be up to date). In other words CompositorPassSceneDef::mUpdateLodLists must be true (in compositor script, just write "lod_update_list yes")
  • Place a breakpoint inside YourFavouriteLodStrategy::lodUpdateImpl to ensure it works. For optimum performance it should be called once per frame (if it's called more than once per frame, it should at least be called with different render queue ranges that don't overlap (firstRq & lastRq from SceneManager::updateAllLodsThread).
The best place to start is placing a breakpoint at YourFavouriteLodStrategy::lodUpdateImpl. If it's not breaking, then the pass_scene is not set to update LODs or the wrong strategy is set.
If it does break, then you can debug why the wrong LOD is being chosen (for easiest debugging just draw one object on screen)

Cheers
Matias
0 x

Hrenli
Halfling
Posts: 62
Joined: Tue Jun 14, 2016 12:26 pm
x 8

Re: About Mesh LOD

Post by Hrenli » Sat May 06, 2017 7:18 pm

Thanks for the tips, Matias! Will dig deeper for what exactly I am doing wrong then...

As for the listeners - I am also trying to play with generating impostors as last visible LOD thing. Is there a better way to find out that my Mesh/Item/Node moves out of a specific distance from camera than enumerating all of them and checking the distance for each? Maybe I still can ask Ogre to tell me that as it has to calculate it anyway? :) Is there any working listener for that (I had plans for the LOD listener before)?
0 x

crancran
Greenskin
Posts: 131
Joined: Wed May 05, 2010 3:36 pm
x 2

Re: About Mesh LOD

Post by crancran » Sun Jan 06, 2019 11:06 am

Hi Matias

The problem I see is that the LodStrategy is manipulating internal state of the MovableObject/Renderable when the LOD changes and therefore there is no indication or event raised that notifies us that this happened. For situations where a renderable may want to leverage a separate material when a certain LOD is being applied, I'm not sure how to handle that.

I'd rather not set off to roll my own LodStrategy unless I have to in order to actually have a "hook" in when the LOD changes. Is there some other place in the pipeline that I could have overlooked where I could catch this change?
0 x

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 3917
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 167
Contact:

Re: About Mesh LOD

Post by dark_sylinc » Sun Jan 06, 2019 4:42 pm

There is indeed no notification when the LOD changes.

You could "detect" changes by overloading passEarlyPreExecute & passPreExecute, as the LOD changes in-between those two. But such detection would be suboptimal.

A much better approach is to create your own LOD strategy. For example derive from AbsolutePixelCountLodStrategy (or DistanceLodSphereStrategy), overload LodStrategy::lodUpdateImpl and do whatever you want there.

Then call LodStrategyManager::getSingleton().addStrategy and set your own strategy as the active one.

Most of the strategies end up calling LodStrategy::lodSet (which is defined in OgreLodStrategyPrivate.inl). My recommendation is that you derive from the strategy you want, overload lodUpdateImpl, and do everything the original implementation does (i.e. copy-paste the base class' code), but instead of calling LodStrategy::lodSet, use your own.
Most of the code in lodUpdateImpl is SIMD, but the code in LodStrategy::lodSet is scalar, which makes it easy to follow. lodSet is also the last step, which is what you're interested in.

Note: Calling Renderable::setDatablock is slow. It's not super slow, but if you're going to be calling this every frame for a lot of objects based on camera movement, it becomes a problem.
This is because the Hlms needs to analyze the geometry signature of the Renderable and its compatibility with the material (e.g. does the renderable have tangents? because we'll need that if the material uses normal maps).

A much more efficient approach is to, at init time, call setDatablock for each datablock/LOD you want to use, and cache every pair of Renderable::mHlmsHash and mHlmsCasterHash.
Then, when switching materials for the LOD, use Renderable::_setHlmsHashes which is fast (if you set the wrong hash, crashes or rendering glitches may occur).

Note 2: Renderable::mCurrentMaterialLod does not appear to be actually used as of now.
0 x

crancran
Greenskin
Posts: 131
Joined: Wed May 05, 2010 3:36 pm
x 2

Re: About Mesh LOD

Post by crancran » Sun Jan 06, 2019 7:40 pm

dark_sylinc wrote:
Sun Jan 06, 2019 4:42 pm
A much better approach is to create your own LOD strategy. For example derive from AbsolutePixelCountLodStrategy (or DistanceLodSphereStrategy), overload LodStrategy::lodUpdateImpl and do whatever you want there.

Then call LodStrategyManager::getSingleton().addStrategy and set your own strategy as the active one.
This is what I suspected I would have to do, but before I went down that path, I wanted to confirm whether there was an alternate approach I could use instead. I understand the performance reason behind the global strategy rather than the per-mesh like 1.x had previously, but I do wonder if a compromise of having one per render queue would ever make sense? If multiple render queues shared the same strategy, then they could be grouped together, but if some queues wanted to use separate ones, it would simply be an additional pass but only for those objects. I'm not sure of a use case where having multiple active strategies makes much sense tbh; but having a single global is what detered me away from this path in the first place.
dark_sylinc wrote:
Sun Jan 06, 2019 4:42 pm
instead of calling LodStrategy::lodSet, use your own.
Is there a particular reason why MovableObject/Renderable don't expose an internal "_setXXXXX" method that gets inlined for accessing this protected state rather than utilizing friend access which impacts extensablity?
0 x

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 3917
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 167
Contact:

Re: About Mesh LOD

Post by dark_sylinc » Mon Jan 07, 2019 2:04 am

crancran wrote:
Sun Jan 06, 2019 7:40 pm
I understand the performance reason behind the global strategy rather than the per-mesh like 1.x had previously
Technically speaking a custom strategy can do that.
crancran wrote:
Sun Jan 06, 2019 7:40 pm
...but I do wonder if a compromise of having one per render queue would ever make sense?
If multiple render queues shared the same strategy, then they could be grouped together, but if some queues wanted to use separate ones, it would simply be an additional pass but only for those objects. I'm not sure of a use case where having multiple active strategies makes much sense tbh; but having a single global is what detered me away from this path in the first place.
That sounds like an interesting compromise. It would be quite trivial to add multiple default strategies LodStrategyManager (i.e. up to 255) and then modify SceneManager::updateAllLodsThread.
The performance price would be minimal.

But I'm not sure how much benefit that adds (unless you count custom LOD schemes as well). TBH all the current LOD strategies we offer are a fancy way of calculating distance to camera.

Distance LOD strategies is measured in units away from camera; while the pixel count strategy family calculate their pixel count by taking the distance away from camera then add some projection math to calculate how much the object shrunk, and finally compare that shrunken size against a pixel count threshold. This method has the benefit that it can account for higher resolutions, bigger screens, aspect ratios, and bigger FOV. But ultimately it's a fancy distance-to-camera system.

There's not much incentive to use multiple strategies at the same time (unless you factor custom strategies), particularly given that there is not much performance difference between them (a lot of the projection math can be calculated once, then reused for every object)
crancran wrote:
Sun Jan 06, 2019 7:40 pm
Is there a particular reason why MovableObject/Renderable don't expose an internal "_setXXXXX" method that gets inlined for accessing this protected state rather than utilizing friend access which impacts extensablity?
Because nobody asked before?
The friend pattern is used because we wanted to keep good performance in Debug builds too, since it's a sensitive location.

Note that _setHlmsHashes is a virtual function though.

Feel free to add these inline versions and submit a PR.
0 x

Post Reply