From 5cf2441b1073875df26d292db1cad1a0a26c461b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 17:57:19 +0100 Subject: [PATCH] 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' --- apps/openmw/mwrender/renderingmanager.cpp | 5 +- apps/openmw/mwrender/terrainstorage.cpp | 16 +--- apps/openmw/mwrender/terrainstorage.hpp | 4 +- components/esmterrain/storage.cpp | 27 ++++-- components/esmterrain/storage.hpp | 5 +- components/resource/imagemanager.cpp | 1 + components/resource/scenemanager.cpp | 17 +++- components/resource/scenemanager.hpp | 8 ++ components/shader/shadervisitor.cpp | 104 +++++++++++++++++----- components/shader/shadervisitor.hpp | 19 +++- files/settings-default.cfg | 12 +++ 11 files changed, 164 insertions(+), 54 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3ad349ba7..ef1b469e3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -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 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())); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 02947b5dd..8d4bf2ea2 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -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::iterator it = esmStore.get().begin(); - for (; it != esmStore.get().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) diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index a12ffd540..7bd4b4fd5 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -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); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index cead73070..990102070 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -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; diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 9ca39e6f3..0c8095a11 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -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 mLayerInfoMap; OpenThreads::Mutex mLayerInfoMutex; + std::string mNormalMapPattern; + bool mAutoUseNormalMaps; + Terrain::LayerInfo getLayerInfo(const std::string& texture); }; diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 870fa370b..ea3adc704 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -121,6 +121,7 @@ namespace Resource } osg::Image* image = result.getImage(); + image->setFileName(normalized); if (!checkSupported(image, filename)) { mCache->addEntryToObjectCache(normalized, mWarningImage); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 57187c734..00030edc5 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -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 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 diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 8f2853789..d78466260 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -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 mInstanceCache; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 331beab34..a69a479e3 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -5,10 +5,15 @@ #include #include #include +#include #include #include +#include + +#include +#include #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;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); - if (attr) + const osg::Texture* diffuseMap = NULL; + const osg::Texture* normalMap = NULL; + for(unsigned int unit=0;unitasTexture(); - 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 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 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; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index c301db47d..645a92969 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -3,6 +3,11 @@ #include +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(); // std::map mTextures; + osg::ref_ptr mDiffuseMap; + bool mHasNormalMap; bool mColorMaterial; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1c87c5dd5..7403375c0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -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.