diff --git a/CHANGELOG.md b/CHANGELOG.md index fac0b4ac0..fad734ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch Feature #4887: Add openmw command option to set initial random seed + Feature #4890: Make Distant Terrain configurable Task #4686: Upgrade media decoder to a more current FFmpeg API 0.45.0 diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c1f2b390f..1bbed3740 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -244,7 +244,7 @@ namespace MWRender int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; if (Settings::Manager::getBool("object shadows", "Shadows")) shadowCastingTraversalMask |= Mask_Object; - + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); @@ -283,12 +283,29 @@ namespace MWRender mDistantFog = Settings::Manager::getBool("use distant fog", "Fog"); mDistantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); - mTerrainStorage = new TerrainStorage(mResourceSystem, Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"), - Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), Settings::Manager::getString("terrain specular map pattern", "Shaders"), - Settings::Manager::getBool("auto use terrain specular maps", "Shaders")); + + const std::string normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); + const std::string heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); + const std::string specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); + const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); + const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); + + mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); if (mDistantTerrain) - mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + { + const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); + int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); + compMapPower = std::max(-3, compMapPower); + float compMapLevel = pow(2, compMapPower); + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); + float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); + maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); + mTerrain.reset(new Terrain::QuadTreeWorld( + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, + compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + } else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index b23b0b76c..27d87b031 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -28,6 +28,8 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mTextureManager(textureManager) , mCompositeMapRenderer(renderer) , mCompositeMapSize(512) + , mCompositeMapLevel(1.f) + , mMaxCompGeometrySize(1.f) , mCullingActive(true) { @@ -68,11 +70,6 @@ void ChunkManager::releaseGLObjects(osg::State *state) mBufferCache.releaseGLObjects(state); } -void ChunkManager::setCullingActive(bool active) -{ - mCullingActive = active; -} - osg::ref_ptr ChunkManager::createCompositeMapRTT() { osg::ref_ptr texture = new osg::Texture2D; @@ -89,7 +86,7 @@ osg::ref_ptr ChunkManager::createCompositeMapRTT() void ChunkManager::createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& compositeMap) { - if (chunkSize > 1.f) + if (chunkSize > mMaxCompGeometrySize) { createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); @@ -199,7 +196,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags)); - bool useCompositeMap = chunkSize >= 1.f; + bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; for (unsigned int i=0; i getChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags); + void setCullingActive(bool active) { mCullingActive = active; } + void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } + void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } + void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void clearCache() override; void releaseGLObjects(osg::State* state) override; - void setCullingActive(bool active); - private: osg::ref_ptr createChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags); @@ -56,6 +59,8 @@ namespace Terrain BufferCache mBufferCache; unsigned int mCompositeMapSize; + float mCompositeMapLevel; + float mMaxCompGeometrySize; bool mCullingActive; }; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index b486f8d60..141f7073c 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -75,8 +75,9 @@ namespace Terrain class DefaultLodCallback : public LodCallback { public: - DefaultLodCallback(float minSize) - : mMinSize(minSize) + DefaultLodCallback(float factor, float minSize) + : mFactor(factor) + , mMinSize(minSize) { } @@ -84,12 +85,13 @@ public: { float dist = distanceToBox(node->getBoundingBox(), eyePoint); int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); - int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize))); + int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); return nativeLodLevel <= lodLevel; } private: + float mFactor; float mMinSize; }; @@ -123,8 +125,9 @@ private: class QuadTreeBuilder { public: - QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float minSize) + QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float lodFactor, float minSize) : mStorage(storage) + , mLodFactor(lodFactor) , mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f) , mMinSize(minSize) , mViewDataMap(viewDataMap) @@ -146,7 +149,7 @@ public: mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY)); mRootNode->setViewDataMap(mViewDataMap); - mRootNode->setLodCallback(new DefaultLodCallback(mMinSize)); + mRootNode->setLodCallback(new DefaultLodCallback(mLodFactor, mMinSize)); addChildren(mRootNode); mRootNode->initNeighbours(); @@ -224,6 +227,7 @@ public: private: Terrain::Storage* mStorage; + float mLodFactor; float mMinX, mMaxX, mMinY, mMaxY; float mMinSize; ViewDataMap* mViewDataMap; @@ -231,13 +235,19 @@ private: osg::ref_ptr mRootNode; }; -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask) +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) , 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); + + mChunkManager->setCompositeMapSize(compMapResolution); + mChunkManager->setCompositeMapLevel(compMapLevel); + mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); } QuadTreeWorld::~QuadTreeWorld() @@ -287,7 +297,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) @@ -302,7 +335,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 @@ -315,13 +348,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; @@ -330,10 +367,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) @@ -378,7 +412,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) { @@ -404,7 +438,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() return; const float minSize = 1/8.f; - QuadTreeBuilder builder(mStorage, mViewDataMap.get(), minSize); + QuadTreeBuilder builder(mStorage, mViewDataMap.get(), mLodFactor, minSize); builder.build(); mRootNode = builder.getRootNode(); @@ -435,7 +469,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()); } } @@ -454,7 +488,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 c166a9cb1..f724c44b1 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=~0, int borderMask=0); + 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(); void accept(osg::NodeVisitor& nv); @@ -44,6 +45,8 @@ 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 687f55e5e..e15a0035d 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -12,7 +12,7 @@ Controls whether the engine will use paging and LOD algorithms to load the terra Otherwise, only the terrain of the surrounding cells is loaded. .. note:: - When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so + When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so that you can actually see the additional terrain. To avoid frame drops as the player moves around, nearby terrain pages are always preloaded in the background, @@ -23,3 +23,81 @@ will still be controlled by cell preloading settings. 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 +---------- + +:Type: float +:Range: >0 +:Default: 1.0 + +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: it 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 +------------------- + +:Type: integer +:Range: >= -3 +:Default: 0 + +Controls at which minimum size (in 2^value cell units) terrain chunks will start to use a composite map instead of the high-detail textures. +With value -3 composite maps are used everywhere. + +A composite map is a pre-rendered texture that contains all the texture layers combined. +Note that resolution of composite maps is currently always fixed at 'composite map resolution', +regardless of the resolution of the underlying terrain textures. +If high-detail texture replacers are used, probably it is worth to increase 'composite map resolution' setting value. + +composite map resolution +------------------------ + +:Type: integer +:Range: >0 +:Default: 512 + +Controls the resolution of composite maps. Larger values result in increased detail, +but may take longer to prepare and thus could result in longer loading times and an increased chance of frame drops during play. +As with most other texture resolution settings, it's most efficient to use values that are powers of two. + +An easy way to observe changes to loading time is to load a save in an interior next to an exterior door +(so it will start preloding terrain) and watch how long it takes for the 'Composite' counter on the F4 panel to fall to zero. + +max composite geometry size +--------------------------- + +:Type: float +:Range: >=1.0 +:Default: 4.0 + +Controls the maximum size of simple composite geometry chunk in cell units. With small values there will more draw calls and small textures, +but higher values create more overdraw (not every texture layer is used everywhere). diff --git a/files/settings-default.cfg b/files/settings-default.cfg index db3d7e75b..d3f5c6a2e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -90,6 +90,22 @@ 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 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 + +# Controls the resolution of composite maps. +composite map resolution = 512 + +# Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. +max composite geometry size = 4.0 + [Fog] # If true, use extended fog parameters for distant terrain not controlled by @@ -236,7 +252,7 @@ barter disposition change is permanent = false # Uses the MCP formula (damage * (strength / 40)) to factor Strength into hand-to-hand combat. # (0 means it does not factor it in, 1 means it factors into werewolves damage calculation and -# 2 means werewolves are ignored) +# 2 means werewolves are ignored) strength influences hand to hand = 0 # Render holstered weapons (with quivers and scabbards), requires modded assets