You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/Cry3DEngine/Ocean.cpp

1010 lines
36 KiB
C++

/*
* 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.
// Description : Create and draw ocean water geometry (screen space grid, cycle buffers)
#include "Cry3DEngine_precompiled.h"
#include "Ocean.h"
#include "CullBuffer.h"
#include "3dEngine.h"
#include "ObjMan.h"
#include "MatMan.h"
#include "VisAreas.h"
#include "Environment/OceanEnvironmentBus.h"
ITimer* COcean::m_pTimer = 0;
CREWaterOcean* COcean::m_pOceanRE = 0;
uint32 COcean::m_nVisiblePixelsCount = ~0;
float COcean::m_fWaterLevelInfo = WATER_LEVEL_UNKNOWN;
// defined in CryEngine\Cry3DEngine\3dEngine.cpp
namespace OceanGlobals
{
extern float g_oceanStep;
}
COcean::COcean(_smart_ptr<IMaterial> pMat, float fWaterLevel)
{
m_pRenderMesh = 0;
m_pBottomCapRenderMesh = 0;
m_swathWidth = 0;
m_bUsingFFT = false;
m_bUseTessHW = false;
m_fWaterLevel = fWaterLevel;
memset(m_fRECustomData, 0, sizeof(m_fRECustomData));
memset(m_fREOceanBottomCustomData, 0, sizeof(m_fREOceanBottomCustomData));
m_windUvTransform = AZ::Vector2(0.0f, 0.0f);
SetMaterial(pMat);
m_fLastFov = 0;
m_fLastVisibleFrameTime = 0.0f;
m_pShaderOcclusionQuery = GetRenderer()->EF_LoadShader("OcclusionTest", 0);
memset(m_pREOcclusionQueries, 0, sizeof(m_pREOcclusionQueries));
m_nLastVisibleFrameId = 0;
m_pBottomCapMaterial = GetMatMan()->LoadMaterial("EngineAssets/Materials/Water/WaterOceanBottom", false);
m_pFogIntoMat = GetMatMan()->LoadMaterial("EngineAssets/Materials/Fog/OceanInto", false);
m_pFogOutofMat = GetMatMan()->LoadMaterial("EngineAssets/Materials/Fog/OceanOutof", false);
m_pFogIntoMatLowSpec = GetMatMan()->LoadMaterial("EngineAssets/Materials/Fog/OceanIntoLowSpec", false);
m_pFogOutofMatLowSpec = GetMatMan()->LoadMaterial("EngineAssets/Materials/Fog/OceanOutofLowSpec", false);
for (int i = 0; i < RT_COMMAND_BUF_COUNT; i++)
{
m_pWVRE[i] = static_cast<CREWaterVolume*>(GetRenderer()->EF_CreateRE(eDATA_WaterVolume));
if (m_pWVRE[i])
{
m_pWVRE[i]->m_drawWaterSurface = false;
m_pWVRE[i]->m_pParams = &m_wvParams[i];
m_pWVRE[i]->m_pOceanParams = &m_wvoParams[i];
}
}
m_pOceanRE = static_cast<CREWaterOcean*>(GetRenderer()->EF_CreateRE(eDATA_WaterOcean));
m_nVertsCount = 0;
m_nIndicesCount = 0;
m_bOceanFFT = false;
}
COcean::~COcean()
{
for (int32 x = 0; x < CYCLE_BUFFERS_NUM; ++x)
{
if (m_pREOcclusionQueries[x])
{
m_pREOcclusionQueries[x]->Release(true);
}
}
m_pRenderMesh = NULL;
m_pBottomCapRenderMesh = NULL;
SAFE_RELEASE(m_pOceanRE);
for (int i = 0; i < RT_COMMAND_BUF_COUNT; i++)
{
SAFE_RELEASE(m_pWVRE[i]);
}
}
int32 COcean::GetMemoryUsage()
{
int32 nSize = 0;
nSize += sizeofVector(m_pMeshIndices);
nSize += sizeofVector(m_pMeshVerts);
nSize += sizeofVector(m_pBottomCapVerts);
nSize += sizeofVector(m_pBottomCapIndices);
return nSize;
}
void COcean::Update(const SRenderingPassInfo& passInfo)
{
FUNCTION_PROFILER_3DENGINE;
C3DEngine* p3DEngine = (C3DEngine*)Get3DEngine();
IRenderer* pRenderer = GetRenderer();
if (passInfo.IsRecursivePass() || !passInfo.RenderWaterOcean() || !m_pMaterial)
{
return;
}
const CCamera& rCamera = passInfo.GetCamera();
int32 nFillThreadID = passInfo.ThreadID();
uint32 nBufID = passInfo.GetFrameID() % CYCLE_BUFFERS_NUM;
Vec3 vCamPos = rCamera.GetPosition();
float fWaterLevel = OceanToggle::IsActive() ? OceanRequest::GetOceanLevel() : p3DEngine->GetWaterLevel();
// No hardware FFT support
m_bOceanFFT = false;
if ((pRenderer->GetFeatures() & (RFT_HW_VERTEXTEXTURES) && GetCVars()->e_WaterOceanFFT) && pRenderer->EF_GetShaderQuality(eST_Water) >= eSQ_High)
{
m_bOceanFFT = true;
}
if (vCamPos.z < fWaterLevel)
{
// if camera is in indoors and lower than ocean level
// and exit portals are higher than ocean level - skip ocean rendering
CVisArea* pVisArea = (CVisArea*)p3DEngine->GetVisAreaFromPos(vCamPos);
if (pVisArea && !pVisArea->IsPortal())
{
for (int32 i = 0; i < pVisArea->m_lstConnections.Count(); i++)
{
if (pVisArea->m_lstConnections[i]->IsConnectedToOutdoor() && pVisArea->m_lstConnections[i]->m_boxArea.min.z < fWaterLevel)
{
break; // there is portal making ocean visible
}
if (i == pVisArea->m_lstConnections.Count())
{
return; // ocean surface is not visible
}
}
}
}
bool bWaterVisible = IsVisible(passInfo);
float _fWaterPlaneSize = rCamera.GetFarPlane();
// Check if water surface occluded
if (fabs(m_fLastFov - rCamera.GetFov()) < 0.01f && GetCVars()->e_HwOcclusionCullingWater && passInfo.IsGeneralPass())
{
AABB boxOcean(Vec3(vCamPos.x - _fWaterPlaneSize, vCamPos.y - _fWaterPlaneSize, fWaterLevel),
Vec3(vCamPos.x + _fWaterPlaneSize, vCamPos.y + _fWaterPlaneSize, fWaterLevel));
if ((!GetVisAreaManager()->IsOceanVisible() && rCamera.IsAABBVisible_EM(boxOcean)) ||
(GetVisAreaManager()->IsOceanVisible() && rCamera.IsAABBVisible_E(boxOcean)))
{
// make element if not ready
if (!m_pREOcclusionQueries[nBufID])
{
m_pREOcclusionQueries[nBufID] = (CREOcclusionQuery*)GetRenderer()->EF_CreateRE(eDATA_OcclusionQuery);
m_pREOcclusionQueries[nBufID]->m_pRMBox = (CRenderMesh*)GetObjManager()->GetRenderMeshBox();
}
// get last test result
// if((m_pREOcclusionQueries[nFillThreadID][nBufID]->m_nCheckFrame - passInfo.GetFrameID())<2)
{
COcean::m_nVisiblePixelsCount = m_pREOcclusionQueries[nBufID]->m_nVisSamples;
if (COcean::IsWaterVisibleOcclusionCheck())
{
m_nLastVisibleFrameId = passInfo.GetFrameID();
bWaterVisible = true;
}
}
// request new test
m_pREOcclusionQueries[nBufID]->m_vBoxMin(boxOcean.min.x, boxOcean.min.y, boxOcean.min.z - 1.0f);
m_pREOcclusionQueries[nBufID]->m_vBoxMax(boxOcean.max.x, boxOcean.max.y, boxOcean.max.z);
m_pREOcclusionQueries[nBufID]->mfReadResult_Try(COcean::m_nVisiblePixelsCount);
if (!m_pREOcclusionQueries[nBufID]->m_nDrawFrame || m_pREOcclusionQueries[nBufID]->HasSucceeded())
{
SShaderItem shItem(m_pShaderOcclusionQuery);
CRenderObject* pObj = GetIdentityCRenderObject(passInfo.ThreadID());
if (!pObj)
{
return;
}
GetRenderer()->EF_AddEf(m_pREOcclusionQueries[nBufID], shItem, pObj, passInfo, EFSLIST_WATER_VOLUMES, 0, SRendItemSorter::CreateDefaultRendItemSorter());
}
}
}
else
{
m_nLastVisibleFrameId = passInfo.GetFrameID();
bWaterVisible = true;
}
if (bWaterVisible || vCamPos.z < fWaterLevel)
{
m_p3DEngine->SetOceanRenderFlags(OCR_OCEANVOLUME_VISIBLE);
// lazy mesh creation
if (bWaterVisible)
{
Create();
}
}
}
void COcean::Create()
{
// Calculate water geometry and update vertex buffers
int32 nScrGridSizeX;
int32 nScrGridSizeY;
GetOceanGridSize(nScrGridSizeX, nScrGridSizeY);
bool bUseWaterTessHW;
GetRenderer()->EF_Query(EFQ_WaterTessellation, bUseWaterTessHW);
// Generate screen space grid
if ((m_bOceanFFT && m_bUsingFFT != m_bOceanFFT) || m_bUseTessHW != bUseWaterTessHW || m_swathWidth != GetCVars()->e_WaterTessellationSwathWidth || !m_nVertsCount || !m_nIndicesCount || nScrGridSizeX * nScrGridSizeY != m_nPrevGridDim)
{
m_nPrevGridDim = nScrGridSizeX * nScrGridSizeY;
m_pMeshVerts.Clear();
m_pMeshIndices.Clear();
m_nVertsCount = 0;
m_nIndicesCount = 0;
m_bUsingFFT = m_bOceanFFT;
m_bUseTessHW = bUseWaterTessHW;
// Update the swath width
m_swathWidth = GetCVars()->e_WaterTessellationSwathWidth;
// Render ocean with screen space tessellation
int32 nScreenY = GetRenderer()->GetHeight();
int32 nScreenX = GetRenderer()->GetWidth();
if (!nScreenY || !nScreenX)
{
return;
}
float fRcpScrGridSizeX = 1.0f / ((float) nScrGridSizeX - 1);
float fRcpScrGridSizeY = 1.0f / ((float) nScrGridSizeY - 1);
SVF_P3F_C4B_T2F tmp;
Vec3 vv;
vv.z = 0;
m_pMeshVerts.reserve(nScrGridSizeX * nScrGridSizeY);
m_pMeshIndices.reserve(nScrGridSizeX * nScrGridSizeY);
// Grid vertex generation
for (int32 y(0); y < nScrGridSizeY; ++y)
{
vv.y = (float) y * fRcpScrGridSizeY;// + fRcpScrGridSize;
for (int32 x(0); x < nScrGridSizeX; ++x)
{
// vert 1
vv.x = (float) x * fRcpScrGridSizeX;// + fRcpScrGridSize;
// store in z edges information
float fx = fabs((vv.x) * 2.0f - 1.0f);
float fy = fabs((vv.y) * 2.0f - 1.0f);
//float fEdgeDisplace = sqrt_tpl(fx*fx + fy * fy);//max(fx, fy);
float fEdgeDisplace = max(fx, fy);
//sqrt_tpl(fx*fx + fy * fy);
vv.z = fEdgeDisplace; //!((y==0 ||y == nScrGridSize-1) || (x==0 || x == nScrGridSize-1));
int32 n = m_pMeshVerts.Count();
tmp.xyz = vv;
m_pMeshVerts.Add(tmp);
}
}
if (m_bUseTessHW)
{
// Normal approach
int32 nIndex = 0;
for (int32 y(0); y < nScrGridSizeY - 1; ++y)
{
for (int32 x(0); x < nScrGridSizeX - 1; ++x, ++nIndex)
{
m_pMeshIndices.Add(nScrGridSizeX * y + x);
m_pMeshIndices.Add(nScrGridSizeX * y + x + 1);
m_pMeshIndices.Add(nScrGridSizeX * (y + 1) + x);
m_pMeshIndices.Add(nScrGridSizeX * (y + 1) + x);
m_pMeshIndices.Add(nScrGridSizeX * y + x + 1);
m_pMeshIndices.Add(nScrGridSizeX * (y + 1) + x + 1);
//m_pMeshIndices.Add( nIndex );
//m_pMeshIndices.Add( nIndex + 1);
//m_pMeshIndices.Add( nIndex + nScrGridSizeX);
//m_pMeshIndices.Add( nIndex + nScrGridSizeX);
//m_pMeshIndices.Add( nIndex + 1);
//m_pMeshIndices.Add( nIndex + nScrGridSizeX + 1);
}
}
}
else
{
// Grid index generation
if (m_swathWidth < 0)
{
// Normal approach
int32 nIndex = 0;
for (int32 y(0); y < nScrGridSizeY - 1; ++y)
{
for (int32 x(0); x < nScrGridSizeX; ++x, ++nIndex)
{
m_pMeshIndices.Add(nIndex);
m_pMeshIndices.Add(nIndex + nScrGridSizeX);
}
if (nScrGridSizeY - 2 > y)
{
m_pMeshIndices.Add(nIndex + nScrGridSizeY - 1);
m_pMeshIndices.Add(nIndex);
}
}
}
else if(m_swathWidth > 1)
{
// Boustrophedonic walk
//
// 0 1 2 3 4
// 5 6 7 8 9
// 10 11 12 13 14
// 15 16 17 18 19
//
// Should generate the following indices
// 0 5 1 6 2 7 3 8 4 9 9 14 14 9 13 8 12 7 11 6 10 5 5 10 10 15 11 16 12 17 13 18 14 19
//
int32 startX = 0, endX = m_swathWidth - 1;
do
{
for (int32 y(0); y < nScrGridSizeY - 1; y += 2)
{
// Forward
for (int32 x(startX); x <= endX; ++x)
{
m_pMeshIndices.Add(y * nScrGridSizeX + x);
m_pMeshIndices.Add((y + 1) * nScrGridSizeX + x);
}
// Can we go backwards?
if (y + 2 < nScrGridSizeY)
{
// Restart strip by duplicating last and first of next strip
m_pMeshIndices.Add((y + 1) * nScrGridSizeX + endX);
m_pMeshIndices.Add((y + 2) * nScrGridSizeX + endX);
//Backward
for (int32 x(endX); x >= startX; --x)
{
m_pMeshIndices.Add((y + 2) * nScrGridSizeX + x);
m_pMeshIndices.Add((y + 1) * nScrGridSizeX + x);
}
// Restart strip
if (y + 2 == nScrGridSizeY - 1 && endX < nScrGridSizeX - 1)
{
if (endX < nScrGridSizeX - 1)
{
// Need to restart at the top of the next column
m_pMeshIndices.Add((nScrGridSizeY - 1) * nScrGridSizeX + startX);
m_pMeshIndices.Add(endX);
}
}
else
{
m_pMeshIndices.Add((y + 1) * nScrGridSizeX + startX);
m_pMeshIndices.Add((y + 2) * nScrGridSizeX + startX);
}
}
else
{
// We can restart to next column
if (endX < nScrGridSizeX - 1)
{
// Restart strip for next swath
m_pMeshIndices.Add((nScrGridSizeY - 1) * nScrGridSizeX + endX);
m_pMeshIndices.Add(endX);
}
}
}
startX = endX;
endX = startX + m_swathWidth - 1;
if (endX >= nScrGridSizeX)
{
endX = nScrGridSizeX - 1;
}
} while (startX < nScrGridSizeX - 1);
}
else
{
AZ_TracePrintf("Ocean", "e_WaterTessellationSwathWidth cannot be 0.")
}
}
m_nVertsCount = m_pMeshVerts.Count();
m_nIndicesCount = m_pMeshIndices.Count();
m_pRenderMesh = GetRenderer()->CreateRenderMeshInitialized(
m_pMeshVerts.GetElements(),
m_pMeshVerts.Count(),
eVF_P3F_C4B_T2F,
m_pMeshIndices.GetElements(),
m_pMeshIndices.Count(),
m_bUseTessHW ? prtTriangleList : prtTriangleStrip,
"OutdoorWaterGrid", "OutdoorWaterGrid",
eRMT_Static);
m_pRenderMesh->SetChunk(m_pMaterial, 0, m_pMeshVerts.Count(), 0, m_pMeshIndices.Count(), 1.0f, eVF_P3F_C4B_T2F);
if (m_bOceanFFT)
{
m_pOceanRE->Create(m_pMeshVerts.Count(), m_pMeshVerts.GetElements(), m_pMeshIndices.Count(), m_pMeshIndices.GetElements(), sizeof(m_pMeshIndices[0]));
}
m_pMeshVerts.Free();
m_pMeshIndices.Free();
}
}
void COcean::Render(const SRenderingPassInfo& passInfo)
{
FUNCTION_PROFILER_3DENGINE;
// if reaches render stage - means ocean is visible
C3DEngine* p3DEngine = (C3DEngine*)Get3DEngine();
IRenderer* pRenderer(GetRenderer());
int32 nBufID = (passInfo.GetFrameID() & 1);
Vec3 vCamPos = passInfo.GetCamera().GetPosition();
float fWaterLevel = OceanToggle::IsActive() ? OceanRequest::GetOceanLevel() : p3DEngine->GetWaterLevel();
CRenderObject* pObject = GetRenderer()->EF_GetObject_Temp(passInfo.ThreadID());
if (!pObject)
{
return;
}
pObject->m_II.m_Matrix.SetIdentity();
pObject->m_pRenderNode = this;
m_fLastFov = passInfo.GetCamera().GetFov();
// make distance to water level near to zero
m_pRenderMesh->SetBBox(vCamPos, vCamPos);
// test for multiple lights and shadows support
SRenderObjData* pOD = pRenderer->EF_GetObjData(pObject, true, passInfo.ThreadID());
m_Camera = passInfo.GetCamera();
pObject->m_fAlpha = 1.f;
int32 gridSizeX;
int32 gridSizeY;
GetOceanGridSize(gridSizeX, gridSizeY);
const float tessellationScaleFactor = 1000.0f; // physical size of grid is how many grid tiles per thousand meters.
m_fRECustomData[0] = tessellationScaleFactor / (gridSizeX - 1);
const auto oceanAnimationData = p3DEngine->GetOceanAnimationParams();
m_fRECustomData[1] = oceanAnimationData.fWindSpeed;
m_fRECustomData[2] = oceanAnimationData.fWavesSpeed;
m_fRECustomData[3] = oceanAnimationData.fWavesAmount;
m_fRECustomData[4] = oceanAnimationData.fWavesSize;
const float timeDiff = p3DEngine->GetTimer()->GetFrameTime();
// calculate the wind direction
AZ::Vector2 windDirectionVector = AZ::Vector2::CreateFromAngle(oceanAnimationData.fWindDirection);
// calculate wind offset based on speed and time delta
float windFrameOffset = 0.0025f * timeDiff * oceanAnimationData.fWindSpeed;
m_windUvTransform += windDirectionVector * windFrameOffset;
// update constant buffer with the values;
m_fRECustomData[6] = m_windUvTransform.GetX();
m_fRECustomData[5] = m_windUvTransform.GetY();
m_fRECustomData[7] = fWaterLevel;
m_fRECustomData[8] = m_fRECustomData[9] = m_fRECustomData[10] = m_fRECustomData[11] = 0.0f;
bool isFastpath = GetCVars()->e_WaterOcean == 2;
bool bUsingMergedFog = false;
{
Vec3 camPos(passInfo.GetCamera().GetPosition());
// Check if we outside water volume - we can enable fast path with merged fog version
if (camPos.z - fWaterLevel >= oceanAnimationData.fWavesSize)
{
Vec3 fogColor;
if (OceanToggle::IsActive())
{
AZ::Vector3 oceanFogColor = OceanRequest::GetFogColorPremultiplied();
fogColor = Vec3(oceanFogColor.GetX(), oceanFogColor.GetY(), oceanFogColor.GetZ());
}
else
{
fogColor = m_p3DEngine->m_oceanFogColor;
}
Vec3 cFinalFogColor = gEnv->p3DEngine->GetSunColor().CompMul(fogColor);
float fogDensity = OceanToggle::IsActive() ? OceanRequest::GetFogDensity() : m_p3DEngine->m_oceanFogDensity;
Vec4 vFogParams = Vec4(cFinalFogColor, fogDensity * 1.44269502f);// log2(e) = 1.44269502
m_fRECustomData[8] = vFogParams.x;
m_fRECustomData[9] = vFogParams.y;
m_fRECustomData[10] = vFogParams.z;
m_fRECustomData[11] = vFogParams.w;
if (isFastpath)
{
bUsingMergedFog = true;
}
}
}
{
CMatInfo* pMatInfo = (CMatInfo*)m_pMaterial.get();
float fInstanceDistance = AZ::OceanConstants::s_oceanIsVeryFarAway;
pMatInfo->PrecacheMaterial(fInstanceDistance, 0, false);
}
if (!GetCVars()->e_WaterOceanFFT || !m_bOceanFFT)
{
m_pRenderMesh->SetREUserData(&m_fRECustomData[0]);
m_pRenderMesh->AddRenderElements(m_pMaterial, pObject, passInfo, EFSLIST_WATER, 0);
}
else
{
SShaderItem& shaderItem(m_pMaterial->GetShaderItem(0));
m_pOceanRE->m_CustomData = &m_fRECustomData[0];
pRenderer->EF_AddEf(m_pOceanRE, shaderItem, pObject, passInfo, EFSLIST_WATER, 0, SRendItemSorter::CreateDefaultRendItemSorter());
}
bool useOceanBottom = OceanToggle::IsActive() ? OceanRequest::GetUseOceanBottom() : (GetCVars()->e_WaterOceanBottom == 1);
if (useOceanBottom)
{
RenderBottomCap(passInfo);
}
if (!bUsingMergedFog)
{
RenderFog(passInfo);
}
}
void COcean::SetMaterial(_smart_ptr<IMaterial> pMat)
{
m_pMaterial = pMat;
}
void COcean::RenderBottomCap(const SRenderingPassInfo& passInfo)
{
C3DEngine* p3DEngine = (C3DEngine*)Get3DEngine();
Vec3 vCamPos = passInfo.GetCamera().GetPosition();
// Render ocean with screen space tessellation
int32 nScreenY = GetRenderer()->GetHeight();
int32 nScreenX = GetRenderer()->GetWidth();
if (!nScreenY || !nScreenX)
{
return;
}
// Calculate water geometry and update vertex buffers
static const int32 nScrGridSize = 5;
// distance between grid points for -1.0 to 1.0 space.
static const float fRcpScrGridSize = 2.0f / static_cast<float>(nScrGridSize - 1);
if (!m_pBottomCapVerts.Count() || !m_pBottomCapIndices.Count() || nScrGridSize * nScrGridSize != m_pBottomCapVerts.Count())
{
m_pBottomCapVerts.Clear();
m_pBottomCapIndices.Clear();
SVF_P3F_C4B_T2F tmp;
tmp.xyz.z = 1.0f;
// Grid vertex generation
for (int32 y = 0; y < nScrGridSize; ++y)
{
tmp.xyz.y = -1.0f + y * fRcpScrGridSize;
for (int32 x(0); x < nScrGridSize; ++x)
{
tmp.xyz.x = -1.0f + x * fRcpScrGridSize;
m_pBottomCapVerts.Add(tmp);
}
}
// Normal approach
int32 nIndex = 0;
for (int32 y(0); y < nScrGridSize - 1; ++y)
{
for (int32 x(0); x < nScrGridSize; ++x, ++nIndex)
{
m_pBottomCapIndices.Add(nIndex);
m_pBottomCapIndices.Add(nIndex + nScrGridSize);
}
if (nScrGridSize - 2 > y)
{
m_pBottomCapIndices.Add(nIndex + nScrGridSize - 1);
m_pBottomCapIndices.Add(nIndex);
}
}
m_pBottomCapRenderMesh = GetRenderer()->CreateRenderMeshInitialized(
m_pBottomCapVerts.GetElements(),
m_pBottomCapVerts.Count(),
eVF_P3F_C4B_T2F,
m_pBottomCapIndices.GetElements(),
m_pBottomCapIndices.Count(),
prtTriangleStrip,
"OceanBottomGrid", "OceanBottomGrid",
eRMT_Static);
m_pBottomCapRenderMesh->SetChunk(m_pBottomCapMaterial, 0, m_pBottomCapVerts.Count(), 0, m_pBottomCapIndices.Count(), 1.0f, eVF_P3F_C4B_T2F);
}
CRenderObject* pObject = GetRenderer()->EF_GetObject_Temp(passInfo.ThreadID());
if (!pObject)
{
return;
}
pObject->m_II.m_Matrix.SetIdentity();
pObject->m_pRenderNode = this;
// make distance to water level near to zero
m_pBottomCapRenderMesh->SetBBox(vCamPos, vCamPos);
m_Camera = passInfo.GetCamera();
pObject->m_fAlpha = 1.f;
m_pBottomCapRenderMesh->AddRenderElements(m_pBottomCapMaterial, pObject, passInfo, EFSLIST_GENERAL, 0);
}
void COcean::RenderFog(const SRenderingPassInfo& passInfo)
{
if (!GetCVars()->e_Fog || !GetCVars()->e_FogVolumes)
{
return;
}
IRenderer* pRenderer(GetRenderer());
C3DEngine* p3DEngine(Get3DEngine());
const int fillThreadID = passInfo.ThreadID();
CRenderObject* pROVol(pRenderer->EF_GetObject_Temp(fillThreadID));
if (!pROVol)
{
return;
}
bool isFastpath = GetCVars()->e_WaterOcean == 2;
bool isLowSpec(GetCVars()->e_ObjQuality == CONFIG_LOW_SPEC || isFastpath);
if (pROVol && m_pWVRE[fillThreadID] && ((!isLowSpec && m_pFogIntoMat && m_pFogOutofMat) ||
(isLowSpec && m_pFogIntoMatLowSpec && m_pFogOutofMatLowSpec)))
{
Vec3 camPos(passInfo.GetCamera().GetPosition());
float waterLevel(OceanToggle::IsActive() ? OceanRequest::GetOceanLevel() : p3DEngine->GetWaterLevel());
Vec3 planeOrigin(camPos.x, camPos.y, waterLevel);
// fill water volume param structure
m_wvParams[fillThreadID].m_center = planeOrigin;
m_wvParams[fillThreadID].m_fogPlane.Set(Vec3(0, 0, 1), -waterLevel);
float distCamToFogPlane(camPos.z + m_wvParams[fillThreadID].m_fogPlane.d);
m_wvParams[fillThreadID].m_viewerCloseToWaterPlane = (distCamToFogPlane) < 0.5f;
m_wvParams[fillThreadID].m_viewerInsideVolume = distCamToFogPlane < 0.00f;
m_wvParams[fillThreadID].m_viewerCloseToWaterVolume = true;
const auto oceanAnimationData = p3DEngine->GetOceanAnimationParams();
if (!isFastpath || (distCamToFogPlane < oceanAnimationData.fWavesSize))
{
Vec3 fogColor;
Vec3 fogColorShallow;
bool oceanToggleIsActive = OceanToggle::IsActive();
if (oceanToggleIsActive)
{
AZ::Vector3 oceanFogColor = OceanRequest::GetFogColorPremultiplied();
fogColor = Vec3(oceanFogColor.GetX(), oceanFogColor.GetY(), oceanFogColor.GetZ());
AZ::Vector3 nearFogColor = OceanRequest::GetNearFogColor();
fogColorShallow = Vec3(nearFogColor.GetX(), nearFogColor.GetY(), nearFogColor.GetZ());
}
else
{
fogColor = m_p3DEngine->m_oceanFogColor;
fogColorShallow = m_p3DEngine->m_oceanFogColorShallow;
}
float fogDensity = oceanToggleIsActive ? OceanRequest::GetFogDensity() : m_p3DEngine->m_oceanFogDensity;
if (isLowSpec)
{
m_wvParams[fillThreadID].m_fogColor = fogColor;
m_wvParams[fillThreadID].m_fogDensity = fogDensity;
m_wvoParams[fillThreadID].m_fogColor = Vec3(0, 0, 0); // not needed for low spec
m_wvoParams[fillThreadID].m_fogColorShallow = Vec3(0, 0, 0); // not needed for low spec
m_wvoParams[fillThreadID].m_fogDensity = 0; // not needed for low spec
m_pWVRE[fillThreadID]->m_pOceanParams = 0;
}
else
{
m_wvParams[fillThreadID].m_fogColor = Vec3(0, 0, 0); // not needed, we set ocean specific params below
m_wvParams[fillThreadID].m_fogDensity = 0; // not needed, we set ocean specific params below
m_wvoParams[fillThreadID].m_fogColor = fogColor;
m_wvoParams[fillThreadID].m_fogColorShallow = fogColorShallow;
m_wvoParams[fillThreadID].m_fogDensity = fogDensity;
m_pWVRE[fillThreadID]->m_pOceanParams = &m_wvoParams[fillThreadID];
}
// tessellate plane
float planeSize(2.0f * passInfo.GetCamera().GetFarPlane());
size_t subDivSize(min(64, 1 + (int32) (planeSize / 512.0f)));
if (isFastpath)
{
subDivSize = 4;
}
size_t numSubDivVerts((subDivSize + 1) * (subDivSize + 1));
if (m_wvVertices[fillThreadID].size() != numSubDivVerts)
{
m_wvVertices[fillThreadID].resize(numSubDivVerts);
m_wvParams[fillThreadID].m_pVertices = &m_wvVertices[fillThreadID][0];
m_wvParams[fillThreadID].m_numVertices = m_wvVertices[fillThreadID].size();
m_wvIndices[fillThreadID].resize(subDivSize * subDivSize * 6);
m_wvParams[fillThreadID].m_pIndices = &m_wvIndices[fillThreadID][0];
m_wvParams[fillThreadID].m_numIndices = m_wvIndices[fillThreadID].size();
size_t ind(0);
for (uint32 y(0); y < subDivSize; ++y)
{
for (uint32 x(0); x < subDivSize; ++x, ind += 6)
{
m_wvIndices[fillThreadID][ind + 0] = (y) * (subDivSize + 1) + (x);
m_wvIndices[fillThreadID][ind + 1] = (y) * (subDivSize + 1) + (x + 1);
m_wvIndices[fillThreadID][ind + 2] = (y + 1) * (subDivSize + 1) + (x + 1);
m_wvIndices[fillThreadID][ind + 3] = (y) * (subDivSize + 1) + (x);
m_wvIndices[fillThreadID][ind + 4] = (y + 1) * (subDivSize + 1) + (x + 1);
m_wvIndices[fillThreadID][ind + 5] = (y + 1) * (subDivSize + 1) + (x);
}
}
}
{
float xyDelta(2.0f * planeSize / (float) subDivSize);
float zDelta(waterLevel - camPos.z);
size_t ind(0);
float yd(-planeSize);
for (uint32 y(0); y <= subDivSize; ++y, yd += xyDelta)
{
float xd(-planeSize);
for (uint32 x(0); x <= subDivSize; ++x, xd += xyDelta, ++ind)
{
m_wvVertices[fillThreadID][ind].xyz = Vec3(xd, yd, zDelta);
m_wvVertices[fillThreadID][ind].st = Vec2(0, 0);
}
}
}
// fill in data for render object
pROVol->m_II.m_Matrix.SetIdentity();
pROVol->m_fSort = 0;
// get shader item
SShaderItem& shaderItem(m_wvParams[fillThreadID].m_viewerInsideVolume ?
(isLowSpec ? m_pFogOutofMatLowSpec->GetShaderItem(0) : m_pFogOutofMat->GetShaderItem(0)) :
(isLowSpec ? m_pFogIntoMatLowSpec->GetShaderItem(0) : m_pFogIntoMat->GetShaderItem(0)));
// add to renderer
pRenderer->EF_AddEf(m_pWVRE[fillThreadID], shaderItem, pROVol, passInfo, EFSLIST_WATER_VOLUMES, distCamToFogPlane < -0.1f, SRendItemSorter::CreateDefaultRendItemSorter());
}
}
}
bool COcean::IsVisible(const SRenderingPassInfo& passInfo)
{
if (abs(m_nLastVisibleFrameId - passInfo.GetFrameID()) <= 2)
{
m_fLastVisibleFrameTime = 0.0f;
}
ITimer* pTimer(gEnv->pTimer);
m_fLastVisibleFrameTime += gEnv->pTimer->GetFrameTime();
if (m_fLastVisibleFrameTime > 2.0f) // at least 2 seconds
{
return (abs(m_nLastVisibleFrameId - passInfo.GetFrameID()) < 64); // and at least 64 frames
}
return true; // keep water visible for a couple frames - or at least 1 second - minimizes popping during fast camera movement
}
void COcean::SetTimer(ITimer* pTimer)
{
assert(pTimer);
m_pTimer = pTimer;
}
float COcean::GetWave(const Vec3& pPos, int32 nFrameID)
{
// todo: optimize...
IRenderer* pRenderer(GetRenderer());
if (!pRenderer)
{
return 0.0f;
}
EShaderQuality nShaderQuality = pRenderer->EF_GetShaderQuality(eST_Water);
if (!m_pTimer || nShaderQuality < eSQ_High)
{
return 0.0f;
}
// Return height - matching computation on GPU
C3DEngine* p3DEngine(Get3DEngine());
bool bOceanFFT = false;
if ((pRenderer->GetFeatures() & (RFT_HW_VERTEXTEXTURES) && GetCVars()->e_WaterOceanFFT) && nShaderQuality >= eSQ_High)
{
bOceanFFT = true;
}
const auto oceanAnimationData = p3DEngine->GetOceanAnimationParams();
if (bOceanFFT)
{
Vec4 pDispPos = Vec4(0, 0, 0, 0);
if (m_pOceanRE)
{
// Get height from FFT grid
Vec4* pGridFFT = m_pOceanRE->GetDisplaceGrid();
if (!pGridFFT)
{
return 0.0f;
}
// match scales used in shader
float fScaleX = pPos.x * 0.0125f * oceanAnimationData.fWavesAmount * 1.25f;
float fScaleY = pPos.y * 0.0125f * oceanAnimationData.fWavesAmount * 1.25f;
float fu = fScaleX * 64.0f;
float fv = fScaleY * 64.0f;
int32 u1 = ((int32)fu) & 63;
int32 v1 = ((int32)fv) & 63;
int32 u2 = (u1 + 1) & 63;
int32 v2 = (v1 + 1) & 63;
// Fractional parts
float fracu = fu - floorf(fu);
float fracv = fv - floorf(fv);
// Get weights
float w1 = (1 - fracu) * (1 - fracv);
float w2 = fracu * (1 - fracv);
float w3 = (1 - fracu) * fracv;
float w4 = fracu * fracv;
Vec4 h1 = pGridFFT[u1 + v1 * 64];
Vec4 h2 = pGridFFT[u2 + v1 * 64];
Vec4 h3 = pGridFFT[u1 + v2 * 64];
Vec4 h4 = pGridFFT[u2 + v2 * 64];
// scale and sum the four heights
pDispPos = h1 * w1 + h2 * w2 + h3 * w3 + h4 * w4;
}
// match scales used in shader
return pDispPos.z * 0.06f * oceanAnimationData.fWavesSize;
}
// constant to scale down values a bit
const float fAnimAmplitudeScale = 1.0f / 5.0f;
static int32 s_nFrameID = 0;
static Vec3 s_vFlowDir = Vec3(0, 0, 0);
static Vec4 s_vFrequencies = Vec4(0, 0, 0, 0);
static Vec4 s_vPhases = Vec4(0, 0, 0, 0);
static Vec4 s_vAmplitudes = Vec4(0, 0, 0, 0);
// Update per-frame data
if (s_nFrameID != nFrameID)
{
sincos_tpl(oceanAnimationData.fWindDirection, &s_vFlowDir.y, &s_vFlowDir.x);
s_vFrequencies = Vec4(0.233f, 0.455f, 0.6135f, -0.1467f) * oceanAnimationData.fWavesSpeed * 5.0f;
s_vPhases = Vec4(0.1f, 0.159f, 0.557f, 0.2199f) * oceanAnimationData.fWavesAmount;
s_vAmplitudes = Vec4(1.0f, 0.5f, 0.25f, 0.5f) * oceanAnimationData.fWavesSize;
s_nFrameID = nFrameID;
}
const float fPhase = sqrt_tpl(pPos.x * pPos.x + pPos.y * pPos.y);
Vec4 vCosPhase = s_vPhases * (fPhase + pPos.x);
Vec4 vWaveFreq = s_vFrequencies * m_pTimer->GetCurrTime();
Vec4 vCosWave = Vec4(cos_tpl(vWaveFreq.x * s_vFlowDir.x + vCosPhase.x),
cos_tpl(vWaveFreq.y * s_vFlowDir.x + vCosPhase.y),
cos_tpl(vWaveFreq.z * s_vFlowDir.x + vCosPhase.z),
cos_tpl(vWaveFreq.w * s_vFlowDir.x + vCosPhase.w));
Vec4 vSinPhase = s_vPhases * (fPhase + pPos.y);
Vec4 vSinWave = Vec4(sin_tpl(vWaveFreq.x * s_vFlowDir.y + vSinPhase.x),
sin_tpl(vWaveFreq.y * s_vFlowDir.y + vSinPhase.y),
sin_tpl(vWaveFreq.z * s_vFlowDir.y + vSinPhase.z),
sin_tpl(vWaveFreq.w * s_vFlowDir.y + vSinPhase.w));
return (vCosWave.Dot(s_vAmplitudes) + vSinWave.Dot(s_vAmplitudes)) * fAnimAmplitudeScale;
}
uint32 COcean::GetVisiblePixelsCount()
{
return m_nVisiblePixelsCount;
}
bool COcean::IsWaterVisibleOcclusionCheck()
{
//Metal only supports yes(1 or more pixels)/no occlusion querys. m_nVisiblePixelsCount will always be 1 for yes, 0 for no.
#if defined(IOS) || defined(ANDROID)
return m_nVisiblePixelsCount >= 1;
#else
return m_nVisiblePixelsCount > 16;
#endif
}
// Gets the X & Y Grid sizes based on cVars. Individual X & Y CVars should probably go away in
// favor of a single value that exists on an ocean component instead of using CVars at all.
void COcean::GetOceanGridSize(int& outX, int& outY) const
{
// Calculate water geometry and update vertex buffers
if (OceanToggle::IsActive())
{
outX = AZ::OceanConstants::s_waterTessellationDefault;
AZ::OceanEnvironmentBus::BroadcastResult(outX, &AZ::OceanEnvironmentBus::Events::GetWaterTessellationAmount);
outY = outX;
}
else
{
// Calculate water geometry and update vertex buffers
outX = outY = GetCVars()->e_WaterTessellationAmount;
bool bUseWaterTessHW;
GetRenderer()->EF_Query(EFQ_WaterTessellation, bUseWaterTessHW);
if (!bUseWaterTessHW && m_bOceanFFT)
{
outX = outY = 20 * 10; // for hi/very specs - use maximum tessellation
}
}
}
void COcean::SetWaterLevel(float fWaterLevel)
{
m_fWaterLevel = fWaterLevel;
OceanGlobals::g_oceanStep = -1; // if e_PhysOceanCell is used, make it re-apply the params on Update
}