Files
watrbx-game-engine/Rendering/GfxRender/Water.cpp
T
2025-09-18 17:55:52 -04:00

366 lines
12 KiB
C++

#include "stdafx.h"
#include "Water.h"
#include "VisualEngine.h"
#include "SceneManager.h"
#include "ShaderManager.h"
#include "TextureManager.h"
#include "LightGrid.h"
#include "Material.h"
#include "GfxCore/Device.h"
#include "EnvMap.h"
#include "SSAO.h"
#include "V8DataModel/DataModel.h"
#include "V8DataModel/Workspace.h"
#include "v8datamodel/MegaCluster.h"
#include "Voxel2/Grid.h"
namespace RBX
{
namespace Graphics
{
static const double kAnimLength = 2.f;
#ifdef RBX_PLATFORM_IOS
static const char* kTextureExt = "pvr";
#elif defined(__ANDROID__)
static const char* kTextureExt = "pvr";
#else
static const char* kTextureExt = "dds";
#endif
static bool isCameraUnderwaterLegacy(MegaClusterInstance* terrain)
{
Camera* cam = DataModel::get(terrain)->getWorkspace()->getCamera();
if (!cam)
return false;
Vector3 cameraPos = cam->getCameraCoordinateFrame().translation;
Vector3 cameraDir = (cam->getCameraFocus().translation - cameraPos).unit();
// this makes popping a bit less evident at steep diving angles
cameraPos.y += 0.8f * cameraDir.y + 0.15f; // - kWaveHeight b/c the shader lowers the water level a little bit
Vector3int16 cellPos = Voxel::worldToCell_floor(cameraPos);
return terrain->getVoxelGrid()->getCell(cellPos).isExplicitWaterCell();
}
static float getWaterDisplacement(const Vector3& position, float waveFrequency, float wavePhase, float waveHeight)
{
// Has to match displacePosition from smoothwater.hlsl
float x = sin((position.z - position.x) * waveFrequency - wavePhase);
float z = sin((position.z + position.x) * waveFrequency + wavePhase);
float p = (x + z) * waveHeight;
return p;
}
static bool isCameraUnderwater(MegaClusterInstance* terrain)
{
Camera* cam = DataModel::get(terrain)->getWorkspace()->getCamera();
if (!cam)
return false;
Vector3 cameraPos = cam->getCameraCoordinateFrame().translation;
Vector3 cameraDir = (cam->getCameraFocus().translation - cameraPos).unit();
// this makes popping a bit less evident at steep diving angles
cameraPos.y += 0.8f * cameraDir.y + 0.15f; // - kWaveHeight b/c the shader lowers the water level a little bit
Vector3int16 cellPos = Voxel::worldToCell_floor(cameraPos);
return terrain->getSmoothGrid()->getCell(cellPos.x, cellPos.y, cellPos.z).getMaterial() == Voxel2::Cell::Material_Water;
}
static bool isCameraUnderwater(MegaClusterInstance* terrain, float waveFrequency, float wavePhase, float waveHeight)
{
Camera* cam = DataModel::get(terrain)->getWorkspace()->getCamera();
if (!cam)
return false;
Vector3 cameraPos = cam->getCameraCoordinateFrame().translation;
// counter-displace camera to match vertex deformation
cameraPos.y -= getWaterDisplacement(cameraPos, waveFrequency, wavePhase, waveHeight);
Vector3 cameraCellPos = Voxel::worldSpaceToCellSpace(cameraPos);
for (int x = -1; x <= 1; ++x)
for (int z = -1; z <= 1; ++z)
{
// look around +-0.5 voxels to compensate for water filling neighboring cells
int cellPosX = floorf(cameraCellPos.x + x * 0.5f);
int cellPosY = floorf(cameraCellPos.y);
int cellPosZ = floorf(cameraCellPos.z + z * 0.5f);
Voxel2::Cell cameraCell0 = terrain->getSmoothGrid()->getCell(cellPosX, cellPosY + 0, cellPosZ);
Voxel2::Cell cameraCell1 = terrain->getSmoothGrid()->getCell(cellPosX, cellPosY + 1, cellPosZ);
if (cameraCell0.getMaterial() == Voxel2::Cell::Material_Water)
{
// camera is in water cell and cell above is not air => we're either completely in water or at the water-solid boundary
// so we can safely assume underwater
if (cameraCell1.getMaterial() != Voxel2::Cell::Material_Air)
return true;
// cell above is air => have to estimate the plane based on occupancy
float occScale = 1.f / (Voxel2::Cell::Occupancy_Max + 1);
float waterHeightCell = (cameraCell0.getOccupancy() + 1) * occScale;
if (cameraCellPos.y < cellPosY + waterHeightCell)
return true;
}
}
return false;
}
Water::Water(VisualEngine* visualEngine)
: visualEngine(visualEngine)
, currentTimeScaled(0)
{
}
Water::~Water()
{
}
void Water::update(DataModel* dataModel, float dt)
{
MegaClusterInstance* terrain = static_cast<MegaClusterInstance*>(dataModel->getWorkspace()->getTerrain());
if (!terrain)
return;
if (terrain->isSmooth())
{
Color3 waterColor = terrain->getWaterColor();
float waterDensity = 1 - terrain->getWaterTransparency();
float waterDepth = G3D::lerp(10.f, 200.f, terrain->getWaterTransparency());
float waveLength = 85 * sqrtf(terrain->getWaterWaveSize());
float waveSpeed = terrain->getWaterWaveSpeed();
float waveHeight = terrain->getWaterWaveSize();
// update stored time so that we can change wave speed without changing state
currentTimeScaled += dt * waveSpeed;
// this is a pre-multiplied frequency (as in, 2pif)
float waveFrequency = 2 * Math::pif() / std::max(waveLength, 1e-3f);
float wavePhase = fmodf(currentTimeScaled * waveFrequency, 2 * Math::pif());
double frame = fmod(currentTimeScaled / 10.f, kAnimLength) / kAnimLength * kAnimFrames;
double ratio = frame - floor(frame);
unsigned int frame1 = static_cast<unsigned int>(frame) % ARRAYSIZE(normalMaps);
unsigned int frame2 = (frame1 + 1) % ARRAYSIZE(normalMaps);
bool disableWaterFog = visualEngine->getFrameRateManager()->GetQualityLevel() == 0;
if (isCameraUnderwater(terrain, waveFrequency, wavePhase, waveHeight) && !disableWaterFog)
{
// At depth=0 fog factor = -fogStart/(fogEnd - fogStart) = waterDensity
float fogStart = -waterDepth * waterDensity / (1 - waterDensity);
float fogEnd = waterDepth;
SceneManager* sceneManager = visualEngine->getSceneManager();
sceneManager->setSkyEnabled(false);
sceneManager->setClearColor(Color4(waterColor));
sceneManager->setFog(waterColor, fogStart, fogEnd);
}
if (smoothMaterial)
{
std::vector<Technique>& techniques = smoothMaterial->getTechniques();
for (size_t i = 0; i < techniques.size(); ++i)
{
Technique& t = techniques[i];
t.setTexture(0, getNormalMap(frame1), SamplerState::Filter_Linear);
t.setTexture(1, getNormalMap(frame2), SamplerState::Filter_Linear);
t.setConstant("WaveParams", Vector4(waveFrequency, wavePhase, waveHeight, ratio));
t.setConstant("WaterColor", Vector4(Color4(waterColor)));
t.setConstant("WaterParams", Vector4((1.f - waterDensity) / waterDepth, waterDensity, 0, 0));
}
}
}
else
{
static const Color3 kWaterColor = Color3(13, 85, 93) / 255;
if (isCameraUnderwaterLegacy(terrain))
{
SceneManager* sceneManager = visualEngine->getSceneManager();
sceneManager->setSkyEnabled(false);
sceneManager->setClearColor(Color4(kWaterColor));
sceneManager->setFog(kWaterColor, -50, 60);
}
double currentTime = Time::nowFastSec();
double frame = fmod(currentTime, kAnimLength) / kAnimLength * kAnimFrames;
unsigned int frame1 = static_cast<unsigned int>(frame) % ARRAYSIZE(normalMaps);
unsigned int frame2 = (frame1 + 1) % ARRAYSIZE(normalMaps);
// waves
float freq = 0.2f;
float speed = 1.1f;
float height = 0.15f;
float phase = fmodf(currentTime * speed * 9, 2*3.1415926f / freq);
double ratio = frame - floor(frame);
if (legacyMaterial)
{
std::vector<Technique>& techniques = legacyMaterial->getTechniques();
for (size_t i = 0; i < techniques.size(); ++i)
{
Technique& t = techniques[i];
t.setTexture(0, getNormalMap(frame1), SamplerState::Filter_Linear);
t.setTexture(1, getNormalMap(frame2), SamplerState::Filter_Linear);
t.setConstant("waveParams", Vector4(freq, phase, height, 0));
t.setConstant("nmAnimLerp", Vector4(ratio, 0, 0, 0));
t.setConstant("WaterColor", Vector4(Color4(kWaterColor)));
}
}
}
}
static void setupTechnique(Technique& technique, VisualEngine* visualEngine, const TextureRef& nm0, const TextureRef& nm1)
{
LightGrid* lightGrid = visualEngine->getLightGrid();
technique.setRasterizerState(RasterizerState(RasterizerState::Cull_None, 4));
technique.setTexture(0, nm0, SamplerState::Filter_Linear);
technique.setTexture(1, nm1, SamplerState::Filter_Linear);
technique.setTexture(2, visualEngine->getSceneManager()->getEnvMap()->getTexture(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp) );
if (lightGrid)
{
technique.setTexture(3, lightGrid->getTexture(), SamplerState::Filter_Linear);
technique.setTexture(4, lightGrid->getLookupTexture(), SamplerState::Filter_Point);
}
}
const shared_ptr<Material>& Water::getLegacyMaterial()
{
if (legacyMaterial)
return legacyMaterial;
legacyMaterial = shared_ptr<Material>(new Material());
const char* vsNameHQ = "WaterHQVS";
const char* psNameHQ = "WaterHQFS";
const char* vsNameLQ = "WaterVS";
const char* psNameLQ = "WaterFS";
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram(vsNameHQ, psNameHQ))
{
Technique technique(program, 1);
setupTechnique(technique, visualEngine, getNormalMap(0), getNormalMap(1));
legacyMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram(vsNameLQ, psNameLQ))
{
Technique technique(program, 2);
setupTechnique(technique, visualEngine, getNormalMap(0), getNormalMap(1));
legacyMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgramFFP())
{
Technique technique(program, 3);
TextureManager* tm = visualEngine->getTextureManager();
technique.setTexture(0, tm->load(ContentId("rbxasset://textures/water_Subsurface.dds"), TextureManager::Fallback_White), SamplerState::Filter_Linear);
legacyMaterial->addTechnique(technique);
}
return legacyMaterial;
}
const shared_ptr<Material>& Water::getSmoothMaterial()
{
if (smoothMaterial)
return smoothMaterial;
smoothMaterial = shared_ptr<Material>(new Material());
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram("SmoothWaterHQVS", "SmoothWaterHQGBufferFS"))
{
Technique technique(program, 0);
setupTechnique(technique, visualEngine, getNormalMap(0), getNormalMap(1));
technique.setTexture(5, visualEngine->getSceneManager()->getGBufferColor(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp));
technique.setTexture(6, visualEngine->getSceneManager()->getGBufferDepth(), SamplerState(SamplerState::Filter_Point, SamplerState::Address_Clamp));
smoothMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram("SmoothWaterHQVS", "SmoothWaterHQFS"))
{
Technique technique(program, 1);
setupTechnique(technique, visualEngine, getNormalMap(0), getNormalMap(1));
smoothMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram("SmoothWaterVS", "SmoothWaterFS"))
{
Technique technique(program, 2);
setupTechnique(technique, visualEngine, getNormalMap(0), getNormalMap(1));
smoothMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgramFFP())
{
Technique technique(program, 3);
TextureManager* tm = visualEngine->getTextureManager();
technique.setTexture(0, tm->load(ContentId("rbxasset://textures/water_Subsurface.dds"), TextureManager::Fallback_White), SamplerState::Filter_Linear);
smoothMaterial->addTechnique(technique);
}
return smoothMaterial;
}
const TextureRef& Water::getNormalMap(unsigned int frame)
{
RBXASSERT(frame < ARRAYSIZE(normalMaps));
if (!normalMaps[frame].getTexture())
{
char id[256];
sprintf( id, "rbxasset://textures/water/normal_%02d.%s", frame+1, kTextureExt );
normalMaps[frame] = visualEngine->getTextureManager()->load(ContentId(id), TextureManager::Fallback_NormalMap);
}
return normalMaps[frame];
}
}
}