#include "water.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include // XXX remove #include #include #include #include #include #include #include #include #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" namespace { osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats) { osg::ref_ptr verts (new osg::Vec3Array); osg::ref_ptr texcoords (new osg::Vec2Array); // some drivers don't like huge triangles, so we do some subdivisons // a paged solution would be even better const float step = size/segments; const float texCoordStep = textureRepeats / segments; for (int x=0; xpush_back(osg::Vec3f(x1, y2, 0.f)); verts->push_back(osg::Vec3f(x1, y1, 0.f)); verts->push_back(osg::Vec3f(x2, y1, 0.f)); verts->push_back(osg::Vec3f(x2, y2, 0.f)); float u1 = x*texCoordStep; float v1 = y*texCoordStep; float u2 = u1 + texCoordStep; float v2 = v1 + texCoordStep; texcoords->push_back(osg::Vec2f(u1, v2)); texcoords->push_back(osg::Vec2f(u1, v1)); texcoords->push_back(osg::Vec2f(u2, v1)); texcoords->push_back(osg::Vec2f(u2, v2)); } } osg::ref_ptr waterGeom (new osg::Geometry); waterGeom->setVertexArray(verts); waterGeom->setTexCoordArray(0, texcoords); waterGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,verts->size())); return waterGeom; } void createSimpleWaterStateSet(Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) { osg::ref_ptr stateset (new osg::StateSet); osg::ref_ptr material (new osg::Material); material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.7f)); material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); material->setColorMode(osg::Material::OFF); stateset->setAttributeAndModes(material, osg::StateAttribute::ON); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); osg::ref_ptr depth (new osg::Depth); depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); std::vector > textures; for (int i=0; i<32; ++i) { std::ostringstream texname; texname << "textures/water/water" << std::setw(2) << std::setfill('0') << i << ".dds"; textures.push_back(resourceSystem->getTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); } osg::ref_ptr controller (new NifOsg::FlipController(0, 2/32.f, textures)); controller->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); node->addUpdateCallback(controller); node->setStateSet(stateset); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); } } namespace MWRender { // -------------------------------------------------------------------------------------------------------------------------------- /// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. /// Also handles flipping of the plane when the eye point goes below it. /// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); class ClipCullNode : public osg::Group { class PlaneCullCallback : public osg::NodeCallback { public: /// @param cullPlane The culling plane (in world space). PlaneCullCallback(const osg::Plane* cullPlane) : osg::NodeCallback() , mCullPlane(cullPlane) { } virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); osg::Plane plane = *mCullPlane; plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); osg::Vec3d eyePoint = cv->getEyePoint(); if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) plane.flip(); cv->getProjectionCullingStack().back().getFrustum().add(plane); traverse(node, nv); // undo cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); } private: const osg::Plane* mCullPlane; }; class FlipCallback : public osg::NodeCallback { public: FlipCallback(const osg::Plane* cullPlane) : mCullPlane(cullPlane) { } virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); osg::Vec3d eyePoint = cv->getEyePoint(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); // flip the below graph if the eye point is above the plane if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) { modelViewMatrix->preMultScale(osg::Vec3(1,1,-1)); } // move the plane back along its normal a little bit to prevent bleeding at the water shore const float clipFudge = 5; modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * (-clipFudge)); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); traverse(node, nv); cv->popModelViewMatrix(); } private: const osg::Plane* mCullPlane; }; public: ClipCullNode() { addCullCallback (new PlaneCullCallback(&mPlane)); mClipNodeTransform = new osg::PositionAttitudeTransform; mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); addChild(mClipNodeTransform); mClipNode = new osg::ClipNode; mClipNodeTransform->addChild(mClipNode); } void setPlane (const osg::Plane& plane) { if (plane == mPlane) return; mPlane = plane; mClipNode->getClipPlaneList().clear(); mClipNode->addClipPlane(new osg::ClipPlane(0, mPlane)); mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); } private: osg::ref_ptr mClipNodeTransform; osg::ref_ptr mClipNode; osg::Plane mPlane; }; // Node callback to entirely skip the traversal. class NoTraverseCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { // no traverse() } }; void addDebugOverlay(osg::Texture2D* texture, int pos, osg::Group* parent) { osg::ref_ptr debugCamera (new osg::Camera); debugCamera->setProjectionMatrix(osg::Matrix::identity()); debugCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); debugCamera->setViewMatrix(osg::Matrix::identity()); debugCamera->setClearMask(0); debugCamera->setRenderOrder(osg::Camera::NESTED_RENDER); debugCamera->setAllowEventFocus(false); const float size = 0.5; osg::ref_ptr debugGeode (new osg::Geode); osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1 + size*pos, -1, 0), osg::Vec3f(size,0,0), osg::Vec3f(0,size,0)); debugGeode->addDrawable(geom); debugCamera->addChild(debugGeode); osg::StateSet* debugStateset = geom->getOrCreateStateSet(); debugStateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); debugStateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); debugStateset->setRenderBinDetails(20, "RenderBin"); debugStateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); parent->addChild(debugCamera); } Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, const MWWorld::Fallback* fallback, const std::string& resourcePath) : mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) , mEnabled(true) , mToggled(true) , mTop(0) { mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); osg::ref_ptr waterGeom = createWaterGeometry(CELL_SIZE*150, 40, 900); osg::ref_ptr geode (new osg::Geode); geode->addDrawable(waterGeom); geode->setNodeMask(Mask_Water); // TODO: node mask to use simple water for local map if (ico) ico->add(geode); //createSimpleWaterStateSet(mResourceSystem, geode); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->addChild(geode); mSceneRoot->addChild(mWaterNode); setHeight(mTop); const float waterLevel = -1; // refraction unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); osg::ref_ptr refractionCamera (new osg::Camera); refractionCamera->setRenderOrder(osg::Camera::PRE_RENDER); refractionCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); refractionCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); refractionCamera->setReferenceFrame(osg::Camera::RELATIVE_RF); refractionCamera->setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|(1<<16)); refractionCamera->setNodeMask(Mask_RenderToTexture); refractionCamera->setViewport(0, 0, rttSize, rttSize); // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) refractionCamera->setUpdateCallback(new NoTraverseCallback); // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog refractionCamera->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr clipNode (new ClipCullNode); clipNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel))); refractionCamera->addChild(clipNode); clipNode->addChild(mSceneRoot); // TODO: add ingame setting for texture quality osg::ref_ptr refractionTexture = new osg::Texture2D; refractionTexture->setTextureSize(rttSize, rttSize); refractionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); refractionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); refractionTexture->setInternalFormat(GL_RGB); refractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); refractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); refractionCamera->attach(osg::Camera::COLOR_BUFFER, refractionTexture); osg::ref_ptr refractionDepthTexture = new osg::Texture2D; refractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); refractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24_ARB); refractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); refractionDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); refractionDepthTexture->setSourceType(GL_UNSIGNED_INT); refractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); refractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); refractionCamera->attach(osg::Camera::DEPTH_BUFFER, refractionDepthTexture); mParent->addChild(refractionCamera); // reflection osg::ref_ptr reflectionCamera (new osg::Camera); reflectionCamera->setRenderOrder(osg::Camera::PRE_RENDER); reflectionCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); reflectionCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); reflectionCamera->setReferenceFrame(osg::Camera::RELATIVE_RF); reflectionCamera->setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|(1<<16)); reflectionCamera->setNodeMask(Mask_RenderToTexture); reflectionCamera->setViewport(0, 0, rttSize, rttSize); // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) reflectionCamera->setUpdateCallback(new NoTraverseCallback); osg::ref_ptr reflectionTexture = new osg::Texture2D; reflectionTexture->setInternalFormat(GL_RGB); reflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); reflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); reflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); reflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); reflectionCamera->attach(osg::Camera::COLOR_BUFFER, reflectionTexture); reflectionCamera->setViewMatrix(osg::Matrix::translate(0,0,-waterLevel) * osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,waterLevel)); osg::ref_ptr reflectNode (new osg::MatrixTransform); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace (new osg::FrontFace); frontFace->setMode(osg::FrontFace::CLOCKWISE); reflectNode->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); osg::ref_ptr clipNode2 (new ClipCullNode); clipNode2->setPlane(osg::Plane(osg::Vec3d(0,0,1), osg::Vec3d(0,0,waterLevel))); reflectNode->addChild(clipNode2); clipNode2->addChild(mSceneRoot); reflectionCamera->addChild(reflectNode); // TODO: add to waterNode so cameras don't get updated when water is hidden? mParent->addChild(reflectionCamera); // debug overlay addDebugOverlay(refractionTexture, 0, mParent); addDebugOverlay(refractionDepthTexture, 1, mParent); addDebugOverlay(reflectionTexture, 2, mParent); // shader // FIXME: windows utf8 path handling? osg::ref_ptr vertexShader (osg::Shader::readShaderFile(osg::Shader::VERTEX, resourcePath + "/shaders/water_vertex.glsl")); osg::ref_ptr fragmentShader (osg::Shader::readShaderFile(osg::Shader::FRAGMENT, resourcePath + "/shaders/water_fragment.glsl")); osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); osg::ref_ptr normalMap (new osg::Texture2D(osgDB::readImageFile(resourcePath + "/shaders/water_nm.png"))); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); normalMap->getImage()->flipVertical(); osg::ref_ptr shaderStateset = new osg::StateSet; shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); shaderStateset->addUniform(new osg::Uniform("reflectionMap", 0)); shaderStateset->addUniform(new osg::Uniform("refractionMap", 1)); shaderStateset->addUniform(new osg::Uniform("refractionDepthMap", 2)); shaderStateset->addUniform(new osg::Uniform("normalMap", 3)); shaderStateset->setTextureAttributeAndModes(0, reflectionTexture, osg::StateAttribute::ON); shaderStateset->setTextureAttributeAndModes(1, refractionTexture, osg::StateAttribute::ON); shaderStateset->setTextureAttributeAndModes(2, refractionDepthTexture, osg::StateAttribute::ON); shaderStateset->setTextureAttributeAndModes(3, normalMap, osg::StateAttribute::ON); shaderStateset->setMode(GL_BLEND, osg::StateAttribute::ON); // TODO: set Off when refraction is on shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); osg::ref_ptr depth (new osg::Depth); depth->setWriteMask(false); shaderStateset->setAttributeAndModes(depth, osg::StateAttribute::ON); // TODO: render after transparent bin when refraction is on shaderStateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); geode->setStateSet(shaderStateset); } Water::~Water() { mParent->removeChild(mWaterNode); } void Water::setEnabled(bool enabled) { mEnabled = enabled; updateVisible(); } void Water::changeCell(const MWWorld::CellStore* store) { if (store->getCell()->isExterior()) mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY)); else mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); // create a new StateSet to prevent threading issues osg::ref_ptr nodeStateSet (new osg::StateSet); nodeStateSet->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWaterNode->getPosition()))); mWaterNode->setStateSet(nodeStateSet); } void Water::setHeight(const float height) { mTop = height; mSimulation->setWaterHeight(height); osg::Vec3f pos = mWaterNode->getPosition(); pos.z() = height; mWaterNode->setPosition(pos); } void Water::update(float dt) { mSimulation->update(dt); } void Water::updateVisible() { mWaterNode->setNodeMask(mEnabled && mToggled ? ~0 : 0); } bool Water::toggle() { mToggled = !mToggled; updateVisible(); return mToggled; } bool Water::isUnderwater(const osg::Vec3f &pos) const { return pos.z() < mTop && mToggled && mEnabled; } osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) { return osg::Vec3f(static_cast(gridX * CELL_SIZE + (CELL_SIZE / 2)), static_cast(gridY * CELL_SIZE + (CELL_SIZE / 2)), mTop); } void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) { mSimulation->addEmitter (ptr, scale, force); } void Water::removeEmitter (const MWWorld::Ptr& ptr) { mSimulation->removeEmitter (ptr); } void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) { mSimulation->updateEmitterPtr(old, ptr); } void Water::removeCell(const MWWorld::CellStore *store) { mSimulation->removeCell(store); } void Water::clearRipples() { mSimulation->clear(); } }