diff --git a/src/features/editScene/resources/materials/scripts/underwater.compositor b/src/features/editScene/resources/materials/scripts/underwater.compositor new file mode 100644 index 0000000..0376b4e --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/underwater.compositor @@ -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 + } + } + } +} diff --git a/src/features/editScene/resources/materials/scripts/underwater.frag b/src/features/editScene/resources/materials/scripts/underwater.frag new file mode 100644 index 0000000..98765d9 --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/underwater.frag @@ -0,0 +1,74 @@ +OGRE_NATIVE_GLSL_VERSION_DIRECTIVE +#include + +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; +} diff --git a/src/features/editScene/resources/materials/scripts/underwater.material b/src/features/editScene/resources/materials/scripts/underwater.material new file mode 100644 index 0000000..b9d1089 --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/underwater.material @@ -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 + } + } + } +} diff --git a/src/features/editScene/resources/materials/scripts/underwater.program b/src/features/editScene/resources/materials/scripts/underwater.program new file mode 100644 index 0000000..81f94de --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/underwater.program @@ -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 + } +} diff --git a/src/features/editScene/resources/materials/scripts/underwater.vert b/src/features/editScene/resources/materials/scripts/underwater.vert new file mode 100644 index 0000000..a9c2c2d --- /dev/null +++ b/src/features/editScene/resources/materials/scripts/underwater.vert @@ -0,0 +1,13 @@ +OGRE_NATIVE_GLSL_VERSION_DIRECTIVE +#include + +OUT(vec2 vUV, TEXCOORD0) + +MAIN_PARAMETERS +IN(vec4 vertex, POSITION) +IN(vec2 uv0, TEXCOORD0) +MAIN_DECLARATION +{ + gl_Position = vertex; + vUV = uv0; +} diff --git a/src/features/editScene/systems/EditorWaterPlaneSystem.cpp b/src/features/editScene/systems/EditorWaterPlaneSystem.cpp index 90a84b7..4857bc5 100644 --- a/src/features/editScene/systems/EditorWaterPlaneSystem.cpp +++ b/src/features/editScene/systems/EditorWaterPlaneSystem.cpp @@ -8,6 +8,7 @@ #include #include #include +#include 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); + } + } +} diff --git a/src/features/editScene/systems/EditorWaterPlaneSystem.hpp b/src/features/editScene/systems/EditorWaterPlaneSystem.hpp index bf1f35e..037c3db 100644 --- a/src/features/editScene/systems/EditorWaterPlaneSystem.hpp +++ b/src/features/editScene/systems/EditorWaterPlaneSystem.hpp @@ -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 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