Merge branch 'improve_shadows_slim' into 'master'

Parallel Shadow Culling and Traversal

See merge request OpenMW/openmw!4881
This commit is contained in:
psi29a 2025-09-20 09:08:26 +00:00
commit b6bfc4c06a
5 changed files with 376 additions and 169 deletions

View File

@ -25,9 +25,16 @@
#include <osg/io_utils>
#include <osg/Depth>
#include <osg/ClipControl>
#include <osg/Math>
#include <osgUtil/RenderStage>
#include <osgUtil/StateGraph>
#include <cstdlib>
#include <cmath>
#include <sstream>
#include <vector>
#include <future>
#include <limits>
#include "glextensions.hpp"
#include "shadowsbin.hpp"
@ -242,7 +249,7 @@ class VDSMCameraCullCallback : public osg::NodeCallback
{
public:
VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope);
VDSMCameraCullCallback(MWShadowTechnique* vdsm, const osg::Polytope& polytope);
void operator()(osg::Node*, osg::NodeVisitor* nv) override;
@ -257,47 +264,61 @@ class VDSMCameraCullCallback : public osg::NodeCallback
osg::Polytope _polytope;
};
VDSMCameraCullCallback::VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope):
VDSMCameraCullCallback::VDSMCameraCullCallback(MWShadowTechnique* vdsm, const osg::Polytope& polytope):
_vdsm(vdsm),
_polytope(polytope)
{
}
{}
void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
osg::Camera* camera = node->asCamera();
OSG_INFO<<"VDSMCameraCullCallback::operator()(osg::Node* "<<camera<<", osg::NodeVisitor* "<<cv<<")"<<std::endl;
// Make sure the current render bin is the camera's RTT render stage so that
// all render leaves generated below land in the RTT stage (not the parent).
if (osgUtil::RenderStage* stage = cv->getCurrentRenderStage())
cv->setCurrentRenderBin(stage);
#if 1
if (!_polytope.empty())
{
OSG_INFO<<"Pushing custom Polytope"<<std::endl;
osg::CullingSet& cs = cv->getProjectionCullingStack().back();
cs.setFrustum(_polytope);
cv->pushCullingSet();
auto& stack = cv->getProjectionCullingStack();
if (!stack.empty())
{
osg::CullingSet& cs = stack.back();
cs.setFrustum(_polytope);
cv->pushCullingSet();
}
else
{
// No projection stack available; skip polytope for safety in parallel paths
}
}
// Push casting state and shadows bin while traversing the RTT camera
if (auto* cast = _vdsm->getCastingStateSet())
{
cv->pushStateSet(cast);
}
#endif
// bin has to go inside camera cull or the rendertexture stage will override it
cv->pushStateSet(_vdsm->getOrCreateShadowsBinStateSet());
if (_vdsm->getShadowedScene())
{
_vdsm->getShadowedScene()->osg::Group::traverse(*nv);
}
cv->popStateSet();
#if 1
cv->popStateSet(); // pop shadows bin stateset
if (_vdsm->getCastingStateSet()){
cv->popStateSet();
}
if (!_polytope.empty())
{
OSG_INFO<<"Popping custom Polytope"<<std::endl;
cv->popCullingSet();
auto& stack = cv->getProjectionCullingStack();
if (!stack.empty())
{
cv->popCullingSet();
}
}
#endif
_renderStage = cv->getCurrentRenderBin()->getStage();
// Capture the RTT render stage after traversal for PSM adjustments later.
_renderStage = cv->getCurrentRenderStage();
OSG_INFO<<"VDSM second : _renderStage = "<<_renderStage<<std::endl;
if (cv->getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR)
@ -305,10 +326,11 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
// make sure that the near plane is computed correctly.
cv->computeNearPlane();
osg::Matrixd projection = *(cv->getProjectionMatrix());
osg::RefMatrix* projRef = cv->getProjectionMatrix();
if (!projRef)
return; // cannot proceed safely without a projection matrix
osg::Matrixd projection = *projRef;
OSG_INFO<<"RTT Projection matrix "<<projection<<std::endl;
osg::Matrix::value_type left, right, bottom, top, zNear, zFar;
osg::Matrix::value_type epsilon = 1e-6;
if (fabs(projection(0,3))<epsilon && fabs(projection(1,3))<epsilon && fabs(projection(2,3))<epsilon )
@ -316,7 +338,6 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
projection.getOrtho(left, right,
bottom, top,
zNear, zFar);
OSG_INFO<<"Ortho zNear="<<zNear<<", zFar="<<zFar<<std::endl;
}
else
@ -324,10 +345,8 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
projection.getFrustum(left, right,
bottom, top,
zNear, zFar);
OSG_INFO<<"Frustum zNear="<<zNear<<", zFar="<<zFar<<std::endl;
}
OSG_INFO<<"Calculated zNear = "<<cv->getCalculatedNearPlane()<<", zFar = "<<cv->getCalculatedFarPlane()<<std::endl;
zNear = osg::maximum(zNear, cv->getCalculatedNearPlane());
@ -337,7 +356,6 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
cv->setCalculatedFarPlane(zFar);
cv->clampProjectionMatrix(projection, zNear, zFar);
//OSG_INFO<<"RTT zNear = "<<zNear<<", zFar = "<<zFar<<std::endl;
OSG_INFO<<"RTT Projection matrix after clamping "<<projection<<std::endl;
@ -652,7 +670,6 @@ MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, d
OSG_INFO<<"Projection matrix after clamping "<<projectionMatrix<<std::endl;
}
}
void SceneUtil::MWShadowTechnique::Frustum::setCustomClipSpace(const osg::BoundingBoxd& clipCornersOverride)
{
useCustomClipSpace = true;
@ -691,7 +708,6 @@ void SceneUtil::MWShadowTechnique::Frustum::init()
++itr)
{
*itr = (*itr) * clipToWorld;
OSG_INFO<<" corner "<<*itr<<std::endl;
}
@ -704,7 +720,6 @@ void SceneUtil::MWShadowTechnique::Frustum::init()
center = (centerNearPlane+centerFarPlane)*0.5;
frustumCenterLine = centerFarPlane-centerNearPlane;
frustumCenterLine.normalize();
OSG_INFO<<" center "<<center<<std::endl;
faces[0].push_back(0);
@ -1048,7 +1063,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
return;
}
OSG_INFO<<std::endl<<std::endl<<"MWShadowTechnique::cull(osg::CullVisitor&"<<&cv<<")"<<std::endl;
if (!_shadowCastingStateSet)
{
@ -1065,13 +1079,17 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
_shadowedScene->osg::Group::traverse(cv);
return;
}
OSG_INFO<<std::endl<<std::endl<<"MWShadowTechnique::cull(osg::CullVisitor&"<<&cv<<")"<<std::endl;
Uniforms& vddUniforms = vdd->_uniforms[cv.getTraversalNumber() % 2];
ShadowSettings* settings = getShadowedScene()->getShadowSettings();
const bool debugDraw = settings->getDebugDraw();
const bool isCascaded = settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED;
const bool isParallelSplit = settings->getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT;
OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<<std::endl;
osg::CullSettings::ComputeNearFarMode cachedNearFarMode = cv.getComputeNearFarMode();
osg::RefMatrix& viewProjectionMatrix = *cv.getProjectionMatrix();
@ -1125,8 +1143,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
maxZFar = osg::minimum(settings->getMaximumShadowMapDistance(),maxZFar);
if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio();
//OSG_NOTICE<<"maxZFar "<<maxZFar<<std::endl;
// Workaround for absurdly huge viewing distances where OSG would otherwise push the near plane out.
cv.setNearFarRatio(minZNear / maxZFar);
@ -1146,7 +1162,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
if (itr != _viewDependentDataMap.end())
{
OSG_INFO << "User provided a valid shared frustum hint, re-using previously generated shadow map" << std::endl;
copyShadowMap(cv, vdd, itr->second);
// return compute near far mode back to it's original settings
@ -1184,8 +1199,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
// return compute near far mode back to it's original settings
cv.setComputeNearFarMode(cachedNearFarMode);
OSG_INFO<<"frustum.eye="<<frustum.eye<<", frustum.centerNearPlane, "<<frustum.centerNearPlane<<" distance = "<<(frustum.eye-frustum.centerNearPlane).length()<<std::endl;
// 2. select active light sources
// create a list of light sources + their matrices to place them
@ -1233,6 +1246,12 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
continue;
}
// Precompute inverse view and base local polytope once per light
osg::Matrixd invViewMatrix;
invViewMatrix.invert(viewMatrix);
osg::Polytope baseLocalPolytope(polytope);
baseLocalPolytope.transformProvidingInverse(invViewMatrix);
// if we are using multiple shadow maps and CastShadowTraversalMask is being used
// traverse the scene to compute the extents of the objects
if (/*numShadowMapsPerLight>1 &&*/ (_shadowedScene->getCastsShadowTraversalMask() & _worldMask) == 0)
@ -1323,12 +1342,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
osg::Vec3d eye_ls = eye_v * projectionMatrix;
OSG_INFO<<"Angle between view vector and eye "<<osg::RadiansToDegrees(angle)<<std::endl;
OSG_INFO<<"eye_ls="<<eye_ls<<std::endl;
if (eye_ls.y()>=-1.0 && eye_ls.y()<=1.0)
{
OSG_INFO<<"Eye point inside light space clip region "<<std::endl;
splitPoint = 0.0;
}
else
@ -1339,13 +1355,108 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
double mid = eye_ls.y()+sqrt_nf;
double ratioOfMidToUseForSplit = 0.8;
splitPoint = mid * ratioOfMidToUseForSplit;
OSG_INFO<<" n="<<n<<", f="<<f<<", sqrt_nf="<<sqrt_nf<<" mid="<<mid<<std::endl;
}
}
#endif
// 4. For each light/shadow map
// Precompute per-cascade parameters and plane lists (parallelizable), reused below
struct SplitParams { double r_start = -1.0; double r_end = 1.0; double nearPlane = std::numeric_limits<double>::quiet_NaN(); double farPlane = std::numeric_limits<double>::quiet_NaN(); };
struct CascadePrep { SplitParams split; osg::Matrixd proj; std::vector<osg::Plane> extraPlanes; bool planesPushed = false; };
std::vector<CascadePrep> cascadePreps;
if (numShadowMapsPerLight > 1)
{
cascadePreps.resize(numShadowMapsPerLight);
std::vector<std::future<void>> preTasks;
if (mParallelShadowCullingEnabled)
preTasks.reserve(numShadowMapsPerLight);
auto doPrep = [&](unsigned int sm_i) {
CascadePrep cp;
// Split params
const double n = reducedNear;
const double f = reducedFar;
const double i = static_cast<double>(sm_i);
const double m = static_cast<double>(numShadowMapsPerLight);
if (sm_i == 0)
{
cp.split.r_start = -1.0;
cp.split.nearPlane = n;
}
else
{
const double ciLog = n * pow(f / n, i / m);
const double ciUniform = n + (f - n) * i / m;
const double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias;
cp.split.nearPlane = ci;
osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci;
osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix;
cp.split.r_start = lightSpacePos.y();
}
if (sm_i + 1 == numShadowMapsPerLight)
{
cp.split.r_end = 1.0;
cp.split.farPlane = f;
}
else
{
const double ciLog = n * pow(f / n, (i + 1) / m);
const double ciUniform = n + (f - n) * (i + 1) / m;
const double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias;
cp.split.farPlane = ci;
osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci;
osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix;
cp.split.r_end = lightSpacePos.y();
}
// Parallel-split planes and projection tweak (if needed)
if (isParallelSplit)
{
double r_start = cp.split.r_start;
double r_end = cp.split.r_end;
// Overlap bias is applied later with motion context, so only base planes here
if (sm_i>0)
{
osg::Plane plane(0.0,1.0,0.0,-r_start);
osg::Plane tplane = plane; tplane.transformProvidingInverse(projectionMatrix);
cp.extraPlanes.push_back(tplane);
}
if (sm_i+1<numShadowMapsPerLight)
{
osg::Plane plane(0.0,-1.0,0.0,r_end);
osg::Plane tplane = plane; tplane.transformProvidingInverse(projectionMatrix);
cp.extraPlanes.push_back(tplane);
}
cp.planesPushed = !cp.extraPlanes.empty();
if (cp.planesPushed)
{
// mid/range scaling matrix for projection tweak
const double mid_r = (r_start + r_end)*0.5;
const double range_r = (r_end - r_start);
cp.proj = osg::Matrixd::translate(osg::Vec3d(0.0,-mid_r,0.0)) *
osg::Matrixd::scale(osg::Vec3d(1.0,2.0/range_r,1.0));
}
}
cascadePreps[sm_i] = std::move(cp);
};
for (unsigned int sm_i = 0; sm_i < numShadowMapsPerLight; ++sm_i)
{
if (mParallelShadowCullingEnabled)
preTasks.emplace_back(std::async(std::launch::async, doPrep, sm_i));
else
doPrep(sm_i);
}
for (auto& t : preTasks) t.get();
}
// 4. For each light/shadow map
struct CascadeCullResult { osg::ref_ptr<ShadowData> sd; osg::ref_ptr<osg::Camera> camera; osg::ref_ptr<VDSMCameraCullCallback> cb; unsigned int sm_i = 0; bool rendered = false; double rStart = std::numeric_limits<double>::quiet_NaN(); double rEnd = std::numeric_limits<double>::quiet_NaN(); };
// Experimental parallel traversal: enabled only when both flags are on
const bool useParallelCull = (mParallelShadowTraversalEnabled && mParallelShadowCullingEnabled);
std::vector<std::future<CascadeCullResult>> parallelCullTasks;
// Note: we used to clone the main CullVisitor as a template, but we now construct
// a fresh CullVisitor per task and seed it explicitly to avoid inheriting unsafe state.
std::vector<CascadeCullResult> serialResults;
serialResults.reserve(numShadowMapsPerLight);
for (unsigned int sm_i=0; sm_i<numShadowMapsPerLight; ++sm_i)
{
osg::ref_ptr<ShadowData> sd;
@ -1367,167 +1478,196 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
camera->setProjectionMatrix(projectionMatrix);
camera->setViewMatrix(viewMatrix);
if (settings->getDebugDraw())
if (debugDraw)
{
camera->getViewport()->x() = pos_x;
pos_x += static_cast<unsigned int>(camera->getViewport()->width()) + 40;
}
// transform polytope in model coords into light spaces eye coords.
osg::Matrixd invertModelView;
invertModelView.invert(camera->getViewMatrix());
osg::Polytope local_polytope(polytope);
local_polytope.transformProvidingInverse(invertModelView);
double cascaseNear = reducedNear;
double cascadeFar = reducedFar;
// For skip-on-unchanged heuristic
double computedRStart = std::numeric_limits<double>::quiet_NaN();
double computedREnd = std::numeric_limits<double>::quiet_NaN();
// Local polytope used by both PARALLEL_SPLIT plane clipping and CASCADED cropping
osg::Polytope local_polytope;
// Start from precomputed base local polytope (light space eye coords)
local_polytope = baseLocalPolytope;
// compute the start and end range in non-dimensional coords if using multiple shadow maps
if (numShadowMapsPerLight>1)
{
// compute the start and end range in non-dimensional coords
#if 0
double r_start = (sm_i==0) ? -1.0 : (double(sm_i)/double(numShadowMapsPerLight)*2.0-1.0);
double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : (double(sm_i+1)/double(numShadowMapsPerLight)*2.0-1.0);
#elif 0
// hardwired for 2 splits
double r_start = (sm_i==0) ? -1.0 : splitPoint;
double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : splitPoint;
#else
double r_start, r_end;
// split system based on the original Parallel Split Shadow Maps paper.
double n = reducedNear;
double f = reducedFar;
double i = double(sm_i);
double m = double(numShadowMapsPerLight);
if (sm_i == 0)
if (!cascadePreps.empty())
{
cascaseNear = cascadePreps[sm_i].split.nearPlane;
cascadeFar = cascadePreps[sm_i].split.farPlane;
r_start = cascadePreps[sm_i].split.r_start;
r_end = cascadePreps[sm_i].split.r_end;
}
else
{
// Fallback to scalar path if no splits
r_start = -1.0;
else
{
// compute the split point in main camera view
double ciLog = n * pow(f / n, i / m);
double ciUniform = n + (f - n) * i / m;
double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias;
cascaseNear = ci;
// work out where this is in light space
osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci;
osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix;
r_start = lightSpacePos.y();
}
if (sm_i + 1 == numShadowMapsPerLight)
r_end = 1.0;
else
{
// compute the split point in main camera view
double ciLog = n * pow(f / n, (i + 1) / m);
double ciUniform = n + (f - n) * (i + 1) / m;
double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias;
cascadeFar = ci;
// work out where this is in light space
osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci;
osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix;
r_end = lightSpacePos.y();
}
#endif
// for all by the last shadowmap shift the r_end so that it overlaps slightly with the next shadowmap
// for all but the last shadowmap shift the r_end so that it overlaps with the next shadowmap
// to prevent a seam showing through between the shadowmaps
if (sm_i+1<numShadowMapsPerLight) r_end+=0.01;
double cascadeOverlapBias = 0.01; // fixed overlap bias
if (sm_i + 1 < numShadowMapsPerLight) r_end += cascadeOverlapBias;
// Record signature
computedRStart = r_start;
computedREnd = r_end;
// We can't add these clipping planes with cascaded shadow maps as they wouldn't be parallel to the light direction.
if (settings->getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i>0)
bool planesPushed = false;
if (isParallelSplit)
{
// not the first shadowmap so insert a polytope to clip the scene from before r_start
// Base planes from prepass; apply overlap bias now by tweaking r_end plane
if (!cascadePreps.empty())
{
// Recreate planes with current r_start/r_end (already have base transformed planes, but we must account for bias)
if (sm_i>0)
{
osg::Plane plane(0.0,1.0,0.0,-r_start);
plane.transformProvidingInverse(projectionMatrix);
local_polytope.getPlaneList().push_back(plane);
planesPushed = true;
}
if (sm_i+1<numShadowMapsPerLight)
{
osg::Plane plane(0.0,-1.0,0.0,r_end);
plane.transformProvidingInverse(projectionMatrix);
local_polytope.getPlaneList().push_back(plane);
planesPushed = true;
}
if (planesPushed)
local_polytope.setupMask();
// plane in clip space coords
osg::Plane plane(0.0,1.0,0.0,-r_start);
// transform into eye coords
plane.transformProvidingInverse(projectionMatrix);
local_polytope.getPlaneList().push_back(plane);
//OSG_NOTICE<<"Adding r_start plane "<<plane<<std::endl;
}
if (settings->getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i+1<numShadowMapsPerLight)
{
// not the last shadowmap so insert a polytope to clip the scene from beyond r_end
// plane in clip space coords
osg::Plane plane(0.0,-1.0,0.0,r_end);
// transform into eye coords
plane.transformProvidingInverse(projectionMatrix);
local_polytope.getPlaneList().push_back(plane);
//OSG_NOTICE<<"Adding r_end plane "<<plane<<std::endl;
}
local_polytope.setupMask();
if (settings->getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT)
{
// OSG_NOTICE<<"Need to adjust RTT camera projection and view matrix here, r_start="<<r_start<<", r_end="<<r_end<<std::endl;
// OSG_NOTICE<<" textureUnit = "<<textureUnit<<std::endl;
double mid_r = (r_start + r_end)*0.5;
double range_r = (r_end - r_start);
// OSG_NOTICE<<" mid_r = "<<mid_r<<", range_r = "<<range_r<<std::endl;
camera->setProjectionMatrix(
camera->getProjectionMatrix() *
osg::Matrixd::translate(osg::Vec3d(0.0,-mid_r,0.0)) *
osg::Matrixd::scale(osg::Vec3d(1.0,2.0/range_r,1.0)));
// Apply projection tweak from prepass (based on base mid/range)
if (cascadePreps[sm_i].planesPushed)
{
camera->setProjectionMatrix(camera->getProjectionMatrix() * cascadePreps[sm_i].proj);
}
}
}
}
osg::ref_ptr<VDSMCameraCullCallback> vdsmCallback;
std::vector<osg::Plane> extraPlanes;
if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED)
extraPlanes.reserve(6);
bool planesPushed = false;
if (isCascaded)
{
cropShadowCameraToMainFrustum(frustum, camera, cascaseNear, cascadeFar, extraPlanes);
for (const auto& plane : extraPlanes)
local_polytope.getPlaneList().push_back(plane);
local_polytope.setupMask();
planesPushed = !extraPlanes.empty();
if (planesPushed)
local_polytope.setupMask();
}
else
{
cropShadowCameraToMainFrustum(frustum, camera, reducedNear, reducedFar, extraPlanes);
}
osg::ref_ptr<VDSMCameraCullCallback> vdsmCallback = new VDSMCameraCullCallback(this, local_polytope);
// Texel snapping for the nearest cascade only (directional light), to stabilize during motion
if (sm_i == 0 && pl.directionalLight)
{
const osg::Viewport* vp = camera->getViewport();
const double texW = vp ? static_cast<double>(vp->width()) : static_cast<double>(settings->getTextureSize().x());
const double texH = vp ? static_cast<double>(vp->height()) : static_cast<double>(settings->getTextureSize().y());
if (texW > 0.0 && texH > 0.0)
{
osg::Vec3d clipCenter = frustum.center * camera->getViewMatrix() * camera->getProjectionMatrix();
const double ndcX = clipCenter.x();
const double ndcY = clipCenter.y();
const double u = (ndcX * 0.5 + 0.5) * texW;
const double v = (ndcY * 0.5 + 0.5) * texH;
const double uSnap = std::round(u);
const double vSnap = std::round(v);
const double du = (uSnap - u) / texW; // in [0..1]
const double dv = (vSnap - v) / texH;
const double ndcOffX = du * 2.0;
const double ndcOffY = dv * 2.0;
if (std::fabs(ndcOffX) > 0.0 || std::fabs(ndcOffY) > 0.0)
{
camera->setProjectionMatrix(
camera->getProjectionMatrix() *
osg::Matrixd::translate(osg::Vec3d(ndcOffX, ndcOffY, 0.0)));
}
}
}
// Ensure shadows bin StateSet exists before any parallel tasks to avoid races.
(void)getOrCreateShadowsBinStateSet();
// Use the camera's cull callback to set up the render stage, push casting/bin state, and apply the custom polytope.
vdsmCallback = new VDSMCameraCullCallback(this, local_polytope);
camera->setCullCallback(vdsmCallback.get());
// 4.3 traverse RTT camera
//
cv.pushStateSet(_shadowCastingStateSet.get());
// If full update frame and stationary, allow skipping deeper cascades whose light-space coverage is unchanged
if (!useParallelCull)
{
// Serial traversal on the main CullVisitor.
cv.pushStateSet(_shadowCastingStateSet.get());
cullShadowCastingScene(&cv, camera.get());
cv.popStateSet();
CascadeCullResult res; res.sd = sd; res.camera = camera; res.cb = vdsmCallback; res.sm_i = sm_i; res.rendered = true; res.rStart = computedRStart; res.rEnd = computedREnd;
serialResults.push_back(std::move(res));
}
else
{
// Parallel traversal using a private CullVisitor and render graph per cascade.
osg::ref_ptr<osg::Camera> taskCamera = camera;
osg::ref_ptr<VDSMCameraCullCallback> taskCb = vdsmCallback;
osg::ref_ptr<ShadowData> taskSd = sd;
const unsigned int castsMask = (_shadowedScene->getShadowSettings()->getCastsShadowTraversalMask() & _worldMask);
const auto mainComputeNF = cv.getComputeNearFarMode();
const auto mainTraversalNumber = cv.getTraversalNumber();
osg::ref_ptr<const osg::FrameStamp> mainFrameStamp = cv.getFrameStamp();
cullShadowCastingScene(&cv, camera.get());
parallelCullTasks.emplace_back(std::async(std::launch::async, [taskCamera, taskCb, taskSd, castsMask, mainComputeNF, mainTraversalNumber, mainFrameStamp, sm_i, computedRStart, computedREnd]() mutable {
CascadeCullResult res; res.sd = taskSd; res.camera = taskCamera; res.cb = taskCb; res.sm_i = sm_i; res.rendered = false; res.rStart = computedRStart; res.rEnd = computedREnd;
osg::ref_ptr<osgUtil::CullVisitor> localCv = new osgUtil::CullVisitor();
localCv->reset();
localCv->setTraversalMask(castsMask);
localCv->setComputeNearFarMode(mainComputeNF);
localCv->setTraversalNumber(mainTraversalNumber);
if (mainFrameStamp.valid())
localCv->setFrameStamp(const_cast<osg::FrameStamp*>(mainFrameStamp.get()));
{
osg::ref_ptr<osgUtil::StateGraph> rootGraph = new osgUtil::StateGraph();
localCv->setStateGraph(rootGraph.get());
osg::ref_ptr<osgUtil::RenderStage> rootStage = new osgUtil::RenderStage();
rootStage->setViewport(taskCamera->getViewport());
rootStage->setInitialViewMatrix(new osg::RefMatrix(taskCamera->getViewMatrix()));
localCv->setCurrentRenderBin(rootStage.get());
}
taskCamera->accept(*localCv);
res.rendered = true;
return res;
}));
}
cv.popStateSet();
if (!orthographicViewFrustum && settings->getShadowMapProjectionHint()==ShadowSettings::PERSPECTIVE_SHADOW_MAP)
if (!useParallelCull && !orthographicViewFrustum && settings->getShadowMapProjectionHint()==ShadowSettings::PERSPECTIVE_SHADOW_MAP)
{
assignValidRegionSettings(cv, camera, sm_i, vddUniforms);
if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED)
adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), cascaseNear, cascadeFar);
else
adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), reducedNear, reducedFar);
const double adjustNear = isCascaded ? cascaseNear : reducedNear;
const double adjustFar = isCascaded ? cascadeFar : reducedFar;
adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), adjustNear, adjustFar);
if (vdsmCallback->getProjectionMatrix())
{
vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix());
}
}
// 4.4 compute main scene graph TexGen + uniform settings + setup state
//
if (!useParallelCull)
{
assignShadowStateSettings(cv, camera, sm_i, vddUniforms);
}
@ -1545,9 +1685,55 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
++textureUnit;
++numValidShadows ;
if (_debugHud)
if (!useParallelCull && _debugHud)
{
_debugHud->draw(sd->_texture, sm_i, camera->getViewMatrix() * camera->getProjectionMatrix(), cv);
}
}
// Parallel traversal join and finalization
if (useParallelCull)
{
for (auto& fut : parallelCullTasks)
{
CascadeCullResult res = fut.get();
if (!res.rendered)
continue;
// Finalize perspective shadow map adjustments if needed.
if (!orthographicViewFrustum && settings->getShadowMapProjectionHint()==ShadowSettings::PERSPECTIVE_SHADOW_MAP)
{
double useNear = reducedNear;
double useFar = reducedFar;
if (isCascaded && res.sm_i < cascadePreps.size())
{
useNear = cascadePreps[res.sm_i].split.nearPlane;
useFar = cascadePreps[res.sm_i].split.farPlane;
}
assignValidRegionSettings(cv, res.camera, res.sm_i, vddUniforms);
adjustPerspectiveShadowMapCameraSettings(res.cb->getRenderStage(), frustum, pl, res.camera.get(), useNear, useFar);
if (res.cb->getProjectionMatrix())
res.cb->getProjectionMatrix()->set(res.camera->getProjectionMatrix());
}
// Assign main-scene shadow uniforms now.
assignShadowStateSettings(cv, res.camera, res.sm_i, vddUniforms);
// Schedule the actual shadow RTT render on the main CullVisitor so draw traversal executes it.
// The worker cull built an isolated render graph not used by the main SceneView.
// By culling the camera here, we link its pre-render stage into the main render graph for this frame.
if (_shadowCastingStateSet.valid())
{
cv.pushStateSet(_shadowCastingStateSet.get());
cullShadowCastingScene(&cv, res.camera.get());
cv.popStateSet();
}
if (_debugHud)
_debugHud->draw(res.sd-> _texture, res.sm_i, res.camera->getViewMatrix() * res.camera->getProjectionMatrix(), cv);
}
}
}
vdd->setNumValidShadows(numValidShadows);
@ -1683,7 +1869,7 @@ void MWShadowTechnique::createShaders()
{
case(ShadowSettings::NO_SHADERS):
{
OSG_INFO<<"No shaders provided by, user must supply own shaders"<<std::endl;
OSG_WARN<<"No shaders provided; user must supply own shaders"<<std::endl;
break;
}
case(ShadowSettings::PROVIDE_VERTEX_AND_FRAGMENT_SHADER):
@ -1783,10 +1969,7 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu
result_mask = result_mask | selector_mask;
}
}
OSG_INFO<<" planes.size() = "<<planes.size()<<std::endl;
OSG_INFO<<" planes.getResultMask() = "<<polytope.getResultMask()<<std::endl;
OSG_INFO<<" resultMask = "<<result_mask<<std::endl;
polytope.setResultMask(result_mask);
}
else
@ -1799,6 +1982,7 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu
double d = planes[i].distance(positionedLight.lightPos3);
OSG_INFO<<" plane "<<planes[i]<<" planes["<<i<<"].distance(lightPos3)="<<d;
if (d<0.0)
{
OSG_INFO<<" Need remove side "<<i<<std::endl;
@ -1810,9 +1994,10 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu
result_mask = result_mask | selector_mask;
}
}
OSG_INFO<<" planes.size() = "<<planes.size()<<std::endl;
OSG_INFO<<" planes.getResultMask() = "<<polytope.getResultMask()<<std::endl;
OSG_INFO<<" resultMask = "<<result_mask<<std::endl;
OSG_INFO<<" resultMask = "<<result_mask<<std::endl;
polytope.setResultMask(result_mask);
}
@ -1865,7 +2050,7 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu
const osg::Polytope::PlaneList& planes = lightVolumePolytope.getPlaneList();
for(unsigned int i=0; i<planes.size(); ++i)
{
OSG_INFO<<" plane "<<planes[i]<<" "<<((lightVolumePolytope.getResultMask() & (0x1<<i))?"on":"off")<<std::endl;
}
return lightVolumePolytope;
@ -1928,7 +2113,7 @@ bool MWShadowTechnique::computeShadowCameraSettings(Frustum& frustum, LightData&
zMax = osg::maximum( zMax, cornerInLightCoords.z());
}
OSG_INFO<<"before bs xMin="<<xMin<<", xMax="<<xMax<<", yMin="<<yMin<<", yMax="<<yMax<<", zMin="<<zMin<<", zMax="<<zMax<<std::endl;
OSG_INFO<<"before bs xMin="<<xMin<<", xMax="<<xMax<<", yMin="<<yMin<<", yMax="<<yMax<<", zMin="<<zMin<<", zMax="<<zMax<<std::endl;
osg::BoundingSphere bs = _shadowedScene->getBound();
osg::Vec3d modelCenterRelativeFrustumCenter(bs.center()-frustum.center);
@ -1963,6 +2148,7 @@ bool MWShadowTechnique::computeShadowCameraSettings(Frustum& frustum, LightData&
{
double zMax=-dbl_max;
OSG_INFO<<"lightDir = "<<positionedLight.lightDir<<std::endl;
OSG_INFO<<"lightPos3 = "<<positionedLight.lightPos3<<std::endl;
for(Frustum::Vertices::iterator itr = frustum.corners.begin();
@ -2735,7 +2921,7 @@ bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::Render
#if 0
OSG_NOTICE<<"ws ConvexHull xMin="<<convexHull.min(0)<<", xMax="<<convexHull.max(0)<<std::endl;
OSG_NOTICE<<"ws ConvexHull yMin="<<convexHull.min(1)<<", yMax="<<convexHull.max(1)<<std::endl;
OSG_NOTICE<<"ws ConvexHull zMin="<<convexHull.min(2)<<", zMax="<<convexHull.max(2)<<std::endl;
OSG_NOTICE<<"ws ConvexHull zMin="<<convexHull.min(2)<<", zMax="<<convexHull.max(2)<<std::endl;
convexHull.output(osg::notify(osg::NOTICE));
#endif
@ -2912,7 +3098,7 @@ bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::Render
#if 0
if (eye_ls.y()>-1.0)
{
OSG_NOTICE<<"Eye point within light space - use standard shadow map."<<std::endl;
return true;
}
#endif

View File

@ -23,6 +23,7 @@
#include <array>
#include <mutex>
#include <string>
#include <limits>
#include <osg/Camera>
#include <osg/Material>
@ -90,6 +91,11 @@ namespace SceneUtil {
virtual void setupCastingShader(Shader::ShaderManager &shaderManager);
// Experimental: enable parallel shadow preparation/culling
void setParallelShadowCullingEnabled(bool enabled) { mParallelShadowCullingEnabled = enabled; }
// Experimental: enable parallel per-cascade RTT traversal
void setParallelShadowTraversalEnabled(bool enabled) { mParallelShadowTraversalEnabled = enabled; }
class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack
{
public:
@ -272,6 +278,7 @@ namespace SceneUtil {
void setWorldMask(unsigned int worldMask) { _worldMask = worldMask; }
osg::ref_ptr<osg::StateSet> getOrCreateShadowsBinStateSet();
osg::StateSet* getCastingStateSet() const { return _shadowCastingStateSet.get(); }
protected:
virtual ~MWShadowTechnique();
@ -309,6 +316,9 @@ namespace SceneUtil {
unsigned int _worldMask = ~0u;
bool mParallelShadowCullingEnabled = false;
bool mParallelShadowTraversalEnabled = false;
class DebugHUD final : public osg::Referenced
{
public:

View File

@ -80,6 +80,9 @@ namespace SceneUtil
mShadowTechnique->enableDebugHUD();
else
mShadowTechnique->disableDebugHUD();
mShadowTechnique->setParallelShadowCullingEnabled(settings.mParallelShadowCulling);
mShadowTechnique->setParallelShadowTraversalEnabled(settings.mParallelShadowTraversal);
}
void ShadowManager::disableShadowsForStateSet(osg::StateSet& stateset) const

View File

@ -42,6 +42,8 @@ namespace Settings
SettingValue<bool> mTerrainShadows{ mIndex, "Shadows", "terrain shadows" };
SettingValue<bool> mObjectShadows{ mIndex, "Shadows", "object shadows" };
SettingValue<bool> mEnableIndoorShadows{ mIndex, "Shadows", "enable indoor shadows" };
SettingValue<bool> mParallelShadowCulling{ mIndex, "Shadows", "parallel shadow culling" };
SettingValue<bool> mParallelShadowTraversal{ mIndex, "Shadows", "parallel shadow traversal" };
};
}

View File

@ -1021,6 +1021,12 @@ wait for all jobs on exit = false
# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.
enable shadows = false
# compute per-cascade prep and cull RTT cameras in parallel
parallel shadow culling = false
# compute per-cascade RTT traversal in parallel
parallel shadow traversal = false
# How many shadow maps to use - more of these means each shadow map texel covers less area, producing better looking shadows, but may decrease performance.
number of shadow maps = 3