Merge pull request #2201 from akortunov/optimize_terrain

Optimize terrain
pull/541/head
Bret Curtis 6 years ago committed by GitHub
commit d56733149c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -58,6 +58,7 @@
Feature #4887: Add openmw command option to set initial random seed Feature #4887: Add openmw command option to set initial random seed
Feature #4890: Make Distant Terrain configurable Feature #4890: Make Distant Terrain configurable
Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption
0.45.0 0.45.0
------ ------

@ -371,8 +371,10 @@ namespace MWRender
mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mNearClip = Settings::Manager::getFloat("near clip", "Camera");
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); float fov = Settings::Manager::getFloat("field of view", "Camera");
mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera"); mFieldOfView = std::min(std::max(1.f, fov), 179.f);
float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera");
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance); mStateUpdater->setFogEnd(mViewDistance);
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
@ -1198,6 +1200,12 @@ namespace MWRender
mUniformNear->set(mNearClip); mUniformNear->set(mNearClip);
mUniformFar->set(mViewDistance); mUniformFar->set(mViewDistance);
// Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear.
// Limit FOV here just for sure, otherwise viewing distance can be too high.
fov = std::min(mFieldOfView, 140.f);
float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f);
mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f));
} }
void RenderingManager::updateTextureFiltering() void RenderingManager::updateTextureFiltering()

@ -22,6 +22,15 @@ namespace MWRender
mResourceSystem->removeResourceManager(mLandManager.get()); mResourceSystem->removeResourceManager(mLandManager.get());
} }
bool TerrainStorage::hasData(int cellX, int cellY)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
const ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
return land != nullptr;
}
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)
{ {
minX = 0, minY = 0, maxX = 0, maxY = 0; minX = 0, minY = 0, maxX = 0, maxY = 0;

@ -23,6 +23,8 @@ namespace MWRender
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY); virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
virtual bool hasData(int cellX, int cellY) override;
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);

@ -189,7 +189,7 @@ osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Ve
geometry->setUseDisplayList(false); geometry->setUseDisplayList(false);
geometry->setUseVertexBufferObjects(true); geometry->setUseVertexBufferObjects(true);
if (chunkSize <= 2.f) if (chunkSize <= 1.f)
geometry->setLightListCallback(new SceneUtil::LightListCallback); geometry->setLightListCallback(new SceneUtil::LightListCallback);
unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1; unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1;

@ -76,6 +76,30 @@ QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir)
return mNeighbours[dir]; return mNeighbours[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();
}
}
void QuadTreeNode::initNeighbours() void QuadTreeNode::initNeighbours()
{ {
for (int i=0; i<4; ++i) for (int i=0; i<4; ++i)
@ -92,7 +116,7 @@ void QuadTreeNode::traverse(osg::NodeVisitor &nv)
ViewData* vd = getView(nv); ViewData* vd = getView(nv);
if ((mLodCallback && mLodCallback->isSufficientDetail(this, vd->getEyePoint())) || !getNumChildren()) if ((mLodCallback && mLodCallback->isSufficientDetail(this, distance(vd->getEyePoint()))) || !getNumChildren())
vd->add(this, true); vd->add(this, true);
else else
osg::Group::traverse(nv); osg::Group::traverse(nv);

@ -23,7 +23,7 @@ namespace Terrain
public: public:
virtual ~LodCallback() {} virtual ~LodCallback() {}
virtual bool isSufficientDetail(QuadTreeNode *node, const osg::Vec3f& eyePoint) = 0; virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0;
}; };
class ViewDataMap; class ViewDataMap;
@ -49,6 +49,8 @@ namespace Terrain
child->addParent(this); child->addParent(this);
}; };
float distance(const osg::Vec3f& v) const;
/// Returns our direction relative to the parent node, or Root if we are the root node. /// Returns our direction relative to the parent node, or Root if we are the root node.
ChildDirection getDirection() { return mDirection; } ChildDirection getDirection() { return mDirection; }

@ -40,33 +40,6 @@ namespace
return targetlevel; return targetlevel;
} }
float distanceToBox(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();
}
}
} }
namespace Terrain namespace Terrain
@ -81,9 +54,8 @@ public:
{ {
} }
virtual bool isSufficientDetail(QuadTreeNode* node, const osg::Vec3f& eyePoint) virtual bool isSufficientDetail(QuadTreeNode* node, float dist)
{ {
float dist = distanceToBox(node->getBoundingBox(), eyePoint);
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize)); int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor)));
@ -201,22 +173,40 @@ public:
node->setLodCallback(parent->getLodCallback()); node->setLodCallback(parent->getLodCallback());
node->setViewDataMap(mViewDataMap); node->setViewDataMap(mViewDataMap);
if (node->getSize() > mMinSize) if (center.x() - halfSize > mMaxX
|| center.x() + halfSize < mMinX
|| center.y() - halfSize > mMaxY
|| center.y() + halfSize < mMinY )
// Out of bounds of the actual terrain - this will happen because
// we rounded the size up to the next power of two
{ {
addChildren(node); // Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children.
return node; return node;
} }
// We arrived at a leaf // Do not add child nodes for default cells without data.
float minZ, maxZ; // size = 1 means that the single shape covers the whole cell.
mStorage->getMinMaxHeights(size, center, minZ, maxZ); if (node->getSize() == 1 && !mStorage->hasData(center.x()-0.5, center.y()-0.5))
return node;
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ),
osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ));
node->setBoundingBox(boundingBox);
return node; if (node->getSize() <= mMinSize)
{
// We arrived at a leaf
float minZ,maxZ;
if (mStorage->getMinMaxHeights(size, center, minZ, maxZ))
{
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ),
osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ));
node->setBoundingBox(boundingBox);
}
return node;
}
else
{
addChildren(node);
return node;
}
} }
osg::ref_ptr<RootNode> getRootNode() osg::ref_ptr<RootNode> getRootNode()
@ -236,11 +226,12 @@ private:
}; };
QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize)
: World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask)
, mViewDataMap(new ViewDataMap) , mViewDataMap(new ViewDataMap)
, mQuadTreeBuilt(false) , mQuadTreeBuilt(false)
, mLodFactor(lodFactor) , mLodFactor(lodFactor)
, mVertexLodMod(vertexLodMod) , mVertexLodMod(vertexLodMod)
, mViewDistance(std::numeric_limits<float>::max())
{ {
// No need for culling on the Drawable / Transform level as the quad tree performs the culling already. // No need for culling on the Drawable / Transform level as the quad tree performs the culling already.
mChunkManager->setCullingActive(false); mChunkManager->setCullingActive(false);
@ -256,7 +247,7 @@ QuadTreeWorld::~QuadTreeWorld()
} }
void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible) void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible, float maxDist)
{ {
if (!node->hasValidBounds()) if (!node->hasValidBounds())
return; return;
@ -264,14 +255,18 @@ void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallbac
if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
visible = visible && !static_cast<osgUtil::CullVisitor*>(nv)->isCulled(node->getBoundingBox()); visible = visible && !static_cast<osgUtil::CullVisitor*>(nv)->isCulled(node->getBoundingBox());
bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, eyePoint)) || !node->getNumChildren(); float dist = node->distance(eyePoint);
if (dist > maxDist)
return;
bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, dist)) || !node->getNumChildren();
if (stopTraversal) if (stopTraversal)
vd->add(node, visible); vd->add(node, visible);
else else
{ {
for (unsigned int i=0; i<node->getNumChildren(); ++i) for (unsigned int i=0; i<node->getNumChildren(); ++i)
traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible); traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible, maxDist);
} }
} }
@ -403,7 +398,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
traverseToCell(mRootNode.get(), vd, x,y); traverseToCell(mRootNode.get(), vd, x,y);
} }
else else
traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true); traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true, mViewDistance);
} }
else else
mRootNode->traverse(nv); mRootNode->traverse(nv);
@ -483,7 +478,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint, std::atomic<
ensureQuadTreeBuilt(); ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view); ViewData* vd = static_cast<ViewData*>(view);
traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false); traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false, mViewDistance);
for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i) for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i)
{ {
@ -502,5 +497,25 @@ void QuadTreeWorld::setDefaultViewer(osg::Object *obj)
mViewDataMap->setDefaultViewer(obj); mViewDataMap->setDefaultViewer(obj);
} }
void QuadTreeWorld::loadCell(int x, int y)
{
// fallback behavior only for undefined cells (every other is already handled in quadtree)
float dummy;
if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
TerrainGrid::loadCell(x,y);
else
World::loadCell(x,y);
}
void QuadTreeWorld::unloadCell(int x, int y)
{
// fallback behavior only for undefined cells (every other is already handled in quadtree)
float dummy;
if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
TerrainGrid::unloadCell(x,y);
else
World::unloadCell(x,y);
}
} }

@ -2,6 +2,7 @@
#define COMPONENTS_TERRAIN_QUADTREEWORLD_H #define COMPONENTS_TERRAIN_QUADTREEWORLD_H
#include "world.hpp" #include "world.hpp"
#include "terraingrid.hpp"
#include <OpenThreads/Mutex> #include <OpenThreads/Mutex>
@ -16,7 +17,7 @@ namespace Terrain
class ViewDataMap; class ViewDataMap;
/// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times. /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times.
class QuadTreeWorld : public Terrain::World class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell)
{ {
public: public:
QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize);
@ -27,7 +28,13 @@ namespace Terrain
virtual void enable(bool enabled); virtual void enable(bool enabled);
virtual void setViewDistance(float distance) { mViewDistance = distance; }
void cacheCell(View *view, int x, int y); void cacheCell(View *view, int x, int y);
/// @note Not thread safe.
virtual void loadCell(int x, int y);
/// @note Not thread safe.
virtual void unloadCell(int x, int y);
View* createView(); View* createView();
void preload(View* view, const osg::Vec3f& eyePoint, std::atomic<bool>& abort); void preload(View* view, const osg::Vec3f& eyePoint, std::atomic<bool>& abort);
@ -47,6 +54,7 @@ namespace Terrain
bool mQuadTreeBuilt; bool mQuadTreeBuilt;
float mLodFactor; float mLodFactor;
int mVertexLodMod; int mVertexLodMod;
float mViewDistance;
}; };
} }

@ -28,6 +28,14 @@ namespace Terrain
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;
/// Return true if there is land data for this cell
/// May be overriden for a faster implementation
virtual bool hasData(int cellX, int cellY)
{
float dummy;
return getMinMaxHeights(1, osg::Vec2f(cellX+0.5, cellY+0.5), dummy, dummy);
}
/// Get the minimum and maximum heights of a terrain region. /// Get the minimum and maximum heights of a terrain region.
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
/// Larger chunks can simply merge AABB of children. /// Larger chunks can simply merge AABB of children.

@ -5,6 +5,7 @@
#include <osg/Group> #include <osg/Group>
#include "chunkmanager.hpp" #include "chunkmanager.hpp"
#include "compositemaprenderer.hpp"
namespace Terrain namespace Terrain
{ {
@ -61,6 +62,10 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
if (parent) if (parent)
parent->addChild(node); parent->addChild(node);
osg::UserDataContainer* udc = node->getUserDataContainer();
if (udc && udc->getUserData())
mCompositeMapRenderer->setImmediate(static_cast<CompositeMap*>(udc->getUserData()));
return node; return node;
} }
} }

@ -101,6 +101,8 @@ namespace Terrain
/// Set the default viewer (usually a Camera), used as viewpoint for any viewers that don't use their own viewpoint. /// Set the default viewer (usually a Camera), used as viewpoint for any viewers that don't use their own viewpoint.
virtual void setDefaultViewer(osg::Object* obj) {} virtual void setDefaultViewer(osg::Object* obj) {}
virtual void setViewDistance(float distance) {}
Storage* getStorage() { return mStorage; } Storage* getStorage() { return mStorage; }
protected: protected:

@ -94,7 +94,7 @@ field of view
------------- -------------
:Type: floating point :Type: floating point
:Range: 0-360 :Range: 1-179
:Default: 55.0 :Default: 55.0
Sets the camera field of view in degrees. Recommended values range from 30 degrees to 110 degrees. Sets the camera field of view in degrees. Recommended values range from 30 degrees to 110 degrees.
@ -109,7 +109,7 @@ first person field of view
-------------------------- --------------------------
:Type: floating point :Type: floating point
:Range: 0-360 :Range: 1-179
:Default: 55.0 :Default: 55.0
This setting controls the field of view for first person meshes such as the player's hands and held objects. This setting controls the field of view for first person meshes such as the player's hands and held objects.

Loading…
Cancel
Save