From 43eb739313be3044d93f7e440efdae3b99195ff4 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 23 Apr 2023 00:10:34 +0100 Subject: [PATCH] Maybe compute an accurate near and far plane using primitives for groundcover I couldn't reproduce the original bug, so can't verify whether this fixes it. There's a strong chance I've boffed the matrix multiplication order. If it's the same as GLSL, then everything *should* be fine. --- apps/openmw/mwrender/groundcover.cpp | 309 ++++++++++++++++++--------- 1 file changed, 210 insertions(+), 99 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 9e169b2563..2423e764e3 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -21,121 +22,231 @@ namespace MWRender { - class InstancingVisitor : public osg::NodeVisitor + namespace { - public: - InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mInstances(instances) - , mChunkPosition(chunkPosition) + using value_type = osgUtil::CullVisitor::value_type; + + // From OSG's CullVisitor.cpp + inline value_type distance(const osg::Vec3& coord, const osg::Matrix& matrix) { + return -((value_type)coord[0] * (value_type)matrix(0, 2) + (value_type)coord[1] * (value_type)matrix(1, 2) + + (value_type)coord[2] * (value_type)matrix(2, 2) + matrix(3, 2)); } - void apply(osg::Geometry& geom) override + inline osg::Matrix computeInstanceMatrix(const Groundcover::GroundcoverEntry& entry) { - for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) - { - geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); - } - - osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); - osg::BoundingBox box; - float radius = geom.getBoundingBox().radius(); - for (unsigned int i = 0; i < transforms->getNumElements(); i++) - { - osg::Vec3f pos(mInstances[i].mPos.asVec3()); - osg::Vec3f relativePos = pos - mChunkPosition; - (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); - - // Use an additional margin due to groundcover animation - float instanceRadius = radius * mInstances[i].mScale * 1.1f; - osg::BoundingSphere instanceBounds(relativePos, instanceRadius); - box.expandBy(instanceBounds); - } - - geom.setInitialBound(box); - - osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); - for (unsigned int i = 0; i < rotations->getNumElements(); i++) - { - (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); - } - - // Display lists do not support instancing in OSG 3.4 - geom.setUseDisplayList(false); - geom.setUseVertexBufferObjects(true); - - geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); - geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + return osg::Matrix::translate(entry.mPos.asVec3()) * osg::Matrix(Misc::Convert::makeOsgQuat(entry.mPos)) + * osg::Matrix::scale(entry.mScale, entry.mScale, entry.mScale); } - private: - std::vector mInstances; - osg::Vec3f mChunkPosition; - }; - - class DensityCalculator - { - public: - DensityCalculator(float density) - : mDensity(density) + class InstancedComputeNearFarCullCallback : public osg::DrawableCullCallback { - } + public: + InstancedComputeNearFarCullCallback(const std::vector& instances) + : mInstances(instances) + { + } - bool isInstanceEnabled() + bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const override + { + osgUtil::CullVisitor& cullVisitor = *nv->asCullVisitor(); + osg::CullSettings::ComputeNearFarMode cnfMode = cullVisitor.getComputeNearFarMode(); + const osg::BoundingBox& boundingBox = drawable->getBoundingBox(); + osg::RefMatrix& matrix = *cullVisitor.getModelViewMatrix(); + + if (cnfMode != osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES + && cnfMode != osg::CullSettings::COMPUTE_NEAR_USING_PRIMITIVES) + return true; + + if (drawable->isCullingActive() && cullVisitor.isCulled(boundingBox)) + return false; + + osg::Vec3 lookVector = cullVisitor.getLookVectorLocal(); + unsigned int bbCornerFar + = (lookVector.x() >= 0 ? 1 : 0) | (lookVector.y() >= 0 ? 2 : 0) | (lookVector.z() >= 0 ? 4 : 0); + unsigned int bbCornerNear = (~bbCornerFar) & 7; + value_type dNear = distance(boundingBox.corner(bbCornerNear), matrix); + value_type dFar = distance(boundingBox.corner(bbCornerFar), matrix); + + if (dNear > dFar) + std::swap(dNear, dFar); + + if (dFar < 0) + return false; + + value_type computedZNear = cullVisitor.getCalculatedNearPlane(); + value_type computedZFar = cullVisitor.getCalculatedFarPlane(); + + if (dNear < computedZNear || dFar > computedZFar) + { + osg::Polytope& frustum = cullVisitor.getCurrentCullingSet().getFrustum(); + osg::Polytope::ClippingMask resultMask = frustum.getResultMask(); + if (resultMask) + { + osg::Polytope::PlaneList planes; + osg::Polytope::ClippingMask selectorMask = 0x1; + for (const auto& plane : frustum.getPlaneList()) + { + if (resultMask & selectorMask) + planes.push_back(plane); + selectorMask <<= 1; + } + + if (dNear < computedZNear) + { + dNear = std::numeric_limits::max(); + for (const auto& entry : mInstances) + { + osg::Matrix instanceMatrix = computeInstanceMatrix(entry); + value_type newNear = cullVisitor.computeNearestPointInFrustum( + matrix * instanceMatrix, planes, *drawable); + dNear = std::min(dNear, newNear); + } + if (dNear < computedZNear) + cullVisitor.setCalculatedNearPlane(dNear); + } + + if (cnfMode == osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES && dFar > computedZFar) + { + dFar = -std::numeric_limits::max(); + for (const auto& entry : mInstances) + { + osg::Matrix instanceMatrix = computeInstanceMatrix(entry); + value_type newFar = cullVisitor.computeFurthestPointInFrustum( + matrix * instanceMatrix, planes, *drawable); + dFar = std::max(dFar, newFar); + } + if (dFar > computedZFar) + cullVisitor.setCalculatedFarPlane(dFar); + } + } + } + + return true; + } + + private: + std::vector mInstances; + }; + + class InstancingVisitor : public osg::NodeVisitor { - if (mDensity >= 1.f) + public: + InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) + { + } + + void apply(osg::Geometry& geom) override + { + for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) + { + geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); + } + + osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); + osg::BoundingBox box; + float radius = geom.getBoundingBox().radius(); + for (unsigned int i = 0; i < transforms->getNumElements(); i++) + { + osg::Vec3f pos(mInstances[i].mPos.asVec3()); + osg::Vec3f relativePos = pos - mChunkPosition; + (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); + + // Use an additional margin due to groundcover animation + float instanceRadius = radius * mInstances[i].mScale * 1.1f; + osg::BoundingSphere instanceBounds(relativePos, instanceRadius); + box.expandBy(instanceBounds); + } + + geom.setInitialBound(box); + + osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); + for (unsigned int i = 0; i < rotations->getNumElements(); i++) + { + (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); + } + + // Display lists do not support instancing in OSG 3.4 + geom.setUseDisplayList(false); + geom.setUseVertexBufferObjects(true); + + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); + geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + + geom.addCullCallback(new InstancedComputeNearFarCullCallback(mInstances)); + } + + private: + std::vector mInstances; + osg::Vec3f mChunkPosition; + }; + + class DensityCalculator + { + public: + DensityCalculator(float density) + : mDensity(density) + { + } + + bool isInstanceEnabled() + { + if (mDensity >= 1.f) + return true; + + mCurrentGroundcover += mDensity; + if (mCurrentGroundcover < 1.f) + return false; + + mCurrentGroundcover -= 1.f; + + return true; + } + void reset() { mCurrentGroundcover = 0.f; } + + private: + float mCurrentGroundcover = 0.f; + float mDensity = 0.f; + }; + + class ViewDistanceCallback : public SceneUtil::NodeCallback + { + 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; + if (size.x() >= 1 && size.y() >= 1) return true; - mCurrentGroundcover += mDensity; - if (mCurrentGroundcover < 1.f) + osg::Vec3f pos = ref.mPos.asVec3(); + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) + || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) + || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) return false; - mCurrentGroundcover -= 1.f; - return true; } - void reset() { mCurrentGroundcover = 0.f; } - - private: - float mCurrentGroundcover = 0.f; - float mDensity = 0.f; - }; - - class ViewDistanceCallback : public SceneUtil::NodeCallback - { - 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; - if (size.x() >= 1 && size.y() >= 1) - return true; - - osg::Vec3f pos = ref.mPos.asVec3(); - osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; - if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) - || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) - || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) - || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) - return false; - - return true; } osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod,