diff --git a/ogre2/include/ignition/rendering/ogre2/Ogre2IgnOgreRenderingMode.hh b/ogre2/include/ignition/rendering/ogre2/Ogre2IgnOgreRenderingMode.hh index bf691d7c3..84e0ff890 100644 --- a/ogre2/include/ignition/rendering/ogre2/Ogre2IgnOgreRenderingMode.hh +++ b/ogre2/include/ignition/rendering/ogre2/Ogre2IgnOgreRenderingMode.hh @@ -38,6 +38,15 @@ namespace ignition /// Used by e.g. Segmentation camera mode IORM_SOLID_COLOR, + /// \brief Like IORM_SOLID_COLOR, but if CustomParameter 2u + /// is present, raw diffuse texture will be multiplied against + /// the solid colour. + /// + /// Also Unlit will behave as if IORM_NORMAL + /// + /// Used by thermal camera + IORM_SOLID_THERMAL_COLOR_TEXTURED, + /// \brief Total number of rendering modes IORM_COUNT, }; diff --git a/ogre2/include/ignition/rendering/ogre2/Ogre2MaterialSwitcher.hh b/ogre2/include/ignition/rendering/ogre2/Ogre2MaterialSwitcher.hh index 8f861b87f..9e4472e0f 100644 --- a/ogre2/include/ignition/rendering/ogre2/Ogre2MaterialSwitcher.hh +++ b/ogre2/include/ignition/rendering/ogre2/Ogre2MaterialSwitcher.hh @@ -20,6 +20,9 @@ #include #include +#include +#include +#include #include #include "ignition/rendering/config.hh" @@ -80,17 +83,15 @@ namespace ignition /// renderable name private: std::map colorDict; - /// \brief A map of ogre sub item pointer to their original hlms material - private: std::map datablockMap; + /// \brief A map of ogre datablock pointer to their original blendblocks + private: std::unordered_map datablockMap; - /// \brief Ogre v1 material consisting of a shader that changes the - /// appearance of item to use a unique color for mouse picking - private: Ogre::MaterialPtr plainMaterial; - - /// \brief Ogre v1 material consisting of a shader that changes the - /// appearance of item to use a unique color for mouse picking. In - /// addition, the depth check and depth write properties disabled. - private: Ogre::MaterialPtr plainOverlayMaterial; + /// \brief A map of ogre sub item pointer to their original low level + /// material. + /// Most objects don't use one so it should be almost always empty. + private: + std::vector> materialMap; /// \brief Increment unique color value that will be assigned to the /// next renderable diff --git a/ogre2/src/Ogre2GpuRays.cc b/ogre2/src/Ogre2GpuRays.cc index 2b3ca74f7..71ba67b90 100644 --- a/ogre2/src/Ogre2GpuRays.cc +++ b/ogre2/src/Ogre2GpuRays.cc @@ -26,6 +26,7 @@ #include "ignition/rendering/ogre2/Ogre2RenderEngine.hh" #include "ignition/rendering/RenderTypes.hh" #include "ignition/rendering/ogre2/Ogre2Conversions.hh" +#include "ignition/rendering/ogre2/Ogre2Heightmap.hh" #include "ignition/rendering/ogre2/Ogre2ParticleEmitter.hh" #include "ignition/rendering/ogre2/Ogre2RenderTarget.hh" #include "ignition/rendering/ogre2/Ogre2RenderTypes.hh" @@ -36,6 +37,8 @@ #include "Ogre2IgnHlmsSphericalClipMinDistance.hh" #include "Ogre2ParticleNoiseListener.hh" +#include "Terra/Terra.h" + #ifdef _MSC_VER #pragma warning(push, 0) #endif @@ -60,38 +63,38 @@ inline namespace IGNITION_RENDERING_VERSION_NAMESPACE { // /// \brief Helper class for switching the ogre item's material to laser retro /// source material when a thermal camera is being rendered. -class Ogre2LaserRetroMaterialSwitcher : public Ogre::Camera::Listener +class IGNITION_RENDERING_OGRE2_HIDDEN + Ogre2LaserRetroMaterialSwitcher : public Ogre::CompositorWorkspaceListener { /// \brief constructor /// \param[in] _scene the scene manager responsible for rendering public: explicit Ogre2LaserRetroMaterialSwitcher(Ogre2ScenePtr _scene); /// \brief destructor - public: ~Ogre2LaserRetroMaterialSwitcher() = default; + public: virtual ~Ogre2LaserRetroMaterialSwitcher() = default; - /// \brief Callback when a camera is about to be rendered - /// \param[in] _evt Ogre camera which is about to render - private: virtual void cameraPreRenderScene( - Ogre::Camera *_cam) override; + /// \brief Called when each pass is about to be executed. + /// \param[in] _pass Ogre pass which is about to execute + private: virtual void passPreExecute( + Ogre::CompositorPass *_pass) override; - /// \brief Callback when a camera is finisned being rendered - /// \param[in] _evt Ogre camera which has already rendered - private: virtual void cameraPostRenderScene( - Ogre::Camera *_cam) override; + /// \brief Callback when each pass is finisned executing. + /// \param[in] _pass Ogre pass which has already executed + private: virtual void passPosExecute( + Ogre::CompositorPass *_pass) override; /// \brief Scene manager private: Ogre2ScenePtr scene = nullptr; - /// \brief Pointer to the laser retro source material - private: Ogre::MaterialPtr laserRetroSourceMaterial; - - /// \brief Custom parameter index of laser retro value in an ogre subitem. - /// This has to match the custom index specifed in LaserRetroSource material - /// script in media/materials/scripts/gpu_rays.material - private: const unsigned int customParamIdx = 10u; + /// \brief A map of ogre datablock pointer to their original blendblocks + private: std::unordered_map datablockMap; - /// \brief A map of ogre sub item pointer to their original hlms material - private: std::map datablockMap; + /// \brief A map of ogre sub item pointer to their original low level + /// material. + /// Most objects don't use one so it should be almost always empty. + private: + std::vector> materialMap; }; } } @@ -100,7 +103,7 @@ class Ogre2LaserRetroMaterialSwitcher : public Ogre::Camera::Listener /// \internal /// \brief Private data for the Ogre2GpuRays class -class ignition::rendering::Ogre2GpuRaysPrivate +class IGNITION_RENDERING_OGRE2_HIDDEN ignition::rendering::Ogre2GpuRaysPrivate { /// \brief Event triggered when new gpu rays range data are available. /// \param[in] _frame New frame containing raw gpu rays data. @@ -197,39 +200,40 @@ class ignition::rendering::Ogre2GpuRaysPrivate using namespace ignition; using namespace rendering; +// Arbitrary value +static const uint32_t kLaserRetroMainDepthPassId = 9525u; ////////////////////////////////////////////////// Ogre2LaserRetroMaterialSwitcher::Ogre2LaserRetroMaterialSwitcher( Ogre2ScenePtr _scene) { this->scene = _scene; - // plain opaque material - Ogre::ResourcePtr res = - Ogre::MaterialManager::getSingleton().load("LaserRetroSource", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - this->laserRetroSourceMaterial = res.staticCast(); - this->laserRetroSourceMaterial->load(); } ////////////////////////////////////////////////// -void Ogre2LaserRetroMaterialSwitcher::cameraPreRenderScene( - Ogre::Camera * /*_cam*/) +void Ogre2LaserRetroMaterialSwitcher::passPreExecute( + Ogre::CompositorPass *_pass) { - { - auto engine = Ogre2RenderEngine::Instance(); - Ogre2IgnHlmsSphericalClipMinDistance &hlmsCustomizations = - engine->SphericalClipMinDistance(); - Ogre::Pass *pass = - this->laserRetroSourceMaterial->getBestTechnique()->getPass(0u); - pass->getVertexProgramParameters()->setNamedConstant( - "ignMinClipDistance", hlmsCustomizations.minDistanceClip ); - } + if(_pass->getDefinition()->mIdentifier != kLaserRetroMainDepthPassId) + return; - // swap item to use v1 shader material - // Note: keep an eye out for performance impact on switching materials - // on the fly. We are not doing this often so should be ok. + auto engine = Ogre2RenderEngine::Instance(); + engine->SetIgnOgreRenderingMode(IORM_SOLID_COLOR); + + this->materialMap.clear(); this->datablockMap.clear(); + Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + + Ogre::HlmsDatablock *defaultPbs = + hlmsManager->getHlms(Ogre::HLMS_PBS)->getDefaultDatablock(); + + // Construct one now so that datablock->setBlendblock + // each is as fast as possible + const Ogre::HlmsBlendblock *noBlend = + hlmsManager->getBlendblock(Ogre::HlmsBlendblock()); + + const std::string laserRetroKey = "laser_retro"; + auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( Ogre::ItemFactory::FACTORY_TYPE_NAME); while (itor.hasMoreElements()) @@ -237,8 +241,6 @@ void Ogre2LaserRetroMaterialSwitcher::cameraPreRenderScene( Ogre::MovableObject *object = itor.peekNext(); Ogre::Item *item = static_cast(object); - std::string laserRetroKey = "laser_retro"; - float retroValue = 0.0f; // get visual @@ -290,7 +292,8 @@ void Ogre2LaserRetroMaterialSwitcher::cameraPreRenderScene( retroValue = std::max(retroValue, 0.0f); } - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { Ogre::SubItem *subItem = item->getSubItem(i); @@ -300,33 +303,164 @@ void Ogre2LaserRetroMaterialSwitcher::cameraPreRenderScene( retroValue = 2000.0f; } float color = retroValue / 2000.0f; - subItem->setCustomParameter(this->customParamIdx, + subItem->setCustomParameter(1u, Ogre::Vector4(color, color, color, 1.0)); - Ogre::HlmsDatablock *datablock = subItem->getDatablock(); - this->datablockMap[subItem] = datablock; - - subItem->setMaterial(this->laserRetroSourceMaterial); + if (!subItem->getMaterial().isNull()) + { + // TODO(anyone): We need to keep the material's vertex shader + // to keep vertex deformation consistent. See + // https://github.com/ignitionrobotics/ign-rendering/issues/544 + this->materialMap.push_back({ subItem, subItem->getMaterial() }); + subItem->setDatablock(defaultPbs); + } + else + { + // regular Pbs Hlms datablock + Ogre::HlmsDatablock *datablock = subItem->getDatablock(); + const Ogre::HlmsBlendblock *blendblock = datablock->getBlendblock(); + + // We can't do any sort of blending. This isn't colour what we're + // storing, but rather an ID. + if (blendblock->mSourceBlendFactor != Ogre::SBF_ONE || + blendblock->mDestBlendFactor != Ogre::SBF_ZERO || + blendblock->mBlendOperation != Ogre::SBO_ADD || + (blendblock->mSeparateBlend && + (blendblock->mSourceBlendFactorAlpha != Ogre::SBF_ONE || + blendblock->mDestBlendFactorAlpha != Ogre::SBF_ZERO || + blendblock->mBlendOperationAlpha != Ogre::SBO_ADD))) + { + hlmsManager->addReference(blendblock); + this->datablockMap[datablock] = blendblock; + datablock->setBlendblock(noBlend); + } + } } itor.moveNext(); } + + // Do the same with heightmaps / terrain + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + { + float retroValue = 0.0f; + + // get visual + VisualPtr visual = heightmap->Parent(); + + if (visual->HasUserData(laserRetroKey)) + { + // get laser_retro + Variant tempLaserRetro = visual->UserData(laserRetroKey); + + try + { + retroValue = std::get(tempLaserRetro); + } + catch (...) + { + try + { + retroValue = static_cast(std::get(tempLaserRetro)); + } + catch (...) + { + try + { + retroValue = std::get(tempLaserRetro); + } + catch (std::bad_variant_access &e) + { + ignerr << "Error casting user data: " << e.what() << "\n"; + } + } + } + } + + // only accept positive laser retro value + retroValue = std::max(retroValue, 0.0f); + + // limit laser retro value to 2000 (as in gazebo) + if (retroValue > 2000.0f) + { + retroValue = 2000.0f; + } + float color = retroValue / 2000.0f; + + // TODO(anyone): Retrieve datablock and make sure it's not blending + // like we do with Items (it should be impossible?) + const Ogre::Vector4 customParameter = + Ogre::Vector4(color, color, color, 1.0); + heightmap->Terra()->SetSolidColor(1u, customParameter); + } + } + + // Remove the reference count on noBlend we created + hlmsManager->destroyBlendblock(noBlend); } ////////////////////////////////////////////////// -void Ogre2LaserRetroMaterialSwitcher::cameraPostRenderScene( - Ogre::Camera * /*_cam*/) +void Ogre2LaserRetroMaterialSwitcher::passPosExecute( + Ogre::CompositorPass *_pass) { - // restore item to use hlms material - for (auto it : this->datablockMap) + if(_pass->getDefinition()->mIdentifier != kLaserRetroMainDepthPassId) + return; + + auto engine = Ogre2RenderEngine::Instance(); + Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + + // Restore original blending to modified materials + for (const auto &[datablock, blendblock] : this->datablockMap) { - Ogre::SubItem *subItem = it.first; - subItem->setDatablock(it.second); + datablock->setBlendblock(blendblock); + // Remove the reference we added (this won't actually destroy it) + hlmsManager->destroyBlendblock(blendblock); } + this->datablockMap.clear(); - Ogre::Pass *pass = - this->laserRetroSourceMaterial->getBestTechnique()->getPass(0u); - pass->getVertexProgramParameters()->setNamedConstant( - "ignMinClipDistance", 0.0f ); + // Remove the custom parameter. Why? If there are multiple cameras that + // use IORM_SOLID_COLOR (or any other mode), we want them to throw if + // that code forgot to call setCustomParameter. We may miss those errors + // if that code forgets to call but it was already carrying the value + // we set here. + // + // This consumes more performance but it's the price to pay for + // safety. + auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( + Ogre::ItemFactory::FACTORY_TYPE_NAME); + while (itor.hasMoreElements()) + { + Ogre::MovableObject *object = itor.peekNext(); + Ogre::Item *item = static_cast(object); + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) + { + Ogre::SubItem *subItem = item->getSubItem(i); + subItem->removeCustomParameter(1u); + } + itor.moveNext(); + } + + // Restore Items with low level materials + for (auto subItemMat : this->materialMap) + { + subItemMat.first->setMaterial(subItemMat.second); + } + this->materialMap.clear(); + + // Remove the custom parameter (same reason as with Items) + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + heightmap->Terra()->UnsetSolidColors(); + } + + engine->SetIgnOgreRenderingMode(IORM_NORMAL); } @@ -906,6 +1040,8 @@ void Ogre2GpuRays::Setup1stPass() colorTargetDef->addPass(Ogre::PASS_SCENE)); passScene->setAllLoadActions(Ogre::LoadAction::Clear); passScene->setAllClearColours(Ogre::ColourValue(0, 0, 0)); + // Id so we can run custom code in our CompositorWorkspaceListener + passScene->mIdentifier = kLaserRetroMainDepthPassId; // set camera custom visibility mask when rendering laser retro passScene->mVisibilityMask = IGN_VISIBILITY_ALL & ~Ogre2ParticleEmitter::kParticleVisibilityFlags; @@ -1020,6 +1156,13 @@ void Ogre2GpuRays::Setup1stPass() wsDefName, false); + // add laser retro material switcher to workspace listener + // so we can switch to use IORM_SOLID_COLOR + this->dataPtr->laserRetroMaterialSwitcher[i].reset( + new Ogre2LaserRetroMaterialSwitcher(this->scene)); + this->dataPtr->ogreCompositorWorkspace1st[i]->addListener( + this->dataPtr->laserRetroMaterialSwitcher[i].get()); + Ogre::CompositorNode *node = this->dataPtr->ogreCompositorWorkspace1st[i]->getNodeSequence()[0]; auto channelsTex = node->getLocalTextures(); @@ -1028,14 +1171,6 @@ void Ogre2GpuRays::Setup1stPass() { if (c->getPixelFormat() == Ogre::PFG_R16_UNORM) { - // add laser retro material switcher to render target listener - // so we can switch to use laser retro material when the camera is being - // updated - this->dataPtr->laserRetroMaterialSwitcher[i].reset( - new Ogre2LaserRetroMaterialSwitcher(this->scene)); - this->dataPtr->cubeCam[i]->addListener( - this->dataPtr->laserRetroMaterialSwitcher[i].get()); - // add particle noise / scatter effects listener so we can set the // amount of noise based on size of emitter this->dataPtr->particleNoiseListener[i].reset( @@ -1236,7 +1371,7 @@ void Ogre2GpuRays::UpdateRenderTarget2ndPass() ////////////////////////////////////////////////// void Ogre2GpuRays::Render() { - this->scene->StartRendering(nullptr); + this->scene->StartRendering(this->dataPtr->ogreCamera); auto engine = Ogre2RenderEngine::Instance(); @@ -1286,6 +1421,9 @@ void Ogre2GpuRays::PostRender() Ogre::TextureBox box = image.getData(0u); float *bufferTmp = static_cast(box.data); + // TODO(anyone): It seems wasteful to have gpuRaysBuffer at all + // We should be able to convert directly from bufferTmp to gpuRaysScan + // copy data row by row. The texture box may not be a contiguous region of // a texture for (unsigned int i = 0; i < height; ++i) diff --git a/ogre2/src/Ogre2IgnHlmsPbsPrivate.cc b/ogre2/src/Ogre2IgnHlmsPbsPrivate.cc index 63413d9dc..46da25dff 100644 --- a/ogre2/src/Ogre2IgnHlmsPbsPrivate.cc +++ b/ogre2/src/Ogre2IgnHlmsPbsPrivate.cc @@ -61,9 +61,14 @@ namespace Ogre SceneManager *_sceneManager, Hlms *_hlms) { - if (!_casterPass && this->ignOgreRenderingMode == IORM_SOLID_COLOR) + if (!_casterPass && + (this->ignOgreRenderingMode == IORM_SOLID_COLOR || + this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED)) { _hlms->_setProperty("ign_render_solid_color", 1); + + if (this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED) + _hlms->_setProperty("ign_render_solid_color_textured", 1); } // Allow additional listener-only customizations to inject their stuff @@ -140,7 +145,9 @@ namespace Ogre listener->hlmsTypeChanged(_casterPass, _commandBuffer, _datablock); } - if (_casterPass || this->ignOgreRenderingMode != IORM_SOLID_COLOR) + if (_casterPass || + (this->ignOgreRenderingMode != IORM_SOLID_COLOR && + this->ignOgreRenderingMode != IORM_SOLID_THERMAL_COLOR_TEXTURED)) { return; } @@ -156,7 +163,9 @@ namespace Ogre const uint32 instanceIdx = HlmsPbs::fillBuffersForV1( _cache, _queuedRenderable, _casterPass, _lastCacheHash, _commandBuffer); - if (this->ignOgreRenderingMode == IORM_SOLID_COLOR && !_casterPass) + if ((this->ignOgreRenderingMode == IORM_SOLID_COLOR || + this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED) && + !_casterPass) { Vector4 customParam = _queuedRenderable.renderable->getCustomParameter(1u); @@ -167,7 +176,22 @@ namespace Ogre dataPtr[0] = customParam.x; dataPtr[1] = customParam.y; dataPtr[2] = customParam.z; - dataPtr[3] = customParam.w; + + if (this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED && + _queuedRenderable.renderable->hasCustomParameter(2u)) + { + IGN_ASSERT(customParam.w >= 0.0f, + "customParam.w can't be negative for " + "IORM_SOLID_THERMAL_COLOR_TEXTURED"); + + // Negate customParam.w to tell the shader we wish to multiply + // against the diffuse texture. We substract 0.5f to avoid -0.0 = 0.0 + dataPtr[3] = -customParam.w - 0.5f; + } + else + { + dataPtr[3] = customParam.w; + } } return instanceIdx; @@ -181,7 +205,9 @@ namespace Ogre const uint32 instanceIdx = HlmsPbs::fillBuffersForV2( _cache, _queuedRenderable, _casterPass, _lastCacheHash, _commandBuffer); - if (this->ignOgreRenderingMode == IORM_SOLID_COLOR && !_casterPass) + if ((this->ignOgreRenderingMode == IORM_SOLID_COLOR || + this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED) && + !_casterPass) { Vector4 customParam; try @@ -209,6 +235,22 @@ namespace Ogre dataPtr[1] = customParam.y; dataPtr[2] = customParam.z; dataPtr[3] = customParam.w; + + if (this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED && + _queuedRenderable.renderable->hasCustomParameter(2u)) + { + IGN_ASSERT(customParam.w >= 0.0f, + "customParam.w can't be negative for " + "IORM_SOLID_THERMAL_COLOR_TEXTURED"); + + // Negate customParam.w to tell the shader we wish to multiply + // against the diffuse texture. We substract 0.5f to avoid -0.0 = 0.0 + dataPtr[3] = -customParam.w - 0.5f; + } + else + { + dataPtr[3] = customParam.w; + } } return instanceIdx; diff --git a/ogre2/src/Ogre2IgnHlmsTerraPrivate.cc b/ogre2/src/Ogre2IgnHlmsTerraPrivate.cc new file mode 100644 index 000000000..0317aaa97 --- /dev/null +++ b/ogre2/src/Ogre2IgnHlmsTerraPrivate.cc @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "Ogre2IgnHlmsTerraPrivate.hh" + +#include +#include +#include + +#include "Terra/Terra.h" + +#ifdef _MSC_VER +# pragma warning(push, 0) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +using namespace ignition; +using namespace rendering; + +namespace Ogre +{ + /// \brief The slot where to bind currPerObjectDataBuffer + /// HlmsPbs might consume slot 3, so we always use slot 4 for simplicity + /// \internal + static const uint16 kPerObjectDataBufferSlot = 4u; + + Ogre2IgnHlmsTerra::Ogre2IgnHlmsTerra( + Archive *dataFolder, ArchiveVec *libraryFolders, + Ogre2IgnHlmsSphericalClipMinDistance *_sphericalClipMinDistance) : + HlmsTerra(dataFolder, libraryFolders) + { + this->customizations.push_back(_sphericalClipMinDistance); + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::preparePassHash( + const CompositorShadowNode *_shadowNode, bool _casterPass, + bool _dualParaboloid, SceneManager *_sceneManager, Hlms *_hlms) + { + if (!_casterPass && + (this->ignOgreRenderingMode == IORM_SOLID_COLOR || + this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED)) + { + _hlms->_setProperty("ign_render_solid_color", 1); + + if (this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED) + _hlms->_setProperty("ign_render_solid_color_textured", 1); + } + + // Allow additional listener-only customizations to inject their stuff + for (Ogre::HlmsListener *listener : this->customizations) + { + listener->preparePassHash(_shadowNode, _casterPass, _dualParaboloid, + _sceneManager, _hlms); + } + } + + ///////////////////////////////////////////////// + uint32 Ogre2IgnHlmsTerra::getPassBufferSize( + const Ogre::CompositorShadowNode *_shadowNode, bool _casterPass, + bool _dualParaboloid, Ogre::SceneManager *_sceneManager) const + { + uint32 bufferSize = 0u; + + // Allow additional listener-only customizations to inject their stuff + for (Ogre::HlmsListener *listener : this->customizations) + { + bufferSize += listener->getPassBufferSize(_shadowNode, _casterPass, + _dualParaboloid, _sceneManager); + } + return bufferSize; + } + + ///////////////////////////////////////////////// + float *Ogre2IgnHlmsTerra::preparePassBuffer( + const Ogre::CompositorShadowNode *_shadowNode, bool _casterPass, + bool _dualParaboloid, Ogre::SceneManager *_sceneManager, + float *_passBufferPtr) + { + // Allow additional listener-only customizations to inject their stuff + for (Ogre::HlmsListener *listener : this->customizations) + { + _passBufferPtr = + listener->preparePassBuffer(_shadowNode, _casterPass, _dualParaboloid, + _sceneManager, _passBufferPtr); + } + return _passBufferPtr; + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::shaderCacheEntryCreated( + const String &_shaderProfile, const HlmsCache *_hlmsCacheEntry, + const HlmsCache &_passCache, const HlmsPropertyVec &_properties, + const QueuedRenderable &_queuedRenderable) + { + // Allow additional listener-only customizations to inject their stuff + for (Ogre::HlmsListener *listener : this->customizations) + { + listener->shaderCacheEntryCreated(_shaderProfile, _hlmsCacheEntry, + _passCache, _properties, + _queuedRenderable); + } + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::notifyPropertiesMergedPreGenerationStep() + { + HlmsTerra::notifyPropertiesMergedPreGenerationStep(); + + setProperty("IgnPerObjectDataSlot", kPerObjectDataBufferSlot); + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::hlmsTypeChanged(bool _casterPass, + CommandBuffer *_commandBuffer, + const HlmsDatablock *_datablock) + { + // Allow additional listener-only customizations to inject their stuff + for (Ogre::HlmsListener *listener : this->customizations) + { + listener->hlmsTypeChanged(_casterPass, _commandBuffer, _datablock); + } + + if (_casterPass || + (this->ignOgreRenderingMode != IORM_SOLID_COLOR && + this->ignOgreRenderingMode != IORM_SOLID_THERMAL_COLOR_TEXTURED)) + { + return; + } + + this->BindObjectDataBuffer(_commandBuffer, kPerObjectDataBufferSlot); + } + + ///////////////////////////////////////////////// + uint32 Ogre2IgnHlmsTerra::fillBuffersForV1( + const HlmsCache *_cache, const QueuedRenderable &_queuedRenderable, + bool _casterPass, uint32 _lastCacheHash, CommandBuffer *_commandBuffer) + { + const uint32 instanceIdx = HlmsTerra::fillBuffersForV1( + _cache, _queuedRenderable, _casterPass, _lastCacheHash, _commandBuffer); + + if ((this->ignOgreRenderingMode == IORM_SOLID_COLOR || + this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED) && + !_casterPass) + { + const Ogre::Terra *terra = + static_cast(_queuedRenderable.movableObject); + + Vector4 customParam; + try + { + customParam = terra->SolidColor(1u); + } + catch (ItemIdentityException &) + { + // This error can trigger for two reasons: + // + // 1. We forgot to call setSolidColor(1u, ...) + // 2. This object should not be rendered and we should've called + // movableObject->setVisible(false) or use RenderQueue IDs + // or visibility flags to prevent rendering it + ignerr << "A module is trying to render an object without " + "specifying a parameter. Please report this bug at " + "https://github.com/ignitionrobotics/ign-rendering/issues\n"; + throw; + } + float *dataPtr = this->MapObjectDataBufferFor( + instanceIdx, _commandBuffer, this->mVaoManager, this->mConstBuffers, + this->mCurrentConstBuffer, this->mStartMappedConstBuffer, + kPerObjectDataBufferSlot); + dataPtr[0] = customParam.x; + dataPtr[1] = customParam.y; + dataPtr[2] = customParam.z; + + if (this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED && + terra->HasSolidColor(2u)) + { + IGN_ASSERT(customParam.w >= 0.0f, + "customParam.w can't be negative for " + "IORM_SOLID_THERMAL_COLOR_TEXTURED"); + + // Negate customParam.w to tell the shader we wish to multiply + // against the diffuse texture. We substract 0.5f to avoid -0.0 = 0.0 + dataPtr[3] = -customParam.w - 0.5f; + } + else + { + dataPtr[3] = customParam.w; + } + } + + return instanceIdx; + } + + ///////////////////////////////////////////////// + uint32 Ogre2IgnHlmsTerra::fillBuffersForV2( + const HlmsCache *_cache, const QueuedRenderable &_queuedRenderable, + bool _casterPass, uint32 _lastCacheHash, CommandBuffer *_commandBuffer) + { + const uint32 instanceIdx = HlmsTerra::fillBuffersForV2( + _cache, _queuedRenderable, _casterPass, _lastCacheHash, _commandBuffer); + + if ((this->ignOgreRenderingMode == IORM_SOLID_COLOR || + this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED) && + !_casterPass) + { + const Ogre::Terra *terra = + static_cast(_queuedRenderable.movableObject); + + Vector4 customParam; + try + { + customParam = terra->SolidColor(1u); + } + catch (ItemIdentityException &) + { + // This error can trigger for two reasons: + // + // 1. We forgot to call setSolidColor(1u, ...) + // 2. This object should not be rendered and we should've called + // movableObject->setVisible(false) or use RenderQueue IDs + // or visibility flags to prevent rendering it + ignerr << "A module is trying to render an object without " + "specifying a parameter. Please report this bug at " + "https://github.com/ignitionrobotics/ign-rendering/issues\n"; + throw; + } + float *dataPtr = this->MapObjectDataBufferFor( + instanceIdx, _commandBuffer, this->mVaoManager, this->mConstBuffers, + this->mCurrentConstBuffer, this->mStartMappedConstBuffer, + kPerObjectDataBufferSlot); + dataPtr[0] = customParam.x; + dataPtr[1] = customParam.y; + dataPtr[2] = customParam.z; + dataPtr[3] = customParam.w; + + if (this->ignOgreRenderingMode == IORM_SOLID_THERMAL_COLOR_TEXTURED && + terra->HasSolidColor(2u)) + { + IGN_ASSERT(customParam.w >= 0.0f, + "customParam.w can't be negative for " + "IORM_SOLID_THERMAL_COLOR_TEXTURED"); + + // Negate customParam.w to tell the shader we wish to multiply + // against the diffuse texture. We substract 0.5f to avoid -0.0 = 0.0 + dataPtr[3] = -customParam.w - 0.5f; + } + else + { + dataPtr[3] = customParam.w; + } + } + + return instanceIdx; + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::preCommandBufferExecution( + CommandBuffer *_commandBuffer) + { + this->UnmapObjectDataBuffer(); + HlmsTerra::preCommandBufferExecution(_commandBuffer); + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::frameEnded() + { + HlmsTerra::frameEnded(); + + this->currPerObjectDataBuffer = nullptr; + this->lastMainConstBuffer = nullptr; + this->currPerObjectDataPtr = nullptr; + } + + ///////////////////////////////////////////////// + void Ogre2IgnHlmsTerra::GetDefaultPaths(String &_outDataFolderPath, + StringVector &_outLibraryFoldersPaths) + { + HlmsTerra::getDefaultPaths(_outDataFolderPath, _outLibraryFoldersPaths); + + _outLibraryFoldersPaths.push_back( + common::joinPaths("Hlms", "Ignition", "SolidColor")); + _outLibraryFoldersPaths.push_back( + common::joinPaths("Hlms", "Ignition", "SphericalClipMinDistance")); + _outLibraryFoldersPaths.push_back( + common::joinPaths("Hlms", "Terra", "ign")); + _outLibraryFoldersPaths.push_back( + common::joinPaths("Hlms", "Ignition", "Pbs")); + } +} // namespace Ogre diff --git a/ogre2/src/Ogre2IgnHlmsTerraPrivate.hh b/ogre2/src/Ogre2IgnHlmsTerraPrivate.hh new file mode 100644 index 000000000..5e42869cb --- /dev/null +++ b/ogre2/src/Ogre2IgnHlmsTerraPrivate.hh @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef IGNITION_RENDERING_OGRE2_OGRE2IGNHLMSTERRAPRIVATE_HH_ +#define IGNITION_RENDERING_OGRE2_OGRE2IGNHLMSTERRAPRIVATE_HH_ + +#include "ignition/rendering/config.hh" +#include "ignition/rendering/ogre2/Export.hh" + +#include "Ogre2IgnHlmsSharedPrivate.hh" +#include "Ogre2IgnHlmsSphericalClipMinDistance.hh" +#include "Terra/Hlms/OgreHlmsTerra.h" + +#ifdef _MSC_VER +# pragma warning(push, 0) +#endif +#include +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#include + +namespace Ogre +{ + /// \brief Controls custom shader snippets of Hlms: + /// + /// - Toggles them on/off + /// - Sends relevant data to the GPU buffers for shaders to use + /// + /// This listener requires Hlms to have been created with the piece data + /// files in ogre2/src/media/Hlms/Ignition registered + /// + /// We need to derive from HlmsTerra (rather than just using + /// HlmsListener) when we need to use code that sends data + /// *per object* + /// + /// For performance reasons Ogre does not allow passing per-object + /// data via listeners; so we override Hlms implementations. + /// + /// Use GetDefaultPaths to get them + /// + /// \internal + /// \remark Public variables take effect immediately (i.e. for the + /// next render) + class IGNITION_RENDERING_OGRE2_HIDDEN Ogre2IgnHlmsTerra final + : public HlmsTerra, + public HlmsListener, + public ignition::rendering::Ogre2IgnHlmsShared + { + /// \brief Constructor. Asks for modular listeners so we can add + /// them in the proper order + public: Ogre2IgnHlmsTerra(Archive *dataFolder, ArchiveVec *libraryFolders, + ignition::rendering:: + Ogre2IgnHlmsSphericalClipMinDistance + *_sphericalClipMinDistance); + + /// \brief Destructor. Virtual to silence warnings + public: virtual ~Ogre2IgnHlmsTerra() override = default; + + // Documentation inherited + public: using HlmsTerra::preparePassHash; + + /// \brief Override HlmsListener to add customizations. + /// We can't override HlmsTerra because adding properties before + /// calling it will be cleared. And adding it afterwards is too late. + /// The listener gets called right in the middle + /// + /// \param[in] _shadowNode see base class + /// \param[in] _casterPass see base class + /// \param[in] _dualParaboloid see base class + /// \param[in] _sceneManager see base class + /// \param[in] _hlms see base class + public: virtual void preparePassHash( + const CompositorShadowNode *_shadowNode, + bool _casterPass, + bool _dualParaboloid, + SceneManager *_sceneManager, + Hlms *_hlms) override; + + /// \brief Tells Ogre the buffer data sent to GPU should be a little + /// bigger to fit our data we need to send + /// \param[in] _shadowNode see base class + /// \param[in] _casterPass see base class + /// \param[in] _dualParaboloid see base class + /// \param[in] _sceneManager see base class + /// \return Size in bytes of how bigger the buffer should be + public: virtual uint32 getPassBufferSize( + const Ogre::CompositorShadowNode *_shadowNode, + bool _casterPass, bool _dualParaboloid, + Ogre::SceneManager *_sceneManager) const override; + + /// \brief Sends our custom data to GPU buffers that our + /// pieces activated in preparePassHash will need. + /// + /// Bytes written must not exceed what we informed in getPassBufferSize + /// \param[in] _shadowNode see base class + /// \param[in] _casterPass see base class + /// \param[in] _dualParaboloid see base class + /// \param[in] _sceneManager see base class + /// \param[in] _passBufferPtr see base class + /// \return The pointer where Ogre should continue appending more data + private: virtual float* preparePassBuffer( + const Ogre::CompositorShadowNode *_shadowNode, + bool _casterPass, bool _dualParaboloid, + Ogre::SceneManager *_sceneManager, + float *_passBufferPtr) override; + + /// \brief See HlmsListener::shaderCacheEntryCreated + public: virtual void shaderCacheEntryCreated( + const String &_shaderProfile, const HlmsCache *_hlmsCacheEntry, + const HlmsCache &_passCache, const HlmsPropertyVec &_properties, + const QueuedRenderable &_queuedRenderable) override; + + /// \brief Override to calculate which slots are used + public: virtual void notifyPropertiesMergedPreGenerationStep() override; + + /// \brief Override HlmsListener::hlmsTypeChanged so we can + /// bind buffers which carry per-object data when in IORM_SOLID_COLOR + /// \param[in] _casterPass true if this is a caster pass + /// \param[in] _commandBuffer command buffer so we can add commands + /// \param[in] _datablock material of the object that caused + /// Ogre2IgnHlmsTerra to be bound again + public: virtual void hlmsTypeChanged( + bool _casterPass, + CommandBuffer *_commandBuffer, + const HlmsDatablock *_datablock) override; + + // Documentation inherited + public: virtual uint32 fillBuffersForV1( + const HlmsCache *_cache, + const QueuedRenderable &_queuedRenderable, + bool _casterPass, uint32 _lastCacheHash, + CommandBuffer *_commandBuffer ) override; + + // Documentation inherited + public: virtual uint32 fillBuffersForV2( + const HlmsCache *_cache, + const QueuedRenderable &_queuedRenderable, + bool _casterPass, uint32 _lastCacheHash, + CommandBuffer *_commandBuffer ) override; + + // Documentation inherited + public: virtual void preCommandBufferExecution( + CommandBuffer *_commandBuffer) override; + + // Documentation inherited + public: virtual void frameEnded() override; + + //// \brief Same as HlmsTerra::getDefaultPaths, but we also append + /// our own paths with customizations + /// \param[out] _outDataFolderPath Path (as a String) used for + /// creating the "dataFolder" Archive the constructor will need + /// \param[out] _outLibraryFoldersPaths + /// Vector of String used for creating the ArchiveVector "libraryFolders" + /// the constructor will need. Our own stuff is appended here + public: static void GetDefaultPaths(String &_outDataFolderPath, + StringVector &_outLibraryFoldersPaths); + + /// \brief Contains additional customizations that are modular and + /// implemented as listener-only + private: std::vector customizations; + }; +} // namespace Ogre + +#endif diff --git a/ogre2/src/Ogre2MaterialSwitcher.cc b/ogre2/src/Ogre2MaterialSwitcher.cc index 45c38e88b..91362c484 100644 --- a/ogre2/src/Ogre2MaterialSwitcher.cc +++ b/ogre2/src/Ogre2MaterialSwitcher.cc @@ -16,16 +16,23 @@ */ #include "ignition/common/Console.hh" + +#include "ignition/rendering/ogre2/Ogre2Heightmap.hh" #include "ignition/rendering/ogre2/Ogre2MaterialSwitcher.hh" +#include "ignition/rendering/ogre2/Ogre2RenderEngine.hh" #include "ignition/rendering/ogre2/Ogre2Scene.hh" #include "ignition/rendering/RenderTypes.hh" +#include "Terra/Terra.h" + #ifdef _MSC_VER #pragma warning(push, 0) #endif +#include #include #include #include +#include #include #include #ifdef _MSC_VER @@ -35,43 +42,11 @@ using namespace ignition; using namespace rendering; - -/// \brief A map of ogre sub item pointer to their original low level material -/// \todo(anyone) Added here for ABI compatibity This can be removed once -/// ign-rendering7 switches to Hlms customization for "switching" materials -std::map> materialMap; - ///////////////////////////////////////////////// Ogre2MaterialSwitcher::Ogre2MaterialSwitcher(Ogre2ScenePtr _scene) { this->currentColor = ignition::math::Color(0.0, 0.0, 0.1); this->scene = _scene; - - // plain opaque material - Ogre::ResourcePtr res = - Ogre::MaterialManager::getSingleton().load("ign-rendering/plain_color", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - this->plainMaterial = res.staticCast(); - this->plainMaterial->load(); - - // plain overlay material - this->plainOverlayMaterial = - this->plainMaterial->clone("plain_color_overlay"); - if (!this->plainOverlayMaterial->getTechnique(0) || - !this->plainOverlayMaterial->getTechnique(0)->getPass(0)) - { - ignerr << "Problem creating selection buffer overlay material" - << std::endl; - return; - } - Ogre::Pass *overlayPass = - this->plainOverlayMaterial->getTechnique(0)->getPass(0); - Ogre::HlmsMacroblock macroblock(*overlayPass->getMacroblock()); - macroblock.mDepthCheck = false; - macroblock.mDepthWrite = false; - overlayPass->setMacroblock(macroblock); } ///////////////////////////////////////////////// @@ -83,9 +58,21 @@ Ogre2MaterialSwitcher::~Ogre2MaterialSwitcher() void Ogre2MaterialSwitcher::cameraPreRenderScene( Ogre::Camera * /*_evt*/) { - // swap item to use v1 shader material - // Note: keep an eye out for performance impact on switching materials - // on the fly. We are not doing this often so should be ok. + auto engine = Ogre2RenderEngine::Instance(); + engine->SetIgnOgreRenderingMode(IORM_SOLID_COLOR); + + this->materialMap.clear(); + this->datablockMap.clear(); + Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + + Ogre::HlmsDatablock *defaultPbs = + hlmsManager->getHlms(Ogre::HLMS_PBS)->getDefaultDatablock(); + + // Construct one now so that datablock->setBlendblock + // each is as fast as possible + const Ogre::HlmsBlendblock *noBlend = + hlmsManager->getBlendblock(Ogre::HlmsBlendblock()); + auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( Ogre::ItemFactory::FACTORY_TYPE_NAME); while (itor.hasMoreElements()) @@ -97,66 +84,128 @@ void Ogre2MaterialSwitcher::cameraPreRenderScene( this->colorDict[this->currentColor.AsRGBA()] = item->getName(); - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) + const Ogre::Vector4 ogreCurrentColor(this->currentColor.R(), + this->currentColor.G(), + this->currentColor.B(), 1.0); + + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { Ogre::SubItem *subItem = item->getSubItem(i); - subItem->setCustomParameter(1, - Ogre::Vector4(this->currentColor.R(), this->currentColor.G(), - this->currentColor.B(), 1.0)); - // case when item is using low level materials - // e.g. shaders + subItem->setCustomParameter(1, ogreCurrentColor); + if (!subItem->getMaterial().isNull()) { - materialMap[this][subItem] = subItem->getMaterial(); - subItem->setMaterial(this->plainMaterial); + // TODO(anyone): We need to keep the material's vertex shader + // to keep vertex deformation consistent. See + // https://github.com/ignitionrobotics/ign-rendering/issues/544 + this->materialMap.push_back({ subItem, subItem->getMaterial() }); + subItem->setDatablock(defaultPbs); } - // regular Pbs Hlms datablock else { + // regular Pbs Hlms datablock Ogre::HlmsDatablock *datablock = subItem->getDatablock(); - this->datablockMap[subItem] = datablock; - // check if it's an overlay material by assuming the - // depth check and depth write properties are off. - if (!datablock->getMacroblock()->mDepthWrite && - !datablock->getMacroblock()->mDepthCheck) - subItem->setMaterial(this->plainOverlayMaterial); - else - subItem->setMaterial(this->plainMaterial); + const Ogre::HlmsBlendblock *blendblock = datablock->getBlendblock(); + + // We can't do any sort of blending. This isn't colour what we're + // storing, but rather an ID. + if (blendblock->mSourceBlendFactor != Ogre::SBF_ONE || + blendblock->mDestBlendFactor != Ogre::SBF_ZERO || + blendblock->mBlendOperation != Ogre::SBO_ADD || + (blendblock->mSeparateBlend && + (blendblock->mSourceBlendFactorAlpha != Ogre::SBF_ONE || + blendblock->mDestBlendFactorAlpha != Ogre::SBF_ZERO || + blendblock->mBlendOperationAlpha != Ogre::SBO_ADD))) + { + hlmsManager->addReference(blendblock); + this->datablockMap[datablock] = blendblock; + datablock->setBlendblock(noBlend); + } } } itor.moveNext(); } + + // Do the same with heightmaps / terrain + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + { + this->NextColor(); + this->colorDict[this->currentColor.AsRGBA()] = heightmap->Name(); + + // TODO(anyone): Retrieve datablock and make sure it's not blending + // like we do with Items (it should be impossible?) + heightmap->Terra()->SetSolidColor( + 1u, Ogre::Vector4(this->currentColor.R(), this->currentColor.G(), + this->currentColor.B(), 1.0)); + } + } + + // Remove the reference count on noBlend we created + hlmsManager->destroyBlendblock(noBlend); } ///////////////////////////////////////////////// void Ogre2MaterialSwitcher::cameraPostRenderScene( Ogre::Camera * /*_evt*/) { - // restore item to use hlms material + auto engine = Ogre2RenderEngine::Instance(); + Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + + // Restore original blending to modified materials + for (const auto &[datablock, blendblock] : this->datablockMap) + { + datablock->setBlendblock(blendblock); + // Remove the reference we added (this won't actually destroy it) + hlmsManager->destroyBlendblock(blendblock); + } + this->datablockMap.clear(); + + // Remove the custom parameter. Why? If there are multiple cameras that + // use IORM_SOLID_COLOR (or any other mode), we want them to throw if + // that code forgot to call setCustomParameter. We may miss those errors + // if that code forgets to call but it was already carrying the value + // we set here. + // + // This consumes more performance but it's the price to pay for + // safety. auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( Ogre::ItemFactory::FACTORY_TYPE_NAME); while (itor.hasMoreElements()) { Ogre::MovableObject *object = itor.peekNext(); Ogre::Item *item = static_cast(object); - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { Ogre::SubItem *subItem = item->getSubItem(i); - auto it = this->datablockMap.find(subItem); - if (it != this->datablockMap.end()) - subItem->setDatablock(it->second); - else - { - auto mIt = materialMap[this].find(subItem); - if (mIt != materialMap[this].end()) - subItem->setMaterial(mIt->second); - } + subItem->removeCustomParameter(1u); } itor.moveNext(); } - this->datablockMap.clear(); - materialMap[this].clear(); + + // Restore Items with low level materials + for (auto subItemMat : this->materialMap) + { + subItemMat.first->setMaterial(subItemMat.second); + } + this->materialMap.clear(); + + // Remove the custom parameter (same reason as with Items) + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + heightmap->Terra()->UnsetSolidColors(); + } + + engine->SetIgnOgreRenderingMode(IORM_NORMAL); } ///////////////////////////////////////////////// diff --git a/ogre2/src/Ogre2RenderEngine.cc b/ogre2/src/Ogre2RenderEngine.cc index 6a0d0d208..83bd044ff 100644 --- a/ogre2/src/Ogre2RenderEngine.cc +++ b/ogre2/src/Ogre2RenderEngine.cc @@ -43,6 +43,7 @@ #include "ignition/rendering/ogre2/Ogre2Storage.hh" #include "Ogre2IgnHlmsPbsPrivate.hh" +#include "Ogre2IgnHlmsTerraPrivate.hh" #include "Ogre2IgnHlmsUnlitPrivate.hh" #include "Terra/Hlms/OgreHlmsTerra.h" @@ -79,6 +80,9 @@ class IGNITION_RENDERING_OGRE2_HIDDEN /// \brief Custom Unlit modifications public: Ogre::Ogre2IgnHlmsUnlit *ignHlmsUnlit{nullptr}; + + /// \brief Custom Terra modifications + public: Ogre::Ogre2IgnHlmsTerra *ignHlmsTerra{nullptr}; }; using namespace ignition; @@ -777,6 +781,14 @@ void Ogre2RenderEngine::RegisterHlms() // Get the library archive(s) Ogre::ArchiveVec archivePbsLibraryFolders; + + { + archivePbsLibraryFolders.push_back(archiveManager.load( + rootHlmsFolder + common::joinPaths("Hlms", "Terra", "GLSL", + "PbsTerraShadows"), "FileSystem", true )); + this->dataPtr->hlmsPbsTerraShadows.reset(new Ogre::HlmsPbsTerraShadows()); + } + libraryFolderPathIt = libraryFoldersPaths.begin(); libraryFolderPathEn = libraryFoldersPaths.end(); while (libraryFolderPathIt != libraryFolderPathEn) @@ -789,12 +801,6 @@ void Ogre2RenderEngine::RegisterHlms() } archivePbsLibraryFolders.push_back(customizationsArchiveLibrary); - { - archivePbsLibraryFolders.push_back(archiveManager.load( - rootHlmsFolder + common::joinPaths("Hlms", "Terra", "GLSL", - "PbsTerraShadows"), "FileSystem", true )); - this->dataPtr->hlmsPbsTerraShadows.reset(new Ogre::HlmsPbsTerraShadows()); - } // Create and register hlmsPbs = @@ -811,16 +817,14 @@ void Ogre2RenderEngine::RegisterHlms() } { - Ogre::HlmsTerra *hlmsTerra = 0; + Ogre::Ogre2IgnHlmsTerra *hlmsTerra = 0; // Create & Register HlmsPbs // Do the same for HlmsPbs: - Ogre::HlmsTerra::getDefaultPaths(mainFolderPath, libraryFoldersPaths); + Ogre::Ogre2IgnHlmsTerra::GetDefaultPaths(mainFolderPath, + libraryFoldersPaths); Ogre::Archive *archiveTerra = archiveManager.load( rootHlmsFolder + mainFolderPath, "FileSystem", true); - // Add ignition's customizations - libraryFoldersPaths.push_back(common::joinPaths("Hlms", "Terra", "ign")); - // Get the library archive(s) Ogre::ArchiveVec archiveTerraLibraryFolders; libraryFolderPathIt = libraryFoldersPaths.begin(); @@ -835,15 +839,19 @@ void Ogre2RenderEngine::RegisterHlms() } // Create and register - hlmsTerra = OGRE_NEW Ogre::HlmsTerra(archiveTerra, - &archiveTerraLibraryFolders); + hlmsTerra = OGRE_NEW Ogre::Ogre2IgnHlmsTerra( + archiveTerra, &archiveTerraLibraryFolders, + &this->dataPtr->sphericalClipMinDistance); Ogre::Root::getSingleton().getHlmsManager()->registerHlms(hlmsTerra); // disable writting debug output to disk hlmsTerra->setDebugOutputPath(false, false); + hlmsTerra->setListener(hlmsTerra); this->dataPtr->terraWorkspaceListener.reset( new Ogre::TerraWorkspaceListener(hlmsTerra)); + + this->dataPtr->ignHlmsTerra = hlmsTerra; } } @@ -1038,6 +1046,8 @@ void Ogre2RenderEngine::SetIgnOgreRenderingMode( IgnOgreRenderingMode renderingMode) { this->dataPtr->ignHlmsPbs->ignOgreRenderingMode = renderingMode; + this->dataPtr->ignHlmsUnlit->ignOgreRenderingMode = renderingMode; + this->dataPtr->ignHlmsTerra->ignOgreRenderingMode = renderingMode; } ///////////////////////////////////////////////// diff --git a/ogre2/src/Ogre2SegmentationCamera.cc b/ogre2/src/Ogre2SegmentationCamera.cc index c31bb6606..45115e7ff 100644 --- a/ogre2/src/Ogre2SegmentationCamera.cc +++ b/ogre2/src/Ogre2SegmentationCamera.cc @@ -310,7 +310,7 @@ ignition::common::ConnectionPtr void Ogre2SegmentationCamera::Render() { // update the compositors - this->scene->StartRendering(nullptr); + this->scene->StartRendering(this->ogreCamera); this->dataPtr->ogreCompositorWorkspace->_validateFinalTarget(); this->dataPtr->ogreCompositorWorkspace->_beginUpdate(false); diff --git a/ogre2/src/Ogre2SegmentationMaterialSwitcher.cc b/ogre2/src/Ogre2SegmentationMaterialSwitcher.cc index bce1790b9..08f2c02ec 100644 --- a/ogre2/src/Ogre2SegmentationMaterialSwitcher.cc +++ b/ogre2/src/Ogre2SegmentationMaterialSwitcher.cc @@ -29,6 +29,8 @@ #include "ignition/rendering/ogre2/Ogre2Visual.hh" #include "ignition/rendering/RenderTypes.hh" +#include "Terra/Terra.h" + #ifdef _MSC_VER #pragma warning(push, 0) #endif @@ -77,6 +79,97 @@ bool Ogre2SegmentationMaterialSwitcher::IsTakenColor(const math::Color &_color) } } +///////////////////////////////////////////////// +Ogre::Vector4 Ogre2SegmentationMaterialSwitcher::ColorForVisual( + const VisualPtr &_visual, std::string &_prevParentName) +{ + // get class user data + Variant labelAny = _visual->UserData("label"); + int label; + try + { + label = std::get(labelAny); + } + catch (std::bad_variant_access &) + { + // items with no class are considered background + label = this->segmentationCamera->BackgroundLabel(); + } + + // sub item custom parameter to set the pixel color material + Ogre::Vector4 customParameter; + + // Material Switching + if (this->segmentationCamera->Type() == SegmentationType::ST_SEMANTIC) + { + if (this->segmentationCamera->IsColoredMap()) + { + // semantic material (each pixel has item's color) + math::Color color = this->LabelToColor(label); + customParameter = Ogre::Vector4(color.R(), color.G(), color.B(), 1.0); + } + else + { + // labels ids material (each pixel has item's label) + float labelColor = label / 255.0f; + customParameter = Ogre::Vector4(labelColor, labelColor, labelColor, 1.0); + } + } + else if (this->segmentationCamera->Type() == SegmentationType::ST_PANOPTIC) + { + auto itemName = _visual->Name(); + std::string parentName = this->TopLevelModelVisual(_visual)->Name(); + + auto it = this->instancesCount.find(label); + if (it == this->instancesCount.end()) + it = this->instancesCount.insert(std::make_pair(label, 0)).first; + + // Multi link model has many links with the same first name and should + // have the same pixels color + bool isMultiLink = false; + if (parentName == _prevParentName) + { + isMultiLink = true; + } + else + { + it->second++; + _prevParentName = parentName; + } + + const int instanceCount = it->second; + + if (this->segmentationCamera->IsColoredMap()) + { + math::Color color; + if (label == this->segmentationCamera->BackgroundLabel()) + { + color = this->LabelToColor(label, isMultiLink); + } + else + { + // convert 24 bit number to int64 + const int compositeId = label * 256 * 256 + instanceCount; + color = this->LabelToColor(compositeId, isMultiLink); + } + + customParameter = Ogre::Vector4(color.R(), color.G(), color.B(), 1.0); + } + else + { + // 256 => 8 bits .. 255 => color percentage + float labelColor = label / 255.0f; + float instanceColor1 = (instanceCount / 256) / 255.0f; + float instanceColor2 = (instanceCount % 256) / 255.0f; + + customParameter = + Ogre::Vector4(instanceColor2, instanceColor1, labelColor, 1.0); + } + } + + return customParameter; +} + ///////////////////////////////////////////////// math::Color Ogre2SegmentationMaterialSwitcher::LabelToColor(int64_t _label, bool _isMultiLink) @@ -162,9 +255,13 @@ void Ogre2SegmentationMaterialSwitcher::cameraPreRenderScene( return object1->getName() > object2->getName(); }); + this->materialMap.clear(); this->datablockMap.clear(); Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + Ogre::HlmsDatablock *defaultPbs = + hlmsManager->getHlms(Ogre::HLMS_PBS)->getDefaultDatablock(); + // Construct one now so that datablock->setBlendblock // each is as fast as possible const Ogre::HlmsBlendblock *noBlend = @@ -191,118 +288,62 @@ void Ogre2SegmentationMaterialSwitcher::cameraPreRenderScene( { ignerr << "Ogre Error:" << e.getFullDescription() << "\n"; } - Ogre2VisualPtr ogreVisual = std::dynamic_pointer_cast( - visual); - // get class user data - Variant labelAny = ogreVisual->UserData("label"); - int label; - try - { - label = std::get(labelAny); - } - catch(std::bad_variant_access &e) - { - // items with no class are considered background - label = this->segmentationCamera->BackgroundLabel(); - } - - // sub item custom parameter to set the pixel color material - Ogre::Vector4 customParameter; + const Ogre::Vector4 customParameter = + ColorForVisual(visual, prevParentName); - // Material Switching - if (this->segmentationCamera->Type() == SegmentationType::ST_SEMANTIC) - { - if (this->segmentationCamera->IsColoredMap()) - { - // semantic material (each pixel has item's color) - math::Color color = this->LabelToColor(label); - customParameter = Ogre::Vector4( - color.R(), color.G(), color.B(), 1.0); - } - else - { - // labels ids material (each pixel has item's label) - float labelColor = label / 255.0; - customParameter = Ogre::Vector4( - labelColor, labelColor, labelColor, 1.0); - } - } - else if (this->segmentationCamera->Type() == - SegmentationType::ST_PANOPTIC) + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { - auto itemName = visual->Name(); - std::string parentName = this->TopLevelModelVisual(visual)->Name(); - - auto it = this->instancesCount.find(label); - if (it == this->instancesCount.end()) - it = this->instancesCount.insert(std::make_pair(label, 0)).first; - - // Multi link model has many links with the same first name and should - // have the same pixels color - bool isMultiLink = false; - if (parentName == prevParentName) - { - isMultiLink = true; - } - else - { - it->second++; - prevParentName = parentName; - } - - int instanceCount = it->second; + // Set the custom value to the sub item to render + Ogre::SubItem *subItem = item->getSubItem(i); + subItem->setCustomParameter(1, customParameter); - if (this->segmentationCamera->IsColoredMap()) + if (!subItem->getMaterial().isNull()) { - // convert 24 bit number to int64 - int compositeId = label * 256 * 256 + instanceCount; - - math::Color color; - if (label == this->segmentationCamera->BackgroundLabel()) - color = this->LabelToColor(label, isMultiLink); - else - color = this->LabelToColor(compositeId, isMultiLink); - - customParameter = Ogre::Vector4( - color.R(), color.G(), color.B(), 1.0); + // TODO(anyone): We need to keep the material's vertex shader + // to keep vertex deformation consistent. See + // https://github.com/ignitionrobotics/ign-rendering/issues/544 + this->materialMap.push_back({ subItem, subItem->getMaterial() }); + subItem->setDatablock(defaultPbs); } else { - // 256 => 8 bits .. 255 => color percentage - float labelColor = label / 255.0; - float instanceColor1 = (instanceCount / 256) / 255.0; - float instanceColor2 = (instanceCount % 256) / 255.0; - - customParameter = Ogre::Vector4( - instanceColor2, instanceColor1, labelColor, 1.0); + Ogre::HlmsDatablock *datablock = subItem->getDatablock(); + const Ogre::HlmsBlendblock *blendblock = datablock->getBlendblock(); + + // We can't do any sort of blending. This isn't colour what we're + // storing, but rather an ID. + if (blendblock->mSourceBlendFactor != Ogre::SBF_ONE || + blendblock->mDestBlendFactor != Ogre::SBF_ZERO || + blendblock->mBlendOperation != Ogre::SBO_ADD || + (blendblock->mSeparateBlend && + (blendblock->mSourceBlendFactorAlpha != Ogre::SBF_ONE || + blendblock->mDestBlendFactorAlpha != Ogre::SBF_ZERO || + blendblock->mBlendOperationAlpha != Ogre::SBO_ADD))) + { + hlmsManager->addReference(blendblock); + this->datablockMap[datablock] = blendblock; + datablock->setBlendblock(noBlend); + } } } + } + } - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) - { - // Set the custom value to the sub item to render - Ogre::SubItem *subItem = item->getSubItem(i); - subItem->setCustomParameter(1, customParameter); - - Ogre::HlmsDatablock *datablock = subItem->getDatablock(); - const Ogre::HlmsBlendblock *blendblock = datablock->getBlendblock(); - - // We can't do any sort of blending. This isn't colour what we're - // storing, but rather an ID. - if (blendblock->mSourceBlendFactor != Ogre::SBF_ONE || - blendblock->mDestBlendFactor != Ogre::SBF_ZERO || - blendblock->mBlendOperation != Ogre::SBO_ADD || - (blendblock->mSeparateBlend && - (blendblock->mSourceBlendFactorAlpha != Ogre::SBF_ONE || - blendblock->mDestBlendFactorAlpha != Ogre::SBF_ZERO || - blendblock->mBlendOperationAlpha != Ogre::SBO_ADD))) - { - hlmsManager->addReference(blendblock); - this->datablockMap[datablock] = blendblock; - datablock->setBlendblock(noBlend); - } - } + // Do the same with heightmaps / terrain + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + { + // TODO(anyone): Retrieve datablock and make sure it's not blending + // like we do with Items (it should be impossible?) + VisualPtr visual = heightmap->Parent(); + const Ogre::Vector4 customParameter = + ColorForVisual(visual, prevParentName); + heightmap->Terra()->SetSolidColor(1u, customParameter); } } @@ -313,18 +354,6 @@ void Ogre2SegmentationMaterialSwitcher::cameraPreRenderScene( this->instancesCount.clear(); this->takenColors.clear(); this->coloredLabel.clear(); - - // disable heightmaps in segmentation camera sensor - // until we support changing its material based on input label - // TODO(anyone) add support for heightmaps with the segmentation camera - // https://github.com/ignitionrobotics/ign-rendering/issues/444 - auto heightmaps = this->scene->Heightmaps(); - for (auto h : heightmaps) - { - auto heightmap = h.lock(); - if (heightmap) - heightmap->Parent()->SetVisible(false); - } } //////////////////////////////////////////////// @@ -343,13 +372,43 @@ void Ogre2SegmentationMaterialSwitcher::cameraPostRenderScene( } this->datablockMap.clear(); - // re-enable heightmaps + // Remove the custom parameter. Why? If there are multiple cameras that + // use IORM_SOLID_COLOR (or any other mode), we want them to throw if + // that code forgot to call setCustomParameter. We may miss those errors + // if that code forgets to call but it was already carrying the value + // we set here. + // + // This consumes more performance but it's the price to pay for + // safety. + auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( + Ogre::ItemFactory::FACTORY_TYPE_NAME); + while (itor.hasMoreElements()) + { + Ogre::MovableObject *object = itor.peekNext(); + Ogre::Item *item = static_cast(object); + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) + { + Ogre::SubItem *subItem = item->getSubItem(i); + subItem->removeCustomParameter(1u); + } + itor.moveNext(); + } + + // Restore Items with low level materials + for (auto subItemMat : this->materialMap) + { + subItemMat.first->setMaterial(subItemMat.second); + } + this->materialMap.clear(); + + // Remove the custom parameter (same reason as with Items) auto heightmaps = this->scene->Heightmaps(); for (auto h : heightmaps) { auto heightmap = h.lock(); if (heightmap) - heightmap->Parent()->SetVisible(true); + heightmap->Terra()->UnsetSolidColors(); } engine->SetIgnOgreRenderingMode(IORM_NORMAL); diff --git a/ogre2/src/Ogre2SegmentationMaterialSwitcher.hh b/ogre2/src/Ogre2SegmentationMaterialSwitcher.hh index dc93f09ee..90825382b 100644 --- a/ogre2/src/Ogre2SegmentationMaterialSwitcher.hh +++ b/ogre2/src/Ogre2SegmentationMaterialSwitcher.hh @@ -18,10 +18,12 @@ #ifndef IGNITION_RENDERING_OGRE2_OGRE2SEGMENTATIONMATERIALSWITCHER_HH_ #define IGNITION_RENDERING_OGRE2_OGRE2SEGMENTATIONMATERIALSWITCHER_HH_ -#include #include +#include #include #include +#include +#include #include @@ -67,6 +69,14 @@ class IGNITION_RENDERING_OGRE2_VISIBLE Ogre2SegmentationMaterialSwitcher : /// \return The map between color and label IDs public: const std::unordered_map &ColorToLabel() const; + /// \brief Create a color to apply for the given visual + /// \param[in] _visual Visual will be applying the color to + /// \param[in,out] _prevParentName A persistent string between call + /// to ensure multilink visuals receive the same color + /// \return The color to apply to the visual + private: Ogre::Vector4 ColorForVisual(const VisualPtr &_visual, + std::string &_prevParentName); + /// \brief Convert label of semantic map to a unique color for colored map and /// add the color of the label to the taken colors if it doesn't exist /// \param[in] _label id of the semantic map or encoded id of panoptic map @@ -94,7 +104,7 @@ class IGNITION_RENDERING_OGRE2_VISIBLE Ogre2SegmentationMaterialSwitcher : /// \brief Keep track of num of instances of the same label /// Key: label id, value: num of instances - private: std::unordered_map instancesCount; + private: std::unordered_map instancesCount; /// \brief keep track of the random colors (store encoded id of r,g,b) private: std::unordered_set takenColors; @@ -113,6 +123,12 @@ class IGNITION_RENDERING_OGRE2_VISIBLE Ogre2SegmentationMaterialSwitcher : private: std::unordered_map datablockMap; + /// \brief A map of ogre sub item pointer to their original low level + /// material. + /// Most objects don't use one so it should be almost always empty. + private: + std::vector> materialMap; + /// \brief Pseudo num generator to generate colors from label id private: std::default_random_engine generator; diff --git a/ogre2/src/Ogre2ThermalCamera.cc b/ogre2/src/Ogre2ThermalCamera.cc index d9a23bff7..5c9accf45 100644 --- a/ogre2/src/Ogre2ThermalCamera.cc +++ b/ogre2/src/Ogre2ThermalCamera.cc @@ -55,6 +55,7 @@ #include "ignition/rendering/RenderTypes.hh" #include "ignition/rendering/ogre2/Ogre2Conversions.hh" +#include "ignition/rendering/ogre2/Ogre2Heightmap.hh" #include "ignition/rendering/ogre2/Ogre2Includes.hh" #include "ignition/rendering/ogre2/Ogre2Material.hh" #include "ignition/rendering/ogre2/Ogre2ParticleEmitter.hh" @@ -68,6 +69,8 @@ #include +#include "Terra/Terra.h" + namespace ignition { namespace rendering @@ -108,9 +111,6 @@ class Ogre2ThermalCameraMaterialSwitcher : public Ogre::Camera::Listener /// \brief Scene manager private: Ogre2ScenePtr scene = nullptr; - /// \brief Pointer to the heat source material - private: Ogre::MaterialPtr heatSourceMaterial; - /// \brief Pointer to the "base" heat signature material. /// All renderable items with a heat signature texture use their own /// copy of this base material, with the item's specific heat @@ -129,14 +129,19 @@ class Ogre2ThermalCameraMaterialSwitcher : public Ogre::Camera::Listener /// \brief The thermal camera private: const Ogre::Camera* ogreCamera{nullptr}; - /// \brief Custom parameter index of temperature data in an ogre subitem. - /// This has to match the custom index specifed in ThermalHeatSource material - /// script in media/materials/scripts/thermal_camera.material - private: const unsigned int customParamIdx = 10u; - /// \brief A map of ogre sub item pointer to their original hlms material - private: std::unordered_map - datablockMap; + private: std::vector> + itemDatablockMap; + + /// \brief A map of ogre sub item pointer to their original low level + /// material. + /// Most objects don't use one so it should be almost always empty. + private: + std::vector> materialMap; + + /// \brief A map of ogre datablock pointer to their original blendblocks + private: std::unordered_map datablockMap; /// \brief linear temperature resolution. Defaults to 10mK private: double resolution = 0.01; @@ -214,9 +219,6 @@ Ogre2ThermalCameraMaterialSwitcher::Ogre2ThermalCameraMaterialSwitcher( Ogre::MaterialManager::getSingleton().load("ThermalHeatSource", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - this->heatSourceMaterial = res.staticCast(); - this->heatSourceMaterial->load(); - this->baseHeatSigMaterial = Ogre::MaterialManager::getSingleton(). getByName("ThermalHeatSignature"); @@ -239,10 +241,26 @@ void Ogre2ThermalCameraMaterialSwitcher::SetLinearResolution(double _resolution) void Ogre2ThermalCameraMaterialSwitcher::cameraPreRenderScene( Ogre::Camera * /*_cam*/) { + auto engine = Ogre2RenderEngine::Instance(); + engine->SetIgnOgreRenderingMode(IORM_SOLID_THERMAL_COLOR_TEXTURED); + // swap item to use v1 shader material // Note: keep an eye out for performance impact on switching materials // on the fly. We are not doing this often so should be ok. - this->datablockMap.clear(); + this->itemDatablockMap.clear(); + this->materialMap.clear(); + Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + + Ogre::HlmsDatablock *defaultPbs = + hlmsManager->getHlms(Ogre::HLMS_PBS)->getDefaultDatablock(); + + // Construct one now so that datablock->setBlendblock + // each is as fast as possible + const Ogre::HlmsBlendblock *noBlend = + hlmsManager->getBlendblock(Ogre::HlmsBlendblock()); + + const std::string tempKey = "temperature"; + auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( Ogre::ItemFactory::FACTORY_TYPE_NAME); while (itor.hasMoreElements()) @@ -250,7 +268,6 @@ void Ogre2ThermalCameraMaterialSwitcher::cameraPreRenderScene( Ogre::MovableObject *object = itor.peekNext(); Ogre::Item *item = static_cast(object); - const std::string tempKey = "temperature"; // get visual Ogre::Any userAny = item->getUserObjectBindings().getUserAny(); if (!userAny.isEmpty() && userAny.getType() == typeid(unsigned int)) @@ -307,22 +324,49 @@ void Ogre2ThermalCameraMaterialSwitcher::cameraPreRenderScene( << "zero. Clamping temperature to 0 degrees Kelvin." << std::endl; } - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) + + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { Ogre::SubItem *subItem = item->getSubItem(i); // normalize temperature value - float color = (temp / this->resolution) / ((1 << bitDepth) - 1.0); + const float color = static_cast((temp / this->resolution) / + ((1 << bitDepth) - 1.0)); // set g, b, a to 0. This will be used by shaders to determine // if particular fragment is a heat source or not - // see media/materials/programs/thermal_camera_fs.glsl - subItem->setCustomParameter(this->customParamIdx, - Ogre::Vector4(color, 0, 0, 0.0)); - Ogre::HlmsDatablock *datablock = subItem->getDatablock(); - this->datablockMap[subItem] = datablock; + // see media/materials/programs/GLSL/thermal_camera_fs.glsl + subItem->setCustomParameter(1, Ogre::Vector4(color, 0, 0, 0.0)); - subItem->setMaterial(this->heatSourceMaterial); + if (!subItem->getMaterial().isNull()) + { + // TODO(anyone): We need to keep the material's vertex shader + // to keep vertex deformation consistent. See + // https://github.com/ignitionrobotics/ign-rendering/issues/544 + this->materialMap.push_back({ subItem, subItem->getMaterial() }); + subItem->setDatablock(defaultPbs); + } + else + { + Ogre::HlmsDatablock *datablock = subItem->getDatablock(); + const Ogre::HlmsBlendblock *blendblock = datablock->getBlendblock(); + + // We can't do any sort of blending. This isn't colour what we're + // storing, but rather an ID. + if (blendblock->mSourceBlendFactor != Ogre::SBF_ONE || + blendblock->mDestBlendFactor != Ogre::SBF_ZERO || + blendblock->mBlendOperation != Ogre::SBO_ADD || + (blendblock->mSeparateBlend && + (blendblock->mSourceBlendFactorAlpha != Ogre::SBF_ONE || + blendblock->mDestBlendFactorAlpha != Ogre::SBF_ZERO || + blendblock->mBlendOperationAlpha != Ogre::SBO_ADD))) + { + hlmsManager->addReference(blendblock); + this->datablockMap[datablock] = blendblock; + datablock->setBlendblock(noBlend); + } + } } } // get heat signature and the corresponding min/max temperature values @@ -336,7 +380,6 @@ void Ogre2ThermalCameraMaterialSwitcher::cameraPreRenderScene( { // make sure the texture is in ogre's resource path const auto &texture = *heatSignature; - auto engine = Ogre2RenderEngine::Instance(); engine->AddResourcePath(texture); // create a material for this item, now that the texture has been @@ -380,62 +423,222 @@ void Ogre2ThermalCameraMaterialSwitcher::cameraPreRenderScene( this->heatSignatureMaterials[item->getId()] = heatSignatureMaterial; } - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { Ogre::SubItem *subItem = item->getSubItem(i); - Ogre::HlmsDatablock *datablock = subItem->getDatablock(); - this->datablockMap[subItem] = datablock; + if (!subItem->getMaterial().isNull()) + { + // TODO(anyone): We need to keep the material's vertex shader + // to keep vertex deformation consistent. See + // https://github.com/ignitionrobotics/ign-rendering/issues/544 + this->materialMap.push_back({ subItem, subItem->getMaterial() }); + } + else + { + // TODO(anyone): We're not using Hlms pieces, therefore HW + // vertex deformation (e.g. skinning / skeletal animation) won't + // show up correctly + Ogre::HlmsDatablock *datablock = subItem->getDatablock(); + this->itemDatablockMap.push_back({ subItem, datablock }); + } subItem->setMaterial(this->heatSignatureMaterials[item->getId()]); } } - // background objects else { - Ogre::Aabb aabb = item->getWorldAabbUpdated(); - Ogre::AxisAlignedBox box = Ogre::AxisAlignedBox(aabb.getMinimum(), - aabb.getMaximum()); - - // we will be converting rgb values to temperature values in shaders - // but we want to make sure the object rgb values are not affected by - // lighting, so disable lighting - // Also check if objects are within camera view - if (ogreVisual->GeometryCount() > 0u && - this->ogreCamera->isVisible(box)) + // Temperature object not set + // We consider this a "background object". + // + // It will be set to ambient temperature in thermal_camera_fs.glsl + // but its unlit, textured RGB color actually matters. + // + // We will be converting rgb values to temperature values in shaders + // thus we want them textured but without lighting + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) { - auto geom = ogreVisual->GeometryByIndex(0); - if (geom) + Ogre::SubItem *subItem = item->getSubItem(i); + + const Ogre::HlmsDatablock *datablock = subItem->getDatablock(); + const Ogre::ColourValue color = datablock->getDiffuseColour(); + subItem->setCustomParameter( + 1u, Ogre::Vector4(color.r, color.g, color.b, 1.0)); + + // Set 2 to signal we want it to multiply against + // the diffuse texture (if any). The actual value doesn't matter. + subItem->setCustomParameter(2u, Ogre::Vector4::ZERO); + + if (!subItem->getMaterial().isNull()) + { + // TODO(anyone): We need to keep the material's vertex shader + // to keep vertex deformation consistent. See + // https://github.com/ignitionrobotics/ign-rendering/issues/544 + this->materialMap.push_back({ subItem, subItem->getMaterial() }); + subItem->setDatablock(defaultPbs); + } + else { - MaterialPtr mat = geom->Material(); - Ogre2MaterialPtr ogreMat = - std::dynamic_pointer_cast(mat); - Ogre::HlmsUnlitDatablock *unlit = ogreMat->UnlitDatablock(); - for (unsigned int i = 0; i < item->getNumSubItems(); ++i) + // We don't save to this->datablockMap because we're already + // honouring the original HlmsBlendblock. There's nothing + // to override. + } + } + } + } + + itor.moveNext(); + } + + // Do the same with heightmaps / terrain + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + { + VisualPtr visual = heightmap->Parent(); + + // get temperature + Variant tempAny = visual->UserData(tempKey); + if (tempAny.index() != 0 && !std::holds_alternative(tempAny)) + { + float temp = -1.0; + bool foundTemp = true; + try + { + temp = std::get(tempAny); + } + catch (...) + { + try + { + temp = static_cast(std::get(tempAny)); + } + catch (...) + { + try { - Ogre::SubItem *subItem = item->getSubItem(i); - Ogre::HlmsDatablock *datablock = subItem->getDatablock(); - this->datablockMap[subItem] = datablock; - subItem->setDatablock(unlit); + temp = std::get(tempAny); + } + catch (std::bad_variant_access &e) + { + ignerr << "Error casting user data: " << e.what() << "\n"; + temp = -1.0; + foundTemp = false; } } } + + // if a non-positive temperature was given, clamp it to 0 + if (foundTemp && temp < 0.0) + { + temp = 0.0; + ignwarn << "Unable to set negatve temperature for: " << visual->Name() + << ". Value cannot be lower than absolute " + << "zero. Clamping temperature to 0 degrees Kelvin." + << std::endl; + } + + // normalize temperature value + const float color = static_cast((temp / this->resolution) / + ((1 << bitDepth) - 1.0)); + + heightmap->Terra()->SetSolidColor(1u, Ogre::Vector4(color, 0, 0, 0.0)); + // TODO(anyone): Retrieve datablock and make sure it's not blending + // like we do with Items (it should be impossible?) + } + // get heat signature and the corresponding min/max temperature values + else if (std::get_if(&tempAny)) + { + ignerr << "Heat Signature not yet supported by Heightmaps. Simulation " + "may crash!\n"; + } + else + { + // Temperature object not set + // We consider this a "background object". + + // TODO(anyone): Retrieve datablock and get diffuse color + // (it's likely gonna be 1 1 1 1 anyway... Does it matter?). + heightmap->Terra()->SetSolidColor(1u, + Ogre::Vector4(1.0, 1.0, 1.0, 1.0)); + // TODO(anyone): Retrieve datablock and make sure it's not blending + // like we do with Items (it should be impossible?) } } - itor.moveNext(); } + + // Remove the reference count on noBlend we created + hlmsManager->destroyBlendblock(noBlend); } ////////////////////////////////////////////////// void Ogre2ThermalCameraMaterialSwitcher::cameraPostRenderScene( Ogre::Camera * /*_cam*/) { + auto engine = Ogre2RenderEngine::Instance(); + Ogre::HlmsManager *hlmsManager = engine->OgreRoot()->getHlmsManager(); + + // Restore original blending to modified materials + for (const auto &[datablock, blendblock] : this->datablockMap) + { + datablock->setBlendblock(blendblock); + // Remove the reference we added (this won't actually destroy it) + hlmsManager->destroyBlendblock(blendblock); + } + this->datablockMap.clear(); + + // Remove the custom parameter. Why? If there are multiple cameras that + // use IORM_SOLID_COLOR (or any other mode), we want them to throw if + // that code forgot to call setCustomParameter. We may miss those errors + // if that code forgets to call but it was already carrying the value + // we set here. + // + // This consumes more performance but it's the price to pay for + // safety. + auto itor = this->scene->OgreSceneManager()->getMovableObjectIterator( + Ogre::ItemFactory::FACTORY_TYPE_NAME); + while (itor.hasMoreElements()) + { + Ogre::MovableObject *object = itor.peekNext(); + Ogre::Item *item = static_cast(object); + const size_t numSubItems = item->getNumSubItems(); + for (size_t i = 0; i < numSubItems; ++i) + { + Ogre::SubItem *subItem = item->getSubItem(i); + subItem->removeCustomParameter(1u); + subItem->removeCustomParameter(2u); + } + itor.moveNext(); + } + + // Restore Items with low level materials + for (auto subItemMat : this->materialMap) + { + subItemMat.first->setMaterial(subItemMat.second); + } + this->materialMap.clear(); + + // Remove the custom parameter (same reason as with Items) + auto heightmaps = this->scene->Heightmaps(); + for (auto h : heightmaps) + { + auto heightmap = h.lock(); + if (heightmap) + heightmap->Terra()->UnsetSolidColors(); + } + // restore item to use pbs hlms material - for (auto it : this->datablockMap) + for (auto it : this->itemDatablockMap) { Ogre::SubItem *subItem = it.first; subItem->setDatablock(it.second); } + + engine->SetIgnOgreRenderingMode(IORM_NORMAL); } ////////////////////////////////////////////////// diff --git a/ogre2/src/media/Hlms/Ignition/SolidColor/800.IgnSolidColor_piece_ps.any b/ogre2/src/media/Hlms/Ignition/SolidColor/800.IgnSolidColor_piece_ps.any index 31f807404..3a51e7f25 100644 --- a/ogre2/src/media/Hlms/Ignition/SolidColor/800.IgnSolidColor_piece_ps.any +++ b/ogre2/src/media/Hlms/Ignition/SolidColor/800.IgnSolidColor_piece_ps.any @@ -1,7 +1,29 @@ @property( ign_render_solid_color ) @piece( custom_ps_posExecution_solid_color ) - outPs_colour0 = inPs.ignSolidColour; + + @property( ign_render_solid_color_textured ) + if( inPs.ignSolidColour.w >= 0.0f ) + { + outPs_colour0 = inPs.ignSolidColour; + } + else + { + @property( diffuse_map ) + float4 diffuse = SampleDiffuse( textureMaps@value( diffuse_map_idx ), + samplerState@value(diffuse_map_sampler), + UV_DIFFUSE( inPs.uv@value(uv_diffuse).xy ), + texIndex_diffuseIdx ); + outPs_colour0 = float4( inPs.ignSolidColour.xyz, + -inPs.ignSolidColour.w - 0.5f ) * diffuse; + @else + outPs_colour0 = float4( inPs.ignSolidColour.xyz, + -inPs.ignSolidColour.w - 0.5f ); + @end + } + @else + outPs_colour0 = inPs.ignSolidColour; + @end @end @end diff --git a/ogre2/src/media/Hlms/Terra/ign/100.ign_CustomVs_piece_vs.any b/ogre2/src/media/Hlms/Terra/ign/100.ign_CustomVs_piece_vs.any index 5e423888f..989c2d9a4 100644 --- a/ogre2/src/media/Hlms/Terra/ign/100.ign_CustomVs_piece_vs.any +++ b/ogre2/src/media/Hlms/Terra/ign/100.ign_CustomVs_piece_vs.any @@ -1,4 +1,4 @@ -@piece( custom_vs_posExecution ) +@piece( custom_vs_posExecution_terra ) outVs.localHeight = worldPos.z - cellData.pos.y; @end diff --git a/ogre2/src/media/Hlms/Terra/ign/500.ign_Structs_piece_all.any b/ogre2/src/media/Hlms/Terra/ign/500.ign_Structs_piece_all.any index cf9b114d1..60ca19c92 100644 --- a/ogre2/src/media/Hlms/Terra/ign/500.ign_Structs_piece_all.any +++ b/ogre2/src/media/Hlms/Terra/ign/500.ign_Structs_piece_all.any @@ -6,6 +6,6 @@ float4 ignWeightsMaxHeight; @end -@piece( custom_VStoPS ) +@piece( custom_VStoPS_terra ) INTERPOLANT( float localHeight, @counter(texcoord) ); @end diff --git a/ogre2/src/terrain/Terra/include/Terra/Hlms/OgreHlmsTerra.h b/ogre2/src/terrain/Terra/include/Terra/Hlms/OgreHlmsTerra.h index 3ec887339..d4b2ceb40 100644 --- a/ogre2/src/terrain/Terra/include/Terra/Hlms/OgreHlmsTerra.h +++ b/ogre2/src/terrain/Terra/include/Terra/Hlms/OgreHlmsTerra.h @@ -56,6 +56,7 @@ namespace Ogre */ class HlmsTerra : public HlmsPbs { + protected: MovableObject const *mLastMovableObject; DescriptorSetSampler const *mTerraDescSetSampler; diff --git a/ogre2/src/terrain/Terra/include/Terra/Terra.h b/ogre2/src/terrain/Terra/include/Terra/Terra.h index ef8d17a89..45bbf9db8 100644 --- a/ogre2/src/terrain/Terra/include/Terra/Terra.h +++ b/ogre2/src/terrain/Terra/include/Terra/Terra.h @@ -107,6 +107,13 @@ namespace Ogre CompositorManager2 *m_compositorManager; Camera const *m_camera; + // IGN CUSTOMIZE BEGIN + /// See IORM_SOLID_COLOR and IORM_SOLID_THERMAL_COLOR_TEXTURED + Vector4 mSolidColor[2]; + /// See IORM_SOLID_COLOR and IORM_SOLID_THERMAL_COLOR_TEXTURED + bool mSolidColorSet[2]; + // IGN CUSTOMIZE END + /// Converts value from Y-up to whatever the user up vector is (see m_zUp) inline Vector3 fromYUp( Vector3 value ) const; /// Same as fromYUp, but preserves original sign. Needed when value is a scale @@ -170,6 +177,33 @@ namespace Ogre void setCustomSkirtMinHeight( const float skirtMinHeight ) { m_skirtSize = skirtMinHeight; } float getCustomSkirtMinHeight( void ) const { return m_skirtSize; } + // IGN CUSTOMIZE BEGIN + /// \brief See IORM_SOLID_COLOR and IORM_SOLID_THERMAL_COLOR_TEXTURED + /// Replaces renderable->setCustomRenderable(...) because + /// a Terrain may have many renderables but the color is the same + /// for all of them + /// \param[in] _idx must in range [1; 2] + /// \param[in] _idx _solidColor color to apply + void SetSolidColor(size_t _idx, const Vector4 _solidColor); + + /// \brief See IORM_SOLID_COLOR and IORM_SOLID_THERMAL_COLOR_TEXTURED + /// Retrieves the value set with SetSolidColor. Throws if unset. + /// \param[in] _idx must in range [1; 2] + /// \return Color value + Vector4 SolidColor(size_t _idx) const; + + /// \brief See IORM_SOLID_COLOR and IORM_SOLID_THERMAL_COLOR_TEXTURED + /// Checks whether a color has been set + /// \param[in] _idx must in range [1; 2] + /// \return True if color has been unset + bool HasSolidColor(size_t _idx) const; + + /// \brief See IORM_SOLID_COLOR and IORM_SOLID_THERMAL_COLOR_TEXTURED + /// Marks all SetSolidColor as unset so that SolidColor throws + /// if used again without setting. + void UnsetSolidColors(); + // IGN CUSTOMIZE END + /** Must be called every frame so we can check the camera's position (passed in the constructor) and update our visible batches (and LODs) We also update the shadow map if the light direction changed. diff --git a/ogre2/src/terrain/Terra/src/Terra.cpp b/ogre2/src/terrain/Terra/src/Terra.cpp index eaee00af0..a97e0a669 100644 --- a/ogre2/src/terrain/Terra/src/Terra.cpp +++ b/ogre2/src/terrain/Terra/src/Terra.cpp @@ -94,6 +94,13 @@ namespace Ogre m_camera( camera ), mHlmsTerraIndex( std::numeric_limits::max() ) { + // IGN CUSTOMIZE BEGIN + for(int i = 0; i < 2; ++i) + { + mSolidColor[i] = Vector4::ZERO; + mSolidColorSet[i] = false; + } + // IGN CUSTOMIZE END } //----------------------------------------------------------------------------------- Terra::~Terra() @@ -523,6 +530,43 @@ namespace Ogre m_collectedCells[0].clear(); } //----------------------------------------------------------------------------------- + // IGN CUSTOMIZE BEGIN + void Terra::SetSolidColor(size_t _idx, const Vector4 solidColor) + { + assert(_idx > 0u && _idx < 3u); + _idx = _idx - 1u; + + mSolidColor[_idx] = solidColor; + mSolidColorSet[_idx] = true; + } + //----------------------------------------------------------------------------------- + Vector4 Terra::SolidColor(size_t _idx) const + { + assert(_idx > 0u && _idx < 3u); + _idx = _idx - 1u; + + if (!mSolidColorSet[_idx]) + { + OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, + "Parameter at the given index was not found.", + "Terra::getSolidColors"); + } + return mSolidColor[_idx]; + } + //----------------------------------------------------------------------------------- + bool Terra::HasSolidColor(size_t _idx) const + { + assert(_idx > 0u && _idx < 3u); + return mSolidColorSet[_idx - 1u]; + } + //----------------------------------------------------------------------------------- + void Terra::UnsetSolidColors() + { + mSolidColorSet[0] = false; + mSolidColorSet[1] = false; + } + // IGN CUSTOMIZE END + //----------------------------------------------------------------------------------- void Terra::update( const Vector3 &lightDir, float lightEpsilon ) { const float lightCosAngleChange = Math::Clamp(