#include "skyutil.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/weather.hpp" #include "vismask.hpp" #include "renderbin.hpp" namespace { enum class Pass { Atmosphere, Atmosphere_Night, Clouds, Moon, Sun, Sunflash_Query, Sunglare, }; osg::ref_ptr createTexturedQuad(int numUvSets = 1, float scale = 1.f) { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; verts->push_back(osg::Vec3f(-0.5 * scale, -0.5 * scale, 0)); verts->push_back(osg::Vec3f(-0.5 * scale, 0.5 * scale, 0)); verts->push_back(osg::Vec3f(0.5 * scale, 0.5 * scale, 0)); verts->push_back(osg::Vec3f(0.5 * scale, -0.5 * scale, 0)); geom->setVertexArray(verts); osg::ref_ptr texcoords = new osg::Vec2Array; texcoords->push_back(osg::Vec2f(0, 1)); texcoords->push_back(osg::Vec2f(0, 0)); texcoords->push_back(osg::Vec2f(1, 0)); texcoords->push_back(osg::Vec2f(1, 1)); osg::ref_ptr colors = new osg::Vec4Array; colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); geom->setColorArray(colors, osg::Array::BIND_OVERALL); for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); return geom; } struct DummyComputeBoundCallback : osg::Node::ComputeBoundingSphereCallback { osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } }; } namespace MWRender { osg::ref_ptr createUnlitMaterial(osg::Material::ColorMode colorMode) { osg::ref_ptr mat = new osg::Material; mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); mat->setColorMode(colorMode); return mat; } osg::ref_ptr createAlphaTrackingUnlitMaterial() { return createUnlitMaterial(osg::Material::DIFFUSE); } class SunUpdater : public SceneUtil::StateSetUpdater { public: osg::Vec4f mColor; SunUpdater() : mColor(1.f, 1.f, 1.f, 1.f) { } void setDefaults(osg::StateSet* stateset) override { stateset->setAttributeAndModes(createUnlitMaterial()); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); } }; OcclusionCallback::OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) : mOcclusionQueryVisiblePixels(oqnVisible) , mOcclusionQueryTotalPixels(oqnTotal) { } float OcclusionCallback::getVisibleRatio (osg::Camera* camera) { int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); float visibleRatio = 0.f; if (total > 0) visibleRatio = static_cast(visible) / static_cast(total); float dt = MWBase::Environment::get().getFrameDuration(); float lastRatio = mLastRatio[osg::observer_ptr(camera)]; float change = dt*10; if (visibleRatio > lastRatio) visibleRatio = std::min(visibleRatio, lastRatio + change); else visibleRatio = std::max(visibleRatio, lastRatio - change); mLastRatio[osg::observer_ptr(camera)] = visibleRatio; return visibleRatio; } /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) : OcclusionCallback(oqnVisible, oqnTotal) , mGlareView(1.f) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); osg::ref_ptr stateset; if (visibleRatio > 0.f) { const float fadeThreshold = 0.1; if (visibleRatio < fadeThreshold) { float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; osg::ref_ptr mat (createUnlitMaterial()); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); stateset = new osg::StateSet; stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } else if (visibleRatio < 1.f) { const float threshold = 0.6; visibleRatio = visibleRatio * (1.f - threshold) + threshold; } } float scale = visibleRatio; if (scale == 0.f) { // no traverse return; } else if (scale == 1.f) traverse(node, cv); else { osg::Matrix modelView = *cv->getModelViewMatrix(); modelView.preMultScale(osg::Vec3f(scale, scale, scale)); if (stateset) cv->pushStateSet(stateset); cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); if (stateset) cv->popStateSet(); } } void setGlareView(float value) { mGlareView = value; } private: float mGlareView; }; /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. /// Must be attached as a cull callback to the node above the glare node. class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, osg::ref_ptr sunTransform) : OcclusionCallback(oqnVisible, oqnTotal) , mSunTransform(sunTransform) , mTimeOfDayFade(1.f) , mGlareView(1.f) { mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, // so the resulting color looks more orange than red. mColor *= 2; for (int i=0; i<3; ++i) mColor[i] = std::min(1.f, mColor[i]); } void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) { float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); float fade = value * mSunGlareFaderMax; fade *= mTimeOfDayFade * mGlareView * visibleRatio; if (fade == 0.f) { // no traverse return; } else { osg::ref_ptr stateset = new osg::StateSet; osg::ref_ptr mat = createUnlitMaterial(); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); stateset->setAttributeAndModes(mat); cv->pushStateSet(stateset); traverse(node, cv); cv->popStateSet(); } } void setTimeOfDayFade(float val) { mTimeOfDayFade = val; } void setGlareView(float glareView) { mGlareView = glareView; } private: float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const { osg::Vec3d eye, center, up; viewMatrix.getLookAt(eye, center, up); osg::Vec3d forward = center - eye; osg::Vec3d sun = mSunTransform->getPosition(); forward.normalize(); sun.normalize(); float angleRadians = std::acos(forward * sun); return angleRadians; } osg::ref_ptr mSunTransform; float mTimeOfDayFade; float mGlareView; osg::Vec4f mColor; float mSunGlareFaderMax; float mSunGlareFaderAngleMax; }; struct MoonUpdater : SceneUtil::StateSetUpdater { Resource::ImageManager& mImageManager; osg::ref_ptr mPhaseTex; osg::ref_ptr mCircleTex; float mTransparency; float mShadowBlend; osg::Vec4f mAtmosphereColor; osg::Vec4f mMoonColor; bool mForceShaders; MoonUpdater(Resource::ImageManager& imageManager, bool forceShaders) : mImageManager(imageManager) , mPhaseTex() , mCircleTex() , mTransparency(1.0f) , mShadowBlend(1.0f) , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) , mForceShaders(forceShaders) { } void setDefaults(osg::StateSet* stateset) override { if (mForceShaders) { stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon))); stateset->setTextureAttributeAndModes(0, mPhaseTex); stateset->setTextureAttributeAndModes(1, mCircleTex); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); stateset->addUniform(new osg::Uniform("atmosphereFade", osg::Vec4f{})); stateset->addUniform(new osg::Uniform("diffuseMap", 0)); stateset->addUniform(new osg::Uniform("maskMap", 1)); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } else { stateset->setTextureAttributeAndModes(0, mPhaseTex); osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor stateset->setTextureAttributeAndModes(0, texEnv); stateset->setTextureAttributeAndModes(1, mCircleTex); osg::ref_ptr texEnv2 = new osg::TexEnvCombine; texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency stateset->setTextureAttributeAndModes(1, texEnv2); stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override { if (mForceShaders) { stateset->setTextureAttribute(0, mPhaseTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureAttribute(1, mCircleTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); if (auto* uMoonBlend = stateset->getUniform("moonBlend")) uMoonBlend->set(mMoonColor * mShadowBlend); if (auto* uAtmosphereFade = stateset->getUniform("atmosphereFade")) uAtmosphereFade->set(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); } else { osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(mMoonColor * mShadowBlend); osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); } } void setTextures(const std::string& phaseTex, const std::string& circleTex) { mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); reset(); } }; class CameraRelativeTransformCullCallback : public SceneUtil::NodeCallback { public: void operator() (osg::Node* node, osgUtil::CullVisitor* cv) { // XXX have to remove unwanted culling plane of the water reflection camera // Remove all planes that aren't from the standard frustum unsigned int numPlanes = 4; if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) ++numPlanes; if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) ++numPlanes; unsigned int mask = 0x1; unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) { if (i >= numPlanes) { // turn off this culling plane resultMask &= (~mask); } mask <<= 1; } cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); cv->getProjectionCullingStack().back().pushCurrentMask(); cv->getCurrentCullingSet().pushCurrentMask(); traverse(node, cv); cv->getProjectionCullingStack().back().popCurrentMask(); cv->getCurrentCullingSet().popCurrentMask(); } }; void AtmosphereUpdater::setEmissionColor(const osg::Vec4f& emissionColor) { mEmissionColor = emissionColor; } void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) { stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere))); } void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); } AtmosphereNightUpdater::AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders) : mColor(osg::Vec4f(0,0,0,0)) , mTexture(new osg::Texture2D(imageManager->getWarningImage())) , mForceShaders(forceShaders) { } void AtmosphereNightUpdater::setFade(float fade) { mColor.a() = fade; } void AtmosphereNightUpdater::setDefaults(osg::StateSet* stateset) { if (mForceShaders) { stateset->addUniform(new osg::Uniform("opacity", 0.f)); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night))); } else { osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } void AtmosphereNightUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { if (mForceShaders) { stateset->getUniform("opacity")->set(mColor.a()); } else { osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(mColor); } } CloudUpdater::CloudUpdater(bool forceShaders) : mOpacity(0.f) , mForceShaders(forceShaders) { } void CloudUpdater::setTexture(osg::ref_ptr texture) { mTexture = texture; } void CloudUpdater::setEmissionColor(const osg::Vec4f& emissionColor) { mEmissionColor = emissionColor; } void CloudUpdater::setOpacity(float opacity) { mOpacity = opacity; } void CloudUpdater::setTextureCoord(float timer) { mTexMat = osg::Matrixf::translate(osg::Vec3f(0.f, -timer, 0.f)); } void CloudUpdater::setDefaults(osg::StateSet *stateset) { stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::ref_ptr texmat = new osg::TexMat; stateset->setTextureAttributeAndModes(0, texmat); if (mForceShaders) { stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("opacity", 1.f)); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds))); } else { stateset->setTextureAttributeAndModes(1, texmat); // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); stateset->setTextureAttributeAndModes(1, texEnvCombine); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } void CloudUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); texMat->setMatrix(mTexMat); if (mForceShaders) { stateset->getUniform("opacity")->set(mOpacity); } else { stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); texEnv->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); } } CameraRelativeTransform::CameraRelativeTransform() { // Culling works in node-local space, not in camera space, so we can't cull this node correctly // That's not a problem though, children of this node can be culled just fine // Just make sure you do not place a CameraRelativeTransform deep in the scene graph setCullingActive(false); addCullCallback(new CameraRelativeTransformCullCallback); } CameraRelativeTransform::CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) : osg::Transform(copy, copyop) { } const osg::Vec3f& CameraRelativeTransform::getLastViewPoint() const { return mViewPoint; } bool CameraRelativeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const { if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { mViewPoint = static_cast(nv)->getViewPoint(); } if (_referenceFrame==RELATIVE_RF) { matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); return false; } else // absolute { matrix.makeIdentity(); return true; } } osg::BoundingSphere CameraRelativeTransform::computeBound() const { return osg::BoundingSphere(); } UnderwaterSwitchCallback::UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) : mCameraRelativeTransform(cameraRelativeTransform) , mEnabled(true) , mWaterLevel(0.f) { } bool UnderwaterSwitchCallback::isUnderwater() { osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); return mEnabled && viewPoint.z() < mWaterLevel; } void UnderwaterSwitchCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) { if (isUnderwater()) return; traverse(node, nv); } void UnderwaterSwitchCallback::setEnabled(bool enabled) { mEnabled = enabled; } void UnderwaterSwitchCallback::setWaterLevel(float waterLevel) { mWaterLevel = waterLevel; } const float CelestialBody::mDistance = 1000.0f; CelestialBody::CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask) : mVisibleMask(visibleMask) { mGeom = createTexturedQuad(numUvSets); mGeom->getOrCreateStateSet(); mTransform = new osg::PositionAttitudeTransform; mTransform->setNodeMask(mVisibleMask); mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); mTransform->addChild(mGeom); parentNode->addChild(mTransform); } void CelestialBody::setVisible(bool visible) { mTransform->setNodeMask(visible ? mVisibleMask : 0); } Sun::Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new SunUpdater) { mTransform->addUpdateCallback(mUpdater); osg::ref_ptr sunTex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")); sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); sunTex->setName("diffuseMap"); mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex); mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); osg::ref_ptr queryNode = new osg::Group; // Need to render after the world geometry so we can correctly test for occlusions osg::StateSet* stateset = queryNode->getOrCreateStateSet(); stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); stateset->setNestRenderBins(false); // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun osg::ref_ptr alphaFunc = new osg::AlphaFunc; alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); stateset->setAttributeAndModes(alphaFunc); stateset->setTextureAttributeAndModes(0, sunTex); stateset->setAttributeAndModes(createUnlitMaterial()); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query))); // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); stateset->setAttributeAndModes(colormask); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); createSunFlash(imageManager); createSunGlare(); } Sun::~Sun() { mTransform->removeUpdateCallback(mUpdater); destroySunFlash(); destroySunGlare(); } void Sun::setColor(const osg::Vec4f& color) { mUpdater->mColor.r() = color.r(); mUpdater->mColor.g() = color.g(); mUpdater->mColor.b() = color.b(); } void Sun::adjustTransparency(const float ratio) { mUpdater->mColor.a() = ratio; if (mSunGlareCallback) mSunGlareCallback->setGlareView(ratio); if (mSunFlashCallback) mSunFlashCallback->setGlareView(ratio); } void Sun::setDirection(const osg::Vec3f& direction) { osg::Vec3f normalizedDirection = direction / direction.length(); mTransform->setPosition(normalizedDirection * mDistance); osg::Quat quat; quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); mTransform->setAttitude(quat); } void Sun::setGlareTimeOfDayFade(float val) { if (mSunGlareCallback) mSunGlareCallback->setTimeOfDayFade(val); } osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) { osg::ref_ptr oqn = new osg::OcclusionQueryNode; oqn->setQueriesEnabled(true); #if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); #else osg::ref_ptr queryGeom = oqn->getQueryGeometry(); #endif // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry // is only called once. // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. queryGeom->setDataVariance(osg::Object::STATIC); // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to // circumvent this. queryGeom->setVertexArray(mGeom->getVertexArray()); queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); // Still need a proper bounding sphere. oqn->setInitialBound(queryGeom->getBound()); #if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) oqn->setQueryGeometry(queryGeom.release()); #endif osg::StateSet* queryStateSet = new osg::StateSet; if (queryVisible) { auto depth = SceneUtil::createDepth(); // This is a trick to make fragments written by the query always use the maximum depth value, // without having to retrieve the current far clipping distance. // We want the sun glare to be "infinitely" far away. double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); queryStateSet->setAttributeAndModes(depth); } else { queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); } oqn->setQueryStateSet(queryStateSet); parent->addChild(oqn); return oqn; } void Sun::createSunFlash(Resource::ImageManager& imageManager) { osg::ref_ptr tex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds")); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); tex->setName("diffuseMap"); osg::ref_ptr group (new osg::Group); mTransform->addChild(group); const float scale = 2.6f; osg::ref_ptr geom = createTexturedQuad(1, scale); group->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, tex); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); mSunFlashNode = group; mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); mSunFlashNode->addCullCallback(mSunFlashCallback); } void Sun::destroySunFlash() { if (mSunFlashNode) { mSunFlashNode->removeCullCallback(mSunFlashCallback); mSunFlashCallback = nullptr; } } void Sun::createSunGlare() { osg::ref_ptr camera = new osg::Camera; camera->setProjectionMatrix(osg::Matrix::identity()); camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? camera->setViewMatrix(osg::Matrix::identity()); camera->setClearMask(0); camera->setRenderOrder(osg::Camera::NESTED_RENDER); camera->setAllowEventFocus(false); osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); camera->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare))); // set up additive blending osg::ref_ptr blendFunc = new osg::BlendFunc; blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); blendFunc->setDestination(osg::BlendFunc::ONE); stateset->setAttributeAndModes(blendFunc); mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); mSunGlareNode = camera; mSunGlareNode->addCullCallback(mSunGlareCallback); mTransform->addChild(camera); } void Sun::destroySunGlare() { if (mSunGlareNode) { mSunGlareNode->removeCullCallback(mSunGlareCallback); mSunGlareCallback = nullptr; } } Moon::Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type) : CelestialBody(parentNode, scaleFactor, 2) , mType(type) , mPhase(MoonState::Phase::Unspecified) , mUpdater(new MoonUpdater(*sceneManager.getImageManager(), sceneManager.getForceShaders())) { setPhase(MoonState::Phase::Full); setVisible(true); mGeom->addUpdateCallback(mUpdater); } Moon::~Moon() { mGeom->removeUpdateCallback(mUpdater); } void Moon::adjustTransparency(const float ratio) { mUpdater->mTransparency *= ratio; } void Moon::setState(const MoonState state) { float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); mTransform->setPosition(direction * mDistance); // The moon quad is initially oriented facing down, so we need to offset its X-axis // rotation to rotate it to face the camera when sitting at the horizon. osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); mTransform->setAttitude(attX * rotZ); setPhase(state.mPhase); mUpdater->mTransparency = state.mMoonAlpha; mUpdater->mShadowBlend = state.mShadowBlend; } void Moon::setAtmosphereColor(const osg::Vec4f& color) { mUpdater->mAtmosphereColor = color; } void Moon::setColor(const osg::Vec4f& color) { mUpdater->mMoonColor = color; } unsigned int Moon::getPhaseInt() const { switch (mPhase) { case MoonState::Phase::New: return 0; case MoonState::Phase::WaxingCrescent: return 1; case MoonState::Phase::WaningCrescent: return 1; case MoonState::Phase::FirstQuarter: return 2; case MoonState::Phase::ThirdQuarter: return 2; case MoonState::Phase::WaxingGibbous: return 3; case MoonState::Phase::WaningGibbous: return 3; case MoonState::Phase::Full: return 4; default: return 0; } } void Moon::setPhase(const MoonState::Phase& phase) { if(mPhase == phase) return; mPhase = phase; std::string textureName = "textures/tx_"; if (mType == Moon::Type_Secunda) textureName += "secunda_"; else textureName += "masser_"; switch (mPhase) { case MoonState::Phase::New: textureName += "new"; break; case MoonState::Phase::WaxingCrescent: textureName += "one_wax"; break; case MoonState::Phase::FirstQuarter: textureName += "half_wax"; break; case MoonState::Phase::WaxingGibbous: textureName += "three_wax"; break; case MoonState::Phase::WaningCrescent: textureName += "one_wan"; break; case MoonState::Phase::ThirdQuarter: textureName += "half_wan"; break; case MoonState::Phase::WaningGibbous: textureName += "three_wan"; break; case MoonState::Phase::Full: textureName += "full"; break; default: break; } textureName += ".dds"; if (mType == Moon::Type_Secunda) mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); else mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); } int RainCounter::numParticlesToCreate(double dt) const { // limit dt to avoid large particle emissions if there are jumps in the simulation time // 0.2 seconds is the same cap as used in Engine's frame loop dt = std::min(dt, 0.2); return ConstantRateCounter::numParticlesToCreate(dt); } RainShooter::RainShooter() : mAngle(0.f) { } void RainShooter::shoot(osgParticle::Particle* particle) const { particle->setVelocity(mVelocity); particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); } void RainShooter::setVelocity(const osg::Vec3f& velocity) { mVelocity = velocity; } void RainShooter::setAngle(float angle) { mAngle = angle; } osg::Object* RainShooter::cloneType() const { return new RainShooter; } osg::Object* RainShooter::clone(const osg::CopyOp &) const { return new RainShooter(*this); } ModVertexAlphaVisitor::ModVertexAlphaVisitor(ModVertexAlphaVisitor::MeshType type) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mType(type) { } void ModVertexAlphaVisitor::apply(osg::Geometry& geometry) { osg::ref_ptr colors = new osg::Vec4Array(geometry.getVertexArray()->getNumElements()); for (unsigned int i=0; isize(); ++i) { float alpha = 1.f; switch (mType) { case ModVertexAlphaVisitor::Atmosphere: { // this is a cylinder, so every second vertex belongs to the bottom-most row alpha = (i%2) ? 0.f : 1.f; break; } case ModVertexAlphaVisitor::Clouds: { if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row else if (i>= 33 && i <= 48) alpha = 0.25098; // second row else alpha = 1.f; break; } case ModVertexAlphaVisitor::Stars: { if (geometry.getColorArray()) { osg::Vec4Array* origColors = static_cast(geometry.getColorArray()); alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; } else alpha = 1.f; break; } } (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); } geometry.setColorArray(colors, osg::Array::BIND_PER_VERTEX); } }