From ed20d869b4e8a8283bd726b82da2ca6a074c8afc Mon Sep 17 00:00:00 2001 From: bzzt Date: Thu, 13 Jun 2019 13:37:00 +0000 Subject: [PATCH 1/5] waterculling for both terrain --- apps/openmw/mwrender/renderingmanager.cpp | 8 ++++-- apps/openmw/mwrender/water.cpp | 28 ++++++++++++++++++ apps/openmw/mwrender/water.hpp | 4 +++ components/terrain/quadtreeworld.cpp | 35 +++++++++++++++++++++++ components/terrain/terraingrid.cpp | 11 +++++++ components/terrain/terraingrid.hpp | 3 +- components/terrain/world.cpp | 8 ++++++ components/terrain/world.hpp | 35 +++++++++++++++++++++++ 8 files changed, 129 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 31af55a44..c966a5d67 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -282,8 +282,6 @@ namespace MWRender mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem)); - mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); - DLLandFogStart = Settings::Manager::getFloat("distant land fog start", "Fog"); DLLandFogEnd = Settings::Manager::getFloat("distant land fog end", "Fog"); DLUnderwaterFogStart = Settings::Manager::getFloat("distant underwater fog start", "Fog"); @@ -322,6 +320,9 @@ namespace MWRender mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); + // water goes after terrain for correct waterculling order + mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); + mCamera.reset(new Camera(mViewer->getCamera())); mViewer->setLightingMode(osgViewer::View::NO_LIGHT); @@ -541,6 +542,8 @@ namespace MWRender void RenderingManager::enableTerrain(bool enable) { + if (!enable) + mWater->setCullCallback(nullptr); mTerrain->enable(enable); } @@ -740,6 +743,7 @@ namespace MWRender void RenderingManager::setWaterHeight(float height) { + mWater->setCullCallback(mTerrain->getHeightCullCallback(height, Mask_Water)); mWater->setHeight(height); mSky->setWaterHeight(height); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 6d230d36e..c9d16b728 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -436,6 +436,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem , mToggled(true) , mTop(0) , mInterior(false) + , mCullCallback(nullptr) { mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem)); @@ -466,6 +467,29 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem ico->add(mWaterNode); } +void Water::setCullCallback(osg::Callback* callback) +{ + if (mCullCallback) + { + mWaterNode->removeCullCallback(mCullCallback); + if (mReflection) + mReflection->removeCullCallback(mCullCallback); + if (mRefraction) + mRefraction->removeCullCallback(mCullCallback); + } + + mCullCallback = callback; + + if (callback) + { + mWaterNode->addCullCallback(callback); + if (mReflection) + mReflection->addCullCallback(callback); + if (mRefraction) + mRefraction->addCullCallback(callback); + } +} + osg::Uniform *Water::getRainIntensityUniform() { return mRainIntensityUniform.get(); @@ -491,6 +515,8 @@ void Water::updateWaterMaterial() mReflection = new Reflection(mInterior); mReflection->setWaterLevel(mTop); mReflection->setScene(mSceneRoot); + if (mCullCallback) + mReflection->addCullCallback(mCullCallback); mParent->addChild(mReflection); if (Settings::Manager::getBool("refraction", "Water")) @@ -498,6 +524,8 @@ void Water::updateWaterMaterial() mRefraction = new Refraction; mRefraction->setWaterLevel(mTop); mRefraction->setScene(mSceneRoot); + if (mCullCallback) + mRefraction->addCullCallback(mCullCallback); mParent->addChild(mRefraction); } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 5d99413c6..3787ef426 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -71,6 +71,8 @@ namespace MWRender float mTop; bool mInterior; + osg::Callback* mCullCallback; + osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); @@ -88,6 +90,8 @@ namespace MWRender const std::string& resourcePath); ~Water(); + void setCullCallback(osg::Callback* callback); + void listAssetsToPreload(std::vector& textures); void setEnabled(bool enabled); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 396ac1138..1d968ddc4 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -307,6 +307,38 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, C entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags); } +void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) +{ + if (!(cv->getTraversalMask() & callback->getCullMask())) + return; + float lowZ = FLT_MAX; + float highZ = callback->getHighZ(); + if (cv->getEyePoint().z() <= highZ || outofworld) + { + callback->setLowZ(-FLT_MAX); + return; + } + cv->pushCurrentMask(); + for (unsigned int i=0; igetNumEntries(); ++i) + { + ViewData::Entry& entry = vd->getEntry(i); + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getBoundingBox(); + float minZ = bb._min.z(); + if (minZ > highZ) + continue; + osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); + bb._min += ofs; bb._max += ofs; + bb._min.z() = highZ; + bb._max.z() = highZ; + if (cv->isCulled(bb)) + continue; + lowZ = minZ; + break; + } + callback->setLowZ(lowZ); + cv->popCurrentMask(); +} + void QuadTreeWorld::accept(osg::NodeVisitor &nv) { bool isCullVisitor = nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR; @@ -384,6 +416,9 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) entry.mRenderingNode->accept(nv); } + if (isCullVisitor) + updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !mGrid.empty()); + if (!isCullVisitor) vd->clear(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray. diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 7310846c2..a0e5e4718 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" @@ -80,6 +81,7 @@ void TerrainGrid::loadCell(int x, int y) mTerrainRoot->addChild(terrainNode); mGrid[std::make_pair(x,y)] = terrainNode; + updateWaterCulling(); } void TerrainGrid::unloadCell(int x, int y) @@ -94,6 +96,15 @@ void TerrainGrid::unloadCell(int x, int y) mTerrainRoot->removeChild(terrainNode); mGrid.erase(it); + updateWaterCulling(); +} + +void TerrainGrid::updateWaterCulling() +{ + osg::ComputeBoundsVisitor computeBoundsVisitor; + mTerrainRoot->accept(computeBoundsVisitor); + float lowZ = computeBoundsVisitor.getBoundingBox()._min.z(); + mHeightCullCallback->setLowZ(lowZ); } View *TerrainGrid::createView() diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index eb30fb97d..e633a258f 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -27,8 +27,9 @@ namespace Terrain View* createView(); - private: + protected: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); + void updateWaterCulling(); // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index da3bdb5c2..2d53f4090 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -19,6 +19,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst , mParent(parent) , mResourceSystem(resourceSystem) , mBorderVisible(false) + , mHeightCullCallback(new HeightCullCallback) { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); @@ -120,4 +121,11 @@ void World::clearAssociatedCaches() mChunkManager->clearCache(); } +osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) +{ + mHeightCullCallback->setHighZ(highz); + mHeightCullCallback->setCullMask(mask); + return mHeightCullCallback; +} + } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 0402b8197..92eb69b80 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,37 @@ namespace Terrain class ChunkManager; class CompositeMapRenderer; +class HeightCullCallback : public osg::NodeCallback +{ +public: + HeightCullCallback() : mLowZ(-FLT_MAX), mHighZ(FLT_MAX), mMask(~0) {} + + void setLowZ(float z) + { + mLowZ = z; + } + float getLowZ() const { return mLowZ; } + + void setHighZ(float highZ) + { + mHighZ = highZ; + } + float getHighZ() const { return mHighZ; } + + void setCullMask(unsigned int mask) { mMask = mask; } + unsigned int getCullMask() const { return mMask; } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (mLowZ <= mHighZ) + traverse(node, nv); + } +private: + float mLowZ; + float mHighZ; + unsigned int mMask; +}; + /** * @brief A View is a collection of rendering objects that are visible from a given camera/intersection. * The base View class is part of the interface for usage in conjunction with preload feature. @@ -116,6 +148,8 @@ namespace Terrain Storage* getStorage() { return mStorage; } + osg::Callback* getHeightCullCallback(float highz, unsigned int mask); + protected: Storage* mStorage; @@ -135,6 +169,7 @@ namespace Terrain bool mBorderVisible; std::set> mLoadedCells; + osg::ref_ptr mHeightCullCallback; }; } From 407fbe320ef2ae7104e8c13a2156f055c41e23ed Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 23 Apr 2020 10:10:50 +0200 Subject: [PATCH 2/5] cleanup and use C++ version of FLT_MAX --- components/terrain/quadtreeworld.cpp | 5 +++-- components/terrain/world.hpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 1d968ddc4..56fa8b7f3 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -311,11 +312,11 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: { if (!(cv->getTraversalMask() & callback->getCullMask())) return; - float lowZ = FLT_MAX; + float lowZ = std::numeric_limits::max(); float highZ = callback->getHighZ(); if (cv->getEyePoint().z() <= highZ || outofworld) { - callback->setLowZ(-FLT_MAX); + callback->setLowZ(-std::numeric_limits::max()); return; } cv->pushCurrentMask(); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 92eb69b80..fb6c45967 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -7,9 +7,9 @@ #include #include +#include #include #include -#include #include "defs.hpp" #include "cellborder.hpp" @@ -43,7 +43,7 @@ namespace Terrain class HeightCullCallback : public osg::NodeCallback { public: - HeightCullCallback() : mLowZ(-FLT_MAX), mHighZ(FLT_MAX), mMask(~0) {} + HeightCullCallback() : mLowZ(-std::numeric_limits::max()), mHighZ(std::numeric_limits::max()), mMask(~0) {} void setLowZ(float z) { From 48713915cb21b86f515fd5d1e95808d8ad56bf17 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 23 Apr 2020 11:12:10 +0200 Subject: [PATCH 3/5] re-use bzzts waterplane overlay for debug; makes it easier to test. To use this, set the env OPENMW_WATER_CULLING_DEBUG=1; You will see blue rectangles corresponding to water outlines. Once there are no more outlines, water is culled. You can further see this by pressing F3 3 times to check the the amount of quads. Before culling it should be around 1600, after culling it should drop to 0. --- components/terrain/chunkmanager.cpp | 2 ++ components/terrain/quadtreeworld.cpp | 34 ++++++++++++++++++++++---- components/terrain/terraindrawable.cpp | 29 ++++++++++++++++++++++ components/terrain/terraindrawable.hpp | 8 ++++-- 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 3c76945d3..95c1ca491 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -227,6 +227,8 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve transform->addChild(geometry); transform->getBound(); + geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); + if (mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 56fa8b7f3..ae3656fd2 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -1,6 +1,8 @@ #include "quadtreeworld.hpp" #include +#include +#include #include #include @@ -13,6 +15,7 @@ #include "viewdata.hpp" #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" +#include "terraindrawable.hpp" namespace { @@ -323,9 +326,8 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getBoundingBox(); - float minZ = bb._min.z(); - if (minZ > highZ) + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); + if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); bb._min += ofs; bb._max += ofs; @@ -333,8 +335,30 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: bb._max.z() = highZ; if (cv->isCulled(bb)) continue; - lowZ = minZ; - break; + lowZ = bb._min.z(); + + static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; + if (!debug) + break; + osg::Box* b = new osg::Box; + b->set(bb.center(), bb._max - bb.center()); + osg::ShapeDrawable* drw = new osg::ShapeDrawable(b); + static osg::ref_ptr stateset = nullptr; + if (!stateset) + { + stateset = new osg::StateSet; + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON); + osg::Material* m = new osg::Material; + m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,1,1)); + m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); + m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); + stateset->setAttributeAndModes(m, osg::StateAttribute::ON); + stateset->setRenderBinDetails(100,"RenderBin"); + } + drw->setStateSet(stateset); + drw->accept(*cv); } callback->setLowZ(lowZ); cv->popCurrentMask(); diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index c37074dac..9593687cf 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -10,6 +10,16 @@ namespace Terrain { +TerrainDrawable::TerrainDrawable() +{ + +} + +TerrainDrawable::~TerrainDrawable() +{ + +} + TerrainDrawable::TerrainDrawable(const TerrainDrawable ©, const osg::CopyOp ©op) : osg::Geometry(copy, copyop) , mPasses(copy.mPasses) @@ -118,6 +128,25 @@ void TerrainDrawable::setLightListCallback(SceneUtil::LightListCallback *lightLi mLightListCallback = lightListCallback; } +void TerrainDrawable::setupWaterBoundingBox(float waterheight, float margin) +{ + osg::Vec3Array* vertices = static_cast(getVertexArray()); + for (unsigned int i=0; isize(); ++i) + { + const osg::Vec3f& vertex = (*vertices)[i]; + if (vertex.z() <= waterheight) + mWaterBoundingBox.expandBy(vertex); + } + if (mWaterBoundingBox.valid()) + { + const osg::BoundingBox& bb = getBoundingBox(); + mWaterBoundingBox.xMin() = std::max(bb.xMin(), mWaterBoundingBox.xMin() - margin); + mWaterBoundingBox.yMin() = std::max(bb.yMin(), mWaterBoundingBox.yMin() - margin); + mWaterBoundingBox.xMax() = std::min(bb.xMax(), mWaterBoundingBox.xMax() + margin); + mWaterBoundingBox.xMax() = std::min(bb.xMax(), mWaterBoundingBox.xMax() + margin); + } +} + void TerrainDrawable::compileGLObjects(osg::RenderInfo &renderInfo) const { for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index 516b1abdb..a84200f66 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -36,8 +36,8 @@ namespace Terrain virtual const char* className() const { return "TerrainDrawable"; } virtual const char* libraryName() const { return "Terrain"; } - TerrainDrawable() = default; - ~TerrainDrawable() = default; + TerrainDrawable(); + ~TerrainDrawable(); // has to be defined in the cpp file because we only forward declared some members. TerrainDrawable(const TerrainDrawable& copy, const osg::CopyOp& copyop); virtual void accept(osg::NodeVisitor &nv); @@ -52,10 +52,14 @@ namespace Terrain virtual void compileGLObjects(osg::RenderInfo& renderInfo) const; + void setupWaterBoundingBox(float waterheight, float margin); + const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; } + void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } private: + osg::BoundingBox mWaterBoundingBox; PassVector mPasses; osg::ref_ptr mClusterCullingCallback; From 756ec7117b4d6b8362ac22415c2e57ddc59c3e80 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 23 Apr 2020 17:40:10 +0200 Subject: [PATCH 4/5] make mGrid (and friends) private again, create accessor method to return bool of mGrid.empty() --- components/terrain/quadtreeworld.cpp | 2 +- components/terrain/terraingrid.hpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index ae3656fd2..0140ade49 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -442,7 +442,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) } if (isCullVisitor) - updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !mGrid.empty()); + updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); if (!isCullVisitor) vd->clear(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray. diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index e633a258f..cb37ca296 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -28,6 +28,9 @@ namespace Terrain View* createView(); protected: + bool isGridEmpty() { return mGrid.empty(); } + + private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); void updateWaterCulling(); From ed970f4d17d0803e31092e055d753dc2010ddbfe Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 24 Apr 2020 06:26:08 +0000 Subject: [PATCH 5/5] Apply suggestion to components/terrain/terraingrid.hpp --- components/terrain/terraingrid.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index cb37ca296..8b36448c1 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -28,7 +28,7 @@ namespace Terrain View* createView(); protected: - bool isGridEmpty() { return mGrid.empty(); } + bool isGridEmpty() const { return mGrid.empty(); } private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter);