WS30: Improved atlas with multi-texture support.

- Prefix all WS30 Uniforms with "fg_"
- fix modelOffset (needs to be passed in as a float)
- Create a true atlas with indexes for relevant material textures
This commit is contained in:
Stuart Buchanan
2021-11-28 19:28:54 +00:00
parent fee4481ef6
commit 8a014df26d
5 changed files with 125 additions and 47 deletions

View File

@@ -546,6 +546,17 @@ std::string SGMaterial::get_one_texture(int setIndex, int texIndex)
return texturePath;
}
std::size_t SGMaterial::get_num_textures(int setIndex)
{
if (_status.empty()) {
SG_LOG( SG_GENERAL, SG_WARN, "No material available.");
return 0;
}
SGMaterial::_internal_state st = _status[setIndex % _status.size()];
return st.texture_paths.size();
}
void SGMaterial::buildEffectProperties(const SGReaderWriterOptions* options)
{
using namespace osg;

View File

@@ -147,11 +147,15 @@ public:
*/
std::string get_one_texture(int setIndex, int texIndex);
/**
* Get the number of textures defined in this set
*/
std::size_t get_num_textures(int setIndex);
/**
* Get the number of textures assigned to this material.
*/
inline int get_num() const { return _status.size(); }
inline int get_num_texture_sets() const { return _status.size(); }
/**

View File

@@ -331,9 +331,10 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co
{
SGMaterialCache::Atlas atlas;
// Non-VPB does not use the Atlas, so save some both and return
// Non-VPB does not use the Atlas, so save some effort and return
if (! SGSceneFeatures::instance()->getVPBActive()) return atlas;
// A simple key to the atlas is just the list of textures.
std::string id;
const_landclass_map_iterator lc_iter = landclasslib.begin();
for (; lc_iter != landclasslib.end(); ++lc_iter) {
@@ -347,7 +348,7 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co
if (atlas_iter != _atlasCache.end()) return atlas_iter->second;
// Cache lookup failure - generate a new atlas, but only if we have a change of reading any textures
// Cache lookup failure - generate a new atlas, but only if we have a chance of reading any textures
if (options == 0) {
return atlas;
}
@@ -356,10 +357,12 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co
if (landclasslib.size() > 128) SG_LOG(SG_TERRAIN, SG_ALERT, "Too many landclass entries for uniform arrays");
atlas.dimensions = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "dimensionsArray", 128);
atlas.ambient = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "ambientArray", 128);
atlas.diffuse = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "diffuseArray", 128);
atlas.specular = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "specularArray", 128);
atlas.textureLookup1 = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_textureLookup1", 128);
atlas.textureLookup2 = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_textureLookup2", 128);
atlas.dimensions = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_dimensionsArray", 128);
atlas.ambient = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_ambientArray", 128);
atlas.diffuse = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_diffuseArray", 128);
atlas.specular = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_specularArray", 128);
atlas.image->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter());
atlas.image->setResizeNonPowerOfTwoHint(false);
@@ -367,53 +370,85 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co
atlas.image->setWrap(osg::Texture::WRAP_S,osg::Texture::REPEAT);
atlas.image->setWrap(osg::Texture::WRAP_T,osg::Texture::REPEAT);
unsigned int index = 0;
unsigned int imageIndex = 0u; // Index into the image
unsigned int materialLookupIndex = 0u; // Index into the material lookup
lc_iter = landclasslib.begin();
for (; lc_iter != landclasslib.end(); ++lc_iter) {
int i = lc_iter->first;
int landclass = lc_iter->first;
SGMaterial* mat = find(lc_iter->second.first, center);
atlas.index[i] = index;
atlas.waterAtlas[i] = lc_iter->second.second;
atlas.index[landclass] = materialLookupIndex;
atlas.waterAtlas[landclass] = lc_iter->second.second;
unsigned int textureList[SGMaterialCache::MAX_TEXTURES];
if (mat != NULL) {
// Just get the first texture in the first texture-set for the moment.
// Should add some variability in texture-set in the future.
const std::string texture = mat->get_one_texture(0,0);
if (! texture.empty()) {
if (texture.empty()) continue;
if (mat->get_num_textures(0) > SGMaterialCache::MAX_TEXTURES) {
SG_LOG(SG_GENERAL, SG_ALERT, "Unable to build texture atlas for landclass "
<< landclass << " aka " << mat->get_names()[0]
<< " too many textures: " << mat->get_num_textures(0)
<< " (maximum " << SGMaterialCache::MAX_TEXTURES << ")");
continue;
}
SGPath texturePath = SGPath("Textures");
//texturePath.append(texture);
std::string fullPath = SGModelLib::findDataFile(texture, options, texturePath);
atlas.dimensions->setElement(materialLookupIndex, osg::Vec4f(mat->get_xsize(), mat->get_ysize(), mat->get_shininess(), 1.0f));
atlas.ambient->setElement(materialLookupIndex, mat->get_ambient());
atlas.diffuse->setElement(materialLookupIndex, mat->get_diffuse());
atlas.specular->setElement(materialLookupIndex, mat->get_specular());
if (fullPath.empty()) {
SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \""
<< texture << "\" in Textures folders when creating texture atlas");
}
else
{
// Copy the texture into the atlas in the appropriate place
osg::ref_ptr<osg::Image> subtexture = osgDB::readRefImageFile(fullPath, options);
// Add any missing textures into the atlas image
for (std::size_t i = 0; i < mat->get_num_textures(0); ++i) {
const std::string texture = mat->get_one_texture(0,i);
if (! texture.empty()) {
if (subtexture && subtexture->valid()) {
SGPath texturePath = SGPath("Textures");
std::string fullPath = SGModelLib::findDataFile(texture, options, texturePath);
if ((subtexture->s() != 2048) || (subtexture->t() != 2048)) {
subtexture->scaleImage(2048,2048,1);
}
atlas.image->setImage(index,subtexture);
atlas.dimensions->setElement(index, osg::Vec4f(mat->get_xsize(), mat->get_ysize(), mat->get_shininess(), 1.0f));
atlas.ambient->setElement(index, mat->get_ambient());
atlas.diffuse->setElement(index, mat->get_diffuse());
atlas.specular->setElement(index, mat->get_specular());
if (fullPath.empty()) {
SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \""
<< texture << "\" in Textures folders when creating texture atlas");
textureList[i] = -2;
continue;
}
if (atlas.textureMap.find(fullPath) == atlas.textureMap.end()) {
// Copy the texture into the atlas in the appropriate place
osg::ref_ptr<osg::Image> subtexture = osgDB::readRefImageFile(fullPath, options);
if (subtexture && subtexture->valid()) {
if ((subtexture->s() != 2048) || (subtexture->t() != 2048)) {
subtexture->scaleImage(2048,2048,1);
}
atlas.image->setImage(imageIndex,subtexture);
atlas.textureMap[fullPath] = imageIndex;
++imageIndex;
}
}
// At this point we know that the texture is present in the
// atlas and referenced in the textureMap, so add it to the materialLookup
textureList[i] = atlas.textureMap[fullPath];
//SG_LOG(SG_TERRAIN, SG_ALERT, "materialLookup Index: " << (materialLookupIndex * SGMaterialCache::MAX_TEXTURES + i) << " " << atlas.textureMap[fullPath] << " " << fullPath);
} else {
textureList[i] = -2;
}
}
// Fill out the rest of the array
for (std::size_t i = mat->get_num_textures(0); i < SGMaterialCache::MAX_TEXTURES; ++i) {
textureList[i] = -2;
}
// We now have a textureList containing the full set of textures. Pack the relevant ones into the Vec4 of the index Uniform.
// This is a bit of a hack to maintain compatibility with the WS2.0 material definitions, as the material definitions use the
// 11-15th textures for the various overlay textures for terrain-default.eff, we do the same for ws30.eff
atlas.textureLookup1->setElement(materialLookupIndex, osg::Vec4f( (float) (textureList[0] / 255.0), (float) (textureList[11] / 255.0), (float) (textureList[12] / 255.0), (float) (textureList[13] / 255.0)));
atlas.textureLookup2->setElement(materialLookupIndex, osg::Vec4f( (float) (textureList[14] / 255.0), (float) (textureList[15] / 255.0), 0.0,0.0));
}
++index;
++materialLookupIndex;
}
// Cache for future lookups

View File

@@ -50,20 +50,38 @@ class SGMaterialCache : public osg::Referenced
{
public:
// An atlas, consisting of a landclass index, an atlas image and set
// of texture1D containing further data
// A texture Atlas with multiple levels of indirection:
// - A given landclass maps to an index in the materialLookup.
// - The materialLookup results in a set of texture indexes that
// represent the textures referenced by the texture-set in the material
// - the texture indexes index into the Atlas itself.
// Mapping of landclass numbers to indexes within the atlas
// materialLookup
typedef std::map<int, int> AtlasIndex;
// Mapping of texture filenames to their index in the Atlas image itself.
typedef std::map<std::string, unsigned int> TextureMap;
// The Texture array itself
typedef osg::ref_ptr<osg::Texture2DArray> AtlasImage;
typedef std::map<int, bool> WaterAtlas;
// Maximum number of textures per texture-set for the Atlas.
static const unsigned int MAX_TEXTURES = 16;
typedef struct {
AtlasIndex index;
AtlasImage image;
osg::ref_ptr<osg::Uniform> textureLookup1;
osg::ref_ptr<osg::Uniform> textureLookup2;
osg::ref_ptr<osg::Uniform> dimensions;
osg::ref_ptr<osg::Uniform> ambient;
osg::ref_ptr<osg::Uniform> diffuse;
osg::ref_ptr<osg::Uniform> specular;
WaterAtlas waterAtlas;
TextureMap textureMap;
} Atlas;
// Constructor

View File

@@ -1257,10 +1257,10 @@ void VPBTechnique::generateGeometry(BufferData& buffer, Locator* masterLocator,
SG_LOG(SG_TERRAIN, SG_DEBUG, "Tile Level " << _terrainTile->getTileID().level << " width " << buffer._width << " height " << buffer._height);
osg::ref_ptr<osg::Uniform> twu = new osg::Uniform("tile_width", buffer._width);
osg::ref_ptr<osg::Uniform> twu = new osg::Uniform("fg_tileWidth", buffer._width);
landStateSet->addUniform(twu);
waterStateSet->addUniform(twu);
osg::ref_ptr<osg::Uniform> thu = new osg::Uniform("tile_height", buffer._height);
osg::ref_ptr<osg::Uniform> thu = new osg::Uniform("fg_tileHeight", buffer._height);
landStateSet->addUniform(thu);
waterStateSet->addUniform(thu);
@@ -1322,7 +1322,7 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator)
osg::Texture2D* texture = SGLoadTexture2D(SGPath(orthotexture), _options, true, true);
osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, texture);
stateset->addUniform(new osg::Uniform("photoScenery", true));
stateset->addUniform(new osg::Uniform("fg_photoScenery", true));
} else {
SG_LOG(SG_TERRAIN, SG_DEBUG, "Unable to find " << orthotexture);
photoScenery = false;
@@ -1354,7 +1354,14 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator)
for (unsigned int t = 0; t < (unsigned int) image->t(); t++) {
osg::Vec4d c = image->getColor(s, t);
int i = int(round(c.x() * 255.0));
c.set(c.x(), (double) (atlasIndex[i] / 255.0), c.z(), c.w() );
// Get texture 0 for this material to save a Uniform lookup in the shader for
// maximal performance on single-texture shaders
osg::Vec4f texture1;
if (! atlas.textureLookup1->getElement(atlasIndex[i], texture1)) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Failed to do texture lookup for landclass: " << atlasIndex[i]);
}
c.set((double) (atlasIndex[i] / 255.0), texture1.r(), texture1.g(), texture1.b());
image->setColor(c, s, t);
}
}
@@ -1377,13 +1384,16 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator)
osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, texture2D, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(1, atlas.image, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("photoScenery", false));
stateset->addUniform(new osg::Uniform("fg_photoScenery", false));
stateset->addUniform(atlas.dimensions);
stateset->addUniform(atlas.ambient);
stateset->addUniform(atlas.diffuse);
stateset->addUniform(atlas.specular);
stateset->addUniform(new osg::Uniform("zUpTransform", osg::Matrixf(osg::Matrix::inverse(makeZUpFrameRelative(loc)))));
stateset->addUniform(new osg::Uniform("modelOffset", buffer._transform->getMatrix().getTrans()));
stateset->addUniform(atlas.textureLookup1);
stateset->addUniform(atlas.textureLookup2);
stateset->addUniform(new osg::Uniform("fg_zUpTransform", osg::Matrixf(osg::Matrix::inverse(makeZUpFrameRelative(loc)))));
stateset->addUniform(new osg::Uniform("fg_modelOffset", (osg::Vec3f) buffer._transform->getMatrix().getTrans()));
//SG_LOG(SG_TERRAIN, SG_ALERT, "modeOffset:" << buffer._transform->getMatrix().getTrans().length() << " " << buffer._transform->getMatrix().getTrans());
}
}