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:
parent
6a0ac824bd
commit
5cf2441b10
11 changed files with 164 additions and 54 deletions
|
@ -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…
Reference in a new issue