Merge branch 'leaf_me_alone' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled

tes5 - add leaf animations

See merge request OpenMW/openmw!4550
This commit is contained in:
Cody Glassman 2025-04-24 04:49:55 -07:00
commit aa2a473ae1
29 changed files with 354 additions and 82 deletions

View file

@ -95,7 +95,7 @@ add_openmw_dir (mwphysics
add_openmw_dir (mwclass add_openmw_dir (mwclass
classes activator creature npc weapon armor potion apparatus book clothing container door classes activator creature npc weapon armor potion apparatus book clothing container door
ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart
esm4base esm4npc light4 esm4base esm4npc light4 tree4
) )
add_openmw_dir (mwmechanics add_openmw_dir (mwmechanics

View file

@ -48,6 +48,7 @@
#include "esm4base.hpp" #include "esm4base.hpp"
#include "esm4npc.hpp" #include "esm4npc.hpp"
#include "light4.hpp" #include "light4.hpp"
#include "tree4.hpp"
namespace MWClass namespace MWClass
{ {

View file

@ -120,15 +120,6 @@ namespace MWClass
} }
}; };
class ESM4Tree final : public MWWorld::RegisteredClass<ESM4Tree, ESM4Base<ESM4::Tree>>
{
friend MWWorld::RegisteredClass<ESM4Tree, ESM4Base<ESM4::Tree>>;
ESM4Tree()
: MWWorld::RegisteredClass<ESM4Tree, ESM4Base<ESM4::Tree>>(ESM4::Tree::sRecordId)
{
}
};
// For records with `mFullName` that should be shown as a tooltip. // For records with `mFullName` that should be shown as a tooltip.
// All objects with a tooltip can be activated (activation can be handled in Lua). // All objects with a tooltip can be activated (activation can be handled in Lua).
template <typename Record> template <typename Record>

View file

@ -0,0 +1,27 @@
#include "tree4.hpp"
#include <components/esm4/loadtree.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwrender/objects.hpp"
#include "../mwrender/renderinginterface.hpp"
#include "../mwrender/vismask.hpp"
#include "../mwworld/ptr.hpp"
namespace MWClass
{
ESM4Tree::ESM4Tree()
: MWWorld::RegisteredClass<ESM4Tree, ESM4Base<ESM4::Tree>>(ESM4::Tree::sRecordId)
{
}
void ESM4Tree ::insertObjectRendering(
const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{
if (!model.empty())
{
renderingInterface.getObjects().insertModel(ptr, model);
ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static);
}
}
}

View file

@ -0,0 +1,22 @@
#ifndef OPENW_MWCLASS_TREE4
#define OPENW_MWCLASS_TREE4
#include "../mwworld/registeredclass.hpp"
#include "esm4base.hpp"
namespace MWClass
{
class ESM4Tree : public MWWorld::RegisteredClass<ESM4Tree, ESM4Base<ESM4::Tree>>
{
friend MWWorld::RegisteredClass<ESM4Tree, ESM4Base<ESM4::Tree>>;
ESM4Tree();
public:
void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model,
MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
};
}
#endif

View file

@ -28,6 +28,7 @@
#include <components/esm3/loadnpc.hpp> #include <components/esm3/loadnpc.hpp>
#include <components/esm3/loadrace.hpp> #include <components/esm3/loadrace.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadtree.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/misc/pathhelpers.hpp> #include <components/misc/pathhelpers.hpp>
@ -67,6 +68,31 @@
namespace namespace
{ {
class LeafParamsAssigner : public osg::NodeVisitor
{
public:
LeafParamsAssigner(const MWWorld::LiveCellRef<ESM4::Tree>* treeRef)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mTreeRef(treeRef)
, mVertexAttrib(new osg::Vec3Array(1))
, mTimeOffset(Misc::Rng::roll0to99())
{
mVertexAttrib->at(0) = osg::Vec3f(
mTreeRef->mBase->mParams.mLeafAmplitude, mTreeRef->mBase->mParams.mLeafFrequency, mTimeOffset);
}
void apply(osg::Geometry& geometry) override
{
geometry.setVertexAttribArray(7, mVertexAttrib, osg::Array::BIND_OVERALL);
traverse(geometry);
}
const MWWorld::LiveCellRef<ESM4::Tree>* mTreeRef = nullptr;
osg::ref_ptr<osg::Vec3Array> mVertexAttrib = nullptr;
float mTimeOffset = 0.f;
};
class MarkDrawablesVisitor : public osg::NodeVisitor class MarkDrawablesVisitor : public osg::NodeVisitor
{ {
public: public:
@ -2079,6 +2105,11 @@ namespace MWRender
addExtraLight(getOrCreateObjectRoot(), SceneUtil::LightCommon(*ptr.get<ESM::Light>()->mBase)); addExtraLight(getOrCreateObjectRoot(), SceneUtil::LightCommon(*ptr.get<ESM::Light>()->mBase));
if (ptr.getType() == ESM4::Light::sRecordId && allowLight) if (ptr.getType() == ESM4::Light::sRecordId && allowLight)
addExtraLight(getOrCreateObjectRoot(), SceneUtil::LightCommon(*ptr.get<ESM4::Light>()->mBase)); addExtraLight(getOrCreateObjectRoot(), SceneUtil::LightCommon(*ptr.get<ESM4::Light>()->mBase));
if (ptr.getType() == ESM4::Tree::sRecordId)
{
LeafParamsAssigner nv{ ptr.get<ESM4::Tree>() };
ptr.getRefData().getBaseNode()->accept(nv);
}
if (!allowLight && mObjectRoot) if (!allowLight && mObjectRoot)
{ {

View file

@ -174,7 +174,6 @@ namespace MWRender
stateset->addUniform(new osg::Uniform("isReflection", false)); stateset->addUniform(new osg::Uniform("isReflection", false));
stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); stateset->addUniform(new osg::Uniform("windSpeed", 0.0f));
stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f))); stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)));
stateset->addUniform(new osg::Uniform("useTreeAnim", false));
} }
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override
@ -447,6 +446,8 @@ namespace MWRender
globalDefines["useOVR_multiview"] = "0"; globalDefines["useOVR_multiview"] = "0";
globalDefines["numViews"] = "1"; globalDefines["numViews"] = "1";
globalDefines["disableNormals"] = "1"; globalDefines["disableNormals"] = "1";
globalDefines["treeAnim"] = "0";
globalDefines["shadowCasting"] = "0";
for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++)
globalDefines[itr->first] = itr->second; globalDefines[itr->first] = itr->second;

View file

@ -53,11 +53,23 @@ void ESM4::Tree::load(ESM4::Reader& reader)
case ESM::fourCC("MODB"): case ESM::fourCC("MODB"):
reader.get(mBoundRadius); reader.get(mBoundRadius);
break; break;
case ESM::fourCC("CNAM"):
{
switch (subHdr.dataSize)
{
case 48: // TES5
reader.get(mParams);
break;
default:
reader.skipSubRecordData();
break;
}
break;
}
case ESM::fourCC("MODT"): // Model data case ESM::fourCC("MODT"): // Model data
case ESM::fourCC("MODC"): case ESM::fourCC("MODC"):
case ESM::fourCC("MODS"): case ESM::fourCC("MODS"):
case ESM::fourCC("MODF"): // Model data end case ESM::fourCC("MODF"): // Model data end
case ESM::fourCC("CNAM"):
case ESM::fourCC("BNAM"): case ESM::fourCC("BNAM"):
case ESM::fourCC("SNAM"): case ESM::fourCC("SNAM"):
case ESM::fourCC("FULL"): case ESM::fourCC("FULL"):

View file

@ -50,6 +50,24 @@ namespace ESM4
std::string mLeafTexture; std::string mLeafTexture;
struct Params
{
float mTrunkFlexibility;
float mBranchFlexibility;
float mTrunkAmplitude;
float mFrontAmplitude;
float mBackAmplitude;
float mSideAmplitude;
float mFrontFrequency;
float mBackFrequency;
float mSideFrequency;
float mLeafFlexibility;
float mLeafAmplitude;
float mLeafFrequency;
};
Params mParams;
void load(ESM4::Reader& reader); void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;

View file

@ -5,4 +5,5 @@ namespace Misc
const std::string OsgUserValues::sFileHash = "fileHash"; const std::string OsgUserValues::sFileHash = "fileHash";
const std::string OsgUserValues::sExtraData = "xData"; const std::string OsgUserValues::sExtraData = "xData";
const std::string OsgUserValues::sXSoftEffect = "xSoftEffect"; const std::string OsgUserValues::sXSoftEffect = "xSoftEffect";
const std::string OsgUserValues::sTreeAnim = "treeAnim";
} }

View file

@ -10,6 +10,7 @@ namespace Misc
static const std::string sFileHash; static const std::string sFileHash;
static const std::string sExtraData; static const std::string sExtraData;
static const std::string sXSoftEffect; static const std::string sXSoftEffect;
static const std::string sTreeAnim;
}; };
} }

View file

@ -74,12 +74,12 @@ namespace Nif
{ "BSDistantObjectInstancedNode", { "BSDistantObjectInstancedNode",
&construct<BSDistantObjectInstancedNode, RC_BSDistantObjectInstancedNode> }, &construct<BSDistantObjectInstancedNode, RC_BSDistantObjectInstancedNode> },
{ "BSFadeNode", &construct<NiNode, RC_NiNode> }, { "BSFadeNode", &construct<NiNode, RC_NiNode> },
{ "BSLeafAnimNode", &construct<NiNode, RC_NiNode> }, { "BSLeafAnimNode", &construct<NiNode, RC_BSLeafAnimNode> },
{ "BSMasterParticleSystem", &construct<BSMasterParticleSystem, RC_NiNode> }, { "BSMasterParticleSystem", &construct<BSMasterParticleSystem, RC_NiNode> },
{ "BSMultiBoundNode", &construct<BSMultiBoundNode, RC_NiNode> }, { "BSMultiBoundNode", &construct<BSMultiBoundNode, RC_NiNode> },
{ "BSOrderedNode", &construct<BSOrderedNode, RC_NiNode> }, { "BSOrderedNode", &construct<BSOrderedNode, RC_NiNode> },
{ "BSRangeNode", &construct<BSRangeNode, RC_NiNode> }, { "BSRangeNode", &construct<BSRangeNode, RC_NiNode> },
{ "BSTreeNode", &construct<BSTreeNode, RC_NiNode> }, { "BSTreeNode", &construct<BSTreeNode, RC_BSTreeNode> },
{ "BSValueNode", &construct<BSValueNode, RC_NiNode> }, { "BSValueNode", &construct<BSValueNode, RC_NiNode> },
// Switch nodes, 4.0.0.2 // Switch nodes, 4.0.0.2

View file

@ -102,6 +102,7 @@ namespace Nif
RC_BSInvMarker, RC_BSInvMarker,
RC_BSKeyframeController, RC_BSKeyframeController,
RC_BSLagBoneController, RC_BSLagBoneController,
RC_BSLeafAnimNode,
RC_BSLightingShaderProperty, RC_BSLightingShaderProperty,
RC_BSLightingShaderPropertyColorController, RC_BSLightingShaderPropertyColorController,
RC_BSLightingShaderPropertyFloatController, RC_BSLightingShaderPropertyFloatController,
@ -145,6 +146,7 @@ namespace Nif
RC_BSSubIndexTriShape, RC_BSSubIndexTriShape,
RC_BSTreadTransfInterpolator, RC_BSTreadTransfInterpolator,
RC_BSTriShape, RC_BSTriShape,
RC_BSTreeNode,
RC_BSWArray, RC_BSWArray,
RC_BSWaterShaderProperty, RC_BSWaterShaderProperty,
RC_BSWindModifier, RC_BSWindModifier,

View file

@ -258,6 +258,7 @@ namespace NifOsg
bool mHasNightDayLabel = false; bool mHasNightDayLabel = false;
bool mHasHerbalismLabel = false; bool mHasHerbalismLabel = false;
bool mHasStencilProperty = false; bool mHasStencilProperty = false;
bool mHasTreeRoot = false;
const Nif::NiSortAdjustNode* mPushedSorter = nullptr; const Nif::NiSortAdjustNode* mPushedSorter = nullptr;
const Nif::NiSortAdjustNode* mLastAppliedNoInheritSorter = nullptr; const Nif::NiSortAdjustNode* mLastAppliedNoInheritSorter = nullptr;
@ -747,6 +748,12 @@ namespace NifOsg
node->setDataVariance(osg::Object::DYNAMIC); node->setDataVariance(osg::Object::DYNAMIC);
} }
// Root node must be a BSTreeNode or BSLeafAnimNode for tree/leaf related shader flags to apply
if (!parent && (nifNode->recType == Nif::RC_BSLeafAnimNode || nifNode->recType == Nif::RC_BSTreeNode))
{
mHasTreeRoot = true;
}
osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater; osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater;
applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags);
@ -2183,8 +2190,8 @@ namespace NifOsg
} }
} }
void handleShaderMaterialNodeProperties( void handleShaderMaterialNodeProperties(osg::Node* node, const Bgsm::MaterialFile* material,
const Bgsm::MaterialFile* material, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
{ {
const unsigned int uvSet = 0; const unsigned int uvSet = 0;
const bool wrapS = material->wrapS(); const bool wrapS = material->wrapS();
@ -2203,8 +2210,8 @@ namespace NifOsg
if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty()) if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty())
attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures); attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (bgsm->mTree) if (bgsm->mTree && mHasTreeRoot)
stateset->addUniform(new osg::Uniform("useTreeAnim", true)); node->setUserValue(Misc::OsgUserValues::sTreeAnim, true);
} }
else if (material->mShaderType == Bgsm::ShaderType::Effect) else if (material->mShaderType == Bgsm::ShaderType::Effect)
{ {
@ -2540,7 +2547,7 @@ namespace NifOsg
clearBoundTextures(stateset, boundTextures); clearBoundTextures(stateset, boundTextures);
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
{ {
handleShaderMaterialNodeProperties(material.get(), stateset, boundTextures); handleShaderMaterialNodeProperties(node, material.get(), stateset, boundTextures);
break; break;
} }
if (!texprop->mTextureSet.empty()) if (!texprop->mTextureSet.empty())
@ -2549,8 +2556,8 @@ namespace NifOsg
handleTextureControllers(texprop, composite, stateset, animflags); handleTextureControllers(texprop, composite, stateset, animflags);
if (texprop->doubleSided()) if (texprop->doubleSided())
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
if (texprop->treeAnim()) if (texprop->treeAnim() && mHasTreeRoot)
stateset->addUniform(new osg::Uniform("useTreeAnim", true)); node->setUserValue(Misc::OsgUserValues::sTreeAnim, true);
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
if (texprop->refraction()) if (texprop->refraction())
SceneUtil::setupDistortion(*node, texprop->mRefractionStrength); SceneUtil::setupDistortion(*node, texprop->mRefractionStrength);
@ -2567,7 +2574,7 @@ namespace NifOsg
clearBoundTextures(stateset, boundTextures); clearBoundTextures(stateset, boundTextures);
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
{ {
handleShaderMaterialNodeProperties(material.get(), stateset, boundTextures); handleShaderMaterialNodeProperties(node, material.get(), stateset, boundTextures);
break; break;
} }
if (!texprop->mSourceTexture.empty()) if (!texprop->mSourceTexture.empty())

View file

@ -922,14 +922,12 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh
std::string useGPUShader4 = SceneUtil::getGLExtensions().isGpuShader4Supported ? "1" : "0"; std::string useGPUShader4 = SceneUtil::getGLExtensions().isGpuShader4Supported ? "1" : "0";
for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc)
{ {
auto& program = _castingPrograms[alphaFunc - GL_NEVER]; osg::ref_ptr<osg::Shader> castingFragmentShader = shaderManager.getShader("shadowcasting.frag", { {"alphaFunc", std::to_string(alphaFunc)},
program = new osg::Program();
program->addShader(castingVertexShader);
program->addShader(shaderManager.getShader("shadowcasting.frag", { {"alphaFunc", std::to_string(alphaFunc)},
{"alphaToCoverage", "0"}, {"alphaToCoverage", "0"},
{"adjustCoverage", "1"}, {"adjustCoverage", "1"},
{"useGPUShader4", useGPUShader4} {"useGPUShader4", useGPUShader4}});
})); auto& program = _castingPrograms[alphaFunc - GL_NEVER];
program = shaderManager.createProgram(castingVertexShader, castingFragmentShader);
} }
} }

View file

@ -1,10 +1,14 @@
#include "shadowsbin.hpp" #include "shadowsbin.hpp"
#include <unordered_set>
#include <osg/AlphaFunc> #include <osg/AlphaFunc>
#include <osg/Material> #include <osg/Material>
#include <osg/Program> #include <osg/Program>
#include <osg/StateSet> #include <osg/StateSet>
#include <osgUtil/StateGraph> #include <osgUtil/StateGraph>
#include <unordered_set>
#include <components/shader/shadermanager.hpp>
using namespace osgUtil; using namespace osgUtil;
@ -115,6 +119,14 @@ namespace SceneUtil
state.mImportantState = true; state.mImportantState = true;
} }
auto dedicatedCastingStateSet
= dynamic_cast<const Shader::ShaderManager::ShadowCastingStateSet*>(ss->getUserData());
if (dedicatedCastingStateSet)
{
state.mShadowCastingStateSet = dedicatedCastingStateSet->mStateSet;
}
if ((*itr) != sg && !state.interesting()) if ((*itr) != sg && !state.interesting())
uninterestingCache.insert(*itr); uninterestingCache.insert(*itr);
} }
@ -132,24 +144,22 @@ namespace SceneUtil
return nullptr; return nullptr;
} }
if (state.mAlphaBlend) auto find_or_insert = [&sg, &sg_new](const osg::StateSet* ss) {
{ sg_new = sg->find_or_insert(ss);
sg_new = sg->find_or_insert(mShaderAlphaTestStateSet);
sg_new->_leaves = std::move(sg->_leaves); sg_new->_leaves = std::move(sg->_leaves);
for (RenderLeaf* leaf : sg_new->_leaves) for (RenderLeaf* leaf : sg_new->_leaves)
leaf->_parent = sg_new; leaf->_parent = sg_new;
sg = sg_new; sg = sg_new;
} };
if (state.mAlphaBlend)
find_or_insert(mShaderAlphaTestStateSet);
if (state.mShadowCastingStateSet)
find_or_insert(state.mShadowCastingStateSet);
// GL_ALWAYS is set by default by mwshadowtechnique // GL_ALWAYS is set by default by mwshadowtechnique
if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) else if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS)
{ find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]);
sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]);
sg_new->_leaves = std::move(sg->_leaves);
for (RenderLeaf* leaf : sg_new->_leaves)
leaf->_parent = sg_new;
sg = sg_new;
}
return sg; return sg;
} }

View file

@ -35,24 +35,14 @@ namespace SceneUtil
struct State struct State
{ {
State() bool mAlphaBlend = false;
: mAlphaBlend(false) bool mAlphaBlendOverride = false;
, mAlphaBlendOverride(false) osg::AlphaFunc* mAlphaFunc = nullptr;
, mAlphaFunc(nullptr) bool mAlphaFuncOverride = false;
, mAlphaFuncOverride(false) osg::Material* mMaterial = nullptr;
, mMaterial(nullptr) bool mMaterialOverride = false;
, mMaterialOverride(false) bool mImportantState = false;
, mImportantState(false) osg::StateSet* mShadowCastingStateSet = nullptr;
{
}
bool mAlphaBlend;
bool mAlphaBlendOverride;
osg::AlphaFunc* mAlphaFunc;
bool mAlphaFuncOverride;
osg::Material* mMaterial;
bool mMaterialOverride;
bool mImportantState;
bool needTexture() const; bool needTexture() const;
bool needShadows() const; bool needShadows() const;
// A state is interesting if there's anything about it that might affect whether we can optimise child state // A state is interesting if there's anything about it that might affect whether we can optimise child state

View file

@ -609,18 +609,26 @@ namespace Shader
{ {
if (!programTemplate) if (!programTemplate)
programTemplate = mProgramTemplate; programTemplate = mProgramTemplate;
osg::ref_ptr<osg::Program> program found = mPrograms
= programTemplate ? cloneProgram(programTemplate) : osg::ref_ptr<osg::Program>(new osg::Program); .insert(std::make_pair(std::make_pair(vertexShader, fragmentShader),
program->addShader(vertexShader); createProgram(vertexShader, fragmentShader, programTemplate)))
program->addShader(fragmentShader); .first;
addLinkedShaders(vertexShader, program);
addLinkedShaders(fragmentShader, program);
found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first;
} }
return found->second; return found->second;
} }
osg::ref_ptr<osg::Program> ShaderManager::createProgram(osg::ref_ptr<osg::Shader> vertexShader,
osg::ref_ptr<osg::Shader> fragmentShader, const osg::Program* programTemplate)
{
osg::ref_ptr<osg::Program> program
= programTemplate ? cloneProgram(programTemplate) : osg::ref_ptr<osg::Program>(new osg::Program);
program->addShader(vertexShader);
program->addShader(fragmentShader);
addLinkedShaders(vertexShader, program);
addLinkedShaders(fragmentShader, program);
return program;
}
osg::ref_ptr<osg::Program> ShaderManager::cloneProgram(const osg::Program* src) osg::ref_ptr<osg::Program> ShaderManager::cloneProgram(const osg::Program* src)
{ {
osg::ref_ptr<osg::Program> program = static_cast<osg::Program*>(src->clone(osg::CopyOp::SHALLOW_COPY)); osg::ref_ptr<osg::Program> program = static_cast<osg::Program*>(src->clone(osg::CopyOp::SHALLOW_COPY));

View file

@ -36,6 +36,17 @@ namespace Shader
typedef std::map<std::string, std::string> DefineMap; typedef std::map<std::string, std::string> DefineMap;
class ShadowCastingStateSet : public osg::Referenced
{
public:
ShadowCastingStateSet(osg::ref_ptr<osg::StateSet>&& stateSet)
: mStateSet(std::move(stateSet))
{
}
osg::ref_ptr<osg::StateSet> mStateSet = nullptr;
};
/// Create or retrieve a shader instance. /// Create or retrieve a shader instance.
/// @param templateName The path of the shader template. /// @param templateName The path of the shader template.
/// @param defines Define values that can be retrieved by the shader template. /// @param defines Define values that can be retrieved by the shader template.
@ -51,6 +62,9 @@ namespace Shader
osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader,
osg::ref_ptr<osg::Shader> fragmentShader, const osg::Program* programTemplate = nullptr); osg::ref_ptr<osg::Shader> fragmentShader, const osg::Program* programTemplate = nullptr);
osg::ref_ptr<osg::Program> createProgram(osg::ref_ptr<osg::Shader> vertexShader,
osg::ref_ptr<osg::Shader> fragmentShader, const osg::Program* programTemplate = nullptr);
const osg::Program* getProgramTemplate() const { return mProgramTemplate; } const osg::Program* getProgramTemplate() const { return mProgramTemplate; }
void setProgramTemplate(const osg::Program* program) { mProgramTemplate = program; } void setProgramTemplate(const osg::Program* program) { mProgramTemplate = program; }

View file

@ -189,6 +189,7 @@ namespace Shader
, mReconstructNormalZ(false) , mReconstructNormalZ(false)
, mTexStageRequiringTangents(-1) , mTexStageRequiringTangents(-1)
, mSoftParticles(false) , mSoftParticles(false)
, mTreeAnim(false)
, mNode(nullptr) , mNode(nullptr)
{ {
} }
@ -308,6 +309,10 @@ namespace Shader
if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect)
mRequirements.back().mSoftParticles = true; mRequirements.back().mSoftParticles = true;
bool treeAnim = false;
if (node.getUserValue(Misc::OsgUserValues::sTreeAnim, treeAnim) && treeAnim)
mRequirements.back().mTreeAnim = true;
// Make sure to disregard any state that came from a previous call to createProgram // Make sure to disregard any state that came from a previous call to createProgram
osg::ref_ptr<AddedState> addedState = getAddedState(*stateset); osg::ref_ptr<AddedState> addedState = getAddedState(*stateset);
@ -736,6 +741,11 @@ namespace Shader
addedState->addUniform("opaqueDepthTex"); addedState->addUniform("opaqueDepthTex");
} }
if (reqs.mTreeAnim)
{
defineMap["treeAnim"] = "1";
}
if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT
&& !previousAddedState->hasMode(GL_ALPHA_TEST)) && !previousAddedState->hasMode(GL_ALPHA_TEST))
removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST));
@ -764,6 +774,16 @@ namespace Shader
shaderPrefix = mDefaultShaderPrefix; shaderPrefix = mDefaultShaderPrefix;
auto program = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate); auto program = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate);
if (reqs.mTreeAnim)
{
program->addBindAttribLocation("aLeafParams", 7);
defineMap["shadowCasting"] = "1";
osg::ref_ptr<osg::StateSet> shadowCastingStateSet = new osg::StateSet;
auto shadowCastingProgram = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate);
shadowCastingStateSet->setAttribute(shadowCastingProgram,
osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE);
writableStateSet->setUserData(new ShaderManager::ShadowCastingStateSet(std::move(shadowCastingStateSet)));
}
writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON);
addedState->setAttributeAndModes(std::move(program)); addedState->setAttributeAndModes(std::move(program));

View file

@ -117,6 +117,8 @@ namespace Shader
bool mSoftParticles; bool mSoftParticles;
bool mTreeAnim;
// the Node that requested these requirements // the Node that requested these requirements
osg::Node* mNode; osg::Node* mNode;
}; };

View file

@ -10,6 +10,8 @@ set(SHADER_FILES
lib/water/fresnel.glsl lib/water/fresnel.glsl
lib/water/rain_ripples.glsl lib/water/rain_ripples.glsl
lib/water/ripples.glsl lib/water/ripples.glsl
lib/nature/leaves.glsl
lib/nature/leaves.h.glsl
lib/view/depth.glsl lib/view/depth.glsl
lib/luminance/constants.glsl lib/luminance/constants.glsl
lib/particle/soft.glsl lib/particle/soft.glsl

View file

@ -37,7 +37,6 @@ uniform float far;
uniform float alphaRef; uniform float alphaRef;
uniform float emissiveMult; uniform float emissiveMult;
uniform float specStrength; uniform float specStrength;
uniform bool useTreeAnim;
uniform float distortionStrength; uniform float distortionStrength;
#include "lib/core/fragment.h.glsl" #include "lib/core/fragment.h.glsl"
@ -52,6 +51,18 @@ uniform float distortionStrength;
void main() void main()
{ {
#if @shadowCasting
#if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
#endif
gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef);
if (gl_FragData[0].a <= 0.5)
discard;
return;
#endif
#if @diffuseMap #if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
@ -69,8 +80,9 @@ void main()
#endif #endif
vec4 diffuseColor = getDiffuseColor(); vec4 diffuseColor = getDiffuseColor();
if (!useTreeAnim) #if !@treeAnim
gl_FragData[0].a *= diffuseColor.a; gl_FragData[0].a *= diffuseColor.a;
#endif
gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef);
vec3 specularColor = getSpecularColor().xyz; vec3 specularColor = getSpecularColor().xyz;

View file

@ -11,6 +11,7 @@
#define PER_PIXEL_LIGHTING 1 #define PER_PIXEL_LIGHTING 1
#include "lib/core/vertex.h.glsl" #include "lib/core/vertex.h.glsl"
#include "lib/nature/leaves.h.glsl"
#if @diffuseMap #if @diffuseMap
varying vec2 diffuseMapUV; varying vec2 diffuseMapUV;
@ -31,6 +32,10 @@ varying float linearDepth;
varying vec3 passViewPos; varying vec3 passViewPos;
varying vec3 passNormal; varying vec3 passNormal;
#if @treeAnim
attribute vec3 aLeafParams;
#endif
#include "lib/light/lighting.glsl" #include "lib/light/lighting.glsl"
#include "lib/view/depth.glsl" #include "lib/view/depth.glsl"
@ -40,9 +45,29 @@ varying vec3 passNormal;
void main(void) void main(void)
{ {
gl_Position = modelToClip(gl_Vertex); vec4 vertex = gl_Vertex;
vec4 viewPos = modelToView(gl_Vertex); #if @treeAnim
vertex = transformLeafVertex(LeafParams(
aLeafParams.x,
aLeafParams.y,
aLeafParams.z
), gl_Vertex, gl_Color, gl_Normal.xyz);
#endif
#if @diffuseMap
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
#endif
#if @shadowCasting
gl_Position = gl_ModelViewProjectionMatrix * vertex;
gl_ClipVertex = (gl_ModelViewMatrix * vertex);
return;
#endif
gl_Position = modelToClip(vertex);
vec4 viewPos = modelToView(vertex);
gl_ClipVertex = viewPos; gl_ClipVertex = viewPos;
euclideanDepth = length(viewPos.xyz); euclideanDepth = length(viewPos.xyz);
linearDepth = getLinearDepth(gl_Position.z, viewPos.z); linearDepth = getLinearDepth(gl_Position.z, viewPos.z);
@ -55,10 +80,6 @@ void main(void)
normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw, passNormal); normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw, passNormal);
#endif #endif
#if @diffuseMap
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
#endif
#if @emissiveMap #if @emissiveMap
emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy;
#endif #endif

View file

@ -43,6 +43,18 @@ uniform float softFalloffDepth;
void main() void main()
{ {
#if @shadowCasting
#if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
#endif
gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef);
if (gl_FragData[0].a <= 0.5)
discard;
return;
#endif
#if @diffuseMap #if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV);

View file

@ -9,6 +9,7 @@
#endif #endif
#include "lib/core/vertex.h.glsl" #include "lib/core/vertex.h.glsl"
#include "lib/nature/leaves.h.glsl"
#if @diffuseMap #if @diffuseMap
varying vec2 diffuseMapUV; varying vec2 diffuseMapUV;
@ -23,6 +24,10 @@ varying float passFalloff;
uniform bool useFalloff; uniform bool useFalloff;
uniform vec4 falloffParams; uniform vec4 falloffParams;
#if @treeAnim
attribute vec3 aLeafParams;
#endif
#include "lib/view/depth.glsl" #include "lib/view/depth.glsl"
#include "compatibility/vertexcolors.glsl" #include "compatibility/vertexcolors.glsl"
@ -30,17 +35,31 @@ uniform vec4 falloffParams;
void main(void) void main(void)
{ {
gl_Position = modelToClip(gl_Vertex); vec4 vertex = gl_Vertex;
vec4 viewPos = modelToView(gl_Vertex); #if @treeAnim
gl_ClipVertex = viewPos; vertex = transformLeafVertex(LeafParams(
euclideanDepth = length(viewPos.xyz); aLeafParams.x,
linearDepth = getLinearDepth(gl_Position.z, viewPos.z); aLeafParams.y,
aLeafParams.z
), gl_Vertex, gl_Color, gl_Normal.xyz);
#endif
#if @diffuseMap #if @diffuseMap
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
#endif #endif
#if @shadowCasting
gl_Position = gl_ModelViewProjectionMatrix * vertex;
gl_ClipVertex = (gl_ModelViewMatrix * vertex);
return;
#endif
vec4 viewPos = modelToView(vertex);
gl_ClipVertex = viewPos;
euclideanDepth = length(viewPos.xyz);
linearDepth = getLinearDepth(gl_Position.z, viewPos.z);
passColor = gl_Color; passColor = gl_Color;
passViewPos = viewPos.xyz; passViewPos = viewPos.xyz;
passNormal = gl_Normal.xyz; passNormal = gl_Normal.xyz;

View file

@ -5,7 +5,6 @@ varying vec2 diffuseMapUV;
varying float alphaPassthrough; varying float alphaPassthrough;
uniform int colorMode; uniform int colorMode;
uniform bool useTreeAnim;
uniform bool useDiffuseMapForShadowAlpha = true; uniform bool useDiffuseMapForShadowAlpha = true;
uniform bool alphaTestShadows = true; uniform bool alphaTestShadows = true;
@ -21,7 +20,7 @@ void main(void)
else else
diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions
if (colorMode == 2) if (colorMode == 2)
alphaPassthrough = useTreeAnim ? 1.0 : gl_Color.a; alphaPassthrough = gl_Color.a;
else else
// This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser // This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser
alphaPassthrough = gl_FrontMaterial.diffuse.a; alphaPassthrough = gl_FrontMaterial.diffuse.a;

View file

@ -0,0 +1,36 @@
#version 120
#include "lib/nature/leaves.h.glsl"
uniform float osg_SimulationTime;
uniform float windSpeed;
vec4 transformLeafVertex(LeafParams params, vec4 position, vec4 color, vec3 normal)
{
// Default constants for leaf/tree animation fading from the Skyrim.ini
const float fLeafAnimDampenDistEnd = 4600.0;
const float fLeafAnimDampenDistStart = 3600.0;
float distance = length(gl_ModelViewMatrix * position);
float fade = 1.0
- clamp((distance - fLeafAnimDampenDistStart) / (fLeafAnimDampenDistEnd - fLeafAnimDampenDistStart), 0.0, 1.0);
float amplitude = color.a * params.mLeafAmplitude * fade;
if (amplitude <= 0.f)
return position;
float offset = params.mTimeOffset + position.x * 0.1 + position.y * 0.1;
float period = 0.5 * sin(0.15 * osg_SimulationTime * 6.136 + params.mTimeOffset) + 0.5;
float wind = sin(osg_SimulationTime * params.mLeafFrequency + offset) * params.mLeafAmplitude * windSpeed * period;
float spatialOffset = dot(position.xyz, vec3(1.0));
const vec2 axisFrequencyFactor = vec2(0.1, 0.25);
vec2 phase = fract(axisFrequencyFactor * (wind * params.mLeafFrequency * 10) + spatialOffset + 0.5);
vec2 leafMotion = smoothstep(0.0, 1.0, abs(2.0 * phase - 1.0));
float normalMultiplier = (leafMotion.x + 0.1 * leafMotion.y) * amplitude;
position.xyz += normal.xyz * normalMultiplier;
return position;
}

View file

@ -0,0 +1,15 @@
#ifndef OPENMW_LIB_NATURE_LEAVES_H_GLSL
#define OPENMW_LIB_NATURE_LEAVES_H_GLSL
@link "lib/nature/leaves.glsl"
struct LeafParams
{
float mLeafAmplitude;
float mLeafFrequency;
float mTimeOffset;
};
vec4 transformLeafVertex(LeafParams params, vec4 position, vec4 color, vec3 normal);
#endif // OPENMW_LIB_NATURE_LEAVES_H_GLSL