diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4a96960646..a14039d211 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -299,7 +299,8 @@ namespace MWRender compMapPower = std::max(-3, compMapPower); float compMapLevel = pow(2, compMapPower); const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); - mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor)); + const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); + mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod)); } else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index f531e241f5..a0c353a730 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -233,11 +233,12 @@ private: osg::ref_ptr mRootNode; }; -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) +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) : World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) + , mVertexLodMod(vertexLodMod) { // No need for culling on the Drawable / Transform level as the quad tree performs the culling already. mChunkManager->setCullingActive(false); @@ -293,7 +294,30 @@ void traverseToCell(QuadTreeNode* node, ViewData* vd, int cellX, int cellY) } } -unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd) +/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. +unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) +{ + int lod = Log2(int(node->getSize())); + if (vertexLodMod > 0) + { + lod = std::max(0, lod-vertexLodMod); + } + else if (vertexLodMod < 0) + { + float size = node->getSize(); + // Stop to simplify at this level since with size = 1 the node already covers the whole cell and has getCellVertices() vertices. + while (size < 1) + { + size *= 2; + vertexLodMod = std::min(0, vertexLodMod+1); + } + lod += std::abs(vertexLodMod); + } + return lod; +} + +/// 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, ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -308,7 +332,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd) neighbour = neighbour->getParent(); int lod = 0; if (neighbour) - lod = Log2(int(neighbour->getSize())); + lod = getVertexLod(neighbour, vertexLodMod); if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - lod = 0; // neighbours with more detail will do the stitching themselves @@ -321,13 +345,17 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd) return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunkManager) +void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, ChunkManager* chunkManager) { + if (!vd->hasChanged() && entry.mRenderingNode) + return; + + int ourLod = getVertexLod(entry.mNode, vertexLodMod); + if (vd->hasChanged()) { // have to recompute the lodFlags in case a neighbour has changed LOD. - int ourLod = Log2(int(entry.mNode->getSize())); - unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vd); + unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vertexLodMod, vd); if (lodFlags != entry.mLodFlags) { entry.mRenderingNode = nullptr; @@ -336,10 +364,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunk } if (!entry.mRenderingNode) - { - int ourLod = Log2(int(entry.mNode->getSize())); entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags); - } } void QuadTreeWorld::accept(osg::NodeVisitor &nv) @@ -384,7 +409,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); if (entry.mVisible) { @@ -441,7 +466,7 @@ void QuadTreeWorld::cacheCell(View *view, int x, int y) for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); } } @@ -460,7 +485,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint) for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); } } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 9c47057c42..252f930fa4 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -19,7 +19,8 @@ namespace Terrain class QuadTreeWorld : public Terrain::World { 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); + 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); + ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -45,6 +46,7 @@ namespace Terrain OpenThreads::Mutex mQuadTreeMutex; bool mQuadTreeBuilt; float mLodFactor; + int mVertexLodMod; }; } diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 9d4ac93795..e14bb1e628 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -24,6 +24,19 @@ The distant terrain engine is currently considered experimental and may receive updates and/or further configuration options in the future. The glaring omission of non-terrain objects in the distance somewhat limits this setting's usefulness. +vertex lod mod +-------------- + +:Type: integer +:Range: any +:Default: 0 + +Controls only the Vertex LOD of the terrain. The amount of terrain chunks and the detail of composite maps is left unchanged. + +Must be changed in increments of 1. Each increment will double (for positive values) or halve (for negative values) the number of vertices rendered. For example: -2 means 4x reduced detail, +3 means 8x increased detail. + +Note this setting will typically not affect near terrain. When set to increase detail, the detail of near terrain can not be increased because the detail is simply not there in the data files, and when set to reduce detail, the detail of near terrain will not be reduced because it was already less detailed than the far terrain (in view relative terms) to begin with. + lod factor ---------- @@ -33,6 +46,12 @@ lod factor Controls the level of detail if distant terrain is enabled. Higher values increase detail at the cost of performance, lower values reduce detail but increase performance. +Note this also changes how the Quad Tree is split. Increasing detail with this setting results in the visible terrain being divided into more chunks, where as reducing detail with this setting would reduce the number of chunks. + +Fewer terrain chunks is faster for rendering, but on the other hand a larger proportion of the entire terrain must be rebuilt when LOD levels change as the camera moves. This could result in frame drops if moving across the map at high speed. + +For this reason, it is not recommended to change this setting if you want to change the LOD. If you want to do that, first try using the 'vertex lod mod' setting to configure the detail of the terrain outlines to your liking and then use 'composite map resolution' to configure the texture detail to your liking. But these settings can only be changed in multiples of two, so you may want to adjust 'lod factor' afterwards for even more fine-tuning. + composite map level ------------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 99d4a4eb59..92b76146d0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -90,9 +90,12 @@ pointers cache size = 40 # If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells distant terrain = false -# Controls the level of detail for distant terrain. Lower values = less detail, higher = more detail, should be > 0 +# Controls how the Quad Tree is split. This affects Vertex LOD, Texture LOD and load times. Values > 1 increase detail, values < 1 reduce detail. lod factor = 1.0 +# Controls only the Vertex LOD. Change in increments of 1, each change doubles (or halves) the number of vertices. Values > 0 increase detail, values < 0 reduce detail. +vertex lod mod = 0 + # Controls when the distant terrain will flip to composited textures instead of high-detail textures, should be >= -3. # Higher value is more detailed textures. composite map level = 0