Underwater effect

This commit is contained in:
2026-04-23 01:55:09 +03:00
parent 9d4fad1d10
commit e95b904f4e
7 changed files with 210 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
compositor Underwater
{
technique
{
texture scene target_width target_height PF_R8G8B8A8
target scene
{
input previous
}
target_output
{
input none
pass render_quad
{
material Underwater/Post
input 0 scene
}
}
}
}

View File

@@ -0,0 +1,74 @@
OGRE_NATIVE_GLSL_VERSION_DIRECTIVE
#include <OgreUnifiedShader.h>
SAMPLER2D(sceneTex, 0);
OGRE_UNIFORMS(
uniform float time;
)
IN(vec2 vUV, TEXCOORD0)
float hash(vec2 p)
{
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
MAIN_DECLARATION
{
// Underwater wavy distortion
float waveX = sin(vUV.y * 40.0 + time * 2.5) * 0.003
+ sin(vUV.y * 20.0 - time * 1.2) * 0.002;
float waveY = cos(vUV.x * 35.0 + time * 2.0) * 0.003
+ cos(vUV.x * 15.0 - time * 1.5) * 0.002;
vec2 distortedUV = vUV + vec2(waveX, waveY);
vec4 color = texture2D(sceneTex, distortedUV);
// Blue-green underwater tint
color.rgb = mix(color.rgb, vec3(0.0, 0.35, 0.5), 0.2);
color.rgb *= vec3(0.9, 1.0, 1.1);
// Bubbles - procedural circles that rise over time
vec2 bubbleUV = vUV * 25.0;
bubbleUV.y += time * 0.3;
vec2 bubbleId = floor(bubbleUV);
vec2 bubbleFract = fract(bubbleUV);
float bubbleNoise = hash(bubbleId);
float bubble = 0.0;
if (bubbleNoise > 0.65) {
vec2 bubbleCenter = vec2(
hash(bubbleId + 1.0),
hash(bubbleId + 2.0));
float d = length(bubbleFract - bubbleCenter);
bubble = smoothstep(0.12, 0.04, d);
// Fade bubbles at top/bottom edges
bubble *= smoothstep(0.0, 0.1, bubbleFract.y)
* smoothstep(1.0, 0.9, bubbleFract.y);
}
color.rgb += vec3(0.7, 0.8, 0.9) * bubble * 0.4;
// Occasional spark flashes
float sparkInterval = 2.5;
float sparkTime = floor(time / sparkInterval);
vec2 sparkUV = vUV * 12.0;
vec2 sparkId = floor(sparkUV);
float sparkNoise = hash(sparkId + sparkTime);
float spark = 0.0;
if (sparkNoise > 0.96) {
vec2 sparkCenter = vec2(
hash(sparkNoise * 17.3),
hash(sparkNoise * 53.1));
float d = length(fract(sparkUV) - sparkCenter);
float flash = sin(time * 15.0) * 0.5 + 0.5;
spark = smoothstep(0.06, 0.01, d) * flash;
}
color.rgb += vec3(1.0, 0.95, 0.7) * spark * 0.7;
// Simple vignette for depth feeling
float vignette = 1.0 - length(vUV - 0.5) * 0.6;
vignette = clamp(vignette, 0.3, 1.0);
color.rgb *= vignette;
gl_FragColor = color;
}

View File

@@ -0,0 +1,26 @@
material Underwater/Post
{
technique
{
pass
{
lighting off
depth_check off
depth_write off
cull_hardware none
vertex_program_ref UnderwaterVP
{
}
fragment_program_ref UnderwaterFP
{
}
texture_unit
{
// filled by compositor input
}
}
}
}

View File

@@ -0,0 +1,13 @@
vertex_program UnderwaterVP glsl glsles glslang hlsl
{
source underwater.vert
}
fragment_program UnderwaterFP glsl glsles glslang hlsl
{
source underwater.frag
default_params
{
param_named time float 0.0
}
}

View File

@@ -0,0 +1,13 @@
OGRE_NATIVE_GLSL_VERSION_DIRECTIVE
#include <OgreUnifiedShader.h>
OUT(vec2 vUV, TEXCOORD0)
MAIN_PARAMETERS
IN(vec4 vertex, POSITION)
IN(vec2 uv0, TEXCOORD0)
MAIN_DECLARATION
{
gl_Position = vertex;
vUV = uv0;
}

View File

@@ -8,6 +8,7 @@
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreGpuProgramParams.h>
#include <OgreCompositorManager.h>
static const uint32_t WATER_MASK = 0xF00;
@@ -89,6 +90,8 @@ void EditorWaterPlaneSystem::update(float deltaTime, Ogre::Camera *camera)
wp.sceneNode->setVisible(true);
});
updateUnderwaterEffect(camera, deltaTime);
}
void EditorWaterPlaneSystem::rebuildWaterPlane(
@@ -405,3 +408,58 @@ void EditorWaterPlaneSystem::updateShaderParams(WaterPlane &wp,
if (vpParams)
vpParams->setNamedConstant("tiling", wp.tiling);
}
void EditorWaterPlaneSystem::updateUnderwaterEffect(Ogre::Camera *camera,
float deltaTime)
{
if (!camera)
return;
Ogre::Viewport *viewport = camera->getViewport();
if (!viewport)
return;
Ogre::SceneNode *camNode = camera->getParentSceneNode();
float camY = camNode ? camNode->_getDerivedPosition().y : 0.0f;
// Check if camera is below any enabled water plane
bool underwater = false;
m_query.each([&](flecs::entity, WaterPlane &wp,
TransformComponent &) {
if (wp.enabled && camY < wp.waterSurfaceY)
underwater = true;
});
// Add compositor to viewport once (or re-add if viewport changed)
if (!m_compositorAdded || m_compositorViewport != viewport) {
if (m_compositorAdded && m_compositorViewport)
Ogre::CompositorManager::getSingleton().removeCompositor(
m_compositorViewport, "Underwater");
Ogre::CompositorManager::getSingleton().addCompositor(
viewport, "Underwater");
m_compositorAdded = true;
m_compositorViewport = viewport;
}
// Enable/disable compositor based on underwater state
Ogre::CompositorManager::getSingleton().setCompositorEnabled(
viewport, "Underwater", underwater);
// Update shader time when underwater
if (underwater) {
m_underwaterTime += deltaTime;
Ogre::MaterialPtr mat =
Ogre::MaterialManager::getSingleton().getByName(
"Underwater/Post",
Ogre::ResourceGroupManager::
DEFAULT_RESOURCE_GROUP_NAME);
if (mat) {
Ogre::Pass *pass = mat->getTechnique(0)->getPass(0);
Ogre::GpuProgramParametersSharedPtr fpParams =
pass->getFragmentProgramParameters();
if (fpParams)
fpParams->setNamedConstant(
"time", m_underwaterTime);
}
}
}

View File

@@ -26,6 +26,7 @@ private:
void updatePosition(WaterPlane &wp, float deltaTime,
Ogre::Camera *camera);
void updateShaderParams(WaterPlane &wp, float deltaTime);
void updateUnderwaterEffect(Ogre::Camera *camera, float deltaTime);
Ogre::SceneNode *getReflectionNode(WaterPlane &wp);
Ogre::SceneNode *getRefractionNode(WaterPlane &wp);
@@ -44,6 +45,9 @@ private:
Ogre::SceneManager *m_sceneMgr;
flecs::query<struct WaterPlane, struct TransformComponent> m_query;
WaterRenderTargetListener m_listener;
float m_underwaterTime = 0.0f;
bool m_compositorAdded = false;
Ogre::Viewport *m_compositorViewport = nullptr;
};
#endif // EDITSCENE_EDITOR_WATERPLANE_SYSTEM_HPP