mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 07:45:39 +00:00
improves groundcover view distance (#3219)
This PR aims to solve all issues with `Groundcover` view distance handling in a satisfying way while preserving other optimisations that benefit other features. The main idea here is not to rely on `ViewData` updates for distance culling calculations because we can not accurately determine distance against lazily updated views. Instead, we perform an accurate measurement in a cull callback we can run every frame in `Groundcover` itself. While we do have to add some code to handle this feature, it is quite loosely coupled code that could be useful elsewhere in the future. These changes should address a performance regression @akortunov experienced.
This commit is contained in:
parent
b6718ecb10
commit
5f1bf89369
11 changed files with 98 additions and 65 deletions
|
@ -1,5 +1,6 @@
|
|||
#include "groundcover.hpp"
|
||||
|
||||
#include <osg/ComputeBoundsVisitor>
|
||||
#include <osg/AlphaFunc>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/Geometry>
|
||||
|
@ -7,6 +8,8 @@
|
|||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/nodecallback.hpp>
|
||||
#include <components/terrain/quadtreenode.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||
|
@ -106,6 +109,20 @@ namespace MWRender
|
|||
float mDensity = 0.f;
|
||||
};
|
||||
|
||||
class ViewDistanceCallback : public SceneUtil::NodeCallback<ViewDistanceCallback>
|
||||
{
|
||||
public:
|
||||
ViewDistanceCallback(float dist, const osg::BoundingBox& box) : mViewDistance(dist), mBox(box) {}
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance)
|
||||
traverse(node, nv);
|
||||
}
|
||||
private:
|
||||
float mViewDistance;
|
||||
osg::BoundingBox mBox;
|
||||
};
|
||||
|
||||
inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound)
|
||||
{
|
||||
osg::Vec2f size = maxBound - minBound;
|
||||
|
@ -122,8 +139,9 @@ namespace MWRender
|
|||
|
||||
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
if (lod > getMaxLodLevel())
|
||||
return nullptr;
|
||||
GroundcoverChunkId id = std::make_tuple(center, size);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||
if (obj)
|
||||
return static_cast<osg::Node*>(obj.get());
|
||||
|
@ -137,12 +155,13 @@ namespace MWRender
|
|||
}
|
||||
}
|
||||
|
||||
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density)
|
||||
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance)
|
||||
: GenericResourceManager<GroundcoverChunkId>(nullptr)
|
||||
, mSceneManager(sceneManager)
|
||||
, mDensity(density)
|
||||
, mStateset(new osg::StateSet)
|
||||
{
|
||||
setViewDistance(viewDistance);
|
||||
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties
|
||||
// Force a unified alpha handling instead of data from meshes
|
||||
osg::ref_ptr<osg::AlphaFunc> alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f);
|
||||
|
@ -220,10 +239,16 @@ namespace MWRender
|
|||
group->addChild(node);
|
||||
}
|
||||
|
||||
osg::ComputeBoundsVisitor cbv;
|
||||
group->accept(cbv);
|
||||
osg::BoundingBox box = cbv.getBoundingBox();
|
||||
box = osg::BoundingBox(box._min+worldCenter, box._max+worldCenter);
|
||||
group->addCullCallback(new ViewDistanceCallback(getViewDistance(), box));
|
||||
|
||||
group->setStateSet(mStateset);
|
||||
group->setNodeMask(Mask_Groundcover);
|
||||
if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP)
|
||||
group->setCullCallback(new SceneUtil::LightListCallback);
|
||||
group->addCullCallback(new SceneUtil::LightListCallback);
|
||||
mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate);
|
||||
mSceneManager->shareState(group);
|
||||
group->getBound();
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace MWRender
|
|||
class Groundcover : public Resource::GenericResourceManager<GroundcoverChunkId>, public Terrain::QuadTreeWorld::ChunkManager
|
||||
{
|
||||
public:
|
||||
Groundcover(Resource::SceneManager* sceneManager, float density);
|
||||
Groundcover(Resource::SceneManager* sceneManager, float density, float viewDistance);
|
||||
~Groundcover() = default;
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
|
|
|
@ -439,11 +439,9 @@ namespace MWRender
|
|||
float density = Settings::Manager::getFloat("density", "Groundcover");
|
||||
density = std::clamp(density, 0.f, 1.f);
|
||||
|
||||
mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density));
|
||||
mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density, groundcoverDistance));
|
||||
static_cast<Terrain::QuadTreeWorld*>(mTerrain.get())->addChunkManager(mGroundcover.get());
|
||||
mResourceSystem->addResourceManager(mGroundcover.get());
|
||||
|
||||
mGroundcover->setViewDistance(groundcoverDistance);
|
||||
}
|
||||
|
||||
mStateUpdater = new StateUpdater;
|
||||
|
|
|
@ -12,8 +12,8 @@ namespace
|
|||
template <typename IndexArrayType>
|
||||
osg::ref_ptr<IndexArrayType> createIndexBuffer(unsigned int flags, unsigned int verts)
|
||||
{
|
||||
// LOD level n means every 2^n-th vertex is kept
|
||||
size_t lodLevel = (flags >> (4*4));
|
||||
// LOD level n means every 2^n-th vertex is kept, but we currently handle LOD elsewhere.
|
||||
size_t lodLevel = 0;//(flags >> (4*4));
|
||||
|
||||
size_t lodDeltas[4];
|
||||
for (int i=0; i<4; ++i)
|
||||
|
|
|
@ -52,6 +52,9 @@ struct FindChunkTemplate
|
|||
|
||||
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
// Override lod with the vertexLodMod adjusted value.
|
||||
// TODO: maybe we can refactor this code by moving all vertexLodMod code into this class.
|
||||
lod = static_cast<unsigned char>(lodFlags >> (4*4));
|
||||
ChunkId id = std::make_tuple(center, lod, lodFlags);
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||
if (obj)
|
||||
|
|
|
@ -10,6 +10,29 @@
|
|||
namespace Terrain
|
||||
{
|
||||
|
||||
float distance(const osg::BoundingBox& box, const osg::Vec3f& v)
|
||||
{
|
||||
if (box.contains(v))
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
osg::Vec3f maxDist(0,0,0);
|
||||
if (v.x() < box.xMin())
|
||||
maxDist.x() = box.xMin() - v.x();
|
||||
else if (v.x() > box.xMax())
|
||||
maxDist.x() = v.x() - box.xMax();
|
||||
if (v.y() < box.yMin())
|
||||
maxDist.y() = box.yMin() - v.y();
|
||||
else if (v.y() > box.yMax())
|
||||
maxDist.y() = v.y() - box.yMax();
|
||||
if (v.z() < box.zMin())
|
||||
maxDist.z() = box.zMin() - v.z();
|
||||
else if (v.z() > box.zMax())
|
||||
maxDist.z() = v.z() - box.zMax();
|
||||
return maxDist.length();
|
||||
}
|
||||
}
|
||||
|
||||
ChildDirection reflect(ChildDirection dir, Direction dir2)
|
||||
{
|
||||
assert(dir != Root);
|
||||
|
@ -78,25 +101,7 @@ QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir)
|
|||
float QuadTreeNode::distance(const osg::Vec3f& v) const
|
||||
{
|
||||
const osg::BoundingBox& box = getBoundingBox();
|
||||
if (box.contains(v))
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
osg::Vec3f maxDist(0,0,0);
|
||||
if (v.x() < box.xMin())
|
||||
maxDist.x() = box.xMin() - v.x();
|
||||
else if (v.x() > box.xMax())
|
||||
maxDist.x() = v.x() - box.xMax();
|
||||
if (v.y() < box.yMin())
|
||||
maxDist.y() = box.yMin() - v.y();
|
||||
else if (v.y() > box.yMax())
|
||||
maxDist.y() = v.y() - box.yMax();
|
||||
if (v.z() < box.zMin())
|
||||
maxDist.z() = box.zMin() - v.z();
|
||||
else if (v.z() > box.zMax())
|
||||
maxDist.z() = v.z() - box.zMax();
|
||||
return maxDist.length();
|
||||
}
|
||||
return Terrain::distance(box, v);
|
||||
}
|
||||
|
||||
void QuadTreeNode::initNeighbours()
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace Terrain
|
|||
|
||||
class ViewData;
|
||||
|
||||
float distance(const osg::BoundingBox&, const osg::Vec3f& v);
|
||||
|
||||
class QuadTreeNode : public osg::Group
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -40,9 +40,9 @@ namespace
|
|||
return 1 << depth;
|
||||
}
|
||||
|
||||
int Log2( unsigned int n )
|
||||
unsigned int Log2( unsigned int n )
|
||||
{
|
||||
int targetlevel = 0;
|
||||
unsigned int targetlevel = 0;
|
||||
while (n >>= 1) ++targetlevel;
|
||||
return targetlevel;
|
||||
}
|
||||
|
@ -78,16 +78,18 @@ public:
|
|||
if (intersects)
|
||||
return Deeper;
|
||||
}
|
||||
|
||||
dist = std::max(0.f, dist + mDistanceModifier);
|
||||
|
||||
if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded
|
||||
return StopTraversal;
|
||||
|
||||
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
|
||||
int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor)));
|
||||
|
||||
return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper;
|
||||
return getNativeLodLevel(node, mMinSize) <= convertDistanceToLodLevel(dist, mMinSize, mFactor) ? StopTraversalAndUse : Deeper;
|
||||
}
|
||||
static unsigned int getNativeLodLevel(const QuadTreeNode* node, float minSize)
|
||||
{
|
||||
return Log2(static_cast<unsigned int>(node->getSize()/minSize));
|
||||
}
|
||||
static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor)
|
||||
{
|
||||
return Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*minSize*factor)));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -278,7 +280,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
|
|||
, mViewDistance(std::numeric_limits<float>::max())
|
||||
, mMinSize(1/8.f)
|
||||
, mDebugTerrainChunks(debugChunks)
|
||||
, mRevalidateDistance(0.f)
|
||||
{
|
||||
mChunkManager->setCompositeMapSize(compMapResolution);
|
||||
mChunkManager->setCompositeMapLevel(compMapLevel);
|
||||
|
@ -296,13 +297,14 @@ QuadTreeWorld::~QuadTreeWorld()
|
|||
{
|
||||
}
|
||||
|
||||
/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set.
|
||||
/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the vertex data set,
|
||||
/// NOT relative to mMinSize as is the case with node LODs.
|
||||
unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod)
|
||||
{
|
||||
int lod = Log2(int(node->getSize()));
|
||||
unsigned int vertexLod = DefaultLodCallback::getNativeLodLevel(node, 1);
|
||||
if (vertexLodMod > 0)
|
||||
{
|
||||
lod = std::max(0, lod-vertexLodMod);
|
||||
vertexLod = static_cast<unsigned int>(std::max(0, static_cast<int>(vertexLod)-vertexLodMod));
|
||||
}
|
||||
else if (vertexLodMod < 0)
|
||||
{
|
||||
|
@ -313,13 +315,13 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod)
|
|||
size *= 2;
|
||||
vertexLodMod = std::min(0, vertexLodMod+1);
|
||||
}
|
||||
lod += std::abs(vertexLodMod);
|
||||
vertexLod += std::abs(vertexLodMod);
|
||||
}
|
||||
return lod;
|
||||
return vertexLod;
|
||||
}
|
||||
|
||||
/// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly
|
||||
unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd)
|
||||
unsigned int getLodFlags(QuadTreeNode* node, unsigned int ourVertexLod, int vertexLodMod, const ViewData* vd)
|
||||
{
|
||||
unsigned int lodFlags = 0;
|
||||
for (unsigned int i=0; i<4; ++i)
|
||||
|
@ -332,40 +334,38 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const
|
|||
// our detail and the neighbour would handle stitching by itself.
|
||||
while (neighbour && !vd->contains(neighbour))
|
||||
neighbour = neighbour->getParent();
|
||||
int lod = 0;
|
||||
unsigned int lod = 0;
|
||||
if (neighbour)
|
||||
lod = getVertexLod(neighbour, vertexLodMod);
|
||||
|
||||
if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
|
||||
if (lod <= ourVertexLod) // We only need to worry about neighbours less detailed than we are -
|
||||
lod = 0; // neighbours with more detail will do the stitching themselves
|
||||
// Use 4 bits for each LOD delta
|
||||
if (lod > 0)
|
||||
{
|
||||
lodFlags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
|
||||
lodFlags |= (lod - ourVertexLod) << (4*i);
|
||||
}
|
||||
}
|
||||
// Use the remaining bits for our vertex LOD
|
||||
lodFlags |= (ourVertexLod << (4*4));
|
||||
return lodFlags;
|
||||
}
|
||||
|
||||
void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance)
|
||||
void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile)
|
||||
{
|
||||
if (!vd->hasChanged() && entry.mRenderingNode)
|
||||
return;
|
||||
|
||||
int ourLod = getVertexLod(entry.mNode, mVertexLodMod);
|
||||
|
||||
if (vd->hasChanged())
|
||||
{
|
||||
unsigned int ourVertexLod = getVertexLod(entry.mNode, mVertexLodMod);
|
||||
// have to recompute the lodFlags in case a neighbour has changed LOD.
|
||||
unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, mVertexLodMod, vd);
|
||||
unsigned int lodFlags = getLodFlags(entry.mNode, ourVertexLod, mVertexLodMod, vd);
|
||||
if (lodFlags != entry.mLodFlags)
|
||||
{
|
||||
entry.mRenderingNode = nullptr;
|
||||
entry.mLodFlags = lodFlags;
|
||||
}
|
||||
// have to revalidate chunks within a custom view distance.
|
||||
if (mRevalidateDistance && entry.mNode->distance(vd->getViewPoint()) <= mRevalidateDistance + reuseDistance)
|
||||
entry.mRenderingNode = nullptr;
|
||||
}
|
||||
|
||||
if (!entry.mRenderingNode)
|
||||
|
@ -378,9 +378,7 @@ void QuadTreeWorld::loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float
|
|||
|
||||
for (QuadTreeWorld::ChunkManager* m : mChunkManagers)
|
||||
{
|
||||
if (mRevalidateDistance && m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance)
|
||||
continue;
|
||||
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile);
|
||||
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, vd->getViewPoint(), compile);
|
||||
if (n) pat->addChild(n);
|
||||
}
|
||||
entry.mRenderingNode = pat;
|
||||
|
@ -462,7 +460,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
|
|||
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
|
||||
{
|
||||
ViewDataEntry& entry = vd->getEntry(i);
|
||||
loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false, mViewDataMap->getReuseDistance());
|
||||
loadRenderingNode(entry, vd, cellWorldSize, mActiveGrid, false);
|
||||
entry.mRenderingNode->accept(nv);
|
||||
}
|
||||
|
||||
|
@ -541,12 +539,11 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::
|
|||
reporter.addTotal(progressTotal);
|
||||
}
|
||||
|
||||
const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier));
|
||||
for (unsigned int i=startEntry; i<vd->getNumEntries() && !abort; ++i)
|
||||
{
|
||||
ViewDataEntry& entry = vd->getEntry(i);
|
||||
|
||||
loadRenderingNode(entry, vd, cellWorldSize, grid, true, reuseDistance);
|
||||
loadRenderingNode(entry, vd, cellWorldSize, grid, true);
|
||||
if (pass==0) reporter.addProgress(entry.mNode->getSize());
|
||||
entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass
|
||||
}
|
||||
|
@ -584,7 +581,7 @@ void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m)
|
|||
mChunkManagers.push_back(m);
|
||||
mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask());
|
||||
if (m->getViewDistance())
|
||||
mRevalidateDistance = std::max(m->getViewDistance(), mRevalidateDistance);
|
||||
m->setMaxLodLevel(DefaultLodCallback::convertDistanceToLodLevel(m->getViewDistance() + mViewDataMap->getReuseDistance(), mMinSize, mLodFactor));
|
||||
}
|
||||
|
||||
void QuadTreeWorld::rebuildViews()
|
||||
|
|
|
@ -56,14 +56,19 @@ namespace Terrain
|
|||
|
||||
void setViewDistance(float viewDistance) { mViewDistance = viewDistance; }
|
||||
float getViewDistance() const { return mViewDistance; }
|
||||
|
||||
// Automatically set by addChunkManager based on getViewDistance()
|
||||
unsigned int getMaxLodLevel() const { return mMaxLodLevel; }
|
||||
void setMaxLodLevel(unsigned int level) { mMaxLodLevel = level; }
|
||||
private:
|
||||
float mViewDistance = 0.f;
|
||||
unsigned int mMaxLodLevel = ~0u;
|
||||
};
|
||||
void addChunkManager(ChunkManager*);
|
||||
|
||||
private:
|
||||
void ensureQuadTreeBuilt();
|
||||
void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile, float reuseDistance);
|
||||
void loadRenderingNode(ViewDataEntry& entry, ViewData* vd, float cellWorldSize, const osg::Vec4i &gridbounds, bool compile);
|
||||
|
||||
osg::ref_ptr<RootNode> mRootNode;
|
||||
|
||||
|
@ -79,7 +84,6 @@ namespace Terrain
|
|||
float mMinSize;
|
||||
bool mDebugTerrainChunks;
|
||||
std::unique_ptr<DebugChunkManager> mDebugChunkManager;
|
||||
float mRevalidateDistance;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -145,7 +145,6 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo
|
|||
}
|
||||
vd->setViewPoint(viewPoint);
|
||||
vd->setActiveGrid(activeGrid);
|
||||
vd->setChanged(true);
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace Terrain
|
|||
double getLastUsageTimeStamp() const { return mLastUsageTimeStamp; }
|
||||
void setLastUsageTimeStamp(double timeStamp) { mLastUsageTimeStamp = timeStamp; }
|
||||
|
||||
/// Indicates at least one mNode of mEntries has changed or the view point has moved beyond mReuseDistance.
|
||||
/// Indicates at least one mNode of mEntries has changed.
|
||||
/// @note Such changes may necessitate a revalidation of cached mRenderingNodes elsewhere depending
|
||||
/// on the parameters that affect the creation of mRenderingNode.
|
||||
bool hasChanged() const { return mChanged; }
|
||||
|
|
Loading…
Reference in a new issue