Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
39 kB · 882 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883#include "shadervisitor.hpp"
#include <set>#include <unordered_map>#include <unordered_set>
#include <osg/AlphaFunc>#include <osg/BlendFunc>#include <osg/ColorMaski>#include <osg/GLExtensions>#include <osg/Geometry>#include <osg/Multisample>#include <osg/Texture>#include <osg/ValueObject>
#include <osgParticle/ParticleSystem>
#include <osgUtil/TangentSpaceGenerator>
#include <components/debug/debuglog.hpp>#include <components/misc/osguservalues.hpp>#include <components/misc/strings/algorithm.hpp>#include <components/resource/imagemanager.hpp>#include <components/sceneutil/glextensions.hpp>#include <components/sceneutil/material.hpp>#include <components/sceneutil/morphgeometry.hpp>#include <components/sceneutil/riggeometry.hpp>#include <components/sceneutil/riggeometryosgaextension.hpp>#include <components/sceneutil/texturetype.hpp>#include <components/sceneutil/util.hpp>#include <components/settings/settings.hpp>#include <components/stereo/stereomanager.hpp>#include <components/vfs/manager.hpp>
#include "removedalphafunc.hpp"#include "shadermanager.hpp"
namespace Shader{ /** * Miniature version of osg::StateSet used to track state added by the shader visitor which should be ignored when * it's applied a second time, and removed when shaders are removed. * Actual StateAttributes aren't kept as they're recoverable from the StateSet this is attached to - we just want * the TypeMemberPair as that uniquely identifies which of those StateAttributes it was we're tracking. * Not all StateSet features have been added yet - we implement an equivalently-named method to each of the StateSet * methods called in createProgram, and implement new ones as they're needed. */ class AddedState : public osg::Object { public: AddedState() = default; AddedState(const AddedState& rhs, const osg::CopyOp& copyOp) : osg::Object(rhs, copyOp) , mUniforms(rhs.mUniforms) , mModes(rhs.mModes) , mAttributes(rhs.mAttributes) { }
void addUniform(const std::string& name) { mUniforms.emplace(name); } void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); }
void setAttribute(const osg::StateAttribute* attribute) { mAttributes.emplace(attribute->getTypeMemberPair()); } template <typename T> void setAttribute(osg::ref_ptr<T> attribute) { setAttribute(attribute.get()); }
void setAttributeAndModes(const osg::StateAttribute* attribute) { setAttribute(attribute); InterrogateModesHelper helper(this); attribute->getModeUsage(helper); } template <typename T> void setAttributeAndModes(osg::ref_ptr<T> attribute) { setAttributeAndModes(attribute.get()); }
void setTextureAttribute(int unit, osg::StateAttribute::TypeMemberPair typeMemberPair) { mTextureAttributes[unit].emplace(typeMemberPair); }
void setTextureAttribute(unsigned int unit, const osg::StateAttribute* attribute) { mTextureAttributes[unit].emplace(attribute->getTypeMemberPair()); } template <typename T> void setTextureAttribute(unsigned int unit, osg::ref_ptr<T> attribute) { setTextureAttribute(unit, attribute.get()); }
bool hasUniform(const std::string& name) { return mUniforms.count(name); } bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(const osg::StateAttribute::TypeMemberPair& typeMemberPair) { return mAttributes.count(typeMemberPair); } bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); }
const std::set<osg::StateAttribute::TypeMemberPair>& getAttributes() { return mAttributes; } const std::unordered_map<unsigned int, std::set<osg::StateAttribute::TypeMemberPair>>& getTextureAttributes() { return mTextureAttributes; }
bool empty() { return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureAttributes.empty(); }
META_Object(Shader, AddedState)
private: class InterrogateModesHelper : public osg::StateAttribute::ModeUsage { public: InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) { } void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } void usesTextureMode(osg::StateAttribute::GLMode mode) override {}
private: AddedState* mTracker; };
using ModeSet = std::unordered_set<osg::StateAttribute::GLMode>; using AttributeSet = std::set<osg::StateAttribute::TypeMemberPair>;
std::unordered_set<std::string> mUniforms; ModeSet mModes; AttributeSet mAttributes; std::unordered_map<unsigned int, AttributeSet> mTextureAttributes; };
ShaderVisitor::ShaderRequirements::ShaderRequirements() : mAlphaTestOverridden(false) , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) , mAlphaBlend(false) , mBlendFuncOverridden(false) , mAdditiveBlending(false) , mDiffuseHeight(false) , mNormalHeight(false) , mReconstructNormalZ(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) , mNode(nullptr) { }
ShaderVisitor::ShaderVisitor( ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) , mAdjustCoverageForAlphaTest(false) , mSupportsNormalsRT(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { }
void ShaderVisitor::apply(osg::Node& node) { bool needPop = false; if (node.getStateSet() || mRequirements.empty()) { needPop = true; pushRequirements(node); if (node.getStateSet()) applyStateSet(node.getStateSet(), node); } traverse(node); if (needPop) popRequirements(); }
osg::StateSet* getWritableStateSet(osg::Node& node) { if (!node.getStateSet()) return node.getOrCreateStateSet();
osg::ref_ptr<osg::StateSet> newStateSet = new osg::StateSet(*node.getStateSet(), osg::CopyOp::SHALLOW_COPY); node.setStateSet(newStateSet); return newStateSet.get(); }
osg::UserDataContainer* getWritableUserDataContainer(osg::Object& object) { if (!object.getUserDataContainer()) return object.getOrCreateUserDataContainer();
osg::ref_ptr<osg::UserDataContainer> newUserData = static_cast<osg::UserDataContainer*>(object.getUserDataContainer()->clone(osg::CopyOp::SHALLOW_COPY)); object.setUserDataContainer(newUserData); return newUserData.get(); }
osg::StateSet* getRemovedState(osg::StateSet& stateSet) { if (!stateSet.getUserDataContainer()) return nullptr;
return static_cast<osg::StateSet*>(stateSet.getUserDataContainer()->getUserObject("removedState")); }
void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* removedState) { unsigned int index = userData.getUserObjectIndex("removedState"); if (index < userData.getNumUserObjects()) userData.setUserObject(index, removedState); else userData.addUserObject(removedState); removedState->setName("removedState"); }
AddedState* getAddedState(osg::StateSet& stateSet) { if (!stateSet.getUserDataContainer()) return nullptr;
return static_cast<AddedState*>(stateSet.getUserDataContainer()->getUserObject("addedState")); }
void updateAddedState(osg::UserDataContainer& userData, AddedState* addedState) { unsigned int index = userData.getUserObjectIndex("addedState"); if (index < userData.getNumUserObjects()) userData.setUserObject(index, addedState); else userData.addUserObject(addedState); addedState->setName("addedState"); }
// This list is used both for detecting known texture types (including added normal maps etc.) and setting the // shader defines. Normal maps and normal height maps both get sent to the shader as a normal map, so the latter // must be detected separately. const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap", "glossMap" }; bool isTextureNameRecognized(std::string_view name) { if (std::find(std::begin(defaultTextures), std::end(defaultTextures), name) != std::end(defaultTextures)) return true; else return name == "normalHeightMap"; }
void ShaderVisitor::applyStateSet(osg::ref_ptr<osg::StateSet> stateset, osg::Node& node) { osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList();
bool softEffect = false; if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true;
// Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr<AddedState> addedState = getAddedState(*stateset);
if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; const osg::Texture* normalMap = nullptr; const osg::Texture* specularMap = nullptr; const osg::Texture* bumpMap = nullptr; for (unsigned int unit = 0; unit < texAttributes.size(); ++unit) { const osg::StateAttribute* attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { const osg::Texture* texture = attr->asTexture(); if (texture) { std::string texName = SceneUtil::getTextureType(*stateset, *texture, unit); if ((texName.empty() || !isTextureNameRecognized(texName)) && unit == 0) texName = "diffuseMap";
if (texName == "normalHeightMap") { mRequirements.back().mNormalHeight = true; texName = "normalMap"; }
if (!texName.empty()) { mRequirements.back().mTextures[unit] = texName; if (texName == "normalMap") { mRequirements.back().mTexStageRequiringTangents = unit; normalMap = texture; } else if (texName == "diffuseMap") { int applyMode; // Oblivion parallax if (node.getUserValue("applyMode", applyMode) && applyMode == 4) { mRequirements.back().mDiffuseHeight = true; mRequirements.back().mTexStageRequiringTangents = unit; } diffuseMap = texture; } else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") { bumpMap = texture; } } else Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } }
if (mAutoUseNormalMaps && diffuseMap != nullptr && normalMap == nullptr && diffuseMap->getImage(0)) { std::string normalMapFileName = diffuseMap->getImage(0)->getFileName();
osg::ref_ptr<osg::Image> image; bool normalHeight = false; std::string normalHeightMap = normalMapFileName; Misc::StringUtils::replaceLast(normalHeightMap, ".", mNormalHeightMapPattern + "."); const VFS::Path::Normalized normalHeightMapPath(normalHeightMap); if (mImageManager.getVFS()->exists(normalHeightMapPath)) { image = mImageManager.getImage(normalHeightMapPath); normalHeight = true; } else { Misc::StringUtils::replaceLast(normalMapFileName, ".", mNormalMapPattern + "."); const VFS::Path::Normalized normalMapPath(normalMapFileName); if (mImageManager.getVFS()->exists(normalMapPath)) { image = mImageManager.getImage(normalMapPath); } } // Avoid using the auto-detected normal map if it's already being used as a bump map. // It's probably not an actual normal map. bool hasNamesakeBumpMap = image && bumpMap && bumpMap->getImage(0) && image->getFileName() == bumpMap->getImage(0)->getFileName();
if (!hasNamesakeBumpMap && image) { osg::ref_ptr<osg::Texture2D> normalMapTex(new osg::Texture2D(image)); normalMapTex->setTextureSize(image->s(), image->t()); 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()); normalMap = normalMapTex;
int unit = static_cast<int>(texAttributes.size()); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttribute(unit, normalMapTex, osg::StateAttribute::ON); writableStateSet->setTextureAttribute(unit, new SceneUtil::TextureType(normalHeight ? "normalHeightMap" : "normalMap"), osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "normalMap"; mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mNormalHeight = normalHeight; } }
if (normalMap != nullptr && normalMap->getImage(0)) { // Special handling for red-green normal maps (e.g. BC5 or R8G8) switch (SceneUtil::computeUnsizedPixelFormat(normalMap->getImage(0)->getPixelFormat())) { case GL_RG: case GL_RG_INTEGER: { mRequirements.back().mReconstructNormalZ = true; mRequirements.back().mNormalHeight = false; } } }
if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); Misc::StringUtils::replaceLast(specularMapFileName, ".", mSpecularMapPattern + "."); const VFS::Path::Normalized specularMapPath(specularMapFileName); if (mImageManager.getVFS()->exists(specularMapPath)) { osg::ref_ptr<osg::Image> image(mImageManager.getImage(specularMapPath)); osg::ref_ptr<osg::Texture2D> specularMapTex(new osg::Texture2D(image)); specularMapTex->setTextureSize(image->s(), image->t()); specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); specularMapTex->setFilter( osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); specularMapTex->setFilter( osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); specularMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy());
int unit = static_cast<int>(texAttributes.size()); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttribute(unit, specularMapTex, osg::StateAttribute::ON); writableStateSet->setTextureAttribute( unit, new SceneUtil::TextureType("specularMap"), osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "specularMap"; } } }
const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr<osg::StateSet> removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList();
for (const auto* attributeMap : std::initializer_list<const osg::StateSet::AttributeList*>{ &attributes, &removedAttributes }) { for (osg::StateSet::AttributeList::const_iterator it = attributeMap->begin(); it != attributeMap->end(); ++it) { if (addedState && attributeMap != &removedAttributes && addedState->hasAttribute(it->first)) continue; if (it->first.first == osg::StateAttribute::MATERIAL) { if (!writableStateSet) writableStateSet = getWritableStateSet(node); static_cast<const SceneUtil::Material*>(it->second.first.get()) ->setStateSet(writableStateSet, it->second.second); } else if (it->first.first == osg::StateAttribute::ALPHAFUNC) { if (!mRequirements.back().mAlphaTestOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mAlphaTestOverridden = true;
const osg::AlphaFunc* alpha = static_cast<const osg::AlphaFunc*>(it->second.first.get()); mRequirements.back().mAlphaFunc = alpha->getFunction(); mRequirements.back().mAlphaRef = alpha->getReferenceValue(); } } else if (it->first.first == osg::StateAttribute::BLENDFUNC) { if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mBlendFuncOverridden = true;
const osg::BlendFunc* blend = static_cast<const osg::BlendFunc*>(it->second.first.get()); mRequirements.back().mAdditiveBlending = blend->getSource() == osg::BlendFunc::SRC_ALPHA && blend->getDestination() == osg::BlendFunc::ONE; } } } }
unsigned int alphaBlend = stateset->getMode(GL_BLEND); if (alphaBlend != osg::StateAttribute::INHERIT && (!mRequirements.back().mAlphaBlendOverridden || alphaBlend & osg::StateAttribute::PROTECTED)) { if (alphaBlend & osg::StateAttribute::OVERRIDE) mRequirements.back().mAlphaBlendOverridden = true;
mRequirements.back().mAlphaBlend = alphaBlend & osg::StateAttribute::ON; } }
void ShaderVisitor::pushRequirements(osg::Node& node) { if (mRequirements.empty()) mRequirements.emplace_back(); else mRequirements.push_back(mRequirements.back()); mRequirements.back().mNode = &node; }
void ShaderVisitor::popRequirements() { mRequirements.pop_back(); }
void ShaderVisitor::createProgram(const ShaderRequirements& reqs) { /** * The shader visitor is supposed to be idempotent and undoable. * That means we need to back up state we've removed (so it can be restored and/or considered by further * applications of the visitor) and track which state we added (so it can be removed and/or ignored by further * applications of the visitor). * Before editing writableStateSet in a way that explicitly removes state or might overwrite existing state, it * should be copied to removedState, another StateSet, unless it's there already or was added by a previous * application of the visitor (is in previousAddedState). * If it's a new class of state that's not already handled by ReinstateRemovedStateVisitor::apply, make sure to * add handling there. * Similarly, any time new state is added to writableStateSet, the equivalent method should be called on * addedState. * If that method doesn't exist yet, implement it - we don't use a full StateSet as we only need to check * existence, not equality, and don't need to actually get the value as we can get it from writableStateSet * instead. */ osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getOrCreateStateSet(); else writableStateSet = getWritableStateSet(node); osg::ref_ptr<AddedState> addedState = new AddedState; osg::ref_ptr<AddedState> previousAddedState = getAddedState(*writableStateSet); if (!previousAddedState) previousAddedState = new AddedState;
ShaderManager::DefineMap defineMap; for (unsigned int i = 0; i < sizeof(defaultTextures) / sizeof(defaultTextures[0]); ++i) { defineMap[defaultTextures[i]] = "0"; defineMap[std::string(defaultTextures[i]) + std::string("UV")] = "0"; } for (std::map<int, std::string>::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { defineMap[texIt->second] = "1"; defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); }
if (defineMap["diffuseMap"] == "0") { writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); addedState->addUniform("useDiffuseMapForShadowAlpha"); }
defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; defineMap["reconstructNormalZ"] = reqs.mReconstructNormalZ ? "1" : "0"; defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); defineMap["additiveBlending"] = reqs.mAdditiveBlending ? "1" : "0";
osg::ref_ptr<osg::StateSet> removedState; if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); if (!removedState) removedState = new osg::StateSet();
defineMap["alphaToCoverage"] = "0"; defineMap["adjustCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); addedState->addUniform("alphaRef");
if (!removedState->getAttributePair(osg::StateAttribute::ALPHAFUNC)) { const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); if (alphaFunc && !previousAddedState->hasAttribute(osg::StateAttribute::ALPHAFUNC, 0)) removedState->setAttribute(alphaFunc->first, alphaFunc->second); } // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::PROTECTED); addedState->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc));
// Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from // ARB_sample_shading would save the day, but requires GLSL 130 if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) { writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); addedState->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); defineMap["alphaToCoverage"] = "1"; }
// Adjusting coverage isn't safe with blending on as blending requires the alpha to be intact. // Maybe we could also somehow (e.g. userdata) detect when the diffuse map has coverage-preserving mip maps // in the future if (mAdjustCoverageForAlphaTest && !reqs.mAlphaBlend) defineMap["adjustCoverage"] = "1";
// Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size if (SceneUtil::getGLExtensions().isGpuShader4Supported) defineMap["useGPUShader4"] = "1"; // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing }
bool isParticle = dynamic_cast<osgParticle::ParticleSystem*>(&node); defineMap["particle"] = isParticle ? "1" : "0";
bool simpleLighting = false; node.getUserValue("simpleLighting", simpleLighting);
if (simpleLighting) defineMap["simpleLighting"] = "1";
if (simpleLighting || isParticle) defineMap["forcePPL"] = "0";
bool particleOcclusion = false; node.getUserValue("particleOcclusion", particleOcclusion); defineMap["particleOcclusion"] = particleOcclusion && mWeatherParticleOcclusion ? "1" : "0";
if (reqs.mAlphaBlend && mSupportsNormalsRT) { if (reqs.mSoftParticles) defineMap["disableNormals"] = "1"; auto colorMask = new osg::ColorMaski(1, false, false, false, false); writableStateSet->setAttribute(colorMask); addedState->setAttribute(colorMask); }
if (reqs.mSoftParticles) { const int unitSoftEffect = mShaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); addedState->addUniform("opaqueDepthTex"); }
if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); addedState->setMode(GL_ALPHA_TEST);
if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) { // user data is normally shallow copied so shared with the original stateset osg::ref_ptr<osg::UserDataContainer> writableUserData; if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getOrCreateUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet);
updateRemovedState(*writableUserData, removedState); }
defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0";
Stereo::shaderStereoDefines(defineMap);
std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix;
ShaderManager::SamplerBindingMap samplers; for (const auto& [unit, name] : reqs.mTextures) samplers[name] = unit;
auto program = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate, samplers); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); addedState->setAttributeAndModes(std::move(program));
if (!addedState->empty()) { // user data is normally shallow copied so shared with the original stateset osg::ref_ptr<osg::UserDataContainer> writableUserData; if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getOrCreateUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet);
updateAddedState(*writableUserData, addedState); } }
bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) { bool changed = false;
if (mAllowedToModifyStateSets) { // make sure that all UV sets are there // it's not safe to assume there's one for slot zero, so try and use one from another slot if possible // if there are none at all, bail. // the TangentSpaceGenerator would bail, but getTangentArray would give an empty array, which is enough to // bypass null checks, but feeds the driver a bad pointer if (sourceGeometry.getTexCoordArray(0) == nullptr) { for (const auto& array : sourceGeometry.getTexCoordArrayList()) { if (array) { sourceGeometry.setTexCoordArray(0, array); break; } } if (sourceGeometry.getTexCoordArray(0) == nullptr) return changed; }
for (const auto& [unit, name] : reqs.mTextures) { if (sourceGeometry.getTexCoordArray(unit) == nullptr) { sourceGeometry.setTexCoordArray(unit, sourceGeometry.getTexCoordArray(0)); changed = true; } }
bool generateTangents = reqs.mTexStageRequiringTangents != -1;
if (generateTangents) { osg::ref_ptr<osgUtil::TangentSpaceGenerator> generator(new osgUtil::TangentSpaceGenerator); generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents);
sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); changed = true; } } return changed; }
void ShaderVisitor::apply(osg::Geometry& geometry) { bool needPop = geometry.getStateSet() || mRequirements.empty(); if (needPop) pushRequirements(geometry);
if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it applyStateSet(geometry.getStateSet(), geometry);
if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back();
adjustGeometry(geometry, reqs);
createProgram(reqs); }
if (needPop) popRequirements(); }
void ShaderVisitor::apply(osg::Drawable& drawable) { bool needPop = drawable.getStateSet() || mRequirements.empty();
// We need to push and pop a requirements object because particle systems can have // different shader requirements to other drawables, so might need a different shader variant. if (!needPop && dynamic_cast<osgParticle::ParticleSystem*>(&drawable)) needPop = true;
if (needPop) { pushRequirements(drawable);
if (drawable.getStateSet()) applyStateSet(drawable.getStateSet(), drawable); }
const ShaderRequirements& reqs = mRequirements.back(); createProgram(reqs);
if (auto rig = dynamic_cast<SceneUtil::RigGeometry*>(&drawable)) { osg::ref_ptr<osg::Geometry> sourceGeometry = rig->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) rig->setSourceGeometry(std::move(sourceGeometry)); } else if (auto morph = dynamic_cast<SceneUtil::MorphGeometry*>(&drawable)) { osg::ref_ptr<osg::Geometry> sourceGeometry = morph->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) morph->setSourceGeometry(std::move(sourceGeometry)); } else if (auto osgaRig = dynamic_cast<SceneUtil::RigGeometryHolder*>(&drawable)) { osg::ref_ptr<SceneUtil::OsgaRigGeometry> sourceOsgaRigGeometry = osgaRig->getSourceRigGeometry(); osg::ref_ptr<osg::Geometry> sourceGeometry = sourceOsgaRigGeometry->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) { sourceOsgaRigGeometry->setSourceGeometry(std::move(sourceGeometry)); osgaRig->setSourceRigGeometry(std::move(sourceOsgaRigGeometry)); } }
if (needPop) popRequirements(); }
void ShaderVisitor::setAllowedToModifyStateSets(bool allowed) { mAllowedToModifyStateSets = allowed; }
void ShaderVisitor::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; }
void ShaderVisitor::setNormalMapPattern(const std::string& pattern) { mNormalMapPattern = pattern; }
void ShaderVisitor::setNormalHeightMapPattern(const std::string& pattern) { mNormalHeightMapPattern = pattern; }
void ShaderVisitor::setAutoUseSpecularMaps(bool use) { mAutoUseSpecularMaps = use; }
void ShaderVisitor::setSpecularMapPattern(const std::string& pattern) { mSpecularMapPattern = pattern; }
void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert) { mConvertAlphaTestToAlphaToCoverage = convert; }
void ShaderVisitor::setAdjustCoverageForAlphaTest(bool adjustCoverage) { mAdjustCoverageForAlphaTest = adjustCoverage; }
ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) { }
void ReinstateRemovedStateVisitor::apply(osg::Node& node) { // TODO: this may eventually need to remove added state. // If so, we can migrate from explicitly copying removed state to just calling osg::StateSet::merge. // Not everything is transferred from removedState yet - implement more when createProgram starts marking more // as removed. if (node.getStateSet()) { osg::ref_ptr<osg::StateSet> removedState = getRemovedState(*node.getStateSet()); if (removedState) { osg::ref_ptr<osg::StateSet> writableStateSet; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); else writableStateSet = getWritableStateSet(node);
// user data is normally shallow copied so shared with the original stateset osg::ref_ptr<osg::UserDataContainer> writableUserData; if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); unsigned int index = writableUserData->getUserObjectIndex("removedState"); writableUserData->removeUserObject(index);
for (const auto& [mode, value] : removedState->getModeList()) writableStateSet->setMode(mode, value);
for (const auto& attribute : removedState->getAttributeList()) writableStateSet->setAttribute(attribute.second.first, attribute.second.second); } }
traverse(node); }
}