From ef18f4217f1aca7830f39f241d355b353a4602ea Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 6 Nov 2015 20:14:57 +0100 Subject: [PATCH] Terrain: create 4x4 terrain chunks per ESM::Cell to improve performance Improves performance because the number of splatting layers per chunk is reduced, and finer grained frustum culling can be done. --- components/esmterrain/storage.cpp | 80 ++++++++---- components/terrain/buffercache.cpp | 3 + components/terrain/material.cpp | 10 +- components/terrain/material.hpp | 6 +- components/terrain/terraingrid.cpp | 191 ++++++++++++++++++----------- components/terrain/terraingrid.hpp | 7 ++ 6 files changed, 193 insertions(+), 104 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index ccfe6d9ee..e954bfec8 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -1,6 +1,7 @@ #include "storage.hpp" #include +#include #include #include @@ -34,19 +35,22 @@ namespace ESMTerrain osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - assert(origin.x() == (int) origin.x()); - assert(origin.y() == (int) origin.y()); + int cellX = static_cast(std::floor(origin.x())); + int cellY = static_cast(std::floor(origin.y())); - int cellX = static_cast(origin.x()); - int cellY = static_cast(origin.y()); + int startRow = (origin.x() - cellX) * ESM::Land::LAND_SIZE; + int startColumn = (origin.y() - cellY) * ESM::Land::LAND_SIZE; + + int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; + int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT)) { min = std::numeric_limits::max(); max = -std::numeric_limits::max(); - for (int row=0; rowmHeights[col*ESM::Land::LAND_SIZE+row]; if (h > max) @@ -143,11 +147,9 @@ namespace ESMTerrain size_t increment = 1 << lodLevel; osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - assert(origin.x() == (int) origin.x()); - assert(origin.y() == (int) origin.y()); - int startX = static_cast(origin.x()); - int startY = static_cast(origin.y()); + int startCellX = static_cast(std::floor(origin.x())); + int startCellY = static_cast(std::floor(origin.y())); size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); @@ -162,10 +164,10 @@ namespace ESMTerrain float vertX = 0; float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); @@ -175,18 +177,31 @@ namespace ESMTerrain int colStart = 0; // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell + // This is only relevant if we're creating a chunk spanning multiple cells if (colStart == 0 && vertY_ != 0) colStart += increment; if (rowStart == 0 && vertX_ != 0) rowStart += increment; + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + vertY = vertY_; - for (int col=colStart; col= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); float height = -2048; if (heightData) @@ -200,7 +215,7 @@ namespace ESMTerrain if (normalData) { for (int i=0; i<3; ++i) - normal[i] = normalData->mNormals[arrayIndex+i]; + normal[i] = normalData->mNormals[srcArrayIndex+i]; normal.normalize(); } @@ -222,7 +237,7 @@ namespace ESMTerrain if (colourData) { for (int i=0; i<3; ++i) - color[i] = colourData->mColours[arrayIndex+i] / 255.f; + color[i] = colourData->mColours[srcArrayIndex+i] / 255.f; } else { @@ -305,8 +320,19 @@ namespace ESMTerrain // and interpolate the rest of the cell by hand? :/ osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); - int cellX = static_cast(origin.x()); - int cellY = static_cast(origin.y()); + int cellX = static_cast(std::floor(origin.x())); + int cellY = static_cast(std::floor(origin.y())); + + int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE+1; // add 1 to wrap around next cell + + int rowStart = (origin.x() - cellX) * realTextureSize; + int colStart = (origin.y() - cellY) * realTextureSize; + int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; + int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; + + assert (rowStart >= 0 && colStart >= 0); + assert (rowEnd <= realTextureSize); + assert (colEnd <= realTextureSize); // Save the used texture indices so we know the total number of textures // and number of required blend maps @@ -317,8 +343,15 @@ namespace ESMTerrain // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. textureIndices.insert(std::make_pair(0,0)); - for (int y=0; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index a64f8ffd1..c6cf0fe33 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -1,6 +1,7 @@ #include "buffercache.hpp" #include +#include #include @@ -208,6 +209,8 @@ namespace Terrain { unsigned int verts = mNumVerts; + std::cout << "getting index buffer for " << verts << std::endl; + if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) { return mIndexBufferMap[flags]; diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index d467a9e16..59d06f254 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -11,7 +11,7 @@ namespace Terrain { FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, - const std::vector >& blendmaps, int blendmapSize, float layerTileSize) + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { bool firstLayer = true; int i=0; @@ -36,7 +36,7 @@ namespace Terrain // This is to map corner vertices directly to the center of a blendmap texel. osg::Matrixf texMat; - float scale = (blendmapSize/(static_cast(blendmapSize)+1.f)); + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); @@ -67,10 +67,10 @@ namespace Terrain } Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, - int blendmapSize, float layerTileSize) + int blendmapScale, float layerTileSize) : mLayers(layers) , mBlendmaps(blendmaps) - , mBlendmapSize(blendmapSize) + , mBlendmapScale(blendmapScale) , mLayerTileSize(layerTileSize) { osg::ref_ptr material (new osg::Material); @@ -82,7 +82,7 @@ namespace Terrain bool Effect::define_techniques() { - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapSize, mLayerTileSize)); + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); return true; } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 1be227b0d..dd00e41ed 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -19,7 +19,7 @@ namespace Terrain public: FixedFunctionTechnique( const std::vector >& layers, - const std::vector >& blendmaps, int blendmapSize, float layerTileSize); + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); protected: virtual void define_passes() {} @@ -30,7 +30,7 @@ namespace Terrain public: Effect( const std::vector >& layers, - const std::vector >& blendmaps, int blendmapSize, float layerTileSize); + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); virtual bool define_techniques(); @@ -50,7 +50,7 @@ namespace Terrain private: std::vector > mLayers; std::vector > mBlendmaps; - int mBlendmapSize; + int mBlendmapScale; float mLayerTileSize; }; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 7c2395566..6414fa951 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -1,6 +1,8 @@ #include "terraingrid.hpp" #include +#include +#include #include #include @@ -47,8 +49,10 @@ namespace Terrain TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) + , mNumSplits(4) , mKdTreeBuilder(new osg::KdTreeBuilder) { + mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } TerrainGrid::~TerrainGrid() @@ -62,108 +66,149 @@ TerrainGrid::~TerrainGrid() class GridElement { public: - osg::ref_ptr mNode; + osg::ref_ptr mNode; }; -void TerrainGrid::loadCell(int x, int y) +osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) { - if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) - return; // already loaded + if (chunkSize * mNumSplits > 1.f) + { + // keep splitting + osg::ref_ptr group (new osg::Group); + if (parent) + parent->addChild(group); + std::cout << "splitting " << chunkSize << " " << std::endl; + float newChunkSize = chunkSize/2.f; + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, -newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, -newChunkSize/2.f)); + return group; + } + else + { + float minH, maxH; + if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH)) + return NULL; // no terrain defined - osg::Vec2f center(x+0.5f, y+0.5f); - float minH, maxH; - if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) - return; // no terrain defined + std::cout << "creating " << chunkSize << " " << chunkCenter << std::endl; - std::auto_ptr element (new GridElement); + osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); + osg::ref_ptr transform (new osg::PositionAttitudeTransform); + transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); - osg::Vec2f worldCenter = center*mStorage->getCellWorldSize(); - element->mNode = new osg::PositionAttitudeTransform; - element->mNode->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); - mTerrainRoot->addChild(element->mNode); + if (parent) + parent->addChild(transform); - osg::ref_ptr positions (new osg::Vec3Array); - osg::ref_ptr normals (new osg::Vec3Array); - osg::ref_ptr colors (new osg::Vec4Array); + osg::ref_ptr positions (new osg::Vec3Array); + osg::ref_ptr normals (new osg::Vec3Array); + osg::ref_ptr colors (new osg::Vec4Array); - osg::ref_ptr vbo (new osg::VertexBufferObject); - positions->setVertexBufferObject(vbo); - normals->setVertexBufferObject(vbo); - colors->setVertexBufferObject(vbo); + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); - mStorage->fillVertexBuffers(0, 1, center, positions, normals, colors); + mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors); - osg::ref_ptr geometry (new osg::Geometry); - geometry->setVertexArray(positions); - geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); - geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); - geometry->setUseDisplayList(false); - geometry->setUseVertexBufferObjects(true); + osg::ref_ptr geometry (new osg::Geometry); + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + geometry->setUseDisplayList(false); + geometry->setUseVertexBufferObjects(true); - geometry->addPrimitiveSet(mCache.getIndexBuffer(0)); + geometry->addPrimitiveSet(mCache.getIndexBuffer(0)); - // we already know the bounding box, so no need to let OSG compute it. - osg::Vec3f min(-0.5f*mStorage->getCellWorldSize(), - -0.5f*mStorage->getCellWorldSize(), - minH); - osg::Vec3f max (0.5f*mStorage->getCellWorldSize(), - 0.5f*mStorage->getCellWorldSize(), - maxH); - osg::BoundingBox bounds(min, max); - geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds)); + // we already know the bounding box, so no need to let OSG compute it. + osg::Vec3f min(-0.5f*mStorage->getCellWorldSize()*chunkSize, + -0.5f*mStorage->getCellWorldSize()*chunkSize, + minH); + osg::Vec3f max (0.5f*mStorage->getCellWorldSize()*chunkSize, + 0.5f*mStorage->getCellWorldSize()*chunkSize, + maxH); + osg::BoundingBox bounds(min, max); + geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds)); - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(geometry); - // build a kdtree to speed up intersection tests with the terrain - // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree - geode->accept(*mKdTreeBuilder); - std::vector layerList; - std::vector > blendmaps; - mStorage->getBlendmaps(1.f, center, false, blendmaps, layerList); + std::vector layerList; + std::vector > blendmaps; + mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList); - // For compiling textures, I don't think the osgFX::Effect does it correctly - osg::ref_ptr textureCompileDummy (new osg::Node); + // For compiling textures, I don't think the osgFX::Effect does it correctly + osg::ref_ptr textureCompileDummy (new osg::Node); - std::vector > layerTextures; - for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) - { - layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); - } + std::vector > layerTextures; + for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) + { + layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + } - std::vector > blendmapTextures; - for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) - { - osg::ref_ptr texture (new osg::Texture2D); - texture->setImage(*it); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setResizeNonPowerOfTwoHint(false); - blendmapTextures.push_back(texture); - - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + std::vector > blendmapTextures; + for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + osg::ref_ptr texture (new osg::Texture2D); + texture->setImage(*it); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setResizeNonPowerOfTwoHint(false); + blendmapTextures.push_back(texture); + + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + } + + // use texture coordinates for both texture units, the layer texture and blend texture + for (unsigned int i=0; i<2; ++i) + geometry->setTexCoordArray(i, mCache.getUVBuffer()); + + float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; + osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); + + effect->addCullCallback(new SceneUtil::LightListCallback); + + transform->addChild(effect); + effect->addChild(geode); + + return transform; } +} + +void TerrainGrid::loadCell(int x, int y) +{ + if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) + return; // already loaded - // use texture coordinates for both texture units, the layer texture and blend texture - for (unsigned int i=0; i<2; ++i) - geometry->setTexCoordArray(i, mCache.getUVBuffer()); + osg::Vec2f center(x+0.5f, y+0.5f); - osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, ESM::Land::LAND_TEXTURE_SIZE, ESM::Land::LAND_TEXTURE_SIZE)); + osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + if (!terrainNode) + return; // no terrain defined + + std::auto_ptr element (new GridElement); + element->mNode = terrainNode; + mTerrainRoot->addChild(element->mNode); - effect->addCullCallback(new SceneUtil::LightListCallback); + /* + // build a kdtree to speed up intersection tests with the terrain + // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree + geode->accept(*mKdTreeBuilder); + */ - effect->addChild(geode); - element->mNode->addChild(effect); + /* if (mIncrementalCompileOperation) { mIncrementalCompileOperation->add(geode); mIncrementalCompileOperation->add(textureCompileDummy); } + */ + mGrid[std::make_pair(x,y)] = element.release(); } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 832b952e8..169a9a622 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,6 +1,8 @@ #ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H +#include + #include "world.hpp" #include "material.hpp" @@ -26,6 +28,11 @@ namespace Terrain virtual void unloadCell(int x, int y); private: + osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); + + // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks + unsigned int mNumSplits; + typedef std::map, GridElement*> Grid; Grid mGrid;