commit
cb5a57e41b
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,287 @@
|
|||||||
|
/* This file is based on OpenSceneGraph's include/osgShadow/ViewDependentShadowMap.
|
||||||
|
* Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL.
|
||||||
|
* The original copyright notice is listed below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 Robert Osfield
|
||||||
|
*
|
||||||
|
* This library is open source and may be redistributed and/or modified under
|
||||||
|
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
|
||||||
|
* (at your option) any later version. The full license is in LICENSE file
|
||||||
|
* included with this distribution, and on the openscenegraph.org website.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* OpenSceneGraph Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H
|
||||||
|
#define COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H 1
|
||||||
|
|
||||||
|
#include <osg/Camera>
|
||||||
|
#include <osg/Material>
|
||||||
|
#include <osg/MatrixTransform>
|
||||||
|
#include <osg/LightSource>
|
||||||
|
#include <osg/PolygonOffset>
|
||||||
|
|
||||||
|
#include <osgShadow/ShadowTechnique>
|
||||||
|
|
||||||
|
#include <components/shader/shadermanager.hpp>
|
||||||
|
#include <components/terrain/quadtreeworld.hpp>
|
||||||
|
|
||||||
|
namespace SceneUtil {
|
||||||
|
|
||||||
|
/** ViewDependentShadowMap provides an base implementation of view dependent shadow mapping techniques.*/
|
||||||
|
class MWShadowTechnique : public osgShadow::ShadowTechnique
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MWShadowTechnique();
|
||||||
|
|
||||||
|
MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
|
||||||
|
|
||||||
|
META_Object(SceneUtil, MWShadowTechnique);
|
||||||
|
|
||||||
|
/** initialize the ShadowedScene and local cached data structures.*/
|
||||||
|
virtual void init();
|
||||||
|
|
||||||
|
/** run the update traversal of the ShadowedScene and update any loca chached data structures.*/
|
||||||
|
virtual void update(osg::NodeVisitor& nv);
|
||||||
|
|
||||||
|
/** run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/
|
||||||
|
virtual void cull(osgUtil::CullVisitor& cv);
|
||||||
|
|
||||||
|
/** Resize any per context GLObject buffers to specified size. */
|
||||||
|
virtual void resizeGLObjectBuffers(unsigned int maxSize);
|
||||||
|
|
||||||
|
/** If State is non-zero, this function releases any associated OpenGL objects for
|
||||||
|
* the specified graphics context. Otherwise, releases OpenGL objects
|
||||||
|
* for all graphics contexts. */
|
||||||
|
virtual void releaseGLObjects(osg::State* = 0) const;
|
||||||
|
|
||||||
|
/** Clean scene graph from any shadow technique specific nodes, state and drawables.*/
|
||||||
|
virtual void cleanSceneGraph();
|
||||||
|
|
||||||
|
virtual void enableShadows();
|
||||||
|
|
||||||
|
virtual void disableShadows();
|
||||||
|
|
||||||
|
virtual void enableDebugHUD();
|
||||||
|
|
||||||
|
virtual void disableDebugHUD();
|
||||||
|
|
||||||
|
virtual void setSplitPointUniformLogarithmicRatio(double ratio);
|
||||||
|
|
||||||
|
virtual void setSplitPointDeltaBias(double bias);
|
||||||
|
|
||||||
|
virtual void setPolygonOffset(float factor, float units);
|
||||||
|
|
||||||
|
virtual void enableFrontFaceCulling();
|
||||||
|
|
||||||
|
virtual void disableFrontFaceCulling();
|
||||||
|
|
||||||
|
virtual void setupCastingShader(Shader::ShaderManager &shaderManager);
|
||||||
|
|
||||||
|
class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix);
|
||||||
|
|
||||||
|
void apply(osg::Node& node);
|
||||||
|
|
||||||
|
void apply(osg::Drawable& drawable);
|
||||||
|
|
||||||
|
void apply(Terrain::QuadTreeWorld& quadTreeWorld);
|
||||||
|
|
||||||
|
void apply(osg::Billboard&);
|
||||||
|
|
||||||
|
void apply(osg::Projection&);
|
||||||
|
|
||||||
|
void apply(osg::Transform& transform);
|
||||||
|
|
||||||
|
void apply(osg::Camera&);
|
||||||
|
|
||||||
|
void updateBound(const osg::BoundingBox& bb);
|
||||||
|
|
||||||
|
void update(const osg::Vec3& v);
|
||||||
|
|
||||||
|
osg::BoundingBox _bb;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Frustum
|
||||||
|
{
|
||||||
|
Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar);
|
||||||
|
|
||||||
|
osg::Matrixd projectionMatrix;
|
||||||
|
osg::Matrixd modelViewMatrix;
|
||||||
|
|
||||||
|
typedef std::vector<osg::Vec3d> Vertices;
|
||||||
|
Vertices corners;
|
||||||
|
|
||||||
|
typedef std::vector<unsigned int> Indices;
|
||||||
|
typedef std::vector<Indices> Faces;
|
||||||
|
Faces faces;
|
||||||
|
|
||||||
|
typedef std::vector<Indices> Edges;
|
||||||
|
Edges edges;
|
||||||
|
|
||||||
|
osg::Vec3d eye;
|
||||||
|
osg::Vec3d centerNearPlane;
|
||||||
|
osg::Vec3d centerFarPlane;
|
||||||
|
osg::Vec3d center;
|
||||||
|
osg::Vec3d frustumCenterLine;
|
||||||
|
};
|
||||||
|
|
||||||
|
// forward declare
|
||||||
|
class ViewDependentData;
|
||||||
|
|
||||||
|
struct LightData : public osg::Referenced
|
||||||
|
{
|
||||||
|
LightData(ViewDependentData* vdd);
|
||||||
|
|
||||||
|
virtual void setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix);
|
||||||
|
|
||||||
|
ViewDependentData* _viewDependentData;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::RefMatrix> lightMatrix;
|
||||||
|
osg::ref_ptr<const osg::Light> light;
|
||||||
|
|
||||||
|
osg::Vec4d lightPos;
|
||||||
|
osg::Vec3d lightPos3;
|
||||||
|
osg::Vec3d lightDir;
|
||||||
|
bool directionalLight;
|
||||||
|
|
||||||
|
typedef std::vector<unsigned int> ActiveTextureUnits;
|
||||||
|
ActiveTextureUnits textureUnits;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::list< osg::ref_ptr<LightData> > LightDataList;
|
||||||
|
|
||||||
|
struct ShadowData : public osg::Referenced
|
||||||
|
{
|
||||||
|
ShadowData(ViewDependentData* vdd);
|
||||||
|
|
||||||
|
virtual void releaseGLObjects(osg::State* = 0) const;
|
||||||
|
|
||||||
|
ViewDependentData* _viewDependentData;
|
||||||
|
|
||||||
|
unsigned int _textureUnit;
|
||||||
|
osg::ref_ptr<osg::Texture2D> _texture;
|
||||||
|
osg::ref_ptr<osg::TexGen> _texgen;
|
||||||
|
osg::ref_ptr<osg::Camera> _camera;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::list< osg::ref_ptr<ShadowData> > ShadowDataList;
|
||||||
|
|
||||||
|
|
||||||
|
class ViewDependentData : public osg::Referenced
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ViewDependentData(MWShadowTechnique* vdsm);
|
||||||
|
|
||||||
|
const MWShadowTechnique* getViewDependentShadowMap() const { return _viewDependentShadowMap; }
|
||||||
|
|
||||||
|
LightDataList& getLightDataList() { return _lightDataList; }
|
||||||
|
|
||||||
|
ShadowDataList& getShadowDataList() { return _shadowDataList; }
|
||||||
|
|
||||||
|
osg::StateSet* getStateSet() { return _stateset.get(); }
|
||||||
|
|
||||||
|
virtual void releaseGLObjects(osg::State* = 0) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ~ViewDependentData() {}
|
||||||
|
|
||||||
|
MWShadowTechnique* _viewDependentShadowMap;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::StateSet> _stateset;
|
||||||
|
|
||||||
|
LightDataList _lightDataList;
|
||||||
|
ShadowDataList _shadowDataList;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv);
|
||||||
|
|
||||||
|
ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
virtual void createShaders();
|
||||||
|
|
||||||
|
virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const;
|
||||||
|
|
||||||
|
virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight);
|
||||||
|
|
||||||
|
virtual bool computeShadowCameraSettings(Frustum& frustum, LightData& positionedLight, osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix);
|
||||||
|
|
||||||
|
virtual bool cropShadowCameraToMainFrustum(Frustum& frustum, osg::Camera* camera, double viewNear, double viewFar, std::vector<osg::Plane>& planeList);
|
||||||
|
|
||||||
|
virtual bool adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& positionedLight, osg::Camera* camera, double viewNear, double viewFar);
|
||||||
|
|
||||||
|
virtual bool assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Camera* camera, unsigned int textureUnit, osg::TexGen* texgen);
|
||||||
|
|
||||||
|
virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const;
|
||||||
|
|
||||||
|
virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const;
|
||||||
|
|
||||||
|
virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ~MWShadowTechnique();
|
||||||
|
|
||||||
|
typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr<ViewDependentData> > ViewDependentDataMap;
|
||||||
|
mutable OpenThreads::Mutex _viewDependentDataMapMutex;
|
||||||
|
ViewDependentDataMap _viewDependentDataMap;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::StateSet> _shadowRecievingPlaceholderStateSet;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::StateSet> _shadowCastingStateSet;
|
||||||
|
osg::ref_ptr<osg::PolygonOffset> _polygonOffset;
|
||||||
|
osg::ref_ptr<osg::Texture2D> _fallbackBaseTexture;
|
||||||
|
osg::ref_ptr<osg::Texture2D> _fallbackShadowMapTexture;
|
||||||
|
|
||||||
|
typedef std::vector< osg::ref_ptr<osg::Uniform> > Uniforms;
|
||||||
|
mutable OpenThreads::Mutex _accessUniformsAndProgramMutex;
|
||||||
|
Uniforms _uniforms;
|
||||||
|
osg::ref_ptr<osg::Program> _program;
|
||||||
|
|
||||||
|
bool _enableShadows;
|
||||||
|
|
||||||
|
double _splitPointUniformLogRatio = 0.5;
|
||||||
|
double _splitPointDeltaBias = 0.0;
|
||||||
|
|
||||||
|
float _polygonOffsetFactor = 1.1;
|
||||||
|
float _polygonOffsetUnits = 4.0;
|
||||||
|
|
||||||
|
bool _useFrontFaceCulling = true;
|
||||||
|
|
||||||
|
class DebugHUD : public osg::Referenced
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DebugHUD(int numberOfShadowMapsPerLight);
|
||||||
|
|
||||||
|
virtual void draw(osg::ref_ptr<osg::Texture2D> texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv);
|
||||||
|
|
||||||
|
virtual void releaseGLObjects(osg::State* state = 0) const;
|
||||||
|
|
||||||
|
virtual void setFrustumVertices(osg::ref_ptr<osg::Vec3Array> vertices, unsigned int traversalNumber);
|
||||||
|
protected:
|
||||||
|
virtual void addAnotherShadowMap();
|
||||||
|
|
||||||
|
static const int sDebugTextureUnit = 0;
|
||||||
|
|
||||||
|
std::vector<osg::ref_ptr<osg::Camera>> mDebugCameras;
|
||||||
|
osg::ref_ptr<osg::Program> mDebugProgram;
|
||||||
|
std::vector<osg::ref_ptr<osg::Node>> mDebugGeometry;
|
||||||
|
std::vector<osg::ref_ptr<osg::Group>> mFrustumTransforms;
|
||||||
|
std::vector<osg::ref_ptr<osg::Uniform>> mFrustumUniforms;
|
||||||
|
std::vector<osg::ref_ptr<osg::Geometry>> mFrustumGeometries;
|
||||||
|
};
|
||||||
|
|
||||||
|
osg::ref_ptr<DebugHUD> _debugHud;
|
||||||
|
osg::ref_ptr<osg::Program> _castingProgram;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,158 @@
|
|||||||
|
#include "shadow.hpp"
|
||||||
|
|
||||||
|
#include <osgShadow/ShadowedScene>
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
using namespace osgShadow;
|
||||||
|
|
||||||
|
void ShadowManager::setupShadowSettings()
|
||||||
|
{
|
||||||
|
mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows");
|
||||||
|
|
||||||
|
if (!mEnableShadows)
|
||||||
|
{
|
||||||
|
mShadowTechnique->disableShadows();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mShadowTechnique->enableShadows();
|
||||||
|
|
||||||
|
mShadowSettings->setLightNum(0);
|
||||||
|
mShadowSettings->setReceivesShadowTraversalMask(~0u);
|
||||||
|
|
||||||
|
int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows");
|
||||||
|
mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight);
|
||||||
|
mShadowSettings->setBaseShadowTextureUnit(8 - numberOfShadowMapsPerLight);
|
||||||
|
|
||||||
|
mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows"));
|
||||||
|
if (Settings::Manager::getBool("compute tight scene bounds", "Shadows"))
|
||||||
|
mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES);
|
||||||
|
|
||||||
|
int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows");
|
||||||
|
mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres));
|
||||||
|
|
||||||
|
mShadowTechnique->setSplitPointUniformLogarithmicRatio(Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows"));
|
||||||
|
mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows"));
|
||||||
|
|
||||||
|
mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), Settings::Manager::getFloat("polygon offset units", "Shadows"));
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("use front face culling", "Shadows"))
|
||||||
|
mShadowTechnique->enableFrontFaceCulling();
|
||||||
|
else
|
||||||
|
mShadowTechnique->disableFrontFaceCulling();
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("allow shadow map overlap", "Shadows"))
|
||||||
|
mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED);
|
||||||
|
else
|
||||||
|
mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT);
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("enable debug hud", "Shadows"))
|
||||||
|
mShadowTechnique->enableDebugHUD();
|
||||||
|
else
|
||||||
|
mShadowTechnique->disableDebugHUD();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowManager::disableShadowsForStateSet(osg::ref_ptr<osg::StateSet> stateset)
|
||||||
|
{
|
||||||
|
int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows");
|
||||||
|
int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Image> fakeShadowMapImage = new osg::Image();
|
||||||
|
fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
|
||||||
|
*(float*)fakeShadowMapImage->data() = std::numeric_limits<float>::infinity();
|
||||||
|
osg::ref_ptr<osg::Texture> fakeShadowMapTexture = new osg::Texture2D(fakeShadowMapImage);
|
||||||
|
fakeShadowMapTexture->setShadowComparison(true);
|
||||||
|
fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS);
|
||||||
|
for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i)
|
||||||
|
{
|
||||||
|
stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
|
||||||
|
stateset->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i));
|
||||||
|
stateset->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowManager::ShadowManager(osg::ref_ptr<osg::Group> sceneRoot, osg::ref_ptr<osg::Group> rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene),
|
||||||
|
mShadowTechnique(new MWShadowTechnique),
|
||||||
|
mOutdoorShadowCastingMask(outdoorShadowCastingMask),
|
||||||
|
mIndoorShadowCastingMask(indoorShadowCastingMask)
|
||||||
|
{
|
||||||
|
mShadowedScene->setShadowTechnique(mShadowTechnique);
|
||||||
|
|
||||||
|
mShadowedScene->addChild(sceneRoot);
|
||||||
|
rootNode->addChild(mShadowedScene);
|
||||||
|
|
||||||
|
mShadowSettings = mShadowedScene->getShadowSettings();
|
||||||
|
setupShadowSettings();
|
||||||
|
|
||||||
|
mShadowTechnique->setupCastingShader(shaderManager);
|
||||||
|
|
||||||
|
enableOutdoorMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines()
|
||||||
|
{
|
||||||
|
if (!mEnableShadows)
|
||||||
|
return getShadowsDisabledDefines();
|
||||||
|
|
||||||
|
Shader::ShaderManager::DefineMap definesWithShadows;
|
||||||
|
|
||||||
|
definesWithShadows["shadows_enabled"] = "1";
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < mShadowSettings->getNumShadowMapsPerLight(); ++i)
|
||||||
|
definesWithShadows["shadow_texture_unit_list"] += std::to_string(i) + ",";
|
||||||
|
// remove extra comma
|
||||||
|
definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr(0, definesWithShadows["shadow_texture_unit_list"].length() - 1);
|
||||||
|
|
||||||
|
definesWithShadows["shadowMapsOverlap"] = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0";
|
||||||
|
|
||||||
|
definesWithShadows["useShadowDebugOverlay"] = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0";
|
||||||
|
|
||||||
|
// switch this to reading settings if it's ever exposed to the user
|
||||||
|
definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0";
|
||||||
|
|
||||||
|
definesWithShadows["disableNormalOffsetShadows"] = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0";
|
||||||
|
|
||||||
|
definesWithShadows["shadowNormalOffset"] = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows"));
|
||||||
|
|
||||||
|
return definesWithShadows;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shader::ShaderManager::DefineMap ShadowManager::getShadowsDisabledDefines()
|
||||||
|
{
|
||||||
|
Shader::ShaderManager::DefineMap definesWithoutShadows;
|
||||||
|
|
||||||
|
definesWithoutShadows["shadows_enabled"] = "0";
|
||||||
|
|
||||||
|
definesWithoutShadows["shadow_texture_unit_list"] = "";
|
||||||
|
|
||||||
|
definesWithoutShadows["shadowMapsOverlap"] = "0";
|
||||||
|
|
||||||
|
definesWithoutShadows["useShadowDebugOverlay"] = "0";
|
||||||
|
|
||||||
|
definesWithoutShadows["perspectiveShadowMaps"] = "0";
|
||||||
|
|
||||||
|
definesWithoutShadows["disableNormalOffsetShadows"] = "0";
|
||||||
|
|
||||||
|
definesWithoutShadows["shadowNormalOffset"] = "0.0";
|
||||||
|
|
||||||
|
return definesWithoutShadows;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowManager::enableIndoorMode()
|
||||||
|
{
|
||||||
|
if (Settings::Manager::getBool("enable indoor shadows", "Shadows"))
|
||||||
|
mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask);
|
||||||
|
else
|
||||||
|
mShadowTechnique->disableShadows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowManager::enableOutdoorMode()
|
||||||
|
{
|
||||||
|
if (mEnableShadows)
|
||||||
|
mShadowTechnique->enableShadows();
|
||||||
|
mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef COMPONENTS_SCENEUTIL_SHADOW_H
|
||||||
|
#define COMPONENTS_SCENEUTIL_SHADOW_H
|
||||||
|
|
||||||
|
#include <osgShadow/ShadowSettings>
|
||||||
|
#include <osgShadow/ShadowedScene>
|
||||||
|
|
||||||
|
#include <components/shader/shadermanager.hpp>
|
||||||
|
|
||||||
|
#include "mwshadowtechnique.hpp"
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
class ShadowManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void disableShadowsForStateSet(osg::ref_ptr<osg::StateSet> stateSet);
|
||||||
|
|
||||||
|
ShadowManager(osg::ref_ptr<osg::Group> sceneRoot, osg::ref_ptr<osg::Group> rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager);
|
||||||
|
|
||||||
|
virtual ~ShadowManager() = default;
|
||||||
|
|
||||||
|
virtual void setupShadowSettings();
|
||||||
|
|
||||||
|
virtual Shader::ShaderManager::DefineMap getShadowDefines();
|
||||||
|
|
||||||
|
virtual Shader::ShaderManager::DefineMap getShadowsDisabledDefines();
|
||||||
|
|
||||||
|
virtual void enableIndoorMode();
|
||||||
|
|
||||||
|
virtual void enableOutdoorMode();
|
||||||
|
protected:
|
||||||
|
bool mEnableShadows;
|
||||||
|
|
||||||
|
osg::ref_ptr<osgShadow::ShadowedScene> mShadowedScene;
|
||||||
|
osg::ref_ptr<osgShadow::ShadowSettings> mShadowSettings;
|
||||||
|
osg::ref_ptr<MWShadowTechnique> mShadowTechnique;
|
||||||
|
|
||||||
|
unsigned int mOutdoorShadowCastingMask;
|
||||||
|
unsigned int mIndoorShadowCastingMask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //COMPONENTS_SCENEUTIL_SHADOW_H
|
@ -0,0 +1,205 @@
|
|||||||
|
Shadow Settings
|
||||||
|
###############
|
||||||
|
|
||||||
|
Main settings
|
||||||
|
*************
|
||||||
|
|
||||||
|
enable shadows
|
||||||
|
--------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Enable or disable the rendering of shadows.
|
||||||
|
Unlike in the original Morrowind engine, 'Shadow Mapping' is used, which can have a performance impact, but has more realistic results.
|
||||||
|
Bear in mind that this will force OpenMW to use shaders as if :ref:`force-shaders-label` was enabled.
|
||||||
|
A keen developer may be able to implement compatibility with fixed-function mode using the advice of `this post <https://github.com/OpenMW/openmw/pull/1547#issuecomment-369657381>`_, but it may be more difficult than it seems.
|
||||||
|
|
||||||
|
|
||||||
|
number of shadow maps
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
:Type: integer
|
||||||
|
:Range: 1 to 8, but higher values may conflict with other texture effects
|
||||||
|
:Default: 3
|
||||||
|
|
||||||
|
Control 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.
|
||||||
|
Using too many shadow maps will lead to them overriding texture slots used for other effects, producing unpleasant artefacts.
|
||||||
|
A value of three is recommended in most cases, but other values may produce better results or performance.
|
||||||
|
|
||||||
|
allow shadow map overlap
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: True
|
||||||
|
|
||||||
|
If true, allow shadow maps to overlap.
|
||||||
|
Counter-intuitively, will produce much better results when the light is behind the camera.
|
||||||
|
When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps.
|
||||||
|
|
||||||
|
enable debug hud
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Enable or disable the debug hud to see what the shadow map(s) contain.
|
||||||
|
This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings.
|
||||||
|
|
||||||
|
enable debug overlay
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Enable or disable the debug overlay to see the area covered by each shadow map.
|
||||||
|
This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings.
|
||||||
|
|
||||||
|
compute tight scene bounds
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area.
|
||||||
|
This can be especially helpful when looking downwards with a high viewing distance but will be less useful with the default value.
|
||||||
|
The performance impact of this may be very large.
|
||||||
|
|
||||||
|
shadow map resolution
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
:Type: integer
|
||||||
|
:Range: Dependent on GPU/driver combination
|
||||||
|
:Default: 1024
|
||||||
|
|
||||||
|
Control How large to make the shadow map(s).
|
||||||
|
Higher values increase GPU load but can produce better-looking results.
|
||||||
|
Power-of-two values may turn out to be faster than smaller values which are not powers of two on some GPU/driver combinations.
|
||||||
|
|
||||||
|
actor shadows
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Allow actors to cast shadows.
|
||||||
|
Potentially decreases performance.
|
||||||
|
|
||||||
|
player shadows
|
||||||
|
--------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Allow the player to cast shadows.
|
||||||
|
Potentially decreases performance.
|
||||||
|
|
||||||
|
terrain shadows
|
||||||
|
---------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Allow terrain to cast shadows.
|
||||||
|
Potentially decreases performance.
|
||||||
|
|
||||||
|
object shadows
|
||||||
|
--------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Allow static objects to cast shadows.
|
||||||
|
Potentially decreases performance.
|
||||||
|
|
||||||
|
enable indoor shadows
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Allow shadows indoors.
|
||||||
|
Due to limitations with Morrowind's data, only actors can cast shadows indoors without the ceiling casting a shadow everywhere.
|
||||||
|
Some might feel this is distracting as shadows can be cast through other objects, so indoor shadows can be disabled completely.
|
||||||
|
|
||||||
|
Expert settings
|
||||||
|
***************
|
||||||
|
|
||||||
|
These settings are probably too complicated for regular users to judge what might be good values to set them to.
|
||||||
|
If you've got a good understanding of how shadow mapping works, or you've got enough time to try a large set of values, you may get better results tuning these yourself.
|
||||||
|
Copying values from another user who's done careful tuning is the recommended way of arriving at an optimal value for these settings.
|
||||||
|
|
||||||
|
Understanding what some of these do might be easier for people who've read `this paper on Parallel Split Shadow Maps <https://pdfs.semanticscholar.org/15a9/f2a7cf6b1494f45799617c017bd42659d753.pdf>`_ and understood how they interact with the transformation used with Light Space Perspective Shadow Maps.
|
||||||
|
|
||||||
|
polygon offset factor
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
:Type: float
|
||||||
|
:Range: Theoretically the whole range of 32-bit floating point, but values just above 1.0 are most sensible.
|
||||||
|
:Default: 1.1
|
||||||
|
|
||||||
|
Used as the factor parameter for the polygon offset used for shadow map rendering.
|
||||||
|
Higher values reduce shadow flicker, but risk increasing Peter Panning.
|
||||||
|
See `the OpenGL documentation for glPolygonOffset <https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml>`_ for details.
|
||||||
|
|
||||||
|
polygon offset units
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
:Type: float
|
||||||
|
:Range: Theoretically the whole range of 32-bit floating point, but values between 1 and 10 are most sensible.
|
||||||
|
:Default: 4.0
|
||||||
|
|
||||||
|
Used as the units parameter for the polygon offset used for shadow map rendering.
|
||||||
|
Higher values reduce shadow flicker, but risk increasing Peter Panning.
|
||||||
|
See `the OpenGL documentation for glPolygonOffset <https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml>`_ for details.
|
||||||
|
|
||||||
|
use front face culling
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: True
|
||||||
|
|
||||||
|
Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance.
|
||||||
|
In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects.
|
||||||
|
|
||||||
|
split point uniform logarithmic ratio
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
:Type: float
|
||||||
|
:Range: 0.0-1.0 for sensible results. Other values may 'work' but could behave bizarrely.
|
||||||
|
:Default: 0.5
|
||||||
|
|
||||||
|
Controls the ratio of :math:`C_i^{log}` versus :math:`C_i^{uniform}` used to form the Practical Split Scheme as described in the linked paper.
|
||||||
|
When using a larger-than-default viewing distance and distant terrain, and you have `allow shadow map overlap`_ enabled, larger values will prevent nearby shadows losing quality.
|
||||||
|
It is therefore recommended that this isn't left at the default when the viewing distance is changed.
|
||||||
|
|
||||||
|
split point bias
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:Type: float
|
||||||
|
:Range: Any value supported by C++ floats on your platform, although undesirable behaviour is more likely to appear the further the value is from zero.
|
||||||
|
:Default: 0.0
|
||||||
|
|
||||||
|
The :math:`\delta_{bias}` parameter used to form the Practical Split Scheme as described in the linked paper.
|
||||||
|
|
||||||
|
minimum lispsm near far ratio
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
:Type: float
|
||||||
|
:Range: Must be greater than zero.
|
||||||
|
:Default: 0.25
|
||||||
|
|
||||||
|
Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation.
|
||||||
|
Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera.
|
||||||
|
Increasing this pushes detail further away by moving the frustum apex further from the near plane.
|
@ -0,0 +1,21 @@
|
|||||||
|
#version 120
|
||||||
|
|
||||||
|
uniform sampler2D diffuseMap;
|
||||||
|
varying vec2 diffuseMapUV;
|
||||||
|
|
||||||
|
varying float alphaPassthrough;
|
||||||
|
|
||||||
|
uniform bool useDiffuseMapForShadowAlpha;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_FragData[0].rgb = vec3(1.0);
|
||||||
|
if (useDiffuseMapForShadowAlpha)
|
||||||
|
gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * alphaPassthrough;
|
||||||
|
else
|
||||||
|
gl_FragData[0].a = alphaPassthrough;
|
||||||
|
|
||||||
|
// Prevent translucent things casting shadow (including the player using an invisibility effect)
|
||||||
|
if (gl_FragData[0].a <= 0.5)
|
||||||
|
discard;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
#version 120
|
||||||
|
|
||||||
|
varying vec2 diffuseMapUV;
|
||||||
|
|
||||||
|
varying float alphaPassthrough;
|
||||||
|
|
||||||
|
uniform int colorMode;
|
||||||
|
uniform bool useDiffuseMapForShadowAlpha;
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
|
||||||
|
|
||||||
|
vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex);
|
||||||
|
gl_ClipVertex = viewPos;
|
||||||
|
|
||||||
|
if (useDiffuseMapForShadowAlpha)
|
||||||
|
diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
|
||||||
|
else
|
||||||
|
diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions
|
||||||
|
|
||||||
|
if (colorMode == 2)
|
||||||
|
alphaPassthrough = gl_Color.a;
|
||||||
|
else
|
||||||
|
// This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser
|
||||||
|
alphaPassthrough = gl_FrontMaterial.diffuse.a;
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
#define SHADOWS @shadows_enabled
|
||||||
|
|
||||||
|
#if SHADOWS
|
||||||
|
@foreach shadow_texture_unit_index @shadow_texture_unit_list
|
||||||
|
uniform sampler2DShadow shadowTexture@shadow_texture_unit_index;
|
||||||
|
varying vec4 shadowSpaceCoords@shadow_texture_unit_index;
|
||||||
|
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
varying vec4 shadowRegionCoords@shadow_texture_unit_index;
|
||||||
|
#endif
|
||||||
|
@endforeach
|
||||||
|
#endif // SHADOWS
|
||||||
|
|
||||||
|
float unshadowedLightRatio()
|
||||||
|
{
|
||||||
|
float shadowing = 1.0;
|
||||||
|
#if SHADOWS
|
||||||
|
#if @shadowMapsOverlap
|
||||||
|
bool doneShadows = false;
|
||||||
|
@foreach shadow_texture_unit_index @shadow_texture_unit_list
|
||||||
|
if (!doneShadows)
|
||||||
|
{
|
||||||
|
vec3 shadowXYZ = shadowSpaceCoords@shadow_texture_unit_index.xyz / shadowSpaceCoords@shadow_texture_unit_index.w;
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
vec3 shadowRegionXYZ = shadowRegionCoords@shadow_texture_unit_index.xyz / shadowRegionCoords@shadow_texture_unit_index.w;
|
||||||
|
#endif
|
||||||
|
if (all(lessThan(shadowXYZ.xy, vec2(1.0, 1.0))) && all(greaterThan(shadowXYZ.xy, vec2(0.0, 0.0))))
|
||||||
|
{
|
||||||
|
shadowing = min(shadow2DProj(shadowTexture@shadow_texture_unit_index, shadowSpaceCoords@shadow_texture_unit_index).r, shadowing);
|
||||||
|
|
||||||
|
|
||||||
|
doneShadows = all(lessThan(shadowXYZ, vec3(0.95, 0.95, 1.0))) && all(greaterThan(shadowXYZ, vec3(0.05, 0.05, 0.0)));
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
doneShadows = doneShadows && all(lessThan(shadowRegionXYZ, vec3(1.0, 1.0, 1.0))) && all(greaterThan(shadowRegionXYZ.xy, vec2(-1.0, -1.0)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endforeach
|
||||||
|
#else
|
||||||
|
@foreach shadow_texture_unit_index @shadow_texture_unit_list
|
||||||
|
shadowing = min(shadow2DProj(shadowTexture@shadow_texture_unit_index, shadowSpaceCoords@shadow_texture_unit_index).r, shadowing);
|
||||||
|
@endforeach
|
||||||
|
#endif
|
||||||
|
#endif // SHADOWS
|
||||||
|
return shadowing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyShadowDebugOverlay()
|
||||||
|
{
|
||||||
|
#if SHADOWS && @useShadowDebugOverlay
|
||||||
|
bool doneOverlay = false;
|
||||||
|
float colourIndex = 0.0;
|
||||||
|
@foreach shadow_texture_unit_index @shadow_texture_unit_list
|
||||||
|
if (!doneOverlay)
|
||||||
|
{
|
||||||
|
vec3 shadowXYZ = shadowSpaceCoords@shadow_texture_unit_index.xyz / shadowSpaceCoords@shadow_texture_unit_index.w;
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
vec3 shadowRegionXYZ = shadowRegionCoords@shadow_texture_unit_index.xyz / shadowRegionCoords@shadow_texture_unit_index.w;
|
||||||
|
#endif
|
||||||
|
if (all(lessThan(shadowXYZ.xy, vec2(1.0, 1.0))) && all(greaterThan(shadowXYZ.xy, vec2(0.0, 0.0))))
|
||||||
|
{
|
||||||
|
colourIndex = mod(@shadow_texture_unit_index.0, 3.0);
|
||||||
|
if (colourIndex < 1.0)
|
||||||
|
gl_FragData[0].x += 0.1;
|
||||||
|
else if (colourIndex < 2.0)
|
||||||
|
gl_FragData[0].y += 0.1;
|
||||||
|
else
|
||||||
|
gl_FragData[0].z += 0.1;
|
||||||
|
|
||||||
|
doneOverlay = all(lessThan(shadowXYZ, vec3(0.95, 0.95, 1.0))) && all(greaterThan(shadowXYZ, vec3(0.05, 0.05, 0.0)));
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
doneOverlay = doneOverlay && all(lessThan(shadowRegionXYZ.xyz, vec3(1.0, 1.0, 1.0))) && all(greaterThan(shadowRegionXYZ.xy, vec2(-1.0, -1.0)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endforeach
|
||||||
|
#endif // SHADOWS
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
#define SHADOWS @shadows_enabled
|
||||||
|
|
||||||
|
#if SHADOWS
|
||||||
|
@foreach shadow_texture_unit_index @shadow_texture_unit_list
|
||||||
|
uniform int shadowTextureUnit@shadow_texture_unit_index;
|
||||||
|
varying vec4 shadowSpaceCoords@shadow_texture_unit_index;
|
||||||
|
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
uniform mat4 validRegionMatrix@shadow_texture_unit_index;
|
||||||
|
varying vec4 shadowRegionCoords@shadow_texture_unit_index;
|
||||||
|
#endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
// Enabling this may reduce peter panning. Probably unnecessary.
|
||||||
|
const bool onlyNormalOffsetUV = false;
|
||||||
|
#endif // SHADOWS
|
||||||
|
|
||||||
|
void setupShadowCoords(vec4 viewPos, vec3 viewNormal)
|
||||||
|
{
|
||||||
|
#if SHADOWS
|
||||||
|
// This matrix has the opposite handedness to the others used here, so multiplication must have the vector to the left. Alternatively it could be transposed after construction, but that's extra work for the GPU just to make the code look a tiny bit cleaner.
|
||||||
|
mat4 eyePlaneMat;
|
||||||
|
vec4 shadowOffset;
|
||||||
|
@foreach shadow_texture_unit_index @shadow_texture_unit_list
|
||||||
|
eyePlaneMat = mat4(gl_EyePlaneS[shadowTextureUnit@shadow_texture_unit_index], gl_EyePlaneT[shadowTextureUnit@shadow_texture_unit_index], gl_EyePlaneR[shadowTextureUnit@shadow_texture_unit_index], gl_EyePlaneQ[shadowTextureUnit@shadow_texture_unit_index]);
|
||||||
|
|
||||||
|
#if @perspectiveShadowMaps
|
||||||
|
shadowRegionCoords@shadow_texture_unit_index = validRegionMatrix@shadow_texture_unit_index * viewPos;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if @disableNormalOffsetShadows
|
||||||
|
shadowSpaceCoords@shadow_texture_unit_index = viewPos * eyePlaneMat;
|
||||||
|
#else
|
||||||
|
shadowOffset = vec4(viewNormal * @shadowNormalOffset, 0.0);
|
||||||
|
|
||||||
|
if (onlyNormalOffsetUV)
|
||||||
|
{
|
||||||
|
shadowSpaceCoords@shadow_texture_unit_index = viewPos * eyePlaneMat;
|
||||||
|
|
||||||
|
vec4 lightSpaceXY = viewPos + shadowOffset;
|
||||||
|
lightSpaceXY = lightSpaceXY * eyePlaneMat;
|
||||||
|
|
||||||
|
shadowSpaceCoords@shadow_texture_unit_index.xy = lightSpaceXY.xy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vec4 offsetViewPosition = viewPos + shadowOffset;
|
||||||
|
shadowSpaceCoords@shadow_texture_unit_index = offsetViewPosition * eyePlaneMat;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
@endforeach
|
||||||
|
#endif // SHADOWS
|
||||||
|
}
|
Loading…
Reference in New Issue