diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 32faafcc8c..8e424ad56c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -102,6 +102,7 @@ target_link_libraries(openmw ${MYGUI_PLATFORM_LIBRARIES} "shiny" "shiny.OgrePlatform" + components ) # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 993ec66239..86584cc229 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -261,7 +261,7 @@ int main(int argc, char**argv) boost::filesystem::current_path(bundlePath); #endif - try + //try { Files::ConfigurationManager cfgMgr; OMW::Engine engine(cfgMgr); @@ -271,11 +271,11 @@ int main(int argc, char**argv) engine.go(); } } - catch (std::exception &e) + /*catch (std::exception &e) { std::cout << "\nERROR: " << e.what() << std::endl; return 1; - } + }*/ return 0; } diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp index c4aa093c0d..457b6e6018 100644 --- a/apps/openmw/mwrender/renderconst.hpp +++ b/apps/openmw/mwrender/renderconst.hpp @@ -14,14 +14,14 @@ enum RenderQueueGroups RQG_Main = Ogre::RENDER_QUEUE_MAIN, + RQG_Alpha = Ogre::RENDER_QUEUE_MAIN+1, + + RQG_OcclusionQuery = Ogre::RENDER_QUEUE_6, + + RQG_UnderWater = Ogre::RENDER_QUEUE_7, + RQG_Water = Ogre::RENDER_QUEUE_7+1, - RQG_Alpha = Ogre::RENDER_QUEUE_MAIN, - - RQG_UnderWater = Ogre::RENDER_QUEUE_7+1, - - RQG_OcclusionQuery = Ogre::RENDER_QUEUE_8, - // Sky late (sun & sun flare) RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE }; @@ -54,7 +54,7 @@ enum VisibilityFlags RV_OcclusionQuery = 256, - RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water, + RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water /// \todo markers (normally hidden) }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15660ade51..916ccb0c0b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -264,7 +264,7 @@ void RenderingManager::update (float duration){ checkUnderwater(); - mWater->update(); + mWater->update(duration); } void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ if(store->cell->data.flags & store->cell->HasWater diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index b1d9dee124..691e7c4af3 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -44,7 +44,6 @@ namespace MWRender mTerrainGlobals->setMaxPixelError(8); mTerrainGlobals->setLayerBlendMapSize(32); - mTerrainGlobals->setDefaultGlobalColourMapSize(65); //10 (default) didn't seem to be quite enough mTerrainGlobals->setSkirtSize(128); @@ -53,28 +52,6 @@ namespace MWRender //this seemed the distance where it wasn't too noticeable mTerrainGlobals->setCompositeMapDistance(mWorldSize*2); - /* - mActiveProfile->setLightmapEnabled(false); - mActiveProfile->setLayerSpecularMappingEnabled(false); - mActiveProfile->setLayerNormalMappingEnabled(false); - mActiveProfile->setLayerParallaxMappingEnabled(false); - - bool shadows = Settings::Manager::getBool("enabled", "Shadows"); - mActiveProfile->setReceiveDynamicShadowsEnabled(shadows); - mActiveProfile->setReceiveDynamicShadowsDepth(shadows); - if (Settings::Manager::getBool("split", "Shadows")) - mActiveProfile->setReceiveDynamicShadowsPSSM(mRendering->getShadows()->getPSSMSetup()); - else - mActiveProfile->setReceiveDynamicShadowsPSSM(0); - - mActiveProfile->setShadowFar(mRendering->getShadows()->getShadowFar()); - mActiveProfile->setShadowFadeStart(mRendering->getShadows()->getFadeStart()); - - //composite maps lead to a drastic increase in loading time so are - //disabled - mActiveProfile->setCompositeMapEnabled(false); - */ - mTerrainGroup.setOrigin(Vector3(mWorldSize/2, 0, -mWorldSize/2)); @@ -181,10 +158,9 @@ namespace MWRender terrain->setVisibilityFlags(RV_Terrain); terrain->setRenderQueueGroup(RQG_Main); + // disable or enable global colour map (depends on available vertex colours) if ( land->landData->usingColours ) { - // disable or enable global colour map (depends on available vertex colours) - //mActiveProfile->setGlobalColourMapEnabled(true); TexturePtr vertex = getVertexColours(land, cellX, cellY, x*(mLandSize-1), @@ -193,17 +169,9 @@ namespace MWRender mActiveProfile->setGlobalColourMapEnabled(true); mActiveProfile->setGlobalColourMap (terrain, vertex->getName()); - - //this is a hack to get around the fact that Ogre seems to - //corrupt the global colour map leading to rendering errors - //MaterialPtr mat = terrain->getMaterial(); - /// \todo - //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); - - - //mat = terrain->_getCompositeMapMaterial(); - //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); } + else + mActiveProfile->setGlobalColourMapEnabled (false); } } } diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp index 89d8953589..1a2d96a144 100644 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -117,6 +117,10 @@ namespace MWRender shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); } + // caustics + sh::MaterialInstanceTextureUnit* caustics = p->createTextureUnit ("causticMap"); + caustics->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("water_nm.png"))); + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty(new sh::StringValue( Ogre::StringConverter::toString(numBlendTextures + numLayers + 2)))); @@ -149,6 +153,8 @@ namespace MWRender --freeTextureUnits; freeTextureUnits -= 3; // shadow PSSM + --freeTextureUnits; // caustics + // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) return static_cast(freeTextureUnits / (1.25f)); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b8642a7541..90967c8868 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -8,11 +8,16 @@ #include #include #include +#include +#include +#include #include "sky.hpp" #include "renderingmanager.hpp" #include "compositors.hpp" +#include + using namespace Ogre; namespace MWRender @@ -22,10 +27,16 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mCamera (camera), mSceneManager (camera->getSceneManager()), mIsUnderwater(false), mVisibilityFlags(0), mReflectionTarget(0), mActive(1), mToggled(1), - mReflectionRenderActive(false), mRendering(rend) + mReflectionRenderActive(false), mRendering(rend), + mOldFarClip(0), + mWaterTimer(0.f) { mSky = rend->getSkyManager(); + sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty(new sh::Vector3(0.5, -0.8, 0.2))); + sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); + sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6))); + mTop = cell->water; mIsUnderwater = false; @@ -40,7 +51,6 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mWater->setCastShadows(false); mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); - mWaterNode->setPosition(0, mTop, 0); mReflectionCamera = mSceneManager->createCamera("ReflectionCamera"); @@ -59,21 +69,30 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel mUnderwaterEffect = Settings::Manager::getBool("underwater effect", "Water"); + Ogre::Entity* underwaterDome = mSceneManager->createEntity ("underwater_dome.mesh"); + underwaterDome->setRenderQueueGroup (RQG_UnderWater); + mUnderwaterDome = mSceneManager->getRootSceneNode ()->createChildSceneNode (); + mUnderwaterDome->attachObject (underwaterDome); + mUnderwaterDome->setScale(100,100,100); + mUnderwaterDome->setVisible(false); + mSceneManager->addRenderQueueListener(this); assignTextures(); + setHeight(mTop); + // ---------------------------------------------------------------------------------------------- // ---------------------------------- reflection debug overlay ---------------------------------- // ---------------------------------------------------------------------------------------------- - /* +/* if (Settings::Manager::getBool("shader", "Water")) { OverlayManager& mgr = OverlayManager::getSingleton(); Overlay* overlay; // destroy if already exists - if (overlay = mgr.getByName("ReflectionDebugOverlay")) + if ((overlay = mgr.getByName("ReflectionDebugOverlay"))) mgr.destroy(overlay); overlay = mgr.create("ReflectionDebugOverlay"); @@ -85,18 +104,17 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); - TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); - t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(mReflectionTexture->getName()); OverlayContainer* debugPanel; // destroy container if exists try { - if (debugPanel = + if ((debugPanel = static_cast( mgr.getOverlayElement("Ogre/ReflectionDebugTexPanel" - ))) + )))) mgr.destroyOverlayElement(debugPanel); } catch (Ogre::Exception&) {} @@ -110,7 +128,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cel overlay->add2D(debugPanel); overlay->show(); } - */ +*/ } void Water::setActive(bool active) @@ -146,6 +164,7 @@ void Water::setHeight(const float height) mTop = height; mWaterPlane = Plane(Vector3::UNIT_Y, height); mWaterNode->setPosition(0, height, 0); + sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(height))); } void Water::toggle() @@ -164,13 +183,15 @@ void Water::checkUnderwater(float y) if ((mIsUnderwater && y > mTop) || !mWater->isVisible() || mCamera->getPolygonMode() != Ogre::PM_SOLID) { - mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); + //mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); // tell the shader we are not underwater + +/* Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(0)); - +*/ mWater->setRenderQueueGroup(RQG_Water); mIsUnderwater = false; @@ -178,15 +199,16 @@ void Water::checkUnderwater(float y) if (!mIsUnderwater && y < mTop && mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID) { - if (mUnderwaterEffect) - mRendering->getCompositors()->setCompositorEnabled(mCompositorName, true); + //if (mUnderwaterEffect) + //mRendering->getCompositors()->setCompositorEnabled(mCompositorName, true); // tell the shader we are underwater +/* Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); if (pass->hasFragmentProgram() && pass->getFragmentProgramParameters()->_findNamedConstantDefinition("isUnderwater", false)) pass->getFragmentProgramParameters()->setNamedConstant("isUnderwater", Real(1)); - - mWater->setRenderQueueGroup(RQG_UnderWater); +*/ + //mWater->setRenderQueueGroup(RQG_UnderWater); mIsUnderwater = true; } @@ -211,12 +233,11 @@ void Water::preRenderTargetUpdate(const RenderTargetEvent& evt) mReflectionCamera->setFOVy(mCamera->getFOVy()); mReflectionRenderActive = true; - /// \todo For some reason this camera is delayed for 1 frame, which causes ugly sky reflection behaviour.. - /// to circumvent this we just scale the sky up, so it's not that noticable + /// \todo the reflection render (and probably all renderingmanager-updates) lag behind 1 camera frame for some reason Vector3 pos = mCamera->getRealPosition(); pos.y = mTop*2 - pos.y; mSky->setSkyPosition(pos); - mSky->scaleSky(mCamera->getFarClipDistance() / 5000.f); + mSky->scaleSky(mCamera->getFarClipDistance() / 50.f); mReflectionCamera->enableReflection(mWaterPlane); } } @@ -242,7 +263,7 @@ void Water::createMaterial() else { mMaterial = MaterialManager::getSingleton().getByName("Water"); - mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTexture(mReflectionTexture); + sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mReflectionTexture->getName()); } // these have to be set in code @@ -251,30 +272,23 @@ void Water::createMaterial() { textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; } - Ogre::Technique* tech; - if (mReflectionTarget == 0) - tech = mMaterial->getTechnique(0); - else - tech = mMaterial->getTechnique(1); - tech->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); + if (mReflectionTarget == 0) + mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); } void Water::assignTextures() { if (Settings::Manager::getBool("shader", "Water")) { + CompositorInstance* compositor = CompositorManager::getSingleton().getCompositorChain(mRendering->getViewport())->getCompositor("gbuffer"); TexturePtr colorTexture = compositor->getTextureInstance("mrt_output", 0); - TextureUnitState* tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("refractionMap"); - if (tus != 0) - tus->setTexture(colorTexture); + sh::Factory::getInstance ().setTextureAlias ("WaterRefraction", colorTexture->getName()); TexturePtr depthTexture = compositor->getTextureInstance("mrt_output", 1); - tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("depthMap"); - if (tus != 0) - tus->setTexture(depthTexture); + sh::Factory::getInstance ().setTextureAlias ("SceneDepth", depthTexture->getName()); } } @@ -288,7 +302,7 @@ void Water::updateVisible() { mWater->setVisible(mToggled && mActive); if (mReflectionTarget) - mReflectionTarget->setActive(mToggled && mActive && !mIsUnderwater); + mReflectionTarget->setActive(mToggled && mActive); } void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) @@ -296,7 +310,9 @@ void Water::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &in // We don't want the sky to get clipped by custom near clip plane (the water plane) if (queueGroupId < 20 && mReflectionRenderActive) { + mOldFarClip = mReflectionCamera->getFarClipDistance (); mReflectionCamera->disableCustomNearClipPlane(); + mReflectionCamera->setFarClipDistance (1000000000); Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); } } @@ -305,13 +321,21 @@ void Water::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invo { if (queueGroupId < 20 && mReflectionRenderActive) { - mReflectionCamera->enableCustomNearClipPlane(mWaterPlane); + mReflectionCamera->setFarClipDistance (mOldFarClip); + if (!mIsUnderwater) + mReflectionCamera->enableCustomNearClipPlane(mWaterPlane); Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mReflectionCamera->getProjectionMatrixRS()); } } -void Water::update() +void Water::update(float dt) { + Ogre::Vector3 pos = mCamera->getDerivedPosition (); + pos.y = -mWaterPlane.d; + mUnderwaterDome->setPosition (pos); + + mWaterTimer += dt; + sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); } void Water::applyRTT() diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 71f261f2b0..ac837ca0d2 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -39,11 +39,17 @@ namespace MWRender { Ogre::SceneNode *mWaterNode; Ogre::Entity *mWater; + Ogre::SceneNode* mUnderwaterDome; + bool mIsUnderwater; bool mActive; bool mToggled; int mTop; + int mOldFarClip; + + float mWaterTimer; + bool mReflectionRenderActive; Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); @@ -83,7 +89,7 @@ namespace MWRender { void setActive(bool active); void toggle(); - void update(); + void update(float dt); void assignTextures(); diff --git a/extern/shiny b/extern/shiny index 5a9bda6010..bf003238a2 160000 --- a/extern/shiny +++ b/extern/shiny @@ -1 +1 @@ -Subproject commit 5a9bda6010413555736479ef03103f764fecb91d +Subproject commit bf003238a27d94be43724e6774d25c38b4d578c8 diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index 6ca35df87b..19b890fec0 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -18,7 +18,7 @@ compositor gbuffer { // Renders everything except water first_render_queue 0 - last_render_queue 70 + last_render_queue 50 } } @@ -66,7 +66,7 @@ compositor gbufferFinalizer } pass render_scene { - first_render_queue 71 + first_render_queue 51 last_render_queue 100 } } diff --git a/files/materials/caustics.h b/files/materials/caustics.h new file mode 100644 index 0000000000..d6be680f0e --- /dev/null +++ b/files/materials/caustics.h @@ -0,0 +1,117 @@ +#define VISIBILITY 1500.0 // how far you can look through water + +#define BIG_WAVES_X 0.3 // strength of big waves +#define BIG_WAVES_Y 0.3 + +#define MID_WAVES_X 0.3 // strength of middle sized waves +#define MID_WAVES_Y 0.15 + +#define SMALL_WAVES_X 0.15 // strength of small waves +#define SMALL_WAVES_Y 0.1 + +#define WAVE_CHOPPYNESS 0.15 // wave choppyness +#define WAVE_SCALE 0.01 // overall wave scale + +#define ABBERATION 0.001 // chromatic abberation amount + +float3 intercept(float3 lineP, + float3 lineN, + float3 planeN, + float planeD) +{ + + float distance = (planeD - dot(planeN, lineP)) / dot(lineN, planeN); + return lineP + lineN * distance; +} + +float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) +{ + float2 nCoord = float2(0.0); + bend *= WAVE_CHOPPYNESS; + nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04); + float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.08)-normal0.xy*bend; + float3 normal1 = 2.0 * shSample(tex, nCoord + float2(+timer*0.020,+timer*0.015)).rgb - 1.0; + + nCoord = coords * (WAVE_SCALE * 0.25) + windDir * timer * (windSpeed*0.07)-normal1.xy*bend; + float3 normal2 = 2.0 * shSample(tex, nCoord + float2(-timer*0.04,-timer*0.03)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.09)-normal2.xy*bend; + float3 normal3 = 2.0 * shSample(tex, nCoord + float2(+timer*0.03,+timer*0.04)).rgb - 1.0; + + nCoord = coords * (WAVE_SCALE* 1.0) + windDir * timer * (windSpeed*0.4)-normal3.xy*bend; + float3 normal4 = 2.0 * shSample(tex, nCoord + float2(-timer*0.2,+timer*0.1)).rgb - 1.0; + nCoord = coords * (WAVE_SCALE * 2.0) + windDir * timer * (windSpeed*0.7)-normal4.xy*bend; + float3 normal5 = 2.0 * shSample(tex, nCoord + float2(+timer*0.1,-timer*0.06)).rgb - 1.0; + + + float3 normal = normalize(normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); + return normal; +} + +float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) +{ + bend *= WAVE_CHOPPYNESS; + float3 col = float3(0.0); + float2 nCoord = float2(0.0); //normal coords + + nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03); + col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20; + nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.05)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.01,+timer*0.005)).rgb*0.20; + + nCoord = coords * (WAVE_SCALE * 0.2) + windDir * timer * (windSpeed*0.1)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(-timer*0.02,-timer*0.03)).rgb*0.20; + nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.2)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.03,+timer*0.02)).rgb*0.15; + + nCoord = coords * (WAVE_SCALE* 0.8) + windDir * timer * (windSpeed*1.0)-(col.xy/col.zz)*bend; + col += shSample(tex, nCoord + float2(-timer*0.06,+timer*0.08)).rgb*0.15; + nCoord = coords * (WAVE_SCALE * 1.0) + windDir * timer * (windSpeed*1.3)-(col.xy/col.zz)*bend; + col += shSample(tex,nCoord + float2(+timer*0.08,-timer*0.06)).rgb*0.10; + + return col; +} + + +float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 eyePosWS, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed) +{ + float3 waterEyePos = intercept(worldPos.xyz, eyePosWS - worldPos, float3(0,1,0), waterLevel); + float waterDepth = shSaturate((waterEyePos.y - worldPos.y) / 50.0); + + float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,1,0), waterLevel); + + ///\ todo clean this up + float causticdepth = length(causticPos-worldPos.xyz); + causticdepth = 1.0-shSaturate(causticdepth / VISIBILITY); + causticdepth = shSaturate(causticdepth); + + // NOTE: the original shader calculated a tangent space basis here, + // but using only the world normal is cheaper and i couldn't see a visual difference + // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information + float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xzy * 2 - 1; + + //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0); + + float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0); + + float causticR = 1.0-perturb(causticMap, causticPos.xz, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + + /// \todo sunFade + + // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; + float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; + float causticG = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + float causticB = 1.0-perturb(causticMap,causticPos.xz+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; + //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; + caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*causticdepth; + + caustics *= 3; + + // shore transition + caustics = shLerp (float3(1,1,1), caustics, waterDepth); + + return caustics; +} + diff --git a/files/materials/core.h b/files/materials/core.h index 34095f93d3..c4d2877617 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -69,7 +69,7 @@ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) - #define shMatrixMult(m, v) m * v + #define shMatrixMult(m, v) (m * v) // automatically recognized by ogre when the input name equals this #define shInputPosition vertex diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 96c6f8422b..9b0357f010 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -61,5 +61,10 @@ material openmw_objects_base tex_address_mode clamp filtering none } + + texture_unit causticMap + { + direct_texture water_nm.png + } } } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index eabdb8ca35..0c1867d668 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -9,13 +9,17 @@ #define SHADOWS LIGHTING && @shGlobalSettingBool(shadows) #if SHADOWS || SHADOWS_PSSM -#include "shadows.h" + #include "shadows.h" #endif #if FOG || MRT || SHADOWS_PSSM #define NEED_DEPTH #endif + +#define UNDERWATER LIGHTING + + #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) #ifdef SH_VERTEX_SHADER @@ -89,6 +93,10 @@ // ----------------------------------- FRAGMENT ------------------------------------------ +#if UNDERWATER + #include "caustics.h" +#endif + SH_BEGIN_PROGRAM shSampler2D(diffuseMap) shInput(float2, UV) @@ -145,6 +153,20 @@ #if SHADOWS || SHADOWS_PSSM shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) #endif + +#if UNDERWATER + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float, waterLevel) @shSharedParameter(waterLevel) + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + shUniform(float4, lightDirectionWS0) @shAutoConstant(lightDirectionWS0, light_position, 0) + + shSampler2D(causticMap) + + shUniform(float, waterTimer) @shSharedParameter(waterTimer) + + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) +#endif + SH_START_PROGRAM { shOutputColour(0) = shSample(diffuseMap, UV); @@ -174,20 +196,42 @@ #if !SHADOWS && !SHADOWS_PSSM float shadow = 1.0; #endif + + + + float3 caustics = float3(1,1,1); +#if UNDERWATER + float4 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)); + if (worldPos.y < waterLevel) + { + float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); + caustics = getCaustics(causticMap, worldPos.xyz, cameraPos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + } +#endif + @shForeach(@shGlobalSettingString(num_lights)) - + /// \todo use the array auto params for lights, and use a real for-loop with auto param "light_count" iterations lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); d = length(lightDir); lightDir = normalize(lightDir); -#if @shIterator == 0 && (SHADOWS || SHADOWS_PSSM) - diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; +#if @shIterator == 0 + + #if (SHADOWS || SHADOWS_PSSM) + diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow * caustics; + + #else + diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * caustics; + + #endif + #else diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); #endif + @shEndForeach #if HAS_VERTEXCOLOR diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 2b7091838f..722108a58d 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -22,6 +22,8 @@ #define NEED_DEPTH 1 #endif +#define UNDERWATER LIGHTING + #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -122,6 +124,10 @@ // ----------------------------------- FRAGMENT ------------------------------------------ +#if UNDERWATER + #include "caustics.h" +#endif + SH_BEGIN_PROGRAM @@ -180,6 +186,20 @@ #endif +#if UNDERWATER + shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) + shUniform(float, waterLevel) @shSharedParameter(waterLevel) + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + shUniform(float4, lightDirectionWS0) @shAutoConstant(lightDirectionWS0, light_position, 0) + + shSampler2D(causticMap) + + shUniform(float, waterTimer) @shSharedParameter(waterTimer) + + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) +#endif + + SH_START_PROGRAM { @@ -198,6 +218,19 @@ + float3 caustics = float3(1,1,1); +#if UNDERWATER + + float4 worldPos = shMatrixMult(worldMatrix, float4(objSpacePosition,1)); + if (worldPos.y < waterLevel) + { + float4 worldNormal = shMatrixMult(worldMatrix, float4(normal.xyz, 0)); + caustics = getCaustics(causticMap, worldPos.xyz, cameraPos.xyz, worldNormal.xyz, lightDirectionWS0.xyz, waterLevel, waterTimer, windDir_windSpeed); + } + +#endif + + // Layer calculations @shForeach(@shPropertyString(num_blendmaps)) float4 blendValues@shIterator = shSample(blendMap@shIterator, UV); @@ -272,11 +305,20 @@ lightDir = normalize(lightDir); -#if @shIterator == 0 && (SHADOWS || SHADOWS_PSSM) - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; +#if @shIterator == 0 + + #if (SHADOWS || SHADOWS_PSSM) + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow * caustics; + + #else + diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * caustics; + + #endif + #else diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); #endif + @shEndForeach shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); diff --git a/files/materials/water.mat b/files/materials/water.mat index c701e5ffe1..d74305c73b 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -1,30 +1,33 @@ -// note: the fixed function water is created manually, not here - -material openmw_water +material Water { pass { vertex_program water_vertex fragment_program water_fragment + cull_hardware none + texture_unit reflectionMap { texture_alias WaterReflection + tex_address_mode clamp } texture_unit refractionMap { texture_alias WaterRefraction + tex_address_mode clamp } texture_unit depthMap { - + texture_alias SceneDepth + tex_address_mode clamp } texture_unit normalMap { - texture + direct_texture water_nm.png } } } diff --git a/files/materials/water.shader b/files/materials/water.shader index e69de29bb2..4945770a5a 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -0,0 +1,228 @@ +#include "core.h" + +// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) + +#ifdef SH_VERTEX_SHADER + + SH_BEGIN_PROGRAM + shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) + shInput(float2, uv0) + shOutput(float2, UV) + + shOutput(float3, screenCoordsPassthrough) + shOutput(float4, position) + shOutput(float, depthPassthrough) + + SH_START_PROGRAM + { + shOutputPosition = shMatrixMult(wvp, shInputPosition); + UV = uv0; + + + #if !SH_GLSL + float4x4 scalemat = float4x4( 0.5, 0, 0, 0.5, + 0, -0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1 ); + #else + mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + #endif + + float4 texcoordProj = shMatrixMult(scalemat, shOutputPosition); + screenCoordsPassthrough = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w); + + position = shInputPosition; + + depthPassthrough = shOutputPosition.z; + } + +#else + + // tweakables ---------------------------------------------------- + + #define BIG_WAVES_X 0.3 // strength of big waves + #define BIG_WAVES_Y 0.3 + + #define MID_WAVES_X 0.3 // strength of middle sized waves + #define MID_WAVES_Y 0.15 + + #define SMALL_WAVES_X 0.15 // strength of small waves + #define SMALL_WAVES_Y 0.1 + + #define WAVE_CHOPPYNESS 0.15 // wave choppyness + #define WAVE_SCALE 150 // overall wave scale + + #define ABBERATION 0.001 // chromatic abberation amount + #define BUMP 1.5 // overall water surface bumpiness + #define REFL_BUMP 0.11 // reflection distortion amount + #define REFR_BUMP 0.08 // refraction distortion amount + + #define SCATTER_AMOUNT 3.0 // amount of sunlight scattering + #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering + + #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction + + #define SPEC_HARDNESS 256 // specular highlights hardness + + + // --------------------------------------------------------------- + + + + float fresnel_dielectric(float3 Incoming, float3 Normal, float eta) + { + /* compute fresnel reflectance without explicitly computing + the refracted direction */ + float c = abs(dot(Incoming, Normal)); + float g = eta * eta - 1.0 + c * c; + float result; + + if(g > 0.0) { + g = sqrt(g); + float A =(g - c)/(g + c); + float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0); + result = 0.5 * A * A *(1.0 + B * B); + } + else + result = 1.0; /* TIR (no refracted component) */ + + return result; + } + + SH_BEGIN_PROGRAM + shInput(float2, UV) + shInput(float3, screenCoordsPassthrough) + shInput(float4, position) + shInput(float, depthPassthrough) + + shUniform(float, far) @shAutoConstant(far, far_clip_distance) + + shSampler2D(reflectionMap) + shSampler2D(refractionMap) + shSampler2D(depthMap) + shSampler2D(normalMap) + + shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) + #define WIND_SPEED windDir_windSpeed.z + #define WIND_DIR windDir_windSpeed.xy + + shUniform(float, waterTimer) @shSharedParameter(waterTimer) + shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) + + shUniform(float4, sunPosition) @shAutoConstant(sunPosition, light_position, 0) + shUniform(float4, sunSpecular) @shAutoConstant(sunSpecular, light_specular_colour, 0) + + + + shUniform(float, renderTargetFlipping) @shAutoConstant(renderTargetFlipping, render_target_flipping) + + + shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) + shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) + + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position_object_space) + + + SH_START_PROGRAM + { + + float2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; + screenCoords.y = (1-shSaturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y; + + float depth = shSample(depthMap, screenCoords).x * far - depthPassthrough; + float shoreFade = shSaturate(depth / 50.0); + + float2 nCoord = float2(0.0); + + nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); + float3 normal0 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS; + float3 normal1 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS; + float3 normal2 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS; + float3 normal3 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS; + float3 normal4 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS; + float3 normal5 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0; + + + + float3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y).xzy; + + normal = normalize(float3(normal.x * BUMP, normal.y, normal.z * BUMP)); + + // normal for sunlight scattering + float3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + + normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xzy; + lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y, lNormal.z * BUMP)); + + + float3 lVec = normalize(sunPosition.xyz); + float3 vVec = normalize(position.xyz - cameraPos.xyz); + + + float isUnderwater = (cameraPos.y > 0) ? 0.0 : 1.0; + + // sunlight scattering + float3 pNormal = float3(0,1,0); + vec3 lR = reflect(lVec, lNormal); + vec3 llR = reflect(lVec, pNormal); + + float s = shSaturate(dot(lR, vVec)*2.0-1.2); + float lightScatter = shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y)); + float3 scatterColour = shLerp(vec3(SCATTER_COLOUR)*vec3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + + // fresnel + float ior = (cameraPos.y>0)?(1.333/1.0):(1.0/1.333); //air to water; water to air + float fresnel = fresnel_dielectric(-vVec, normal, ior); + + fresnel = shSaturate(fresnel); + + // reflection + float3 reflection = shSample(reflectionMap, screenCoords+(normal.xz*REFL_BUMP)).rgb; + + // refraction + float3 R = reflect(vVec, normal); + + float3 refraction = float3(0,0,0); + refraction.r = shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP))*1.0).r; + refraction.g = shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP))*1.0-(R.xy*ABBERATION)).g; + refraction.b = shSample(refractionMap, (screenCoords-(shoreFade * normal.xz*REFR_BUMP))*1.0-(R.xy*ABBERATION*2.0)).b; + + // brighten up the refraction underwater + refraction = (cameraPos.y < 0) ? shSaturate(refraction * 1.5) : refraction; + + float waterSunGradient = dot(-vVec, -lVec); + waterSunGradient = shSaturate(pow(waterSunGradient*0.7+0.3,2.0)); + float3 waterSunColour = float3(0.0,1.0,0.85)*waterSunGradient * 0.5; + + float waterGradient = dot(-vVec, float3(0.0,-1.0,0.0)); + waterGradient = clamp((waterGradient*0.5+0.5),0.2,1.0); + float3 watercolour = (float3(0.0078, 0.5176, 0.700)+waterSunColour)*waterGradient*2.0; + float3 waterext = float3(0.6, 0.9, 1.0);//water extinction + watercolour = mix(watercolour*0.3*waterSunFade_sunHeight.x, watercolour, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); + + // specular + float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS); + + shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz; + + // smooth transition to shore (above water only) + shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, refraction, (1-shoreFade) * (1-isUnderwater)); + + // fog + float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); + shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); + } + +#endif diff --git a/files/materials/water.shaderset b/files/materials/water.shaderset index e69de29bb2..754ac8e49f 100644 --- a/files/materials/water.shaderset +++ b/files/materials/water.shaderset @@ -0,0 +1,15 @@ +shader_set water_vertex +{ + source water.shader + type vertex + profiles_cg vs_2_0 vp40 arbvp1 + profiles_hlsl vs_2_0 +} + +shader_set water_fragment +{ + source water.shader + type fragment + profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 + profiles_hlsl ps_2_0 +} diff --git a/files/water/underwater_dome.mesh b/files/water/underwater_dome.mesh new file mode 100644 index 0000000000..64ca569c22 Binary files /dev/null and b/files/water/underwater_dome.mesh differ diff --git a/files/water/water.compositor b/files/water/water.compositor index 8d9c3cb396..94b778773d 100644 --- a/files/water/water.compositor +++ b/files/water/water.compositor @@ -39,7 +39,6 @@ compositor Underwater { material Water/Compositor input 0 rt0 - input 3 scene 1 } } } diff --git a/files/water/water.material b/files/water/water.material index d1f7fcf494..7376d10cc9 100644 --- a/files/water/water.material +++ b/files/water/water.material @@ -44,7 +44,7 @@ fragment_program Water_FP cg profiles ps_2_x arbfp1 } -material Water +material __Water { technique { diff --git a/files/water/water_nm.png b/files/water/water_nm.png new file mode 100644 index 0000000000..361431a0ef Binary files /dev/null and b/files/water/water_nm.png differ