//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "environment/VolumetricFog.h" #include "windowManager/platformWindowMgr.h" #include "gfx/gfxTransformSaver.h" #include "renderInstance/renderPassManager.h" #include "math/mathIO.h" #include "materials/shaderData.h" #include "math/util/matrixSet.h" #include "core/resourceManager.h" #include "core/stream/bitStream.h" #include "T3D/gameBase/gameConnection.h" #include "T3D/shapeBase.h" #include "ts/tsShapeInstance.h" #include "console/engineAPI.h" #include "gui/core/guiCanvas.h" #include "VolumetricFogRTManager.h" #include "lighting/lightInfo.h" #include "lighting/lightManager.h" #define COLBOX_SCALE Point3F(1.02f, 1.02f, 1.02f) IMPLEMENT_CO_NETOBJECT_V1(VolumetricFog); ConsoleDocClass(VolumetricFog, "@brief Volumetric Fog Object class. Main class defining the Volumetric\n" "Fog objects in the scene. Used in conjunction with the VolumetricFogRTManager\n" "class which is responsible for the required rendertargets.\n\n" "Methods (exposed to script):\n" " setFogColorF(color) Changes the overall fog color (color.rgba range 0.0 - 1.0).\n;" " setFogColor(color) Changes the overall fog color color.rgba range 0 - 255).\n;" " setFogDensity(density) Changes the overall fog density.\n" " setFogModulation(strength, speed1, speed2) changes the strength\n" " and the speeds of the 2 animation layers.\n\n" "Callbacks:\n" "onEnterFog triggered whenever the controlobject (Player or Camera) enters the Fog.\n" " (current Fog object and the controlobject are exposed to script.\n" "onLeaveFog triggered whenever the controlobject (Player or Camera) left the Fog.\n" " (current Fog object and the controlobject are exposed to script.\n\n" "@tsexample\n" " new VolumetricFog()\n" " {\n" " shapeName = \"art/environment/FogRCube.dts\";\n" " fogColor = \"200 200 200 128\";\n" " fogDensity = \"0.2\";\n" " ignoreWater = \"0\";\n" " MinSize = \"250\";\n" " FadeSize = \"750\";\n" " texture = \"art/environment/FogMod_heavy.dds\";\n" " tiles = \"1.5\";\n" " modStrength = \"0.2\";\n" " PrimSpeed = \"-0.01 0.04\";\n" " SecSpeed = \"0.02 -0.02\";\n" " position = \"748.644 656.371 65.3506\"; \n" " rotation = \"0 0 1 20.354\";\n" " scale = \"40 30 6\";\n" " };\n" "@endtsexample\n" ); IMPLEMENT_CALLBACK(VolumetricFog, onEnterFog, void, (SimObjectId obj), (obj), "@brief Called when an object enters the volume of the Fog instance.\n\n" "@param obj the controlobject entering the fog."); IMPLEMENT_CALLBACK(VolumetricFog, onLeaveFog, void, (SimObjectId obj), (obj), "@brief Called when an object left the volume of the Fog instance.\n\n" "@param obj the controlobject leaving the fog."); VolumetricFog::VolumetricFog() { AssertFatal(VFRTM != NULL, "VolumetricFog Fatal Error: No Manager found"); if (!VFRTM->IsInitialized()) VFRTM->Init(); mNetFlags.set(Ghostable | ScopeAlways); mTypeMask |= EnvironmentObjectType | StaticObjectType; mDeferredTarget = NULL; mDepthBufferTarget = NULL; mFrontBufferTarget = NULL; z_buf = NULL; mTexture = NULL; mIsVBDirty = false; mIsPBDirty = false; mFogColor.set(200, 200, 200, 255); mFogDensity = 0.3f; mIgnoreWater = false; mReflect = false; mCamInFog = false; mResizing = false; mFogReflStrength = 20.0; mUseGlow = false; mGlowStrength = 0.3f; mGlowing = 0; mModifLightRays = false; mLightRayMod = 1.0f; mOldLightRayStrength = 0.1f; mShapeLoaded = false; mMinDisplaySize = 10.0f; mFadeSize = 0.0f; mCurDetailLevel = 0; mNumDetailLevels = 0; det_size.clear(); mIsTextured = false; mStrength = 0.5f; mTexTiles = 1.0f; mSpeed1.set(0.5f, 0.0f); mSpeed2.set(0.1f, 0.1f); INIT_ASSET(Shape); INIT_ASSET(Texture); } VolumetricFog::~VolumetricFog() { if (!isClientObject()) return; for (S32 i = 0; i < det_size.size(); i++) { if (det_size[i].indices != NULL) delete(det_size[i].indices); if (det_size[i].piArray != NULL) delete(det_size[i].piArray); if (det_size[i].verts != NULL) delete [] (det_size[i].verts); } det_size.clear(); z_buf = NULL; if (!mTexture.isNull()) mTexture.free(); } void VolumetricFog::initPersistFields() { addGroup("VolumetricFogData"); INITPERSISTFIELD_SHAPEASSET(Shape, VolumetricFog, "The source shape asset."); addField("FogColor", TypeColorI, Offset(mFogColor, VolumetricFog), "Fog color RGBA (Alpha is ignored)"); addField("FogDensity", TypeF32, Offset(mFogDensity, VolumetricFog), "Overal fog density value (0 disables the fog)."); addField("IgnoreWater", TypeBool, Offset(mIgnoreWater, VolumetricFog), "Set to true if volumetric fog should continue while submerged."); addField("MinSize", TypeF32, Offset(mMinDisplaySize, VolumetricFog), "Min size (in pixels) for fog to be rendered."); addField("FadeSize", TypeF32, Offset(mFadeSize, VolumetricFog), "Object size in pixels at which the FX-fading kicks in (0 disables fading)."); endGroup("VolumetricFogData"); addGroup("VolumetricFogModulation"); INITPERSISTFIELD_IMAGEASSET(Texture, VolumetricFog, "A texture which contains Fogdensity modulator in the red channel and color with 1-green channel. No texture disables modulation."); addField("tiles", TypeF32, Offset(mTexTiles, VolumetricFog), "How many times the texture is mapped to the object."); addField("modStrength", TypeF32, Offset(mStrength, VolumetricFog), "Overall strength of the density modulation (0 disables modulation)."); addField("PrimSpeed", TypePoint2F, Offset(mSpeed1, VolumetricFog), "Overall primary speed of the density modulation (x-speed(u) y-speed(v))"); addField("SecSpeed", TypePoint2F, Offset(mSpeed2, VolumetricFog), "Overall secundary speed of the density modulation (x-speed(u) y-speed(v))"); endGroup("VolumetricFogModulation"); addGroup("Reflections"); addField("Reflectable", TypeBool, Offset(mReflect, VolumetricFog), "Set to true if volumetric fog should be reflected."); addField("ReflectStrength", TypeF32, Offset(mFogReflStrength, VolumetricFog), "Strength of the reflections (0 disables the fog)."); endGroup("Reflections"); addGroup("PostFX"); addField("useGlow", TypeBool, Offset(mUseGlow, VolumetricFog), "Set to true if volumetric fog should use glow PostFX."); addField("glowStrength", TypeF32, Offset(mGlowStrength, VolumetricFog), "Overall strength of the glow PostFX."); addField("modLightRay", TypeBool, Offset(mModifLightRays, VolumetricFog), "Set to true if volumetric fog should modify the brightness of the Lightrays."); addField("lightRayMod", TypeF32, Offset(mLightRayMod, VolumetricFog), "Modifier for LightRay PostFX when inside Fog."); endGroup("PostFX"); Parent::initPersistFields(); } bool VolumetricFog::_setShapeAsset(void* obj, const char* index, const char* data) { VolumetricFog* fog = static_cast(obj);// ->setFile(FileName(data)); fog->setShapeAsset(StringTable->insert(data)); return false; } void VolumetricFog::inspectPostApply() { Parent::inspectPostApply(); mSpeed.set(mSpeed1.x, mSpeed1.y, mSpeed2.x, mSpeed2.y); setMaskBits(VolumetricFogMask | FogColorMask | FogDensityMask | FogModulationMask | FogPostFXMask | FogShapeMask); } bool VolumetricFog::onAdd() { if (!Parent::onAdd()) return false; if (!VFRTM->IsInitialized()) { Con::errorf("No VolumetricFogRTManager present!!"); return false; } resetWorldBox(); mShapeLoaded = LoadShape(); setRenderTransform(mObjToWorld); addToScene(); ColBox.set(getTransform(), (mObjBox.getExtents() * getScale() * COLBOX_SCALE)); mObjSize = mWorldBox.getGreatestDiagonalLength(); mObjScale = getScale(); mTexTiles = mAbs(mTexTiles); mSpeed.set(mSpeed1.x, mSpeed1.y, mSpeed2.x, mSpeed2.y); mInvScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); VFRTM->IncFogObjects(); if (isClientObject()) { conn = GameConnection::getConnectionToServer(); if (!conn) { Con::errorf("VolumetricFog::onAdd - No Serverconnection"); return false; } glowFX = static_cast(Sim::findObject("VolFogGlowPostFx")); mOldLightRayStrength = Con::getFloatVariable("$LightRayPostFX::brightScalar",1.0f); GuiCanvas* cv = dynamic_cast(Sim::findObject("Canvas")); if (cv == NULL) { Con::errorf("VolumetricFog::onAdd - Canvas not found!!"); return false; } mPlatformWindow = cv->getPlatformWindow(); VolumetricFogRTManager::getVolumetricFogRTMResizeSignal().notify(this, &VolumetricFog::handleResize); GuiCanvas::getCanvasSizeChangeSignal().notify(this, &VolumetricFog::handleCanvasResize); InitTexture(); return setupRenderer(); } return true; } void VolumetricFog::onRemove() { if (isClientObject()) { if (isTicking()) { setProcessTick(false); if (mGlowing != 0) { mGlowing = 0; glowFX->disable(); } _leaveFog(dynamic_cast(conn->getControlObject())); } VolumetricFogRTManager::getVolumetricFogRTMResizeSignal().remove(this, &VolumetricFog::handleResize); GuiCanvas::getCanvasSizeChangeSignal().remove(this, &VolumetricFog::handleCanvasResize); } removeFromScene(); VFRTM->DecFogObjects(); Parent::onRemove(); } void VolumetricFog::handleCanvasResize(GuiCanvas* canvas) { UpdateBuffers(0,true); } void VolumetricFog::handleResize(VolumetricFogRTManager *RTM, bool resize) { if (resize) { mResizing = true; RTM->FogAnswered(); } else mResizing = false; if (mIsTextured) { F32 width = (F32)mPlatformWindow->getClientExtent().x; F32 height = (F32)mPlatformWindow->getClientExtent().y; mTexScale.x = 2.0f - ((F32)mTexture.getWidth() / width); mTexScale.y = 2.0f - ((F32)mTexture.getHeight() / height); } UpdateBuffers(0,true); } //----------------------------------------------------------------------------- // Loadshape extracted from TSMesh and TSShapeInstance //----------------------------------------------------------------------------- bool VolumetricFog::setShapeAsset(const StringTableEntry shapeAssetId) { mShapeAssetId = shapeAssetId; LoadShape(); return true; } bool VolumetricFog::LoadShape() { GFXPrimitiveType GFXdrawTypes[] = { GFXTriangleList, GFXTriangleStrip }; if (mShapeAsset.isNull()) { Con::errorf("[VolumetricFog] Failed to load shape asset."); return false; } if (!mShape) { Con::errorf("VolumetricFog::_createShape() - Shape Asset had no valid shape!"); return false; } mObjBox = mShape->mBounds; mRadius = mShape->mRadius; resetWorldBox(); if (!isClientObject()) return false; TSShapeInstance *mShapeInstance = new TSShapeInstance(mShape, false); meshes mesh_detail; for (S32 i = 0; i < det_size.size(); i++) { if (det_size[i].indices != NULL) delete(det_size[i].indices); if (det_size[i].piArray != NULL) delete(det_size[i].piArray); if (det_size[i].verts != NULL) delete [] (det_size[i].verts); } det_size.clear(); // browsing model for detail levels for (U32 i = 0; i < mShape->details.size(); i++) { const TSDetail *detail = &mShape->details[i]; mesh_detail.det_size = detail->size; mesh_detail.sub_shape = detail->subShapeNum; mesh_detail.obj_det = detail->objectDetailNum; mesh_detail.verts = NULL; mesh_detail.piArray = NULL; mesh_detail.indices = NULL; if (detail->size >= 0.0f && detail->subShapeNum >= 0) det_size.push_back(mesh_detail); } for (U32 i = 0; i < det_size.size(); i++) { const S32 ss = det_size[i].sub_shape; if (ss >= 0) { const S32 start = mShape->subShapeFirstObject[ss]; const S32 end = start + mShape->subShapeNumObjects[ss]; for (S32 j = start; j < end; j++) { // Loading shape, only the first mesh for each detail will be used! TSShapeInstance::MeshObjectInstance *meshObj = &mShapeInstance->mMeshObjects[j]; if (!meshObj) continue; TSMesh *mesh = meshObj->getMesh(det_size[i].obj_det); if (mesh != NULL) { const U32 numNrms = mesh->mNumVerts; GFXVertexPNTT *tmpVerts = NULL; tmpVerts = new GFXVertexPNTT[numNrms]; mIsVBDirty = true; for (U32 k = 0; k < numNrms; k++) { const TSMesh::__TSMeshVertexBase &vd = mesh->mVertexData.getBase(k); Point3F norm = vd.normal(); Point3F vert = vd.vert(); Point2F uv = vd.tvert(); tmpVerts[k].point = vert; tmpVerts[k].texCoord = uv; tmpVerts[k].normal = norm; } det_size[i].verts = tmpVerts; det_size[i].num_verts = numNrms; det_size[i].piArray = new Vector(); GFXPrimitive pInfo; det_size[i].indices = new Vector(); for (U32 k = 0; k < mesh->mIndices.size(); k++) det_size[i].indices->push_back(mesh->mIndices[k]); U32 primitivesSize = mesh->mPrimitives.size(); for (U32 k = 0; k < primitivesSize; k++) { const TSDrawPrimitive & draw = mesh->mPrimitives[k]; GFXPrimitiveType drawType = GFXdrawTypes[draw.matIndex >> 30]; switch (drawType) { case GFXTriangleList: pInfo.type = drawType; pInfo.numPrimitives = draw.numElements / 3; pInfo.startIndex = draw.start; // Use the first index to determine which 16-bit address space we are operating in pInfo.startVertex = mesh->mIndices[draw.start] & 0xFFFF0000; pInfo.minIndex = pInfo.startVertex; pInfo.numVertices = getMin((U32)0x10000, mesh->mNumVerts - pInfo.startVertex); break; case GFXTriangleStrip: pInfo.type = drawType; pInfo.numPrimitives = draw.numElements - 2; pInfo.startIndex = draw.start; // Use the first index to determine which 16-bit address space we are operating in pInfo.startVertex = mesh->mIndices[draw.start] & 0xFFFF0000; pInfo.minIndex = pInfo.startVertex; pInfo.numVertices = getMin((U32)0x10000, mesh->mNumVerts - pInfo.startVertex); break; default: Con::errorf("VolumetricFog::LoadShape Unknown drawtype!?!"); return false; break; } det_size[i].piArray->push_back(pInfo); j = end; } } else { Con::errorf("VolumetricFog::LoadShape Error loading mesh from shape!"); delete mShapeInstance; return false; } mIsVBDirty = true; mIsPBDirty = true; } } } mNumDetailLevels = det_size.size(); mCurDetailLevel = 0; UpdateBuffers(mCurDetailLevel); delete mShapeInstance; return true; } //----------------------------------------------------------------------------- // UpdateBuffers called whenever detaillevel changes (LOD) //----------------------------------------------------------------------------- bool VolumetricFog::UpdateBuffers(U32 dl, bool force) { if (mVB.isNull() || mIsVBDirty || dl != mCurDetailLevel || force) { mVB.set(GFX, det_size[dl].num_verts, GFXBufferTypeDynamic); mIsVBDirty = false; } GFXVertexPNTT *vertPtr = mVB.lock(); if (!vertPtr) { mVB.unlock(); return false; } dMemcpy(vertPtr, det_size[dl].verts, sizeof (GFXVertexPNTT)* det_size[dl].num_verts); mVB.unlock(); if (mIsPBDirty || mPB.isNull() || dl != mCurDetailLevel || force) { #ifdef TORQUE_DEBUG mPB.set(GFX, det_size[dl].indices->size(), det_size[dl].piArray->size(), GFXBufferTypeDynamic, avar("%s() - VolFogPrimBuffer (line %d)", __FUNCTION__, __LINE__)); #else mPB.set(GFX, det_size[dl].indices->size(), det_size[dl].piArray->size(), GFXBufferTypeDynamic); #endif U16 *ibIndices = NULL; GFXPrimitive *piInput = NULL; mPB.lock(&ibIndices, &piInput); dCopyArray(ibIndices, det_size[dl].indices->address(), det_size[dl].indices->size()); dMemcpy(piInput, det_size[dl].piArray->address(), det_size[dl].piArray->size() * sizeof(GFXPrimitive)); mPB.unlock(); mIsPBDirty = false; } mCurDetailLevel = dl; return true; } U32 VolumetricFog::packUpdate(NetConnection *con, U32 mask, BitStream *stream) { U32 retMask = Parent::packUpdate(con, mask, stream); if (stream->writeFlag(mask & FogColorMask)) stream->write(mFogColor); if (stream->writeFlag(mask & FogDensityMask)) stream->write(mFogDensity); if (stream->writeFlag(mask & FogModulationMask)) { PACK_ASSET(con, Texture); mTexTiles = mFabs(mTexTiles); stream->write(mTexTiles); stream->write(mStrength); mathWrite(*stream, mSpeed); } if (stream->writeFlag(mask & FogPostFXMask)) { stream->writeFlag(mUseGlow); stream->write(mGlowStrength); stream->writeFlag(mModifLightRays); stream->write(mLightRayMod); } if (stream->writeFlag(mask & VolumetricFogMask)) { stream->writeFlag(mIgnoreWater); stream->writeFlag(mReflect); stream->write(mFogReflStrength); stream->writeFlag(mResizing); stream->write(mMinDisplaySize); stream->write(mFadeSize); } if (stream->writeFlag(mask & FogShapeMask)) { PACK_ASSET(con, Shape); mathWrite(*stream, getTransform()); mathWrite(*stream, getScale()); if (mShapeAsset.notNull()) { mObjBox = mShapeAsset->getShapeResource()->mBounds; mRadius = mShapeAsset->getShapeResource()->mRadius; } else { return retMask; } resetWorldBox(); mObjSize = mWorldBox.getGreatestDiagonalLength(); mObjScale = getScale(); mInvScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); } return retMask; } void VolumetricFog::unpackUpdate(NetConnection *con, BitStream *stream) { Parent::unpackUpdate(con, stream); MatrixF mat; VectorF scale; VectorF mOldScale = getScale(); StringTableEntry oldTextureName = mTextureAssetId; StringTableEntry oldShapeAsset = mShapeAssetId; StringTableEntry oldShape = mShapeName; if (stream->readFlag())// Fog color stream->read(&mFogColor); if (stream->readFlag())// Fog Density { stream->read(&mFogDensity); if (isTicking()) { char buf[20]; dSprintf(buf, sizeof(buf), "%3.7f", mFogDensity); Con::setVariable("$VolumetricFog::density", buf); } } if (stream->readFlag())// Fog Modulation { UNPACK_ASSET(con, Texture); stream->read(&mTexTiles); mTexTiles = mFabs(mTexTiles); stream->read(&mStrength); mathRead(*stream, &mSpeed); mSpeed1.set(mSpeed.x, mSpeed.y); mSpeed2.set(mSpeed.z, mSpeed.w); if (isProperlyAdded()) { if (oldTextureName != mTextureAssetId) InitTexture(); if (oldTextureName != StringTable->EmptyString() && mTextureAssetId == StringTable->EmptyString()) { mIsTextured = false; mTexture.free(); } } } if (stream->readFlag())//Fog PostFX { mUseGlow = stream->readFlag(); stream->read(&mGlowStrength); mModifLightRays = stream->readFlag(); stream->read(&mLightRayMod); if (isTicking()) { char glowStrBuf[20]; dSprintf(glowStrBuf, sizeof(glowStrBuf), "%3.7f", mGlowStrength); Con::setVariable("$VolFogGlowPostFx::glowStrength", glowStrBuf); if (mUseGlow && !glowFX->isEnabled()) glowFX->enable(); if (!mUseGlow && glowFX->isEnabled()) glowFX->disable(); F32 rayStrength = mOldLightRayStrength; if (mModifLightRays) rayStrength *= mLightRayMod; char rayStrBuf[20]; dSprintf(rayStrBuf, sizeof(rayStrBuf), "%3.7f", rayStrength); Con::setVariable("$LightRayPostFX::brightScalar", rayStrBuf); } } if (stream->readFlag())//Volumetric Fog { mIgnoreWater = stream->readFlag(); mReflect = stream->readFlag(); stream->read(&mFogReflStrength); mResizing = stream->readFlag(); stream->read(&mMinDisplaySize); stream->read(&mFadeSize); } if (stream->readFlag())//Fog shape { UNPACK_ASSET(con, Shape); mathRead(*stream, &mat); mathRead(*stream, &scale); if (strcmp(oldShapeAsset, mShapeAssetId) != 0 || strcmp(oldShape, mShapeName) != 0) { mIsVBDirty = true; mShapeLoaded = LoadShape(); } setScale(scale); setTransform(mat); ColBox.set(getTransform(), (mObjBox.getExtents() * getScale() * COLBOX_SCALE)); mObjSize = mWorldBox.getGreatestDiagonalLength(); mObjScale = getScale(); mInvScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); } } void VolumetricFog::processTick(const Move* move) { Parent::processTick(move); mCounter++; if ( mGlowing==1 && mCurGlow < mGlowStrength ) { mCurGlow += (mGlowStrength / 10.0); char buf[20]; dSprintf(buf, sizeof(buf), "%3.7f", mCurGlow); Con::setVariable("$VolFogGlowPostFx::glowStrength", buf); } else if ( mGlowing == 2 && mCurGlow > 0.0f ) { mCurGlow -= (mGlowStrength / 5.0f); if (mCurGlow <= 0.0f) { glowFX->disable(); mGlowing = 0; setProcessTick(false); return; } else { char buf[20]; dSprintf(buf, sizeof(buf), "%3.7f", mCurGlow); Con::setVariable("$VolFogGlowPostFx::glowStrength", buf); } } if (mCounter == 3) { ShapeBase* control = dynamic_cast(conn->getControlObject()); if (!control) return; MatrixF xfm; control->getRenderEyeTransform(&xfm); Point3F pos = xfm.getPosition(); if (!ColBox.isContained(pos)) _leaveFog(control); mCounter = 0; } } void VolumetricFog::_enterFog(ShapeBase *control) { if (mUseGlow) { if (glowFX) { mCurGlow = 0.0f; Con::setVariable("$VolFogGlowPostFx::glowStrength", "0.0"); glowFX->enable(); mGlowing = 1; } } if (mModifLightRays) { char buf[20]; dSprintf(buf, sizeof(buf), "%3.7f", mOldLightRayStrength * mLightRayMod); Con::setVariable("$LightRayPostFX::brightScalar", buf); } mCounter = 0; char buf[20]; dSprintf(buf, sizeof(buf), "%3.7f", mFogDensity); Con::setVariable("$VolumetricFog::density", buf); setProcessTick(true); if (control) onEnterFog_callback(control->getId()); } void VolumetricFog::_leaveFog(ShapeBase *control) { mCamInFog = false; Con::setVariable("$VolumetricFog::density", "0.0"); if (mModifLightRays) { char buf[20]; dSprintf(buf, sizeof(buf), "%3.7f", mOldLightRayStrength); Con::setVariable("$LightRayPostFX::brightScalar", buf); } if (mUseGlow) { if (glowFX && mGlowing != 2) { mCurGlow = mGlowStrength; mGlowing = 2; if (control) onLeaveFog_callback(control->getId()); } } else { setProcessTick(false); if (control) onLeaveFog_callback(control->getId()); } } //----------------------------------------------------------------------------- // Setting up the renderers //----------------------------------------------------------------------------- bool VolumetricFog::setupRenderer() { // Search for the deferred rendertarget and shadermacros. mDeferredTarget = NamedTexTarget::find("deferred"); if (!mDeferredTarget.isValid()) { Con::errorf("VolumetricFog::setupRenderer - could not find DeferredTarget"); return false; } Vector macros; if (mDeferredTarget) mDeferredTarget->getShaderMacros(¯os); // Search the depth and frontbuffers which are created by the VolumetricFogRTManager mDepthBufferTarget = NamedTexTarget::find("volfogdepth"); if (!mDepthBufferTarget.isValid()) { Con::errorf("VolumetricFog::setupRenderer - could not find depthbuffer"); return false; } mFrontBufferTarget = NamedTexTarget::find("volfogfront"); if (!mFrontBufferTarget.isValid()) { Con::errorf("VolumetricFog::setupRenderer - could not find frontbuffer"); return false; } // Find and setup the deferred Shader ShaderData *shaderData; mDeferredShader = Sim::findObject("VolumetricFogDeferredShader", shaderData) ? shaderData->getShader() : NULL; if (!mDeferredShader) { Con::errorf("VolumetricFog::setupRenderer - could not find VolumetricFogDeferredShader"); return false; } // Create ShaderConstBuffer and Handles mPPShaderConsts = mDeferredShader->allocConstBuffer(); if (mPPShaderConsts.isNull()) { Con::errorf("VolumetricFog::setupRenderer - could not allocate ShaderConstants 1."); return false; } mPPModelViewProjSC = mDeferredShader->getShaderConstHandle("$modelView"); // Find and setup the VolumetricFog Shader shaderData = NULL; mShader = Sim::findObject("VolumetricFogShader", shaderData) ? shaderData->getShader(macros) : NULL; if (!mShader) { Con::errorf("VolumetricFog::setupRenderer - could not find VolumetricFogShader"); return false; } // Create ShaderConstBuffer and Handles mShaderConsts = mShader->allocConstBuffer(); if (mShaderConsts.isNull()) { Con::errorf("VolumetricFog::setupRenderer - could not allocate ShaderConstants 2."); return false; } mModelViewProjSC = mShader->getShaderConstHandle("$modelView"); mFadeSizeSC = mShader->getShaderConstHandle("$fadesize"); mFogColorSC = mShader->getShaderConstHandle("$fogColor"); mFogDensitySC = mShader->getShaderConstHandle("$fogDensity"); mPreBias = mShader->getShaderConstHandle("$preBias"); mAccumTime = mShader->getShaderConstHandle("$accumTime"); mIsTexturedSC = mShader->getShaderConstHandle("$textured"); mTexTilesSC = mShader->getShaderConstHandle("$numtiles"); mModStrengthSC = mShader->getShaderConstHandle("$modstrength"); mModSpeedSC = mShader->getShaderConstHandle("$modspeed"); mViewPointSC = mShader->getShaderConstHandle("$viewpoint"); mTexScaleSC = mShader->getShaderConstHandle("$texscale"); mAmbientColorSC = mShader->getShaderConstHandle("$ambientColor"); // Find and setup the reflection Shader shaderData = NULL; mReflectionShader = Sim::findObject("VolumetricFogReflectionShader", shaderData) ? shaderData->getShader() : NULL; if (!mReflectionShader) { Con::errorf("VolumetricFog::setupRenderer - could not find VolumetricFogReflectionShader"); return false; } mReflShaderConsts = mReflectionShader->allocConstBuffer(); if (mReflShaderConsts.isNull()) { Con::errorf("VolumetricFog::setupRenderer - could not allocate ShaderConstants for VolumetricFogReflectionShader."); return false; } mReflModelViewProjSC = mReflectionShader->getShaderConstHandle("$modelView"); mReflFogColorSC = mReflectionShader->getShaderConstHandle("$fogColor"); mReflFogDensitySC = mReflectionShader->getShaderConstHandle("$fogDensity"); mReflFogStrengthSC = mReflectionShader->getShaderConstHandle("$reflStrength"); // Create the deferred StateBlock desc_preD.setCullMode(GFXCullCW); desc_preD.setBlend(true); desc_preD.setZReadWrite(false, false); desc_preD.stencilEnable = false; desc_preF.setCullMode(GFXCullCCW); desc_preF.setBlend(true); desc_preF.setZReadWrite(true, false); desc_preF.stencilEnable = false; // Create the VolumetricFog StateBlock descD.setCullMode(GFXCullCW); descD.setBlend(true); descD.setZReadWrite(false, false);// desc.setZReadWrite(true, false); // deferredBuffer sampler descD.samplersDefined = true; descD.samplers[0].addressModeU = GFXAddressClamp; descD.samplers[0].addressModeV = GFXAddressClamp; descD.samplers[0].addressModeW = GFXAddressClamp; descD.samplers[0].magFilter = GFXTextureFilterLinear; descD.samplers[0].minFilter = GFXTextureFilterLinear; descD.samplers[0].mipFilter = GFXTextureFilterLinear; // DepthBuffer sampler descD.samplers[1].addressModeU = GFXAddressClamp; descD.samplers[1].addressModeV = GFXAddressClamp; descD.samplers[1].addressModeW = GFXAddressClamp; descD.samplers[1].magFilter = GFXTextureFilterLinear; descD.samplers[1].minFilter = GFXTextureFilterLinear; descD.samplers[1].mipFilter = GFXTextureFilterLinear; // FrontBuffer sampler descD.samplers[2].addressModeU = GFXAddressClamp; descD.samplers[2].addressModeV = GFXAddressClamp; descD.samplers[2].addressModeW = GFXAddressClamp; descD.samplers[2].magFilter = GFXTextureFilterLinear; descD.samplers[2].minFilter = GFXTextureFilterLinear; descD.samplers[2].mipFilter = GFXTextureFilterLinear; // animated density modifier map sampler descD.samplers[3].addressModeU = GFXAddressWrap; descD.samplers[3].addressModeV = GFXAddressWrap; descD.samplers[3].addressModeW = GFXAddressWrap; descD.samplers[3].magFilter = GFXTextureFilterLinear; descD.samplers[3].minFilter = GFXTextureFilterLinear; descD.samplers[3].mipFilter = GFXTextureFilterLinear; dMemcpy(&descF, &descD, sizeof(GFXStateBlockDesc)); descF.setCullMode(GFXCullCCW); descF.setBlend(true); descF.setZReadWrite(true, false); desc_refl.setCullMode(GFXCullCCW); desc_refl.setBlend(true); desc_refl.setZReadWrite(true, false); mStateblock_preD = GFX->createStateBlock(desc_preD); mStateblock_preF = GFX->createStateBlock(desc_preF); mStateblockD = GFX->createStateBlock(descD); mStateblockF = GFX->createStateBlock(descF); mStateblock_refl = GFX->createStateBlock(desc_refl); // Create Rendertarget z_buf = GFX->allocRenderToTextureTarget(); if (z_buf == NULL) { Con::errorf("VolumetricFog::setupRenderer - Could not create Render Target"); return false; } return true; } void VolumetricFog::prepRenderImage(SceneRenderState *state) { if (!mShapeLoaded || mFogDensity <= 0.0f || mResizing) return; if (!state->isDiffusePass()) { if (!state->isReflectPass()) return; } PROFILE_SCOPE(VolumetricFog_prepRenderImage); ShapeBase* control = dynamic_cast(conn->getControlObject()); if (!control || (control->getWaterCoverage() >= 0.9f && !mIgnoreWater)) return; camPos = state->getCameraPosition(); F32 dist = (camPos - getBoxCenter()).len(); F32 scaleFactor = dist * mInvScale; if (scaleFactor <= 0.0f) { if (mCurDetailLevel != 0) UpdateBuffers(0); } const F32 pixelScale = state->getViewport().extent.y / 300.0f; mPixelSize = (mRadius / scaleFactor) * state->getWorldToScreenScale().y * pixelScale; if (mPixelSize < mMinDisplaySize) return; if (mNumDetailLevels > 1) { if ((mCurDetailLevel < mNumDetailLevels - 1) && (det_size[mCurDetailLevel].det_size > mPixelSize)) UpdateBuffers(mCurDetailLevel + 1); else if (mCurDetailLevel > 0) { if (mPixelSize >= det_size[mCurDetailLevel - 1].det_size) UpdateBuffers(mCurDetailLevel - 1); } } if (state->isReflectPass() && mReflect) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind(this, &VolumetricFog::reflect_render); ri->type = RenderPassManager::RIT_VolumetricFog; ri->translucentSort = true; ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint(camPos); if (dist < 1.0f) ri->defaultKey = 1; else ri->defaultKey = U32(dist); state->getRenderPass()->addInst(ri); return; } else if (state->isDiffusePass()) { viewDist = state->getFarPlane(); mFOV = state->getCameraFrustum().getFov() / M_PI_F; Point3F mEyeVec = state->getVectorEye() * viewDist; mViewPoint.x = ((mAtan2(mEyeVec.x, mEyeVec.y) / M_PI_F) + 1.0f) * mTexTiles; mViewPoint.y = (0.5f - (mAsin(mEyeVec.z) / M_PI_F)) * mTexTiles; bool isInside = ColBox.isContained(camPos); if (isInside && !mCamInFog) { mCamInFog = true; _enterFog(control); } else if (!isInside && mCamInFog) mCamInFog = false; ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind(this, &VolumetricFog::render); ri->type = RenderPassManager::RIT_VolumetricFog; ri->translucentSort = true; ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint(camPos); if (dist < 1.0f) ri->defaultKey = 1; else ri->defaultKey = U32(dist); state->getRenderPass()->addInst(ri); return; } return; } void VolumetricFog::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) { if (overrideMat || !mShapeLoaded || !isClientObject() || mResizing) return; PROFILE_SCOPE(VolumetricFog_Render); GFXTransformSaver saver; GFX->setVertexBuffer(mVB); GFX->setPrimitiveBuffer(mPB); MatrixF mat = getRenderTransform(); mat.scale(mObjScale); GFX->multWorld(mat); GFX->setShader(mDeferredShader); GFX->setShaderConstBuffer(mPPShaderConsts); GFX->setStateBlock(mStateblock_preD); // Set all the shader consts... MatrixF xform(GFX->getProjectionMatrix()); xform *= GFX->getViewMatrix(); xform *= GFX->getWorldMatrix(); mPPShaderConsts->setSafe(mPPModelViewProjSC, xform); const LinearColorF &sunlight = state->getAmbientLightColor(); Point3F ambientColor(sunlight.red, sunlight.green, sunlight.blue); mShaderConsts->setSafe(mAmbientColorSC, ambientColor); GFXTextureObject *mDepthBuffer = mDepthBufferTarget ? mDepthBufferTarget->getTexture(0) : NULL; GFXTextureObject *mFrontBuffer = mFrontBufferTarget ? mFrontBufferTarget->getTexture(0) : NULL; GFX->pushActiveRenderTarget(); //render backside to target mDepthBuffer z_buf->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); z_buf->attachTexture(GFXTextureTarget::Color0, mDepthBuffer); GFX->setActiveRenderTarget(z_buf); GFX->clear(GFXClearStencil | GFXClearTarget , ColorI(0,0,0,0), 1.0f, 0); GFX->drawPrimitive(0); z_buf->resolve(); //render frontside to target mFrontBuffer z_buf->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); z_buf->attachTexture(GFXTextureTarget::Color0, mFrontBuffer); GFX->clear(GFXClearStencil | GFXClearTarget, ColorI(0, 0, 0, 0), 1.0f, 0); GFX->setStateBlock(mStateblock_preF); GFX->drawPrimitive(0); z_buf->resolve(); GFX->popActiveRenderTarget(); z_buf->attachTexture(GFXTextureTarget::Color0, NULL); //render Volumetric Fog GFX->setShader(mShader); GFX->setShaderConstBuffer(mShaderConsts); mShaderConsts->setSafe(mModelViewProjSC, xform); if (mFadeSize > 0.0f) mShaderConsts->setSafe(mFadeSizeSC, mClampF(mPixelSize / mFadeSize, 0.0f, 1.0f)); else mShaderConsts->setSafe(mFadeSizeSC, 1.0f); mShaderConsts->setSafe(mFogColorSC, mFogColor); mShaderConsts->setSafe(mFogDensitySC, mFogDensity); mShaderConsts->setSafe(mPreBias, viewDist); mShaderConsts->setSafe(mAccumTime, (F32)Sim::getCurrentTime() / 1000.0f); mShaderConsts->setSafe(mModStrengthSC, mStrength); mShaderConsts->setSafe(mModSpeedSC, mSpeed); mShaderConsts->setSafe(mViewPointSC, mViewPoint); mShaderConsts->setSafe(mTexScaleSC, mTexScale * mFOV); mShaderConsts->setSafe(mTexTilesSC, mTexTiles); GFXTextureObject *deferredtex = mDeferredTarget ? mDeferredTarget->getTexture(0) : NULL; GFX->setTexture(0, deferredtex); GFX->setTexture(1, mDepthBuffer); GFX->setTexture(2, mFrontBuffer); if (mIsTextured && mStrength > 0.0f) { GFX->setTexture(3, mTexture); mShaderConsts->setSafe(mIsTexturedSC, 1.0f); } else mShaderConsts->setSafe(mIsTexturedSC, 0.0f); if (mCamInFog) { /*GFXLockedRect *rect=mDepthBuffer->lock(); U32 pixoffset = 0;// 1572864 + (512 * 4); U8 red = rect->bits[pixoffset]; U8 green = rect->bits[pixoffset+1]; U8 blue = rect->bits[pixoffset+2]; U8 alpha = rect->bits[pixoffset+3]; mDepthBuffer->unlock(); S32 lval = ((alpha << 24) + (blue << 16) + (green << 8) + (red)); F32 fval = ((F32)lval / S32_MAX); Con::printf("Color %d %d %d %d %d %f", red, green, blue, alpha, lval, fval);*/ GFX->setStateBlock(mStateblockD); } else GFX->setStateBlock(mStateblockF); GFX->drawPrimitive(0); // Ensure these two textures are NOT bound to the pixel shader input on the second run as they are used as pixel shader outputs (render targets). GFX->clearTextureStateImmediate(1); //mDepthBuffer GFX->clearTextureStateImmediate(2); //mFrontBuffer } void VolumetricFog::reflect_render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) { if (overrideMat || !mShapeLoaded || !isClientObject() || mResizing || (mFogReflStrength==0.0f)) return; GFXTransformSaver saver; GFX->setVertexBuffer(mVB); GFX->setPrimitiveBuffer(mPB); MatrixF mat = getRenderTransform(); mat.scale(mObjScale); GFX->multWorld(mat); GFX->setShader(mReflectionShader); GFX->setShaderConstBuffer(mReflShaderConsts); GFX->setStateBlock(mStateblock_refl); // Set all the shader consts... MatrixF xform(GFX->getProjectionMatrix()); xform *= GFX->getViewMatrix(); xform *= GFX->getWorldMatrix(); mReflShaderConsts->setSafe(mReflModelViewProjSC, xform); mReflShaderConsts->setSafe(mReflFogColorSC, mFogColor); mReflShaderConsts->setSafe(mReflFogDensitySC, mFogDensity); mReflShaderConsts->setSafe(mReflFogStrengthSC, mFogReflStrength); GFX->drawPrimitive(0); } //----------------------------------------------------------------------------- // InitTexture is called whenever a modulation texture is added to the object //----------------------------------------------------------------------------- void VolumetricFog::InitTexture() { mIsTextured = false; if (mTextureAsset.isNull()) return; if (!mTexture.isNull()) { mIsTextured = true; F32 width = (F32)mPlatformWindow->getClientExtent().x; F32 height = (F32)mPlatformWindow->getClientExtent().y; mTexScale.x = 2.0f - ((F32)mTexture.getWidth() / width); mTexScale.y = 2.0f - ((F32)mTexture.getHeight() / height); } } void VolumetricFog::setFogColor(LinearColorF color) { mFogColor.set(255 * color.red,255 * color.green,255 * color.blue); setMaskBits(FogColorMask); } void VolumetricFog::setFogColor(ColorI color) { mFogColor = color; setMaskBits(FogColorMask); } void VolumetricFog::setFogDensity(F32 density) { if (density < 0.0f) density = 0.0f; mFogDensity = density; setMaskBits(FogDensityMask); } void VolumetricFog::setFogModulation(F32 strength,Point2F speed1,Point2F speed2) { mStrength = strength; mSpeed1 = speed1; mSpeed2 = speed2; mSpeed.set(speed1.x, speed1.y, speed2.x, speed2.y); setMaskBits(FogModulationMask); } void VolumetricFog::setFogGlow(bool on_off, F32 strength) { mUseGlow = on_off; mGlowStrength = strength; setMaskBits(FogPostFXMask); } void VolumetricFog::setFogLightray(bool on_off, F32 strength) { mModifLightRays = on_off; mLightRayMod = strength; setMaskBits(FogPostFXMask); } bool VolumetricFog::isInsideFog() { return mCamInFog; } DefineEngineMethod(VolumetricFog, SetFogColorF, void, (LinearColorF new_color), , "@brief Changes the color of the fog\n\n." "@params new_color the new fog color (rgb 0.0 - 1.0, a is ignored.") { object->setFogColor(new_color); } DefineEngineMethod(VolumetricFog, SetFogColor, void, (ColorI new_color), , "@brief Changes the color of the fog\n\n." "@params new_color the new fog color (rgb 0-255, a is ignored.") { object->setFogColor(new_color); } DefineEngineMethod(VolumetricFog, SetFogDensity, void, (F32 new_density), , "@brief Changes the density of the fog\n\n." "@params new_density the new fog density.") { object->setFogDensity(new_density); } DefineEngineMethod(VolumetricFog, SetFogModulation, void, (F32 new_strenght, Point2F new_speed1, Point2F new_speed2), , "@brief Changes the modulation of the fog\n\n." "@params new_strenght the new strength of the modulation.\n" "@params new_speed1 the new speed (x y) of the modulation layer 1.\n" "@params new_speed2 the new speed (x y) of the modulation layer 2.\n") { object->setFogModulation(new_strenght, new_speed1, new_speed2); } DefineEngineMethod(VolumetricFog, SetFogGlow, void, (bool on_off,F32 strength), , "@brief Changes the glow postfx when inside the fog\n\n." "@params on_off set to true to enable glow.\n" "@params strength glow strength.\n") { object->setFogGlow(on_off, strength); } DefineEngineMethod(VolumetricFog, SetFogLightray, void, (bool on_off, F32 strength), , "@brief Changes the lightrays postfx when inside the fog\n\n." "@params on_off set to true to modification of the lightray postfx.\n" "@params strength lightray strength.\n") { object->setFogLightray(on_off, strength); } DefineEngineMethod(VolumetricFog, isInsideFog, bool, (), , "@brief returns true if control object is inside the fog\n\n.") { return object->isInsideFog(); }