1
0
forked from 0ad/0ad

Water GLSL shader improvements around reflections and whitespace fixes.

This improves refractions around entities close to the surface, such as
fishes, by handling depth better and by clipping the water plane a
little lower.

This uses the skybox for reflections when refractions are enabled but
reflections are disabled, making it possible to play with reflections
disabled without having super-ugly water (arguably a performance
improvement).

Differential Revision: https://code.wildfiregames.com/D359
This was SVN commit r22297.
This commit is contained in:
wraitii 2019-05-25 11:08:57 +00:00
parent b637fdbae9
commit 01a8138780
4 changed files with 154 additions and 130 deletions

View File

@ -79,8 +79,8 @@ uniform sampler2D reflectionMap;
#if USE_SHADOW_PCF
vec2 offset = fract(coords.xy - 0.5);
vec4 size = vec4(offset + 1.0, 2.0 - offset);
vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (coords.xy - 0.5*offset).xyxy) * shadowScale.zwzw;
return (1.0/9.0)*dot(size.zxzx*size.wwyy,
vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (coords.xy - 0.5 * offset).xyxy) * shadowScale.zwzw;
return (1.0 / 9.0) * dot(size.zxzx * size.wwyy,
vec4(shadow2D(shadowTex, vec3(weight.zw, coords.z)).r,
shadow2D(shadowTex, vec3(weight.xw, coords.z)).r,
shadow2D(shadowTex, vec3(weight.zy, coords.z)).r,
@ -117,24 +117,24 @@ vec3 get_fog(vec3 color)
{
float density = fogParams.x;
float maxFog = fogParams.y;
const float LOG2 = 1.442695;
float z = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = exp2(-density * density * z * z * LOG2);
fogFactor = fogFactor * (1.0 - maxFog) + maxFog;
fogFactor = clamp(fogFactor, 0.0, 1.0);
return mix(fogColor, color, fogFactor);
}
void main()
{
{
float fresnel;
vec2 reflCoords, refrCoords;
vec3 reflColor, refrColor, specular;
float losMod, reflMod;
float losMod;
// Calculate water normals.
@ -146,206 +146,229 @@ void main()
// This method uses 60 animated water frames. We're blending between each two frames
// Scale the normal textures by waviness so that big waviness means bigger waves.
vec3 ww1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).xzy;
vec3 ww2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).xzy;
vec3 wwInterp = mix(ww1, ww2, moddedTime) - vec3(0.5,0.0,0.5);
vec3 ww1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement * waviness / 10.0) * (baseScale - waviness / wavyEffect)).xzy;
vec3 ww2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement * waviness / 10.0) * (baseScale - waviness / wavyEffect)).xzy;
vec3 wwInterp = mix(ww1, ww2, moddedTime) - vec3(0.5, 0.0, 0.5);
ww1.x = wwInterp.x * WindCosSin.x - wwInterp.z * WindCosSin.y;
ww1.z = wwInterp.x * WindCosSin.y + wwInterp.z * WindCosSin.x;
ww1.y = wwInterp.y;
// Flatten them based on waviness.
vec3 n = normalize(mix(vec3(0.0,1.0,0.0), ww1, clamp(baseBump + fwaviness/flattenism,0.0,1.0)));
vec3 n = normalize(mix(vec3(0.0, 1.0, 0.0), ww1, clamp(baseBump + fwaviness / flattenism, 0.0, 1.0)));
#if USE_FANCY_EFFECTS
vec4 fancyeffects = texture2D(waterEffectsTexNorm, gl_FragCoord.xy/screenSize);
n = mix(vec3(0.0,1.0,0.0), n,0.5 + waterInfo.r/2.0);
n.xz = mix(n.xz, fancyeffects.rb,fancyeffects.a/2.0);
vec4 fancyeffects = texture2D(waterEffectsTexNorm, gl_FragCoord.xy / screenSize);
n = mix(vec3(0.0, 1.0, 0.0), n, 0.5 + waterInfo.r / 2.0);
n.xz = mix(n.xz, fancyeffects.rb, fancyeffects.a / 2.0);
#else
n = mix(vec3(0.0,1.0,0.0), n, 0.5 + waterInfo.r/2.0);
n = mix(vec3(0.0, 1.0, 0.0), n, 0.5 + waterInfo.r / 2.0);
#endif
n = vec3(-n.x,n.y,-n.z); // The final wave normal vector.
n = vec3(-n.x, n.y, -n.z); // The final wave normal vector.
// How perpendicular to the normal our view is. Used for fresnel.
float ndotv = clamp(dot(n, v),0.0,1.0);
float ndotv = clamp(dot(n, v), 0.0, 1.0);
// Fresnel for "how much reflection vs how much refraction".
fresnel = clamp(((pow(1.1 - ndotv, 2.0)) * 1.5), 0.1, 0.75); // Approximation. I'm using 1.1 and not 1.0 because it causes artifacts, see #1714
// Specular lighting vectors
vec3 specVector = reflect(sunDir, ww1);
// pow is undefined for null or negative values, except on intel it seems.
// pow is undefined for null or negative values, except on intel it seems.
float specIntensity = clamp(pow(abs(dot(specVector, v)), 100.0), 0.0, 1.0);
specular = specIntensity*1.2 * mix(vec3(1.5), sunColor,0.5);
specular = specIntensity * 1.2 * mix(vec3(1.5), sunColor, 0.5);
#if USE_SHADOWS_ON_WATER && USE_SHADOW
float shadow = get_shadow(vec4(v_shadow.xy, v_shadow.zw));
#endif
// for refraction, we want to adjust the value by v.y slightly otherwise it gets too different between "from above" and "from the sides".
// And it looks weird (again, we are not used to seeing water from above).
float fixedVy = max(v.y, 0.01);
float murky = mix(200.0, 0.1, pow(murkiness, 0.25));
float depth;
#if USE_REAL_DEPTH
// Don't change these two. They should match the values in the config (TODO: dec uniforms).
float zNear = 2.0;
float zFar = 4096.0;
// Okay so here it's a tad complicated. I want to distort the depth buffer along the waves for a nice effect.
// However this causes a problem around underwater objects (think fishes): on some pixels, the depth will be seen as the same as the fishes'
// and the color will be grass ( cause I don't distort the refraction coord by exactly the same stuff)
// Also, things like towers with the feet in water would cause the buffer to see the depth as actually negative in some places.
// So what I do is first check the undistorted depth, then I compare with the distorted value and fix.
// Compute real depth at the target point.
float water_b = gl_FragCoord.z;
float water_n = 2.0 * water_b - 1.0;
float waterDBuffer = 2.0 * zNear * zFar / (zFar + zNear - water_n * (zFar - zNear));
float undisto_z_b = texture2D(depthTex, (gl_FragCoord.xy) / screenSize).x;
float undisto_z_n = 2.0 * undisto_z_b - 1.0;
float waterDepth_undistorted = (2.0 * zNear * zFar / (zFar + zNear - undisto_z_n * (zFar - zNear)) - waterDBuffer);
vec2 depthCoord = clamp((gl_FragCoord.xy) / screenSize - n.xz*clamp( waterDepth_undistorted/400.0,0.0,0.05) , 0.001, 0.999);
float z_b = texture2D(depthTex, depthCoord).x;
if (z_b < undisto_z_b)
z_b = undisto_z_b;
float z_n = 2.0 * z_b - 1.0;
depth = (2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)) - waterDBuffer);
// Set depth to the depth at the undistorted point.
depth = waterDepth_undistorted;
#else
depth = waterDepth / (min(0.5,v.y)*1.5*min(0.5,v.y)*2.0);
// fake depth computation: take the value at the vertex, add some if we are looking at a more oblique angle.
depth = waterDepth / (min(0.5, v.y) * 1.5 * min(0.5, v.y) * 2.0);
#endif
#if USE_FANCY_EFFECTS
depth = max(depth,fancyeffects.a);
#endif
#if USE_SHADOWS_ON_WATER && USE_SHADOW
float shadow = get_shadow(vec4(v_shadow.xy, v_shadow.zw));
#endif
// for refraction, we want to adjust the value by v.y slightly otherwise it gets too different between "from above" and "from the sides".
// And it looks weird (again, we are not used to seeing water from above).
float fixedVy = max(v.y,0.01);
float murky = mix(200.0,0.1,pow(murkiness,0.25));
#if USE_REFRACTION
// for refraction we want to distort more as depth goes down.
// 1) compute a distortion based on depth at the pixel.
// 2) Re-sample the depth at the target point
// 3) Sample refraction texture
// distoFactor controls the amount of distortion relative to wave normals.
float distoFactor = 0.5 + clamp(depth/2.0,0.0,7.0);
float distoFactor = 0.5 + clamp(depth / 2.0, 0.0, 7.0);
#if USE_REAL_DEPTH
vec2 depthCoord = clamp((gl_FragCoord.xy) / screenSize - n.xz * distoFactor / refractionCoords.z, 0.001, 0.999);
float z_b = texture2D(depthTex, depthCoord).x;
float z_n = 2.0 * z_b - 1.0;
float newDepth = (2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)) - waterDBuffer);
// try to correct for fish. In general they'd look weirder without this fix.
if (depth > newDepth + 3.0)
distoFactor /= 2.0; // this in general will not fall on the fish but still look distorted.
else
depth = newDepth;
#endif
#if USE_FANCY_EFFECTS
depth = max(depth, fancyeffects.a);
#endif
// Distort the texture coords under where the water is to simulate refraction.
refrCoords = (0.5 * refractionCoords.xy - n.xz * distoFactor) / refractionCoords.z + 0.5;
vec3 refColor = texture2D(refractionMap, refrCoords).rgb;
// Note, the refraction map is cleared using (255, 0, 0), so pixels outside of the water plane are pure red.
// If we get a pure red fragment, use an undistorted/less distorted coord instead.
if (refColor.r > 0.999 && refColor.g < 0.001)
{
refrCoords = (0.5*refractionCoords.xy) / refractionCoords.z + 0.5;
refColor = texture2D(refractionMap, refrCoords).rgb;
// blur the refraction map, distoring using n so that it looks more random than it really is
// and thus looks much better.
float blur = (0.3 + clamp(n.x, -0.1, 0.1)) / refractionCoords.z;
if (refColor.r > 0.999 && refColor.g < 0.001)
fresnel = 1.0;
}
else
{
// blur the refraction map, distoring using n so that it looks more random than it really is
// and thus looks much better.
float blur = (0.3+clamp(n.x,-0.1,0.1))/refractionCoords.z;
vec4 blurColor = vec4(refColor, 1.0);
vec4 tex = texture2D(refractionMap, refrCoords + vec2(blur + n.x, blur + n.z));
blurColor += vec4(tex.rgb * tex.a, tex.a);
tex = texture2D(refractionMap, refrCoords + vec2(-blur, blur + n.z));
blurColor += vec4(tex.rgb * tex.a, tex.a);
tex = texture2D(refractionMap, refrCoords + vec2(-blur, -blur + n.x));
blurColor += vec4(tex.rgb * tex.a, tex.a);
tex = texture2D(refractionMap, refrCoords + vec2(blur + n.z, -blur));
blurColor += vec4(tex.rgb * tex.a, tex.a);
blurColor /= blurColor.a;
float blurFactor = (distoFactor / 7.0);
refColor = (refColor + blurColor.rgb * blurFactor) / (1.0 + blurFactor);
vec3 blurColor = texture2D(refractionMap, refrCoords + vec2(blur+n.x, blur+n.z)).rgb;
blurColor += texture2D(refractionMap, refrCoords + vec2(-blur, blur+n.z)).rgb;
blurColor += texture2D(refractionMap, refrCoords + vec2(-blur, -blur+n.x)).rgb;
blurColor += texture2D(refractionMap, refrCoords + vec2(blur+n.z, -blur)).rgb;
blurColor /= 4.0;
float blurFactor = (distoFactor/7.0);
refColor = (refColor + blurColor * blurFactor) / (1.0+blurFactor);
}
// Apply water tint and murk color.
float extFact = max(0.0,1.0 - (depth*fixedVy/murky));
float ColextFact = max(0.0,1.0 - (depth*fixedVy/murky));
vec3 colll = mix(refColor*tint,refColor,ColextFact);
float extFact = max(0.0, 1.0 - (depth * fixedVy / murky));
float ColextFact = max(0.0, 1.0 - (depth * fixedVy / murky));
vec3 colll = mix(refColor * tint, refColor, ColextFact);
refrColor = mix(color, colll, extFact);
#else
#if USE_FANCY_EFFECTS
depth = max(depth, fancyeffects.a);
#endif
// Apply water tint and murk color only.
float extFact = max(0.0,1.0 - (depth*fixedVy/murky));
float ColextFact = max(0.0,1.0 - (depth*fixedVy/murky));
vec3 colll = mix(color*tint,color,ColextFact);
float extFact = max(0.0, 1.0 - (depth * fixedVy / murky));
float ColextFact = max(0.0, 1.0 - (depth * fixedVy / murky));
vec3 colll = mix(color * tint, color, ColextFact);
refrColor = mix(color, colll, extFact);
#endif
#if USE_REFLECTION
// Reflections
// We use real reflections against the skybox, and distort a texture of objects closer.
vec3 eye = reflect(v,n);
float refVY = clamp(v.y*2.0,0.05,1.0);
// Reflections
// 3 level of settings:
// -If a player has refraction and reflection disabled, we return a gradient of blue based on the Y component.
// -If a player has refraction OR reflection, we return a reflection of the actual skybox used.
// -If a player has reflection enabled, we also return a reflection of actual entities where applicable.
float reflMod = 0.75;
vec3 eye = reflect(v, n);
#if USE_REFLECTION || USE_REFRACTION
#if USE_REFLECTION
float refVY = clamp(v.y * 2.0, 0.05, 1.0);
// Distort the reflection coords based on waves.
reflCoords = (0.5*reflectionCoords.xy - 15.0 * n.zx / refVY) / reflectionCoords.z + 0.5;
reflCoords = (0.5 * reflectionCoords.xy - 15.0 * n.zx / refVY) / reflectionCoords.z + 0.5;
vec4 refTex = texture2D(reflectionMap, reflCoords);
reflColor = refTex.rgb;
if (refTex.a < 0.99)
{
#endif
// Calculate where we intersect with the skycube.
Ray myRay = Ray(vec3(worldPos.x / 4.0, worldPos.y, worldPos.z / 4.0), eye);
vec3 start = vec3(-1500.0 + mapSize / 2.0, -100.0, -1500.0 + mapSize / 2.0);
vec3 end = vec3(1500.0 + mapSize / 2.0, 500.0, 1500.0 + mapSize / 2.0);
float tmin = IntersectBox(myRay, start, end);
vec4 newpos = vec4(-worldPos.x / 4.0, worldPos.y, -worldPos.z / 4.0, 1.0) + vec4(eye * tmin, 0.0) - vec4(-mapSize / 2.0, worldPos.y, -mapSize / 2.0, 0.0);
newpos *= skyBoxRot;
newpos.y *= 4.0;
#if !USE_REFLECTION
reflColor = textureCube(skyCube, newpos.rgb).rgb;
#else
// Interpolate between the sky color and nearby objects.
reflColor = mix(textureCube(skyCube, (vec4(eye, 0.0) * skyBoxRot).xyz).rgb, refTex.rgb, refTex.a);
}
// reflMod is used to reduce the intensity of sky reflections, which otherwise are too extreme.
reflMod = max(refTex.a, 0.75);
#else
reflMod = 0.75;
reflColor = vec3(0.15, 0.7, 0.82);
#endif
#else
// Simplest case for reflection, return a gradient of blue based on Y component.
reflColor = mix(vec3(0.76, 0.84, 0.92), vec3(0.24, 0.43, 0.71), -eye.y);
#endif
losMod = texture2D(losMap, losCoords.st).a;
losMod = losMod < 0.03 ? 0.0 : losMod;
vec3 color;
#if USE_SHADOWS_ON_WATER && USE_SHADOW
float fresShadow = mix(fresnel, fresnel*shadow, 0.05 + murkiness*0.2);
color = mix(refrColor, reflColor, fresShadow);
float fresShadow = mix(fresnel, fresnel * shadow, 0.05 + murkiness * 0.2);
color = mix(refrColor, reflColor, fresShadow * reflMod);
#else
color = mix(refrColor, reflColor, fresnel * reflMod);
#endif
#if USE_SHADOWS_ON_WATER && USE_SHADOW
color += shadow*specular;
color += shadow * specular;
#else
color += specular;
#endif
#if USE_FOG
color = get_fog(color);
#endif
color = get_fog(color);
#if USE_FANCY_EFFECTS
vec4 FoamEffects = texture2D(waterEffectsTexOther, gl_FragCoord.xy/screenSize);
vec3 foam1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).aaa;
vec3 foam2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).aaa;
vec3 foam3 = texture2D(normalMap, normalCoords.st/6.0 - normalCoords.zw * 0.02).aaa;
vec3 foam4 = texture2D(normalMap2, normalCoords.st/6.0 - normalCoords.zw * 0.02).aaa;
vec4 FoamEffects = texture2D(waterEffectsTexOther, gl_FragCoord.xy / screenSize);
vec3 foam1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement * waviness / 10.0) * (baseScale - waviness / wavyEffect)).aaa;
vec3 foam2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement * waviness / 10.0) * (baseScale - waviness / wavyEffect)).aaa;
vec3 foam3 = texture2D(normalMap, normalCoords.st / 6.0 - normalCoords.zw * 0.02).aaa;
vec3 foam4 = texture2D(normalMap2, normalCoords.st / 6.0 - normalCoords.zw * 0.02).aaa;
vec3 foaminterp = mix(foam1, foam2, moddedTime);
foaminterp *= mix(foam3, foam4, moddedTime);
foam1.x = foaminterp.x * WindCosSin.x - foaminterp.z * WindCosSin.y;
color += FoamEffects.r * FoamEffects.a * 0.4 + pow(foam1.x * (5.0 + waviness), 2.6 - waviness / 5.5);
color += FoamEffects.r * FoamEffects.a * 0.4 + pow(foam1.x * (5.0 + waviness), (2.6 - waviness / 5.5));
#endif
float alpha = 1.0;
float alpha = clamp(depth, 0.0, 1.0);
#if !USE_REFRACTION
alpha = 1.4 - extFact;
#endif
#if USE_FANCY_EFFECTS
if (fancyeffects.a < 0.05 && waterDepth < -1.0 )
alpha = 0.0;
alpha = (1.4 - extFact) * alpha;
#endif
gl_FragColor = vec4(color * losMod, alpha);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -1135,7 +1135,9 @@ void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAlign
camera.m_Orientation.Scale(1, -1, 1);
camera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0);
camera.UpdateFrustum(scissor);
camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight));
// Clip slightly above the water to improve reflections of objects on the water
// when the reflections are distorted.
camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight + 2.0f));
SViewPort vp;
vp.m_Height = wm.m_RefTextureSize;

View File

@ -756,8 +756,6 @@ bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGr
if (WaterMgr->m_WaterRefraction)
m->fancyWaterShader->BindTexture(str_refractionMap, WaterMgr->m_RefractionTexture);
if (WaterMgr->m_WaterReflection)
m->fancyWaterShader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube());
m->fancyWaterShader->BindTexture(str_reflectionMap, WaterMgr->m_ReflectionTexture);
m->fancyWaterShader->BindTexture(str_losMap, losTexture.GetTextureSmooth());
@ -767,8 +765,9 @@ bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGr
m->fancyWaterShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());
//TODO: bind only what's needed
if (WaterMgr->m_WaterReflection)
if (WaterMgr->m_WaterRefraction || WaterMgr->m_WaterReflection)
{
m->fancyWaterShader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube());
// TODO: check that this rotates in the right direction.
CMatrix3D skyBoxRotation;
skyBoxRotation.SetIdentity();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -256,7 +256,7 @@ int WaterManager::LoadWaterTextures()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// Create depth textures
glGenTextures(1, &m_ReflFboDepthTexture);