diff --git a/Changelog.md b/Changelog.md index a1b151afd..b1c981c39 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,12 @@ ## Ignition Rendering +### Ignition Rendering 5.X + +### Ignition Rendering 5.X.X + +1. Add ogre2 skybox support + * [Pull request #168](https://github.com/ignitionrobotics/ign-rendering/pull/168) + ### Ignition Rendering 4.X ### Ignition Rendering 4.X.X diff --git a/examples/ogre2_demo/GlutWindow.cc b/examples/ogre2_demo/GlutWindow.cc index 2412df075..9c314dedc 100644 --- a/examples/ogre2_demo/GlutWindow.cc +++ b/examples/ogre2_demo/GlutWindow.cc @@ -310,6 +310,14 @@ void keyboardCB(unsigned char _key, int, int) } } } + else if (_key == 's') + { + // toggle sky + for (ir::CameraPtr camera : g_cameras) + { + camera->Scene()->SetSkyEnabled(!camera->Scene()->SkyEnabled()); + } + } } ////////////////////////////////////////////////// @@ -345,6 +353,7 @@ void printUsage() std::cout << " TAB - Switch render engines " << std::endl; std::cout << " ESC - Exit " << std::endl; std::cout << " P - Toggle render pass " << std::endl; + std::cout << " S - Toggle skybox " << std::endl; std::cout << "===============================" << std::endl; } diff --git a/examples/ogre2_demo/Main.cc b/examples/ogre2_demo/Main.cc index f64cf63a9..28cbcd003 100644 --- a/examples/ogre2_demo/Main.cc +++ b/examples/ogre2_demo/Main.cc @@ -47,9 +47,12 @@ void buildScene(ScenePtr _scene) { // initialize _scene _scene->SetAmbientLight(0.2, 0.2, 0.2); - _scene->SetBackgroundColor(0.0, 0.0, 0.0); + _scene->SetBackgroundColor(0.2, 0.2, 0.2); VisualPtr root = _scene->RootVisual(); + // enable sky + _scene->SetSkyEnabled(true); + // create PBR material MaterialPtr matPBR = _scene->CreateMaterial(); std::string textureMap = common::joinPaths(RESOURCE_PATH, "pump_albedo.png"); @@ -64,6 +67,8 @@ void buildScene(ScenePtr _scene) matPBR->SetNormalMap(normalMap); matPBR->SetRoughnessMap(roughnessMap); matPBR->SetMetalnessMap(metalnessMap); + matPBR->SetMetalness(0.7); + matPBR->SetRoughness(0.3); matPBR->SetEnvironmentMap(environmentMap); // create mesh for PBR @@ -130,8 +135,12 @@ void buildScene(ScenePtr _scene) // create mirror material MaterialPtr mirrorMat = _scene->CreateMaterial(); - mirrorMat->SetRoughness(0.01); - mirrorMat->SetEnvironmentMap(environmentMap); + mirrorMat->SetDiffuse(1.0, 1.0, 1.0); + mirrorMat->SetRoughness(0.1); + mirrorMat->SetMetalness(0.9); + std::string skyEnvironmentMap = + common::joinPaths(RESOURCE_PATH, "skybox_lowres.dds"); + mirrorMat->SetEnvironmentMap(skyEnvironmentMap); // create box visual VisualPtr box = _scene->CreateVisual("box"); @@ -189,6 +198,10 @@ void buildScene(ScenePtr _scene) light1->SetSpecularColor(0.2, 0.2, 0.2); light1->SetLocalPosition(0, 3, 3); light1->SetDirection(1, -1, -1); + light1->SetAttenuationConstant(0.1); + light1->SetAttenuationLinear(0.001); + light1->SetAttenuationQuadratic(0.0001); + light1->SetFalloff(0.8); light1->SetCastShadows(true); root->AddChild(light1); @@ -197,6 +210,9 @@ void buildScene(ScenePtr _scene) light2->SetDiffuseColor(0.2, 0.4, 0.8); light2->SetSpecularColor(0.2, 0.2, 0.2); light2->SetLocalPosition(3, 0, 2); + light2->SetAttenuationConstant(0.1); + light2->SetAttenuationLinear(0.001); + light2->SetAttenuationQuadratic(0.0001); light2->SetCastShadows(true); root->AddChild(light2); @@ -206,6 +222,10 @@ void buildScene(ScenePtr _scene) light3->SetSpecularColor(0.2, 0.2, 0.2); light3->SetLocalPosition(0, -3, 3); light3->SetDirection(1, 1, -1); + light3->SetAttenuationConstant(0.1); + light3->SetAttenuationLinear(0.001); + light3->SetAttenuationQuadratic(0.0001); + light3->SetFalloff(0.8); light3->SetCastShadows(false); root->AddChild(light3); diff --git a/examples/ogre2_demo/media/skybox_lowres.dds b/examples/ogre2_demo/media/skybox_lowres.dds new file mode 100644 index 000000000..c6c8bd15d Binary files /dev/null and b/examples/ogre2_demo/media/skybox_lowres.dds differ diff --git a/include/ignition/rendering/Scene.hh b/include/ignition/rendering/Scene.hh index 8c2f20146..9cef42044 100644 --- a/include/ignition/rendering/Scene.hh +++ b/include/ignition/rendering/Scene.hh @@ -171,6 +171,16 @@ namespace ignition /// const std::array &_colors) public: virtual void RemoveGradientBackgroundColor() = 0; + /// \brief Get the scene background material + /// e.g. a material with skybox cubemap texture + /// \return Material of the background + public: virtual MaterialPtr BackgroundMaterial() const = 0; + + /// \brief Set the scene background material + /// e.g. a material with skybox cubemap texture + /// \param[in] _material Material to set the background to + public: virtual void SetBackgroundMaterial(MaterialPtr _material) = 0; + /// \brief Get the number of nodes managed by this scene. Note these /// nodes may not be directly or indirectly attached to the root node. /// \return The number of nodes managed by this scene @@ -954,6 +964,14 @@ namespace ignition public: virtual ParticleEmitterPtr CreateParticleEmitter( unsigned int _id, const std::string &_name) = 0; + /// \brief Enable sky in the scene. + /// \param[in] _enabled True to enable sky + public: virtual void SetSkyEnabled(bool _enabled) = 0; + + /// \brief Get whether the sky is enabled in the scene. + /// \return true to sky is enabled, false otherwise + public: virtual bool SkyEnabled() const = 0; + /// \brief Prepare scene for rendering. The scene will flushing any scene /// changes by traversing scene-graph, calling PreRender on all objects public: virtual void PreRender() = 0; diff --git a/include/ignition/rendering/base/BaseScene.hh b/include/ignition/rendering/base/BaseScene.hh index 3b4c8be67..d77e8671f 100644 --- a/include/ignition/rendering/base/BaseScene.hh +++ b/include/ignition/rendering/base/BaseScene.hh @@ -94,6 +94,13 @@ namespace ignition // Documentation inherited. public: virtual void RemoveGradientBackgroundColor() override; + // Documentation inherited. + public: virtual MaterialPtr BackgroundMaterial() const override; + + // Documentation inherited. + public: virtual void SetBackgroundMaterial(MaterialPtr _material) + override; + public: virtual unsigned int NodeCount() const override; public: virtual bool HasNode(ConstNodePtr _node) const override; @@ -432,6 +439,12 @@ namespace ignition public: virtual ParticleEmitterPtr CreateParticleEmitter( unsigned int _id, const std::string &_name) override; + // Documentation inherited. + public: virtual void SetSkyEnabled(bool _enabled) override; + + // Documentation inherited. + public: virtual bool SkyEnabled() const override; + public: virtual void PreRender() override; public: virtual void Clear() override; @@ -641,6 +654,9 @@ namespace ignition /// \brief Whether the scene has a gradient background. protected: bool isGradientBackgroundColor = false; + /// \brief Scene background material. + protected: MaterialPtr backgroundMaterial; + private: unsigned int nextObjectId; private: NodeStorePtr nodes; diff --git a/ogre2/include/ignition/rendering/ogre2/Ogre2Camera.hh b/ogre2/include/ignition/rendering/ogre2/Ogre2Camera.hh index 82637d035..0f891dcdc 100644 --- a/ogre2/include/ignition/rendering/ogre2/Ogre2Camera.hh +++ b/ogre2/include/ignition/rendering/ogre2/Ogre2Camera.hh @@ -73,6 +73,14 @@ namespace ignition public: virtual void SetBackgroundColor(const math::Color &_color); + /// \brief Get the background material of this camera + /// \return Background material of this camera + public: virtual MaterialPtr BackgroundMaterial() const; + + /// \brief Set the background material of this camera + /// \param[in] _material Material to set the background to + public: virtual void SetBackgroundMaterial(MaterialPtr _material); + // Documentation inherited. public: virtual void Render() override; diff --git a/ogre2/include/ignition/rendering/ogre2/Ogre2RenderTarget.hh b/ogre2/include/ignition/rendering/ogre2/Ogre2RenderTarget.hh index 183d2cff5..ee42a6cd7 100644 --- a/ogre2/include/ignition/rendering/ogre2/Ogre2RenderTarget.hh +++ b/ogre2/include/ignition/rendering/ogre2/Ogre2RenderTarget.hh @@ -79,6 +79,14 @@ namespace ignition /// \param[in] _color Color to set the background to public: virtual void SetBackgroundColor(math::Color _color); + /// \brief Set the background material of this camera + /// \param[in] _material Material to set the background to + public: virtual void SetBackgroundMaterial(MaterialPtr _material); + + /// \brief Get the background material of this camera + /// \return background material + public: virtual MaterialPtr BackgroundMaterial() const; + // Documentation inherited public: virtual void PreRender() override; @@ -112,6 +120,9 @@ namespace ignition /// \brief Update the background color protected: virtual void UpdateBackgroundColor(); + /// \brief Update the background material + protected: virtual void UpdateBackgroundMaterial(); + /// \brief Update the render pass chain protected: virtual void UpdateRenderPassChain(); @@ -171,6 +182,9 @@ namespace ignition /// \brief Stores the background color of the render target protected: Ogre::ColourValue ogreBackgroundColor; + /// \brief Background material of the render target + protected: MaterialPtr backgroundMaterial; + /// \brief a material used by for the render target protected: MaterialPtr material; @@ -180,6 +194,10 @@ namespace ignition /// \brief Flag to indicate if the render target color has changed protected: bool colorDirty = true; + /// \brief Flag to indicate if the render target background material has + /// changed + protected: bool backgroundMaterialDirty = false; + /// \brief Anti-aliasing level protected: unsigned int antiAliasing = 4; diff --git a/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh b/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh index ec5743faf..f676c6190 100644 --- a/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh +++ b/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh @@ -77,6 +77,12 @@ namespace ignition // Documentation inherited public: virtual void Destroy() override; + // Documentation inherited + public: virtual void SetSkyEnabled(bool _enabled) override; + + // Documentation inherited + public: virtual bool SkyEnabled() const override; + /// \brief Get a pointer to the ogre scene manager /// \return Pointer to the ogre scene manager public: virtual Ogre::SceneManager *OgreSceneManager() const; diff --git a/ogre2/src/Ogre2Camera.cc b/ogre2/src/Ogre2Camera.cc index e35304c94..2d97eafaa 100644 --- a/ogre2/src/Ogre2Camera.cc +++ b/ogre2/src/Ogre2Camera.cc @@ -110,6 +110,18 @@ void Ogre2Camera::SetBackgroundColor(const math::Color &_color) this->renderTexture->SetBackgroundColor(_color); } +////////////////////////////////////////////////// +MaterialPtr Ogre2Camera::BackgroundMaterial() const +{ + return this->renderTexture->BackgroundMaterial(); +} + +////////////////////////////////////////////////// +void Ogre2Camera::SetBackgroundMaterial(MaterialPtr _material) +{ + this->renderTexture->SetBackgroundMaterial(_material); +} + ////////////////////////////////////////////////// void Ogre2Camera::SetMaterial(const MaterialPtr &_material) { diff --git a/ogre2/src/Ogre2DepthCamera.cc b/ogre2/src/Ogre2DepthCamera.cc index 0cff8792d..07123a782 100644 --- a/ogre2/src/Ogre2DepthCamera.cc +++ b/ogre2/src/Ogre2DepthCamera.cc @@ -80,6 +80,9 @@ class ignition::rendering::Ogre2DepthCameraPrivate public: ignition::common::EventT newDepthFrame; + + /// \brief Name of sky box material + public: const std::string kSkyboxMaterialName = "SkyBox"; }; using namespace ignition; @@ -283,6 +286,33 @@ void Ogre2DepthCamera::CreateDepthTexture() psParams->setNamedConstant("tolerance", static_cast(1e-6)); + // create background material is specified + MaterialPtr backgroundMaterial = this->Scene()->BackgroundMaterial(); + bool validBackground = backgroundMaterial && + !backgroundMaterial->EnvironmentMap().empty(); + + if (validBackground) + { + Ogre::MaterialManager &matManager = Ogre::MaterialManager::getSingleton(); + std::string skyMatName = this->dataPtr->kSkyboxMaterialName + "_" + + this->Name(); + auto mat = matManager.getByName(skyMatName); + if (!mat) + { + auto skyboxMat = matManager.getByName(this->dataPtr->kSkyboxMaterialName); + if (!skyboxMat) + { + ignerr << "Unable to find skybox material" << std::endl; + return; + } + mat = skyboxMat->clone(skyMatName); + } + Ogre::TextureUnitState *texUnit = + mat->getTechnique(0u)->getPass(0u)->getTextureUnitState(0u); + texUnit->setTextureName(backgroundMaterial->EnvironmentMap(), + Ogre::TEX_TYPE_CUBE_MAP); + } + // Create depth camera compositor auto engine = Ogre2RenderEngine::Instance(); auto ogreRoot = engine->OgreRoot(); @@ -379,7 +409,11 @@ void Ogre2DepthCamera::CreateDepthTexture() nodeDef->setNumTargetPass(2); Ogre::CompositorTargetDef *colorTargetDef = nodeDef->addTargetPass("colorTexture"); - colorTargetDef->setNumPasses(2); + + if (validBackground) + colorTargetDef->setNumPasses(3); + else + colorTargetDef->setNumPasses(2); { // clear pass Ogre::CompositorPassClearDef *passClear = @@ -387,6 +421,19 @@ void Ogre2DepthCamera::CreateDepthTexture() colorTargetDef->addPass(Ogre::PASS_CLEAR)); passClear->mColourValue = Ogre::ColourValue( Ogre2Conversions::Convert(this->Scene()->BackgroundColor())); + + if (validBackground) + { + // quad pass + Ogre::CompositorPassQuadDef *passQuad = + static_cast( + colorTargetDef->addPass(Ogre::PASS_QUAD)); + passQuad->mMaterialName = this->dataPtr->kSkyboxMaterialName + "_" + + this->Name(); + passQuad->mFrustumCorners = + Ogre::CompositorPassQuadDef::CAMERA_DIRECTION; + } + // scene pass Ogre::CompositorPassSceneDef *passScene = static_cast( diff --git a/ogre2/src/Ogre2RenderEngine.cc b/ogre2/src/Ogre2RenderEngine.cc index b0543e17b..a4797f61a 100644 --- a/ogre2/src/Ogre2RenderEngine.cc +++ b/ogre2/src/Ogre2RenderEngine.cc @@ -561,6 +561,8 @@ void Ogre2RenderEngine::CreateResources() std::make_pair(p + "/materials/programs", "General")); archNames.push_back( std::make_pair(p + "/materials/scripts", "General")); + archNames.push_back( + std::make_pair(p + "/materials/textures", "General")); for (auto aiter = archNames.begin(); aiter != archNames.end(); ++aiter) { diff --git a/ogre2/src/Ogre2RenderTarget.cc b/ogre2/src/Ogre2RenderTarget.cc index 5f1c255f2..2f6825d00 100644 --- a/ogre2/src/Ogre2RenderTarget.cc +++ b/ogre2/src/Ogre2RenderTarget.cc @@ -62,7 +62,7 @@ class Ogre2RenderTargetCompositorListener : IGN_ASSERT(scenePass != nullptr, "Unable to get scene pass"); Ogre::Viewport *vp = scenePass->getViewport(); // make sure we do not alter the reserved visibility flags - uint32_t f = ogreRenderTarget->VisibilityMask() | + uint32_t f = this->ogreRenderTarget->VisibilityMask() | ~Ogre::VisibilityFlags::RESERVED_VISIBILITY_FLAGS; // apply the new visibility mask uint32_t flags = f & vp->getVisibilityMask(); @@ -82,6 +82,18 @@ class ignition::rendering::Ogre2RenderTargetPrivate { /// \brief Listener for chaning compositor pass properties public: Ogre2RenderTargetCompositorListener *rtListener = nullptr; + + /// \brief Name of sky box material + public: const std::string kSkyboxMaterialName = "SkyBox"; + + /// \brief Name of base rendering compositor node + public: const std::string kBaseNodeName = "PbsMaterialsRenderingNode"; + + /// \brief Name of final rendering compositor node + public: const std::string kFinalNodeName = "FinalComposition"; + + /// \brief Name of shadow compositor node + public: const std::string kShadowNodeName = "PbsMaterialsShadowNode"; }; using namespace ignition; @@ -110,12 +122,145 @@ Ogre2RenderTarget::~Ogre2RenderTarget() ////////////////////////////////////////////////// void Ogre2RenderTarget::BuildCompositor() { - this->UpdateShadowNode(); - auto engine = Ogre2RenderEngine::Instance(); auto ogreRoot = engine->OgreRoot(); Ogre::CompositorManager2 *ogreCompMgr = ogreRoot->getCompositorManager2(); + this->UpdateBackgroundMaterial(); + + bool validBackground = this->backgroundMaterial && + !this->backgroundMaterial->EnvironmentMap().empty(); + + // The function build a similar compositor as the one defined in + // ogre2/media/2.0/scripts/Compositors/PbsMaterials.compositor + // but supports an extra quad pass to render the skybox cubemap if + // sky is enabled. + // todo(anyone) Note the definition programmatically created here + // replaces the one defined in the script so it maybe safe to remove the + // PbsMaterials.compositor file + std::string wsDefName = "PbsMaterialWorkspace_" + this->Name(); + this->ogreCompositorWorkspaceDefName = wsDefName; + if (!ogreCompMgr->hasWorkspaceDefinition(wsDefName)) + { + // PbsMaterialsRenderingNode + std::string nodeDefName = wsDefName + "/" + this->dataPtr->kBaseNodeName; + Ogre::CompositorNodeDef *nodeDef = + ogreCompMgr->addNodeDefinition(nodeDefName); + + // Input texture + Ogre::TextureDefinitionBase::TextureDefinition *rt0TexDef = + nodeDef->addTextureDefinition("rt0"); + rt0TexDef->textureType = Ogre::TEX_TYPE_2D; + rt0TexDef->width = 0; + rt0TexDef->height = 0; + rt0TexDef->depth = 1; + rt0TexDef->numMipmaps = 0; + rt0TexDef->widthFactor = 1; + rt0TexDef->heightFactor = 1; + rt0TexDef->formatList = {Ogre::PF_R8G8B8}; + rt0TexDef->fsaa = 0; + rt0TexDef->uav = false; + rt0TexDef->automipmaps = false; + rt0TexDef->hwGammaWrite = Ogre::TextureDefinitionBase::BoolTrue; + rt0TexDef->depthBufferId = Ogre::DepthBuffer::POOL_DEFAULT; + rt0TexDef->depthBufferFormat = Ogre::PF_UNKNOWN; + rt0TexDef->fsaaExplicitResolve = false; + + Ogre::TextureDefinitionBase::TextureDefinition *rt1TexDef = + nodeDef->addTextureDefinition("rt1"); + rt1TexDef->textureType = Ogre::TEX_TYPE_2D; + rt1TexDef->width = 0; + rt1TexDef->height = 0; + rt1TexDef->depth = 1; + rt1TexDef->numMipmaps = 0; + rt1TexDef->widthFactor = 1; + rt1TexDef->heightFactor = 1; + rt1TexDef->formatList = {Ogre::PF_R8G8B8}; + rt1TexDef->fsaa = 0; + rt1TexDef->uav = false; + rt1TexDef->automipmaps = false; + rt1TexDef->hwGammaWrite = Ogre::TextureDefinitionBase::BoolTrue; + rt1TexDef->depthBufferId = Ogre::DepthBuffer::POOL_DEFAULT; + rt1TexDef->depthBufferFormat = Ogre::PF_UNKNOWN; + rt1TexDef->fsaaExplicitResolve = false; + + nodeDef->setNumTargetPass(2); + Ogre::CompositorTargetDef *rt0TargetDef = + nodeDef->addTargetPass("rt0"); + + if (validBackground) + rt0TargetDef->setNumPasses(3); + else + rt0TargetDef->setNumPasses(2); + { + // clear pass + Ogre::CompositorPassClearDef *passClear = + static_cast( + rt0TargetDef->addPass(Ogre::PASS_CLEAR)); + passClear->mColourValue = this->ogreBackgroundColor; + + if (validBackground) + { + // quad pass + Ogre::CompositorPassQuadDef *passQuad = + static_cast( + rt0TargetDef->addPass(Ogre::PASS_QUAD)); + passQuad->mMaterialName = this->dataPtr->kSkyboxMaterialName + "_" + + this->Name(); + passQuad->mFrustumCorners = + Ogre::CompositorPassQuadDef::CAMERA_DIRECTION; + } + + // scene pass + Ogre::CompositorPassSceneDef *passScene = + static_cast( + rt0TargetDef->addPass(Ogre::PASS_SCENE)); + passScene->mShadowNode = this->dataPtr->kShadowNodeName; + passScene->mIncludeOverlays = true; + } + + nodeDef->mapOutputChannel(0, "rt0"); + nodeDef->mapOutputChannel(1, "rt1"); + + // Final Composition + std::string finalNodeDefName = wsDefName + "/FinalComposition"; + Ogre::CompositorNodeDef *finalNodeDef = + ogreCompMgr->addNodeDefinition(finalNodeDefName); + finalNodeDef->addTextureSourceName("rt_output", 0, + Ogre::TextureDefinitionBase::TEXTURE_INPUT); + finalNodeDef->addTextureSourceName("rtN", 1, + Ogre::TextureDefinitionBase::TEXTURE_INPUT); + + finalNodeDef->setNumTargetPass(2); + Ogre::CompositorTargetDef *outTargetDef = + finalNodeDef->addTargetPass("rt_output"); + outTargetDef->setNumPasses(2); + { + // quad pass + Ogre::CompositorPassQuadDef *passQuad = + static_cast( + outTargetDef->addPass(Ogre::PASS_QUAD)); + passQuad->mMaterialName = "Ogre/Copy/4xFP32"; + passQuad->addQuadTextureSource(0, "rtN", 0); + + // scene pass + Ogre::CompositorPassSceneDef *passScene = + static_cast( + outTargetDef->addPass(Ogre::PASS_SCENE)); + passScene->mUpdateLodLists = false; + passScene->mIncludeOverlays = true; + passScene->mFirstRQ = 254; + passScene->mLastRQ = 255; + } + + Ogre::CompositorWorkspaceDef *workDef = + ogreCompMgr->addWorkspaceDefinition(wsDefName); + workDef->connectExternal(0, finalNodeDefName, 0); + workDef->connect(nodeDefName, 0, finalNodeDefName, 1); + } + + this->UpdateShadowNode(); + this->ogreCompositorWorkspace = ogreCompMgr->addWorkspace(this->scene->OgreSceneManager(), this->RenderTarget(), this->ogreCamera, @@ -136,6 +281,12 @@ void Ogre2RenderTarget::DestroyCompositor() Ogre::CompositorManager2 *ogreCompMgr = ogreRoot->getCompositorManager2(); this->ogreCompositorWorkspace->setListener(nullptr); ogreCompMgr->removeWorkspace(this->ogreCompositorWorkspace); + ogreCompMgr->removeWorkspaceDefinition(this->ogreCompositorWorkspaceDefName); + ogreCompMgr->removeNodeDefinition(this->ogreCompositorWorkspaceDefName + + "/" + this->dataPtr->kBaseNodeName); + ogreCompMgr->removeNodeDefinition(this->ogreCompositorWorkspaceDefName + + "/" + this->dataPtr->kFinalNodeName); + this->ogreCompositorWorkspace = nullptr; delete this->dataPtr->rtListener; this->dataPtr->rtListener = nullptr; @@ -192,6 +343,20 @@ void Ogre2RenderTarget::SetBackgroundColor(math::Color _color) this->colorDirty = true; } +////////////////////////////////////////////////// +void Ogre2RenderTarget::SetBackgroundMaterial(MaterialPtr _material) +{ + this->backgroundMaterial = _material; + this->backgroundMaterialDirty = true; + this->targetDirty = true; +} + +////////////////////////////////////////////////// +MaterialPtr Ogre2RenderTarget::BackgroundMaterial() const +{ + return this->backgroundMaterial; +} + ////////////////////////////////////////////////// unsigned int Ogre2RenderTarget::AntiAliasing() const { @@ -285,6 +450,40 @@ void Ogre2RenderTarget::UpdateBackgroundColor() } } +////////////////////////////////////////////////// +void Ogre2RenderTarget::UpdateBackgroundMaterial() +{ + if (!this->backgroundMaterialDirty) + return; + + bool validBackground = this->backgroundMaterial && + !this->backgroundMaterial->EnvironmentMap().empty(); + + if (validBackground) + { + Ogre::MaterialManager &matManager = Ogre::MaterialManager::getSingleton(); + std::string skyMatName = this->dataPtr->kSkyboxMaterialName + "_" + + this->Name(); + auto mat = matManager.getByName(skyMatName); + if (!mat) + { + auto skyboxMat = matManager.getByName(this->dataPtr->kSkyboxMaterialName); + if (!skyboxMat) + { + ignerr << "Unable to find skybox material" << std::endl; + return; + } + mat = skyboxMat->clone(skyMatName); + } + Ogre::TextureUnitState *texUnit = + mat->getTechnique(0u)->getPass(0u)->getTextureUnitState(0u); + texUnit->setTextureName(this->backgroundMaterial->EnvironmentMap(), + Ogre::TEX_TYPE_CUBE_MAP); + } + + this->backgroundMaterialDirty = false; +} + ////////////////////////////////////////////////// void Ogre2RenderTarget::UpdateRenderPassChain() { @@ -336,9 +535,11 @@ void Ogre2RenderTarget::UpdateRenderPassChain() // The first node and final node in the workspace are defined in // PbsMaterials.compositor // the first node is the base scene pass node - std::string outNodeDefName = "PbsMaterialsRenderingNode"; + std::string outNodeDefName = this->ogreCompositorWorkspaceDefName + + "/" + this->dataPtr->kBaseNodeName; // the final compositor node - std::string finalNodeDefName = "FinalComposition"; + std::string finalNodeDefName = ogreCompositorWorkspaceDefName + + "/" + this->dataPtr->kFinalNodeName; std::string inNodeDefName; // if new nodes need to be added then clear everything, @@ -474,7 +675,7 @@ void Ogre2RenderTarget::UpdateShadowNode() } } - std::string shadowNodeDefName = "PbsMaterialsShadowNode"; + std::string shadowNodeDefName = this->dataPtr->kShadowNodeName; if (compositorManager->hasShadowNodeDefinition(shadowNodeDefName)) compositorManager->removeShadowNodeDefinition(shadowNodeDefName); diff --git a/ogre2/src/Ogre2Scene.cc b/ogre2/src/Ogre2Scene.cc index 8f389bb0e..752b8ab9e 100644 --- a/ogre2/src/Ogre2Scene.cc +++ b/ogre2/src/Ogre2Scene.cc @@ -48,6 +48,9 @@ class ignition::rendering::Ogre2ScenePrivate { /// \brief Flag to indicate if shadows need to be updated public: bool shadowsDirty = true; + + /// \brief Flag to indicate if sky is enabled or not + public: bool skyEnabled = false; }; using namespace ignition; @@ -247,6 +250,8 @@ CameraPtr Ogre2Scene::CreateCameraImpl(unsigned int _id, Ogre2CameraPtr camera(new Ogre2Camera); bool result = this->InitObject(camera, _id, _name); camera->SetBackgroundColor(this->backgroundColor); + if (this->backgroundMaterial) + camera->SetBackgroundMaterial(this->backgroundMaterial); return (result) ? camera : nullptr; } @@ -567,3 +572,40 @@ bool Ogre2Scene::ShadowsDirty() const { return this->dataPtr->shadowsDirty; } + +////////////////////////////////////////////////// +void Ogre2Scene::SetSkyEnabled(bool _enabled) +{ + MaterialPtr skyboxMat; + if (_enabled) + { + // get skybox material + std::string skyboxMatName = "Default/skybox"; + skyboxMat = this->Material(skyboxMatName); + if (!skyboxMat) + { + // ogre2 should be able to find this texture as resource search + // paths are already set up in Ogre2RenderEngine.cc + std::string skyboxEnvMap = "skybox.dds"; + skyboxMat = this->CreateMaterial(skyboxMatName); + skyboxMat->SetEnvironmentMap(skyboxEnvMap); + } + } + this->SetBackgroundMaterial(skyboxMat); + for (unsigned int i = 0; i < this->Sensors()->Size(); ++i) + { + auto sensor = this->Sensors()->GetByIndex(i); + auto camera = std::dynamic_pointer_cast(sensor); + if (camera) + { + camera->SetBackgroundMaterial(skyboxMat); + } + } + this->dataPtr->skyEnabled = _enabled; +} + +////////////////////////////////////////////////// +bool Ogre2Scene::SkyEnabled() const +{ + return this->dataPtr->skyEnabled; +} diff --git a/ogre2/src/media/materials/programs/skybox_fs.glsl b/ogre2/src/media/materials/programs/skybox_fs.glsl new file mode 100644 index 000000000..b44d98a8b --- /dev/null +++ b/ogre2/src/media/materials/programs/skybox_fs.glsl @@ -0,0 +1,46 @@ +// The code in this file is adapted from OGRE Samples. The OGRE's license and +// copyright header is copied below. + +/* +----------------------------------------------------------------------------- +OGRE (www.ogre3d.org) is made available under the MIT License. + +Copyright (c) 2000-2013 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + + +#version 330 + +uniform samplerCube skyCubemap; + +in block +{ + vec3 cameraDir; +} inPs; + +out vec3 fragColour; + +void main() +{ + // Cubemaps are left-handed + fragColour = texture( skyCubemap, vec3( inPs.cameraDir.xy, -inPs.cameraDir.z ) ).xyz; +} diff --git a/ogre2/src/media/materials/programs/skybox_vs.glsl b/ogre2/src/media/materials/programs/skybox_vs.glsl new file mode 100644 index 000000000..c0a130e97 --- /dev/null +++ b/ogre2/src/media/materials/programs/skybox_vs.glsl @@ -0,0 +1,52 @@ +// The code in this file is adapted from OGRE Samples. The OGRE's license and +// copyright header is copied below. + +/* +----------------------------------------------------------------------------- +OGRE (www.ogre3d.org) is made available under the MIT License. + +Copyright (c) 2000-2013 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + + +#version 330 + +in vec4 vertex; +in vec3 normal; +uniform mat4 worldViewProj; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +out block +{ + vec3 cameraDir; +} outVs; + +void main() +{ + gl_Position.xyw = (worldViewProj * vertex).xyw; + gl_Position.z = 0.0; + outVs.cameraDir.xyz = normal.xyz; +} diff --git a/ogre2/src/media/materials/scripts/skybox.material b/ogre2/src/media/materials/scripts/skybox.material new file mode 100644 index 000000000..22aebf8f7 --- /dev/null +++ b/ogre2/src/media/materials/scripts/skybox.material @@ -0,0 +1,79 @@ +// The code in this file is adapted from OGRE Samples. The OGRE's license and +// copyright header is copied below. + +/* +----------------------------------------------------------------------------- +OGRE (www.ogre3d.org) is made available under the MIT License. + +Copyright (c) 2000-2013 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + + +// GLSL shaders +vertex_program SkyBox_vs glsl +{ + source skybox_vs.glsl + default_params + { + param_named_auto worldViewProj worldviewproj_matrix + } +} + +fragment_program SkyBox_fs glsl +{ + source skybox_fs.glsl + default_params + { + param_named skyCubemap int 0 + } +} + +// Material definition +material SkyBox +{ + technique + { + pass + { + depth_check on + depth_write off + + cull_hardware none + + vertex_program_ref SkyBox_vs + { + } + + fragment_program_ref SkyBox_fs + { + } + + texture_unit + { + texture skybox.dds cubic gamma + filtering trilinear + tex_address_mode clamp + } + } + } +} + diff --git a/ogre2/src/media/materials/textures/skybox.dds b/ogre2/src/media/materials/textures/skybox.dds new file mode 100644 index 000000000..d38037846 Binary files /dev/null and b/ogre2/src/media/materials/textures/skybox.dds differ diff --git a/src/Camera_TEST.cc b/src/Camera_TEST.cc index 0432ed609..60250a1c9 100644 --- a/src/Camera_TEST.cc +++ b/src/Camera_TEST.cc @@ -337,6 +337,7 @@ TEST_P(CameraTest, AddRemoveRenderPass) { AddRemoveRenderPass(GetParam()); } + ///////////////////////////////////////////////// TEST_P(CameraTest, VisibilityMask) { diff --git a/src/RenderTarget_TEST.cc b/src/RenderTarget_TEST.cc index 612e0a960..a314583ad 100644 --- a/src/RenderTarget_TEST.cc +++ b/src/RenderTarget_TEST.cc @@ -179,6 +179,10 @@ void RenderTargetTest::AddRemoveRenderPass(const std::string &_renderEngine) renderTexture->RemoveRenderPass(pass1); EXPECT_EQ(1u, renderTexture->RenderPassCount()); EXPECT_EQ(pass2, renderTexture->RenderPassByIndex(0u)); + + // Clean up + engine->DestroyScene(scene); + rendering::unloadEngine(engine->Name()); } ///////////////////////////////////////////////// @@ -199,7 +203,6 @@ TEST_P(RenderTargetTest, AddRemoveRenderPass) AddRemoveRenderPass(GetParam()); } - INSTANTIATE_TEST_CASE_P(RenderTarget, RenderTargetTest, RENDER_ENGINE_VALUES, ignition::rendering::PrintToStringParam()); diff --git a/src/Scene_TEST.cc b/src/Scene_TEST.cc index aa1879ab1..aa0b6e431 100644 --- a/src/Scene_TEST.cc +++ b/src/Scene_TEST.cc @@ -31,6 +31,12 @@ using namespace rendering; class SceneTest : public testing::Test, public testing::WithParamInterface { + // Documentation inherited + public: void SetUp() override + { + ignition::common::Console::SetVerbosity(4); + } + public: void Scene(const std::string &_renderEngine); public: void Nodes(const std::string &_renderEngine); @@ -48,6 +54,12 @@ class SceneTest : public testing::Test, /// \brief Test setting and getting Time public: void Time(const std::string &_renderEngine); + + /// \brief Test background material + public: void BackgroundMaterial(const std::string &_renderEngine); + + /// \brief Test enablng sky + public: void Sky(const std::string &_renderEngine); }; ///////////////////////////////////////////////// @@ -679,6 +691,85 @@ void SceneTest::Time(const std::string &_renderEngine) std::chrono::milliseconds(123); scene->SetTime(duration); EXPECT_EQ(duration, scene->Time()); + + // Clean up + engine->DestroyScene(scene); + rendering::unloadEngine(engine->Name()); +} + +///////////////////////////////////////////////// +void SceneTest::BackgroundMaterial(const std::string &_renderEngine) +{ + auto engine = rendering::engine(_renderEngine); + if (!engine) + { + igndbg << "Engine '" << _renderEngine << "' is not supported" << std::endl; + return; + } + + auto scene = engine->CreateScene("scene"); + ASSERT_NE(nullptr, scene); + + EXPECT_EQ(nullptr, scene->BackgroundMaterial()); + + rendering::MaterialPtr mat = scene->CreateMaterial("test_mat"); + scene->SetBackgroundMaterial(mat); + EXPECT_EQ(mat, scene->BackgroundMaterial()); + + scene->SetBackgroundMaterial(nullptr); + EXPECT_EQ(nullptr, scene->BackgroundMaterial()); + + // Clean up + engine->DestroyScene(scene); + rendering::unloadEngine(engine->Name()); +} + +///////////////////////////////////////////////// +void SceneTest::Sky(const std::string &_renderEngine) +{ + auto engine = rendering::engine(_renderEngine); + if (!engine) + { + igndbg << "Engine '" << _renderEngine << "' is not supported" << std::endl; + return; + } + + if (_renderEngine != "ogre2") + { + igndbg << "Sky not supported yet in rendering engine: " + << _renderEngine << std::endl; + return; + } + + auto scene = engine->CreateScene("scene"); + ASSERT_NE(nullptr, scene); + + EXPECT_FALSE(scene->SkyEnabled()); + + scene->SetSkyEnabled(false); + EXPECT_FALSE(scene->SkyEnabled()); + + scene->SetSkyEnabled(true); + EXPECT_TRUE(scene->SkyEnabled()); + + scene->SetSkyEnabled(false); + EXPECT_FALSE(scene->SkyEnabled()); + + // set background material and verify sky remains disabled + rendering::MaterialPtr mat = scene->CreateMaterial("test_mat"); + scene->SetBackgroundMaterial(mat); + EXPECT_EQ(mat, scene->BackgroundMaterial()); + EXPECT_FALSE(scene->SkyEnabled()); + + // enable sky and verify it is not affected by background material + scene->SetSkyEnabled(true); + EXPECT_TRUE(scene->SkyEnabled()); + scene->SetBackgroundMaterial(nullptr); + EXPECT_TRUE(scene->SkyEnabled()); + + // Clean up + engine->DestroyScene(scene); + rendering::unloadEngine(engine->Name()); } ///////////////////////////////////////////////// @@ -723,6 +814,18 @@ TEST_P(SceneTest, Time) Time(GetParam()); } +///////////////////////////////////////////////// +TEST_P(SceneTest, BackgroundMaterial) +{ + BackgroundMaterial(GetParam()); +} + +///////////////////////////////////////////////// +TEST_P(SceneTest, Sky) +{ + Sky(GetParam()); +} + INSTANTIATE_TEST_CASE_P(Scene, SceneTest, RENDER_ENGINE_VALUES, ignition::rendering::PrintToStringParam()); diff --git a/src/base/BaseScene.cc b/src/base/BaseScene.cc index f35f5546d..9f86a7845 100644 --- a/src/base/BaseScene.cc +++ b/src/base/BaseScene.cc @@ -237,6 +237,18 @@ void BaseScene::RemoveGradientBackgroundColor() this->isGradientBackgroundColor = false; } +////////////////////////////////////////////////// +MaterialPtr BaseScene::BackgroundMaterial() const +{ + return this->backgroundMaterial; +} + +////////////////////////////////////////////////// +void BaseScene::SetBackgroundMaterial(MaterialPtr _material) +{ + this->backgroundMaterial = _material; +} + ////////////////////////////////////////////////// unsigned int BaseScene::NodeCount() const { @@ -1151,6 +1163,21 @@ ParticleEmitterPtr BaseScene::CreateParticleEmitter(unsigned int _id, return (result) ? visual : nullptr; } +////////////////////////////////////////////////// +void BaseScene::SetSkyEnabled(bool) // NOLINT(readability/casting) +{ + // no op, let derived class implement this. + ignerr << "Sky not supported by: " + << this->Engine()->Name() << std::endl; +} + +////////////////////////////////////////////////// +bool BaseScene::SkyEnabled() const +{ + return false; +} + + ////////////////////////////////////////////////// void BaseScene::PreRender() { diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 49ec236f4..12316694d 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -7,6 +7,7 @@ set(tests render_pass.cc shadows.cc scene.cc + sky.cc thermal_camera.cc lidar_visual.cc ) diff --git a/test/integration/sky.cc b/test/integration/sky.cc new file mode 100644 index 000000000..fb622a5c4 --- /dev/null +++ b/test/integration/sky.cc @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020 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 + +#include +#include + +#include "test_config.h" // NOLINT(build/include) + +#include "ignition/rendering/Camera.hh" +#include "ignition/rendering/Image.hh" +#include "ignition/rendering/PixelFormat.hh" +#include "ignition/rendering/RenderEngine.hh" +#include "ignition/rendering/RenderingIface.hh" +#include "ignition/rendering/Scene.hh" + +using namespace ignition; +using namespace rendering; + +class SkyTest: public testing::Test, + public testing::WithParamInterface +{ + // Documentation inherited + public: void SetUp() override + { + ignition::common::Console::SetVerbosity(4); + } + + // Test and verify sky is created + public: void Sky(const std::string &_renderEngine); +}; + +///////////////////////////////////////////////// +void SkyTest::Sky(const std::string &_renderEngine) +{ + // create and populate scene + RenderEngine *engine = rendering::engine(_renderEngine); + if (!engine) + { + igndbg << "Engine '" << _renderEngine + << "' is not supported" << std::endl; + return; + } + + if (_renderEngine != "ogre2") + { + igndbg << "Sky not supported yet in rendering engine: " + << _renderEngine << std::endl; + return; + } + + // add resources in build dir + engine->AddResourcePath( + common::joinPaths(std::string(PROJECT_BUILD_PATH), "src")); + + ScenePtr scene = engine->CreateScene("scene"); + ASSERT_TRUE(scene != nullptr); + scene->SetAmbientLight(0.3, 0.3, 0.3); + + scene->SetBackgroundColor(1.0, 0.0, 0.0); + + VisualPtr root = scene->RootVisual(); + + // create camera + CameraPtr camera = scene->CreateCamera(); + ASSERT_TRUE(camera != nullptr); + camera->SetImageWidth(100); + camera->SetImageHeight(100); + // look up into the sky + camera->SetLocalRotation(math::Quaterniond(0, -IGN_PI/2.0, 0)); + root->AddChild(camera); + + // capture original image with red background + Image image = camera->CreateImage(); + camera->Capture(image); + + // Enable sky + scene->SetSkyEnabled(true); + + // capture image with sky enabled + Image imageSky = camera->CreateImage(); + camera->Capture(imageSky); + + // Compare image pixels + unsigned char *data = image.Data(); + unsigned char *dataSky = imageSky.Data(); + unsigned int height = camera->ImageHeight(); + unsigned int width = camera->ImageWidth(); + unsigned int channelCount = PixelUtil::ChannelCount(camera->ImageFormat()); + unsigned int step = width * channelCount; + + unsigned int rSum = 0u; + unsigned int gSum = 0u; + unsigned int bSum = 0u; + unsigned int rSkySum = 0u; + unsigned int gSkySum = 0u; + unsigned int bSkySum = 0u; + + + for (unsigned int i = 0; i < height; ++i) + { + for (unsigned int j = 0; j < step; j += channelCount) + { + unsigned int idx = i * step + j; + rSum += data[idx]; + gSum += data[idx + 1]; + bSum += data[idx + 2]; + + rSkySum += dataSky[idx]; + gSkySum += dataSky[idx + 1]; + bSkySum += dataSky[idx + 2]; + } + } + + // sky disabled - red background + EXPECT_GT(rSum, 0u); + EXPECT_EQ(0u, gSum); + EXPECT_EQ(0u, bSum); + + // sky enabled - blue should be the dominant color + EXPECT_GT(rSkySum, 0u); + EXPECT_GT(gSkySum, 0u); + EXPECT_GT(bSkySum, 0u); + EXPECT_GT(bSkySum, gSkySum); + EXPECT_GT(bSkySum, rSkySum); + + // Clean up + engine->DestroyScene(scene); + rendering::unloadEngine(engine->Name()); +} + +///////////////////////////////////////////////// +TEST_P(SkyTest, Sky) +{ + Sky(GetParam()); +} + +INSTANTIATE_TEST_CASE_P(Sky, SkyTest, + RENDER_ENGINE_VALUES, + ignition::rendering::PrintToStringParam()); + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}