1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-25 15:35:23 +00:00

Merge branch 'i-can-see-nearly-now-the-grass-is-gone' into 'master'

Maybe compute an accurate near and far plane using primitives for groundcover

See merge request OpenMW/openmw!2960
This commit is contained in:
psi29a 2023-07-08 08:58:13 +00:00
commit 63e9a63c67

View File

@ -6,10 +6,12 @@
#include <osg/Geometry>
#include <osg/Program>
#include <osg/VertexAttribDivisor>
#include <osgUtil/CullVisitor>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/misc/convert.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/shader/shadermanager.hpp>
@ -21,121 +23,287 @@
namespace MWRender
{
class InstancingVisitor : public osg::NodeVisitor
namespace
{
public:
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& 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, const osg::Vec3& chunkPosition)
{
for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i)
{
geom.getPrimitiveSet(i)->setNumInstances(mInstances.size());
}
osg::ref_ptr<osg::Vec4Array> 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<osg::Vec3Array> 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::scale(entry.mScale, entry.mScale, entry.mScale)
* osg::Matrix(Misc::Convert::makeOsgQuat(entry.mPos))
* osg::Matrix::translate(entry.mPos.asVec3() - chunkPosition);
}
private:
std::vector<Groundcover::GroundcoverEntry> mInstances;
osg::Vec3f mChunkPosition;
};
class DensityCalculator
{
public:
DensityCalculator(float density)
: mDensity(density)
class InstancedComputeNearFarCullCallback : public osg::DrawableCullCallback
{
}
public:
InstancedComputeNearFarCullCallback(const std::vector<Groundcover::GroundcoverEntry>& instances,
const osg::Vec3& chunkPosition, const osg::BoundingBox& instanceBounds)
: mInstanceMatrices()
, mInstanceBounds(instanceBounds)
{
mInstanceMatrices.reserve(instances.size());
for (const auto& instance : instances)
mInstanceMatrices.emplace_back(computeInstanceMatrix(instance, chunkPosition));
}
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 false;
if (drawable->isCullingActive() && cullVisitor.isCulled(boundingBox))
return true;
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 true;
value_type computedZNear = cullVisitor.getCalculatedNearPlane();
value_type computedZFar = cullVisitor.getCalculatedFarPlane();
if (dNear < computedZNear || dFar > computedZFar)
{
osg::Polytope frustum;
osg::Polytope::ClippingMask resultMask
= cullVisitor.getCurrentCullingSet().getFrustum().getResultMask();
if (resultMask)
{
// Other objects are likely cheaper and should let us skip all but a few groundcover instances
cullVisitor.computeNearPlane();
if (dNear < computedZNear)
{
dNear = computedZNear;
for (const auto& instanceMatrix : mInstanceMatrices)
{
osg::Matrix fullMatrix = instanceMatrix * matrix;
osg::Vec3 instanceLookVector(-fullMatrix(0, 2), -fullMatrix(1, 2), -fullMatrix(2, 2));
unsigned int instanceBbCornerFar = (instanceLookVector.x() >= 0 ? 1 : 0)
| (instanceLookVector.y() >= 0 ? 2 : 0) | (instanceLookVector.z() >= 0 ? 4 : 0);
unsigned int instanceBbCornerNear = (~instanceBbCornerFar) & 7;
value_type instanceDNear
= distance(mInstanceBounds.corner(instanceBbCornerNear), fullMatrix);
value_type instanceDFar
= distance(mInstanceBounds.corner(instanceBbCornerFar), fullMatrix);
if (instanceDNear > instanceDFar)
std::swap(instanceDNear, instanceDFar);
if (instanceDFar < 0 || instanceDNear > dNear)
continue;
frustum.setAndTransformProvidingInverse(
cullVisitor.getProjectionCullingStack().back().getFrustum(), fullMatrix);
osg::Polytope::PlaneList planes;
osg::Polytope::ClippingMask selectorMask = 0x1;
for (const auto& plane : frustum.getPlaneList())
{
if (resultMask & selectorMask)
planes.push_back(plane);
selectorMask <<= 1;
}
value_type newNear
= cullVisitor.computeNearestPointInFrustum(fullMatrix, 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 = computedZFar;
for (const auto& instanceMatrix : mInstanceMatrices)
{
osg::Matrix fullMatrix = instanceMatrix * matrix;
osg::Vec3 instanceLookVector(-fullMatrix(0, 2), -fullMatrix(1, 2), -fullMatrix(2, 2));
unsigned int instanceBbCornerFar = (instanceLookVector.x() >= 0 ? 1 : 0)
| (instanceLookVector.y() >= 0 ? 2 : 0) | (instanceLookVector.z() >= 0 ? 4 : 0);
unsigned int instanceBbCornerNear = (~instanceBbCornerFar) & 7;
value_type instanceDNear
= distance(mInstanceBounds.corner(instanceBbCornerNear), fullMatrix);
value_type instanceDFar
= distance(mInstanceBounds.corner(instanceBbCornerFar), fullMatrix);
if (instanceDNear > instanceDFar)
std::swap(instanceDNear, instanceDFar);
if (instanceDFar < 0 || instanceDFar < dFar)
continue;
frustum.setAndTransformProvidingInverse(
cullVisitor.getProjectionCullingStack().back().getFrustum(), fullMatrix);
osg::Polytope::PlaneList planes;
osg::Polytope::ClippingMask selectorMask = 0x1;
for (const auto& plane : frustum.getPlaneList())
{
if (resultMask & selectorMask)
planes.push_back(plane);
selectorMask <<= 1;
}
value_type newFar = cullVisitor.computeFurthestPointInFrustum(
instanceMatrix * matrix, planes, *drawable);
dFar = std::max(dFar, newFar);
}
if (dFar > computedZFar)
cullVisitor.setCalculatedFarPlane(dFar);
}
}
}
return false;
}
private:
std::vector<osg::Matrix> mInstanceMatrices;
osg::BoundingBox mInstanceBounds;
};
class InstancingVisitor : public osg::NodeVisitor
{
if (mDensity >= 1.f)
public:
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& 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<osg::Vec4Array> transforms = new osg::Vec4Array(mInstances.size());
osg::BoundingBox box;
osg::BoundingBox originalBox = geom.getBoundingBox();
float radius = originalBox.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<osg::Vec3Array> 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, mChunkPosition, originalBox));
}
private:
std::vector<Groundcover::GroundcoverEntry> 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<ViewDistanceCallback>
{
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<ViewDistanceCallback>
{
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<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod,