Skip to content

Commit

Permalink
Fix ripples for D3D - added simpler effect (Fixes #1649)
Browse files Browse the repository at this point in the history
  • Loading branch information
scrawl committed Feb 9, 2015
1 parent 9dbd9af commit dbd4abd
Show file tree
Hide file tree
Showing 19 changed files with 195 additions and 474 deletions.
1 change: 1 addition & 0 deletions apps/openmw/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
addResourcesDirectory(mResDir / "mygui");
addResourcesDirectory(mResDir / "water");
addResourcesDirectory(mResDir / "shadows");
addResourcesDirectory(mResDir / "materials");

OEngine::Render::WindowSettings windowSettings;
windowSettings.fullscreen = settings.getBool("fullscreen", "Video");
Expand Down
1 change: 1 addition & 0 deletions apps/openmw/mwrender/renderconst.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum RenderQueueGroups
RQG_UnderWater = Ogre::RENDER_QUEUE_4,

RQG_Water = RQG_Alpha,
RQG_Ripples = RQG_Water+1,

// Sky late (sun & sun flare)
RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE
Expand Down
3 changes: 2 additions & 1 deletion apps/openmw/mwrender/renderingmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
mDebugging = new Debugging(mRootNode, engine);
mLocalMap = new MWRender::LocalMap(&mRendering, this);

mWater = new MWRender::Water(mRendering.getCamera(), this);
mWater = new MWRender::Water(mRendering.getCamera(), this, mFallback);

setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI"));
}
Expand Down Expand Up @@ -693,6 +693,7 @@ void RenderingManager::enableLights(bool sun)
void RenderingManager::notifyWorldSpaceChanged()
{
mEffectManager->clear();
mWater->clearRipples();
}

Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds)
Expand Down
275 changes: 88 additions & 187 deletions apps/openmw/mwrender/ripplesimulation.cpp
Original file line number Diff line number Diff line change
@@ -1,162 +1,72 @@
#include "ripplesimulation.hpp"

#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreRoot.h>
#include <OgreRectangle2D.h>
#include <stdexcept>

#include <OgreSceneNode.h>
#include <OgreRenderTexture.h>
#include <OgreViewport.h>
#include <OgreSceneManager.h>
#include <OgreParticleSystem.h>
#include <OgreParticle.h>

#include <extern/shiny/Main/Factory.hpp>

#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"

namespace MWRender
{
#include "../mwworld/fallback.hpp"

#include "renderconst.hpp"

RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager)
: mMainSceneMgr(mainSceneManager),
mTime(0),
mCurrentFrameOffset(0,0),
mPreviousFrameOffset(0,0),
mRippleCenter(0,0),
mTextureSize(512),
mRippleAreaLength(1000),
mImpulseSize(20),
mTexelOffset(0,0),
mFirstUpdate(true),
mRectangle(NULL),
mImpulse(NULL)
namespace MWRender
{
Ogre::AxisAlignedBox aabInf;
aabInf.setInfinite();

mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);

mCamera = mSceneMgr->createCamera("RippleCamera");

mRectangle = new Ogre::Rectangle2D(true);
mRectangle->setBoundingBox(aabInf);
mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0, false);
Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
node->attachObject(mRectangle);

mImpulse = new Ogre::Rectangle2D(true);
mImpulse->setCorners(-0.1, 0.1, 0.1, -0.1, false);
mImpulse->setBoundingBox(aabInf);
mImpulse->setMaterial("AddImpulse");
Ogre::SceneNode* impulseNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
impulseNode->attachObject(mImpulse);

//float w=0.05;
for (int i=0; i<4; ++i)
{
Ogre::TexturePtr texture;
if (i != 3)
texture = Ogre::TextureManager::getSingleton().createManual("RippleHeight" + Ogre::StringConverter::toString(i),
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
else
texture = Ogre::TextureManager::getSingleton().createManual("RippleNormal",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);


Ogre::RenderTexture* rt = texture->getBuffer()->getRenderTarget();
rt->removeAllViewports();
rt->addViewport(mCamera);
rt->setAutoUpdated(false);
rt->getViewport(0)->setClearEveryFrame(false);
RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager, const MWWorld::Fallback* fallback)
: mSceneMgr(mainSceneManager)
, mParticleSystem(NULL)
, mSceneNode(NULL)
{
mRippleLifeTime = fallback->getFallbackFloat("Water_RippleLifetime");
mRippleRotSpeed = fallback->getFallbackFloat("Water_RippleRotSpeed");

// debug overlay
/*
Ogre::Rectangle2D* debugOverlay = new Ogre::Rectangle2D(true);
debugOverlay->setCorners(w*2-1, 0.9, (w+0.18)*2-1, 0.4, false);
w += 0.2;
debugOverlay->setBoundingBox(aabInf);
Ogre::SceneNode* debugNode = mMainSceneMgr->getRootSceneNode()->createChildSceneNode();
debugNode->attachObject(debugOverlay);
// Unknown:
// fallback=Water_RippleScale,0.15, 6.5
// fallback=Water_RippleAlphas,0.7, 0.1, 0.01

Ogre::MaterialPtr debugMaterial = Ogre::MaterialManager::getSingleton().create("RippleDebug" + Ogre::StringConverter::toString(i),
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
// Instantiate from ripples.particle file
mParticleSystem = mSceneMgr->createParticleSystem("openmw/Ripples", "openmw/Ripples");

if (i != 3)
debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleHeight" + Ogre::StringConverter::toString(i));
else
debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleNormal");
debugMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
mParticleSystem->setRenderQueueGroup(RQG_Ripples);
mParticleSystem->setVisibilityFlags(RV_Effects);

debugOverlay->setMaterial("RippleDebug" + Ogre::StringConverter::toString(i));
*/
int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount");
std::string tex = fallback->getFallbackString("Water_RippleTexture");

mRenderTargets[i] = rt;
mTextures[i] = texture;
}
sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("openmw/Ripple");
mat->setProperty("anim_texture2", sh::makeProperty(new sh::StringValue(std::string("textures\\water\\") + tex + ".dds "
+ Ogre::StringConverter::toString(rippleFrameCount)
+ " "
+ Ogre::StringConverter::toString(0.3))));

sh::Factory::getInstance().setSharedParameter("rippleTextureSize", sh::makeProperty<sh::Vector4>(
new sh::Vector4(1.0/512, 1.0/512, 512, 512)));
sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty<sh::Vector3>(
new sh::Vector3(0, 0, 0)));
sh::Factory::getInstance().setSharedParameter("rippleAreaLength", sh::makeProperty<sh::FloatValue>(
new sh::FloatValue(mRippleAreaLength)));
// seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better
mParticleSystem->_update(0.f);

mSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mSceneNode->attachObject(mParticleSystem);
}

RippleSimulation::~RippleSimulation()
{
delete mRectangle;
delete mImpulse;
if (mParticleSystem)
mSceneMgr->destroyParticleSystem(mParticleSystem);
mParticleSystem = NULL;

Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
if (mSceneNode)
mSceneMgr->destroySceneNode(mSceneNode);
mSceneNode = NULL;
}

void RippleSimulation::update(float dt, Ogre::Vector2 position)
{
// try to keep 20 fps
mTime += dt;

while (mTime >= 1/20.0 || mFirstUpdate)
{
mPreviousFrameOffset = mCurrentFrameOffset;

mCurrentFrameOffset = position - mRippleCenter;
// add texel offsets from previous frame.
mCurrentFrameOffset += mTexelOffset;

mTexelOffset = Ogre::Vector2(std::fmod(mCurrentFrameOffset.x, 1.0f/mTextureSize),
std::fmod(mCurrentFrameOffset.y, 1.0f/mTextureSize));

// now subtract new offset in order to snap to texels
mCurrentFrameOffset -= mTexelOffset;

// texture coordinate space
mCurrentFrameOffset /= mRippleAreaLength;

mRippleCenter = position;

addImpulses();
waterSimulation();
heightMapToNormalMap();

swapHeightMaps();
if (!mFirstUpdate)
mTime -= 1/20.0;
else
mFirstUpdate = false;
}

sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty<sh::Vector3>(
new sh::Vector3(mRippleCenter.x + mTexelOffset.x, mRippleCenter.y + mTexelOffset.y, 0)));
}

void RippleSimulation::addImpulses()
{
mRectangle->setVisible(false);
mImpulse->setVisible(true);

/// \todo it should be more efficient to render all emitters at once
bool newParticle = false;
for (std::vector<Emitter>::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it)
{
if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr())
Expand All @@ -165,69 +75,50 @@ void RippleSimulation::addImpulses()
// for non-player actors this is done in updateObjectCell
it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr();
}
const float* _currentPos = it->mPtr.getRefData().getPosition().pos;
Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]);

if ( (currentPos - it->mLastEmitPosition).length() > 2
&& MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), currentPos))
Ogre::Vector3 currentPos (it->mPtr.getRefData().getPosition().pos);
currentPos.z = 0;
if ( (currentPos - it->mLastEmitPosition).length() > 10
// Only emit when close to the water surface, not above it and not too deep in the water
&& MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(),
Ogre::Vector3(it->mPtr.getRefData().getPosition().pos))
&& !MWBase::Environment::get().getWorld()->isSubmerged(it->mPtr))
{
it->mLastEmitPosition = currentPos;

Ogre::Vector2 pos (currentPos.x, currentPos.y);
pos -= mRippleCenter;
pos /= mRippleAreaLength;
float size = mImpulseSize / mRippleAreaLength;
mImpulse->setCorners(pos.x-size, pos.y+size, pos.x+size, pos.y-size, false);

// don't render if we are offscreen
if (pos.x - size >= 1.0 || pos.y+size <= -1.0 || pos.x+size <= -1.0 || pos.y-size >= 1.0)
continue;
mRenderTargets[1]->update();
newParticle = true;
Ogre::Particle* created = mParticleSystem->createParticle();
if (!created)
break; // TODO: cleanup the oldest particle to make room
#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
Ogre::Vector3& position = created->mPosition;
Ogre::Vector3& direction = created->mDirection;
Ogre::ColourValue& colour = created->mColour;
float& totalTimeToLive = created->mTotalTimeToLive;
float& timeToLive = created->mTimeToLive;
Ogre::Radian& rotSpeed = created->mRotationSpeed;
Ogre::Radian& rotation = created->mRotation;
#else
Ogre::Vector3& position = created->position;
Ogre::Vector3& direction = created->direction;
Ogre::ColourValue& colour = created->colour;
float& totalTimeToLive = created->totalTimeToLive;
float& timeToLive = created->timeToLive;
Ogre::Radian& rotSpeed = created->rotationSpeed;
Ogre::Radian& rotation = created->rotation;
#endif
timeToLive = totalTimeToLive = mRippleLifeTime;
colour = Ogre::ColourValue(0.f, 0.f, 0.f, 0.7); // Water_RippleAlphas.x?
direction = Ogre::Vector3(0,0,0);
position = currentPos;
position.z = 0; // Z is set by the Scene Node
rotSpeed = mRippleRotSpeed;
rotation = Ogre::Radian(Ogre::Math::RangeRandom(-Ogre::Math::PI, Ogre::Math::PI));
created->setDimensions(50,50);
}
}

mImpulse->setVisible(false);
mRectangle->setVisible(true);
}

void RippleSimulation::waterSimulation()
{
mRectangle->setMaterial("HeightmapSimulation");

sh::Factory::getInstance().setTextureAlias("Heightmap0", mTextures[0]->getName());
sh::Factory::getInstance().setTextureAlias("Heightmap1", mTextures[1]->getName());

sh::Factory::getInstance().setSharedParameter("currentFrameOffset", sh::makeProperty<sh::Vector3>(
new sh::Vector3(mCurrentFrameOffset.x, mCurrentFrameOffset.y, 0)));
sh::Factory::getInstance().setSharedParameter("previousFrameOffset", sh::makeProperty<sh::Vector3>(
new sh::Vector3(mPreviousFrameOffset.x, mPreviousFrameOffset.y, 0)));

mRenderTargets[2]->update();
}

void RippleSimulation::heightMapToNormalMap()
{
mRectangle->setMaterial("HeightToNormalMap");

sh::Factory::getInstance().setTextureAlias("Heightmap2", mTextures[2]->getName());

mRenderTargets[TEX_NORMAL]->update();
}

void RippleSimulation::swapHeightMaps()
{
// 0 -> 1 -> 2 to 2 -> 0 ->1
Ogre::RenderTexture* tmp = mRenderTargets[0];
Ogre::TexturePtr tmp2 = mTextures[0];

mRenderTargets[0] = mRenderTargets[1];
mTextures[0] = mTextures[1];

mRenderTargets[1] = mRenderTargets[2];
mTextures[1] = mTextures[2];

mRenderTargets[2] = tmp;
mTextures[2] = tmp2;
if (newParticle) // now apparently needs another update, otherwise it won't render in the first frame after a particle is created. TODO: patch Ogre to handle this better
mParticleSystem->_update(0.f);
}

void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force)
Expand Down Expand Up @@ -264,5 +155,15 @@ void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld:
}
}

void RippleSimulation::setWaterHeight(float height)
{
mSceneNode->setPosition(0,0,height);
}

void RippleSimulation::clear()
{
mParticleSystem->clear();
}


}
Loading

2 comments on commit dbd4abd

@Staudey
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the old effect as an option? Also there seem to be no ripples when you jump straight up and down in shallow water.

@Staudey
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice. Yeah, the old effect had some issues, and I can live without it to be honest ;)

Please sign in to comment.