Combining Fresnel Water and PLSM2 in Ogre

Here is a short code snippet that should show you how to combine the great water demo that ships with Ogre and the terrain features of PLSM2 (or any other scene manager, especially the terrain ones). The code also implements custom clipmaps, so that there is no need for 2 lists with objects below and above the water, as they are all clipped automatically. As all parts of BlueWar, it is published under the LGPL license, so it would be great if you contact me with any improvements you make.

Header file “WaterManager.h”

#ifndef WATERMANAGER_H
#define WATERMANAGER_H

#include "OgreVector3.h"
#include "OgreMovablePlane.h"
#include "OgreEntity.h"
#include "OgreRenderTargetListener.h"

#include "combatsettings.h"

class watermanager
{
protected:

    // you can offset the water planes using this vector
    Ogre::Vector3 mWaterPos;

    // water plane definitions and their entities

    // this plane is used to render the ground below the water
    // so that it can be shown refracted
    Ogre::MovablePlane* mRefractionPlane;

    // the rendered water
    Ogre::MovablePlane* mWaterPlane;
    Ogre::Entity* mWaterPlaneEnt;

    // the plane to render everything above the water, it is
    // used for reflection rendering
    Ogre::MovablePlane* mReflectionPlane;

    // SceneNodes for all water planes
    Ogre::SceneNode* mWaterNode;

    // cameras used for reflection and refraction rendering
    Ogre::Camera* mReflectCam;
    Ogre::Camera* mRefractCam;

    // material used for rendering
    std::string mMaterial;

    // activate/deactivate reflection rendering
    bool mWaterReflections;

    // check the hardware requirements
    bool isWaterPossible();

public:
    // render updates
    class CRefractionListener : public Ogre::RenderTargetListener
    {
    public:
        // pointer back to the manager
        watermanager* water;

        // updates before and after refraction rendering
        void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
        void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
    };
    class CReflectionListener : public Ogre::RenderTargetListener
    {
    public:
        // pointer back to the manager
        watermanager* water;

        // updates before and after reflection rendering
        void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
        void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
    };

    watermanager(combatsettings* settings);

    ~watermanager();

    // just update every frame
    void update();

    CRefractionListener mRefractionListener;
    CReflectionListener mReflectionListener;
};

#endif

“WaterManager.cpp” :

#include "watermanager.h"
#include "logs.h"

#include "CombatApplication.h"

#include "OgreMeshManager.h"

watermanager::watermanager(combatsettings* settings)
{
    Log::logmessage("loading water...");

    // save quality
    mWaterReflections = settings->mWaterReflections;
    mMaterial = settings->mWaterMaterial;

    // save height
    mWaterPos = settings->mWaterPos;

    // Create water planes
    mRefractionPlane    = new Ogre::MovablePlane("RefractionPlane");
    mWaterPlane            = new Ogre::MovablePlane("WaterPlane");
    mReflectionPlane    = new Ogre::MovablePlane("ReflectionPlane");

    // set heights
    mRefractionPlane    ->d = 0.25;
    mWaterPlane            ->d = 0;
    mReflectionPlane    ->d = -0.25;

    // set normals
    mRefractionPlane    ->normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
    mWaterPlane            ->normal = Ogre::Vector3::UNIT_Y;
    mReflectionPlane    ->normal = Ogre::Vector3::UNIT_Y;

    // create Planes
    Ogre::MeshManager::getSingleton().createPlane("RefractionClipPlane",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        *mRefractionPlane,settings->mGameArea.x_length,settings->mGameArea.z_length,
        10,10,true,1,5,5,Ogre::Vector3::UNIT_Z);
    Ogre::MeshManager::getSingleton().createPlane("WaterEntPlane",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        *mWaterPlane,settings->mGameArea.x_length,settings->mGameArea.z_length,
        10,10,true,1,5,5,Ogre::Vector3::UNIT_Z);
    Ogre::MeshManager::getSingleton().createPlane("ReflectionClipPlane",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        *mReflectionPlane,settings->mGameArea.x_length,settings->mGameArea.z_length,
        10,10,true,1,5,5,Ogre::Vector3::UNIT_Z);

    mWaterPlaneEnt = app->getSceneManager().createEntity( "mWaterPlaneEnt", "WaterEntPlane" );

    // Attach the rtt entity to the root of the scene
    mWaterNode = app->getSceneManager().getRootSceneNode()->createChildSceneNode();

    // Attach both the plane entity, and the plane definition
    mWaterNode->attachObject(mWaterPlaneEnt);

    mWaterNode->attachObject(mRefractionPlane);
    mWaterNode->attachObject(mWaterPlane);
    mWaterNode->attachObject(mReflectionPlane);

    // set water position
    mWaterNode->translate(mWaterPos);

    if( mWaterReflections && isWaterPossible() )
    {
        Log::logmessage("loading highly detailed water...");

        this->mReflectionListener.water = this;
        this->mRefractionListener.water = this;

        float quali = settings->mWaterReflectionQuality;

        // create reflection camera
        mReflectCam = app->getSceneManager().createCamera("ReflectCam");
//        mReflectCam->setNearClipDistance(app->getCamera()->getNearClipDistance());
        mReflectCam->setFarClipDistance(app->mSettings->mRangeOfSight);
        mReflectCam->setAspectRatio(
            (Ogre::Real)app->mWindow->getViewport(0)->getActualWidth() /
            (Ogre::Real)app->mWindow->getViewport(0)->getActualHeight());

        // create refraction camera
        mRefractCam = app->getSceneManager().createCamera("RefractCam");
//        mRefractCam->setNearClipDistance(app->getCamera()->getNearClipDistance());
        mRefractCam->setFarClipDistance(app->mSettings->mRangeOfSight);
        mRefractCam->setAspectRatio(
            (Ogre::Real)app->mWindow->getViewport(0)->getActualWidth() /
            (Ogre::Real)app->mWindow->getViewport(0)->getActualHeight());

        // init RenderTexture for water refraction
        Ogre::TexturePtr tex1 = Ogre::TextureManager::getSingleton().createManual( "Refraction",
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D,
            quali, quali, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET );
        Ogre::RenderTarget* rttTex1 = tex1->getBuffer()->getRenderTarget();
        {
            // setup viewport for refraction
            Ogre::Viewport *v = rttTex1->addViewport( mRefractCam );
            v->setClearEveryFrame(true);
            v->setBackgroundColour( Ogre::ColourValue::Black);
            v->setOverlaysEnabled(false);

            // init refraction texture unit
            Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(mMaterial);
            Ogre::TextureUnitState* t = mat->getTechnique(0)->getPass(0)->getTextureUnitState(2);
            t->setTextureName("Refraction");
//            t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
//            t->setProjectiveTexturing(true, mReflectCam);

            // add refraction listener
            rttTex1->addListener(&mRefractionListener);
        }

        // init RenderTexture for water reflection
        Ogre::TexturePtr tex2 = Ogre::TextureManager::getSingleton().createManual( "Reflection",
            Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D,
            quali, quali, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET );
        Ogre::RenderTarget* rttTex2 = tex2->getBuffer()->getRenderTarget();
        {
            // setup viewport for reflection
            Ogre::Viewport *v = rttTex2->addViewport( mReflectCam );
            v->setShadowsEnabled(false);
            v->setClearEveryFrame(true);
            v->setBackgroundColour( Ogre::ColourValue::Black);
            v->setOverlaysEnabled(false);

            // init reflection texture unit
            Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(mMaterial);
            Ogre::TextureUnitState* t = mat->getTechnique(0)->getPass(0)->getTextureUnitState(1);
            t->setTextureName("Reflection");
            t->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
            t->setProjectiveTexturing(true, mReflectCam);

            // add reflection listener
            rttTex2->addListener(&mReflectionListener);
        }
        // Give the plane a material
        mWaterPlaneEnt->setMaterialName(mMaterial);
//        mWaterPlaneEnt->setMaterialName("Examples/FresnelReflectionRefraction");
//        mWaterPlaneEnt->setMaterialName("RttMat");

//        mWaterPlane->setRenderQueueGroup(Ogre::RENDER_QUEUE_SKIES_LATE);
    }
    else
    {
        Log::logmessage("loading water without reflections...");

        // Give the plane a material
        mWaterPlaneEnt->setMaterialName("Ocean2_HLSL_GLSL");
    }
}

watermanager::~watermanager()
{
    /*
    mWaterNode->detachAllObjects();
    mWaterNode->getParentSceneNode()->removeAndDestroyChild(mWaterNode->getName());

    app->getSceneManager().destroyCamera(mRefractCam);
    app->getSceneManager().destroyCamera(mReflectCam);

    app->getSceneManager().destroyEntity(mWaterPlaneEnt);

    Ogre::TextureManager::getSingleton().unload("Reflection");
    Ogre::TextureManager::getSingleton().unload("Refraction");

    delete mRefractionPlane;
    delete mWaterPlane;
    delete mReflectionPlane;
    */
}

void watermanager::update()
{
    if( mWaterReflections && mReflectCam )
    {
        // update reflection camera
        mReflectCam->setOrientation(CameraController::getSingletonPtr()->getCameraOrientation());
        mReflectCam->setPosition(CameraController::getSingletonPtr()->getCameraPosition());

        // update refraction camera
        mRefractCam->setOrientation(CameraController::getSingletonPtr()->getCameraOrientation());
        mRefractCam->setPosition(CameraController::getSingletonPtr()->getCameraPosition());
    }
}

// render updates
void watermanager::CReflectionListener::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
{
    // Hide plane
    water->mWaterPlaneEnt->setVisible(false);
    water->mReflectCam->enableReflection(water->mWaterPlane);
    water->mReflectCam->enableCustomNearClipPlane(water->mReflectionPlane);

    // hide HUD
    app->myHUDNode->setVisible(false);
}

void watermanager::CReflectionListener::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
{
    // Show plane
    water->mWaterPlaneEnt->setVisible(true);
    water->mReflectCam->disableReflection();
    water->mReflectCam->disableCustomNearClipPlane();

    // show HUD
    app->myHUDNode->setVisible(true);
}

// render updates
void watermanager::CRefractionListener::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
{
    // hide reflection
    water->mReflectionPlane->setVisible(false);

    // Hide plane
    water->mWaterPlaneEnt->setVisible(false);
    water->mRefractCam->enableCustomNearClipPlane(water->mRefractionPlane);

    // hide HUD
    app->myHUDNode->setVisible(false);
}

void watermanager::CRefractionListener::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
{
    // show reflection
    water->mReflectionPlane->setVisible(true);

    // Show plane
    water->mWaterPlaneEnt->setVisible(true);
    water->mRefractCam->disableCustomNearClipPlane();

    // show HUD
    app->myHUDNode->setVisible(true);
}

bool watermanager::isWaterPossible()
{
    const Ogre::RenderSystemCapabilities* rsc = Ogre::Root::getSingleton().getRenderSystem()->getCapabilities();

    return
        rsc->hasCapability(Ogre::RSC_FRAGMENT_PROGRAM) &&
        rsc->hasCapability(Ogre::RSC_USER_CLIP_PLANES) &&
        rsc->hasCapability(Ogre::RSC_VERTEX_PROGRAM);
}

However, there seems to be a deallocation bug and it would be great if someone could find it for me 😉

Advertisements

3 Responses to “Combining Fresnel Water and PLSM2 in Ogre”

  1. bluewar Says:

    this code is old. Now I would suggest not to use PLSM2 and not to make the water by hand.

    Use Hydrax instead!
    http://www.ogre3d.org/addonforums/viewforum.php?f=20&sid=ae1fbf216ad4cec1093bdc28fdb889cc

    • Sam Says:

      Why should you not use PLSM2? (I’m currently assessing whether we should use Ogre3D or not…)

      • bluewar Says:

        You should not use PLSM2 because now there is a new landscape plugin built by Sinbad (the main Ogre dev). PLSM2 is very old and not really neat to use.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: