// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" // #include "nel/3d/u_instance_material.h" #include "nel/3d/u_camera.h" #include "nel/3d/u_scene.h" #include "nel/3d/u_instance.h" // #include "world_database_manager.h" #include "continent_manager.h" #include "weather_manager_client.h" #include "weather.h" #include "sky_render.h" #include "sky_material_setup.h" #include "light_cycle_manager.h" #include "global.h" H_AUTO_DECL(RZ_SkyRender) using namespace NL3D; using namespace NLMISC; UScene *SkyScene = NULL; UInstance Sky = NULL; UInstance Sky2ndPass = NULL; NL3D::UInstance SkyFogPart = NULL; ///////////// // GLOBALS // ///////////// // Hierarchical timer H_AUTO_DECL ( RZ_Client_Render_Sky ) extern UDriver *Driver; extern UScene *Scene; //=================================================================================================== void createSkyScene() { if (SkyScene) return; if (!Scene) return; // Create the scene for the sky. SkyScene = Driver->createScene(true); // enable Scene Lighting SkyScene->enableLightingSystem(true); SkyScene->setAmbientGlobal(CRGBA::Black); } //=================================================================================================== void deleteSkyScene() { if (!SkyScene) return; Driver->deleteScene(SkyScene); SkyScene = NULL; } //=================================================================================================== static void applySkyMaterialSetup(UInstance instance, bool isNight, uint stage, bool skipFirstMaterial = false) { H_AUTO_USE(RZ_SkyRender) if(instance.empty()) return; // Return if there is no continent selected. if(ContinentMngr.cur() == 0) return; if(!isNight) ContinentMngr.cur()->DaySkySetup.applyToInstance(instance, stage, skipFirstMaterial); else ContinentMngr.cur()->NightSkySetup.applyToInstance(instance, stage, skipFirstMaterial); } //=================================================================================================== static void applySkyTex(UInstance instance, uint stage, const std::string &name) { H_AUTO_USE(RZ_SkyRender) if (instance.empty()) return; if (instance.getNumMaterials() < 1) return; UInstanceMaterial im = instance.getMaterial(0); if (im.getLastTextureStage() >= (sint) stage) { if (NLMISC::nlstricmp(im.getTextureFileName(stage), name) != 0) { im.setTextureFileName(name, stage); } } } //=================================================================================================== /** Set alpha factors for a sky dome. * the constantAlpha gives the blend between 2 sky texture for the instance * the diffuseAlpha gives a modulate factor for the sky, which is then blended additively */ static void setSkyAlpha(UInstance instance, float constantAlpha, float diffuseAlpha, bool opaque) { H_AUTO_USE(RZ_SkyRender) if (instance.empty()) return; CRGBA constantCol(255, 255, 255, (uint8) (constantAlpha * 255.f)); CRGBA diffuseCol(255, 255, 255, (uint8) (diffuseAlpha * 255.f)); uint numMat = instance.getNumMaterials(); if (numMat == 0) return; for(uint k = 0; k < numMat; ++k) { instance.getMaterial(k).setConstantColor(1, constantCol); instance.getMaterial(k).setColor(diffuseCol); } // set the first material to be opaque instance.getMaterial(0).setDstBlend(opaque ? UInstanceMaterial::zero : UInstanceMaterial::one); } //=================================================================================================== static inline void showSkyPart(UInstance i, bool show) { H_AUTO_USE(RZ_SkyRender) if (i.empty()) return; if (show) i.show(); else i.hide(); } //=================================================================================================== static void showSkyParts(bool firstPass, bool secondPass, bool fogPart) { H_AUTO_USE(RZ_SkyRender) showSkyPart(Sky, firstPass); showSkyPart(Sky2ndPass, secondPass); showSkyPart(SkyFogPart, fogPart); } //=================================================================================================== static void renderStandardSky(float dayNight) { H_AUTO_USE(RZ_SkyRender) showSkyParts(true, false, false); // normal setup : stage 0 is day and stage 1 is night, 1 pass is required applySkyMaterialSetup(Sky, false, 0); // duplicate current setup in both stages applySkyMaterialSetup(Sky, true, 1); setSkyAlpha(Sky, dayNight, 1.f, true); // replace mode SkyScene->render(); } //=================================================================================================== /** setup a sky dome for a blend between night & day, with eventually a weather blend */ static void setupSkyDomeTextures(UInstance instance, const std::string &weatherDay, const std::string &weatherNight) { H_AUTO_USE(RZ_SkyRender) if (instance.empty()) return; // setup stage 0 (day) if (weatherDay.empty()) { applySkyMaterialSetup(instance, false, 0); } else { applySkyMaterialSetup(instance, false, 0, true); applySkyTex(instance, 0, weatherDay); } // setup stage 1 (night) if (weatherNight.empty()) { applySkyMaterialSetup(instance, true, 1); } else { applySkyMaterialSetup(instance, true, 1, true); applySkyTex(instance, 1, weatherNight); } } //=================================================================================================== /** Set constant color for all material of an instance */ static void setInstanceConstantColor(UInstance instance, CRGBA color) { H_AUTO_USE(RZ_SkyRender) if (instance.empty()) return; uint numMat = instance.getNumMaterials(); for(uint k = 0; k < numMat; ++k) { instance.getMaterial(k).setConstantColor(0, color); } } //=================================================================================================== /** remove all textures from an instance (to free memory..) */ static void removeInstanceTextures(UInstance instance) { H_AUTO_USE(RZ_SkyRender) if (instance.empty()) return; uint numMat = instance.getNumMaterials(); for(uint k = 0; k < numMat; ++k) { sint numStages = instance.getMaterial(k).getLastTextureStage() + 1; for(sint l = 0; l < numStages; ++l) { if (instance.getMaterial(k).isTextureFile((uint) l)) { instance.getMaterial(k).setTextureFileName("", (uint) l); } } } } //=================================================================================================== void renderSky(const CLightCycleManager &lcm, NLMISC::CRGBA fogColor) { H_AUTO_USE(RZ_SkyRender) H_AUTO_USE ( RZ_Client_Render_Sky ) static bool secondPassUsed = true; // tells if a second pass was needed at the previous rendering if (!SkyScene || MainCam.empty() || !Scene) return; // float lightLevel = lcm.getLightLevel(); float duskRatio = lcm.getLightDesc().DuskRatio; // Driver->enableFog(false); // Render the Sky. CFrustum frust = MainCam.getFrustum(); UCamera camSky = SkyScene->getCam(); camSky.setTransformMode(UTransform::DirectMatrix); // must have our own Far!!! frust.Far= SkyCameraZFar; camSky.setFrustum(frust); CMatrix skyCameraMatrix; skyCameraMatrix.identity(); skyCameraMatrix= MainCam.getMatrix(); skyCameraMatrix.setPos(CVector::Null); camSky.setMatrix(skyCameraMatrix); SkyScene->setViewport(Scene->getViewport()); bool isNight = lightLevel > 0.5f; if (Sky.empty()) return; // See in the weather setup if there is a weather related skyDome const CWeatherState &ws = WeatherManager.getCurrWeatherState(); bool twoPassDone = false; // build a set of 4 background between which to blend depending on weather & hour // 00 01 // bg for setup 0 +-----+ ---> background depending on hour (day, dusk, night) // | | // | | // bg for setup 1 +-----+ // 10 11 const std::string *bg00, *bg01, *bg10, *bg11; float blendFactor; // blendFactor for time switch (lcm.getState()) { case CLightCycleManager::DayToNight: if (lightLevel <= duskRatio) { blendFactor = duskRatio != 0 ? lightLevel / duskRatio : 0.f; bg00 = &ws.DayBackgroundFileName1; bg01 = &ws.DuskBackgroundFileName1; bg10 = &ws.DayBackgroundFileName2; bg11 = &ws.DuskBackgroundFileName2; } else { blendFactor = duskRatio != 1.f ? (lightLevel - duskRatio) / (1.f - duskRatio) : 0.f; bg00 = &ws.DuskBackgroundFileName1; bg01 = &ws.NightBackgroundFileName1; bg10 = &ws.DuskBackgroundFileName2; bg11 = &ws.NightBackgroundFileName2; } break; default: // not a day->night transition, so no 'dusk' step. blendFactor = lightLevel; bg00 = &ws.DayBackgroundFileName1; bg01 = &ws.NightBackgroundFileName1; bg10 = &ws.DayBackgroundFileName2; bg11 = &ws.NightBackgroundFileName2; break; } // Do not draw the sky in indoor continents if (!ContinentMngr.cur()->Indoor) { if (lightLevel == 0.f || lightLevel == 1.f) { const std::string &bg1 = isNight ? *bg01 : *bg00; const std::string &bg2 = isNight ? *bg11 : *bg10; if (!bg1.empty() || !bg2.empty()) { if (!bg1.empty()) { if (!bg2.empty()) // transition between 2 weather sky domes ? { applySkyMaterialSetup(Sky, isNight, 0, true); // duplicate current setup in both stages applySkyMaterialSetup(Sky, isNight, 1, true); // override sky dome texture with current texture applySkyTex(Sky, 0, bg1); applySkyTex(Sky, 1, bg2); // } else { // transition from bad weather sky to normal sky applySkyMaterialSetup(Sky, isNight, 0, true); // duplicate current setup in both stages applySkyMaterialSetup(Sky, isNight, 1); applySkyTex(Sky, 0, bg1); } } else { // transition from normal weather to bad weather applySkyMaterialSetup(Sky, isNight, 0); // duplicate current setup in both stages applySkyMaterialSetup(Sky, isNight, 1, true); applySkyTex(Sky, 1, bg2); } showSkyParts(true, false, false); setSkyAlpha(Sky, ws.BlendFactor, 1.f, true); SkyScene->render(); } else { renderStandardSky(blendFactor); } } else // We are in a transition between day and night { // if there's one weather texture, multipass rendering is needed if ( bg00->empty() && bg01->empty() && bg10->empty() && bg11->empty()) { // no weather texture renderStandardSky(blendFactor); } else // 2 pass rendering { // setup textures of the skydomes setupSkyDomeTextures(Sky, *bg00, *bg01); setupSkyDomeTextures(Sky2ndPass, *bg10, *bg11); setSkyAlpha(Sky, blendFactor, 1.f - ws.BlendFactor, true /* opaque */); setSkyAlpha(Sky2ndPass, blendFactor, ws.BlendFactor, false /* alpha additif */); showSkyParts(true, false, false); SkyScene->render(); showSkyParts(false, true, false); SkyScene->render(); twoPassDone = true; secondPassUsed = true; } } // Blend the fog part if (!SkyFogPart.empty()) { showSkyParts(false, false, true); setInstanceConstantColor(SkyFogPart, fogColor); // set texture matrix for first material to get the right height for fog if (SkyFogPart.getNumMaterials() > 0) { UInstanceMaterial im = SkyFogPart.getMaterial(0); im.enableUserTexMat(0); CMatrix uvMat; uvMat.scale(CVector(1.f, ws.FogGradientFactor != 0.f ? 1.f / ws.FogGradientFactor : 0.f, 1.f)); // scale the fog part im.setUserTexMat(0, uvMat); } SkyScene->render(); } // release textures for second pass if it is not needed anymore if (!twoPassDone && secondPassUsed) { removeInstanceTextures(Sky2ndPass); secondPassUsed = false; } } }