mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-09-22 11:23:27 -04:00
Merge branch 'improve_shadows_slim' into 'master'
Parallel Shadow Culling and Traversal See merge request OpenMW/openmw!4881
This commit is contained in:
commit
b6bfc4c06a
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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" };
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user