1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2024-12-28 18:18:52 +00:00

Merge branch 'master' into 'master'

Overhaul raindrop water ripple effect

Closes #6360

See merge request OpenMW/openmw!1316
This commit is contained in:
psi29a 2021-11-13 21:44:25 +00:00
commit 68e7a4083e
7 changed files with 164 additions and 41 deletions

View File

@ -232,6 +232,7 @@ namespace MWGui
getWidget(mControllerSwitch, "ControllerButton");
getWidget(mWaterTextureSize, "WaterTextureSize");
getWidget(mWaterReflectionDetail, "WaterReflectionDetail");
getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail");
getWidget(mLightingMethodButton, "LightingMethodButton");
getWidget(mLightsResetButton, "LightsResetButton");
getWidget(mMaxLights, "MaxLights");
@ -259,6 +260,7 @@ namespace MWGui
mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged);
mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged);
mWaterRainRippleDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterRainRippleDetailChanged);
mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged);
mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked);
@ -307,10 +309,12 @@ namespace MWGui
if (waterTextureSize >= 2048)
mWaterTextureSize->setIndexSelected(2);
int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
waterReflectionDetail = std::clamp(waterReflectionDetail, 0, 5);
int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5);
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2);
mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail);
updateMaxLightsComboBox(mMaxLights);
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
@ -394,11 +398,18 @@ namespace MWGui
void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
{
unsigned int level = std::min((unsigned int)5, (unsigned int)pos);
unsigned int level = static_cast<unsigned int>(std::min<size_t>(pos, 5));
Settings::Manager::setInt("reflection detail", "Water", level);
apply();
}
void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
{
unsigned int level = static_cast<unsigned int>(std::min<size_t>(pos, 2));
Settings::Manager::setInt("rain ripple detail", "Water", level);
apply();
}
void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos)
{
if (pos == MyGUI::ITEM_NONE)

View File

@ -31,6 +31,7 @@ namespace MWGui
MyGUI::ComboBox* mWaterTextureSize;
MyGUI::ComboBox* mWaterReflectionDetail;
MyGUI::ComboBox* mWaterRainRippleDetail;
MyGUI::ComboBox* mMaxLights;
MyGUI::ComboBox* mLightingMethodButton;
@ -55,6 +56,7 @@ namespace MWGui
void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos);
void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos);
void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightsResetButtonClicked(MyGUI::Widget* _sender);

View File

@ -32,6 +32,7 @@
#include <components/sceneutil/lightmanager.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/stringops.hpp>
#include <components/nifosg/controller.hpp>
@ -676,7 +677,10 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
{
// use a define map to conditionally compile the shader
std::map<std::string, std::string> defineMap;
defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(mRefraction ? "1" : "0")));
defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0");
const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2);
defineMap["rain_ripple_detail"] = std::to_string(rippleDetail);
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
osg::ref_ptr<osg::Shader> vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX));

View File

@ -77,6 +77,21 @@ Controls what kinds of things are rendered in water reflections.
In interiors the lowest level is 2.
This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu.
rain ripple detail
-----------------
:Type: integer
:Range: 0, 1, 2
:Default: 2
Controls how detailed the raindrop ripples on water are.
0: single, non-normal-mapped ring per raindrop
1: normal-mapped raindrops, with multiple rings
2: same as 1, but with a greater number of raindrops
This setting can be changed ingame with the "Rain ripple detail/density" dropdown under the Water tab of the Video panel in the Options menu.
small feature culling pixel size
--------------------------------

View File

@ -454,6 +454,16 @@
<Property key="Caption" value="Reflection shader detail"/>
</Widget>
</Widget>
<Widget type="HBox" skin="" position="4 84 350 24">
<Widget type="ComboBox" skin="MW_ComboBox" position="0 0 115 24" align="Left Top" name="WaterRainRippleDetail">
<Property key="AddItem" value="Simple"/>
<Property key="AddItem" value="Sparse"/>
<Property key="AddItem" value="Dense"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="64 4 90 16" align="Left Top">
<Property key="Caption" value="Rain ripple detail/density"/>
</Widget>
</Widget>
</Widget>
</Widget>

View File

@ -652,6 +652,10 @@ refraction = false
# Draw objects on water reflections.
reflection detail = 2
# Whether to use fully detailed raindrop ripples. (0, 1, 2).
# 0 = rings only; 1 = sparse, high detail; 2 = dense, high detail
rain ripple detail = 2
# Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures.
small feature culling pixel size = 20.0

View File

@ -9,6 +9,7 @@
#endif
#define REFRACTION @refraction_enabled
#define RAIN_RIPPLE_DETAIL @rain_ripple_detail
// Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html )
@ -55,14 +56,22 @@ const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to
// ---------------- rain ripples related stuff ---------------------
const float RAIN_RIPPLE_GAPS = 5.0;
const float RAIN_RIPPLE_RADIUS = 0.1;
const float RAIN_RIPPLE_GAPS = 10.0;
const float RAIN_RIPPLE_RADIUS = 0.2;
vec2 randOffset(vec2 c)
float scramble(float x, float z)
{
return fract(vec2(
c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2,
c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7));
return fract(pow(fract(x)*3.0+1.0, z));
}
vec2 randOffset(vec2 c, float time)
{
time = fract(time/1000.0);
c = vec2(c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2,
c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7);
c.x *= scramble(scramble(time + c.x/1000.0, 4.0), 3.0) + 1.0;
c.y *= scramble(scramble(time + c.y/1000.0, 3.5), 3.0) + 1.0;
return fract(c);
}
float randPhase(vec2 c)
@ -70,43 +79,104 @@ float randPhase(vec2 c)
return fract((c.x * c.y) / (c.x + c.y + 0.1));
}
vec4 circle(vec2 coords, vec2 i_part, float phase)
float blip(float x)
{
vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(i_part) - 1.0);
vec2 toCenter = coords - center;
float d = length(toCenter);
float r = RAIN_RIPPLE_RADIUS * phase;
if (d > r)
return vec4(0.0,0.0,1.0,0.0);
float sinValue = (sin(d / r * 1.2) + 0.7) / 2.0;
float height = (1.0 - abs(phase)) * pow(sinValue,3.0);
vec3 normal = normalize(mix(vec3(0.0,0.0,1.0),vec3(normalize(toCenter),0.0),height));
return vec4(normal,height);
x = max(0.0, 1.0-x*x);
return x*x*x;
}
float blipDerivative(float x)
{
x = clamp(x, -1.0, 1.0);
float n = x*x-1.0;
return -6.0*x*n*n;
}
const float RAIN_RING_TIME_OFFSET = 1.0/6.0;
vec4 circle(vec2 coords, vec2 corner, float adjusted_time)
{
vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0);
float phase = fract(adjusted_time);
vec2 toCenter = coords - center;
float r = RAIN_RIPPLE_RADIUS;
float d = length(toCenter);
float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring
#if RAIN_RIPPLE_DETAIL > 0
// normal mapped ripples
if(ringfollower < -1.0 || ringfollower > 1.0)
return vec4(0.0);
if(d > 1.0) // normalize center direction vector, but not for near-center ripples
toCenter /= d;
float height = blip(ringfollower*2.0+0.5); // brighten up outer edge of ring; for fake specularity
float range_limit = blip(min(0.0, ringfollower));
float energy = 1.0-phase;
vec2 normal2d = -toCenter*blipDerivative(ringfollower)*5.0;
vec3 normal = vec3(normal2d, 0.5);
vec4 ret = vec4(normal, height);
ret.xyw *= energy*energy;
// do energy adjustment here rather than later, so that we can use the w component for fake specularity
ret.xyz = normalize(ret.xyz) * energy*range_limit;
ret.z *= range_limit;
return ret;
#else
// ring-only ripples
if(ringfollower < -1.0 || ringfollower > 0.5)
return vec4(0.0);
float energy = 1.0-phase;
float height = blip(ringfollower*2.0+0.5)*energy*energy; // fake specularity
return vec4(0.0, 0.0, 0.0, height);
#endif
}
vec4 rain(vec2 uv, float time)
{
vec2 i_part = floor(uv * RAIN_RIPPLE_GAPS);
vec2 f_part = fract(uv * RAIN_RIPPLE_GAPS);
return circle(f_part,i_part,fract(time * 1.2 + randPhase(i_part)));
uv *= RAIN_RIPPLE_GAPS;
vec2 f_part = fract(uv);
vec2 i_part = floor(uv);
float adjusted_time = time * 1.2 + randPhase(i_part);
#if RAIN_RIPPLE_DETAIL > 0
vec4 a = circle(f_part, i_part, adjusted_time);
vec4 b = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET);
vec4 c = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*2.0);
vec4 d = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*3.0);
vec4 ret;
ret.xy = a.xy - b.xy/2.0 + c.xy/4.0 - d.xy/8.0;
// z should always point up
ret.z = a.z + b.z /2.0 + c.z /4.0 + d.z /8.0;
//ret.xyz *= 1.5;
// fake specularity looks weird if we use every single ring, also if the inner rings are too bright
ret.w = (a.w + c.w /8.0)*1.5;
return ret;
#else
return circle(f_part, i_part, adjusted_time) * 1.5;
#endif
}
vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and ripple height in w
vec2 complex_mult(vec2 a, vec2 b)
{
return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
}
vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake specularity in w
{
return
rain(uv,time) +
rain(uv + vec2(10.5,5.7),time) +
rain(uv * 0.75 + vec2(3.7,18.9),time) +
rain(uv * 0.9 + vec2(5.7,30.1),time) +
rain(uv * 0.8 + vec2(1.2,3.0),time);
rain(uv, time)
+ rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time)
#if RAIN_RIPPLE_DETAIL == 2
+ rain(uv * 0.75 + vec2( 3.7,18.9),time)
+ rain(uv * 0.9 + vec2( 5.7,30.1),time)
+ rain(uv * 1.0 + vec2(10.5 ,5.7),time)
#endif
;
}
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta)
@ -193,11 +263,11 @@ void main(void)
vec4 rainRipple;
if (rainIntensity > 0.01)
rainRipple = rainCombined(position.xy / 1000.0,waterTimer) * clamp(rainIntensity,0.0,1.0);
rainRipple = rainCombined(position.xy/1000.0, waterTimer) * clamp(rainIntensity, 0.0, 1.0);
else
rainRipple = vec4(0.0);
vec3 rippleAdd = rainRipple.xyz * rainRipple.w * 10.0;
vec3 rippleAdd = rainRipple.xyz * 10.0;
vec2 bigWaves = vec2(BIG_WAVES_X,BIG_WAVES_Y);
vec2 midWaves = mix(vec2(MID_WAVES_X,MID_WAVES_Y),vec2(MID_WAVES_RAIN_X,MID_WAVES_RAIN_Y),rainIntensity);
@ -245,7 +315,14 @@ void main(void)
vec4 sunSpec = lcalcSpecular(0);
// artificial specularity to make rain ripples more noticeable
vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade)));
vec3 rainSpecular = abs(rainRipple.w)*mix(skyColorEstimate, vec3(1.0), 0.05)*0.5;
#if REFRACTION
// no alpha here, so make sure raindrop ripple specularity gets properly subdued
rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0);
// refraction
vec3 refraction = texture2D(refractionMap, screenCoords - screenCoordsOffset).rgb;
vec3 rawRefraction = refraction;
@ -265,7 +342,7 @@ void main(void)
vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0));
vec3 lR = reflect(lVec, lNormal);
float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0);
gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.2;
gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + rainSpecular;
gl_FragData[0].w = 1.0;
// wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping
@ -278,7 +355,7 @@ void main(void)
shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0);
gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset);
#else
gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.7;
gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + rainSpecular;
gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0);
#endif
@ -288,7 +365,7 @@ void main(void)
#else
float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#endif
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
applyShadowDebugOverlay();
}