/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "FogVolumeRenderNode.h" #include "VisAreas.h" #include "CREFogVolume.h" #include "Cry_Geo.h" #include "ObjMan.h" #include "ClipVolumeManager.h" #include "Environment/OceanEnvironmentBus.h" #include AABB CFogVolumeRenderNode::s_tracableFogVolumeArea(Vec3(0, 0, 0), Vec3(0, 0, 0)); StaticInstance CFogVolumeRenderNode::s_cachedFogVolumes; StaticInstance CFogVolumeRenderNode::s_globalFogVolumeMap; bool CFogVolumeRenderNode::s_forceTraceableAreaUpdate(false); void CFogVolumeRenderNode::StaticReset() { stl::free_container(s_cachedFogVolumes); } void CFogVolumeRenderNode::ForceTraceableAreaUpdate() { s_forceTraceableAreaUpdate = true; } void CFogVolumeRenderNode::SetTraceableArea(const AABB& traceableArea, const SRenderingPassInfo& passInfo) { // do we bother? if (!GetCVars()->e_Fog || !GetCVars()->e_FogVolumes) { return; } if (GetCVars()->e_VolumetricFog != 0) { return; } // is update of traceable areas necessary if (!s_forceTraceableAreaUpdate) { if ((s_tracableFogVolumeArea.GetCenter() - traceableArea.GetCenter()).GetLengthSquared() < 1e-4f && (s_tracableFogVolumeArea.GetSize() - traceableArea.GetSize()).GetLengthSquared() < 1e-4f) { return; } } // set new area and reset list of traceable fog volumes s_tracableFogVolumeArea = traceableArea; s_cachedFogVolumes.resize(0); // collect all candidates Vec3 traceableAreaCenter(s_tracableFogVolumeArea.GetCenter()); IVisArea* pVisAreaOfCenter(GetVisAreaManager() ? GetVisAreaManager()->GetVisAreaFromPos(traceableAreaCenter) : NULL); GlobalFogVolumeMap::const_iterator itEnd(s_globalFogVolumeMap.end()); for (GlobalFogVolumeMap::const_iterator it(s_globalFogVolumeMap.begin()); it != itEnd; ++it) { const CFogVolumeRenderNode* pFogVolume(*it); if (pVisAreaOfCenter || (!pVisAreaOfCenter && !pFogVolume->GetEntityVisArea())) // if outside only add fog volumes which are outside as well { if (Overlap::AABB_AABB(s_tracableFogVolumeArea, pFogVolume->m_WSBBox)) // bb of fog volume overlaps with traceable area { s_cachedFogVolumes.push_back(SCachedFogVolume(pFogVolume, Vec3(pFogVolume->m_pos - traceableAreaCenter).GetLengthSquared())); } } } // sort by distance std::sort(s_cachedFogVolumes.begin(), s_cachedFogVolumes.end()); // reset force-update flags s_forceTraceableAreaUpdate = false; } void CFogVolumeRenderNode::RegisterFogVolume(const CFogVolumeRenderNode* pFogVolume) { GlobalFogVolumeMap::const_iterator it(s_globalFogVolumeMap.find(pFogVolume)); assert(it == s_globalFogVolumeMap.end() && "CFogVolumeRenderNode::RegisterFogVolume() -- Fog volume already registered!"); if (it == s_globalFogVolumeMap.end()) { s_globalFogVolumeMap.insert(pFogVolume); ForceTraceableAreaUpdate(); } } void CFogVolumeRenderNode::UnregisterFogVolume(const CFogVolumeRenderNode* pFogVolume) { GlobalFogVolumeMap::iterator it(s_globalFogVolumeMap.find(pFogVolume)); assert(it != s_globalFogVolumeMap.end() && "CFogVolumeRenderNode::UnRegisterFogVolume() -- Fog volume previously not registered!"); if (it != s_globalFogVolumeMap.end()) { s_globalFogVolumeMap.erase(it); ForceTraceableAreaUpdate(); } } CFogVolumeRenderNode::CFogVolumeRenderNode() : m_matNodeWS() , m_matWS() , m_matWSInv() , m_volumeType(0) , m_pos(0, 0, 0) , m_x(1, 0, 0) , m_y(0, 1, 0) , m_z(0, 0, 1) , m_size(1, 1, 1) , m_scale(1, 1, 1) , m_globalDensity(1) , m_densityOffset(0) , m_nearCutoff(0) , m_fHDRDynamic(0) , m_softEdges(1) , m_color(1, 1, 1, 1) , m_useGlobalFogColor(false) , m_affectsThisAreaOnly(false) , m_rampParams(0, 1, 0) , m_updateFrameID(0) , m_windInfluence(1) , m_noiseElapsedTime(-5000.0f) , m_densityNoiseScale(0) , m_densityNoiseOffset(0) , m_densityNoiseTimeFrequency(0) , m_densityNoiseFrequency(1, 1, 1) , m_heightFallOffDir(0, 0, 1) , m_heightFallOffDirScaled(0, 0, 1) , m_heightFallOffShift(0, 0, 0) , m_heightFallOffBasePoint(0, 0, 0) , m_localBounds(Vec3(-0.5f, -0.5f, -0.5f), Vec3(0.5f, 0.5f, 0.5f)) , m_globalDensityFader() , m_pMatFogVolEllipsoid(0) , m_pMatFogVolBox(0) , m_WSBBox() , m_cachedSoftEdgesLerp(1, 0) , m_cachedFogColor(1, 1, 1, 1) { m_matNodeWS.SetIdentity(); m_matWS.SetIdentity(); m_matWSInv.SetIdentity(); m_windOffset.x = cry_random(0.0f, 1000.0f); m_windOffset.y = cry_random(0.0f, 1000.0f); m_windOffset.z = cry_random(0.0f, 1000.0f); for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++i) { m_pFogVolumeRenderElement[i] = (CREFogVolume*) GetRenderer()->EF_CreateRE(eDATA_FogVolume); } m_pMatFogVolEllipsoid = Get3DEngine()->m_pMatFogVolEllipsoid; m_pMatFogVolBox = Get3DEngine()->m_pMatFogVolBox; //Get3DEngine()->RegisterEntity( this ); RegisterFogVolume(this); } CFogVolumeRenderNode::~CFogVolumeRenderNode() { for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++i) { if (m_pFogVolumeRenderElement[i]) { m_pFogVolumeRenderElement[i]->Release(false); m_pFogVolumeRenderElement[i] = 0; } } UnregisterFogVolume(this); Get3DEngine()->FreeRenderNodeState(this); } void CFogVolumeRenderNode::UpdateFogVolumeMatrices() { // update matrices used for ray tracing, distance sorting, etc. Matrix34 mtx = Matrix34::CreateFromVectors(m_size.x * m_x * 0.5f, m_size.y * m_y * 0.5f, m_size.z * m_z * 0.5f, m_pos); m_matWS = mtx; m_matWSInv = mtx.GetInverted(); } void CFogVolumeRenderNode::UpdateWorldSpaceBBox() { // update bounding box in world space used for culling m_WSBBox.SetTransformedAABB(m_matNodeWS, m_localBounds); } void CFogVolumeRenderNode::UpdateHeightFallOffBasePoint() { m_heightFallOffBasePoint = m_pos + m_heightFallOffShift; } void CFogVolumeRenderNode::SetFogVolumeProperties(const SFogVolumeProperties& properties) { m_globalDensityFader.SetInvalid(); assert(properties.m_size.x > 0 && properties.m_size.y > 0 && properties.m_size.z > 0); if ((m_size - properties.m_size).GetLengthSquared() > 1e-4) { m_size = properties.m_size; m_localBounds.min = Vec3(-0.5f, -0.5f, -0.5f).CompMul(m_size); m_localBounds.max = -m_localBounds.min; UpdateWorldSpaceBBox(); } m_volumeType = properties.m_volumeType; assert(m_volumeType >= 0 && m_volumeType <= 1); m_color = properties.m_color; assert(properties.m_globalDensity >= 0); m_useGlobalFogColor = properties.m_useGlobalFogColor; m_globalDensity = properties.m_globalDensity; m_densityOffset = properties.m_densityOffset; m_nearCutoff = properties.m_nearCutoff; m_fHDRDynamic = properties.m_fHDRDynamic; assert(properties.m_softEdges >= 0 && properties.m_softEdges <= 1); m_softEdges = properties.m_softEdges; // IgnoreVisArea and AffectsThisAreaOnly don't work concurrently. SetRndFlags(ERF_RENDER_ALWAYS, properties.m_ignoresVisAreas && !properties.m_affectsThisAreaOnly); m_affectsThisAreaOnly = properties.m_affectsThisAreaOnly; float latiArc(DEG2RAD(90.0f - properties.m_heightFallOffDirLati)); float longArc(DEG2RAD(properties.m_heightFallOffDirLong)); float sinLati(sinf(latiArc)); float cosLati(cosf(latiArc)); float sinLong(sinf(longArc)); float cosLong(cosf(longArc)); m_heightFallOffDir = Vec3(sinLati * cosLong, sinLati * sinLong, cosLati); m_heightFallOffShift = m_heightFallOffDir * properties.m_heightFallOffShift; m_heightFallOffDirScaled = m_heightFallOffDir * properties.m_heightFallOffScale; UpdateHeightFallOffBasePoint(); m_rampParams = Vec3(properties.m_rampStart, properties.m_rampEnd, properties.m_rampInfluence); m_windInfluence = properties.m_windInfluence; m_densityNoiseScale = properties.m_densityNoiseScale; m_densityNoiseOffset = properties.m_densityNoiseOffset + 1.0f; m_densityNoiseTimeFrequency = properties.m_densityNoiseTimeFrequency; m_densityNoiseFrequency = properties.m_densityNoiseFrequency * 0.01f;// scale the value to useful range } const Matrix34& CFogVolumeRenderNode::GetMatrix() const { return m_matNodeWS; } void CFogVolumeRenderNode::GetLocalBounds(AABB& bbox) { bbox = m_localBounds; }; void CFogVolumeRenderNode::SetMatrix(const Matrix34& mat) { m_matNodeWS = mat; // get translation and rotational part of fog volume from entity matrix // scale is specified explicitly as fog volumes can be non-uniformly scaled m_pos = m_matNodeWS.GetTranslation(); m_x = m_matNodeWS.GetColumn(0); m_y = m_matNodeWS.GetColumn(1); m_z = m_matNodeWS.GetColumn(2); UpdateFogVolumeMatrices(); UpdateWorldSpaceBBox(); UpdateHeightFallOffBasePoint(); Get3DEngine()->RegisterEntity(this); ForceTraceableAreaUpdate(); } void CFogVolumeRenderNode::SetScale(const Vec3& scale) { m_scale = scale; } void CFogVolumeRenderNode::FadeGlobalDensity(float fadeTime, float newGlobalDensity) { if (newGlobalDensity >= 0) { if (fadeTime == 0) { m_globalDensity = newGlobalDensity; m_globalDensityFader.SetInvalid(); } else if (fadeTime > 0) { float curFrameTime(gEnv->pTimer->GetCurrTime()); m_globalDensityFader.Set(curFrameTime, curFrameTime + fadeTime, m_globalDensity, newGlobalDensity); } } } const char* CFogVolumeRenderNode::GetEntityClassName() const { return "FogVolume"; } const char* CFogVolumeRenderNode::GetName() const { return "FogVolume"; } ColorF CFogVolumeRenderNode::GetFogColor() const { //FUNCTION_PROFILER_3DENGINE Vec3 fogColor(m_color.r, m_color.g, m_color.b); bool bVolFogEnabled = (GetCVars()->e_VolumetricFog != 0); if (bVolFogEnabled) { if (m_useGlobalFogColor) { Get3DEngine()->GetGlobalParameter(E3DPARAM_VOLFOG2_COLOR, fogColor); } } else { if (m_useGlobalFogColor) { fogColor = Get3DEngine()->GetFogColor(); } bool bHDRModeEnabled = false; GetRenderer()->EF_Query(EFQ_HDRModeEnabled, bHDRModeEnabled); if (bHDRModeEnabled) { const float HDRDynamicMultiplier = 2.0f; fogColor *= powf(HDRDynamicMultiplier, m_fHDRDynamic); } } return fogColor; } Vec2 CFogVolumeRenderNode::GetSoftEdgeLerp(const Vec3& viewerPosOS) const { // Volumetric fog doesn't need special treatment when camera is in the ellipsoid. if (GetCVars()->e_VolumetricFog != 0) { return Vec2(m_softEdges, 1.0f - m_softEdges); } //FUNCTION_PROFILER_3DENGINE // ramp down soft edge factor as soon as camera enters the ellipsoid float softEdge(m_softEdges * clamp_tpl((viewerPosOS.GetLength() - 0.95f) * 20.0f, 0.0f, 1.0f)); return Vec2(softEdge, 1.0f - softEdge); } bool CFogVolumeRenderNode::IsViewerInsideVolume(const SRenderingPassInfo& passInfo) const { const CCamera& cam(passInfo.GetCamera()); // check if fog volumes bounding box intersects the near clipping plane const Plane* pNearPlane(cam.GetFrustumPlane(FR_PLANE_NEAR)); Vec3 pntOnNearPlane(cam.GetPosition() - pNearPlane->DistFromPlane(cam.GetPosition()) * pNearPlane->n); Vec3 pntOnNearPlaneOS(m_matWSInv.TransformPoint(pntOnNearPlane)); Vec3 nearPlaneOS_n(m_matWSInv.TransformVector(pNearPlane->n) /*.GetNormalized()*/); f32 nearPlaneOS_d(-nearPlaneOS_n.Dot(pntOnNearPlaneOS)); // get extreme lengths float t(fabsf(nearPlaneOS_n.x) + fabsf(nearPlaneOS_n.y) + fabsf(nearPlaneOS_n.z)); //float t( 0.0f ); //if( nearPlaneOS_n.x >= 0 ) t += -nearPlaneOS_n.x; else t += nearPlaneOS_n.x; //if( nearPlaneOS_n.y >= 0 ) t += -nearPlaneOS_n.y; else t += nearPlaneOS_n.y; //if( nearPlaneOS_n.z >= 0 ) t += -nearPlaneOS_n.z; else t += nearPlaneOS_n.z; float t0 = t + nearPlaneOS_d; float t1 = -t + nearPlaneOS_d; return t0 * t1 < 0.0f; } void CFogVolumeRenderNode::Render(const SRendParams& rParam, const SRenderingPassInfo& passInfo) { FUNCTION_PROFILER_3DENGINE; // anything to render? if (passInfo.IsRecursivePass()) { return; } if (!m_pMatFogVolBox || !m_pMatFogVolEllipsoid || GetCVars()->e_Fog == 0 || GetCVars()->e_FogVolumes == 0) { return; } const int32 fillThreadID = passInfo.ThreadID(); if (!m_pFogVolumeRenderElement[fillThreadID]) { return; } if (m_globalDensityFader.IsValid()) { float curFrameTime(gEnv->pTimer->GetCurrTime()); m_globalDensity = m_globalDensityFader.GetValue(curFrameTime); if (!m_globalDensityFader.IsTimeInRange(curFrameTime)) { m_globalDensityFader.SetInvalid(); } } // transform camera into fog volumes object space (where fog volume is a unit-sphere at (0,0,0)) const CCamera& cam(passInfo.GetCamera()); Vec3 viewerPosWS(cam.GetPosition()); Vec3 viewerPosOS(m_matWSInv * viewerPosWS); m_cachedFogColor = GetFogColor(); m_cachedSoftEdgesLerp = GetSoftEdgeLerp(viewerPosOS); #if AZ_RENDER_TO_TEXTURE_GEM_ENABLED // volumetric fog is not supported in the render scene to texture pass bool bVolFog = (GetCVars()->e_VolumetricFog != 0) && !passInfo.IsRenderSceneToTexturePass(); #else bool bVolFog = (GetCVars()->e_VolumetricFog != 0); #endif // if AZ_RENDER_TO_TEXTURE_GEM_ENABLED // reset elapsed time for noise when FogVolume stayed out of viewport for 30 frames. // this prevents the time from being too large number. if ((m_updateFrameID + 30) < passInfo.GetMainFrameID() && m_noiseElapsedTime > 5000.0f) { m_noiseElapsedTime = -5000.0f; } if (bVolFog && m_densityNoiseScale > 0.0f && m_updateFrameID != passInfo.GetMainFrameID()) { Vec3 wind = Get3DEngine()->GetGlobalWind(false); const float elapsedTime = gEnv->pTimer->GetFrameTime(); m_windOffset = ((-m_windInfluence * elapsedTime) * wind) + m_windOffset; const float windOffsetSpan = 1000.0f;// it should match the constant value in FogVolume.cfx m_windOffset.x = m_windOffset.x - floor(m_windOffset.x / windOffsetSpan) * windOffsetSpan; m_windOffset.y = m_windOffset.y - floor(m_windOffset.y / windOffsetSpan) * windOffsetSpan; m_windOffset.z = m_windOffset.z - floor(m_windOffset.z / windOffsetSpan) * windOffsetSpan; m_noiseElapsedTime += m_densityNoiseTimeFrequency * elapsedTime; m_updateFrameID = passInfo.GetMainFrameID(); } float densityOffset = bVolFog ? (m_densityOffset * 0.001f) : m_densityOffset;// scale the value to useful range // set render element attributes m_pFogVolumeRenderElement[fillThreadID]->m_center = m_pos; m_pFogVolumeRenderElement[fillThreadID]->m_viewerInsideVolume = IsViewerInsideVolume(passInfo) ? 1 : 0; m_pFogVolumeRenderElement[fillThreadID]->m_affectsThisAreaOnly = m_affectsThisAreaOnly ? 1 : 0; m_pFogVolumeRenderElement[fillThreadID]->m_stencilRef = rParam.nClipVolumeStencilRef; m_pFogVolumeRenderElement[fillThreadID]->m_volumeType = (m_volumeType != 0) ? 1 : 0; m_pFogVolumeRenderElement[fillThreadID]->m_localAABB = m_localBounds; m_pFogVolumeRenderElement[fillThreadID]->m_matWSInv = m_matWSInv; m_pFogVolumeRenderElement[fillThreadID]->m_fogColor = m_cachedFogColor; m_pFogVolumeRenderElement[fillThreadID]->m_globalDensity = m_globalDensity; m_pFogVolumeRenderElement[fillThreadID]->m_densityOffset = densityOffset; m_pFogVolumeRenderElement[fillThreadID]->m_nearCutoff = m_nearCutoff; m_pFogVolumeRenderElement[fillThreadID]->m_softEdgesLerp = m_cachedSoftEdgesLerp; m_pFogVolumeRenderElement[fillThreadID]->m_heightFallOffDirScaled = m_heightFallOffDirScaled; m_pFogVolumeRenderElement[fillThreadID]->m_heightFallOffBasePoint = m_heightFallOffBasePoint; m_pFogVolumeRenderElement[fillThreadID]->m_eyePosInWS = viewerPosWS; m_pFogVolumeRenderElement[fillThreadID]->m_eyePosInOS = viewerPosOS; m_pFogVolumeRenderElement[fillThreadID]->m_rampParams = m_rampParams; m_pFogVolumeRenderElement[fillThreadID]->m_windOffset = m_windOffset; m_pFogVolumeRenderElement[fillThreadID]->m_noiseScale = m_densityNoiseScale; m_pFogVolumeRenderElement[fillThreadID]->m_noiseFreq = m_densityNoiseFrequency; m_pFogVolumeRenderElement[fillThreadID]->m_noiseOffset = m_densityNoiseOffset; m_pFogVolumeRenderElement[fillThreadID]->m_noiseElapsedTime = m_noiseElapsedTime; m_pFogVolumeRenderElement[fillThreadID]->m_scale = m_scale; if (bVolFog && GetCVars()->e_FogVolumesTiledInjection) { // add FogVolume to volumetric fog renderer GetRenderer()->PushFogVolume(m_pFogVolumeRenderElement[fillThreadID], passInfo); } else { IRenderer* pRenderer = GetRenderer(); CRenderObject* pRenderObject = pRenderer->EF_GetObject_Temp(fillThreadID); if (!pRenderObject) { return; } // set basic render object properties pRenderObject->m_II.m_Matrix = m_matNodeWS; pRenderObject->m_fSort = 0; int nAfterWater = GetObjManager()->IsAfterWater(m_pos, passInfo) ? 1 : 0; // TODO: add constant factor to sortID to make fog volumes render before all other alpha transparent geometry (or have separate render list?) pRenderObject->m_fSort = WATER_LEVEL_SORTID_OFFSET * 0.5f; // get shader item SShaderItem& shaderItem(0 != rParam.pMaterial ? rParam.pMaterial->GetShaderItem(0) : 1 == m_volumeType ? m_pMatFogVolBox->GetShaderItem(0) : m_pMatFogVolEllipsoid->GetShaderItem(0)); // get target render list int nList = bVolFog ? EFSLIST_FOG_VOLUME : EFSLIST_TRANSP; // add to renderer GetRenderer()->EF_AddEf(m_pFogVolumeRenderElement[fillThreadID], shaderItem, pRenderObject, passInfo, nList, nAfterWater, SRendItemSorter(rParam.rendItemSorter)); } } void CFogVolumeRenderNode::SetMaterial(_smart_ptr pMat) { } void CFogVolumeRenderNode::GetMemoryUsage(ICrySizer* pSizer) const { SIZER_COMPONENT_NAME(pSizer, "FogVolumeNode"); pSizer->AddObject(this, sizeof(*this)); } void CFogVolumeRenderNode::OffsetPosition(const Vec3& delta) { if (m_pRNTmpData) { m_pRNTmpData->OffsetPosition(delta); } m_pos += delta; m_matNodeWS.SetTranslation(m_matNodeWS.GetTranslation() + delta); m_matWS.SetTranslation(m_matWS.GetTranslation() + delta); m_matWSInv = m_matWS.GetInverted(); m_heightFallOffBasePoint += delta; m_WSBBox.Move(delta); }