VulkanSceneGraph icon indicating copy to clipboard operation
VulkanSceneGraph copied to clipboard

vsg::LOD node does not support a visible range

Open rhabacker opened this issue 8 months ago • 9 comments

0001-Add-example-for-vsg-LOD-nodes.patch.txt

Does your function request refer to a problem? Please describe it With the current implementation of the vsg::LOD class, it is not possible to hide a node if the distance to the camera falls below a certain value. You can only show an object if the distance falls below a certain value, as can be inspected by the attached test case, which can be applied to the vsgExamples git repo.

Describe the desired solution The LOD node should support an area in which the associated node is visible. This is necessary, for example, if two different resolutions of an object are required depending on the distance (see e.g. https://github.com/vsg-dev/osg2vsg/issues/61)

Describe the alternatives you have considered not checked

Additional information In osg, the LOD node supports the specification of an area in which the associated node is visible (by the attribute RangeList).

0001-Add-example-for-vsg-LOD-nodes.zip

vsglod.vsgt.zip

rhabacker avatar Apr 16 '25 14:04 rhabacker

The VSG uses screen space height ratio for choosing which LOD children are active.

I chose not to implement distance based test as the height ratio is better mapped to visual influence of a LOD child.

You can compute the height ratio from the distance if you make a standard assumption of the vertical field of view that suites your application.

robertosfield avatar Apr 16 '25 16:04 robertosfield

Yes, the conversion from a distance to the screen height ratio is processed in [ConvertToVsg::apply(osg::LOD& lod)](https://github.com/vsg-dev/osg2vsg/blob/471ced7c91834a5f2e49ccaf3fec4f132ee96656/src/osg2vsg/ConvertToVsg.cpp#L498].

However, the problem is that there is currently no such thing as maximumScreenHeightRatio in vsg to specify a range to hide a node again when approaching the camera, as you can see from the vsgt file https://github.com/user-attachments/files/19790169/vsglod.vsgt.zip generated with the test case.

The example contains a cylinder that is supposed to represent a lower resolution and is replaced by the cone when approaching the camera, which is not possible. Both objects are displayed when approaching the camera.

rhabacker avatar Apr 17 '25 07:04 rhabacker

The way LOD/RecordTraversal::apply(LOD&) work is the children are meant to be ordered for high res to low res, each child is tested in turn and once one passes the test it's traversed and none of the other lower res children will be traversed.

This is a bit different to osg::LOD where there isn't an implied ordering.

The vsg::LOD works this way for efficiency, aiming to minimize the CPU overhead in making the decision what children to traverse.

It could be that the osg2vsg code isn't setting up vsg::LOD correctly. osg2vsg needs a major rewrite as the VSG has moved on so much since osg2vsg was written.

robertosfield avatar Apr 17 '25 08:04 robertosfield

The test case https://github.com/user-attachments/files/19790158/0001-Add-example-for-vsg-LOD-nodes.zip, which shows the problem, has no reference to osg2vsg. It simply adds two vsg::LOD instances to a vsg:group, where the first instance (a cone) represents a node with higher resolution (with child.minimumScreenHeightRatio 1.01) and the second instance (a cylinder) represents a node with lower resolution (child.minimumScreenHeightRatio 0.01) at the second position in the list. According to your theory, only the cylinder should be visible at larger distances and only the cone when approaching, which does not happen. When approaching, the cone and the cylinder are visible, but only the cone should be visible. Have you tried this yourself?

rhabacker avatar Apr 17 '25 09:04 rhabacker

I have just tried out your test program and see the issue you are talking about, but it's not an issue with vsg::LOD but the way you've set you scene graph.

You've created two independent LODs that are attempting to render the "same" object for different resolutions, but the way vsg::LOD is written is that this usage case requires you to put both of the subgraphs into the same vsg::LOD. I implemented this and it worked correctly, switching between the two LOD subgraphs correctly.

To test it I modified your example so that it could do either the incorrect independent LOD approach, or the correct single LOD:

    if (settings.independentLOD)
    {
        auto group = vsg::Group::create();

        auto lod = vsg::LOD::create();
        auto bb = vsg::visit<vsg::ComputeBounds>(node).bounds;
        lod->bound = vsg::dsphere((bb.min + bb.max) * 0.5, vsg::length(bb.max - bb.min));
        lod->addChild(vsg::LOD::Child{settings.lodMinScreenRatio+1, node});
        group->addChild(lod);

        auto lod2 = vsg::LOD::create();
        auto bb2 = vsg::visit<vsg::ComputeBounds>(node2).bounds;
        lod2->bound = vsg::dsphere((bb2.min + bb2.max) * 0.5, vsg::length(bb2.max - bb2.min));
        lod2->addChild(vsg::LOD::Child{settings.lodMinScreenRatio, node2});
        group->addChild(lod2);

        scene->addChild(group);

    }
    else
    {
        vsg::ComputeBounds cb;
        node->accept(cb);
        node2->accept(cb);

        auto bb = cb.bounds;

        auto lod = vsg::LOD::create();
        lod->bound = vsg::dsphere((bb.min + bb.max) * 0.5, vsg::length(bb.max - bb.min));
        lod->addChild(vsg::LOD::Child{settings.lodMinScreenRatio, node});
        lod->addChild(vsg::LOD::Child{0.0, node2});

        scene->addChild(lod);
    }

The VSG by design does not support the independent LOD approach, the independent approach requires more memory and more CPU overhead for the same end result. The VSG strives for providing best performance rather than offering lots of ways of creating less inefficient scene graphs.

Not supporting the less efficient approach isn't a design flaw, it's a design strength.

robertosfield avatar Apr 17 '25 09:04 robertosfield

Thank you for this analysis. When using

 lod->addChild(vsg::LOD::Child{0.0, node2});

node2 is completely hidden here. After switching to

 lod->addChild(vsg::LOD::Child{settings.lodMinScreenRatio+1, node});
 lod->addChild(vsg::LOD::Child{settings.lodMinScreenRatio, node2});

I can see that node2 is replaced by node1 when starting up.

rhabacker avatar Apr 17 '25 10:04 rhabacker

This combination

 lod->addChild(vsg::LOD::Child{settings.lodMinScreenRatio+1, node});
 lod->addChild(vsg::LOD::Child{0.0, node2});

shows the replacement, but this one not

 lod->addChild(vsg::LOD::Child{settings.lodMinScreenRatio, node});
 lod->addChild(vsg::LOD::Child{0.0, node2});

It only shows node, not node2.

rhabacker avatar Apr 17 '25 11:04 rhabacker

That's because the far plane in the vsglod is set so near that the scene is clipped out before the transition happens - I know because I hit upon this issue myself. The transition will still be happening fine.

robertosfield avatar Apr 17 '25 12:04 robertosfield

I can confirm this and have extended the far plane in the example at https://github.com/vsg-dev/vsgExamples/pull/352 to be able to see the transition.

rhabacker avatar Apr 17 '25 14:04 rhabacker