mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-24 09:53:53 +00:00
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.
This commit is contained in:
parent
44ed3bed2b
commit
43eb739313
1 changed files with 210 additions and 99 deletions
|
@ -10,6 +10,7 @@
|
||||||
#include <components/esm3/esmreader.hpp>
|
#include <components/esm3/esmreader.hpp>
|
||||||
#include <components/esm3/loadland.hpp>
|
#include <components/esm3/loadland.hpp>
|
||||||
#include <components/esm3/readerscache.hpp>
|
#include <components/esm3/readerscache.hpp>
|
||||||
|
#include <components/misc/convert.hpp>
|
||||||
#include <components/sceneutil/lightmanager.hpp>
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
#include <components/sceneutil/nodecallback.hpp>
|
#include <components/sceneutil/nodecallback.hpp>
|
||||||
#include <components/shader/shadermanager.hpp>
|
#include <components/shader/shadermanager.hpp>
|
||||||
|
@ -21,121 +22,231 @@
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
class InstancingVisitor : public osg::NodeVisitor
|
namespace
|
||||||
{
|
{
|
||||||
public:
|
using value_type = osgUtil::CullVisitor::value_type;
|
||||||
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& instances, osg::Vec3f& chunkPosition)
|
|
||||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
// From OSG's CullVisitor.cpp
|
||||||
, mInstances(instances)
|
inline value_type distance(const osg::Vec3& coord, const osg::Matrix& matrix)
|
||||||
, mChunkPosition(chunkPosition)
|
|
||||||
{
|
{
|
||||||
|
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)
|
return osg::Matrix::translate(entry.mPos.asVec3()) * osg::Matrix(Misc::Convert::makeOsgQuat(entry.mPos))
|
||||||
{
|
* osg::Matrix::scale(entry.mScale, entry.mScale, entry.mScale);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
class InstancedComputeNearFarCullCallback : public osg::DrawableCullCallback
|
||||||
std::vector<Groundcover::GroundcoverEntry> mInstances;
|
|
||||||
osg::Vec3f mChunkPosition;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DensityCalculator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DensityCalculator(float density)
|
|
||||||
: mDensity(density)
|
|
||||||
{
|
{
|
||||||
}
|
public:
|
||||||
|
InstancedComputeNearFarCullCallback(const std::vector<Groundcover::GroundcoverEntry>& 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<value_type>::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<value_type>::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<Groundcover::GroundcoverEntry> mInstances;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
|
||||||
|
geom.addCullCallback(new InstancedComputeNearFarCullCallback(mInstances));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return true;
|
||||||
|
|
||||||
mCurrentGroundcover += mDensity;
|
osg::Vec3f pos = ref.mPos.asVec3();
|
||||||
if (mCurrentGroundcover < 1.f)
|
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 false;
|
||||||
|
|
||||||
mCurrentGroundcover -= 1.f;
|
|
||||||
|
|
||||||
return true;
|
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,
|
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod,
|
||||||
|
|
Loading…
Reference in a new issue