Merge branch 'toboldlygowherenowizardcathasgonebefore' into 'master'

Support Bethesda shader material files (#7777)

Closes #7777

See merge request OpenMW/openmw!4041
pull/3235/head
psi29a 4 weeks ago
commit 7a172b061f

@ -219,6 +219,7 @@
Feature #7652: Sort inactive post processing shaders list properly Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher Feature #7709: Improve resolution selection in Launcher
Feature #7777: Support external Bethesda material files (BGSM/BGEM)
Feature #7792: Support Timescale Clouds Feature #7792: Support Timescale Clouds
Feature #7795: Support MaxNumberRipples INI setting Feature #7795: Support MaxNumberRipples INI setting
Feature #7805: Lua Menu context Feature #7805: Lua Menu context

@ -12,6 +12,7 @@
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/platform/platform.hpp> #include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshape.hpp> #include <components/resource/bulletshape.hpp>
#include <components/resource/bulletshapemanager.hpp> #include <components/resource/bulletshapemanager.hpp>
#include <components/resource/foreachbulletobject.hpp> #include <components/resource/foreachbulletobject.hpp>
@ -173,7 +174,8 @@ namespace
constexpr double expiryDelay = 0; constexpr double expiryDelay = 0;
Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
Resource::forEachBulletObject( Resource::forEachBulletObject(

@ -19,6 +19,7 @@
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/platform/platform.hpp> #include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshapemanager.hpp> #include <components/resource/bulletshapemanager.hpp>
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp> #include <components/resource/niffilemanager.hpp>
@ -220,7 +221,8 @@ namespace NavMeshTool
Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();

@ -8,6 +8,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <components/bgsm/reader.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/constrainedfilestream.hpp> #include <components/files/constrainedfilestream.hpp>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
@ -25,7 +26,7 @@
namespace bpo = boost::program_options; namespace bpo = boost::program_options;
/// See if the file has the named extension /// See if the file has the named extension
bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind) bool hasExtension(const std::filesystem::path& filename, std::string_view extensionToFind)
{ {
const auto extension = Files::pathToUnicodeString(filename.extension()); const auto extension = Files::pathToUnicodeString(filename.extension());
return Misc::StringUtils::ciEqual(extension, extensionToFind); return Misc::StringUtils::ciEqual(extension, extensionToFind);
@ -36,6 +37,13 @@ bool isNIF(const std::filesystem::path& filename)
{ {
return hasExtension(filename, ".nif") || hasExtension(filename, ".kf"); return hasExtension(filename, ".nif") || hasExtension(filename, ".kf");
} }
/// Check if the file is a material file.
bool isMaterial(const std::filesystem::path& filename)
{
return hasExtension(filename, ".bgem") || hasExtension(filename, ".bgsm");
}
/// See if the file has the "bsa" extension. /// See if the file has the "bsa" extension.
bool isBSA(const std::filesystem::path& filename) bool isBSA(const std::filesystem::path& filename)
{ {
@ -51,16 +59,17 @@ std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
return nullptr; return nullptr;
} }
void readNIF( void readFile(
const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet)
{ {
const std::string pathStr = Files::pathToUnicodeString(path); const std::string pathStr = Files::pathToUnicodeString(path);
const bool isNif = isNIF(path);
if (!quiet) if (!quiet)
{ {
if (hasExtension(path, ".kf")) if (isNif)
std::cout << "Reading KF file '" << pathStr << "'"; std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'";
else else
std::cout << "Reading NIF file '" << pathStr << "'"; std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'";
if (!source.empty()) if (!source.empty())
std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'";
std::cout << std::endl; std::cout << std::endl;
@ -68,12 +77,23 @@ void readNIF(
const std::filesystem::path fullPath = !source.empty() ? source / path : path; const std::filesystem::path fullPath = !source.empty() ? source / path : path;
try try
{ {
Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); if (isNif)
Nif::Reader reader(file, nullptr); {
if (vfs != nullptr) Nif::NIFFile file(Files::pathToUnicodeString(fullPath));
reader.parse(vfs->get(pathStr)); Nif::Reader reader(file, nullptr);
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
else
reader.parse(Files::openConstrainedFileStream(fullPath));
}
else else
reader.parse(Files::openConstrainedFileStream(fullPath)); {
Bgsm::Reader reader;
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
else
reader.parse(Files::openConstrainedFileStream(fullPath));
}
} }
catch (std::exception& e) catch (std::exception& e)
{ {
@ -97,9 +117,9 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
for (const auto& name : vfs.getRecursiveDirectoryIterator("")) for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
{ {
if (isNIF(name.value())) if (isNIF(name.value()) || isMaterial(name.value()))
{ {
readNIF(archivePath, name.value(), &vfs, quiet); readFile(archivePath, name.value(), &vfs, quiet);
} }
} }
@ -129,10 +149,10 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives,
bool& writeDebugLog, bool& quiet) bool& writeDebugLog, bool& quiet)
{ {
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF, BGEM/BGSM and BSA/BA2 files
Usages: Usages:
niftest <nif files, kf files, BSA/BA2 files, or directories> niftest <nif files, kf files, bgem/bgsm files, BSA/BA2 files, or directories>
Scan the file or directories for NIF errors. Scan the file or directories for NIF errors.
Allowed options)"); Allowed options)");
@ -221,9 +241,9 @@ int main(int argc, char** argv)
const std::string pathStr = Files::pathToUnicodeString(path); const std::string pathStr = Files::pathToUnicodeString(path);
try try
{ {
if (isNIF(path)) if (isNIF(path) || isMaterial(path))
{ {
readNIF({}, path, vfs.get(), quiet); readFile({}, path, vfs.get(), quiet);
} }
else if (auto archive = makeArchive(path)) else if (auto archive = makeArchive(path))
{ {
@ -231,7 +251,7 @@ int main(int argc, char** argv)
} }
else else
{ {
std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory" std::cerr << "Error: '" << pathStr << "' is not a NIF/KF/BGEM/BGSM file, BSA/BA2 archive, or directory"
<< std::endl; << std::endl;
} }
} }

@ -3,6 +3,7 @@
#include <components/nif/node.hpp> #include <components/nif/node.hpp>
#include <components/nif/property.hpp> #include <components/nif/property.hpp>
#include <components/nifosg/nifloader.hpp> #include <components/nifosg/nifloader.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/sceneutil/serialize.hpp> #include <components/sceneutil/serialize.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
@ -29,6 +30,7 @@ namespace
{ {
VFS::Manager mVfs; VFS::Manager mVfs;
Resource::ImageManager mImageManager{ &mVfs, 0 }; Resource::ImageManager mImageManager{ &mVfs, 0 };
Resource::BgsmFileManager mMaterialManager{ &mVfs, 0 };
const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt"); const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt");
osg::ref_ptr<osgDB::Options> mOptions = new osgDB::Options; osg::ref_ptr<osgDB::Options> mOptions = new osgDB::Options;
@ -70,7 +72,7 @@ namespace
init(node); init(node);
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node); file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager); auto result = Loader::load(file, &mImageManager, &mMaterialManager);
EXPECT_EQ(serialize(*result), R"( EXPECT_EQ(serialize(*result), R"(
osg::Group { osg::Group {
UniqueID 1 UniqueID 1
@ -259,7 +261,7 @@ osg::Group {
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property)); node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node); file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager); auto result = Loader::load(file, &mImageManager, &mMaterialManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix));
} }
@ -289,7 +291,7 @@ osg::Group {
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property)); node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node); file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager); auto result = Loader::load(file, &mImageManager, &mMaterialManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix));
} }

@ -107,6 +107,10 @@ add_component_dir (settings
windowmode windowmode
) )
add_component_dir (bgsm
reader stream file
)
add_component_dir (bsa add_component_dir (bsa
bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream
) )
@ -125,7 +129,7 @@ add_component_dir (vfs
add_component_dir (resource add_component_dir (resource
scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem
resourcemanager stats animation foreachbulletobject errormarker cachestats resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager
) )
add_component_dir (shader add_component_dir (shader

@ -0,0 +1,208 @@
#include "file.hpp"
#include "stream.hpp"
namespace Bgsm
{
void MaterialFile::read(BGSMStream& stream)
{
stream.read(mVersion);
stream.read(mClamp);
stream.read(mUVOffset);
stream.read(mUVScale);
stream.read(mTransparency);
stream.read(mAlphaBlend);
stream.read(mSourceBlendMode);
stream.read(mDestinationBlendMode);
stream.read(mAlphaTestThreshold);
stream.read(mAlphaTest);
stream.read(mDepthWrite);
stream.read(mDepthTest);
stream.read(mSSR);
stream.read(mWetnessControlSSR);
stream.read(mDecal);
stream.read(mTwoSided);
stream.read(mDecalNoFade);
stream.read(mNonOccluder);
stream.read(mRefraction);
stream.read(mRefractionFalloff);
stream.read(mRefractionPower);
if (mVersion < 10)
{
stream.read(mEnvMapEnabled);
stream.read(mEnvMapMaskScale);
}
else
{
stream.read(mDepthBias);
}
stream.read(mGrayscaleToPaletteColor);
if (mVersion >= 6)
stream.read(mMaskWrites);
}
void BGSMFile::read(BGSMStream& stream)
{
MaterialFile::read(stream);
stream.read(mDiffuseMap);
stream.read(mNormalMap);
stream.read(mSmoothSpecMap);
stream.read(mGrayscaleMap);
if (mVersion >= 3)
{
stream.read(mGlowMap);
stream.read(mWrinkleMap);
stream.read(mSpecularMap);
stream.read(mLightingMap);
stream.read(mFlowMap);
if (mVersion >= 17)
stream.read(mDistanceFieldAlphaMap);
}
else
{
stream.read(mEnvMap);
stream.read(mGlowMap);
stream.read(mInnerLayerMap);
stream.read(mWrinkleMap);
stream.read(mDisplacementMap);
}
stream.read(mEnableEditorAlphaThreshold);
if (mVersion >= 8)
{
stream.read(mTranslucency);
stream.read(mTranslucencyThickObject);
stream.read(mTranslucencyMixAlbedoWithSubsurfaceColor);
stream.read(mTranslucencySubsurfaceColor);
stream.read(mTranslucencyTransmissiveScale);
stream.read(mTranslucencyTurbulence);
}
else
{
stream.read(mRimLighting);
stream.read(mRimPower);
stream.read(mBackLightPower);
stream.read(mSubsurfaceLighting);
stream.read(mSubsurfaceLightingRolloff);
}
stream.read(mSpecularEnabled);
stream.read(mSpecularColor);
stream.read(mSpecularMult);
stream.read(mSmoothness);
stream.read(mFresnelPower);
stream.read(mWetnessControlSpecScale);
stream.read(mWetnessControlSpecPowerScale);
stream.read(mWetnessControlSpecMinvar);
if (mVersion < 10)
stream.read(mWetnessControlEnvMapScale);
stream.read(mWetnessControlFresnelPower);
stream.read(mWetnessControlMetalness);
if (mVersion >= 3)
{
stream.read(mPBR);
if (mVersion >= 9)
{
stream.read(mCustomPorosity);
stream.read(mPorosityValue);
}
}
stream.read(mRootMaterialPath);
stream.read(mAnisoLighting);
stream.read(mEmitEnabled);
if (mEmitEnabled)
stream.read(mEmittanceColor);
stream.read(mEmittanceMult);
stream.read(mModelSpaceNormals);
stream.read(mExternalEmittance);
if (mVersion >= 12)
{
stream.read(mLumEmittance);
if (mVersion >= 13)
{
stream.read(mUseAdaptiveEmissive);
stream.read(mAdaptiveEmissiveExposureParams);
}
}
else if (mVersion < 8)
{
stream.read(mBackLighting);
}
stream.read(mReceiveShadows);
stream.read(mHideSecret);
stream.read(mCastShadows);
stream.read(mDissolveFade);
stream.read(mAssumeShadowmask);
stream.read(mGlowMapEnabled);
if (mVersion < 7)
{
stream.read(mEnvMapWindow);
stream.read(mEnvMapEye);
}
stream.read(mHair);
stream.read(mHairTintColor);
stream.read(mTree);
stream.read(mFacegen);
stream.read(mSkinTint);
stream.read(mTessellate);
if (mVersion < 3)
{
stream.read(mDisplacementMapParams);
stream.read(mTessellationParams);
}
stream.read(mGrayscaleToPaletteScale);
if (mVersion >= 1)
{
stream.read(mSkewSpecularAlpha);
stream.read(mTerrain);
if (mTerrain)
{
if (mVersion == 3)
stream.skip(4); // Unknown
stream.read(mTerrainParams);
}
}
}
void BGEMFile::read(BGSMStream& stream)
{
MaterialFile::read(stream);
stream.read(mBaseMap);
stream.read(mGrayscaleMap);
stream.read(mEnvMap);
stream.read(mNormalMap);
stream.read(mEnvMapMask);
if (mVersion >= 11)
{
stream.read(mSpecularMap);
stream.read(mLightingMap);
stream.read(mGlowMap);
}
if (mVersion >= 10)
{
stream.read(mEnvMapEnabled);
stream.read(mEnvMapMaskScale);
}
stream.read(mBlood);
stream.read(mEffectLighting);
stream.read(mFalloff);
stream.read(mFalloffColor);
stream.read(mGrayscaleToPaletteAlpha);
stream.read(mSoft);
stream.read(mBaseColor);
stream.read(mBaseColorScale);
stream.read(mFalloffParams);
stream.read(mLightingInfluence);
stream.read(mEnvmapMinLOD);
stream.read(mSoftDepth);
if (mVersion >= 11)
stream.read(mEmittanceColor);
if (mVersion >= 15)
stream.read(mAdaptiveEmissiveExposureParams);
if (mVersion >= 16)
stream.read(mGlowMapEnabled);
if (mVersion >= 20)
stream.read(mEffectPbrSpecular);
}
}

@ -0,0 +1,164 @@
#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP
#define OPENMW_COMPONENTS_BGSM_FILE_HPP
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
namespace Bgsm
{
class BGSMStream;
enum class ShaderType
{
Lighting,
Effect,
};
struct MaterialFile
{
ShaderType mShaderType;
std::uint32_t mVersion;
std::uint32_t mClamp;
osg::Vec2f mUVOffset, mUVScale;
float mTransparency;
bool mAlphaBlend;
std::uint32_t mSourceBlendMode;
std::uint32_t mDestinationBlendMode;
std::uint8_t mAlphaTestThreshold;
bool mAlphaTest;
bool mDepthWrite, mDepthTest;
bool mSSR;
bool mWetnessControlSSR;
bool mDecal;
bool mTwoSided;
bool mDecalNoFade;
bool mNonOccluder;
bool mRefraction;
bool mRefractionFalloff;
float mRefractionPower;
bool mEnvMapEnabled;
float mEnvMapMaskScale;
bool mDepthBias;
bool mGrayscaleToPaletteColor;
std::uint8_t mMaskWrites;
MaterialFile() = default;
virtual void read(BGSMStream& stream);
virtual ~MaterialFile() = default;
};
struct BGSMFile : MaterialFile
{
std::string mDiffuseMap;
std::string mNormalMap;
std::string mSmoothSpecMap;
std::string mGrayscaleMap;
std::string mGlowMap;
std::string mWrinkleMap;
std::string mSpecularMap;
std::string mLightingMap;
std::string mFlowMap;
std::string mDistanceFieldAlphaMap;
std::string mEnvMap;
std::string mInnerLayerMap;
std::string mDisplacementMap;
bool mEnableEditorAlphaThreshold;
bool mTranslucency;
bool mTranslucencyThickObject;
bool mTranslucencyMixAlbedoWithSubsurfaceColor;
osg::Vec3f mTranslucencySubsurfaceColor;
float mTranslucencyTransmissiveScale;
float mTranslucencyTurbulence;
bool mRimLighting;
float mRimPower;
float mBackLightPower;
bool mSubsurfaceLighting;
float mSubsurfaceLightingRolloff;
bool mSpecularEnabled;
osg::Vec3f mSpecularColor;
float mSpecularMult;
float mSmoothness;
float mFresnelPower;
float mWetnessControlSpecScale;
float mWetnessControlSpecPowerScale;
float mWetnessControlSpecMinvar;
float mWetnessControlEnvMapScale;
float mWetnessControlFresnelPower;
float mWetnessControlMetalness;
bool mPBR;
bool mCustomPorosity;
float mPorosityValue;
std::string mRootMaterialPath;
bool mAnisoLighting;
bool mEmitEnabled;
osg::Vec3f mEmittanceColor;
float mEmittanceMult;
bool mModelSpaceNormals;
bool mExternalEmittance;
float mLumEmittance;
bool mUseAdaptiveEmissive;
osg::Vec3f mAdaptiveEmissiveExposureParams;
bool mBackLighting;
bool mReceiveShadows;
bool mHideSecret;
bool mCastShadows;
bool mDissolveFade;
bool mAssumeShadowmask;
bool mGlowMapEnabled;
bool mEnvMapWindow;
bool mEnvMapEye;
bool mHair;
osg::Vec3f mHairTintColor;
bool mTree;
bool mFacegen;
bool mSkinTint;
bool mTessellate;
osg::Vec2f mDisplacementMapParams;
osg::Vec3f mTessellationParams;
float mGrayscaleToPaletteScale;
bool mSkewSpecularAlpha;
bool mTerrain;
osg::Vec3f mTerrainParams;
void read(BGSMStream& stream) override;
};
struct BGEMFile : MaterialFile
{
std::string mBaseMap;
std::string mGrayscaleMap;
std::string mEnvMap;
std::string mNormalMap;
std::string mEnvMapMask;
std::string mSpecularMap;
std::string mLightingMap;
std::string mGlowMap;
bool mBlood;
bool mEffectLighting;
bool mFalloff;
bool mFalloffColor;
bool mGrayscaleToPaletteAlpha;
bool mSoft;
osg::Vec3f mBaseColor;
float mBaseColorScale;
osg::Vec4f mFalloffParams;
float mLightingInfluence;
std::uint8_t mEnvmapMinLOD;
float mSoftDepth;
osg::Vec3f mEmittanceColor;
osg::Vec3f mAdaptiveEmissiveExposureParams;
bool mGlowMapEnabled;
bool mEffectPbrSpecular;
void read(BGSMStream& stream) override;
};
using MaterialFilePtr = std::shared_ptr<const Bgsm::MaterialFile>;
}
#endif

@ -0,0 +1,33 @@
#include "reader.hpp"
#include <array>
#include <stdexcept>
#include <string>
#include "stream.hpp"
namespace Bgsm
{
void Reader::parse(Files::IStreamPtr&& inputStream)
{
BGSMStream stream(std::move(inputStream));
std::array<char, 4> signature;
stream.readArray(signature);
std::string shaderType(signature.data(), 4);
if (shaderType == "BGEM")
{
mFile = std::make_unique<BGEMFile>();
mFile->mShaderType = Bgsm::ShaderType::Effect;
}
else if (shaderType == "BGSM")
{
mFile = std::make_unique<BGSMFile>();
mFile->mShaderType = Bgsm::ShaderType::Lighting;
}
else
throw std::runtime_error("Invalid material file");
mFile->read(stream);
}
}

@ -0,0 +1,22 @@
#ifndef OPENMW_COMPONENTS_BGSM_READER_HPP
#define OPENMW_COMPONENTS_BGSM_READER_HPP
#include <memory>
#include <components/files/istreamptr.hpp>
#include "file.hpp"
namespace Bgsm
{
class Reader
{
std::unique_ptr<MaterialFile> mFile;
public:
void parse(Files::IStreamPtr&& stream);
std::unique_ptr<MaterialFile> getFile() { return std::move(mFile); }
};
}
#endif

@ -0,0 +1,39 @@
#include "stream.hpp"
namespace Bgsm
{
template <>
void BGSMStream::read<osg::Vec2f>(osg::Vec2f& vec)
{
readBufferOfType(mStream, vec._v);
}
template <>
void BGSMStream::read<osg::Vec3f>(osg::Vec3f& vec)
{
readBufferOfType(mStream, vec._v);
}
template <>
void BGSMStream::read<osg::Vec4f>(osg::Vec4f& vec)
{
readBufferOfType(mStream, vec._v);
}
template <>
void BGSMStream::read<std::string>(std::string& str)
{
std::uint32_t length;
read(length);
// Prevent potential memory allocation freezes; strings this long are not expected in BGSM
if (length > 1024)
throw std::runtime_error("Requested string length is too large: " + std::to_string(length));
str = std::string(length, '\0');
mStream->read(str.data(), length);
if (mStream->bad())
throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars");
std::size_t end = str.find('\0');
if (end != std::string::npos)
str.erase(end);
}
}

@ -0,0 +1,77 @@
#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP
#define OPENMW_COMPONENTS_BGSM_STREAM_HPP
#include <array>
#include <cassert>
#include <cstdint>
#include <istream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <components/files/istreamptr.hpp>
#include <components/misc/endianness.hpp>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
namespace Bgsm
{
template <std::size_t numInstances, typename T>
inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest)
{
static_assert(std::is_arithmetic_v<T>, "Buffer element type is not arithmetic");
pIStream->read(reinterpret_cast<char*>(dest), numInstances * sizeof(T));
if (pIStream->bad())
throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of "
+ std::to_string(numInstances) + " instances");
if constexpr (Misc::IS_BIG_ENDIAN)
for (std::size_t i = 0; i < numInstances; i++)
Misc::swapEndiannessInplace(dest[i]);
}
template <std::size_t numInstances, typename T>
inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances])
{
readBufferOfType<numInstances>(pIStream, static_cast<T*>(dest));
}
class BGSMStream
{
Files::IStreamPtr mStream;
public:
explicit BGSMStream(Files::IStreamPtr&& stream)
: mStream(std::move(stream))
{
}
void skip(size_t size) { mStream->ignore(size); }
/// Read into a single instance of type
template <class T>
void read(T& data)
{
readBufferOfType<1>(mStream, &data);
}
/// Read multiple instances of type into an array
template <class T, size_t size>
void readArray(std::array<T, size>& arr)
{
readBufferOfType<size>(mStream, arr.data());
}
};
template <>
void BGSMStream::read<osg::Vec2f>(osg::Vec2f& vec);
template <>
void BGSMStream::read<osg::Vec3f>(osg::Vec3f& vec);
template <>
void BGSMStream::read<osg::Vec4f>(osg::Vec4f& vec);
template <>
void BGSMStream::read<std::string>(std::string& str);
}
#endif

@ -173,6 +173,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP
return mdlname; return mdlname;
} }
std::string Misc::ResourceHelpers::correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs)
{
return correctResourcePath({ { "materials" } }, resPath, vfs);
}
std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath)
{ {
std::string res = "meshes\\"; std::string res = "meshes\\";

@ -34,6 +34,7 @@ namespace Misc
/// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available /// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available
/// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :( /// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :(
std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs);
std::string correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs);
// Adds "meshes\\". // Adds "meshes\\".
std::string correctMeshPath(std::string_view resPath); std::string correctMeshPath(std::string_view resPath);

@ -21,6 +21,7 @@
#include <components/misc/strings/algorithm.hpp> #include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
#include <components/nif/parent.hpp> #include <components/nif/parent.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
// particle // particle
@ -42,6 +43,7 @@
#include <osg/TexEnvCombine> #include <osg/TexEnvCombine>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <components/bgsm/file.hpp>
#include <components/nif/effect.hpp> #include <components/nif/effect.hpp>
#include <components/nif/exception.hpp> #include <components/nif/exception.hpp>
#include <components/nif/extra.hpp> #include <components/nif/extra.hpp>
@ -247,6 +249,8 @@ namespace NifOsg
} }
std::filesystem::path mFilename; std::filesystem::path mFilename;
unsigned int mVersion, mUserVersion, mBethVersion; unsigned int mVersion, mUserVersion, mBethVersion;
Resource::BgsmFileManager* mMaterialManager{ nullptr };
Resource::ImageManager* mImageManager{ nullptr };
size_t mFirstRootTextureIndex{ ~0u }; size_t mFirstRootTextureIndex{ ~0u };
bool mFoundFirstRootTexturingProperty = false; bool mFoundFirstRootTexturingProperty = false;
@ -339,7 +343,6 @@ namespace NifOsg
struct HandleNodeArgs struct HandleNodeArgs
{ {
unsigned int mNifVersion; unsigned int mNifVersion;
Resource::ImageManager* mImageManager;
SceneUtil::TextKeyMap* mTextKeys; SceneUtil::TextKeyMap* mTextKeys;
std::vector<unsigned int> mBoundTextures = {}; std::vector<unsigned int> mBoundTextures = {};
int mAnimFlags = 0; int mAnimFlags = 0;
@ -349,7 +352,7 @@ namespace NifOsg
osg::Node* mRootNode = nullptr; osg::Node* mRootNode = nullptr;
}; };
osg::ref_ptr<osg::Node> load(Nif::FileView nif, Resource::ImageManager* imageManager) osg::ref_ptr<osg::Node> load(Nif::FileView nif)
{ {
const size_t numRoots = nif.numRoots(); const size_t numRoots = nif.numRoots();
std::vector<const Nif::NiAVObject*> roots; std::vector<const Nif::NiAVObject*> roots;
@ -371,10 +374,8 @@ namespace NifOsg
created->setDataVariance(osg::Object::STATIC); created->setDataVariance(osg::Object::STATIC);
for (const Nif::NiAVObject* root : roots) for (const Nif::NiAVObject* root : roots)
{ {
auto node = handleNode(root, nullptr, nullptr, auto node = handleNode(
{ .mNifVersion = nif.getVersion(), root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), .mTextKeys = &textkeys->mTextKeys });
.mImageManager = imageManager,
.mTextKeys = &textkeys->mTextKeys });
created->addChild(node); created->addChild(node);
} }
if (mHasNightDayLabel) if (mHasNightDayLabel)
@ -405,8 +406,7 @@ namespace NifOsg
} }
void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo, void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo,
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, SceneUtil::CompositeStateSetUpdater* composite, std::vector<unsigned int>& boundTextures, int animflags)
std::vector<unsigned int>& boundTextures, int animflags)
{ {
bool hasStencilProperty = false; bool hasStencilProperty = false;
@ -444,8 +444,7 @@ namespace NifOsg
if (property.getPtr()->recIndex == mFirstRootTextureIndex) if (property.getPtr()->recIndex == mFirstRootTextureIndex)
applyTo->setUserValue("overrideFx", 1); applyTo->setUserValue("overrideFx", 1);
} }
handleProperty(property.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, handleProperty(property.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty);
hasStencilProperty);
} }
} }
@ -457,8 +456,7 @@ namespace NifOsg
shaderprop = static_cast<const Nif::BSTriShape*>(nifNode)->mShaderProperty; shaderprop = static_cast<const Nif::BSTriShape*>(nifNode)->mShaderProperty;
if (!shaderprop.empty()) if (!shaderprop.empty())
handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, handleProperty(shaderprop.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty);
hasStencilProperty);
} }
static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags)
@ -522,32 +520,21 @@ namespace NifOsg
sequenceNode->setMode(osg::Sequence::START); sequenceNode->setMode(osg::Sequence::START);
} }
osg::ref_ptr<osg::Image> handleSourceTexture( osg::ref_ptr<osg::Image> handleSourceTexture(const Nif::NiSourceTexture* st) const
const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager)
{ {
if (!st) if (st)
return nullptr;
osg::ref_ptr<osg::Image> image;
if (st->mExternal)
{ {
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS()); if (st->mExternal)
image = imageManager->getImage(filename); return getTextureImage(st->mFile);
}
else if (!st->mData.empty()) if (!st->mData.empty())
{ return handleInternalTexture(st->mData.getPtr());
image = handleInternalTexture(st->mData.getPtr());
} }
return image;
}
void handleTextureWrapping(osg::Texture2D* texture, bool wrapS, bool wrapT) return nullptr;
{
texture->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
} }
bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager) bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset)
{ {
if (nifNode->recType != Nif::RC_NiTextureEffect) if (nifNode->recType != Nif::RC_NiTextureEffect)
{ {
@ -590,16 +577,12 @@ namespace NifOsg
return false; return false;
} }
osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); const unsigned int uvSet = 0;
osg::ref_ptr<osg::Texture2D> texture2d(new osg::Texture2D(image)); const unsigned int texUnit = 3; // FIXME
if (image) std::vector<unsigned int> boundTextures;
texture2d->setTextureSize(image->s(), image->t()); boundTextures.resize(3); // Dummy vector for attachNiSourceTexture
texture2d->setName("envMap"); attachNiSourceTexture("envMap", textureEffect->mTexture.getPtr(), textureEffect->wrapS(),
handleTextureWrapping(texture2d, textureEffect->wrapS(), textureEffect->wrapT()); textureEffect->wrapT(), uvSet, stateset, boundTextures);
int texUnit = 3; // FIXME
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
@ -761,7 +744,7 @@ namespace NifOsg
osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater; osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater;
applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags);
const bool isNiGeometry = isTypeNiGeometry(nifNode->recType); const bool isNiGeometry = isTypeNiGeometry(nifNode->recType);
const bool isBSGeometry = isTypeBSGeometry(nifNode->recType); const bool isBSGeometry = isTypeBSGeometry(nifNode->recType);
@ -769,7 +752,7 @@ namespace NifOsg
if (isGeometry && !args.mSkipMeshes) if (isGeometry && !args.mSkipMeshes)
{ {
bool skip; bool skip = false;
if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW) if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW)
{ {
skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker")) skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker"))
@ -777,7 +760,11 @@ namespace NifOsg
|| Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow"); || Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow");
} }
else else
skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); {
if (args.mHasMarkers)
skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker")
|| Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker");
}
if (!skip) if (!skip)
{ {
if (isNiGeometry) if (isNiGeometry)
@ -859,7 +846,7 @@ namespace NifOsg
if (!effect.empty()) if (!effect.empty())
{ {
osg::ref_ptr<osg::StateSet> effectStateSet = new osg::StateSet; osg::ref_ptr<osg::StateSet> effectStateSet = new osg::StateSet;
if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager)) if (handleEffect(effect.getPtr(), effectStateSet))
for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i) for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i)
currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet); currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet);
} }
@ -1025,9 +1012,56 @@ namespace NifOsg
} }
} }
osg::ref_ptr<osg::Image> getTextureImage(std::string_view path) const
{
if (!mImageManager)
return nullptr;
std::string filename = Misc::ResourceHelpers::correctTexturePath(path, mImageManager->getVFS());
return mImageManager->getImage(filename);
}
osg::ref_ptr<osg::Texture2D> attachTexture(const std::string& name, osg::ref_ptr<osg::Image> image, bool wrapS,
bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
{
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
unsigned int texUnit = boundTextures.size();
if (stateset)
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
texture2d->setName(name);
boundTextures.emplace_back(uvSet);
return texture2d;
}
osg::ref_ptr<osg::Texture2D> attachExternalTexture(const std::string& name, const std::string& path, bool wrapS,
bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
{
return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures);
}
osg::ref_ptr<osg::Texture2D> attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st,
bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset,
std::vector<unsigned int>& boundTextures) const
{
return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures);
}
static void clearBoundTextures(osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
{
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
}
void handleTextureControllers(const Nif::NiProperty* texProperty, void handleTextureControllers(const Nif::NiProperty* texProperty,
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags)
osg::StateSet* stateset, int animflags)
{ {
for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext)
{ {
@ -1056,17 +1090,16 @@ namespace NifOsg
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
} }
const unsigned int uvSet = 0;
std::vector<unsigned int> boundTextures; // Dummy list for attachTexture
for (const auto& source : flipctrl->mSources) for (const auto& source : flipctrl->mSources)
{ {
if (source.empty()) if (source.empty())
continue; continue;
osg::ref_ptr<osg::Image> image(handleSourceTexture(source.getPtr(), imageManager)); // NB: not changing the stateset
osg::ref_ptr<osg::Texture2D> texture(new osg::Texture2D(image)); osg::ref_ptr<osg::Texture2D> texture
if (image) = attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures);
texture->setTextureSize(image->s(), image->t());
texture->setWrap(osg::Texture::WRAP_S, wrapS);
texture->setWrap(osg::Texture::WRAP_T, wrapT);
textures.push_back(texture); textures.push_back(texture);
} }
osg::ref_ptr<FlipController> callback(new FlipController(flipctrl, textures)); osg::ref_ptr<FlipController> callback(new FlipController(flipctrl, textures));
@ -1811,7 +1844,7 @@ namespace NifOsg
} }
} }
osg::ref_ptr<osg::Image> handleInternalTexture(const Nif::NiPixelData* pixelData) osg::ref_ptr<osg::Image> handleInternalTexture(const Nif::NiPixelData* pixelData) const
{ {
if (pixelData->mMipmaps.empty()) if (pixelData->mMipmaps.empty())
return nullptr; return nullptr;
@ -1946,7 +1979,7 @@ namespace NifOsg
return image; return image;
} }
osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv() static osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
{ {
osg::ref_ptr<osg::TexEnvCombine> texEnv(new osg::TexEnvCombine); osg::ref_ptr<osg::TexEnvCombine> texEnv(new osg::TexEnvCombine);
// Sum the previous colour and the emissive colour. // Sum the previous colour and the emissive colour.
@ -1977,33 +2010,42 @@ namespace NifOsg
void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName,
osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite,
Resource::ImageManager* imageManager, std::vector<unsigned int>& boundTextures, int animflags) std::vector<unsigned int>& boundTextures, int animflags)
{ {
if (!boundTextures.empty()) // overriding a parent NiTexturingProperty, so remove what was previously bound
{ clearBoundTextures(stateset, boundTextures);
// overriding a parent NiTexturingProperty, so remove what was previously bound
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
// If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the
// shadow casting shader will need to be updated accordingly. // shadow casting shader will need to be updated accordingly.
for (size_t i = 0; i < texprop->mTextures.size(); ++i) for (size_t i = 0; i < texprop->mTextures.size(); ++i)
{ {
if (texprop->mTextures[i].mEnabled const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i];
|| (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) if (tex.mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty()))
{ {
std::string textureName;
switch (i) switch (i)
{ {
// These are handled later on // These are handled later on
case Nif::NiTexturingProperty::BaseTexture: case Nif::NiTexturingProperty::BaseTexture:
textureName = "diffuseMap";
break;
case Nif::NiTexturingProperty::GlowTexture: case Nif::NiTexturingProperty::GlowTexture:
textureName = "glowMap";
break;
case Nif::NiTexturingProperty::DarkTexture: case Nif::NiTexturingProperty::DarkTexture:
textureName = "darkMap";
break;
case Nif::NiTexturingProperty::BumpTexture: case Nif::NiTexturingProperty::BumpTexture:
textureName = "bumpMap";
break;
case Nif::NiTexturingProperty::DetailTexture: case Nif::NiTexturingProperty::DetailTexture:
textureName = "detailMap";
break;
case Nif::NiTexturingProperty::DecalTexture: case Nif::NiTexturingProperty::DecalTexture:
textureName = "decalMap";
break;
case Nif::NiTexturingProperty::GlossTexture: case Nif::NiTexturingProperty::GlossTexture:
textureName = "glossMap";
break; break;
default: default:
{ {
@ -2013,12 +2055,9 @@ namespace NifOsg
} }
} }
unsigned int uvSet = 0; const unsigned int texUnit = boundTextures.size();
// create a new texture, will later attempt to share using the SharedStateManager if (tex.mEnabled)
osg::ref_ptr<osg::Texture2D> texture2d;
if (texprop->mTextures[i].mEnabled)
{ {
const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i];
if (tex.mSourceTexture.empty() && texprop->mController.empty()) if (tex.mSourceTexture.empty() && texprop->mController.empty())
{ {
if (i == 0) if (i == 0)
@ -2028,32 +2067,18 @@ namespace NifOsg
} }
if (!tex.mSourceTexture.empty()) if (!tex.mSourceTexture.empty())
{ attachNiSourceTexture(textureName, tex.mSourceTexture.getPtr(), tex.wrapS(), tex.wrapT(),
const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); tex.mUVSet, stateset, boundTextures);
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
}
else else
texture2d = new osg::Texture2D; attachTexture(
textureName, nullptr, tex.wrapS(), tex.wrapT(), tex.mUVSet, stateset, boundTextures);
handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT());
uvSet = tex.mUVSet;
} }
else else
{ {
// Texture only comes from NiFlipController, so tex is ignored, set defaults // Texture only comes from NiFlipController, so tex is ignored, set defaults
texture2d = new osg::Texture2D; attachTexture(textureName, nullptr, true, true, 0, stateset, boundTextures);
handleTextureWrapping(texture2d, true, true);
uvSet = 0;
} }
unsigned int texUnit = boundTextures.size();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
if (i == Nif::NiTexturingProperty::GlowTexture) if (i == Nif::NiTexturingProperty::GlowTexture)
{ {
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
@ -2121,51 +2146,165 @@ namespace NifOsg
texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA);
stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON);
} }
}
}
handleTextureControllers(texprop, composite, stateset, animflags);
}
switch (i) static Bgsm::MaterialFilePtr getShaderMaterial(
{ std::string_view path, Resource::BgsmFileManager* materialManager)
case Nif::NiTexturingProperty::BaseTexture: {
texture2d->setName("diffuseMap"); if (!materialManager)
break; return nullptr;
case Nif::NiTexturingProperty::BumpTexture:
texture2d->setName("bumpMap"); if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm"))
break; return nullptr;
case Nif::NiTexturingProperty::GlowTexture:
texture2d->setName("emissiveMap"); std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, materialManager->getVFS());
break; try
case Nif::NiTexturingProperty::DarkTexture: {
texture2d->setName("darkMap"); return materialManager->get(VFS::Path::Normalized(normalizedPath));
break; }
case Nif::NiTexturingProperty::DetailTexture: catch (std::exception& e)
texture2d->setName("detailMap"); {
break; Log(Debug::Error) << "Failed to load shader material: " << e.what();
case Nif::NiTexturingProperty::DecalTexture: return nullptr;
texture2d->setName("decalMap"); }
break; }
case Nif::NiTexturingProperty::GlossTexture:
texture2d->setName("glossMap"); void handleShaderMaterialNodeProperties(
break; Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
default: {
break; const unsigned int uvSet = 0;
} const bool wrapS = (material->mClamp >> 1) & 0x1;
const bool wrapT = material->mClamp & 0x1;
if (material->mShaderType == Bgsm::ShaderType::Lighting)
{
const Bgsm::BGSMFile* bgsm = static_cast<const Bgsm::BGSMFile*>(material.get());
if (!bgsm->mDiffuseMap.empty())
attachExternalTexture(
"diffuseMap", bgsm->mDiffuseMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (!bgsm->mNormalMap.empty())
attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty())
attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (bgsm->mTree)
stateset->addUniform(new osg::Uniform("useTreeAnim", true));
}
else if (material->mShaderType == Bgsm::ShaderType::Effect)
{
const Bgsm::BGEMFile* bgem = static_cast<const Bgsm::BGEMFile*>(material.get());
if (!bgem->mBaseMap.empty())
attachExternalTexture("diffuseMap", bgem->mBaseMap, wrapS, wrapT, uvSet, stateset, boundTextures);
bool useFalloff = bgem->mFalloff;
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
if (useFalloff)
stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams));
}
if (material->mTwoSided)
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite);
}
void handleDecal(bool enabled, bool hasSortAlpha, osg::Node& node)
{
if (!enabled)
return;
osg::ref_ptr<osg::StateSet> stateset = node.getOrCreateStateSet();
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
polygonOffset = shareAttribute(polygonOffset);
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
if (!mPushedSorter && !hasSortAlpha)
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
}
void handleAlphaTesting(
bool enabled, osg::AlphaFunc::ComparisonFunction function, int threshold, osg::Node& node)
{
if (enabled)
{
osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(function, threshold / 255.f));
alphaFunc = shareAttribute(alphaFunc);
node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
}
else if (osg::StateSet* stateset = node.getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
stateset->removeMode(GL_ALPHA_TEST);
}
}
void handleAlphaBlending(
bool enabled, int sourceMode, int destMode, bool sort, bool& hasSortAlpha, osg::Node& node)
{
if (enabled)
{
osg::ref_ptr<osg::StateSet> stateset = node.getOrCreateStateSet();
osg::ref_ptr<osg::BlendFunc> blendFunc(
new osg::BlendFunc(getBlendMode(sourceMode), getBlendMode(destMode)));
// on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL.
// This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug.
// Either way, D3D8.1 doesn't do that, so adapt the destination factor.
if (blendFunc->getDestination() == GL_DST_ALPHA)
blendFunc->setDestination(GL_ONE);
blendFunc = shareAttribute(blendFunc);
stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
boundTextures.push_back(uvSet); if (sort)
{
hasSortAlpha = true;
if (!mPushedSorter)
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
else if (!mPushedSorter)
{
stateset->setRenderBinToInherit();
} }
} }
handleTextureControllers(texprop, composite, imageManager, stateset, animflags); else if (osg::ref_ptr<osg::StateSet> stateset = node.getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
stateset->removeMode(GL_BLEND);
if (!mPushedSorter)
stateset->setRenderBinToInherit();
}
} }
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, void handleShaderMaterialDrawableProperties(
const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr<osg::Material> mat, osg::Node& node, bool& hasSortAlpha)
std::vector<unsigned int>& boundTextures)
{ {
if (!boundTextures.empty()) mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency);
handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node);
handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode,
true, hasSortAlpha, node);
handleDecal(shaderMat->mDecal, hasSortAlpha, node);
if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting)
{ {
for (unsigned int i = 0; i < boundTextures.size(); ++i) auto bgsm = static_cast<const Bgsm::BGSMFile*>(shaderMat.get());
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f));
boundTextures.clear(); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f));
}
else if (shaderMat->mShaderType == Bgsm::ShaderType::Effect)
{
auto bgem = static_cast<const Bgsm::BGEMFile*>(shaderMat.get());
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f));
if (bgem->mSoft)
SceneUtil::setupSoftEffect(node, bgem->mSoftDepth, true, bgem->mSoftDepth);
} }
}
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, bool wrapS, bool wrapT,
const std::string& nodeName, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
{
const unsigned int uvSet = 0; const unsigned int uvSet = 0;
for (size_t i = 0; i < textureSet->mTextures.size(); ++i) for (size_t i = 0; i < textureSet->mTextures.size(); ++i)
@ -2175,8 +2314,16 @@ namespace NifOsg
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i)) switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
{ {
case Nif::BSShaderTextureSet::TextureType::Base: case Nif::BSShaderTextureSet::TextureType::Base:
attachExternalTexture(
"diffuseMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
break;
case Nif::BSShaderTextureSet::TextureType::Normal: case Nif::BSShaderTextureSet::TextureType::Normal:
attachExternalTexture(
"normalMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
break;
case Nif::BSShaderTextureSet::TextureType::Glow: case Nif::BSShaderTextureSet::TextureType::Glow:
attachExternalTexture(
"emissiveMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
break; break;
default: default:
{ {
@ -2185,31 +2332,6 @@ namespace NifOsg
continue; continue;
} }
} }
std::string filename
= Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
handleTextureWrapping(texture2d, (clamp >> 1) & 0x1, clamp & 0x1);
unsigned int texUnit = boundTextures.size();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
// BSShaderTextureSet presence means there's no need for FFP support for the affected node
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
{
case Nif::BSShaderTextureSet::TextureType::Base:
texture2d->setName("diffuseMap");
break;
case Nif::BSShaderTextureSet::TextureType::Normal:
texture2d->setName("normalMap");
break;
case Nif::BSShaderTextureSet::TextureType::Glow:
texture2d->setName("emissiveMap");
break;
default:
break;
}
boundTextures.emplace_back(uvSet);
} }
} }
@ -2269,8 +2391,8 @@ namespace NifOsg
} }
void handleProperty(const Nif::NiProperty* property, osg::Node* node, void handleProperty(const Nif::NiProperty* property, osg::Node* node,
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, SceneUtil::CompositeStateSetUpdater* composite, std::vector<unsigned int>& boundTextures, int animflags,
std::vector<unsigned int>& boundTextures, int animflags, bool hasStencilProperty) bool hasStencilProperty)
{ {
switch (property->recType) switch (property->recType)
{ {
@ -2352,8 +2474,7 @@ namespace NifOsg
{ {
const Nif::NiTexturingProperty* texprop = static_cast<const Nif::NiTexturingProperty*>(property); const Nif::NiTexturingProperty* texprop = static_cast<const Nif::NiTexturingProperty*>(property);
osg::StateSet* stateset = node->getOrCreateStateSet(); osg::StateSet* stateset = node->getOrCreateStateSet();
handleTextureProperty( handleTextureProperty(texprop, node->getName(), stateset, composite, boundTextures, animflags);
texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags);
node->setUserValue("applyMode", static_cast<int>(texprop->mApplyMode)); node->setUserValue("applyMode", static_cast<int>(texprop->mApplyMode));
break; break;
} }
@ -2364,13 +2485,13 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType)));
node->setUserValue("shaderRequired", shaderRequired); node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet(); osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
const bool wrapT = texprop->mClamp & 0x1;
if (!texprop->mTextureSet.empty()) if (!texprop->mTextureSet.empty())
{
auto textureSet = texprop->mTextureSet.getPtr();
handleTextureSet( handleTextureSet(
textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures);
} handleTextureControllers(texprop, composite, stateset, animflags);
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
if (texprop->refraction()) if (texprop->refraction())
SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength);
break; break;
@ -2383,34 +2504,20 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType)));
node->setUserValue("shaderRequired", shaderRequired); node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet(); osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
if (!texprop->mFilename.empty()) if (!texprop->mFilename.empty())
{ {
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
std::string filename
= Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
texture2d->setName("diffuseMap");
if (image)
texture2d->setTextureSize(image->s(), image->t());
handleTextureWrapping(texture2d, texprop->wrapS(), texprop->wrapT());
const unsigned int texUnit = 0;
const unsigned int uvSet = 0; const unsigned int uvSet = 0;
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); attachExternalTexture("diffuseMap", texprop->mFilename, texprop->wrapS(), texprop->wrapT(),
boundTextures.push_back(uvSet); uvSet, stateset, boundTextures);
if (mBethVersion >= 27) }
{ if (mBethVersion >= 27)
useFalloff = true; {
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); useFalloff = true;
} stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
} }
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
handleTextureControllers(texprop, composite, imageManager, stateset, animflags); handleTextureControllers(texprop, composite, stateset, animflags);
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
break; break;
} }
@ -2421,10 +2528,18 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType)));
node->setUserValue("shaderRequired", shaderRequired); node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet(); osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
{
handleShaderMaterialNodeProperties(material, stateset, boundTextures);
break;
}
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
const bool wrapT = texprop->mClamp & 0x1;
if (!texprop->mTextureSet.empty()) if (!texprop->mTextureSet.empty())
handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, handleTextureSet(
imageManager, boundTextures); texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures);
handleTextureControllers(texprop, composite, imageManager, 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())
@ -2442,27 +2557,20 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderPrefix", std::string("bs/nolighting"));
node->setUserValue("shaderRequired", shaderRequired); node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet(); osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
{
handleShaderMaterialNodeProperties(material, stateset, boundTextures);
break;
}
if (!texprop->mSourceTexture.empty()) if (!texprop->mSourceTexture.empty())
{ {
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
std::string filename = Misc::ResourceHelpers::correctTexturePath(
texprop->mSourceTexture, imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
texture2d->setName("diffuseMap");
if (image)
texture2d->setTextureSize(image->s(), image->t());
handleTextureWrapping(texture2d, (texprop->mClamp >> 1) & 0x1, texprop->mClamp & 0x1);
const unsigned int texUnit = 0;
const unsigned int uvSet = 0; const unsigned int uvSet = 0;
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); const bool wrapS = (texprop->mClamp >> 1) & 0x1;
boundTextures.push_back(uvSet); const bool wrapT = texprop->mClamp & 0x1;
unsigned int texUnit = boundTextures.size();
attachExternalTexture(
"diffuseMap", texprop->mSourceTexture, wrapS, wrapT, uvSet, stateset, boundTextures);
{ {
osg::ref_ptr<osg::TexMat> texMat(new osg::TexMat); osg::ref_ptr<osg::TexMat> texMat(new osg::TexMat);
// This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController) // This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController)
@ -2484,7 +2592,7 @@ namespace NifOsg
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
if (useFalloff) if (useFalloff)
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
handleTextureControllers(texprop, composite, imageManager, 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);
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
@ -2566,12 +2674,9 @@ namespace NifOsg
bool hasMatCtrl = false; bool hasMatCtrl = false;
bool hasSortAlpha = false; bool hasSortAlpha = false;
osg::StateSet* blendFuncStateSet = nullptr;
auto setBin_Transparent = [](osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); };
auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); }; auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); };
auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); }; auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); };
auto setBin_Inherit = [](osg::StateSet* ss) { ss->setRenderBinToInherit(); };
auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif; auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif;
float emissiveMult = 1.f; float emissiveMult = 1.f;
@ -2657,52 +2762,10 @@ namespace NifOsg
case Nif::RC_NiAlphaProperty: case Nif::RC_NiAlphaProperty:
{ {
const Nif::NiAlphaProperty* alphaprop = static_cast<const Nif::NiAlphaProperty*>(property); const Nif::NiAlphaProperty* alphaprop = static_cast<const Nif::NiAlphaProperty*>(property);
if (alphaprop->useAlphaBlending()) handleAlphaBlending(alphaprop->useAlphaBlending(), alphaprop->sourceBlendMode(),
{ alphaprop->destinationBlendMode(), !alphaprop->noSorter(), hasSortAlpha, *node);
osg::ref_ptr<osg::BlendFunc> blendFunc( handleAlphaTesting(alphaprop->useAlphaTesting(), getTestMode(alphaprop->alphaTestMode()),
new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()), alphaprop->mThreshold, *node);
getBlendMode(alphaprop->destinationBlendMode())));
// on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL.
// This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug.
// Either way, D3D8.1 doesn't do that, so adapt the destination factor.
if (blendFunc->getDestination() == GL_DST_ALPHA)
blendFunc->setDestination(GL_ONE);
blendFunc = shareAttribute(blendFunc);
node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
if (!alphaprop->noSorter())
{
hasSortAlpha = true;
if (!mPushedSorter)
setBin_Transparent(node->getStateSet());
}
else
{
if (!mPushedSorter)
setBin_Inherit(node->getStateSet());
}
}
else if (osg::StateSet* stateset = node->getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
stateset->removeMode(GL_BLEND);
blendFuncStateSet = stateset;
if (!mPushedSorter)
blendFuncStateSet->setRenderBinToInherit();
}
if (alphaprop->useAlphaTesting())
{
osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(
getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f));
alphaFunc = shareAttribute(alphaFunc);
node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
}
else if (osg::StateSet* stateset = node->getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
stateset->removeMode(GL_ALPHA_TEST);
}
break; break;
} }
case Nif::RC_BSShaderPPLightingProperty: case Nif::RC_BSShaderPPLightingProperty:
@ -2714,6 +2777,18 @@ namespace NifOsg
case Nif::RC_BSLightingShaderProperty: case Nif::RC_BSLightingShaderProperty:
{ {
auto shaderprop = static_cast<const Nif::BSLightingShaderProperty*>(property); auto shaderprop = static_cast<const Nif::BSLightingShaderProperty*>(property);
if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager))
{
handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha);
if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting)
{
auto bgsm = static_cast<const Bgsm::BGSMFile*>(shaderMat.get());
specEnabled = false; // bgsm->mSpecularEnabled; TODO: PBR specular lighting
specStrength = 1.f; // bgsm->mSpecularMult;
emissiveMult = bgsm->mEmittanceMult;
}
break;
}
mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha);
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f));
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f));
@ -2722,31 +2797,18 @@ namespace NifOsg
emissiveMult = shaderprop->mEmissiveMult; emissiveMult = shaderprop->mEmissiveMult;
specStrength = shaderprop->mSpecStrength; specStrength = shaderprop->mSpecStrength;
specEnabled = shaderprop->specular(); specEnabled = shaderprop->specular();
if (shaderprop->decal()) handleDecal(shaderprop->decal(), hasSortAlpha, *node);
{
osg::StateSet* stateset = node->getOrCreateStateSet();
if (!mPushedSorter && !hasSortAlpha)
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
}
break; break;
} }
case Nif::RC_BSEffectShaderProperty: case Nif::RC_BSEffectShaderProperty:
{ {
auto shaderprop = static_cast<const Nif::BSEffectShaderProperty*>(property); auto shaderprop = static_cast<const Nif::BSEffectShaderProperty*>(property);
if (shaderprop->decal()) if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager))
{ {
osg::StateSet* stateset = node->getOrCreateStateSet(); handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha);
if (!mPushedSorter && !hasSortAlpha) break;
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
} }
handleDecal(shaderprop->decal(), hasSortAlpha, *node);
if (shaderprop->softEffect()) if (shaderprop->softEffect())
SceneUtil::setupSoftEffect( SceneUtil::setupSoftEffect(
*node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth);
@ -2860,10 +2922,13 @@ namespace NifOsg
} }
}; };
osg::ref_ptr<osg::Node> Loader::load(Nif::FileView file, Resource::ImageManager* imageManager) osg::ref_ptr<osg::Node> Loader::load(
Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager)
{ {
LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion()); LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion());
return impl.load(file, imageManager); impl.mMaterialManager = materialManager;
impl.mImageManager = imageManager;
return impl.load(file);
} }
void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target) void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target)

@ -18,6 +18,7 @@ namespace osg
namespace Resource namespace Resource
{ {
class ImageManager; class ImageManager;
class BgsmFileManager;
} }
namespace NifOsg namespace NifOsg
@ -30,7 +31,8 @@ namespace NifOsg
public: public:
/// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton
/// if so. /// if so.
static osg::ref_ptr<osg::Node> load(Nif::FileView file, Resource::ImageManager* imageManager); static osg::ref_ptr<osg::Node> load(
Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager);
/// Load keyframe controllers from the given kf file. /// Load keyframe controllers from the given kf file.
static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target); static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target);

@ -0,0 +1,58 @@
#include "bgsmfilemanager.hpp"
#include <osg/Object>
#include <components/bgsm/reader.hpp>
#include <components/vfs/manager.hpp>
#include "objectcache.hpp"
namespace Resource
{
class BgsmFileHolder : public osg::Object
{
public:
BgsmFileHolder(const Bgsm::MaterialFilePtr& file)
: mBgsmFile(file)
{
}
BgsmFileHolder(const BgsmFileHolder& copy, const osg::CopyOp& copyop)
: mBgsmFile(copy.mBgsmFile)
{
}
BgsmFileHolder() = default;
META_Object(Resource, BgsmFileHolder)
Bgsm::MaterialFilePtr mBgsmFile;
};
BgsmFileManager::BgsmFileManager(const VFS::Manager* vfs, double expiryDelay)
: ResourceManager(vfs, expiryDelay)
{
}
Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name)
{
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name);
if (obj)
return static_cast<BgsmFileHolder*>(obj.get())->mBgsmFile;
else
{
Bgsm::Reader reader;
reader.parse(mVFS->get(name));
Bgsm::MaterialFilePtr file = reader.getFile();
obj = new BgsmFileHolder(file);
mCache->addEntryToObjectCache(name.value(), obj);
return file;
}
}
void BgsmFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
{
Resource::reportStats("BSShader Material", frameNumber, mCache->getStats(), *stats);
}
}

@ -0,0 +1,27 @@
#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H
#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H
#include <components/bgsm/file.hpp>
#include "resourcemanager.hpp"
namespace Resource
{
/// @brief Handles caching of material files.
/// @note May be used from any thread.
class BgsmFileManager : public ResourceManager
{
public:
BgsmFileManager(const VFS::Manager* vfs, double expiryDelay);
~BgsmFileManager() = default;
/// Retrieve a material file from the cache or load it from the VFS if not cached yet.
Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name);
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
};
}
#endif

@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include "bgsmfilemanager.hpp"
#include "imagemanager.hpp" #include "imagemanager.hpp"
#include "keyframemanager.hpp" #include "keyframemanager.hpp"
#include "niffilemanager.hpp" #include "niffilemanager.hpp"
@ -15,11 +16,14 @@ namespace Resource
: mVFS(vfs) : mVFS(vfs)
{ {
mNifFileManager = std::make_unique<NifFileManager>(vfs, encoder); mNifFileManager = std::make_unique<NifFileManager>(vfs, encoder);
mBgsmFileManager = std::make_unique<BgsmFileManager>(vfs, expiryDelay);
mImageManager = std::make_unique<ImageManager>(vfs, expiryDelay); mImageManager = std::make_unique<ImageManager>(vfs, expiryDelay);
mSceneManager = std::make_unique<SceneManager>(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); mSceneManager = std::make_unique<SceneManager>(
vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay);
mKeyframeManager = std::make_unique<KeyframeManager>(vfs, mSceneManager.get(), expiryDelay, encoder); mKeyframeManager = std::make_unique<KeyframeManager>(vfs, mSceneManager.get(), expiryDelay, encoder);
addResourceManager(mNifFileManager.get()); addResourceManager(mNifFileManager.get());
addResourceManager(mBgsmFileManager.get());
addResourceManager(mKeyframeManager.get()); addResourceManager(mKeyframeManager.get());
// note, scene references images so add images afterwards for correct implementation of updateCache() // note, scene references images so add images afterwards for correct implementation of updateCache()
addResourceManager(mSceneManager.get()); addResourceManager(mSceneManager.get());
@ -43,6 +47,11 @@ namespace Resource
return mImageManager.get(); return mImageManager.get();
} }
BgsmFileManager* ResourceSystem::getBgsmFileManager()
{
return mBgsmFileManager.get();
}
NifFileManager* ResourceSystem::getNifFileManager() NifFileManager* ResourceSystem::getNifFileManager()
{ {
return mNifFileManager.get(); return mNifFileManager.get();

@ -25,6 +25,7 @@ namespace Resource
class SceneManager; class SceneManager;
class ImageManager; class ImageManager;
class BgsmFileManager;
class NifFileManager; class NifFileManager;
class KeyframeManager; class KeyframeManager;
class BaseResourceManager; class BaseResourceManager;
@ -41,6 +42,7 @@ namespace Resource
SceneManager* getSceneManager(); SceneManager* getSceneManager();
ImageManager* getImageManager(); ImageManager* getImageManager();
BgsmFileManager* getBgsmFileManager();
NifFileManager* getNifFileManager(); NifFileManager* getNifFileManager();
KeyframeManager* getKeyframeManager(); KeyframeManager* getKeyframeManager();
@ -74,6 +76,7 @@ namespace Resource
private: private:
std::unique_ptr<SceneManager> mSceneManager; std::unique_ptr<SceneManager> mSceneManager;
std::unique_ptr<ImageManager> mImageManager; std::unique_ptr<ImageManager> mImageManager;
std::unique_ptr<BgsmFileManager> mBgsmFileManager;
std::unique_ptr<NifFileManager> mNifFileManager; std::unique_ptr<NifFileManager> mNifFileManager;
std::unique_ptr<KeyframeManager> mKeyframeManager; std::unique_ptr<KeyframeManager> mKeyframeManager;

@ -55,6 +55,7 @@
#include <components/files/hash.hpp> #include <components/files/hash.hpp>
#include <components/files/memorystream.hpp> #include <components/files/memorystream.hpp>
#include "bgsmfilemanager.hpp"
#include "errormarker.hpp" #include "errormarker.hpp"
#include "imagemanager.hpp" #include "imagemanager.hpp"
#include "niffilemanager.hpp" #include "niffilemanager.hpp"
@ -409,7 +410,7 @@ namespace Resource
}; };
SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
Resource::NifFileManager* nifFileManager, double expiryDelay) Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay)
: ResourceManager(vfs, expiryDelay) : ResourceManager(vfs, expiryDelay)
, mShaderManager(new Shader::ShaderManager) , mShaderManager(new Shader::ShaderManager)
, mForceShaders(false) , mForceShaders(false)
@ -424,6 +425,7 @@ namespace Resource
, mSharedStateManager(new SharedStateManager) , mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager) , mImageManager(imageManager)
, mNifFileManager(nifFileManager) , mNifFileManager(nifFileManager)
, mBgsmFileManager(bgsmFileManager)
, mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR)
, mMagFilter(osg::Texture::LINEAR) , mMagFilter(osg::Texture::LINEAR)
, mMaxAnisotropy(1) , mMaxAnisotropy(1)
@ -795,11 +797,12 @@ namespace Resource
} }
osg::ref_ptr<osg::Node> load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, osg::ref_ptr<osg::Node> load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs,
Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager,
Resource::BgsmFileManager* materialMgr)
{ {
const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); const std::string_view ext = Misc::getFileExtension(normalizedFilename.value());
if (ext == "nif") if (ext == "nif")
return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager, materialMgr);
else if (ext == "spt") else if (ext == "spt")
{ {
Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename; Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename;
@ -921,7 +924,7 @@ namespace Resource
{ {
path.changeExtension(meshType); path.changeExtension(meshType);
if (mVFS->exists(path)) if (mVFS->exists(path))
return load(path, mVFS, mImageManager, mNifFileManager); return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
} }
} }
catch (const std::exception& e) catch (const std::exception& e)
@ -953,7 +956,7 @@ namespace Resource
osg::ref_ptr<osg::Node> loaded; osg::ref_ptr<osg::Node> loaded;
try try
{ {
loaded = load(normalized, mVFS, mImageManager, mNifFileManager); loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this);
loaded->accept(extraDataVisitor); loaded->accept(extraDataVisitor);

@ -32,6 +32,7 @@ namespace Resource
{ {
class ImageManager; class ImageManager;
class NifFileManager; class NifFileManager;
class BgsmFileManager;
class SharedStateManager; class SharedStateManager;
} }
@ -90,7 +91,7 @@ namespace Resource
{ {
public: public:
explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
Resource::NifFileManager* nifFileManager, double expiryDelay); Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay);
~SceneManager(); ~SceneManager();
Shader::ShaderManager& getShaderManager(); Shader::ShaderManager& getShaderManager();
@ -259,6 +260,7 @@ namespace Resource
Resource::ImageManager* mImageManager; Resource::ImageManager* mImageManager;
Resource::NifFileManager* mNifFileManager; Resource::NifFileManager* mNifFileManager;
Resource::BgsmFileManager* mBgsmFileManager;
osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMinFilter;
osg::Texture::FilterMode mMagFilter; osg::Texture::FilterMode mMagFilter;

@ -87,6 +87,7 @@ namespace Resource
"Image", "Image",
"Nif", "Nif",
"Keyframe", "Keyframe",
"BSShader Material",
"Groundcover Chunk", "Groundcover Chunk",
"Object Chunk", "Object Chunk",
"Terrain Chunk", "Terrain Chunk",

Loading…
Cancel
Save