1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-30 07:45:39 +00:00

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'
This commit is contained in:
scrawl 2016-02-20 17:57:19 +01:00
parent 6a0ac824bd
commit 5cf2441b10
11 changed files with 164 additions and 54 deletions

View file

@ -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()));

View file

@ -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)

View file

@ -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);

View file

@ -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;

View file

@ -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);
};

View file

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

View file

@ -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

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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.