ShaderVisitor: support automatic recognition of normal maps based on file pattern

Introduce new settings 'auto use object normal maps', 'auto use terrain normal maps', 'normal map pattern'
pull/903/head
scrawl 9 years ago
parent 6a0ac824bd
commit 5cf2441b10

@ -172,6 +172,8 @@ namespace MWRender
resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders"));
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders"));
resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders"));
resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders"));
osg::ref_ptr<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager;
sceneRoot->setLightingMask(Mask_Lighting);
@ -193,7 +195,8 @@ namespace MWRender
mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath));
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(),
new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get()));
new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain normal maps", "Shaders")),
Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get()));
mCamera.reset(new Camera(mViewer->getCamera()));

@ -9,21 +9,9 @@
namespace MWRender
{
TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload)
: ESMTerrain::Storage(vfs)
TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, bool autoUseNormalMaps)
: ESMTerrain::Storage(vfs, normalMapPattern, autoUseNormalMaps)
{
if (preload)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
MWWorld::Store<ESM::Land>::iterator it = esmStore.get<ESM::Land>().begin();
for (; it != esmStore.get<ESM::Land>().end(); ++it)
{
const ESM::Land* land = &*it;
land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX);
}
}
}
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)

@ -14,9 +14,7 @@ namespace MWRender
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
public:
///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this
/// should be set to "true" in order to avoid race conditions.
TerrainStorage(const VFS::Manager* vfs, bool preload);
TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false);
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);

@ -18,8 +18,10 @@ namespace ESMTerrain
const float defaultHeight = -2048;
Storage::Storage(const VFS::Manager *vfs)
Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, bool autoUseNormalMaps)
: mVFS(vfs)
, mNormalMapPattern(normalMapPattern)
, mAutoUseNormalMaps(autoUseNormalMaps)
{
}
@ -511,6 +513,8 @@ namespace ESMTerrain
info.mParallax = false;
info.mSpecular = false;
info.mDiffuseMap = texture;
/*
std::string texture_ = texture;
boost::replace_last(texture_, ".", "_nh.");
@ -519,21 +523,26 @@ namespace ESMTerrain
info.mNormalMap = texture_;
info.mParallax = true;
}
else
*/
if (mAutoUseNormalMaps)
{
texture_ = texture;
boost::replace_last(texture_, ".", "_n.");
std::string texture_ = texture;
boost::replace_last(texture_, ".", mNormalMapPattern + ".");
if (mVFS->exists(texture_))
info.mNormalMap = texture_;
}
texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec.");
if (mVFS->exists(texture_))
/*
{
info.mDiffuseMap = texture_;
info.mSpecular = true;
std::string texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec.");
if (mVFS->exists(texture_))
{
info.mDiffuseMap = texture_;
info.mSpecular = true;
}
}
*/
mLayerInfoMap[texture] = info;

@ -27,7 +27,7 @@ namespace ESMTerrain
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
public:
Storage(const VFS::Manager* vfs);
Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false);
/// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for
/// any of the data types specified via \a flags. Will also return a 0-pointer if there
@ -109,6 +109,9 @@ namespace ESMTerrain
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
OpenThreads::Mutex mLayerInfoMutex;
std::string mNormalMapPattern;
bool mAutoUseNormalMaps;
Terrain::LayerInfo getLayerInfo(const std::string& texture);
};

@ -121,6 +121,7 @@ namespace Resource
}
osg::Image* image = result.getImage();
image->setFileName(normalized);
if (!checkSupported(image, filename))
{
mCache->addEntryToObjectCache(normalized, mWarningImage);

@ -212,6 +212,7 @@ namespace Resource
, mForceShaders(false)
, mClampLighting(false)
, mForcePerPixelLighting(false)
, mAutoUseNormalMaps(false)
, mInstanceCache(new MultiObjectCache)
, mImageManager(imageManager)
, mNifFileManager(nifFileManager)
@ -235,7 +236,7 @@ namespace Resource
void SceneManager::recreateShaders(osg::ref_ptr<osg::Node> node)
{
Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl");
Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl");
shaderVisitor.setForceShaders(mForceShaders);
shaderVisitor.setClampLighting(mClampLighting);
shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting);
@ -263,6 +264,16 @@ namespace Resource
return mForcePerPixelLighting;
}
void SceneManager::setAutoUseNormalMaps(bool use)
{
mAutoUseNormalMaps = use;
}
void SceneManager::setNormalMapPattern(const std::string &pattern)
{
mNormalMapPattern = pattern;
}
SceneManager::~SceneManager()
{
// this has to be defined in the .cpp file as we can't delete incomplete types
@ -386,10 +397,12 @@ namespace Resource
SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy);
loaded->accept(setFilterSettingsControllerVisitor);
Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl");
Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl");
shaderVisitor.setForceShaders(mForceShaders);
shaderVisitor.setClampLighting(mClampLighting);
shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting);
shaderVisitor.setAutoUseNormalMaps(mAutoUseNormalMaps);
shaderVisitor.setNormalMapPattern(mNormalMapPattern);
loaded->accept(shaderVisitor);
// share state

@ -57,6 +57,12 @@ namespace Resource
void setForcePerPixelLighting(bool force);
bool getForcePerPixelLighting() const;
/// @see ShaderVisitor::setAutoUseNormalMaps
void setAutoUseNormalMaps(bool use);
/// @see ShaderVisitor::setNormalMapPattern
void setNormalMapPattern(const std::string& pattern);
void setShaderPath(const std::string& path);
/// Get a read-only copy of this scene "template"
@ -126,6 +132,8 @@ namespace Resource
bool mForceShaders;
bool mClampLighting;
bool mForcePerPixelLighting;
bool mAutoUseNormalMaps;
std::string mNormalMapPattern;
osg::ref_ptr<MultiObjectCache> mInstanceCache;

@ -5,10 +5,15 @@
#include <osg/Texture>
#include <osg/Material>
#include <osg/Geometry>
#include <osg/Image>
#include <osgUtil/TangentSpaceGenerator>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/vfs/manager.hpp>
#include "shadermanager.hpp"
@ -24,13 +29,20 @@ namespace Shader
{
}
ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate)
ShaderVisitor::ShaderRequirements::~ShaderRequirements()
{
}
ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mForceShaders(false)
, mClampLighting(false)
, mForcePerPixelLighting(false)
, mAllowedToModifyStateSets(true)
, mAutoUseNormalMaps(false)
, mShaderManager(shaderManager)
, mImageManager(imageManager)
, mDefaultVsTemplate(defaultVsTemplate)
, mDefaultFsTemplate(defaultFsTemplate)
{
@ -81,37 +93,69 @@ namespace Shader
if (mAllowedToModifyStateSets)
writableStateSet = node.getStateSet();
const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList();
for(unsigned int unit=0;unit<texAttributes.size();++unit)
if (texAttributes.size())
{
const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE);
if (attr)
const osg::Texture* diffuseMap = NULL;
const osg::Texture* normalMap = NULL;
for(unsigned int unit=0;unit<texAttributes.size();++unit)
{
const osg::Texture* texture = attr->asTexture();
if (texture)
const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE);
if (attr)
{
if (!texture->getName().empty())
const osg::Texture* texture = attr->asTexture();
if (texture)
{
mRequirements.back().mTextures[unit] = texture->getName();
if (texture->getName() == "normalMap")
if (!texture->getName().empty())
{
mRequirements.back().mTexStageRequiringTangents = unit;
mRequirements.back().mHasNormalMap = true;
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
// normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On
writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON);
mRequirements.back().mTextures[unit] = texture->getName();
if (texture->getName() == "normalMap")
{
mRequirements.back().mTexStageRequiringTangents = unit;
mRequirements.back().mHasNormalMap = true;
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
// normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On
writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON);
normalMap = texture;
}
if (texture->getName() == "diffuseMap")
diffuseMap = texture;
}
else
std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl;
}
else
std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl;
}
// remove state that has no effect when rendering with shaders
if (stateset->getTextureAttribute(unit, osg::StateAttribute::TEXENV))
{
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
writableStateSet->removeTextureAttribute(unit, osg::StateAttribute::TEXENV);
}
}
// remove state that has no effect when rendering with shaders
if (stateset->getTextureAttribute(unit, osg::StateAttribute::TEXENV))
if (mAutoUseNormalMaps && diffuseMap != NULL && normalMap == NULL)
{
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
writableStateSet->removeTextureAttribute(unit, osg::StateAttribute::TEXENV);
std::string normalMap = diffuseMap->getImage(0)->getFileName();
boost::replace_last(normalMap, ".", mNormalMapPattern + ".");
if (mImageManager.getVFS()->exists(normalMap))
{
osg::ref_ptr<osg::Texture2D> normalMapTex (new osg::Texture2D(mImageManager.getImage(normalMap)));
normalMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S));
normalMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T));
normalMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER));
normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER));
normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy());
normalMapTex->setName("normalMap");
int unit = texAttributes.size();
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON);
mRequirements.back().mTextures[unit] = "normalMap";
mRequirements.back().mTexStageRequiringTangents = unit;
mRequirements.back().mHasNormalMap = true;
}
}
}
@ -209,8 +253,12 @@ namespace Shader
if (!mRequirements.empty())
{
const ShaderRequirements& reqs = mRequirements.back();
if (reqs.mTexStageRequiringTangents != -1)
if (reqs.mTexStageRequiringTangents != -1 && mAllowedToModifyStateSets)
{
if (geometry.getTexCoordArray(reqs.mTexStageRequiringTangents) == NULL) // assign tex coord array for normal map if necessary
// the normal-map may be assigned on-the-fly in applyStateSet() when mAutoUseNormalMaps is true
geometry.setTexCoordArray(reqs.mTexStageRequiringTangents, geometry.getTexCoordArray(0));
osg::ref_ptr<osgUtil::TangentSpaceGenerator> generator (new osgUtil::TangentSpaceGenerator);
generator->generate(&geometry, reqs.mTexStageRequiringTangents);
@ -254,4 +302,14 @@ namespace Shader
mAllowedToModifyStateSets = allowed;
}
void ShaderVisitor::setAutoUseNormalMaps(bool use)
{
mAutoUseNormalMaps = use;
}
void ShaderVisitor::setNormalMapPattern(const std::string &pattern)
{
mNormalMapPattern = pattern;
}
}

@ -3,6 +3,11 @@
#include <osg/NodeVisitor>
namespace Resource
{
class ImageManager;
}
namespace Shader
{
@ -12,7 +17,7 @@ namespace Shader
class ShaderVisitor : public osg::NodeVisitor
{
public:
ShaderVisitor(ShaderManager& shaderManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate);
ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate);
/// By default, only bump mapped objects will have a shader added to them.
/// Setting force = true will cause all objects to render using shaders, regardless of having a bump map.
@ -30,6 +35,11 @@ namespace Shader
/// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering.
void setAllowedToModifyStateSets(bool allowed);
/// Automatically use normal maps if a file with suitable name exists (see normal map pattern).
void setAutoUseNormalMaps(bool use);
void setNormalMapPattern(const std::string& pattern);
virtual void apply(osg::Node& node);
virtual void apply(osg::Drawable& drawable);
@ -46,15 +56,22 @@ namespace Shader
bool mForcePerPixelLighting;
bool mAllowedToModifyStateSets;
bool mAutoUseNormalMaps;
std::string mNormalMapPattern;
ShaderManager& mShaderManager;
Resource::ImageManager& mImageManager;
struct ShaderRequirements
{
ShaderRequirements();
~ShaderRequirements();
// <texture stage, texture name>
std::map<int, std::string> mTextures;
osg::ref_ptr<osg::Texture> mDiffuseMap;
bool mHasNormalMap;
bool mColorMaterial;

@ -178,6 +178,18 @@ force per pixel lighting = false
# Setting this option to 'false' results in more realistic lighting.
clamp lighting = true
# If this option is enabled, normal maps are automatically recognized and used if they are named appropriately
# (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds).
# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (NIF file).
# Affects objects.
auto use object normal maps = false
# See 'auto use object normal maps'. Affects terrain.
auto use terrain normal maps = false
# The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps')
normal map pattern = _n
[Input]
# Capture control of the cursor prevent movement outside the window.

Loading…
Cancel
Save